Initial sketch of tools testbed (#31765)

This commit is contained in:
Jonah Williams 2019-04-30 15:42:16 -07:00 committed by GitHub
parent 48936d9a95
commit 5e77d6508e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 173 additions and 62 deletions

View file

@ -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<List<int>> stderr;
}
/// A fake process implemenation which can be provided all necessary values.
class FakeProcess implements Process {
FakeProcess({
this.pid = 1,
Future<int> exitCode,
Stream<List<int>> stdin,
this.stdout = const Stream<List<int>>.empty(),
this.stderr = const Stream<List<int>>.empty(),
}) : exitCode = exitCode ?? Future<int>.value(0),
stdin = stdin ?? MemoryIOSink();
@override
final int pid;
@override
final Future<int> exitCode;
@override
final io.IOSink stdin;
@override
final Stream<List<int>> stdout;
@override
final Stream<List<int>> 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 {

View file

@ -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<Type, Generator> _testbedDefaults = <Type, Generator>{
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<void> Function() setup, Map<Type, Generator> overrides})
: _setup = setup,
_overrides = overrides;
final Future<void> Function() _setup;
final Map<Type, Generator> _overrides;
/// Runs `test` within a tool zone.
FutureOr<T> run<T>(FutureOr<T> Function() test) {
final Map<Type, Generator> testOverrides = Map<Type, Generator>.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<T>(() {
return context.run<T>(
name: 'testbed',
overrides: testOverrides,
body: () async {
Cache.flutterRoot = '';
if (_setup != null) {
await _setup();
}
await test();
Cache.flutterRoot = originalFlutterRoot;
}
);
});
}
}

View file

@ -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>[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: <Type, Generator>{
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>[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: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
});
}));
});
}

View file

@ -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<List<int>>.empty());
when(mockProcess.stderr).thenAnswer((Invocation invocation) => const Stream<List<int>>.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: <Type, Generator>{
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(<String>[
engineDartPath,
dart2jsPath,
'lib/main.dart',
'-o',
outputPath,
'--libraries-spec=$librariesPath',
'-m',
])).called(1);
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
Logger: () => mockLogger,
verify(mockProcessManager.start(<String>[
'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 {}