use IOOverrides to allow inject file system, write test, find bug (#40066)

This commit is contained in:
Jonah Williams 2019-09-30 08:41:12 -07:00 committed by GitHub
parent 45f3c8d060
commit c8b3c9b727
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 230 additions and 33 deletions

View file

@ -26,7 +26,8 @@
/// increase the API surface that we have to test in Flutter tools, and the APIs
/// in `dart:io` can sometimes be hard to use in tests.
import 'dart:async';
import 'dart:io' as io show exit, IOSink, Process, ProcessInfo, ProcessSignal, stderr, stdin, Stdout, stdout;
import 'dart:io' as io show exit, IOSink, Process, ProcessInfo, ProcessSignal,
stderr, stdin, Stdout, stdout;
import 'package:meta/meta.dart';
@ -38,10 +39,10 @@ export 'dart:io'
show
BytesBuilder,
CompressionOptions,
// Directory NO! Use `file_system.dart`
// Directory, NO! Use `file_system.dart`
exitCode,
// File NO! Use `file_system.dart`
// FileSystemEntity NO! Use `file_system.dart`
// File, NO! Use `file_system.dart`
// FileSystemEntity, NO! Use `file_system.dart`
gzip,
HandshakeException,
HttpClient,

View file

@ -125,20 +125,20 @@ class MultirootFileBasedAssetReader extends core.FileBasedAssetReader {
@override
Stream<AssetId> findAssets(Glob glob, {String package}) async* {
if (package == null || packageGraph.root.name == package) {
final String generatedRoot = fs.path.join(
generatedDirectory.path, packageGraph.root.name
);
await for (io.FileSystemEntity entity in glob.list(followLinks: true, root: packageGraph.root.path)) {
if (entity is io.File && _isNotHidden(entity)) {
if (entity is io.File && _isNotHidden(entity) && !fs.path.isWithin(generatedRoot, entity.path)) {
yield _fileToAssetId(entity, packageGraph.root);
}
}
final String generatedRoot = fs.path.join(
generatedDirectory.path, packageGraph.root.name,
);
if (!fs.isDirectorySync(generatedRoot)) {
return;
}
await for (io.FileSystemEntity entity in glob.list(followLinks: true, root: generatedRoot)) {
if (entity is io.File && _isNotHidden(entity)) {
yield _fileToAssetId(entity, packageGraph.root, generatedRoot);
yield _fileToAssetId(entity, packageGraph.root, fs.path.relative(generatedRoot), true);
}
}
return;
@ -161,9 +161,14 @@ class MultirootFileBasedAssetReader extends core.FileBasedAssetReader {
}
/// Creates an [AssetId] for [file], which is a part of [packageNode].
AssetId _fileToAssetId(io.File file, core.PackageNode packageNode, [String root]) {
AssetId _fileToAssetId(io.File file, core.PackageNode packageNode, [String root, bool generated = false]) {
final String filePath = path.normalize(file.absolute.path);
final String relativePath = path.relative(filePath, from: root ?? packageNode.path);
String relativePath;
if (generated) {
relativePath = filePath.substring(root.length + 2);
} else {
relativePath = path.relative(filePath, from: packageNode.path);
}
return AssetId(packageNode.name, relativePath);
}
}

View file

@ -5,29 +5,68 @@
import 'dart:async';
import 'dart:io' as io;
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/io.dart';
void main() {
group('ProcessSignal', () {
test('IOOverrides can inject a memory file system', () async {
final MemoryFileSystem memoryFileSystem = MemoryFileSystem();
final FlutterIOOverrides flutterIOOverrides = FlutterIOOverrides(fileSystem: memoryFileSystem);
await io.IOOverrides.runWithIOOverrides(() async {
// statics delegate correctly.
expect(io.FileSystemEntity.isWatchSupported, memoryFileSystem.isWatchSupported);
expect(io.Directory.systemTemp.path, memoryFileSystem.systemTempDirectory.path);
testUsingContext('signals are properly delegated', () async {
final MockIoProcessSignal mockSignal = MockIoProcessSignal();
final ProcessSignal signalUnderTest = ProcessSignal(mockSignal);
final StreamController<io.ProcessSignal> controller = StreamController<io.ProcessSignal>();
// can create and write to files/directories sync.
final io.File file = io.File('abc');
file.writeAsStringSync('def');
final io.Directory directory = io.Directory('foobar');
directory.createSync();
when(mockSignal.watch()).thenAnswer((Invocation invocation) => controller.stream);
controller.add(mockSignal);
expect(memoryFileSystem.file('abc').existsSync(), true);
expect(memoryFileSystem.file('abc').readAsStringSync(), 'def');
expect(memoryFileSystem.directory('foobar').existsSync(), true);
expect(signalUnderTest, await signalUnderTest.watch().first);
});
// can create and write to files/directories async.
final io.File fileB = io.File('xyz');
await fileB.writeAsString('def');
final io.Directory directoryB = io.Directory('barfoo');
await directoryB.create();
testUsingContext('toString() works', () async {
expect(io.ProcessSignal.sigint.toString(), ProcessSignal.SIGINT.toString());
});
expect(memoryFileSystem.file('xyz').existsSync(), true);
expect(memoryFileSystem.file('xyz').readAsStringSync(), 'def');
expect(memoryFileSystem.directory('barfoo').existsSync(), true);
// Links
final io.Link linkA = io.Link('hhh');
final io.Link linkB = io.Link('ggg');
io.File('jjj').createSync();
io.File('lll').createSync();
await linkA.create('jjj');
linkB.createSync('lll');
expect(await memoryFileSystem.link('hhh').resolveSymbolicLinks(), await linkA.resolveSymbolicLinks());
expect(memoryFileSystem.link('ggg').resolveSymbolicLinksSync(), linkB.resolveSymbolicLinksSync());
}, flutterIOOverrides);
});
testUsingContext('ProcessSignal signals are properly delegated', () async {
final MockIoProcessSignal mockSignal = MockIoProcessSignal();
final ProcessSignal signalUnderTest = ProcessSignal(mockSignal);
final StreamController<io.ProcessSignal> controller = StreamController<io.ProcessSignal>();
when(mockSignal.watch()).thenAnswer((Invocation invocation) => controller.stream);
controller.add(mockSignal);
expect(signalUnderTest, await signalUnderTest.watch().first);
});
testUsingContext('ProcessSignal toString() works', () async {
expect(io.ProcessSignal.sigint.toString(), ProcessSignal.SIGINT.toString());
});
}

View file

@ -3,13 +3,16 @@
// found in the LICENSE file.
import 'dart:convert';
import 'dart:io';
import 'package:build/build.dart';
import 'package:build_runner_core/build_runner_core.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_runner/web_compilation_delegate.dart';
import 'package:glob/glob.dart';
import '../../src/common.dart';
import '../../src/io.dart';
import '../../src/testbed.dart';
void main() {
@ -34,17 +37,30 @@ void main() {
});
test('Can find assets from the generated directory', () => testbed.run(() async {
final MultirootFileBasedAssetReader reader = MultirootFileBasedAssetReader(
packageGraph,
fs.directory(fs.path.join('.dart_tool', 'build', 'generated')),
);
await IOOverrides.runWithIOOverrides(() async {
final MultirootFileBasedAssetReader reader = MultirootFileBasedAssetReader(
packageGraph,
fs.directory(fs.path.join('.dart_tool', 'build', 'generated'))
);
expect(await reader.canRead(AssetId('foobar', 'lib/bar.dart')), true);
expect(await reader.canRead(AssetId('foobar', 'lib/main.dart')), true);
// Note: we can't read from the regular directory because the default
// asset reader uses the regular file system.
expect(await reader.canRead(AssetId('foobar', 'lib/bar.dart')), true);
expect(await reader.readAsString(AssetId('foobar', 'lib/bar.dart')), 'bar');
expect(await reader.readAsBytes(AssetId('foobar', 'lib/bar.dart')), utf8.encode('bar'));
}));
expect(await reader.readAsString(AssetId('foobar', 'lib/bar.dart')), 'bar');
expect(await reader.readAsString(AssetId('foobar', 'lib/main.dart')), 'main');
expect(await reader.readAsBytes(AssetId('foobar', 'lib/bar.dart')), utf8.encode('bar'));
expect(await reader.readAsBytes(AssetId('foobar', 'lib/main.dart')), utf8.encode('main'));
expect(await reader.findAssets(Glob('**')).toList(), unorderedEquals(<AssetId>[
AssetId('foobar', 'pubspec.yaml'),
AssetId('foobar', 'lib/bar.dart'),
AssetId('foobar', 'lib/main.dart'),
]));
}, FlutterIOOverrides(fileSystem: fs));
// Some component of either dart:io or build_runner normalizes file uris
// into file paths for windows. This doesn't seem to work with IOOverrides
// leaving all filepaths on windows with forward slashes.
}), skip: Platform.isWindows);
});
}

View file

@ -0,0 +1,136 @@
// 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:io' as io show IOOverrides, Directory, File, Link;
import 'package:flutter_tools/src/base/file_system.dart';
/// An [IOOverrides] that can delegate to [FileSystem] implementation if provided.
///
/// Does not override any of the socket facilities.
///
/// Do not provide a [LocalFileSystem] as a delegate. Since internally this calls
/// out to `dart:io` classes, it will result in a stack overflow error as the
/// IOOverrides and LocalFileSystem call eachother endlessly.
///
/// The only safe delegate types are those that do not call out to `dart:io`,
/// like the [MemoryFileSystem].
class FlutterIOOverrides extends io.IOOverrides {
FlutterIOOverrides({ FileSystem fileSystem })
: _fileSystemDelegate = fileSystem;
final FileSystem _fileSystemDelegate;
@override
io.Directory createDirectory(String path) {
if (_fileSystemDelegate == null) {
return super.createDirectory(path);
}
return _fileSystemDelegate.directory(path);
}
@override
io.File createFile(String path) {
if (_fileSystemDelegate == null) {
return super.createFile(path);
}
return _fileSystemDelegate.file(path);
}
@override
io.Link createLink(String path) {
if (_fileSystemDelegate == null) {
return super.createLink(path);
}
return _fileSystemDelegate.link(path);
}
@override
Stream<FileSystemEvent> fsWatch(String path, int events, bool recursive) {
if (_fileSystemDelegate == null) {
return super.fsWatch(path, events, recursive);
}
return _fileSystemDelegate.file(path).watch(events: events, recursive: recursive);
}
@override
bool fsWatchIsSupported() {
if (_fileSystemDelegate == null) {
return super.fsWatchIsSupported();
}
return _fileSystemDelegate.isWatchSupported;
}
@override
Future<FileSystemEntityType> fseGetType(String path, bool followLinks) {
if (_fileSystemDelegate == null) {
return super.fseGetType(path, followLinks);
}
return _fileSystemDelegate.type(path, followLinks: followLinks ?? true);
}
@override
FileSystemEntityType fseGetTypeSync(String path, bool followLinks) {
if (_fileSystemDelegate == null) {
return super.fseGetTypeSync(path, followLinks);
}
return _fileSystemDelegate.typeSync(path, followLinks: followLinks ?? true);
}
@override
Future<bool> fseIdentical(String path1, String path2) {
if (_fileSystemDelegate == null) {
return super.fseIdentical(path1, path2);
}
return _fileSystemDelegate.identical(path1, path2);
}
@override
bool fseIdenticalSync(String path1, String path2) {
if (_fileSystemDelegate == null) {
return super.fseIdenticalSync(path1, path2);
}
return _fileSystemDelegate.identicalSync(path1, path2);
}
@override
io.Directory getCurrentDirectory() {
if (_fileSystemDelegate == null) {
return super.getCurrentDirectory();
}
return _fileSystemDelegate.currentDirectory;
}
@override
io.Directory getSystemTempDirectory() {
if (_fileSystemDelegate == null) {
return super.getSystemTempDirectory();
}
return _fileSystemDelegate.systemTempDirectory;
}
@override
void setCurrentDirectory(String path) {
if (_fileSystemDelegate == null) {
return super.setCurrentDirectory(path);
}
_fileSystemDelegate.currentDirectory = path;
}
@override
Future<FileStat> stat(String path) {
if (_fileSystemDelegate == null) {
return super.stat(path);
}
return _fileSystemDelegate.stat(path);
}
@override
FileStat statSync(String path) {
if (_fileSystemDelegate == null) {
return super.statSync(path);
}
return _fileSystemDelegate.statSync(path);
}
}