Unverified Commit d65c98b4 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] replace some mock file/directories with new op handle (#76268)

parent 34198423
...@@ -14,7 +14,6 @@ import 'package:flutter_tools/src/base/platform.dart'; ...@@ -14,7 +14,6 @@ import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/bundle.dart'; import 'package:flutter_tools/src/bundle.dart';
import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:mockito/mockito.dart';
import '../src/common.dart'; import '../src/common.dart';
import '../src/context.dart'; import '../src/context.dart';
...@@ -180,14 +179,16 @@ flutter: ...@@ -180,14 +179,16 @@ flutter:
}); });
testUsingContext('Failed directory delete shows message', () async { testUsingContext('Failed directory delete shows message', () async {
final MockDirectory mockDirectory = MockDirectory(); final FileExceptionHandler handler = FileExceptionHandler();
when(mockDirectory.existsSync()).thenReturn(true); final FileSystem fileSystem = MemoryFileSystem.test(opHandle: handler.opHandle);
when(mockDirectory.deleteSync(recursive: true)).thenThrow(const FileSystemException('ABCD'));
await writeBundle(mockDirectory, <String, DevFSContent>{}, loggerOverride: testLogger); final Directory directory = fileSystem.directory('foo')
..createSync();
handler.addError(directory, FileSystemOp.delete, const FileSystemException('Expected Error Text'));
verify(mockDirectory.createSync(recursive: true)).called(1); await writeBundle(directory, <String, DevFSContent>{}, loggerOverride: testLogger);
expect(testLogger.errorText, contains('ABCD'));
expect(testLogger.errorText, contains('Expected Error Text'));
}); });
testUsingContext('does not unnecessarily recreate asset manifest, font manifest, license', () async { testUsingContext('does not unnecessarily recreate asset manifest, font manifest, license', () async {
...@@ -450,7 +451,7 @@ flutter: ...@@ -450,7 +451,7 @@ flutter:
'''); ''');
globals.fs.file('assets/foo.txt').createSync(recursive: true); globals.fs.file('assets/foo.txt').createSync(recursive: true);
// Potential build artifacts outisde of build directory. // Potential build artifacts outside of build directory.
globals.fs.file('linux/flutter/foo.txt').createSync(recursive: true); globals.fs.file('linux/flutter/foo.txt').createSync(recursive: true);
globals.fs.file('windows/flutter/foo.txt').createSync(recursive: true); globals.fs.file('windows/flutter/foo.txt').createSync(recursive: true);
globals.fs.file('windows/CMakeLists.txt').createSync(); globals.fs.file('windows/CMakeLists.txt').createSync();
...@@ -468,5 +469,3 @@ flutter: ...@@ -468,5 +469,3 @@ flutter:
Platform: () => FakePlatform(operatingSystem: 'linux'), Platform: () => FakePlatform(operatingSystem: 'linux'),
}); });
} }
class MockDirectory extends Mock implements Directory {}
...@@ -383,25 +383,26 @@ void main() { ...@@ -383,25 +383,26 @@ void main() {
testWithoutContext('If unzip fails, include stderr in exception text', () { testWithoutContext('If unzip fails, include stderr in exception text', () {
const String exceptionMessage = 'Something really bad happened.'; const String exceptionMessage = 'Something really bad happened.';
final FileExceptionHandler handler = FileExceptionHandler();
final FileSystem fileSystem = MemoryFileSystem.test(opHandle: handler.opHandle);
fakeProcessManager.addCommand( fakeProcessManager.addCommand(
const FakeCommand(command: <String>[ const FakeCommand(command: <String>[
'unzip', 'unzip',
'-o', '-o',
'-q', '-q',
null, 'bar.zip',
'-d', '-d',
null, 'foo',
], exitCode: 1, stderr: exceptionMessage), ], exitCode: 1, stderr: exceptionMessage),
); );
final MockFileSystem fileSystem = MockFileSystem(); final Directory foo = fileSystem.directory('foo')
final MockFile mockFile = MockFile(); ..createSync();
final MockDirectory mockDirectory = MockDirectory(); final File bar = fileSystem.file('bar.zip')
when(fileSystem.file(any)).thenReturn(mockFile); ..createSync();
when(mockFile.readAsBytesSync()).thenThrow( handler.addError(bar, FileSystemOp.read, const FileSystemException(exceptionMessage));
const FileSystemException(exceptionMessage),
);
final OperatingSystemUtils osUtils = OperatingSystemUtils( final OperatingSystemUtils osUtils = OperatingSystemUtils(
fileSystem: fileSystem, fileSystem: fileSystem,
logger: BufferLogger.test(), logger: BufferLogger.test(),
...@@ -410,7 +411,7 @@ void main() { ...@@ -410,7 +411,7 @@ void main() {
); );
expect( expect(
() => osUtils.unzip(mockFile, mockDirectory), () => osUtils.unzip(bar, foo),
throwsProcessException(message: exceptionMessage), throwsProcessException(message: exceptionMessage),
); );
}); });
...@@ -470,6 +471,3 @@ void main() { ...@@ -470,6 +471,3 @@ void main() {
} }
class MockProcessManager extends Mock implements ProcessManager {} class MockProcessManager extends Mock implements ProcessManager {}
class MockDirectory extends Mock implements Directory {}
class MockFileSystem extends Mock implements FileSystem {}
class MockFile extends Mock implements File {}
...@@ -11,7 +11,6 @@ import 'package:file_testing/file_testing.dart'; ...@@ -11,7 +11,6 @@ import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_system/file_store.dart'; import 'package:flutter_tools/src/build_system/file_store.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart'; import '../../src/common.dart';
...@@ -143,14 +142,16 @@ void main() { ...@@ -143,14 +142,16 @@ void main() {
}); });
testWithoutContext('FileStore handles failure to persist file cache', () async { testWithoutContext('FileStore handles failure to persist file cache', () async {
final MockFile mockFile = MockFile(); final FileExceptionHandler handler = FileExceptionHandler();
final FileSystem fileSystem = MemoryFileSystem.test(opHandle: handler.opHandle);
final BufferLogger logger = BufferLogger.test(); final BufferLogger logger = BufferLogger.test();
when(mockFile.writeAsBytesSync(any)).thenThrow(const FileSystemException('Out of space!'));
when(mockFile.readAsBytesSync()).thenReturn(Uint8List(0)); final File cacheFile = fileSystem.file('foo')
when(mockFile.existsSync()).thenReturn(true); ..createSync();
handler.addError(cacheFile, FileSystemOp.write, const FileSystemException('Out of space!'));
final FileStore fileCache = FileStore( final FileStore fileCache = FileStore(
cacheFile: mockFile, cacheFile: cacheFile,
logger: logger, logger: logger,
); );
...@@ -161,13 +162,16 @@ void main() { ...@@ -161,13 +162,16 @@ void main() {
}); });
testWithoutContext('FileStore handles failure to restore file cache', () async { testWithoutContext('FileStore handles failure to restore file cache', () async {
final MockFile mockFile = MockFile(); final FileExceptionHandler handler = FileExceptionHandler();
final FileSystem fileSystem = MemoryFileSystem.test(opHandle: handler.opHandle);
final BufferLogger logger = BufferLogger.test(); final BufferLogger logger = BufferLogger.test();
when(mockFile.readAsBytesSync()).thenThrow(const FileSystemException('Out of space!'));
when(mockFile.existsSync()).thenReturn(true); final File cacheFile = fileSystem.file('foo')
..createSync();
handler.addError(cacheFile, FileSystemOp.read, const FileSystemException('Out of space!'));
final FileStore fileCache = FileStore( final FileStore fileCache = FileStore(
cacheFile: mockFile, cacheFile: cacheFile,
logger: logger, logger: logger,
); );
...@@ -198,5 +202,3 @@ void main() { ...@@ -198,5 +202,3 @@ void main() {
expect(fileCache.currentAssetKeys['foo.dart'], '5d41402abc4b2a76b9719d911017c592'); expect(fileCache.currentAssetKeys['foo.dart'], '5d41402abc4b2a76b9719d911017c592');
}); });
} }
class MockFile extends Mock implements File {}
...@@ -15,7 +15,6 @@ import 'package:flutter_tools/src/base/platform.dart'; ...@@ -15,7 +15,6 @@ import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/dart/pub.dart'; import 'package:flutter_tools/src/dart/pub.dart';
import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
import 'package:fake_async/fake_async.dart'; import 'package:fake_async/fake_async.dart';
...@@ -28,10 +27,6 @@ void main() { ...@@ -28,10 +27,6 @@ void main() {
Cache.flutterRoot = ''; Cache.flutterRoot = '';
}); });
tearDown(() {
MockDirectory.findCache = false;
});
testWithoutContext('checkUpToDate skips pub get if the package config is newer than the pubspec ' testWithoutContext('checkUpToDate skips pub get if the package config is newer than the pubspec '
'and the current framework version is the same as the last version', () async { 'and the current framework version is the same as the last version', () async {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[]); final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[]);
...@@ -300,8 +295,9 @@ void main() { ...@@ -300,8 +295,9 @@ void main() {
final MockProcessManager processMock = MockProcessManager(69); final MockProcessManager processMock = MockProcessManager(69);
final BufferLogger logger = BufferLogger.test(); final BufferLogger logger = BufferLogger.test();
final FileSystem fileSystem = MemoryFileSystem.test();
final Pub pub = Pub( final Pub pub = Pub(
fileSystem: MockFileSystem(), fileSystem: fileSystem,
logger: logger, logger: logger,
processManager: processMock, processManager: processMock,
usage: TestUsage(), usage: TestUsage(),
...@@ -370,9 +366,10 @@ void main() { ...@@ -370,9 +366,10 @@ void main() {
testWithoutContext('pub get 66 shows message from pub', () async { testWithoutContext('pub get 66 shows message from pub', () async {
final BufferLogger logger = BufferLogger.test(); final BufferLogger logger = BufferLogger.test();
final FileSystem fileSystem = MemoryFileSystem.test();
final Pub pub = Pub( final Pub pub = Pub(
platform: FakePlatform(environment: const <String, String>{}), platform: FakePlatform(environment: const <String, String>{}),
fileSystem: MockFileSystem(), fileSystem: fileSystem,
logger: logger, logger: logger,
usage: TestUsage(), usage: TestUsage(),
botDetector: const BotDetectorAlwaysNo(), botDetector: const BotDetectorAlwaysNo(),
...@@ -400,18 +397,19 @@ void main() { ...@@ -400,18 +397,19 @@ void main() {
testWithoutContext('pub cache in root is used', () async { testWithoutContext('pub cache in root is used', () async {
String error; String error;
final MockProcessManager processMock = MockProcessManager(69); final MockProcessManager processMock = MockProcessManager(69);
final MockFileSystem fsMock = MockFileSystem(); final FileSystem fileSystem = MemoryFileSystem.test();
fileSystem.directory(Cache.flutterRoot).childDirectory('.pub-cache').createSync();
final Pub pub = Pub( final Pub pub = Pub(
platform: FakePlatform(environment: const <String, String>{}), platform: FakePlatform(environment: const <String, String>{}),
usage: TestUsage(), usage: TestUsage(),
fileSystem: fsMock, fileSystem: fileSystem,
logger: BufferLogger.test(), logger: BufferLogger.test(),
processManager: processMock, processManager: processMock,
botDetector: const BotDetectorAlwaysNo(), botDetector: const BotDetectorAlwaysNo(),
); );
FakeAsync().run((FakeAsync time) { FakeAsync().run((FakeAsync time) {
MockDirectory.findCache = true;
expect(processMock.lastPubEnvironment, isNull); expect(processMock.lastPubEnvironment, isNull);
expect(processMock.lastPubCache, isNull); expect(processMock.lastPubCache, isNull);
pub.get(context: PubContext.flutterTests).then((void value) { pub.get(context: PubContext.flutterTests).then((void value) {
...@@ -421,15 +419,17 @@ void main() { ...@@ -421,15 +419,17 @@ void main() {
}); });
time.elapse(const Duration(milliseconds: 500)); time.elapse(const Duration(milliseconds: 500));
expect(processMock.lastPubCache, equals(fsMock.path.join(Cache.flutterRoot, '.pub-cache'))); expect(processMock.lastPubCache, equals(fileSystem.path.join(Cache.flutterRoot, '.pub-cache')));
expect(error, isNull); expect(error, isNull);
}); });
}); });
testWithoutContext('pub cache in environment is used', () async { testWithoutContext('pub cache in environment is used', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
fileSystem.directory('custom/pub-cache/path').createSync(recursive: true);
final MockProcessManager processMock = MockProcessManager(69); final MockProcessManager processMock = MockProcessManager(69);
final Pub pub = Pub( final Pub pub = Pub(
fileSystem: MockFileSystem(), fileSystem: fileSystem,
logger: BufferLogger.test(), logger: BufferLogger.test(),
processManager: processMock, processManager: processMock,
usage: TestUsage(), usage: TestUsage(),
...@@ -442,7 +442,6 @@ void main() { ...@@ -442,7 +442,6 @@ void main() {
); );
FakeAsync().run((FakeAsync time) { FakeAsync().run((FakeAsync time) {
MockDirectory.findCache = true;
expect(processMock.lastPubEnvironment, isNull); expect(processMock.lastPubEnvironment, isNull);
expect(processMock.lastPubCache, isNull); expect(processMock.lastPubCache, isNull);
...@@ -534,13 +533,13 @@ void main() { ...@@ -534,13 +533,13 @@ void main() {
); );
}); });
testWithoutContext('analytics sent on failure', () async { testWithoutContext('analytics sent on failure', () async {
MockDirectory.findCache = true; final FileSystem fileSystem = MemoryFileSystem.test();
fileSystem.directory('custom/pub-cache/path').createSync(recursive: true);
final TestUsage usage = TestUsage(); final TestUsage usage = TestUsage();
final Pub pub = Pub( final Pub pub = Pub(
usage: usage, usage: usage,
fileSystem: MockFileSystem(), fileSystem: fileSystem,
logger: BufferLogger.test(), logger: BufferLogger.test(),
processManager: MockProcessManager(1), processManager: MockProcessManager(1),
botDetector: const BotDetectorAlwaysNo(), botDetector: const BotDetectorAlwaysNo(),
...@@ -721,50 +720,3 @@ class MockProcessManager implements ProcessManager { ...@@ -721,50 +720,3 @@ class MockProcessManager implements ProcessManager {
@override @override
dynamic noSuchMethod(Invocation invocation) => null; dynamic noSuchMethod(Invocation invocation) => null;
} }
class MockFileSystem extends ForwardingFileSystem {
MockFileSystem() : super(MemoryFileSystem.test());
@override
File file(dynamic path) {
return MockFile();
}
@override
Directory directory(dynamic path) {
return MockDirectory(path as String);
}
}
class MockFile implements File {
@override
Future<RandomAccessFile> open({ FileMode mode = FileMode.read }) async {
return MockRandomAccessFile();
}
@override
bool existsSync() => true;
@override
DateTime lastModifiedSync() => DateTime(0);
@override
dynamic noSuchMethod(Invocation invocation) => null;
}
class MockDirectory implements Directory {
MockDirectory(this.path);
@override
final String path;
static bool findCache = false;
@override
bool existsSync() => findCache && path.endsWith('.pub-cache');
@override
dynamic noSuchMethod(Invocation invocation) => null;
}
class MockRandomAccessFile extends Mock implements RandomAccessFile {}
...@@ -30,6 +30,7 @@ const List<String> kChromeArgs = <String>[ ...@@ -30,6 +30,7 @@ const List<String> kChromeArgs = <String>[
const String kDevtoolsStderr = '\n\nDevTools listening\n\n'; const String kDevtoolsStderr = '\n\nDevTools listening\n\n';
void main() { void main() {
FileExceptionHandler exceptionHandler;
ChromiumLauncher chromeLauncher; ChromiumLauncher chromeLauncher;
FileSystem fileSystem; FileSystem fileSystem;
Platform platform; Platform platform;
...@@ -37,6 +38,7 @@ void main() { ...@@ -37,6 +38,7 @@ void main() {
OperatingSystemUtils operatingSystemUtils; OperatingSystemUtils operatingSystemUtils;
setUp(() { setUp(() {
exceptionHandler = FileExceptionHandler();
operatingSystemUtils = MockOperatingSystemUtils(); operatingSystemUtils = MockOperatingSystemUtils();
when(operatingSystemUtils.findFreePort()) when(operatingSystemUtils.findFreePort())
.thenAnswer((Invocation invocation) async { .thenAnswer((Invocation invocation) async {
...@@ -45,7 +47,7 @@ void main() { ...@@ -45,7 +47,7 @@ void main() {
platform = FakePlatform(operatingSystem: 'macos', environment: <String, String>{ platform = FakePlatform(operatingSystem: 'macos', environment: <String, String>{
kChromeEnvironment: 'example_chrome', kChromeEnvironment: 'example_chrome',
}); });
fileSystem = MemoryFileSystem.test(); fileSystem = MemoryFileSystem.test(opHandle: exceptionHandler.opHandle);
processManager = FakeProcessManager.list(<FakeCommand>[]); processManager = FakeProcessManager.list(<FakeCommand>[]);
chromeLauncher = ChromiumLauncher( chromeLauncher = ChromiumLauncher(
fileSystem: fileSystem, fileSystem: fileSystem,
...@@ -105,10 +107,8 @@ void main() { ...@@ -105,10 +107,8 @@ void main() {
testWithoutContext('does not crash if saving profile information fails due to a file system exception.', () async { testWithoutContext('does not crash if saving profile information fails due to a file system exception.', () async {
final BufferLogger logger = BufferLogger.test(); final BufferLogger logger = BufferLogger.test();
final ErrorThrowingFileSystem errorFileSystem = ErrorThrowingFileSystem(fileSystem);
errorFileSystem.addErrorEntity(FakeDirectory('/.tmp_rand0/flutter_tools_chrome_device.rand0/Default', errorFileSystem));
chromeLauncher = ChromiumLauncher( chromeLauncher = ChromiumLauncher(
fileSystem: errorFileSystem, fileSystem: fileSystem,
platform: platform, platform: platform,
processManager: processManager, processManager: processManager,
operatingSystemUtils: operatingSystemUtils, operatingSystemUtils: operatingSystemUtils,
...@@ -132,9 +132,18 @@ void main() { ...@@ -132,9 +132,18 @@ void main() {
cacheDir: fileSystem.currentDirectory, cacheDir: fileSystem.currentDirectory,
); );
// Create cache dir that the Chrome launcher will atttempt to persist. // Create cache dir that the Chrome launcher will atttempt to persist, and a file
fileSystem.directory('/.tmp_rand0/flutter_tools_chrome_device.rand0/Default/Local Storage') // that will thrown an exception when it is read.
const String directoryPrefix = '/.tmp_rand0/flutter_tools_chrome_device.rand0/Default';
fileSystem.directory('$directoryPrefix/Local Storage')
.createSync(recursive: true); .createSync(recursive: true);
final File file = fileSystem.file('$directoryPrefix/Local Storage/foo')
..createSync(recursive: true);
exceptionHandler.addError(
file,
FileSystemOp.read,
const FileSystemException(),
);
await chrome.close(); // does not exit with error. await chrome.close(); // does not exit with error.
expect(logger.errorText, contains('Failed to save Chrome preferences')); expect(logger.errorText, contains('Failed to save Chrome preferences'));
...@@ -142,11 +151,15 @@ void main() { ...@@ -142,11 +151,15 @@ void main() {
testWithoutContext('does not crash if restoring profile information fails due to a file system exception.', () async { testWithoutContext('does not crash if restoring profile information fails due to a file system exception.', () async {
final BufferLogger logger = BufferLogger.test(); final BufferLogger logger = BufferLogger.test();
final ErrorThrowingFileSystem errorFileSystem = ErrorThrowingFileSystem(fileSystem); final File file = fileSystem.file('/Default/foo')
errorFileSystem.addErrorEntity(FakeDirectory('/Default', errorFileSystem)); ..createSync(recursive: true);
exceptionHandler.addError(
file,
FileSystemOp.read,
const FileSystemException(),
);
chromeLauncher = ChromiumLauncher( chromeLauncher = ChromiumLauncher(
fileSystem: errorFileSystem, fileSystem: fileSystem,
platform: platform, platform: platform,
processManager: processManager, processManager: processManager,
operatingSystemUtils: operatingSystemUtils, operatingSystemUtils: operatingSystemUtils,
...@@ -169,7 +182,7 @@ void main() { ...@@ -169,7 +182,7 @@ void main() {
final Chromium chrome = await chromeLauncher.launch( final Chromium chrome = await chromeLauncher.launch(
'example_url', 'example_url',
skipCheck: true, skipCheck: true,
cacheDir: errorFileSystem.currentDirectory, cacheDir: fileSystem.currentDirectory,
); );
// Create cache dir that the Chrome launcher will atttempt to persist. // Create cache dir that the Chrome launcher will atttempt to persist.
...@@ -363,60 +376,3 @@ Future<Chromium> _testLaunchChrome(String userDataDir, FakeProcessManager proces ...@@ -363,60 +376,3 @@ Future<Chromium> _testLaunchChrome(String userDataDir, FakeProcessManager proces
skipCheck: true, skipCheck: true,
); );
} }
class FakeDirectory extends Fake implements Directory {
FakeDirectory(this.path, this.fileSystem);
@override
final FileSystem fileSystem;
@override
final String path;
@override
void createSync({bool recursive = false}) {}
@override
bool existsSync() {
return true;
}
@override
List<FileSystemEntity> listSync({bool recursive = false, bool followLinks = true}) {
throw FileSystemException(path, '');
}
}
class ErrorThrowingFileSystem extends ForwardingFileSystem {
ErrorThrowingFileSystem(FileSystem delegate) : super(delegate);
final Map<String, FileSystemEntity> errorEntities = <String, FileSystemEntity>{};
void addErrorEntity(FileSystemEntity entity) {
errorEntities[entity.path] = entity;
}
@override
Directory directory(dynamic path) {
if (errorEntities.containsKey(path)) {
return errorEntities[path] as Directory;
}
return delegate.directory(path);
}
@override
File file(dynamic path) {
if (errorEntities.containsKey(path)) {
return errorEntities[path] as File;
}
return delegate.file(path);
}
@override
Link link(dynamic path) {
if (errorEntities.containsKey(path)) {
return errorEntities[path] as Link;
}
return delegate.link(path);
}
}
...@@ -8,6 +8,7 @@ import 'dart:async'; ...@@ -8,6 +8,7 @@ import 'dart:async';
import 'package:args/args.dart'; import 'package:args/args.dart';
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/user_messages.dart'; import 'package:flutter_tools/src/base/user_messages.dart';
...@@ -419,27 +420,6 @@ class TestFlutterCommandRunner extends FlutterCommandRunner { ...@@ -419,27 +420,6 @@ class TestFlutterCommandRunner extends FlutterCommandRunner {
} }
} }
/// A file system that allows preconfiguring certain entities.
///
/// This is useful for inserting mocks/entities which throw errors or
/// have other behavior that is not easily configured through the
/// filesystem interface.
class ConfiguredFileSystem extends ForwardingFileSystem {
ConfiguredFileSystem(FileSystem delegate, {@required this.entities}) : super(delegate);
final Map<String, FileSystemEntity> entities;
@override
File file(dynamic path) {
return (entities[path] as File) ?? super.file(path);
}
@override
Directory directory(dynamic path) {
return (entities[path] as Directory) ?? super.directory(path);
}
}
/// Matches a doctor validation result. /// Matches a doctor validation result.
Matcher matchDoctorValidation({ Matcher matchDoctorValidation({
ValidationType validationType, ValidationType validationType,
...@@ -451,3 +431,44 @@ Matcher matchDoctorValidation({ ...@@ -451,3 +431,44 @@ Matcher matchDoctorValidation({
.having((ValidationResult result) => result.statusInfo, 'statusInfo', statusInfo) .having((ValidationResult result) => result.statusInfo, 'statusInfo', statusInfo)
.having((ValidationResult result) => result.messages, 'messages', messages); .having((ValidationResult result) => result.messages, 'messages', messages);
} }
/// Allows inserting file system exceptions into certain
/// [MemoryFileSystem] operations by tagging path/op combinations.
///
/// Example use:
///
/// ```
/// void main() {
/// var handler = FileExceptionHandler();
/// var fs = MemoryFileSystem(opHandle: handler.opHandle);
///
/// var file = fs.file('foo')..createSync();
/// handler.addError(file, FileSystemOp.read, FileSystemException('Error Reading foo'));
///
/// expect(() => file.writeAsStringSync('A'), throwsA(isA<FileSystemException>()));
/// }
/// ```
class FileExceptionHandler {
final Map<String, Map<FileSystemOp, FileSystemException>> _contextErrors = <String, Map<FileSystemOp, FileSystemException>>{};
/// Add an exception that will be thrown whenever the file system attached to this
/// handler performs the [operation] on the [entity].
void addError(FileSystemEntity entity, FileSystemOp operation, FileSystemException exception) {
final String path = entity.path;
_contextErrors[path] ??= <FileSystemOp, FileSystemException>{};
_contextErrors[path][operation] = exception;
}
// Tear-off this method and pass it to the memory filesystem `opHandle` parameter.
void opHandle(String path, FileSystemOp operation) {
final Map<FileSystemOp, FileSystemException> exceptions = _contextErrors[path];
if (exceptions == null) {
return;
}
final FileSystemException exception = exceptions[operation];
if (exception == null) {
return;
}
throw exception;
}
}
...@@ -10,12 +10,12 @@ import 'package:flutter_tools/src/base/file_system.dart'; ...@@ -10,12 +10,12 @@ import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/template.dart'; import 'package:flutter_tools/src/base/template.dart';
import 'package:flutter_tools/src/template.dart'; import 'package:flutter_tools/src/template.dart';
import 'package:mockito/mockito.dart';
import 'src/common.dart'; import 'src/common.dart';
void main() { void main() {
testWithoutContext('Template.render throws ToolExit when FileSystem exception is raised', () { testWithoutContext('Template.render throws ToolExit when FileSystem exception is raised', () {
final MemoryFileSystem fileSystem = MemoryFileSystem.test(); final FileExceptionHandler handler = FileExceptionHandler();
final MemoryFileSystem fileSystem = MemoryFileSystem.test(opHandle: handler.opHandle);
final Template template = Template( final Template template = Template(
fileSystem.directory('examples'), fileSystem.directory('examples'),
fileSystem.currentDirectory, fileSystem.currentDirectory,
...@@ -25,11 +25,11 @@ void main() { ...@@ -25,11 +25,11 @@ void main() {
templateRenderer: FakeTemplateRenderer(), templateRenderer: FakeTemplateRenderer(),
templateManifest: null, templateManifest: null,
); );
final MockDirectory mockDirectory = MockDirectory(); final Directory directory = fileSystem.directory('foo');
when(mockDirectory.createSync(recursive: true)).thenThrow(const FileSystemException()); handler.addError(directory, FileSystemOp.create, const FileSystemException());
expect(() => template.render(mockDirectory, <String, Object>{}), expect(() => template.render(directory, <String, Object>{}),
throwsToolExit()); throwsToolExit());
}); });
testWithoutContext('Template.render replaces .img.tmpl files with files from the image source', () { testWithoutContext('Template.render replaces .img.tmpl files with files from the image source', () {
...@@ -60,8 +60,6 @@ void main() { ...@@ -60,8 +60,6 @@ void main() {
}); });
} }
class MockDirectory extends Mock implements Directory {}
class FakeTemplateRenderer extends TemplateRenderer { class FakeTemplateRenderer extends TemplateRenderer {
@override @override
String renderString(String template, dynamic context, {bool htmlEscapeValues = false}) { String renderString(String template, dynamic context, {bool htmlEscapeValues = false}) {
......
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