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

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

parent 45f3c8d0
...@@ -26,7 +26,8 @@ ...@@ -26,7 +26,8 @@
/// increase the API surface that we have to test in Flutter tools, and the APIs /// 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. /// in `dart:io` can sometimes be hard to use in tests.
import 'dart:async'; 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'; import 'package:meta/meta.dart';
...@@ -38,10 +39,10 @@ export 'dart:io' ...@@ -38,10 +39,10 @@ export 'dart:io'
show show
BytesBuilder, BytesBuilder,
CompressionOptions, CompressionOptions,
// Directory NO! Use `file_system.dart` // Directory, NO! Use `file_system.dart`
exitCode, exitCode,
// File NO! Use `file_system.dart` // File, NO! Use `file_system.dart`
// FileSystemEntity NO! Use `file_system.dart` // FileSystemEntity, NO! Use `file_system.dart`
gzip, gzip,
HandshakeException, HandshakeException,
HttpClient, HttpClient,
......
...@@ -125,20 +125,20 @@ class MultirootFileBasedAssetReader extends core.FileBasedAssetReader { ...@@ -125,20 +125,20 @@ class MultirootFileBasedAssetReader extends core.FileBasedAssetReader {
@override @override
Stream<AssetId> findAssets(Glob glob, {String package}) async* { Stream<AssetId> findAssets(Glob glob, {String package}) async* {
if (package == null || packageGraph.root.name == package) { 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)) { 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); yield _fileToAssetId(entity, packageGraph.root);
} }
} }
final String generatedRoot = fs.path.join(
generatedDirectory.path, packageGraph.root.name,
);
if (!fs.isDirectorySync(generatedRoot)) { if (!fs.isDirectorySync(generatedRoot)) {
return; return;
} }
await for (io.FileSystemEntity entity in glob.list(followLinks: true, root: generatedRoot)) { await for (io.FileSystemEntity entity in glob.list(followLinks: true, root: generatedRoot)) {
if (entity is io.File && _isNotHidden(entity)) { if (entity is io.File && _isNotHidden(entity)) {
yield _fileToAssetId(entity, packageGraph.root, generatedRoot); yield _fileToAssetId(entity, packageGraph.root, fs.path.relative(generatedRoot), true);
} }
} }
return; return;
...@@ -161,9 +161,14 @@ class MultirootFileBasedAssetReader extends core.FileBasedAssetReader { ...@@ -161,9 +161,14 @@ class MultirootFileBasedAssetReader extends core.FileBasedAssetReader {
} }
/// Creates an [AssetId] for [file], which is a part of [packageNode]. /// 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 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); return AssetId(packageNode.name, relativePath);
} }
} }
...@@ -5,29 +5,68 @@ ...@@ -5,29 +5,68 @@
import 'dart:async'; import 'dart:async';
import 'dart:io' as io; import 'dart:io' as io;
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
import '../../src/io.dart';
void main() { 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 { // can create and write to files/directories sync.
final MockIoProcessSignal mockSignal = MockIoProcessSignal(); final io.File file = io.File('abc');
final ProcessSignal signalUnderTest = ProcessSignal(mockSignal); file.writeAsStringSync('def');
final StreamController<io.ProcessSignal> controller = StreamController<io.ProcessSignal>(); final io.Directory directory = io.Directory('foobar');
directory.createSync();
when(mockSignal.watch()).thenAnswer((Invocation invocation) => controller.stream); expect(memoryFileSystem.file('abc').existsSync(), true);
controller.add(mockSignal); 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(memoryFileSystem.file('xyz').existsSync(), true);
expect(io.ProcessSignal.sigint.toString(), ProcessSignal.SIGINT.toString()); 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());
}); });
} }
......
...@@ -3,13 +3,16 @@ ...@@ -3,13 +3,16 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:build/build.dart'; import 'package:build/build.dart';
import 'package:build_runner_core/build_runner_core.dart'; import 'package:build_runner_core/build_runner_core.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_runner/web_compilation_delegate.dart'; import 'package:flutter_tools/src/build_runner/web_compilation_delegate.dart';
import 'package:glob/glob.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/io.dart';
import '../../src/testbed.dart'; import '../../src/testbed.dart';
void main() { void main() {
...@@ -34,17 +37,30 @@ void main() { ...@@ -34,17 +37,30 @@ void main() {
}); });
test('Can find assets from the generated directory', () => testbed.run(() async { test('Can find assets from the generated directory', () => testbed.run(() async {
final MultirootFileBasedAssetReader reader = MultirootFileBasedAssetReader( await IOOverrides.runWithIOOverrides(() async {
packageGraph, final MultirootFileBasedAssetReader reader = MultirootFileBasedAssetReader(
fs.directory(fs.path.join('.dart_tool', 'build', 'generated')), packageGraph,
); fs.directory(fs.path.join('.dart_tool', 'build', 'generated'))
);
// Note: we can't read from the regular directory because the default expect(await reader.canRead(AssetId('foobar', 'lib/bar.dart')), true);
// asset reader uses the regular file system. expect(await reader.canRead(AssetId('foobar', 'lib/main.dart')), true);
expect(await reader.canRead(AssetId('foobar', 'lib/bar.dart')), true);
expect(await reader.readAsString(AssetId('foobar', 'lib/bar.dart')), 'bar'); 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/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);
}); });
} }
......
// 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);
}
}
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