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

import 'package:fuchsia_remote_debug_protocol/fuchsia_remote_debug_protocol.dart';
6
import 'package:test/fake.dart';
7
import 'package:test/test.dart';
8
import 'package:vm_service/vm_service.dart' as vms;
9 10 11

void main() {
  group('FuchsiaRemoteConnection.connect', () {
12
    late List<FakePortForwarder> forwardedPorts;
13
    List<FakeVmService> fakeVmServices;
14
    late List<Uri> uriConnections;
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

    setUp(() {
      final List<Map<String, dynamic>> flutterViewCannedResponses =
          <Map<String, dynamic>>[
        <String, dynamic>{
          'views': <Map<String, dynamic>>[
            <String, dynamic>{
              'type': 'FlutterView',
              'id': 'flutterView0',
            },
          ],
        },
        <String, dynamic>{
          'views': <Map<String, dynamic>>[
            <String, dynamic>{
              'type': 'FlutterView',
              'id': 'flutterView1',
              'isolate': <String, dynamic>{
                'type': '@Isolate',
                'fixedId': 'true',
                'id': 'isolates/1',
                'name': 'file://flutterBinary1',
                'number': '1',
              },
39
            },
40 41 42 43 44 45 46 47 48 49 50 51 52 53
          ],
        },
        <String, dynamic>{
          'views': <Map<String, dynamic>>[
            <String, dynamic>{
              'type': 'FlutterView',
              'id': 'flutterView2',
              'isolate': <String, dynamic>{
                'type': '@Isolate',
                'fixedId': 'true',
                'id': 'isolates/2',
                'name': 'file://flutterBinary2',
                'number': '2',
              },
54
            },
55 56 57 58
          ],
        },
      ];

59 60
      forwardedPorts = <FakePortForwarder>[];
      fakeVmServices = <FakeVmService>[];
61
      uriConnections = <Uri>[];
62
      Future<vms.VmService> fakeVmConnectionFunction(
63
        Uri uri, {
64
        Duration? timeout,
65
      }) {
66
        return Future<vms.VmService>(() async {
67 68
          final FakeVmService service = FakeVmService();
          fakeVmServices.add(service);
69
          uriConnections.add(uri);
70
          service.flutterListViews = vms.Response.parse(flutterViewCannedResponses[uri.port]);
71
          return service;
72 73 74
        });
      }

75
      fuchsiaVmServiceConnectionFunction = fakeVmConnectionFunction;
76 77 78
    });

    tearDown(() {
79
      /// Most tests will fake out the port forwarding and connection
80 81 82 83
      /// functions.
      restoreFuchsiaPortForwardingFunction();
      restoreVmServiceConnectionFunction();
    });
84

85
    test('end-to-end with one vm connection and flutter view query', () async {
86
      int port = 0;
87
      Future<PortForwarder> fakePortForwardingFunction(
88 89
        String address,
        int remotePort, [
90 91
        String? interface = '',
        String? configFile,
92 93
      ]) {
        return Future<PortForwarder>(() {
94
          final FakePortForwarder pf = FakePortForwarder();
95
          forwardedPorts.add(pf);
96 97
          pf.port = port++;
          pf.remotePort = remotePort;
98 99 100 101
          return pf;
        });
      }

102 103
      fuchsiaPortForwardingFunction = fakePortForwardingFunction;
      final FakeSshCommandRunner fakeRunner = FakeSshCommandRunner();
104
      // Adds some extra junk to make sure the strings will be cleaned up.
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
      fakeRunner.iqueryResponse = <String>[
        '[',
        '   {',
        '     "data_source": "Inspect",',
        '     "metadata": {',
        '       "filename": "fuchsia.inspect.Tree",',
        '       "component_url": "fuchsia-pkg://fuchsia.com/flutter_runner#meta/flutter_runner.cm",',
        '       "timestamp": 12345678901234',
        '     },',
        '     "moniker": "core/session-manager/session/flutter_runner",',
        '     "payload": {',
        '       "root": {',
        '         "vm_service_port": "12345",',
        '         "16859221": {',
        '           "empty_tree": "this semantic tree is empty"',
        '         },',
        '         "build_info": {',
        '           "dart_sdk_git_revision": "77e83fcc14fa94049f363d554579f48fbd6bb7a1",',
        '           "dart_sdk_semantic_version": "2.19.0-317.0.dev",',
        '           "flutter_engine_git_revision": "563b8e830c697a543bf0a8a9f4ae3edfad86ea86",',
        '           "fuchsia_sdk_version": "10.20221018.0.1"',
        '         },',
        '         "vm": {',
        '           "dst_status": 1,',
        '           "get_profile_status": 0,',
        '           "num_get_profile_calls": 1,',
        '           "num_intl_provider_errors": 0,',
        '           "num_on_change_calls": 0,',
        '           "timezone_content_status": 0,',
        '           "tz_data_close_status": -1,',
        '           "tz_data_status": -1',
        '         }',
        '       }',
        '     },',
        '     "version": 1',
        '   }',
        ' ]'
      ];
143 144
      fakeRunner.address = 'fe80::8eae:4cff:fef4:9247';
      fakeRunner.interface = 'eno1';
145

146
      final FuchsiaRemoteConnection connection =
147
          await FuchsiaRemoteConnection.connectWithSshCommandRunner(fakeRunner);
148

149 150
      expect(forwardedPorts.length, 1);
      expect(forwardedPorts[0].remotePort, 12345);
151

152
      // VMs should be accessed via localhost ports given by
153
      // [fakePortForwardingFunction].
154
      expect(uriConnections[0],
155
          Uri(scheme: 'ws', host: '[::1]', port: 0, path: '/ws'));
156 157 158

      final List<FlutterView> views = await connection.getFlutterViews();
      expect(views, isNot(null));
159
      expect(views.length, 1);
160 161 162 163 164 165 166
      // Since name can be null, check for the ID on all of them.
      expect(views[0].id, 'flutterView0');

      expect(views[0].name, equals(null));

      // Ensure the ports are all closed after stop was called.
      await connection.stop();
167
      expect(forwardedPorts[0].stopped, true);
168 169
    });

170
    test('end-to-end with one vm and remote open port', () async {
171
      int port = 0;
172
      Future<PortForwarder> fakePortForwardingFunction(
173 174
        String address,
        int remotePort, [
175 176
        String? interface = '',
        String? configFile,
177 178
      ]) {
        return Future<PortForwarder>(() {
179
          final FakePortForwarder pf = FakePortForwarder();
180
          forwardedPorts.add(pf);
181 182 183
          pf.port = port++;
          pf.remotePort = remotePort;
          pf.openPortAddress = 'fe80::1:2%eno2';
184 185 186 187
          return pf;
        });
      }

188 189
      fuchsiaPortForwardingFunction = fakePortForwardingFunction;
      final FakeSshCommandRunner fakeRunner = FakeSshCommandRunner();
190
      // Adds some extra junk to make sure the strings will be cleaned up.
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
      fakeRunner.iqueryResponse = <String>[
        '[',
        '   {',
        '     "data_source": "Inspect",',
        '     "metadata": {',
        '       "filename": "fuchsia.inspect.Tree",',
        '       "component_url": "fuchsia-pkg://fuchsia.com/flutter_runner#meta/flutter_runner.cm",',
        '       "timestamp": 12345678901234',
        '     },',
        '     "moniker": "core/session-manager/session/flutter_runner",',
        '     "payload": {',
        '       "root": {',
        '         "vm_service_port": "12345",',
        '         "16859221": {',
        '           "empty_tree": "this semantic tree is empty"',
        '         },',
        '         "build_info": {',
        '           "dart_sdk_git_revision": "77e83fcc14fa94049f363d554579f48fbd6bb7a1",',
        '           "dart_sdk_semantic_version": "2.19.0-317.0.dev",',
        '           "flutter_engine_git_revision": "563b8e830c697a543bf0a8a9f4ae3edfad86ea86",',
        '           "fuchsia_sdk_version": "10.20221018.0.1"',
        '         },',
        '         "vm": {',
        '           "dst_status": 1,',
        '           "get_profile_status": 0,',
        '           "num_get_profile_calls": 1,',
        '           "num_intl_provider_errors": 0,',
        '           "num_on_change_calls": 0,',
        '           "timezone_content_status": 0,',
        '           "tz_data_close_status": -1,',
        '           "tz_data_status": -1',
        '         }',
        '       }',
        '     },',
        '     "version": 1',
        '   }',
        ' ]'
      ];
229 230
      fakeRunner.address = 'fe80::8eae:4cff:fef4:9247';
      fakeRunner.interface = 'eno1';
231
      final FuchsiaRemoteConnection connection =
232
          await FuchsiaRemoteConnection.connectWithSshCommandRunner(fakeRunner);
233

234 235
      expect(forwardedPorts.length, 1);
      expect(forwardedPorts[0].remotePort, 12345);
236

237
      // VMs should be accessed via the alternate address given by
238
      // [fakePortForwardingFunction].
239
      expect(uriConnections[0],
240
          Uri(scheme: 'ws', host: '[fe80::1:2%25eno2]', port: 0, path: '/ws'));
241 242 243

      final List<FlutterView> views = await connection.getFlutterViews();
      expect(views, isNot(null));
244
      expect(views.length, 1);
245 246 247 248 249 250 251
      // Since name can be null, check for the ID on all of them.
      expect(views[0].id, 'flutterView0');

      expect(views[0].name, equals(null));

      // Ensure the ports are all closed after stop was called.
      await connection.stop();
252
      expect(forwardedPorts[0].stopped, true);
253 254
    });

255
    test('end-to-end with one vm and ipv4', () async {
256
      int port = 0;
257
      Future<PortForwarder> fakePortForwardingFunction(
258 259
        String address,
        int remotePort, [
260 261
        String? interface = '',
        String? configFile,
262 263
      ]) {
        return Future<PortForwarder>(() {
264
          final FakePortForwarder pf = FakePortForwarder();
265
          forwardedPorts.add(pf);
266 267
          pf.port = port++;
          pf.remotePort = remotePort;
268 269 270 271
          return pf;
        });
      }

272 273
      fuchsiaPortForwardingFunction = fakePortForwardingFunction;
      final FakeSshCommandRunner fakeRunner = FakeSshCommandRunner();
274
      // Adds some extra junk to make sure the strings will be cleaned up.
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
      fakeRunner.iqueryResponse = <String>[
        '[',
        '   {',
        '     "data_source": "Inspect",',
        '     "metadata": {',
        '       "filename": "fuchsia.inspect.Tree",',
        '       "component_url": "fuchsia-pkg://fuchsia.com/flutter_runner#meta/flutter_runner.cm",',
        '       "timestamp": 12345678901234',
        '     },',
        '     "moniker": "core/session-manager/session/flutter_runner",',
        '     "payload": {',
        '       "root": {',
        '         "vm_service_port": "12345",',
        '         "16859221": {',
        '           "empty_tree": "this semantic tree is empty"',
        '         },',
        '         "build_info": {',
        '           "dart_sdk_git_revision": "77e83fcc14fa94049f363d554579f48fbd6bb7a1",',
        '           "dart_sdk_semantic_version": "2.19.0-317.0.dev",',
        '           "flutter_engine_git_revision": "563b8e830c697a543bf0a8a9f4ae3edfad86ea86",',
        '           "fuchsia_sdk_version": "10.20221018.0.1"',
        '         },',
        '         "vm": {',
        '           "dst_status": 1,',
        '           "get_profile_status": 0,',
        '           "num_get_profile_calls": 1,',
        '           "num_intl_provider_errors": 0,',
        '           "num_on_change_calls": 0,',
        '           "timezone_content_status": 0,',
        '           "tz_data_close_status": -1,',
        '           "tz_data_status": -1',
        '         }',
        '       }',
        '     },',
        '     "version": 1',
        '   }',
        ' ]'
      ];
313
      fakeRunner.address = '196.168.1.4';
314 315

      final FuchsiaRemoteConnection connection =
316
          await FuchsiaRemoteConnection.connectWithSshCommandRunner(fakeRunner);
317

318 319
      expect(forwardedPorts.length, 1);
      expect(forwardedPorts[0].remotePort, 12345);
320 321 322

      // VMs should be accessed via the ipv4 loopback.
      expect(uriConnections[0],
323
          Uri(scheme: 'ws', host: '127.0.0.1', port: 0, path: '/ws'));
324

325 326
      final List<FlutterView> views = await connection.getFlutterViews();
      expect(views, isNot(null));
327
      expect(views.length, 1);
328 329 330 331 332 333 334
      // Since name can be null, check for the ID on all of them.
      expect(views[0].id, 'flutterView0');

      expect(views[0].name, equals(null));

      // Ensure the ports are all closed after stop was called.
      await connection.stop();
335
      expect(forwardedPorts[0].stopped, true);
336
    });
337 338

    test('env variable test without remote addr', () async {
339
      Future<void> failingFunction() async {
340 341 342 343
        await FuchsiaRemoteConnection.connect();
      }

      // Should fail as no env variable has been passed.
344 345
      expect(failingFunction,
          throwsA(isA<FuchsiaRemoteConnectionError>()));
346
    });
347 348 349
  });
}

350
class FakeSshCommandRunner extends Fake implements SshCommandRunner {
351
  List<String>? iqueryResponse;
352 353
  @override
  Future<List<String>> run(String command) async {
354 355
    if (command.startsWith('iquery --format json show')) {
      return iqueryResponse!;
356 357 358 359 360
    }
    throw UnimplementedError(command);
  }

  @override
361
  String interface = '';
362 363

  @override
364
  String address = '';
365 366 367 368 369 370 371

  @override
  String get sshConfigPath => '~/.ssh';
}

class FakePortForwarder extends Fake implements PortForwarder {
  @override
372
  int port = 0;
373 374

  @override
375
  int remotePort = 0;
376

377
  @override
378
  String? openPortAddress;
379

380 381 382 383 384 385 386 387 388
  bool stopped = false;
  @override
  Future<void> stop() async {
    stopped = true;
  }
}

class FakeVmService extends Fake implements vms.VmService {
  bool disposed = false;
389
  vms.Response? flutterListViews;
390 391 392 393 394 395 396

  @override
  Future<void> dispose() async {
    disposed = true;
  }

  @override
397
  Future<vms.Response> callMethod(String method, {String? isolateId, Map<String, dynamic>? args}) async {
398
    if (method == '_flutter.listViews') {
399
      return flutterListViews!;
400 401 402 403 404
    }
    throw UnimplementedError(method);
  }

  @override
405
  Future<void> onDone = Future<void>.value();
406 407 408 409 410 411

  @override
  Future<vms.Version> getVersion() async {
    return vms.Version(major: -1, minor: -1);
  }
}