From 5e77d6508e44a282fe2aa3dd1813bc2d9e61555a Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 30 Apr 2019 15:42:16 -0700 Subject: [PATCH] Initial sketch of tools testbed (#31765) --- packages/flutter_tools/test/src/mocks.dart | 34 +++++++- packages/flutter_tools/test/src/testbed.dart | 86 +++++++++++++++++++ .../test/test_compiler_test.dart | 53 ++++++------ .../flutter_tools/test/web/compile_test.dart | 62 +++++++------ 4 files changed, 173 insertions(+), 62 deletions(-) create mode 100644 packages/flutter_tools/test/src/testbed.dart diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart index 70049bc845f..8c318bc8c6d 100644 --- a/packages/flutter_tools/test/src/mocks.dart +++ b/packages/flutter_tools/test/src/mocks.dart @@ -4,7 +4,7 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io' as io show IOSink; +import 'dart:io' as io show IOSink, ProcessSignal; import 'package:flutter_tools/src/android/android_device.dart'; import 'package:flutter_tools/src/android/android_sdk.dart' show AndroidSdk; @@ -196,6 +196,38 @@ class MockProcess extends Mock implements Process { final Stream> stderr; } +/// A fake process implemenation which can be provided all necessary values. +class FakeProcess implements Process { + FakeProcess({ + this.pid = 1, + Future exitCode, + Stream> stdin, + this.stdout = const Stream>.empty(), + this.stderr = const Stream>.empty(), + }) : exitCode = exitCode ?? Future.value(0), + stdin = stdin ?? MemoryIOSink(); + + @override + final int pid; + + @override + final Future exitCode; + + @override + final io.IOSink stdin; + + @override + final Stream> stdout; + + @override + final Stream> stderr; + + @override + bool kill([io.ProcessSignal signal = io.ProcessSignal.sigterm]) { + return true; + } +} + /// A process that prompts the user to proceed, then asynchronously writes /// some lines to stdout before it exits. class PromptingProcess implements Process { diff --git a/packages/flutter_tools/test/src/testbed.dart b/packages/flutter_tools/test/src/testbed.dart new file mode 100644 index 00000000000..9582152d046 --- /dev/null +++ b/packages/flutter_tools/test/src/testbed.dart @@ -0,0 +1,86 @@ +// Copyright 2019 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'; + +import 'package:file/memory.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/cache.dart'; +import 'package:flutter_tools/src/context_runner.dart'; + +export 'package:flutter_tools/src/base/context.dart' show Generator; + +final Map _testbedDefaults = { + FileSystem: () => MemoryFileSystem(), + Logger: () => BufferLogger(), +}; + +/// 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 [MemoryFileSytem]. +/// +/// Example: +/// +/// Testing that a filesystem operation works as expected +/// +/// void main() { +/// group('Example', () { +/// Testbed testbed; +/// +/// setUp(() { +/// testbed = Testbed(setUp: () { +/// fs.file('foo').createSync() +/// }); +/// }) +/// +/// test('Can delete a file', () => testBed.run(() { +/// expect(fs.file('foo').existsSync(), true); +/// fs.file('foo').deleteSync(); +/// expect(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({Future Function() setup, Map overrides}) + : _setup = setup, + _overrides = overrides; + + + final Future Function() _setup; + final Map _overrides; + + /// Runs `test` within a tool zone. + FutureOr run(FutureOr Function() test) { + final Map testOverrides = Map.from(_testbedDefaults); + if (_overrides != null) { + testOverrides.addAll(_overrides); + } + // Cache the original flutter root to restore after the test case. + final String originalFlutterRoot = Cache.flutterRoot; + return runInContext(() { + return context.run( + name: 'testbed', + overrides: testOverrides, + body: () async { + Cache.flutterRoot = ''; + if (_setup != null) { + await _setup(); + } + await test(); + Cache.flutterRoot = originalFlutterRoot; + } + ); + }); + } +} \ No newline at end of file diff --git a/packages/flutter_tools/test/test_compiler_test.dart b/packages/flutter_tools/test/test_compiler_test.dart index 3a3550d33a6..f2fb4b16a2a 100644 --- a/packages/flutter_tools/test/test_compiler_test.dart +++ b/packages/flutter_tools/test/test_compiler_test.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/compile.dart'; import 'package:flutter_tools/src/project.dart'; @@ -10,20 +9,31 @@ import 'package:flutter_tools/src/test/test_compiler.dart'; import 'package:mockito/mockito.dart'; import 'src/common.dart'; -import 'src/context.dart'; +import 'src/testbed.dart'; void main() { - group('TestCompiler', () { - testUsingContext('compiles test file with no errors', () async { - fs.file('pubspec.yaml').createSync(); - fs.file('.packages').createSync(); - fs.file('test/foo.dart').createSync(recursive: true); - final MockResidentCompiler residentCompiler = MockResidentCompiler(); - final TestCompiler testCompiler = FakeTestCompiler( - false, - FlutterProject.current(), - residentCompiler, + group(TestCompiler, () { + Testbed testbed; + FakeTestCompiler testCompiler; + MockResidentCompiler residentCompiler; + + setUp(() { + testbed = Testbed( + setup: () { + fs.file('pubspec.yaml').createSync(); + fs.file('.packages').createSync(); + fs.file('test/foo.dart').createSync(recursive: true); + residentCompiler = MockResidentCompiler(); + testCompiler = FakeTestCompiler( + false, + FlutterProject.current(), + residentCompiler, + ); + }, ); + }); + + test('Reports a dill file when compile is successful', () => testbed.run(() async { when(residentCompiler.recompile( 'test/foo.dart', [Uri.parse('test/foo.dart')], @@ -35,20 +45,9 @@ void main() { expect(await testCompiler.compile('test/foo.dart'), 'test/foo.dart.dill'); expect(fs.file('test/foo.dart.dill').existsSync(), true); - }, overrides: { - FileSystem: () => MemoryFileSystem(), - }); + })); - testUsingContext('does not compile test file with errors', () async { - fs.file('pubspec.yaml').createSync(); - fs.file('.packages').createSync(); - fs.file('test/foo.dart').createSync(recursive: true); - final MockResidentCompiler residentCompiler = MockResidentCompiler(); - final TestCompiler testCompiler = FakeTestCompiler( - false, - FlutterProject.current(), - residentCompiler, - ); + test('Reports null when a compile fails', () => testbed.run(() async { when(residentCompiler.recompile( 'test/foo.dart', [Uri.parse('test/foo.dart')], @@ -60,9 +59,7 @@ void main() { expect(await testCompiler.compile('test/foo.dart'), null); expect(fs.file('test/foo.dart.dill').existsSync(), false); - }, overrides: { - FileSystem: () => MemoryFileSystem(), - }); + })); }); } diff --git a/packages/flutter_tools/test/web/compile_test.dart b/packages/flutter_tools/test/web/compile_test.dart index b7a33c5cc93..905dd83e275 100644 --- a/packages/flutter_tools/test/web/compile_test.dart +++ b/packages/flutter_tools/test/web/compile_test.dart @@ -3,51 +3,47 @@ // found in the LICENSE file. import 'package:flutter_tools/src/artifacts.dart'; -import 'package:flutter_tools/src/base/file_system.dart'; -import 'package:flutter_tools/src/base/io.dart'; -import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/globals.dart'; import 'package:flutter_tools/src/web/compile.dart'; import 'package:mockito/mockito.dart'; import 'package:process/process.dart'; -import '../src/context.dart'; +import '../src/common.dart'; +import '../src/mocks.dart'; +import '../src/testbed.dart'; void main() { - final MockProcessManager mockProcessManager = MockProcessManager(); - final MockProcess mockProcess = MockProcess(); - final BufferLogger mockLogger = BufferLogger(); + group(WebCompiler, () { + MockProcessManager mockProcessManager; + Testbed testBed; - testUsingContext('invokes dart2js with correct arguments', () async { - const WebCompiler webCompiler = WebCompiler(); - final String engineDartPath = artifacts.getArtifactPath(Artifact.engineDartBinary); - final String dart2jsPath = artifacts.getArtifactPath(Artifact.dart2jsSnapshot); - final String flutterWebSdkPath = artifacts.getArtifactPath(Artifact.flutterWebSdk); - final String librariesPath = fs.path.join(flutterWebSdkPath, 'libraries.json'); + setUp(() { + mockProcessManager = MockProcessManager(); + testBed = Testbed(setup: () async { + final String engineDartPath = artifacts.getArtifactPath(Artifact.engineDartBinary); + when(mockProcessManager.start(any)).thenAnswer((Invocation invocation) async => FakeProcess()); + when(mockProcessManager.canRun(engineDartPath)).thenReturn(true); - when(mockProcess.stdout).thenAnswer((Invocation invocation) => const Stream>.empty()); - when(mockProcess.stderr).thenAnswer((Invocation invocation) => const Stream>.empty()); - when(mockProcess.exitCode).thenAnswer((Invocation invocation) async => 0); - when(mockProcessManager.start(any)).thenAnswer((Invocation invocation) async => mockProcess); - when(mockProcessManager.canRun(engineDartPath)).thenReturn(true); + }, overrides: { + ProcessManager: () => mockProcessManager, + }); + }); - await webCompiler.compile(target: 'lib/main.dart'); + test('invokes dart2js with correct arguments', () => testBed.run(() async { + await webCompiler.compile(target: 'lib/main.dart'); - final String outputPath = fs.path.join('build', 'web', 'main.dart.js'); - verify(mockProcessManager.start([ - engineDartPath, - dart2jsPath, - 'lib/main.dart', - '-o', - outputPath, - '--libraries-spec=$librariesPath', - '-m', - ])).called(1); - }, overrides: { - ProcessManager: () => mockProcessManager, - Logger: () => mockLogger, + verify(mockProcessManager.start([ + 'bin/cache/dart-sdk/bin/dart', + 'bin/cache/dart-sdk/bin/snapshots/dart2js.dart.snapshot', + 'lib/main.dart', + '-o', + 'build/web/main.dart.js', + '--libraries-spec=bin/cache/flutter_web_sdk/libraries.json', + '-m', + ])).called(1); + + })); }); } class MockProcessManager extends Mock implements ProcessManager {} -class MockProcess extends Mock implements Process {}