Revert "Devfs cleanup and testing (#33374)" (#33673)
This reverts commit 445505d6f2d4a0b658385f0ef735d3a826ca49ab.
diff --git a/packages/flutter_tools/bin/fuchsia_asset_builder.dart b/packages/flutter_tools/bin/fuchsia_asset_builder.dart
index 0163a1b..2de5624 100644
--- a/packages/flutter_tools/bin/fuchsia_asset_builder.dart
+++ b/packages/flutter_tools/bin/fuchsia_asset_builder.dart
@@ -36,9 +36,10 @@
});
}
-void writeFile(libfs.File outputFile, DevFSContent content) {
+Future<void> writeFile(libfs.File outputFile, DevFSContent content) async {
outputFile.createSync(recursive: true);
- content.copyToFile(outputFile);
+ final List<int> data = await content.contentsAsBytes();
+ outputFile.writeAsBytesSync(data);
}
Future<void> run(List<String> args) async {
@@ -70,10 +71,12 @@
exit(1);
}
+ final List<Future<void>> calls = <Future<void>>[];
assets.entries.forEach((String fileName, DevFSContent content) {
final libfs.File outputFile = libfs.fs.file(libfs.fs.path.join(assetDir, fileName));
- writeFile(outputFile, content);
+ calls.add(writeFile(outputFile, content));
});
+ await Future.wait<void>(calls);
final String outputMan = argResults[_kOptionAssetManifestOut];
await writeFuchsiaManifest(assets, argResults[_kOptionAsset], outputMan, argResults[_kOptionComponentName]);
diff --git a/packages/flutter_tools/lib/src/bundle.dart b/packages/flutter_tools/lib/src/bundle.dart
index 3355f72..70a4265 100644
--- a/packages/flutter_tools/lib/src/bundle.dart
+++ b/packages/flutter_tools/lib/src/bundle.dart
@@ -147,7 +147,7 @@
if (assets == null)
throwToolExit('Error building assets', exitCode: 1);
- assemble(
+ await assemble(
buildMode: buildMode,
assetBundle: assets,
kernelContent: kernelContent,
@@ -182,14 +182,14 @@
return assetBundle;
}
-void assemble({
+Future<void> assemble({
BuildMode buildMode,
AssetBundle assetBundle,
DevFSContent kernelContent,
String privateKeyPath = defaultPrivateKeyPath,
String assetDirPath,
String compilationTraceFilePath,
-}) {
+}) async {
assetDirPath ??= getAssetBuildDirectory();
printTrace('Building bundle');
@@ -214,21 +214,22 @@
printTrace('Writing asset files to $assetDirPath');
ensureDirectoryExists(assetDirPath);
- writeBundle(fs.directory(assetDirPath), assetEntries);
+ await writeBundle(fs.directory(assetDirPath), assetEntries);
printTrace('Wrote $assetDirPath');
}
-void writeBundle(
+Future<void> writeBundle(
Directory bundleDir,
Map<String, DevFSContent> assetEntries,
-) {
+) async {
if (bundleDir.existsSync())
bundleDir.deleteSync(recursive: true);
bundleDir.createSync(recursive: true);
- for (MapEntry<String, DevFSContent> entry in assetEntries.entries) {
- final File file = fs.file(fs.path.join(bundleDir.path, entry.key));
- file.parent.createSync(recursive: true);
- entry.value.copyToFile(file);
- }
+ await Future.wait<void>(
+ assetEntries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
+ final File file = fs.file(fs.path.join(bundleDir.path, entry.key));
+ file.parent.createSync(recursive: true);
+ await file.writeAsBytes(await entry.value.contentsAsBytes());
+ }));
}
diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart
index 6a2307f..6f1caa4 100644
--- a/packages/flutter_tools/lib/src/commands/test.dart
+++ b/packages/flutter_tools/lib/src/commands/test.dart
@@ -242,7 +242,7 @@
throwToolExit('Error: Failed to build asset bundle');
}
if (_needRebuild(assetBundle.entries)) {
- writeBundle(fs.directory(fs.path.join('build', 'unit_test_assets')),
+ await writeBundle(fs.directory(fs.path.join('build', 'unit_test_assets')),
assetBundle.entries);
}
}
diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart
index ec97f95..e15fb5c 100644
--- a/packages/flutter_tools/lib/src/devfs.dart
+++ b/packages/flutter_tools/lib/src/devfs.dart
@@ -38,25 +38,21 @@
/// or if the given time is null.
bool isModifiedAfter(DateTime time);
- /// The number of bytes in this file.
int get size;
- /// Returns the raw bytes of this file.
- List<int> contentsAsBytes();
+ Future<List<int>> contentsAsBytes();
- /// Returns a gzipped representation of the contents of this file.
- List<int> contentsAsCompressedBytes() {
- return gzip.encode(contentsAsBytes());
+ Stream<List<int>> contentsAsStream();
+
+ Stream<List<int>> contentsAsCompressedStream() {
+ return contentsAsStream().cast<List<int>>().transform<List<int>>(gzip.encoder);
}
- /// Copies the content into the provided file.
- ///
- /// Requires that the `destination` directory already exists, but the target
- /// file need not.
- void copyToFile(File destination);
+ /// Return the list of files this content depends on.
+ List<String> get fileDependencies => <String>[];
}
-/// File content to be copied to the device.
+// File content to be copied to the device.
class DevFSFileContent extends DevFSContent {
DevFSFileContent(this.file);
@@ -107,6 +103,9 @@
}
@override
+ List<String> get fileDependencies => <String>[_getFile().path];
+
+ @override
bool get isModified {
final FileStat _oldFileStat = _fileStat;
_stat();
@@ -136,12 +135,10 @@
}
@override
- List<int> contentsAsBytes() => _getFile().readAsBytesSync().cast<int>();
+ Future<List<int>> contentsAsBytes() => _getFile().readAsBytes();
@override
- void copyToFile(File destination) {
- _getFile().copySync(destination.path);
- }
+ Stream<List<int>> contentsAsStream() => _getFile().openRead();
}
/// Byte content to be copied to the device.
@@ -178,15 +175,14 @@
int get size => _bytes.length;
@override
- List<int> contentsAsBytes() => _bytes;
+ Future<List<int>> contentsAsBytes() async => _bytes;
@override
- void copyToFile(File destination) {
- destination.writeAsBytesSync(contentsAsBytes());
- }
+ Stream<List<int>> contentsAsStream() =>
+ Stream<List<int>>.fromIterable(<List<int>>[_bytes]);
}
-/// String content to be copied to the device encoded as utf8.
+/// String content to be copied to the device.
class DevFSStringContent extends DevFSByteContent {
DevFSStringContent(String string)
: _string = string,
@@ -207,29 +203,75 @@
}
}
-class DevFSOperations {
- DevFSOperations(this.vmService, this.fsName)
- : httpAddress = vmService.httpAddress;
+/// Abstract DevFS operations interface.
+abstract class DevFSOperations {
+ Future<Uri> create(String fsName);
+ Future<dynamic> destroy(String fsName);
+ Future<dynamic> writeFile(String fsName, Uri deviceUri, DevFSContent content);
+}
+
+/// An implementation of [DevFSOperations] that speaks to the
+/// vm service.
+class ServiceProtocolDevFSOperations implements DevFSOperations {
+ ServiceProtocolDevFSOperations(this.vmService);
final VMService vmService;
+
+ @override
+ Future<Uri> create(String fsName) async {
+ final Map<String, dynamic> response = await vmService.vm.createDevFS(fsName);
+ return Uri.parse(response['uri']);
+ }
+
+ @override
+ Future<dynamic> destroy(String fsName) async {
+ await vmService.vm.deleteDevFS(fsName);
+ }
+
+ @override
+ Future<dynamic> writeFile(String fsName, Uri deviceUri, DevFSContent content) async {
+ List<int> bytes;
+ try {
+ bytes = await content.contentsAsBytes();
+ } catch (e) {
+ return e;
+ }
+ final String fileContents = base64.encode(bytes);
+ try {
+ return await vmService.vm.invokeRpcRaw(
+ '_writeDevFSFile',
+ params: <String, dynamic>{
+ 'fsName': fsName,
+ 'uri': deviceUri.toString(),
+ 'fileContents': fileContents,
+ },
+ );
+ } catch (error) {
+ printTrace('DevFS: Failed to write $deviceUri: $error');
+ }
+ }
+}
+
+class DevFSException implements Exception {
+ DevFSException(this.message, [this.error, this.stackTrace]);
+ final String message;
+ final dynamic error;
+ final StackTrace stackTrace;
+}
+
+class _DevFSHttpWriter {
+ _DevFSHttpWriter(this.fsName, VMService serviceProtocol)
+ : httpAddress = serviceProtocol.httpAddress;
+
final String fsName;
final Uri httpAddress;
- final HttpClient _client = HttpClient();
static const int kMaxInFlight = 6;
int _inFlight = 0;
Map<Uri, DevFSContent> _outstanding;
Completer<void> _completer;
-
- Future<Uri> create(String fsName) async {
- final Map<String, dynamic> response = await vmService.vm.createDevFS(fsName);
- return Uri.parse(response['uri']);
- }
-
- Future<void> destroy(String fsName) async {
- await vmService.vm.deleteDevFS(fsName);
- }
+ final HttpClient _client = HttpClient();
Future<void> write(Map<Uri, DevFSContent> entries) async {
_client.maxConnectionsPerHost = kMaxInFlight;
@@ -259,9 +301,9 @@
final HttpClientRequest request = await _client.putUrl(httpAddress);
request.headers.removeAll(HttpHeaders.acceptEncodingHeader);
request.headers.add('dev_fs_name', fsName);
- request.headers.add('dev_fs_uri_b64',
- base64.encode(utf8.encode(deviceUri.toString())));
- request.add(content.contentsAsCompressedBytes());
+ request.headers.add('dev_fs_uri_b64', base64.encode(utf8.encode('$deviceUri')));
+ final Stream<List<int>> contents = content.contentsAsCompressedStream();
+ await request.addStream(contents);
final HttpClientResponse response = await request.close();
await response.drain<void>();
} catch (error, trace) {
@@ -275,14 +317,6 @@
}
}
-class DevFSException implements Exception {
- DevFSException(this.message, [this.error, this.stackTrace]);
-
- final String message;
- final dynamic error;
- final StackTrace stackTrace;
-}
-
// Basic statistics for DevFS update operation.
class UpdateFSReport {
UpdateFSReport({
@@ -319,7 +353,8 @@
this.fsName,
this.rootDirectory, {
String packagesFilePath,
- }) : _operations = DevFSOperations(serviceProtocol, fsName),
+ }) : _operations = ServiceProtocolDevFSOperations(serviceProtocol),
+ _httpWriter = _DevFSHttpWriter(fsName, serviceProtocol),
_packagesFilePath = packagesFilePath ?? fs.path.join(rootDirectory.path, kPackagesFileName);
DevFS.operations(
@@ -327,9 +362,11 @@
this.fsName,
this.rootDirectory, {
String packagesFilePath,
- }) : _packagesFilePath = packagesFilePath ?? fs.path.join(rootDirectory.path, kPackagesFileName);
+ }) : _httpWriter = null,
+ _packagesFilePath = packagesFilePath ?? fs.path.join(rootDirectory.path, kPackagesFileName);
final DevFSOperations _operations;
+ final _DevFSHttpWriter _httpWriter;
final String fsName;
final Directory rootDirectory;
String _packagesFilePath;
@@ -452,7 +489,7 @@
printTrace('Updating files');
if (dirtyEntries.isNotEmpty) {
try {
- await _operations.write(dirtyEntries);
+ await _httpWriter.write(dirtyEntries);
} on SocketException catch (socketException, stackTrace) {
printTrace('DevFS sync failed. Lost connection to device: $socketException');
throw DevFSException('Lost connection to device.', socketException, stackTrace);
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart
index 3dac651..00d95fa 100644
--- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart
+++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_build.dart
@@ -52,7 +52,7 @@
final Map<String, DevFSContent> assetEntries =
Map<String, DevFSContent>.from(assets.entries);
- writeBundle(fs.directory(assetDir), assetEntries);
+ await writeBundle(fs.directory(assetDir), assetEntries);
final String appName = fuchsiaProject.project.manifest.appName;
final String outDir = getFuchsiaBuildDirectory();
diff --git a/packages/flutter_tools/lib/src/resident_web_runner.dart b/packages/flutter_tools/lib/src/resident_web_runner.dart
index aa1b61d..1652628 100644
--- a/packages/flutter_tools/lib/src/resident_web_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_web_runner.dart
@@ -120,7 +120,8 @@
if (build != 0) {
throwToolExit('Error: Failed to build asset bundle');
}
- writeBundle(fs.directory(getAssetBuildDirectory()), assetBundle.entries);
+ await writeBundle(
+ fs.directory(getAssetBuildDirectory()), assetBundle.entries);
// Step 2: Start an HTTP server
_server = WebAssetServer(flutterProject, target, ipv6);
diff --git a/packages/flutter_tools/lib/src/web/web_device.dart b/packages/flutter_tools/lib/src/web/web_device.dart
index 3288e41..d8ce9ce 100644
--- a/packages/flutter_tools/lib/src/web/web_device.dart
+++ b/packages/flutter_tools/lib/src/web/web_device.dart
@@ -119,7 +119,7 @@
if (build != 0) {
throwToolExit('Error: Failed to build asset bundle');
}
- writeBundle(fs.directory(getAssetBuildDirectory()), assetBundle.entries);
+ await writeBundle(fs.directory(getAssetBuildDirectory()), assetBundle.entries);
_package = package;
_server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
diff --git a/packages/flutter_tools/test/asset_bundle_package_fonts_test.dart b/packages/flutter_tools/test/asset_bundle_package_fonts_test.dart
index 0d2a3a2..648a092 100644
--- a/packages/flutter_tools/test/asset_bundle_package_fonts_test.dart
+++ b/packages/flutter_tools/test/asset_bundle_package_fonts_test.dart
@@ -74,7 +74,7 @@
final String entryKey = 'packages/$packageName/$packageFont';
expect(bundle.entries.containsKey(entryKey), true);
expect(
- utf8.decode(bundle.entries[entryKey].contentsAsBytes()),
+ utf8.decode(await bundle.entries[entryKey].contentsAsBytes()),
packageFont,
);
}
@@ -82,14 +82,14 @@
for (String localFont in localFonts) {
expect(bundle.entries.containsKey(localFont), true);
expect(
- utf8.decode(bundle.entries[localFont].contentsAsBytes()),
+ utf8.decode(await bundle.entries[localFont].contentsAsBytes()),
localFont,
);
}
}
expect(
- json.decode(utf8.decode(bundle.entries['FontManifest.json'].contentsAsBytes())),
+ json.decode(utf8.decode(await bundle.entries['FontManifest.json'].contentsAsBytes())),
json.decode(expectedAssetManifest),
);
}
diff --git a/packages/flutter_tools/test/asset_bundle_package_test.dart b/packages/flutter_tools/test/asset_bundle_package_test.dart
index 1e67c11..2ea7946 100644
--- a/packages/flutter_tools/test/asset_bundle_package_test.dart
+++ b/packages/flutter_tools/test/asset_bundle_package_test.dart
@@ -79,14 +79,14 @@
final String entryKey = Uri.encodeFull('packages/$packageName/$asset');
expect(bundle.entries.containsKey(entryKey), true, reason: 'Cannot find key on bundle: $entryKey');
expect(
- utf8.decode(bundle.entries[entryKey].contentsAsBytes()),
+ utf8.decode(await bundle.entries[entryKey].contentsAsBytes()),
asset,
);
}
}
expect(
- utf8.decode(bundle.entries['AssetManifest.json'].contentsAsBytes()),
+ utf8.decode(await bundle.entries['AssetManifest.json'].contentsAsBytes()),
expectedAssetManifest,
);
}
@@ -126,11 +126,11 @@
expect(bundle.entries.length, 3); // LICENSE, AssetManifest, FontManifest
const String expectedAssetManifest = '{}';
expect(
- utf8.decode(bundle.entries['AssetManifest.json'].contentsAsBytes()),
+ utf8.decode(await bundle.entries['AssetManifest.json'].contentsAsBytes()),
expectedAssetManifest,
);
expect(
- utf8.decode(bundle.entries['FontManifest.json'].contentsAsBytes()),
+ utf8.decode(await bundle.entries['FontManifest.json'].contentsAsBytes()),
'[]',
);
}, overrides: <Type, Generator>{
@@ -153,11 +153,11 @@
expect(bundle.entries.length, 3); // LICENSE, AssetManifest, FontManifest
const String expectedAssetManifest = '{}';
expect(
- utf8.decode(bundle.entries['AssetManifest.json'].contentsAsBytes()),
+ utf8.decode(await bundle.entries['AssetManifest.json'].contentsAsBytes()),
expectedAssetManifest,
);
expect(
- utf8.decode(bundle.entries['FontManifest.json'].contentsAsBytes()),
+ utf8.decode(await bundle.entries['FontManifest.json'].contentsAsBytes()),
'[]',
);
}, overrides: <Type, Generator>{
diff --git a/packages/flutter_tools/test/asset_bundle_test.dart b/packages/flutter_tools/test/asset_bundle_test.dart
index 6bd084e..6d99cde 100644
--- a/packages/flutter_tools/test/asset_bundle_test.dart
+++ b/packages/flutter_tools/test/asset_bundle_test.dart
@@ -50,7 +50,7 @@
expect(bundle.entries.length, 1);
const String expectedAssetManifest = '{}';
expect(
- utf8.decode(bundle.entries['AssetManifest.json'].contentsAsBytes()),
+ utf8.decode(await bundle.entries['AssetManifest.json'].contentsAsBytes()),
expectedAssetManifest,
);
}, overrides: <Type, Generator>{
diff --git a/packages/flutter_tools/test/asset_bundle_variant_test.dart b/packages/flutter_tools/test/asset_bundle_variant_test.dart
index 77bdbfd..9a7b9f5 100644
--- a/packages/flutter_tools/test/asset_bundle_variant_test.dart
+++ b/packages/flutter_tools/test/asset_bundle_variant_test.dart
@@ -76,7 +76,7 @@
// The main asset file, /a/b/c/foo, and its variants exist.
for (String asset in assets) {
expect(bundle.entries.containsKey(asset), true);
- expect(utf8.decode(bundle.entries[asset].contentsAsBytes()), asset);
+ expect(utf8.decode(await bundle.entries[asset].contentsAsBytes()), asset);
}
fs.file(fixPath('a/b/c/foo')).deleteSync();
@@ -88,7 +88,7 @@
expect(bundle.entries.containsKey('a/b/c/foo'), false);
for (String asset in assets.skip(1)) {
expect(bundle.entries.containsKey(asset), true);
- expect(utf8.decode(bundle.entries[asset].contentsAsBytes()), asset);
+ expect(utf8.decode(await bundle.entries[asset].contentsAsBytes()), asset);
}
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
diff --git a/packages/flutter_tools/test/asset_test.dart b/packages/flutter_tools/test/asset_test.dart
index af961eb..bb8f259 100644
--- a/packages/flutter_tools/test/asset_test.dart
+++ b/packages/flutter_tools/test/asset_test.dart
@@ -7,7 +7,6 @@
import 'package:flutter_tools/src/asset.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/cache.dart';
-import 'package:flutter_tools/src/convert.dart';
import 'src/common.dart';
import 'src/context.dart';
@@ -66,5 +65,5 @@
}
Future<String> getValueAsString(String key, AssetBundle asset) async {
- return utf8.decode(asset.entries[key].contentsAsBytes());
+ return String.fromCharCodes(await asset.entries[key].contentsAsBytes());
}
diff --git a/packages/flutter_tools/test/devfs_test.dart b/packages/flutter_tools/test/devfs_test.dart
index 1887e74..b8d93d4 100644
--- a/packages/flutter_tools/test/devfs_test.dart
+++ b/packages/flutter_tools/test/devfs_test.dart
@@ -4,17 +4,15 @@
import 'dart:async';
import 'dart:convert';
+import 'dart:io'; // ignore: dart_io_import
import 'package:file/file.dart';
import 'package:file/memory.dart';
-import 'package:flutter_tools/src/base/context.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/devfs.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
-import 'package:mockito/mockito.dart';
import 'src/common.dart';
import 'src/context.dart';
@@ -22,39 +20,17 @@
void main() {
FileSystem fs;
- MockPlatform mockPlatform;
+ String filePath;
+ Directory tempDir;
+ String basePath;
+ DevFS devFS;
- setUp(() {
- fs = MemoryFileSystem(style: FileSystemStyle.posix);
- mockPlatform = MockPlatform();
- when(mockPlatform.pathSeparator).thenReturn('/');
- when(mockPlatform.isWindows).thenReturn(false);
+ setUpAll(() {
+ fs = MemoryFileSystem();
+ filePath = fs.path.join('lib', 'foo.txt');
});
group('DevFSContent', () {
- test('copyToFile', () {
- final String filePath = fs.path.join('lib', 'foo.txt');
- final File file = fs.file(filePath)
- ..createSync(recursive: true)
- ..writeAsStringSync('hello, world');
- final DevFSByteContent byteContent = DevFSByteContent(<int>[4, 5, 6]);
- final DevFSStringContent stringContent = DevFSStringContent('some string');
- final DevFSFileContent fileContent = DevFSFileContent(file);
-
- final File byteDestination = fs.file('byte_dest');
- final File stringDestination = fs.file('string_dest');
- final File fileDestination = fs.file('file_dest');
-
- byteContent.copyToFile(byteDestination);
- expect(byteDestination.readAsBytesSync(), <int>[4, 5, 6]);
-
- stringContent.copyToFile(stringDestination);
- expect(stringDestination.readAsStringSync(), 'some string');
-
- fileContent.copyToFile(fileDestination);
- expect(fileDestination.readAsStringSync(), 'hello, world');
- });
-
test('bytes', () {
final DevFSByteContent content = DevFSByteContent(<int>[4, 5, 6]);
expect(content.bytes, orderedEquals(<int>[4, 5, 6]));
@@ -65,7 +41,6 @@
expect(content.isModified, isTrue);
expect(content.isModified, isFalse);
});
-
test('string', () {
final DevFSStringContent content = DevFSStringContent('some string');
expect(content.string, 'some string');
@@ -83,9 +58,7 @@
expect(content.isModified, isTrue);
expect(content.isModified, isFalse);
});
-
testUsingContext('file', () async {
- final String filePath = fs.path.join('lib', 'foo.txt');
final File file = fs.file(filePath);
final DevFSFileContent content = DevFSFileContent(file);
expect(content.isModified, isFalse);
@@ -100,9 +73,10 @@
expect(content.isModifiedAfter(null), isTrue);
file.writeAsBytesSync(<int>[2, 3, 4], flush: true);
+ expect(content.fileDependencies, <String>[filePath]);
expect(content.isModified, isTrue);
expect(content.isModified, isFalse);
- expect(content.contentsAsBytes(), <int>[2, 3, 4]);
+ expect(await content.contentsAsBytes(), <int>[2, 3, 4]);
updateFileModificationTime(file.path, fiveSecondsAgo, 0);
expect(content.isModified, isFalse);
expect(content.isModified, isFalse);
@@ -113,56 +87,36 @@
expect(content.isModified, isFalse);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
- }, skip: platform.isWindows); // Still flaky, but only on CI :(
+ }, skip: Platform.isWindows); // TODO(jonahwilliams): fix or disable this functionality.
});
group('devfs remote', () {
- DevFS devFS;
- MockResidentCompiler residentCompiler;
- MockDevFSOperations mockDevFSOperations;
- int created;
- int destroyed;
- List<String> writtenFiles;
- bool exists;
+ MockVMService vmService;
+ final MockResidentCompiler residentCompiler = MockResidentCompiler();
- setUp(() async {
- mockDevFSOperations = MockDevFSOperations();
- devFS = DevFS.operations(mockDevFSOperations, 'test', fs.currentDirectory);
- residentCompiler = MockResidentCompiler();
- created = 0;
- destroyed = 0;
- exists = false;
- writtenFiles = <String>[];
- when(mockDevFSOperations.create('test')).thenAnswer((Invocation invocation) async {
- if (exists) {
- throw rpc.RpcException(1001, 'already exists');
- }
- exists = true;
- created += 1;
- return Uri.parse(InternetAddress.loopbackIPv4.toString());
- });
- when(mockDevFSOperations.destroy('test')).thenAnswer((Invocation invocation) async {
- exists = false;
- destroyed += 1;
- });
- when(mockDevFSOperations.write(any)).thenAnswer((Invocation invocation) async {
- final Map<Uri, DevFSContent> entries = invocation.positionalArguments.first;
- writtenFiles.addAll(entries.keys.map((Uri uri) => uri.toFilePath()));
- });
+ setUpAll(() async {
+ tempDir = _newTempDir(fs);
+ basePath = tempDir.path;
+ vmService = MockVMService();
+ await vmService.setUp();
+ });
+ tearDownAll(() async {
+ await vmService.tearDown();
+ _cleanupTempDirs();
});
testUsingContext('create dev file system', () async {
// simulate workspace
- final String filePath = fs.path.join('lib', 'foo.txt');
- final File file = fs.file(filePath);
+ final File file = fs.file(fs.path.join(basePath, filePath));
await file.parent.create(recursive: true);
file.writeAsBytesSync(<int>[1, 2, 3]);
// simulate package
await _createPackage(fs, 'somepkg', 'somefile.txt');
- await devFS.create();
- expect(created, 1);
+ devFS = DevFS(vmService, 'test', tempDir);
+ await devFS.create();
+ vmService.expectMessages(<String>['create test']);
expect(devFS.assetPathsToEvict, isEmpty);
final UpdateFSReport report = await devFS.update(
@@ -172,8 +126,9 @@
trackWidgetCreation: false,
invalidatedFiles: <Uri>[],
);
-
- expect(writtenFiles.single, contains('foo.txt.dill'));
+ vmService.expectMessages(<String>[
+ 'writeFile test lib/foo.txt.dill',
+ ]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(report.syncedBytes, 22);
expect(report.success, true);
@@ -182,8 +137,9 @@
});
testUsingContext('delete dev file system', () async {
+ expect(vmService.messages, isEmpty, reason: 'prior test timeout');
await devFS.destroy();
- expect(destroyed, 1);
+ vmService.expectMessages(<String>['destroy test']);
expect(devFS.assetPathsToEvict, isEmpty);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
@@ -191,27 +147,26 @@
testUsingContext('cleanup preexisting file system', () async {
// simulate workspace
- final String filePath = fs.path.join('lib', 'foo.txt');
- final File file = fs.file(filePath);
+ final File file = fs.file(fs.path.join(basePath, filePath));
await file.parent.create(recursive: true);
file.writeAsBytesSync(<int>[1, 2, 3]);
// simulate package
await _createPackage(fs, 'somepkg', 'somefile.txt');
+ devFS = DevFS(vmService, 'test', tempDir);
await devFS.create();
- expect(created, 1);
+ vmService.expectMessages(<String>['create test']);
expect(devFS.assetPathsToEvict, isEmpty);
// Try to create again.
await devFS.create();
- expect(created, 2);
- expect(destroyed, 1);
+ vmService.expectMessages(<String>['create test', 'destroy test', 'create test']);
expect(devFS.assetPathsToEvict, isEmpty);
// Really destroy.
await devFS.destroy();
- expect(destroyed, 2);
+ vmService.expectMessages(<String>['destroy test']);
expect(devFS.assetPathsToEvict, isEmpty);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
@@ -219,20 +174,113 @@
});
}
-class MockVMService extends Mock implements VMService {}
+class MockVMService extends BasicMock implements VMService {
+ MockVMService() {
+ _vm = MockVM(this);
+ }
-class MockDevFSOperations extends Mock implements DevFSOperations {}
+ Uri _httpAddress;
+ HttpServer _server;
+ MockVM _vm;
-class MockPlatform extends Mock implements Platform {}
+ @override
+ Uri get httpAddress => _httpAddress;
+ @override
+ VM get vm => _vm;
+
+ Future<void> setUp() async {
+ try {
+ _server = await HttpServer.bind(InternetAddress.loopbackIPv6, 0);
+ _httpAddress = Uri.parse('http://[::1]:${_server.port}');
+ } on SocketException {
+ // Fall back to IPv4 if the host doesn't support binding to IPv6 localhost
+ _server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
+ _httpAddress = Uri.parse('http://127.0.0.1:${_server.port}');
+ }
+ _server.listen((HttpRequest request) {
+ final String fsName = request.headers.value('dev_fs_name');
+ final String devicePath = utf8.decode(base64.decode(request.headers.value('dev_fs_uri_b64')));
+ messages.add('writeFile $fsName $devicePath');
+ request.drain<List<int>>().then<void>((List<int> value) {
+ request.response
+ ..write('Got it')
+ ..close();
+ });
+ });
+ }
+
+ Future<void> tearDown() async {
+ await _server?.close();
+ }
+
+ @override
+ dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
+}
+
+class MockVM implements VM {
+ MockVM(this._service);
+
+ final MockVMService _service;
+ final Uri _baseUri = Uri.parse('file:///tmp/devfs/test');
+ bool _devFSExists = false;
+
+ static const int kFileSystemAlreadyExists = 1001;
+
+ @override
+ Future<Map<String, dynamic>> createDevFS(String fsName) async {
+ _service.messages.add('create $fsName');
+ if (_devFSExists) {
+ throw rpc.RpcException(kFileSystemAlreadyExists, 'File system already exists');
+ }
+ _devFSExists = true;
+ return <String, dynamic>{'uri': '$_baseUri'};
+ }
+
+ @override
+ Future<Map<String, dynamic>> deleteDevFS(String fsName) async {
+ _service.messages.add('destroy $fsName');
+ _devFSExists = false;
+ return <String, dynamic>{'type': 'Success'};
+ }
+
+ @override
+ Future<Map<String, dynamic>> invokeRpcRaw(
+ String method, {
+ Map<String, dynamic> params = const <String, dynamic>{},
+ Duration timeout,
+ bool timeoutFatal = true,
+ }) async {
+ _service.messages.add('$method $params');
+ return <String, dynamic>{'success': true};
+ }
+
+ @override
+ dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
+}
+
+
+final List<Directory> _tempDirs = <Directory>[];
final Map <String, Uri> _packages = <String, Uri>{};
+Directory _newTempDir(FileSystem fs) {
+ final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_devfs${_tempDirs.length}_test.');
+ _tempDirs.add(tempDir);
+ return tempDir;
+}
+
+void _cleanupTempDirs() {
+ while (_tempDirs.isNotEmpty)
+ tryToDelete(_tempDirs.removeLast());
+}
Future<void> _createPackage(FileSystem fs, String pkgName, String pkgFileName, { bool doubleSlash = false }) async {
- String pkgFilePath = fs.path.join(pkgName, 'lib', pkgFileName);
+ final Directory pkgTempDir = _newTempDir(fs);
+ String pkgFilePath = fs.path.join(pkgTempDir.path, pkgName, 'lib', pkgFileName);
if (doubleSlash) {
// Force two separators into the path.
- pkgFilePath = fs.path.join(pkgName, 'lib', pkgFileName);
+ final String doubleSlash = fs.path.separator + fs.path.separator;
+ pkgFilePath = pkgTempDir.path + doubleSlash + fs.path.join(pkgName, 'lib', pkgFileName);
}
final File pkgFile = fs.file(pkgFilePath);
await pkgFile.parent.create(recursive: true);
@@ -242,5 +290,6 @@
_packages.forEach((String pkgName, Uri pkgUri) {
sb.writeln('$pkgName:$pkgUri');
});
- fs.file('.packages').writeAsStringSync(sb.toString());
+ fs.file(fs.path.join(_tempDirs[0].path, '.packages')).writeAsStringSync(sb.toString());
}
+
diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart
index 1d1da52..8c318bc 100644
--- a/packages/flutter_tools/test/src/mocks.dart
+++ b/packages/flutter_tools/test/src/mocks.dart
@@ -14,6 +14,7 @@
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/compile.dart';
+import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/ios/devices.dart';
import 'package:flutter_tools/src/ios/simulators.dart';
@@ -477,6 +478,30 @@
}
}
+class MockDevFSOperations extends BasicMock implements DevFSOperations {
+ Map<Uri, DevFSContent> devicePathToContent = <Uri, DevFSContent>{};
+
+ @override
+ Future<Uri> create(String fsName) async {
+ messages.add('create $fsName');
+ return Uri.parse('file:///$fsName');
+ }
+
+ @override
+ Future<dynamic> destroy(String fsName) async {
+ messages.add('destroy $fsName');
+ }
+
+ @override
+ Future<dynamic> writeFile(String fsName, Uri deviceUri, DevFSContent content) async {
+ String message = 'writeFile $fsName $deviceUri';
+ if (content is DevFSFileContent) {
+ message += ' ${content.file.path}';
+ }
+ messages.add(message);
+ devicePathToContent[deviceUri] = content;
+ }
+}
class MockResidentCompiler extends BasicMock implements ResidentCompiler {
@override