context.dart 9.78 KB
Newer Older
1 2 3 4 5 6
// Copyright 2016 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/artifacts.dart';
9
import 'package:flutter_tools/src/base/config.dart';
10
import 'package:flutter_tools/src/base/context.dart';
11
import 'package:flutter_tools/src/base/file_system.dart';
12
import 'package:flutter_tools/src/base/io.dart';
13
import 'package:flutter_tools/src/base/logger.dart';
14
import 'package:flutter_tools/src/base/os.dart';
15
import 'package:flutter_tools/src/base/platform.dart';
16
import 'package:flutter_tools/src/base/port_scanner.dart';
17
import 'package:flutter_tools/src/base/utils.dart';
18
import 'package:flutter_tools/src/cache.dart';
19
import 'package:flutter_tools/src/devfs.dart';
20
import 'package:flutter_tools/src/device.dart';
21 22
import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/ios/mac.dart';
23
import 'package:flutter_tools/src/ios/simulators.dart';
24
import 'package:flutter_tools/src/ios/xcodeproj.dart';
25
import 'package:flutter_tools/src/run_hot.dart';
26
import 'package:flutter_tools/src/usage.dart';
27
import 'package:flutter_tools/src/version.dart';
28
import 'package:mockito/mockito.dart';
29
import 'package:process/process.dart';
30
import 'package:quiver/time.dart';
31 32
import 'package:test/test.dart';

33 34
import 'common.dart';

35 36 37
/// Return the test logger. This assumes that the current Logger is a BufferLogger.
BufferLogger get testLogger => context[Logger];

38 39 40
MockDeviceManager get testDeviceManager => context[DeviceManager];
MockDoctor get testDoctor => context[Doctor];

41 42
typedef dynamic Generator();

43 44 45
typedef void ContextInitializer(AppContext testContext);

void _defaultInitializeContext(AppContext testContext) {
46 47 48 49 50 51 52 53 54 55
  testContext
    ..putIfAbsent(DeviceManager, () => new MockDeviceManager())
    ..putIfAbsent(DevFSConfig, () => new DevFSConfig())
    ..putIfAbsent(Doctor, () => new MockDoctor())
    ..putIfAbsent(HotRunnerConfig, () => new HotRunnerConfig())
    ..putIfAbsent(Cache, () => new Cache())
    ..putIfAbsent(Artifacts, () => new CachedArtifacts())
    ..putIfAbsent(OperatingSystemUtils, () => new MockOperatingSystemUtils())
    ..putIfAbsent(PortScanner, () => new MockPortScanner())
    ..putIfAbsent(Xcode, () => new Xcode())
56
    ..putIfAbsent(XcodeProjectInterpreter, () => new MockXcodeProjectInterpreter())
57 58 59 60 61 62
    ..putIfAbsent(IOSSimulatorUtils, () {
      final MockIOSSimulatorUtils mock = new MockIOSSimulatorUtils();
      when(mock.getAttachedDevices()).thenReturn(<IOSSimulator>[]);
      return mock;
    })
    ..putIfAbsent(SimControl, () => new MockSimControl())
63
    ..putIfAbsent(Usage, () => new MockUsage())
64
    ..putIfAbsent(FlutterVersion, () => new MockFlutterVersion())
65 66
    ..putIfAbsent(Clock, () => const Clock())
    ..putIfAbsent(HttpClient, () => new MockHttpClient());
67 68
}

69 70
void testUsingContext(String description, dynamic testMethod(), {
  Timeout timeout,
71
  Map<Type, Generator> overrides: const <Type, Generator>{},
72
  ContextInitializer initializeContext: _defaultInitializeContext,
73
  String testOn,
74
  bool skip, // should default to `false`, but https://github.com/dart-lang/test/issues/545 doesn't allow this
75
}) {
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90

  // Ensure we don't rely on the default [Config] constructor which will
  // leak a sticky $HOME/.flutter_settings behind!
  Directory configDir;
  tearDown(() {
    configDir?.deleteSync(recursive: true);
    configDir = null;
  });
  Config buildConfig(FileSystem fs) {
    configDir = fs.systemTempDirectory.createTempSync('config-dir');
    final File settingsFile = fs.file(
        fs.path.join(configDir.path, '.flutter_settings'));
    return new Config(settingsFile);
  }

91
  test(description, () async {
92
    final AppContext testContext = new AppContext();
93

94
    // The context always starts with these value since others depend on them.
95
    testContext
96
      ..putIfAbsent(BotDetector, () => const BotDetector())
97
      ..putIfAbsent(Stdio, () => const Stdio())
98 99 100 101
      ..putIfAbsent(Platform, () => const LocalPlatform())
      ..putIfAbsent(FileSystem, () => const LocalFileSystem())
      ..putIfAbsent(ProcessManager, () => const LocalProcessManager())
      ..putIfAbsent(Logger, () => new BufferLogger())
102
      ..putIfAbsent(Config, () => buildConfig(testContext[FileSystem]));
103

104 105 106 107 108
    // Apply the initializer after seeding the base value above.
    initializeContext(testContext);

    final String flutterRoot = getFlutterRoot();

109
    try {
110
      return await testContext.runInZone(() async {
111 112 113 114 115
        // Apply the overrides to the test context in the zone since their
        // instantiation may reference items already stored on the context.
        overrides.forEach((Type type, dynamic value()) {
          context.setVariable(type, value());
        });
116

117
        // Provide a sane default for the flutterRoot directory. Individual
118 119 120
        // tests can override this either in the test or during setup.
        Cache.flutterRoot ??= flutterRoot;

121 122 123 124
        return await testMethod();
      }, onError: (dynamic error, StackTrace stackTrace) {
        _printBufferedErrors(testContext);
        throw error;
125
      });
126
    } catch (error) {
127
      _printBufferedErrors(testContext);
128
      rethrow;
129 130
    }

131
  }, timeout: timeout, testOn: testOn, skip: skip);
132 133
}

134 135 136 137 138 139 140 141 142
void _printBufferedErrors(AppContext testContext) {
  if (testContext[Logger] is BufferLogger) {
    final BufferLogger bufferLogger = testContext[Logger];
    if (bufferLogger.errorText.isNotEmpty)
      print(bufferLogger.errorText);
    bufferLogger.clear();
  }
}

143 144 145 146 147 148 149 150 151 152
class MockPortScanner extends PortScanner {
  static int _nextAvailablePort = 12345;

  @override
  Future<bool> isPortAvailable(int port) async => true;

  @override
  Future<int> findAvailablePort() async => _nextAvailablePort++;
}

153
class MockDeviceManager implements DeviceManager {
154 155
  List<Device> devices = <Device>[];

156 157 158 159 160 161 162 163 164
  String _specifiedDeviceId;

  @override
  String get specifiedDeviceId {
    if (_specifiedDeviceId == null || _specifiedDeviceId == 'all')
      return null;
    return _specifiedDeviceId;
  }

165
  @override
166 167 168
  set specifiedDeviceId(String id) {
    _specifiedDeviceId = id;
  }
169 170

  @override
171 172
  bool get hasSpecifiedDeviceId => specifiedDeviceId != null;

173 174 175 176 177
  @override
  bool get hasSpecifiedAllDevices {
    return _specifiedDeviceId != null && _specifiedDeviceId == 'all';
  }

178
  @override
179
  Stream<Device> getAllConnectedDevices() => new Stream<Device>.fromIterable(devices);
180

181
  @override
182 183 184
  Stream<Device> getDevicesById(String deviceId) {
    return new Stream<Device>.fromIterable(
        devices.where((Device device) => device.id == deviceId));
185 186
  }

187
  @override
188 189 190 191
  Stream<Device> getDevices() {
    return hasSpecifiedDeviceId
        ? getDevicesById(specifiedDeviceId)
        : getAllConnectedDevices();
192 193 194
  }

  void addDevice(Device device) => devices.add(device);
195 196 197 198 199 200

  @override
  bool get canListAnything => true;

  @override
  Future<List<String>> getDeviceDiagnostics() async => <String>[];
201 202
}

203 204 205 206 207
class MockAndroidWorkflowValidator extends AndroidWorkflow {
  @override
  Future<LicensesAccepted> get licensesAccepted async => LicensesAccepted.all;
}

208
class MockDoctor extends Doctor {
209 210 211 212
  // True for testing.
  @override
  bool get canListAnything => true;

213
  // True for testing.
214
  @override
215
  bool get canLaunchAnything => true;
216 217 218 219 220 221 222 223 224 225 226 227 228 229

  @override
  /// Replaces the android workflow with a version that overrides licensesAccepted,
  /// to prevent individual tests from having to mock out the process for
  /// the Doctor.
  List<DoctorValidator> get validators {
    final List<DoctorValidator> superValidators = super.validators;
    return superValidators.map((DoctorValidator v) {
      if (v is AndroidWorkflow) {
        return new MockAndroidWorkflowValidator();
      }
      return v;
    }).toList();
  }
230
}
231 232 233

class MockSimControl extends Mock implements SimControl {
  MockSimControl() {
234
    when(getConnectedDevices()).thenReturn(<SimDevice>[]);
235 236 237
  }
}

238 239 240
class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {
  @override
  List<File> whichAll(String execName) => <File>[];
241 242 243

  @override
  String get name => 'fake OS name and version';
244 245 246

  @override
  String get pathVarSeparator => ';';
247
}
248 249

class MockIOSSimulatorUtils extends Mock implements IOSSimulatorUtils {}
250 251 252 253 254

class MockUsage implements Usage {
  @override
  bool get isFirstRun => false;

255 256 257 258 259 260
  @override
  bool get suppressAnalytics => false;

  @override
  set suppressAnalytics(bool value) { }

261 262 263 264 265 266
  @override
  bool get enabled => true;

  @override
  set enabled(bool value) { }

267 268 269
  @override
  String get clientId => '00000000-0000-4000-0000-000000000000';

270
  @override
271
  void sendCommand(String command, { Map<String, String> parameters }) { }
272 273

  @override
274
  void sendEvent(String category, String parameter, { Map<String, String> parameters }) { }
275

276
  @override
277
  void sendTiming(String category, String variableName, Duration duration, { String label }) { }
278 279 280 281 282 283 284 285 286

  @override
  void sendException(dynamic exception, StackTrace trace) { }

  @override
  Stream<Map<String, dynamic>> get onSend => null;

  @override
  Future<Null> ensureAnalyticsSent() => new Future<Null>.value();
287 288

  @override
289
  void printWelcome() { }
290 291
}

292 293
class MockXcodeProjectInterpreter implements XcodeProjectInterpreter {
  @override
294 295 296 297 298 299 300 301 302 303
  bool get isInstalled => true;

  @override
  String get versionText => 'Xcode 9.2';

  @override
  int get majorVersion => 9;

  @override
  int get minorVersion => 2;
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319

  @override
  Map<String, String> getBuildSettings(String projectPath, String target) {
    return <String, String>{};
  }

  @override
  XcodeProjectInfo getInfo(String projectPath) {
    return new XcodeProjectInfo(
      <String>['Runner'],
      <String>['Debug', 'Release'],
      <String>['Runner'],
    );
  }
}

320
class MockFlutterVersion extends Mock implements FlutterVersion {}
321 322

class MockClock extends Mock implements Clock {}
323 324

class MockHttpClient extends Mock implements HttpClient {}