daemon_test.dart 12.4 KB
Newer Older
Devon Carew's avatar
Devon Carew committed
1 2 3 4 5 6
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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/android/android_workflow.dart';
8
import 'package:flutter_tools/src/base/logger.dart';
9
import 'package:flutter_tools/src/commands/daemon.dart';
10
import 'package:flutter_tools/src/fuchsia/fuchsia_workflow.dart';
11
import 'package:flutter_tools/src/globals.dart';
12
import 'package:flutter_tools/src/ios/ios_workflow.dart';
13
import 'package:flutter_tools/src/resident_runner.dart';
Devon Carew's avatar
Devon Carew committed
14

15 16 17
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/mocks.dart';
Devon Carew's avatar
Devon Carew committed
18

19
void main() {
20 21 22 23
  Daemon daemon;
  NotifyingLogger notifyingLogger;

  group('daemon', () {
24
    setUp(() {
25
      notifyingLogger = NotifyingLogger();
26
    });
Devon Carew's avatar
Devon Carew committed
27 28 29 30

    tearDown(() {
      if (daemon != null)
        return daemon.shutdown();
31
      notifyingLogger.dispose();
Devon Carew's avatar
Devon Carew committed
32 33
    });

34
    testUsingContext('daemon.version command should succeed', () async {
35 36 37
      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
      daemon = Daemon(
Devon Carew's avatar
Devon Carew committed
38
        commands.stream,
39
        responses.add,
40
        notifyingLogger: notifyingLogger,
Devon Carew's avatar
Devon Carew committed
41
      );
Ian Hickson's avatar
Ian Hickson committed
42
      commands.add(<String, dynamic>{'id': 0, 'method': 'daemon.version'});
43
      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
Devon Carew's avatar
Devon Carew committed
44 45 46
      expect(response['id'], 0);
      expect(response['result'], isNotEmpty);
      expect(response['result'] is String, true);
47 48
      await responses.close();
      await commands.close();
Devon Carew's avatar
Devon Carew committed
49 50
    });

51
    testUsingContext('printError should send daemon.logMessage event', () async {
52 53 54
      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
      daemon = Daemon(
55 56 57 58 59 60 61
        commands.stream,
        responses.add,
        notifyingLogger: notifyingLogger,
      );
      printError('daemon.logMessage test');
      final Map<String, dynamic> response = await responses.stream.firstWhere((Map<String, dynamic> map) {
        return map['event'] == 'daemon.logMessage' && map['params']['level'] == 'error';
62
      });
63 64
      expect(response['id'], isNull);
      expect(response['event'], 'daemon.logMessage');
65
      final Map<String, String> logMessage = response['params'].cast<String, String>();
66 67
      expect(logMessage['level'], 'error');
      expect(logMessage['message'], 'daemon.logMessage test');
68 69
      await responses.close();
      await commands.close();
70 71
    }, overrides: <Type, Generator>{
      Logger: () => notifyingLogger,
72 73
    });

74
    testUsingContext('printStatus should log to stdout when logToStdout is enabled', () async {
75
      final StringBuffer buffer = StringBuffer();
76

77
      await runZoned<Future<void>>(() async {
78 79 80
        final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
        final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
        daemon = Daemon(
81 82 83 84 85 86 87
          commands.stream,
          responses.add,
          notifyingLogger: notifyingLogger,
          logToStdout: true,
        );
        printStatus('daemon.logMessage test');
        // Service the event loop.
88
        await Future<void>.value();
89
      }, zoneSpecification: ZoneSpecification(print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
90 91 92 93
        buffer.writeln(line);
      }));

      expect(buffer.toString().trim(), 'daemon.logMessage test');
94 95
    }, overrides: <Type, Generator>{
      Logger: () => notifyingLogger,
96 97
    });

98
    testUsingContext('daemon.shutdown command should stop daemon', () async {
99 100 101
      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
      daemon = Daemon(
Devon Carew's avatar
Devon Carew committed
102
        commands.stream,
103
        responses.add,
104
        notifyingLogger: notifyingLogger,
Devon Carew's avatar
Devon Carew committed
105
      );
106
      commands.add(<String, dynamic>{'id': 0, 'method': 'daemon.shutdown'});
107
      return daemon.onExit.then<void>((int code) async {
108
        await commands.close();
Devon Carew's avatar
Devon Carew committed
109 110 111 112
        expect(code, 0);
      });
    });

113
    testUsingContext('app.restart without an appId should report an error', () async {
114
      final DaemonCommand command = DaemonCommand();
115 116
      applyMocksToCommand(command);

117 118 119
      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
      daemon = Daemon(
120
        commands.stream,
121
        responses.add,
122
        daemonCommand: command,
123
        notifyingLogger: notifyingLogger,
124 125
      );

126
      commands.add(<String, dynamic>{'id': 0, 'method': 'app.restart'});
127
      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
128 129
      expect(response['id'], 0);
      expect(response['error'], contains('appId is required'));
130 131
      await responses.close();
      await commands.close();
132 133
    });

134
    testUsingContext('ext.flutter.debugPaint via service extension without an appId should report an error', () async {
135
      final DaemonCommand command = DaemonCommand();
136 137
      applyMocksToCommand(command);

138 139 140
      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
      daemon = Daemon(
141
          commands.stream,
142
          responses.add,
143
          daemonCommand: command,
144
          notifyingLogger: notifyingLogger,
145 146 147 148 149
      );

      commands.add(<String, dynamic>{
        'id': 0,
        'method': 'app.callServiceExtension',
150
        'params': <String, String>{
151 152
          'methodName': 'ext.flutter.debugPaint',
        },
153
      });
154
      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
155 156
      expect(response['id'], 0);
      expect(response['error'], contains('appId is required'));
157 158
      await responses.close();
      await commands.close();
159 160
    });

161
    testUsingContext('app.stop without appId should report an error', () async {
162
      final DaemonCommand command = DaemonCommand();
Devon Carew's avatar
Devon Carew committed
163 164
      applyMocksToCommand(command);

165 166 167
      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
      daemon = Daemon(
Devon Carew's avatar
Devon Carew committed
168
        commands.stream,
169
        responses.add,
170
        daemonCommand: command,
171
        notifyingLogger: notifyingLogger,
Devon Carew's avatar
Devon Carew committed
172 173
      );

174
      commands.add(<String, dynamic>{'id': 0, 'method': 'app.stop'});
175
      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
Devon Carew's avatar
Devon Carew committed
176
      expect(response['id'], 0);
177
      expect(response['error'], contains('appId is required'));
178 179
      await responses.close();
      await commands.close();
Devon Carew's avatar
Devon Carew committed
180
    });
181

182
    testUsingContext('device.getDevices should respond with list', () async {
183 184 185
      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
      daemon = Daemon(
186
        commands.stream,
187
        responses.add,
188
        notifyingLogger: notifyingLogger,
189
      );
190
      commands.add(<String, dynamic>{'id': 0, 'method': 'device.getDevices'});
191
      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
192 193
      expect(response['id'], 0);
      expect(response['result'], isList);
194 195
      await responses.close();
      await commands.close();
196
    });
197

Chris Bracken's avatar
Chris Bracken committed
198
    testUsingContext('device.getDevices reports available devices', () async {
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
      daemon = Daemon(
        commands.stream,
        responses.add,
        notifyingLogger: notifyingLogger,
      );
      final MockPollingDeviceDiscovery discoverer = MockPollingDeviceDiscovery();
      daemon.deviceDomain.addDeviceDiscoverer(discoverer);
      discoverer.addDevice(MockAndroidDevice());
      commands.add(<String, dynamic>{'id': 0, 'method': 'device.getDevices'});
      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
      expect(response['id'], 0);
      final dynamic result = response['result'];
      expect(result, isList);
      expect(result, isNotEmpty);
      await responses.close();
      await commands.close();
    });

219
    testUsingContext('should send device.added event when device is discovered', () async {
220 221 222
      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
      daemon = Daemon(
223 224
          commands.stream,
          responses.add,
225
          notifyingLogger: notifyingLogger,
226 227
      );

228
      final MockPollingDeviceDiscovery discoverer = MockPollingDeviceDiscovery();
229
      daemon.deviceDomain.addDeviceDiscoverer(discoverer);
230
      discoverer.addDevice(MockAndroidDevice());
231

232
      return await responses.stream.skipWhile(_isConnectedEvent).first.then<void>((Map<String, dynamic> response) async {
233 234 235 236 237 238
        expect(response['event'], 'device.added');
        expect(response['params'], isMap);

        final Map<String, dynamic> params = response['params'];
        expect(params['platform'], isNotEmpty); // the mock device has a platform of 'android-arm'

239 240
        await responses.close();
        await commands.close();
241
      });
242
    }, overrides: <Type, Generator>{
243 244
      AndroidWorkflow: () => MockAndroidWorkflow(),
      IOSWorkflow: () => MockIOSWorkflow(),
245
      FuchsiaWorkflow: () => MockFuchsiaWorkflow(),
246
    });
247 248

    testUsingContext('emulator.launch without an emulatorId should report an error', () async {
249
      final DaemonCommand command = DaemonCommand();
250 251
      applyMocksToCommand(command);

252 253 254
      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
      daemon = Daemon(
255 256 257
        commands.stream,
        responses.add,
        daemonCommand: command,
258
        notifyingLogger: notifyingLogger,
259 260
      );

261
      commands.add(<String, dynamic>{'id': 0, 'method': 'emulator.launch'});
262 263 264
      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
      expect(response['id'], 0);
      expect(response['error'], contains('emulatorId is required'));
265 266
      await responses.close();
      await commands.close();
267 268 269
    });

    testUsingContext('emulator.getEmulators should respond with list', () async {
270 271 272
      final StreamController<Map<String, dynamic>> commands = StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = StreamController<Map<String, dynamic>>();
      daemon = Daemon(
273 274
        commands.stream,
        responses.add,
275
        notifyingLogger: notifyingLogger,
276 277 278 279 280
      );
      commands.add(<String, dynamic>{'id': 0, 'method': 'emulator.getEmulators'});
      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
      expect(response['id'], 0);
      expect(response['result'], isList);
281 282
      await responses.close();
      await commands.close();
283
    });
Devon Carew's avatar
Devon Carew committed
284
  });
285 286 287 288 289

  group('daemon serialization', () {
    test('OperationResult', () {
      expect(
        jsonEncodeObject(OperationResult.ok),
290
        '{"code":0,"message":""}',
291 292
      );
      expect(
293
        jsonEncodeObject(OperationResult(1, 'foo')),
294
        '{"code":1,"message":"foo"}',
295 296 297
      );
    });
  });
Devon Carew's avatar
Devon Carew committed
298
}
299 300

bool _notEvent(Map<String, dynamic> map) => map['event'] == null;
301

302 303
bool _isConnectedEvent(Map<String, dynamic> map) => map['event'] == 'daemon.connected';

304 305 306 307 308 309 310
class MockFuchsiaWorkflow extends FuchsiaWorkflow {
  MockFuchsiaWorkflow({ this.canListDevices = true });

  @override
  final bool canListDevices;
}

311
class MockAndroidWorkflow extends AndroidWorkflow {
312
  MockAndroidWorkflow({ this.canListDevices = true });
313

314 315 316 317
  @override
  final bool canListDevices;
}

318
class MockIOSWorkflow extends IOSWorkflow {
319
  MockIOSWorkflow({ this.canListDevices = true });
320

321 322 323
  @override
  final bool canListDevices;
}