context.dart 10.3 KB
Newer Older
1 2 3 4 5
// 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';
6
import 'dart:io' as io;
7

8
import 'package:flutter_tools/src/android/android_workflow.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/terminal.dart';
16
import 'package:flutter_tools/src/cache.dart';
17
import 'package:flutter_tools/src/context_runner.dart';
18
import 'package:flutter_tools/src/device.dart';
19
import 'package:flutter_tools/src/doctor.dart';
20
import 'package:flutter_tools/src/ios/simulators.dart';
21
import 'package:flutter_tools/src/ios/xcodeproj.dart';
22
import 'package:flutter_tools/src/base/time.dart';
23
import 'package:flutter_tools/src/usage.dart';
24
import 'package:flutter_tools/src/version.dart';
25
import 'package:meta/meta.dart';
26
import 'package:mockito/mockito.dart';
27

28 29
import 'common.dart';

30 31
export 'package:flutter_tools/src/base/context.dart' show Generator;

32
/// Return the test logger. This assumes that the current Logger is a BufferLogger.
33
BufferLogger get testLogger => context.get<Logger>();
34

35 36
MockDeviceManager get testDeviceManager => context.get<DeviceManager>();
MockDoctor get testDoctor => context.get<Doctor>();
37

38
typedef ContextInitializer = void Function(AppContext testContext);
39

40
@isTest
41 42 43
void testUsingContext(
  String description,
  dynamic testMethod(), {
44
  Timeout timeout,
45 46
  Map<Type, Generator> overrides = const <Type, Generator>{},
  bool initializeFlutterRoot = true,
47
  String testOn,
48
  bool skip, // should default to `false`, but https://github.com/dart-lang/test/issues/545 doesn't allow this
49
}) {
50 51 52 53
  // Ensure we don't rely on the default [Config] constructor which will
  // leak a sticky $HOME/.flutter_settings behind!
  Directory configDir;
  tearDown(() {
54 55 56 57
    if (configDir != null) {
      tryToDelete(configDir);
      configDir = null;
    }
58 59
  });
  Config buildConfig(FileSystem fs) {
60
    configDir = fs.systemTempDirectory.createTempSync('flutter_config_dir_test.');
61
    final File settingsFile = fs.file(
62 63
      fs.path.join(configDir.path, '.flutter_settings')
    );
64
    return Config(settingsFile);
65 66
  }

67
  test(description, () async {
68
    await runInContext<dynamic>(() {
69
      return context.run<dynamic>(
70 71 72
        name: 'mocks',
        overrides: <Type, Generator>{
          Config: () => buildConfig(fs),
73 74 75 76
          DeviceManager: () => MockDeviceManager(),
          Doctor: () => MockDoctor(),
          FlutterVersion: () => MockFlutterVersion(),
          HttpClient: () => MockHttpClient(),
77
          IOSSimulatorUtils: () {
78
            final MockIOSSimulatorUtils mock = MockIOSSimulatorUtils();
79 80 81
            when(mock.getAttachedDevices()).thenReturn(<IOSSimulator>[]);
            return mock;
          },
82 83
          OutputPreferences: () => OutputPreferences(showColor: false),
          Logger: () => BufferLogger(),
84 85 86 87
          OperatingSystemUtils: () => MockOperatingSystemUtils(),
          SimControl: () => MockSimControl(),
          Usage: () => MockUsage(),
          XcodeProjectInterpreter: () => MockXcodeProjectInterpreter(),
88
          FileSystem: () => LocalFileSystemBlockingSetCurrentDirectory(),
89
          TimeoutConfiguration: () => const TimeoutConfiguration(),
90 91 92 93
        },
        body: () {
          final String flutterRoot = getFlutterRoot();

94
          return runZoned<Future<dynamic>>(() {
95
            try {
96
              return context.run<dynamic>(
97 98 99 100 101
                // Apply the overrides to the test context in the zone since their
                // instantiation may reference items already stored on the context.
                overrides: overrides,
                name: 'test-specific overrides',
                body: () async {
102 103 104 105 106
                  if (initializeFlutterRoot) {
                    // Provide a sane default for the flutterRoot directory. Individual
                    // tests can override this either in the test or during setup.
                    Cache.flutterRoot ??= flutterRoot;
                  }
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123

                  return await testMethod();
                },
              );
            } catch (error) {
              _printBufferedErrors(context);
              rethrow;
            }
          }, onError: (dynamic error, StackTrace stackTrace) {
            io.stdout.writeln(error);
            io.stdout.writeln(stackTrace);
            _printBufferedErrors(context);
            throw error;
          });
        },
      );
    });
124
  }, timeout: timeout ?? const Timeout(Duration(seconds: 60)),
125
      testOn: testOn, skip: skip);
126 127
}

128
void _printBufferedErrors(AppContext testContext) {
129 130
  if (testContext.get<Logger>() is BufferLogger) {
    final BufferLogger bufferLogger = testContext.get<Logger>();
131 132 133 134 135 136
    if (bufferLogger.errorText.isNotEmpty)
      print(bufferLogger.errorText);
    bufferLogger.clear();
  }
}

137
class MockDeviceManager implements DeviceManager {
138 139
  List<Device> devices = <Device>[];

140 141 142 143 144 145 146 147 148
  String _specifiedDeviceId;

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

149
  @override
150 151 152
  set specifiedDeviceId(String id) {
    _specifiedDeviceId = id;
  }
153 154

  @override
155 156
  bool get hasSpecifiedDeviceId => specifiedDeviceId != null;

157 158 159 160 161
  @override
  bool get hasSpecifiedAllDevices {
    return _specifiedDeviceId != null && _specifiedDeviceId == 'all';
  }

162
  @override
163
  Stream<Device> getAllConnectedDevices() => Stream<Device>.fromIterable(devices);
164

165
  @override
166
  Stream<Device> getDevicesById(String deviceId) {
167
    return Stream<Device>.fromIterable(
168
        devices.where((Device device) => device.id == deviceId));
169 170
  }

171
  @override
172 173 174 175
  Stream<Device> getDevices() {
    return hasSpecifiedDeviceId
        ? getDevicesById(specifiedDeviceId)
        : getAllConnectedDevices();
176 177 178
  }

  void addDevice(Device device) => devices.add(device);
179 180 181 182 183 184

  @override
  bool get canListAnything => true;

  @override
  Future<List<String>> getDeviceDiagnostics() async => <String>[];
185 186 187

  @override
  List<DeviceDiscovery> get deviceDiscoverers => <DeviceDiscovery>[];
188 189
}

190
class MockAndroidLicenseValidator extends AndroidLicenseValidator {
191 192 193 194
  @override
  Future<LicensesAccepted> get licensesAccepted async => LicensesAccepted.all;
}

195
class MockDoctor extends Doctor {
196 197 198 199
  // True for testing.
  @override
  bool get canListAnything => true;

200
  // True for testing.
201
  @override
202
  bool get canLaunchAnything => true;
203 204 205 206 207 208 209

  @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;
210
    return superValidators.map<DoctorValidator>((DoctorValidator v) {
211 212
      if (v is AndroidLicenseValidator) {
        return MockAndroidLicenseValidator();
213 214 215 216
      }
      return v;
    }).toList();
  }
217
}
218 219 220

class MockSimControl extends Mock implements SimControl {
  MockSimControl() {
221
    when(getConnectedDevices()).thenReturn(<SimDevice>[]);
222 223 224
  }
}

225 226 227 228 229 230 231
class MockOperatingSystemUtils implements OperatingSystemUtils {
  @override
  ProcessResult makeExecutable(File file) => null;

  @override
  File which(String execName) => null;

232 233
  @override
  List<File> whichAll(String execName) => <File>[];
234

235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
  @override
  File makePipe(String path) => null;

  @override
  void zip(Directory data, File zipFile) { }

  @override
  void unzip(File file, Directory targetDirectory) { }

  @override
  bool verifyZip(File file) => true;

  @override
  void unpack(File gzippedTarFile, Directory targetDirectory) { }

  @override
  bool verifyGzip(File gzippedFile) => true;

253 254
  @override
  String get name => 'fake OS name and version';
255 256 257

  @override
  String get pathVarSeparator => ';';
258 259 260

  @override
  Future<int> findFreePort({bool ipv6 = false}) async => 12345;
261
}
262 263

class MockIOSSimulatorUtils extends Mock implements IOSSimulatorUtils {}
264 265 266 267 268

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

269 270 271 272 273 274
  @override
  bool get suppressAnalytics => false;

  @override
  set suppressAnalytics(bool value) { }

275 276 277 278 279 280
  @override
  bool get enabled => true;

  @override
  set enabled(bool value) { }

281 282 283
  @override
  String get clientId => '00000000-0000-4000-0000-000000000000';

284
  @override
285
  void sendCommand(String command, { Map<String, String> parameters }) { }
286 287

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

290
  @override
291
  void sendTiming(String category, String variableName, Duration duration, { String label }) { }
292 293 294 295 296 297 298 299

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

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

  @override
300
  Future<void> ensureAnalyticsSent() => Future<void>.value();
301 302

  @override
303
  void printWelcome() { }
304 305
}

306 307
class MockXcodeProjectInterpreter implements XcodeProjectInterpreter {
  @override
308 309 310 311 312 313 314 315 316 317
  bool get isInstalled => true;

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

  @override
  int get majorVersion => 9;

  @override
  int get minorVersion => 2;
318 319 320 321 322 323 324

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

  @override
325
  Future<XcodeProjectInfo> getInfo(String projectPath) async {
326
    return XcodeProjectInfo(
327 328 329 330 331 332 333
      <String>['Runner'],
      <String>['Debug', 'Release'],
      <String>['Runner'],
    );
  }
}

334
class MockFlutterVersion extends Mock implements FlutterVersion {
335 336 337 338
  MockFlutterVersion({bool isStable = false}) : _isStable = isStable;

  final bool _isStable;

339
  @override
340
  bool get isStable => _isStable;
341
}
342

343
class MockClock extends Mock implements SystemClock {}
344 345

class MockHttpClient extends Mock implements HttpClient {}
346 347 348 349 350 351 352 353 354 355

class LocalFileSystemBlockingSetCurrentDirectory extends LocalFileSystem {
  @override
  set currentDirectory(dynamic value) {
    throw 'fs.currentDirectory should not be set on the local file system during '
          'tests as this can cause race conditions with concurrent tests. '
          'Consider using a MemoryFileSystem for testing if possible or refactor '
          'code to not require setting fs.currentDirectory.';
  }
}