| // Copyright 2014 The Flutter 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'; | |
| import 'dart:io'; | |
| import 'package:file/memory.dart'; | |
| import 'package:flutter_tools/src/base/bot_detector.dart'; | |
| import 'package:flutter_tools/src/base/config.dart'; | |
| import 'package:flutter_tools/src/base/context.dart'; | |
| import 'package:flutter_tools/src/base/file_system.dart'; | |
| import 'package:flutter_tools/src/base/logger.dart'; | |
| import 'package:flutter_tools/src/base/os.dart'; | |
| import 'package:flutter_tools/src/base/process.dart'; | |
| import 'package:flutter_tools/src/base/signals.dart'; | |
| import 'package:flutter_tools/src/base/terminal.dart'; | |
| import 'package:flutter_tools/src/cache.dart'; | |
| import 'package:flutter_tools/src/context_runner.dart'; | |
| import 'package:flutter_tools/src/dart/pub.dart'; | |
| import 'package:flutter_tools/src/globals.dart' as globals; | |
| import 'package:flutter_tools/src/persistent_tool_state.dart'; | |
| import 'package:flutter_tools/src/version.dart'; | |
| import 'package:unified_analytics/unified_analytics.dart'; | |
| import 'context.dart'; | |
| import 'fake_http_client.dart'; | |
| import 'fakes.dart'; | |
| import 'throwing_pub.dart'; | |
| export 'package:flutter_tools/src/base/context.dart' show Generator; | |
| // A default value should be provided if the vast majority of tests should use | |
| // this provider. For example, [BufferLogger], [MemoryFileSystem]. | |
| final _testbedDefaults = <Type, Generator>{ | |
| // Keeps tests fast by avoiding the actual file system. | |
| FileSystem: () => MemoryFileSystem( | |
| style: globals.platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix, | |
| ), | |
| ProcessManager: () => FakeProcessManager.any(), | |
| Logger: () => BufferLogger( | |
| terminal: AnsiTerminal( | |
| stdio: globals.stdio, | |
| platform: globals.platform, | |
| ), // Danger, using real stdio. | |
| outputPreferences: OutputPreferences.test(), | |
| ), // Allows reading logs and prevents stdout. | |
| OperatingSystemUtils: () => FakeOperatingSystemUtils(), | |
| OutputPreferences: () => | |
| OutputPreferences.test(), // configures BufferLogger to avoid color codes. | |
| Analytics: () => const NoOpAnalytics(), | |
| FlutterVersion: () => FakeFlutterVersion(), // prevent requirement to mock git for test runner. | |
| Signals: () => FakeSignals(), // prevent registering actual signal handlers. | |
| Pub: () => const ThrowingPub(), // prevent accidental invocations of pub. | |
| BotDetector: () => const FakeBotDetector(true), | |
| Config: () => Config.test( | |
| name: Config.kFlutterSettings, | |
| directory: globals.fs.systemTempDirectory.createTempSync('flutter_config_dir_test.'), | |
| logger: globals.logger, | |
| ), | |
| PersistentToolState: () => PersistentToolState.test( | |
| directory: globals.fs.systemTempDirectory.createTempSync('flutter_config_dir_test.'), | |
| logger: globals.logger, | |
| ), | |
| }; | |
| /// Manages interaction with the tool injection and runner system. | |
| /// | |
| /// The Testbed automatically injects reasonable defaults through the context | |
| /// DI system such as a [BufferLogger] and a [MemoryFileSystem]. | |
| /// | |
| /// Example: | |
| /// | |
| /// Testing that a filesystem operation works as expected: | |
| /// | |
| /// ```dart | |
| /// void main() { | |
| /// group('Example', () { | |
| /// Testbed testbed; | |
| /// | |
| /// setUp(() { | |
| /// testbed = Testbed(setUp: () { | |
| /// globals.fs.file('foo').createSync() | |
| /// }); | |
| /// }) | |
| /// | |
| /// test('Can delete a file', () => testbed.run(() { | |
| /// expect(globals.fs.file('foo').existsSync(), true); | |
| /// globals.fs.file('foo').deleteSync(); | |
| /// expect(globals.fs.file('foo').existsSync(), false); | |
| /// })); | |
| /// }); | |
| /// } | |
| /// ``` | |
| /// | |
| /// For a more detailed example, see the code in test_compiler_test.dart. | |
| class TestBed { | |
| /// Creates a new [TestBed] | |
| /// | |
| /// `overrides` provides more overrides in addition to the test defaults. | |
| /// `setup` may be provided to apply mocks within the tool managed zone, | |
| /// including any specified overrides. | |
| TestBed({FutureOr<void> Function()? setup, Map<Type, Generator>? overrides}) | |
| : _setup = setup, | |
| _overrides = overrides; | |
| final FutureOr<void> Function()? _setup; | |
| final Map<Type, Generator>? _overrides; | |
| /// Runs `test` within a tool zone. | |
| /// | |
| /// `overrides` may be used to provide new context values for the single test | |
| /// case or override any context values from the setup. | |
| Future<T?> run<T>(FutureOr<T> Function() test, {Map<Type, Generator>? overrides}) { | |
| final testOverrides = <Type, Generator>{ | |
| ..._testbedDefaults, | |
| // Add the initial setUp overrides | |
| ...?_overrides, | |
| // Add the test-specific overrides | |
| ...?overrides, | |
| }; | |
| if (testOverrides.containsKey(ProcessUtils)) { | |
| throw StateError('Do not inject ProcessUtils for testing, use ProcessManager instead.'); | |
| } | |
| // Cache the original flutter root to restore after the test case. | |
| final String? originalFlutterRoot = Cache.flutterRoot; | |
| // Track pending timers to verify that they were correctly cleaned up. | |
| final timers = <Timer, StackTrace>{}; | |
| return HttpOverrides.runZoned(() { | |
| return runInContext<T?>(() { | |
| return context.run<T?>( | |
| name: 'testbed-zone', | |
| zoneSpecification: ZoneSpecification( | |
| createTimer: | |
| ( | |
| Zone self, | |
| ZoneDelegate parent, | |
| Zone zone, | |
| Duration duration, | |
| void Function() timer, | |
| ) { | |
| final Timer result = parent.createTimer(zone, duration, timer); | |
| timers[result] = StackTrace.current; | |
| return result; | |
| }, | |
| createPeriodicTimer: | |
| ( | |
| Zone self, | |
| ZoneDelegate parent, | |
| Zone zone, | |
| Duration period, | |
| void Function(Timer) timer, | |
| ) { | |
| final Timer result = parent.createPeriodicTimer(zone, period, timer); | |
| timers[result] = StackTrace.current; | |
| return result; | |
| }, | |
| ), | |
| body: () async { | |
| Cache.flutterRoot = ''; | |
| if (_setup != null) { | |
| await _setup.call(); | |
| } | |
| await test(); | |
| Cache.flutterRoot = originalFlutterRoot; | |
| for (final MapEntry<Timer, StackTrace> entry in timers.entries) { | |
| if (entry.key.isActive) { | |
| throw StateError('A Timer was active at the end of a test: ${entry.value}'); | |
| } | |
| } | |
| return null; | |
| }, | |
| ); | |
| }, overrides: testOverrides); | |
| }, createHttpClient: (SecurityContext? c) => FakeHttpClient.any()); | |
| } | |
| } |