daemon_test.dart 12.6 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 '../src/common.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
      await responses.close();
      await 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
      expect(response['id'], isNull);
      expect(response['event'], 'daemon.logMessage');
64
      final Map<String, String> logMessage = response['params'].cast<String, String>();
65 66
      expect(logMessage['level'], 'error');
      expect(logMessage['message'], 'daemon.logMessage test');
67 68
      await responses.close();
      await commands.close();
69 70
    }, 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 107
      return daemon.onExit.then<Null>((int code) async {
        await commands.close();
Devon Carew's avatar
Devon Carew committed
108 109 110 111
        expect(code, 0);
      });
    });

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

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

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

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

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

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

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

164 165
      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
166 167
      daemon = new Daemon(
        commands.stream,
168
        responses.add,
169
        daemonCommand: command,
170
        notifyingLogger: notifyingLogger,
Devon Carew's avatar
Devon Carew committed
171 172
      );

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

181 182 183 184 185 186 187 188 189
    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,
      );

190 191
      final Map<String, dynamic> response =
        await responses.stream.skipWhile(_isConnectedEvent).first;
192 193
      expect(response['event'], 'daemon.showMessage');
      expect(response['params'], isMap);
194
      expect(response['params'], containsPair('level', 'warning'));
195 196 197
      expect(response['params'], containsPair('title', 'Unable to list devices'));
      expect(response['params'], containsPair('message', contains('Unable to discover Android devices')));
    }, overrides: <Type, Generator>{
198 199
      AndroidWorkflow: () => new MockAndroidWorkflow(canListDevices: false),
      IOSWorkflow: () => new MockIOSWorkflow(),
200 201 202
    });

    testUsingContext('device.getDevices should respond with list', () async {
203 204
      final StreamController<Map<String, dynamic>> commands = new StreamController<Map<String, dynamic>>();
      final StreamController<Map<String, dynamic>> responses = new StreamController<Map<String, dynamic>>();
205 206
      daemon = new Daemon(
        commands.stream,
207
        responses.add,
208
        notifyingLogger: notifyingLogger
209
      );
210
      commands.add(<String, dynamic>{'id': 0, 'method': 'device.getDevices'});
211
      final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
212 213
      expect(response['id'], 0);
      expect(response['result'], isList);
214 215
      await responses.close();
      await commands.close();
216
    });
217

218
    testUsingContext('should send device.added event when device is discovered', () async {
219 220 221 222 223 224 225 226 227 228 229 230
      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());

231
      return await responses.stream.skipWhile(_isConnectedEvent).first.then((Map<String, dynamic> response) async {
232 233 234 235 236 237
        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'

238 239
        await responses.close();
        await commands.close();
240
      });
241
    }, overrides: <Type, Generator>{
242 243
      AndroidWorkflow: () => new MockAndroidWorkflow(),
      IOSWorkflow: () => new MockIOSWorkflow(),
244
    });
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262

    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'));
263 264
      await responses.close();
      await commands.close();
265 266 267 268 269 270 271 272 273 274 275 276 277 278
    });

    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);
279 280
      await responses.close();
      await commands.close();
281
    });
Devon Carew's avatar
Devon Carew committed
282
  });
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299

  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
300
}
301 302

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

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

306
class MockAndroidWorkflow extends AndroidWorkflow {
307
  MockAndroidWorkflow({ this.canListDevices = true });
308

309 310 311 312
  @override
  final bool canListDevices;
}

313
class MockIOSWorkflow extends IOSWorkflow {
314
  MockIOSWorkflow({ this.canListDevices =true });
315

316 317 318
  @override
  final bool canListDevices;
}