protocol_discovery_test.dart 14.8 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

7
import 'package:flutter_tools/src/device.dart';
8
import 'package:flutter_tools/src/protocol_discovery.dart';
9
import 'package:quiver/testing/async.dart';
10

11 12 13
import '../src/common.dart';
import '../src/context.dart';
import '../src/mocks.dart';
14

15
void main() {
16
  group('service_protocol discovery', () {
17 18 19
    MockDeviceLogReader logReader;
    ProtocolDiscovery discoverer;

20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
    /// Performs test set-up functionality that must be performed as part of
    /// the `test()` pass and not part of the `setUp()` pass.
    ///
    /// This exists to make sure we're not creating an error that tries to
    /// cross an error-zone boundary. Our use of `testUsingContext()` runs the
    /// test code inside an error zone, but the `setUp()` code is not run in
    /// any zone. This creates the potential for errors that try to cross
    /// error-zone boundaries, which are considered uncaught.
    ///
    /// This also exists for cases where our initialization requires access to
    /// a `Context` object, which is only set up inside the zone.
    ///
    /// These issues do not pertain to real code and are a test-only concern,
    /// because in real code, the zone is set up in `main()`.
    ///
    /// See also: [runZoned]
    void initialize({
      int devicePort,
      Duration throttleDuration = const Duration(milliseconds: 200),
    }) {
      logReader = MockDeviceLogReader();
      discoverer = ProtocolDiscovery.observatory(
        logReader,
        ipv6: false,
        hostPort: null,
        devicePort: devicePort,
        throttleDuration: throttleDuration,
      );
    }

    testUsingContext('returns non-null uri future', () async {
      initialize();
      expect(discoverer.uri, isNotNull);
    });
54

55
    group('no port forwarding', () {
56 57 58 59 60
      tearDown(() {
        discoverer.cancel();
        logReader.dispose();
      });

61
      testUsingContext('discovers uri if logs already produced output', () async {
62
        initialize();
63 64 65 66 67
        logReader.addLine('HELLO WORLD');
        logReader.addLine('Observatory listening on http://127.0.0.1:9999');
        final Uri uri = await discoverer.uri;
        expect(uri.port, 9999);
        expect('$uri', 'http://127.0.0.1:9999');
68 69
      });

70
      testUsingContext('discovers uri if logs already produced output and no listener is attached', () async {
71 72 73
        initialize();
        logReader.addLine('HELLO WORLD');
        logReader.addLine('Observatory listening on http://127.0.0.1:9999');
74 75 76

        await Future<void>.delayed(Duration.zero);

77
        final Uri uri = await discoverer.uri;
78
        expect(uri, isNotNull);
79 80 81 82
        expect(uri.port, 9999);
        expect('$uri', 'http://127.0.0.1:9999');
      });

83 84 85 86 87 88 89 90 91
      testUsingContext('uri throws if logs produce bad line and no listener is attached', () async {
        initialize();
        logReader.addLine('Observatory listening on http://127.0.0.1:apple');

        await Future<void>.delayed(Duration.zero);

        expect(discoverer.uri, throwsA(isFormatException));
      });

92 93 94 95 96 97 98 99 100
      testUsingContext('discovers uri if logs not yet produced output', () async {
        initialize();
        final Future<Uri> uriFuture = discoverer.uri;
        logReader.addLine('Observatory listening on http://127.0.0.1:3333');
        final Uri uri = await uriFuture;
        expect(uri.port, 3333);
        expect('$uri', 'http://127.0.0.1:3333');
      });

101
      testUsingContext('discovers uri with Ascii Esc code', () async {
102
        initialize();
103
        logReader.addLine('Observatory listening on http://127.0.0.1:3333\x1b[');
104 105 106 107 108
        final Uri uri = await discoverer.uri;
        expect(uri.port, 3333);
        expect('$uri', 'http://127.0.0.1:3333');
      });

109 110
      testUsingContext('uri throws if logs produce bad line', () async {
        initialize();
111
        logReader.addLine('Observatory listening on http://127.0.0.1:apple');
112 113 114
        expect(discoverer.uri, throwsA(isFormatException));
      });

115 116 117 118 119 120 121 122
      testUsingContext('uri is null when the log reader closes early', () async {
        initialize();
        final Future<Uri> uriFuture = discoverer.uri;
        await logReader.dispose();

        expect(await uriFuture, isNull);
      });

123 124 125 126 127 128
      testUsingContext('uri waits for correct log line', () async {
        initialize();
        final Future<Uri> uriFuture = discoverer.uri;
        logReader.addLine('Observatory not listening...');
        final Uri timeoutUri = Uri.parse('http://timeout');
        final Uri actualUri = await uriFuture.timeout(
129 130 131
          const Duration(milliseconds: 100),
          onTimeout: () => timeoutUri,
        );
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
        expect(actualUri, timeoutUri);
      });

      testUsingContext('discovers uri if log line contains Android prefix', () async {
        initialize();
        logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:52584');
        final Uri uri = await discoverer.uri;
        expect(uri.port, 52584);
        expect('$uri', 'http://127.0.0.1:52584');
      });

      testUsingContext('discovers uri if log line contains auth key', () async {
        initialize();
        final Future<Uri> uriFuture = discoverer.uri;
        logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:54804/PTwjm8Ii8qg=/');
        final Uri uri = await uriFuture;
        expect(uri.port, 54804);
        expect('$uri', 'http://127.0.0.1:54804/PTwjm8Ii8qg=/');
      });

      testUsingContext('discovers uri if log line contains non-localhost', () async {
        initialize();
        final Future<Uri> uriFuture = discoverer.uri;
155
        logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:54804/PTwjm8Ii8qg=/');
156 157
        final Uri uri = await uriFuture;
        expect(uri.port, 54804);
158
        expect('$uri', 'http://127.0.0.1:54804/PTwjm8Ii8qg=/');
159
      });
160 161

      testUsingContext('skips uri if port does not match the requested vmservice - requested last', () async {
162
        initialize(devicePort: 12346);
163 164 165 166 167 168 169 170 171
        final Future<Uri> uriFuture = discoverer.uri;
        logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12345/PTwjm8Ii8qg=/');
        logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/');
        final Uri uri = await uriFuture;
        expect(uri.port, 12346);
        expect('$uri', 'http://127.0.0.1:12346/PTwjm8Ii8qg=/');
      });

      testUsingContext('skips uri if port does not match the requested vmservice - requested first', () async {
172
        initialize(devicePort: 12346);
173 174 175 176 177 178 179
        final Future<Uri> uriFuture = discoverer.uri;
        logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/');
        logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12345/PTwjm8Ii8qg=/');
        final Uri uri = await uriFuture;
        expect(uri.port, 12346);
        expect('$uri', 'http://127.0.0.1:12346/PTwjm8Ii8qg=/');
      });
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259

      testUsingContext('first uri in the stream is the last one from the log', () async {
        initialize();
        logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/');
        logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12345/PTwjm8Ii8qg=/');
        final Uri uri = await discoverer.uris.first;
        expect(uri.port, 12345);
        expect('$uri', 'http://127.0.0.1:12345/PTwjm8Ii8qg=/');
      });

      testUsingContext('first uri in the stream is the last one from the log that matches the port', () async {
        initialize(devicePort: 12345);
        logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/');
        logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12345/PTwjm8Ii8qg=/');
        logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12344/PTwjm8Ii8qg=/');
        final Uri uri = await discoverer.uris.first;
        expect(uri.port, 12345);
        expect('$uri', 'http://127.0.0.1:12345/PTwjm8Ii8qg=/');
      });

      testUsingContext('uris in the stream are throttled', () async {
        const Duration kThrottleDuration = Duration(milliseconds: 10);

        FakeAsync().run((FakeAsync time) {
          initialize(throttleDuration: kThrottleDuration);

          final List<Uri> discoveredUris = <Uri>[];
          discoverer.uris.listen((Uri uri) {
            discoveredUris.add(uri);
          });

          logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/');
          logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12345/PTwjm8Ii8qg=/');

          time.elapse(kThrottleDuration);

          logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12344/PTwjm8Ii8qg=/');
          logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12343/PTwjm8Ii8qg=/');

          time.elapse(kThrottleDuration);

          expect(discoveredUris.length, 2);
          expect(discoveredUris[0].port, 12345);
          expect('${discoveredUris[0]}', 'http://127.0.0.1:12345/PTwjm8Ii8qg=/');
          expect(discoveredUris[1].port, 12343);
          expect('${discoveredUris[1]}', 'http://127.0.0.1:12343/PTwjm8Ii8qg=/');
        });
      });

      testUsingContext('uris in the stream are throttled when they match the port', () async {
        const Duration kThrottleTimeInMilliseconds = Duration(milliseconds: 10);

        FakeAsync().run((FakeAsync time) {
          initialize(
            devicePort: 12345,
            throttleDuration: kThrottleTimeInMilliseconds,
          );

          final List<Uri> discoveredUris = <Uri>[];
          discoverer.uris.listen((Uri uri) {
            discoveredUris.add(uri);
          });

          logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12346/PTwjm8Ii8qg=/');
          logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12345/PTwjm8Ii8qg=/');

          time.elapse(kThrottleTimeInMilliseconds);

          logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12345/PTwjm8Ii8qc=/');
          logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:12344/PTwjm8Ii8qf=/');

          time.elapse(kThrottleTimeInMilliseconds);

          expect(discoveredUris.length, 2);
          expect(discoveredUris[0].port, 12345);
          expect('${discoveredUris[0]}', 'http://127.0.0.1:12345/PTwjm8Ii8qg=/');
          expect(discoveredUris[1].port, 12345);
          expect('${discoveredUris[1]}', 'http://127.0.0.1:12345/PTwjm8Ii8qc=/');
        });
      });
260
    });
261

262 263
    group('port forwarding', () {
      testUsingContext('default port', () async {
264 265
        final MockDeviceLogReader logReader = MockDeviceLogReader();
        final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory(
266
          logReader,
267
          portForwarder: MockPortForwarder(99),
268 269 270
          hostPort: null,
          devicePort: null,
          ipv6: false,
271
        );
272 273 274 275 276

        // Get next port future.
        final Future<Uri> nextUri = discoverer.uri;
        logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:54804/PTwjm8Ii8qg=/');
        final Uri uri = await nextUri;
277 278
        expect(uri.port, 99);
        expect('$uri', 'http://127.0.0.1:99/PTwjm8Ii8qg=/');
279

280
        await discoverer.cancel();
281
        await logReader.dispose();
282 283 284
      });

      testUsingContext('specified port', () async {
285 286
        final MockDeviceLogReader logReader = MockDeviceLogReader();
        final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory(
287
          logReader,
288
          portForwarder: MockPortForwarder(99),
289
          hostPort: 1243,
290 291
          devicePort: null,
          ipv6: false,
292
        );
293 294 295 296 297 298 299 300

        // Get next port future.
        final Future<Uri> nextUri = discoverer.uri;
        logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:54804/PTwjm8Ii8qg=/');
        final Uri uri = await nextUri;
        expect(uri.port, 1243);
        expect('$uri', 'http://127.0.0.1:1243/PTwjm8Ii8qg=/');

301
        await discoverer.cancel();
302
        await logReader.dispose();
303 304
      });

305
      testUsingContext('specified port zero', () async {
306 307
        final MockDeviceLogReader logReader = MockDeviceLogReader();
        final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory(
308
          logReader,
309
          portForwarder: MockPortForwarder(99),
310
          hostPort: 0,
311 312
          devicePort: null,
          ipv6: false,
313 314 315 316 317 318 319 320 321
        );

        // Get next port future.
        final Future<Uri> nextUri = discoverer.uri;
        logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:54804/PTwjm8Ii8qg=/');
        final Uri uri = await nextUri;
        expect(uri.port, 99);
        expect('$uri', 'http://127.0.0.1:99/PTwjm8Ii8qg=/');

322
        await discoverer.cancel();
323
        await logReader.dispose();
324 325
      });

326
      testUsingContext('ipv6', () async {
327 328
        final MockDeviceLogReader logReader = MockDeviceLogReader();
        final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory(
329
          logReader,
330
          portForwarder: MockPortForwarder(99),
331 332
          hostPort: 54777,
          ipv6: true,
333
          devicePort: null,
334
        );
335

336 337 338 339 340 341 342
        // Get next port future.
        final Future<Uri> nextUri = discoverer.uri;
        logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:54804/PTwjm8Ii8qg=/');
        final Uri uri = await nextUri;
        expect(uri.port, 54777);
        expect('$uri', 'http://[::1]:54777/PTwjm8Ii8qg=/');

343
        await discoverer.cancel();
344
        await logReader.dispose();
345
      });
346 347

      testUsingContext('ipv6 with Ascii Escape code', () async {
348 349
        final MockDeviceLogReader logReader = MockDeviceLogReader();
        final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory(
350
          logReader,
351
          portForwarder: MockPortForwarder(99),
352 353
          hostPort: 54777,
          ipv6: true,
354
          devicePort: null,
355 356 357 358 359 360 361 362 363
        );

        // Get next port future.
        final Future<Uri> nextUri = discoverer.uri;
        logReader.addLine('I/flutter : Observatory listening on http://[::1]:54777/PTwjm8Ii8qg=/\x1b[');
        final Uri uri = await nextUri;
        expect(uri.port, 54777);
        expect('$uri', 'http://[::1]:54777/PTwjm8Ii8qg=/');

364
        await discoverer.cancel();
365
        await logReader.dispose();
366
      });
367
    });
368 369
  });
}
370 371 372 373

class MockPortForwarder extends DevicePortForwarder {
  MockPortForwarder([this.availablePort]);

374 375
  final int availablePort;

376
  @override
377
  Future<int> forward(int devicePort, { int hostPort }) async {
378
    hostPort ??= 0;
379
    if (hostPort == 0) {
380
      return availablePort;
381
    }
382 383
    return hostPort;
  }
384 385 386 387 388

  @override
  List<ForwardedPort> get forwardedPorts => throw 'not implemented';

  @override
389
  Future<void> unforward(ForwardedPort forwardedPort) {
390 391
    throw 'not implemented';
  }
392 393 394

  @override
  Future<void> dispose() async {}
395
}