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

5
import 'package:flutter_tools/src/device.dart';
6
import 'package:flutter_tools/src/protocol_discovery.dart';
7
import 'package:fake_async/fake_async.dart';
8

9 10
import '../src/common.dart';
import '../src/context.dart';
11
import '../src/fakes.dart';
12

13
void main() {
14
  group('service_protocol discovery', () {
15
    FakeDeviceLogReader logReader;
16 17
    ProtocolDiscovery discoverer;

18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
    /// 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),
    }) {
38
      logReader = FakeDeviceLogReader();
39 40 41 42 43 44 45 46 47 48 49 50 51
      discoverer = ProtocolDiscovery.observatory(
        logReader,
        ipv6: false,
        hostPort: null,
        devicePort: devicePort,
        throttleDuration: throttleDuration,
      );
    }

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

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

59
      testUsingContext('discovers uri if logs already produced output', () async {
60
        initialize();
61 62 63 64 65
        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');
66 67
      });

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

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

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

81 82 83 84 85 86 87 88 89
      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));
      });

90 91 92 93 94 95 96 97 98
      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');
      });

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

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

113 114 115 116 117 118 119 120
      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);
      });

121 122 123 124 125 126
      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(
127 128 129
          const Duration(milliseconds: 100),
          onTimeout: () => timeoutUri,
        );
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
        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;
153
        logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:54804/PTwjm8Ii8qg=/');
154 155
        final Uri uri = await uriFuture;
        expect(uri.port, 54804);
156
        expect('$uri', 'http://127.0.0.1:54804/PTwjm8Ii8qg=/');
157
      });
158 159

      testUsingContext('skips uri if port does not match the requested vmservice - requested last', () async {
160
        initialize(devicePort: 12346);
161 162 163 164 165 166 167 168 169
        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 {
170
        initialize(devicePort: 12346);
171 172 173 174 175 176 177
        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=/');
      });
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197

      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=/');
      });

198 199 200 201 202 203 204 205 206 207 208 209
      testUsingContext('protocol discovery does not crash if the log reader is closed while delaying', () async {
        initialize(devicePort: 12346, throttleDuration: const Duration(milliseconds: 10));
        final Future<List<Uri>> results = discoverer.uris.toList();
        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:12346/PTwjm8Ii8qg=/');
        await logReader.dispose();

        // Give time for throttle to finish.
        await Future<void>.delayed(const Duration(milliseconds: 11));
        expect(await results, isEmpty);
      });

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 260 261 262 263 264 265 266 267 268 269
      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=/');
        });
      });
270
    });
271

272 273
    group('port forwarding', () {
      testUsingContext('default port', () async {
274
        final FakeDeviceLogReader logReader = FakeDeviceLogReader();
275
        final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory(
276
          logReader,
277
          portForwarder: MockPortForwarder(99),
278 279 280
          hostPort: null,
          devicePort: null,
          ipv6: false,
281
        );
282 283 284 285 286

        // 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;
287 288
        expect(uri.port, 99);
        expect('$uri', 'http://127.0.0.1:99/PTwjm8Ii8qg=/');
289

290
        await discoverer.cancel();
291
        await logReader.dispose();
292 293 294
      });

      testUsingContext('specified port', () async {
295
        final FakeDeviceLogReader logReader = FakeDeviceLogReader();
296
        final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory(
297
          logReader,
298
          portForwarder: MockPortForwarder(99),
299
          hostPort: 1243,
300 301
          devicePort: null,
          ipv6: false,
302
        );
303 304 305 306 307 308 309 310

        // 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=/');

311
        await discoverer.cancel();
312
        await logReader.dispose();
313 314
      });

315
      testUsingContext('specified port zero', () async {
316
        final FakeDeviceLogReader logReader = FakeDeviceLogReader();
317
        final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory(
318
          logReader,
319
          portForwarder: MockPortForwarder(99),
320
          hostPort: 0,
321 322
          devicePort: null,
          ipv6: false,
323 324 325 326 327 328 329 330 331
        );

        // 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=/');

332
        await discoverer.cancel();
333
        await logReader.dispose();
334 335
      });

336
      testUsingContext('ipv6', () async {
337
        final FakeDeviceLogReader logReader = FakeDeviceLogReader();
338
        final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory(
339
          logReader,
340
          portForwarder: MockPortForwarder(99),
341 342
          hostPort: 54777,
          ipv6: true,
343
          devicePort: null,
344
        );
345

346 347 348 349 350 351 352
        // 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=/');

353
        await discoverer.cancel();
354
        await logReader.dispose();
355
      });
356 357

      testUsingContext('ipv6 with Ascii Escape code', () async {
358
        final FakeDeviceLogReader logReader = FakeDeviceLogReader();
359
        final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory(
360
          logReader,
361
          portForwarder: MockPortForwarder(99),
362 363
          hostPort: 54777,
          ipv6: true,
364
          devicePort: null,
365 366 367 368 369 370 371 372 373
        );

        // 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=/');

374
        await discoverer.cancel();
375
        await logReader.dispose();
376
      });
377
    });
378 379
  });
}
380 381 382 383

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

384 385
  final int availablePort;

386
  @override
387
  Future<int> forward(int devicePort, { int hostPort }) async {
388
    hostPort ??= 0;
389
    if (hostPort == 0) {
390
      return availablePort;
391
    }
392 393
    return hostPort;
  }
394 395 396 397 398

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

  @override
399
  Future<void> unforward(ForwardedPort forwardedPort) {
400 401
    throw 'not implemented';
  }
402 403 404

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