daemon_test.dart 13.3 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/globals.dart';
11
import 'package:flutter_tools/src/ios/ios_workflow.dart';
12
import 'package:flutter_tools/src/resident_runner.dart';
Devon Carew's avatar
Devon Carew committed
13 14
import 'package:test/test.dart';

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

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

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

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

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

50 51 52 53 54 55 56 57 58 59 60
    testUsingContext('printError should send daemon.logMessage event', () async {
      final StreamController<Map<String, dynamic>> commands = new StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = new StreamController<Map<String, dynamic>>();
      daemon = new Daemon(
        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';
61
      });
62 63 64 65 66 67 68 69 70
      expect(response['id'], isNull);
      expect(response['event'], 'daemon.logMessage');
      final Map<String, String> logMessage = response['params'];
      expect(logMessage['level'], 'error');
      expect(logMessage['message'], 'daemon.logMessage test');
      responses.close();
      commands.close();
    }, overrides: <Type, Generator>{
      Logger: () => notifyingLogger,
71 72
    });

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

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

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

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

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

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

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

134
    testUsingContext('app.restart without an appId should report an error', () async {
135
      final DaemonCommand command = new DaemonCommand();
136 137
      applyMocksToCommand(command);

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

      commands.add(<String, dynamic>{ 'id': 0, 'method': 'app.restart' });
148
      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
149 150
      expect(response['id'], 0);
      expect(response['error'], contains('appId is required'));
151 152
      responses.close();
      commands.close();
153 154
    });

155
    testUsingContext('ext.flutter.debugPaint via service extension without an appId should report an error', () async {
156
      final DaemonCommand command = new DaemonCommand();
157 158
      applyMocksToCommand(command);

159 160
      final StreamController<Map<String, dynamic>> commands = new StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = new StreamController<Map<String, dynamic>>();
161 162
      daemon = new Daemon(
          commands.stream,
163
          responses.add,
164 165 166 167 168 169 170 171 172 173 174
          daemonCommand: command,
          notifyingLogger: notifyingLogger
      );

      commands.add(<String, dynamic>{
        'id': 0,
        'method': 'app.callServiceExtension',
        'params': <String, String> {
          'methodName': 'ext.flutter.debugPaint'
        }
      });
175
      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
176 177 178 179 180 181
      expect(response['id'], 0);
      expect(response['error'], contains('appId is required'));
      responses.close();
      commands.close();
    });

182
    testUsingContext('app.stop without appId should report an error', () async {
183
      final DaemonCommand command = new DaemonCommand();
Devon Carew's avatar
Devon Carew committed
184 185
      applyMocksToCommand(command);

186 187
      final StreamController<Map<String, dynamic>> commands = new StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = new StreamController<Map<String, dynamic>>();
Devon Carew's avatar
Devon Carew committed
188 189
      daemon = new Daemon(
        commands.stream,
190
        responses.add,
191
        daemonCommand: command,
192
        notifyingLogger: notifyingLogger
Devon Carew's avatar
Devon Carew committed
193 194
      );

195
      commands.add(<String, dynamic>{ 'id': 0, 'method': 'app.stop' });
196
      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
Devon Carew's avatar
Devon Carew committed
197
      expect(response['id'], 0);
198
      expect(response['error'], contains('appId is required'));
199 200
      responses.close();
      commands.close();
Devon Carew's avatar
Devon Carew committed
201
    });
202

203 204 205 206 207 208 209 210 211
    testUsingContext('daemon should send showMessage on startup if no Android devices are available', () async {
      final StreamController<Map<String, dynamic>> commands = new StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = new StreamController<Map<String, dynamic>>();
      daemon = new Daemon(
          commands.stream,
          responses.add,
          notifyingLogger: notifyingLogger,
      );

212 213
      final Map<String, dynamic> response =
        await responses.stream.skipWhile(_isConnectedEvent).first;
214 215
      expect(response['event'], 'daemon.showMessage');
      expect(response['params'], isMap);
216
      expect(response['params'], containsPair('level', 'warning'));
217 218 219
      expect(response['params'], containsPair('title', 'Unable to list devices'));
      expect(response['params'], containsPair('message', contains('Unable to discover Android devices')));
    }, overrides: <Type, Generator>{
220 221
      AndroidWorkflow: () => new MockAndroidWorkflow(canListDevices: false),
      IOSWorkflow: () => new MockIOSWorkflow(),
222 223 224
    });

    testUsingContext('device.getDevices should respond with list', () async {
225 226
      final StreamController<Map<String, dynamic>> commands = new StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = new StreamController<Map<String, dynamic>>();
227 228
      daemon = new Daemon(
        commands.stream,
229
        responses.add,
230
        notifyingLogger: notifyingLogger
231
      );
232
      commands.add(<String, dynamic>{'id': 0, 'method': 'device.getDevices'});
233
      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
234 235
      expect(response['id'], 0);
      expect(response['result'], isList);
236 237
      responses.close();
      commands.close();
238
    });
239

240
    testUsingContext('should send device.added event when device is discovered', () {
241 242 243 244 245 246 247 248 249 250 251 252
      final StreamController<Map<String, dynamic>> commands = new StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = new StreamController<Map<String, dynamic>>();
      daemon = new Daemon(
          commands.stream,
          responses.add,
          notifyingLogger: notifyingLogger
      );

      final MockPollingDeviceDiscovery discoverer = new MockPollingDeviceDiscovery();
      daemon.deviceDomain.addDeviceDiscoverer(discoverer);
      discoverer.addDevice(new MockAndroidDevice());

253
      return responses.stream.skipWhile(_isConnectedEvent).first.then((Map<String, dynamic> response) {
254 255 256 257 258 259 260 261 262
        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'

        responses.close();
        commands.close();
      });
263
    }, overrides: <Type, Generator>{
264 265
      AndroidWorkflow: () => new MockAndroidWorkflow(),
      IOSWorkflow: () => new MockIOSWorkflow(),
266
    });
267 268 269 270 271 272 273 274 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

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

      final StreamController<Map<String, dynamic>> commands = new StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = new StreamController<Map<String, dynamic>>();
      daemon = new Daemon(
        commands.stream,
        responses.add,
        daemonCommand: command,
        notifyingLogger: notifyingLogger
      );

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

    testUsingContext('emulator.getEmulators should respond with list', () async {
      final StreamController<Map<String, dynamic>> commands = new StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = new StreamController<Map<String, dynamic>>();
      daemon = new Daemon(
        commands.stream,
        responses.add,
        notifyingLogger: notifyingLogger
      );
      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);
      responses.close();
      commands.close();
    });
Devon Carew's avatar
Devon Carew committed
304
  });
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321

  group('daemon serialization', () {
    test('OperationResult', () {
      expect(
        jsonEncodeObject(OperationResult.ok),
        '{"code":0,"message":""}'
      );
      expect(
        jsonEncodeObject(new OperationResult(1, 'foo')),
        '{"code":1,"message":"foo"}'
      );
      expect(
        jsonEncodeObject(new OperationResult(0, 'foo', hintMessage: 'my hint', hintId: 'myId')),
        '{"code":0,"message":"foo","hintMessage":"my hint","hintId":"myId"}'
      );
    });
  });
Devon Carew's avatar
Devon Carew committed
322
}
323 324

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

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

328
class MockAndroidWorkflow extends AndroidWorkflow {
329
  MockAndroidWorkflow({ this.canListDevices = true });
330

331 332 333 334
  @override
  final bool canListDevices;
}

335
class MockIOSWorkflow extends IOSWorkflow {
336
  MockIOSWorkflow({ this.canListDevices =true });
337

338 339 340
  @override
  final bool canListDevices;
}