Unverified Commit 533cd7a6 authored by Zachary Anderson's avatar Zachary Anderson Committed by GitHub

[flutter_tools] Delete system temp entries on fatal signals (#55513)

parent ea0c73c1
......@@ -3,13 +3,19 @@
// found in the LICENSE file.
import 'package:file/file.dart';
import 'package:file/local.dart' as local_fs;
import 'package:meta/meta.dart';
import 'common.dart' show throwToolExit;
import 'io.dart';
import 'platform.dart';
import 'process.dart';
import 'signals.dart';
// package:file/local.dart must not be exported. This exposes LocalFileSystem,
// which we override to ensure that temporary directories are cleaned up when
// the tool is killed by a signal.
export 'package:file/file.dart';
export 'package:file/local.dart';
/// Exception indicating that a file that was expected to exist was not found.
class FileNotFoundException implements IOException {
......@@ -136,11 +142,94 @@ class FileSystemUtils {
/// Return the absolute path of the user's home directory
String get homeDirPath {
String path = _platform.isWindows
? _platform.environment['USERPROFILE']
: _platform.environment['HOME'];
? _platform.environment['USERPROFILE']
: _platform.environment['HOME'];
if (path != null) {
path = _fileSystem.path.absolute(path);
}
return path;
}
}
/// This class extends [local_fs.LocalFileSystem] in order to clean up
/// directories and files that the tool creates under the system temporary
/// directory when the tool exits either normally or when killed by a signal.
class LocalFileSystem extends local_fs.LocalFileSystem {
LocalFileSystem._(Signals signals, List<ProcessSignal> fatalSignals) :
_signals = signals, _fatalSignals = fatalSignals;
@visibleForTesting
LocalFileSystem.test({
@required Signals signals,
List<ProcessSignal> fatalSignals = Signals.defaultExitSignals,
}) : this._(signals, fatalSignals);
// Unless we're in a test of this class's signal hanlding features, we must
// have only one instance created with the singleton LocalSignals instance
// and the catchable signals it considers to be fatal.
static LocalFileSystem _instance;
static LocalFileSystem get instance => _instance ??= LocalFileSystem._(
LocalSignals.instance,
Signals.defaultExitSignals,
);
Directory _systemTemp;
final Map<ProcessSignal, Object> _signalTokens = <ProcessSignal, Object>{};
@visibleForTesting
static Future<void> dispose() => LocalFileSystem.instance?._dispose();
Future<void> _dispose() async {
_tryToDeleteTemp();
for (final MapEntry<ProcessSignal, Object> signalToken in _signalTokens.entries) {
await _signals.removeHandler(signalToken.key, signalToken.value);
}
_signalTokens.clear();
}
final Signals _signals;
final List<ProcessSignal> _fatalSignals;
void _tryToDeleteTemp() {
try {
if (_systemTemp?.existsSync() ?? false) {
_systemTemp.deleteSync(recursive: true);
}
} on FileSystemException {
// ignore.
}
_systemTemp = null;
}
// This getter returns a fresh entry under /tmp, like
// /tmp/flutter_tools.abcxyz, then the rest of the tool creates /tmp entries
// under that, like /tmp/flutter_tools.abcxyz/flutter_build_stuff.123456.
// Right before exiting because of a signal or otherwise, we delete
// /tmp/flutter_tools.abcxyz, not the whole of /tmp.
@override
Directory get systemTempDirectory {
if (_systemTemp == null) {
_systemTemp = super.systemTempDirectory.createTempSync(
'flutter_tools.',
)..createSync(recursive: true);
// Make sure that the temporary directory is cleaned up if the tool is
// killed by a signal.
for (final ProcessSignal signal in _fatalSignals) {
final Object token = _signals.addHandler(
signal,
(ProcessSignal _) {
_tryToDeleteTemp();
},
);
_signalTokens[signal] = token;
}
// Make sure that the temporary directory is cleaned up when the tool
// exits normally.
shutdownHooks?.addShutdownHook(
_tryToDeleteTemp,
ShutdownStage.CLEANUP,
);
}
return _systemTemp;
}
}
......@@ -90,6 +90,7 @@ export 'dart:io'
ProcessStartMode,
// RandomAccessFile NO! Use `file_system.dart`
ServerSocket,
SignalException,
// stderr, NO! Use `io.dart`
// stdin, NO! Use `io.dart`
Stdin,
......
......@@ -4,30 +4,27 @@
import 'dart:async';
import 'package:meta/meta.dart';
import 'async_guard.dart';
import 'context.dart';
import 'io.dart';
typedef SignalHandler = FutureOr<void> Function(ProcessSignal signal);
Signals get signals => Signals.instance;
// The default list of signals that should cause the process to exit.
const List<ProcessSignal> _defaultExitSignals = <ProcessSignal>[
ProcessSignal.SIGTERM,
ProcessSignal.SIGINT,
ProcessSignal.SIGKILL,
];
/// A class that manages signal handlers
///
/// Signal handlers are run in the order that they were added.
abstract class Signals {
factory Signals({
List<ProcessSignal> exitSignals = _defaultExitSignals,
}) => _DefaultSignals._(exitSignals);
@visibleForTesting
factory Signals.test({
List<ProcessSignal> exitSignals = defaultExitSignals,
}) => LocalSignals._(exitSignals);
static Signals get instance => context.get<Signals>();
// The default list of signals that should cause the process to exit.
static const List<ProcessSignal> defaultExitSignals = <ProcessSignal>[
ProcessSignal.SIGTERM,
ProcessSignal.SIGINT,
];
/// Adds a signal handler to run on receipt of signal.
///
......@@ -48,8 +45,17 @@ abstract class Signals {
Stream<Object> get errors;
}
class _DefaultSignals implements Signals {
_DefaultSignals._(this.exitSignals);
/// A class that manages the real dart:io signal handlers.
///
/// We use a singleton instance of this class to ensure that all handlers for
/// fatal signals run before this class calls exit().
class LocalSignals implements Signals {
LocalSignals._(this.exitSignals);
static LocalSignals _instance;
static LocalSignals get instance => _instance ??= LocalSignals._(
Signals.defaultExitSignals,
);
final List<ProcessSignal> exitSignals;
......@@ -84,7 +90,13 @@ class _DefaultSignals implements Signals {
// If we added the first one, then call signal.watch(), listen, and cache
// the stream controller.
if (_handlersList[signal].length == 1) {
_streamSubscriptions[signal] = signal.watch().listen(_handleSignal);
_streamSubscriptions[signal] = signal.watch().listen(
_handleSignal,
onError: (Object e) {
_handlersTable[signal].remove(token);
_handlersList[signal].remove(handler);
},
);
}
return token;
}
......@@ -99,7 +111,10 @@ class _DefaultSignals implements Signals {
if (!_handlersTable[signal].containsKey(token)) {
return false;
}
final SignalHandler handler = _handlersTable[signal][token];
final SignalHandler handler = _handlersTable[signal].remove(token);
if (handler == null) {
return false;
}
final bool removed = _handlersList[signal].remove(handler);
if (!removed) {
return false;
......
......@@ -103,41 +103,50 @@ class AnalyzeOnce extends AnalyzeBase {
experiments: experiments,
);
StreamSubscription<bool> subscription;
subscription = server.onAnalyzing.listen((bool isAnalyzing) {
if (!isAnalyzing) {
analysisCompleter.complete();
subscription?.cancel();
subscription = null;
}
});
server.onErrors.listen((FileAnalysisErrors fileErrors) {
// Record the issues found (but filter out to do comments).
errors.addAll(fileErrors.errors.where((AnalysisError error) => error.type != 'TODO'));
});
await server.start();
// Completing the future in the callback can't fail.
unawaited(server.onExit.then<void>((int exitCode) {
if (!analysisCompleter.isCompleted) {
analysisCompleter.completeError('analysis server exited: $exitCode');
}
}));
Cache.releaseLockEarly();
// collect results
final Stopwatch timer = Stopwatch()..start();
final String message = directories.length > 1
? '${directories.length} ${directories.length == 1 ? 'directory' : 'directories'}'
: fileSystem.path.basename(directories.first);
final Status progress = argResults['preamble'] as bool
? logger.startProgress('Analyzing $message...', timeout: timeoutConfiguration.slowOperation)
: null;
await analysisCompleter.future;
progress?.cancel();
timer.stop();
Stopwatch timer;
Status progress;
try {
StreamSubscription<bool> subscription;
subscription = server.onAnalyzing.listen((bool isAnalyzing) {
if (!isAnalyzing) {
analysisCompleter.complete();
subscription?.cancel();
subscription = null;
}
});
server.onErrors.listen((FileAnalysisErrors fileErrors) {
// Record the issues found (but filter out to do comments).
errors.addAll(fileErrors.errors.where((AnalysisError error) => error.type != 'TODO'));
});
await server.start();
// Completing the future in the callback can't fail.
unawaited(server.onExit.then<void>((int exitCode) {
if (!analysisCompleter.isCompleted) {
analysisCompleter.completeError('analysis server exited: $exitCode');
}
}));
Cache.releaseLockEarly();
// collect results
timer = Stopwatch()..start();
final String message = directories.length > 1
? '${directories.length} ${directories.length == 1 ? 'directory' : 'directories'}'
: fileSystem.path.basename(directories.first);
progress = argResults['preamble'] as bool
? logger.startProgress(
'Analyzing $message...',
timeout: timeoutConfiguration.slowOperation,
)
: null;
await analysisCompleter.future;
} finally {
await server.dispose();
progress?.cancel();
timer?.stop();
}
// count missing dartdocs
final int undocumentedMembers = errors.where((AnalysisError error) {
......
......@@ -17,7 +17,6 @@ import 'base/io.dart';
import 'base/logger.dart';
import 'base/os.dart';
import 'base/process.dart';
import 'base/signals.dart';
import 'base/time.dart';
import 'base/user_messages.dart';
import 'build_system/build_system.dart';
......@@ -182,7 +181,6 @@ Future<T> runInContext<T>(
usage: globals.flutterUsage,
),
ShutdownHooks: () => ShutdownHooks(logger: globals.logger),
Signals: () => Signals(),
Stdio: () => Stdio(),
SystemClock: () => const SystemClock(),
TimeoutConfiguration: () => const TimeoutConfiguration(),
......
......@@ -17,6 +17,7 @@ import 'base/logger.dart';
import 'base/net.dart';
import 'base/os.dart';
import 'base/platform.dart';
import 'base/signals.dart';
import 'base/template.dart';
import 'base/terminal.dart';
import 'base/user_messages.dart';
......@@ -45,20 +46,22 @@ HttpClientFactory get httpClientFactory => context.get<HttpClientFactory>();
Logger get logger => context.get<Logger>();
OperatingSystemUtils get os => context.get<OperatingSystemUtils>();
PersistentToolState get persistentToolState => PersistentToolState.instance;
Signals get signals => context.get<Signals>() ?? LocalSignals.instance;
Usage get flutterUsage => context.get<Usage>();
FlutterProjectFactory get projectFactory => context.get<FlutterProjectFactory>() ?? FlutterProjectFactory(
logger: logger,
fileSystem: fs,
);
const FileSystem _kLocalFs = LocalFileSystem();
FlutterProjectFactory get projectFactory {
return context.get<FlutterProjectFactory>() ?? FlutterProjectFactory(
logger: logger,
fileSystem: fs,
);
}
/// Currently active implementation of the file system.
///
/// By default it uses local disk-based implementation. Override this in tests
/// with [MemoryFileSystem].
FileSystem get fs => ErrorHandlingFileSystem(
delegate: context.get<FileSystem>() ?? _kLocalFs,
delegate: context.get<FileSystem>() ?? LocalFileSystem.instance,
platform: platform,
);
......
......@@ -1295,7 +1295,7 @@ class TerminalHandler {
final Map<io.ProcessSignal, Object> _signalTokens = <io.ProcessSignal, Object>{};
void _addSignalHandler(io.ProcessSignal signal, SignalHandler handler) {
_signalTokens[signal] = signals.addHandler(signal, handler);
_signalTokens[signal] = globals.signals.addHandler(signal, handler);
}
void registerSignalHandlers() {
......@@ -1314,7 +1314,7 @@ class TerminalHandler {
void stop() {
assert(residentRunner.stayResident);
for (final MapEntry<io.ProcessSignal, Object> entry in _signalTokens.entries) {
signals.removeHandler(entry.key, entry.value);
globals.signals.removeHandler(entry.key, entry.value);
}
_signalTokens.clear();
subscription.cancel();
......
......@@ -712,8 +712,8 @@ abstract class FlutterCommand extends Command<void> {
systemClock.now(),
);
};
signals.addHandler(io.ProcessSignal.SIGTERM, handler);
signals.addHandler(io.ProcessSignal.SIGINT, handler);
globals.signals.addHandler(io.ProcessSignal.SIGTERM, handler);
globals.signals.addHandler(io.ProcessSignal.SIGINT, handler);
}
/// Logs data about this command.
......
......@@ -31,7 +31,7 @@ void main() {
setUp(() {
platform = const LocalPlatform();
fileSystem = const LocalFileSystem();
fileSystem = LocalFileSystem.instance;
platform = const LocalPlatform();
processManager = const LocalProcessManager();
terminal = AnsiTerminal(platform: platform, stdio: Stdio());
......
......@@ -76,37 +76,39 @@ sky_engine:$flutterRootUri/bin/cache/pkg/sky_engine/lib/
flutter_project:lib/
''';
fileSystem.file(fileSystem.path.join(projectPath, '.packages'))
..createSync(recursive: true)
..writeAsStringSync(dotPackagesSrc);
..createSync(recursive: true)
..writeAsStringSync(dotPackagesSrc);
}
setUpAll(() {
Cache.disableLocking();
Cache.flutterRoot = FlutterCommandRunner.defaultFlutterRoot;
processManager = const LocalProcessManager();
terminal = AnsiTerminal(platform: platform, stdio: Stdio());
fileSystem = const LocalFileSystem();
platform = const LocalPlatform();
terminal = AnsiTerminal(platform: platform, stdio: Stdio());
fileSystem = LocalFileSystem.instance;
logger = BufferLogger(
outputPreferences: OutputPreferences.test(),
terminal: terminal,
);
analyzerSeparator = platform.isWindows ? '-' : '•';
tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_analyze_once_test_1.').absolute;
});
setUp(() {
tempDir = fileSystem.systemTempDirectory.createTempSync(
'flutter_analyze_once_test_1.',
).absolute;
projectPath = fileSystem.path.join(tempDir.path, 'flutter_project');
fileSystem.file(fileSystem.path.join(projectPath, 'pubspec.yaml'))
..createSync(recursive: true)
..writeAsStringSync(pubspecYamlSrc);
_createDotPackages(projectPath);
});
setUp(() {
libMain = fileSystem.file(fileSystem.path.join(projectPath, 'lib', 'main.dart'))
..createSync(recursive: true)
..writeAsStringSync(mainDartSrc);
});
tearDownAll(() {
tearDown(() {
tryToDelete(tempDir);
});
......
......@@ -226,7 +226,7 @@ void main() {
testWithoutContext('Throws type error if Directory type is set to curentDirectory with LocalFileSystem', () {
final FileSystem fs = ErrorHandlingFileSystem(
delegate: const LocalFileSystem(),
delegate: LocalFileSystem.instance,
platform: const LocalPlatform(),
);
final MockDirectory directory = MockDirectory();
......
......@@ -2,12 +2,18 @@
// 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' as io;
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/signals.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
import '../../src/context.dart';
class MockPlatform extends Mock implements Platform {}
......@@ -121,4 +127,40 @@ void main() {
expect(fsUtils.escapePath(r'foo\cool.dart'), r'foo\cool.dart');
});
});
group('LocalFileSystem', () {
MockIoProcessSignal mockSignal;
ProcessSignal signalUnderTest;
StreamController<io.ProcessSignal> controller;
setUp(() {
mockSignal = MockIoProcessSignal();
signalUnderTest = ProcessSignal(mockSignal);
controller = StreamController<io.ProcessSignal>();
when(mockSignal.watch()).thenAnswer((Invocation invocation) => controller.stream);
});
testUsingContext('deletes system temp entry on a fatal signal', () async {
final Completer<void> completer = Completer<void>();
final Signals signals = Signals.test();
final LocalFileSystem localFileSystem = LocalFileSystem.test(
signals: signals,
fatalSignals: <ProcessSignal>[signalUnderTest],
);
final Directory temp = localFileSystem.systemTempDirectory;
signals.addHandler(signalUnderTest, (ProcessSignal s) {
completer.complete();
});
expect(temp.existsSync(), isTrue);
controller.add(mockSignal);
await completer.future;
expect(temp.existsSync(), isFalse);
});
});
}
class MockIoProcessSignal extends Mock implements io.ProcessSignal {}
......@@ -10,22 +10,23 @@ import 'package:flutter_tools/src/base/signals.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
import '../../src/context.dart';
void main() {
group('Signals', () {
Signals signals;
MockIoProcessSignal mockSignal;
ProcessSignal signalUnderTest;
StreamController<io.ProcessSignal> controller;
setUp(() {
signals = Signals.test();
mockSignal = MockIoProcessSignal();
signalUnderTest = ProcessSignal(mockSignal);
controller = StreamController<io.ProcessSignal>();
when(mockSignal.watch()).thenAnswer((Invocation invocation) => controller.stream);
});
testUsingContext('signal handler runs', () async {
testWithoutContext('signal handler runs', () async {
final Completer<void> completer = Completer<void>();
signals.addHandler(signalUnderTest, (ProcessSignal s) {
expect(s, signalUnderTest);
......@@ -34,11 +35,9 @@ void main() {
controller.add(mockSignal);
await completer.future;
}, overrides: <Type, Generator>{
Signals: () => Signals(),
});
testUsingContext('signal handlers run in order', () async {
testWithoutContext('signal handlers run in order', () async {
final Completer<void> completer = Completer<void>();
bool first = false;
......@@ -56,11 +55,9 @@ void main() {
controller.add(mockSignal);
await completer.future;
}, overrides: <Type, Generator>{
Signals: () => Signals(),
});
testUsingContext('signal handler error goes on error stream', () async {
testWithoutContext('signal handler error goes on error stream', () async {
final Exception exn = Exception('Error');
signals.addHandler(signalUnderTest, (ProcessSignal s) {
throw exn;
......@@ -68,65 +65,71 @@ void main() {
final Completer<void> completer = Completer<void>();
final List<Object> errList = <Object>[];
final StreamSubscription<Object> errSub = signals.errors.listen((Object err) {
errList.add(err);
completer.complete();
});
final StreamSubscription<Object> errSub = signals.errors.listen(
(Object err) {
errList.add(err);
completer.complete();
},
);
controller.add(mockSignal);
await completer.future;
await errSub.cancel();
expect(errList, contains(exn));
}, overrides: <Type, Generator>{
Signals: () => Signals(),
});
testUsingContext('removed signal handler does not run', () async {
final Object token = signals.addHandler(signalUnderTest, (ProcessSignal s) {
fail('Signal handler should have been removed.');
});
testWithoutContext('removed signal handler does not run', () async {
final Object token = signals.addHandler(
signalUnderTest,
(ProcessSignal s) {
fail('Signal handler should have been removed.');
},
);
await signals.removeHandler(signalUnderTest, token);
final List<Object> errList = <Object>[];
final StreamSubscription<Object> errSub = signals.errors.listen((Object err) {
errList.add(err);
});
final StreamSubscription<Object> errSub = signals.errors.listen(
(Object err) {
errList.add(err);
},
);
controller.add(mockSignal);
await errSub.cancel();
expect(errList, isEmpty);
}, overrides: <Type, Generator>{
Signals: () => Signals(),
});
testUsingContext('non-removed signal handler still runs', () async {
testWithoutContext('non-removed signal handler still runs', () async {
final Completer<void> completer = Completer<void>();
signals.addHandler(signalUnderTest, (ProcessSignal s) {
expect(s, signalUnderTest);
completer.complete();
});
final Object token = signals.addHandler(signalUnderTest, (ProcessSignal s) {
fail('Signal handler should have been removed.');
});
final Object token = signals.addHandler(
signalUnderTest,
(ProcessSignal s) {
fail('Signal handler should have been removed.');
},
);
await signals.removeHandler(signalUnderTest, token);
final List<Object> errList = <Object>[];
final StreamSubscription<Object> errSub = signals.errors.listen((Object err) {
errList.add(err);
});
final StreamSubscription<Object> errSub = signals.errors.listen(
(Object err) {
errList.add(err);
},
);
controller.add(mockSignal);
await completer.future;
await errSub.cancel();
expect(errList, isEmpty);
}, overrides: <Type, Generator>{
Signals: () => Signals(),
});
testUsingContext('only handlers for the correct signal run', () async {
testWithoutContext('only handlers for the correct signal run', () async {
final MockIoProcessSignal mockSignal2 = MockIoProcessSignal();
final StreamController<io.ProcessSignal> controller2 = StreamController<io.ProcessSignal>();
final ProcessSignal otherSignal = ProcessSignal(mockSignal2);
......@@ -144,19 +147,22 @@ void main() {
});
final List<Object> errList = <Object>[];
final StreamSubscription<Object> errSub = signals.errors.listen((Object err) {
errList.add(err);
});
final StreamSubscription<Object> errSub = signals.errors.listen(
(Object err) {
errList.add(err);
},
);
controller.add(mockSignal);
await completer.future;
await errSub.cancel();
expect(errList, isEmpty);
}, overrides: <Type, Generator>{
Signals: () => Signals(),
});
testUsingContext('all handlers for exiting signals are run before exit', () async {
testWithoutContext('all handlers for exiting signals are run before exit', () async {
final Signals signals = Signals.test(
exitSignals: <ProcessSignal>[signalUnderTest],
);
final Completer<void> completer = Completer<void>();
bool first = false;
bool second = false;
......@@ -186,8 +192,6 @@ void main() {
controller.add(mockSignal);
await completer.future;
}, overrides: <Type, Generator>{
Signals: () => Signals(exitSignals: <ProcessSignal>[signalUnderTest]),
});
});
}
......
......@@ -53,6 +53,7 @@ void main() {
setUp(() {
mockPlatform = MockPlatform();
when(mockPlatform.isWindows).thenReturn(false);
mockProcessManager = MockProcessManager();
flutterPlatform = TestFlutterPlatform();
});
......
......@@ -134,6 +134,27 @@ void main() {
}
});
test('no unauthorized imports of package:file/local.dart', () {
final List<String> whitelistedPath = <String>[
globals.fs.path.join(flutterTools, 'lib', 'src', 'base', 'file_system.dart'),
];
for (final String dirName in <String>['lib', 'bin', 'test']) {
final Iterable<File> files = globals.fs.directory(globals.fs.path.join(flutterTools, dirName))
.listSync(recursive: true)
.where(_isDartFile)
.where((FileSystemEntity entity) => !whitelistedPath.contains(entity.path))
.map(_asFile);
for (final File file in files) {
for (final String line in file.readAsLinesSync()) {
if (line.startsWith(RegExp(r'import.*package:file/local.dart'))) {
final String relativePath = globals.fs.path.relative(file.path, from:flutterTools);
fail("$relativePath imports 'package:file/local.dart'; use 'lib/src/base/file_system.dart' instead");
}
}
}
}
});
test('no unauthorized imports of dart:convert', () {
final List<String> whitelistedPaths = <String>[
globals.fs.path.join(flutterTools, 'lib', 'src', 'convert.dart'),
......
......@@ -52,7 +52,7 @@ void main() {
stdio: null,
),
);
fileSystem = const LocalFileSystemBlockingSetCurrentDirectory();
fileSystem = LocalFileSystemBlockingSetCurrentDirectory();
processManager = const LocalProcessManager();
parser = PlistParser(
fileSystem: fileSystem,
......
......@@ -450,7 +450,7 @@ class FakeSignals implements Signals {
FakeSignals({
this.subForSigTerm,
List<ProcessSignal> exitSignals,
}) : delegate = Signals(exitSignals: exitSignals);
}) : delegate = Signals.test(exitSignals: exitSignals);
final ProcessSignal subForSigTerm;
final Signals delegate;
......
......@@ -14,7 +14,7 @@ import '../src/common.dart';
const String _kInitialVersion = 'v1.9.1';
const String _kBranch = 'dev';
const FileSystem fileSystem = LocalFileSystem();
final FileSystem fileSystem = LocalFileSystem.instance;
const ProcessManager processManager = LocalProcessManager();
final Stdio stdio = Stdio();
final ProcessUtils processUtils = ProcessUtils(processManager: processManager, logger: StdoutLogger(
......
......@@ -21,7 +21,7 @@ void main() {
FlutterRunTestDriver flutter;
VmService vmService;
setUpAll(() async {
setUp(() async {
tempDir = createResolvedTempDirectorySync('vmservice_integration_test.');
final BasicProject _project = BasicProject();
......@@ -33,7 +33,7 @@ void main() {
vmService = await vmServiceConnectUri('ws://localhost:$port/ws');
});
tearDownAll(() async {
tearDown(() async {
await flutter?.stop();
tryToDelete(tempDir);
});
......
......@@ -20,7 +20,7 @@ import 'package:meta/meta.dart';
import 'package:test_api/test_api.dart' as test_package show TypeMatcher, test; // ignore: deprecated_member_use
import 'package:test_api/test_api.dart' hide TypeMatcher, isInstanceOf; // ignore: deprecated_member_use
// ignore: deprecated_member_use
export 'package:test_core/test_core.dart' hide TypeMatcher, isInstanceOf; // Defines a 'package:test' shim.
export 'package:test_core/test_core.dart' hide TypeMatcher, isInstanceOf, test; // Defines a 'package:test' shim.
/// A matcher that compares the type of the actual value to the type argument T.
// TODO(ianh): Remove this once https://github.com/dart-lang/matcher/issues/98 is fixed
......@@ -31,7 +31,9 @@ void tryToDelete(Directory directory) {
// on Windows it's common for deletions to fail due to
// bogus (we think) "access denied" errors.
try {
directory.deleteSync(recursive: true);
if (directory.existsSync()) {
directory.deleteSync(recursive: true);
}
} on FileSystemException catch (error) {
print('Failed to delete ${directory.path}: $error');
}
......@@ -156,6 +158,35 @@ Matcher containsIgnoringWhitespace(String toSearch) {
);
}
/// The tool overrides `test` to ensure that files created under the
/// system temporary directory are deleted after each test by calling
/// `LocalFileSystem.dispose()`.
@isTest
void test(String description, FutureOr<void> body(), {
String testOn,
Timeout timeout,
dynamic skip,
List<String> tags,
Map<String, dynamic> onPlatform,
int retry,
}) {
test_package.test(
description,
() async {
addTearDown(() async {
await LocalFileSystem.dispose();
});
return body();
},
timeout: timeout,
skip: skip,
tags: tags,
onPlatform: onPlatform,
retry: retry,
testOn: testOn,
);
}
/// Executes a test body in zone that does not allow context-based injection.
///
/// For classes which have been refactored to excluded context-based injection
......@@ -168,12 +199,12 @@ Matcher containsIgnoringWhitespace(String toSearch) {
void testWithoutContext(String description, FutureOr<void> body(), {
String testOn,
Timeout timeout,
bool skip,
dynamic skip,
List<String> tags,
Map<String, dynamic> onPlatform,
int retry,
}) {
return test_package.test(
return test(
description, () async {
return runZoned(body, zoneValues: <Object, Object>{
contextKey: const NoContext(),
......
......@@ -126,7 +126,7 @@ void testUsingContext(
SimControl: () => MockSimControl(),
Usage: () => FakeUsage(),
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(),
FileSystem: () => const LocalFileSystemBlockingSetCurrentDirectory(),
FileSystem: () => LocalFileSystemBlockingSetCurrentDirectory(),
TimeoutConfiguration: () => const TimeoutConfiguration(),
PlistParser: () => FakePlistParser(),
Signals: () => FakeSignals(),
......@@ -443,7 +443,9 @@ class FakePlistParser implements PlistParser {
}
class LocalFileSystemBlockingSetCurrentDirectory extends LocalFileSystem {
const LocalFileSystemBlockingSetCurrentDirectory();
LocalFileSystemBlockingSetCurrentDirectory() : super.test(
signals: LocalSignals.instance,
);
@override
set currentDirectory(dynamic value) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment