fuchsia_remote_connection_test.dart 12.2 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:vm_service/vm_service.dart' as vms;
6
import 'package:test/fake.dart';
7 8 9

import 'package:fuchsia_remote_debug_protocol/fuchsia_remote_debug_protocol.dart';

10 11
import 'common.dart';

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

    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',
              },
41
            },
42 43 44 45 46 47 48 49 50 51 52 53 54 55
          ],
        },
        <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',
              },
56
            },
57 58 59 60
          ],
        },
      ];

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

77
      fuchsiaVmServiceConnectionFunction = fakeVmConnectionFunction;
78 79 80
    });

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

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

104 105
      fuchsiaPortForwardingFunction = fakePortForwardingFunction;
      final FakeSshCommandRunner fakeRunner = FakeSshCommandRunner();
106
      // Adds some extra junk to make sure the strings will be cleaned up.
107 108 109 110
      fakeRunner.findResponse = <String>['/hub/blah/blah/blah/vmservice-port\n'];
      fakeRunner.lsResponse = <String>['123\n\n\n', '456  ', '789'];
      fakeRunner.address = 'fe80::8eae:4cff:fef4:9247';
      fakeRunner.interface = 'eno1';
111

112
      final FuchsiaRemoteConnection connection =
113
          await FuchsiaRemoteConnection.connectWithSshCommandRunner(fakeRunner);
114

115
      // [fakePortForwardingFunction] will have returned three different
116 117 118 119 120 121 122 123 124 125
      // forwarded ports, incrementing the port each time by one. (Just a sanity
      // check that the forwarding port was called).
      expect(forwardedPorts.length, 3);
      expect(forwardedPorts[0].remotePort, 123);
      expect(forwardedPorts[1].remotePort, 456);
      expect(forwardedPorts[2].remotePort, 789);
      expect(forwardedPorts[0].port, 0);
      expect(forwardedPorts[1].port, 1);
      expect(forwardedPorts[2].port, 2);

126
      // VMs should be accessed via localhost ports given by
127
      // [fakePortForwardingFunction].
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
      expect(uriConnections[0],
        Uri(scheme:'ws', host:'[::1]', port:0, path:'/ws'));
      expect(uriConnections[1],
        Uri(scheme:'ws', host:'[::1]', port:1, path:'/ws'));
      expect(uriConnections[2],
        Uri(scheme:'ws', host:'[::1]', port:2, path:'/ws'));

      final List<FlutterView> views = await connection.getFlutterViews();
      expect(views, isNot(null));
      expect(views.length, 3);
      // Since name can be null, check for the ID on all of them.
      expect(views[0].id, 'flutterView0');
      expect(views[1].id, 'flutterView1');
      expect(views[2].id, 'flutterView2');

      expect(views[0].name, equals(null));
      expect(views[1].name, 'file://flutterBinary1');
      expect(views[2].name, 'file://flutterBinary2');

      // Ensure the ports are all closed after stop was called.
      await connection.stop();
149 150 151
      expect(forwardedPorts[0].stopped, true);
      expect(forwardedPorts[1].stopped, true);
      expect(forwardedPorts[2].stopped, true);
152 153 154 155
    });

    test('end-to-end with three vms and remote open port', () async {
      int port = 0;
156
      Future<PortForwarder> fakePortForwardingFunction(
157 158
        String address,
        int remotePort, [
159 160
        String? interface = '',
        String? configFile,
161 162
      ]) {
        return Future<PortForwarder>(() {
163
          final FakePortForwarder pf = FakePortForwarder();
164
          forwardedPorts.add(pf);
165 166 167
          pf.port = port++;
          pf.remotePort = remotePort;
          pf.openPortAddress = 'fe80::1:2%eno2';
168 169 170 171
          return pf;
        });
      }

172 173
      fuchsiaPortForwardingFunction = fakePortForwardingFunction;
      final FakeSshCommandRunner fakeRunner = FakeSshCommandRunner();
174
      // Adds some extra junk to make sure the strings will be cleaned up.
175 176 177 178
      fakeRunner.findResponse = <String>['/hub/blah/blah/blah/vmservice-port\n'];
      fakeRunner.lsResponse = <String>['123\n\n\n', '456  ', '789'];
      fakeRunner.address = 'fe80::8eae:4cff:fef4:9247';
      fakeRunner.interface = 'eno1';
179
      final FuchsiaRemoteConnection connection =
180
          await FuchsiaRemoteConnection.connectWithSshCommandRunner(fakeRunner);
181

182
      // [fakePortForwardingFunction] will have returned three different
183 184 185 186 187 188 189 190 191 192 193
      // forwarded ports, incrementing the port each time by one. (Just a sanity
      // check that the forwarding port was called).
      expect(forwardedPorts.length, 3);
      expect(forwardedPorts[0].remotePort, 123);
      expect(forwardedPorts[1].remotePort, 456);
      expect(forwardedPorts[2].remotePort, 789);
      expect(forwardedPorts[0].port, 0);
      expect(forwardedPorts[1].port, 1);
      expect(forwardedPorts[2].port, 2);

      // VMs should be accessed via the alternate adddress given by
194
      // [fakePortForwardingFunction].
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
      expect(uriConnections[0],
        Uri(scheme:'ws', host:'[fe80::1:2%25eno2]', port:0, path:'/ws'));
      expect(uriConnections[1],
        Uri(scheme:'ws', host:'[fe80::1:2%25eno2]', port:1, path:'/ws'));
      expect(uriConnections[2],
        Uri(scheme:'ws', host:'[fe80::1:2%25eno2]', port:2, path:'/ws'));

      final List<FlutterView> views = await connection.getFlutterViews();
      expect(views, isNot(null));
      expect(views.length, 3);
      // Since name can be null, check for the ID on all of them.
      expect(views[0].id, 'flutterView0');
      expect(views[1].id, 'flutterView1');
      expect(views[2].id, 'flutterView2');

      expect(views[0].name, equals(null));
      expect(views[1].name, 'file://flutterBinary1');
      expect(views[2].name, 'file://flutterBinary2');

      // Ensure the ports are all closed after stop was called.
      await connection.stop();
216 217 218
      expect(forwardedPorts[0].stopped, true);
      expect(forwardedPorts[1].stopped, true);
      expect(forwardedPorts[2].stopped, true);
219 220 221 222
    });

    test('end-to-end with three vms and ipv4', () async {
      int port = 0;
223
      Future<PortForwarder> fakePortForwardingFunction(
224 225
        String address,
        int remotePort, [
226 227
        String? interface = '',
        String? configFile,
228 229
      ]) {
        return Future<PortForwarder>(() {
230
          final FakePortForwarder pf = FakePortForwarder();
231
          forwardedPorts.add(pf);
232 233
          pf.port = port++;
          pf.remotePort = remotePort;
234 235 236 237
          return pf;
        });
      }

238 239
      fuchsiaPortForwardingFunction = fakePortForwardingFunction;
      final FakeSshCommandRunner fakeRunner = FakeSshCommandRunner();
240
      // Adds some extra junk to make sure the strings will be cleaned up.
241 242 243
      fakeRunner.findResponse = <String>['/hub/blah/blah/blah/vmservice-port\n'];
      fakeRunner.lsResponse = <String>['123\n\n\n', '456  ', '789'];
      fakeRunner.address = '196.168.1.4';
244 245

      final FuchsiaRemoteConnection connection =
246
          await FuchsiaRemoteConnection.connectWithSshCommandRunner(fakeRunner);
247

248
      // [fakePortForwardingFunction] will have returned three different
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
      // forwarded ports, incrementing the port each time by one. (Just a sanity
      // check that the forwarding port was called).
      expect(forwardedPorts.length, 3);
      expect(forwardedPorts[0].remotePort, 123);
      expect(forwardedPorts[1].remotePort, 456);
      expect(forwardedPorts[2].remotePort, 789);
      expect(forwardedPorts[0].port, 0);
      expect(forwardedPorts[1].port, 1);
      expect(forwardedPorts[2].port, 2);

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

267 268 269 270 271 272 273 274 275 276 277 278 279 280
      final List<FlutterView> views = await connection.getFlutterViews();
      expect(views, isNot(null));
      expect(views.length, 3);
      // Since name can be null, check for the ID on all of them.
      expect(views[0].id, 'flutterView0');
      expect(views[1].id, 'flutterView1');
      expect(views[2].id, 'flutterView2');

      expect(views[0].name, equals(null));
      expect(views[1].name, 'file://flutterBinary1');
      expect(views[2].name, 'file://flutterBinary2');

      // Ensure the ports are all closed after stop was called.
      await connection.stop();
281 282 283
      expect(forwardedPorts[0].stopped, true);
      expect(forwardedPorts[1].stopped, true);
      expect(forwardedPorts[2].stopped, true);
284
    });
285 286

    test('env variable test without remote addr', () async {
287
      Future<void> failingFunction() async {
288 289 290 291 292
        await FuchsiaRemoteConnection.connect();
      }

      // Should fail as no env variable has been passed.
      expect(failingFunction,
Dan Field's avatar
Dan Field committed
293
          throwsA(isA<FuchsiaRemoteConnectionError>()));
294
    });
295 296 297
  });
}

298
class FakeSshCommandRunner extends Fake implements SshCommandRunner {
299 300
  List<String>? findResponse;
  List<String>? lsResponse;
301 302 303
  @override
  Future<List<String>> run(String command) async {
    if (command.startsWith('/bin/find')) {
304
      return findResponse!;
305 306
    }
    if (command.startsWith('/bin/ls')) {
307
      return lsResponse!;
308 309 310 311 312
    }
    throw UnimplementedError(command);
  }

  @override
313
  String interface = '';
314 315

  @override
316
  String address = '';
317 318 319 320 321 322 323

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

class FakePortForwarder extends Fake implements PortForwarder {
  @override
324
  int port = 0;
325 326

  @override
327
  int remotePort = 0;
328

329
  @override
330
  String? openPortAddress;
331

332 333 334 335 336 337 338 339 340
  bool stopped = false;
  @override
  Future<void> stop() async {
    stopped = true;
  }
}

class FakeVmService extends Fake implements vms.VmService {
  bool disposed = false;
341
  vms.Response? flutterListViews;
342 343 344 345 346 347 348

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

  @override
349
  Future<vms.Response> callMethod(String method, {String? isolateId, Map<String, dynamic>? args}) async {
350
    if (method == '_flutter.listViews') {
351
      return flutterListViews!;
352 353 354 355 356
    }
    throw UnimplementedError(method);
  }

  @override
357
  Future<void> onDone = Future<void>.value();
358 359 360 361 362 363

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