mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
use IOOverrides to allow inject file system, write test, find bug (#40066)
This commit is contained in:
parent
45f3c8d060
commit
c8b3c9b727
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
136
packages/flutter_tools/test/src/io.dart
Normal file
136
packages/flutter_tools/test/src/io.dart
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue