Unverified Commit 27876e09 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Revert "Devfs cleanup and testing (#33374)" (#33673)

This reverts commit 445505d6.
parent b7bd5768
...@@ -36,9 +36,10 @@ Future<void> main(List<String> args) { ...@@ -36,9 +36,10 @@ Future<void> main(List<String> args) {
}); });
} }
void writeFile(libfs.File outputFile, DevFSContent content) { Future<void> writeFile(libfs.File outputFile, DevFSContent content) async {
outputFile.createSync(recursive: true); outputFile.createSync(recursive: true);
content.copyToFile(outputFile); final List<int> data = await content.contentsAsBytes();
outputFile.writeAsBytesSync(data);
} }
Future<void> run(List<String> args) async { Future<void> run(List<String> args) async {
...@@ -70,10 +71,12 @@ Future<void> run(List<String> args) async { ...@@ -70,10 +71,12 @@ Future<void> run(List<String> args) async {
exit(1); exit(1);
} }
final List<Future<void>> calls = <Future<void>>[];
assets.entries.forEach((String fileName, DevFSContent content) { assets.entries.forEach((String fileName, DevFSContent content) {
final libfs.File outputFile = libfs.fs.file(libfs.fs.path.join(assetDir, fileName)); 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]; final String outputMan = argResults[_kOptionAssetManifestOut];
await writeFuchsiaManifest(assets, argResults[_kOptionAsset], outputMan, argResults[_kOptionComponentName]); await writeFuchsiaManifest(assets, argResults[_kOptionAsset], outputMan, argResults[_kOptionComponentName]);
......
...@@ -147,7 +147,7 @@ Future<void> build({ ...@@ -147,7 +147,7 @@ Future<void> build({
if (assets == null) if (assets == null)
throwToolExit('Error building assets', exitCode: 1); throwToolExit('Error building assets', exitCode: 1);
assemble( await assemble(
buildMode: buildMode, buildMode: buildMode,
assetBundle: assets, assetBundle: assets,
kernelContent: kernelContent, kernelContent: kernelContent,
...@@ -182,14 +182,14 @@ Future<AssetBundle> buildAssets({ ...@@ -182,14 +182,14 @@ Future<AssetBundle> buildAssets({
return assetBundle; return assetBundle;
} }
void assemble({ Future<void> assemble({
BuildMode buildMode, BuildMode buildMode,
AssetBundle assetBundle, AssetBundle assetBundle,
DevFSContent kernelContent, DevFSContent kernelContent,
String privateKeyPath = defaultPrivateKeyPath, String privateKeyPath = defaultPrivateKeyPath,
String assetDirPath, String assetDirPath,
String compilationTraceFilePath, String compilationTraceFilePath,
}) { }) async {
assetDirPath ??= getAssetBuildDirectory(); assetDirPath ??= getAssetBuildDirectory();
printTrace('Building bundle'); printTrace('Building bundle');
...@@ -214,21 +214,22 @@ void assemble({ ...@@ -214,21 +214,22 @@ void assemble({
printTrace('Writing asset files to $assetDirPath'); printTrace('Writing asset files to $assetDirPath');
ensureDirectoryExists(assetDirPath); ensureDirectoryExists(assetDirPath);
writeBundle(fs.directory(assetDirPath), assetEntries); await writeBundle(fs.directory(assetDirPath), assetEntries);
printTrace('Wrote $assetDirPath'); printTrace('Wrote $assetDirPath');
} }
void writeBundle( Future<void> writeBundle(
Directory bundleDir, Directory bundleDir,
Map<String, DevFSContent> assetEntries, Map<String, DevFSContent> assetEntries,
) { ) async {
if (bundleDir.existsSync()) if (bundleDir.existsSync())
bundleDir.deleteSync(recursive: true); bundleDir.deleteSync(recursive: true);
bundleDir.createSync(recursive: true); bundleDir.createSync(recursive: true);
for (MapEntry<String, DevFSContent> entry in assetEntries.entries) { await Future.wait<void>(
final File file = fs.file(fs.path.join(bundleDir.path, entry.key)); assetEntries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
file.parent.createSync(recursive: true); final File file = fs.file(fs.path.join(bundleDir.path, entry.key));
entry.value.copyToFile(file); file.parent.createSync(recursive: true);
} await file.writeAsBytes(await entry.value.contentsAsBytes());
}));
} }
...@@ -242,7 +242,7 @@ class TestCommand extends FastFlutterCommand { ...@@ -242,7 +242,7 @@ class TestCommand extends FastFlutterCommand {
throwToolExit('Error: Failed to build asset bundle'); throwToolExit('Error: Failed to build asset bundle');
} }
if (_needRebuild(assetBundle.entries)) { 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); assetBundle.entries);
} }
} }
......
...@@ -38,25 +38,21 @@ abstract class DevFSContent { ...@@ -38,25 +38,21 @@ abstract class DevFSContent {
/// or if the given time is null. /// or if the given time is null.
bool isModifiedAfter(DateTime time); bool isModifiedAfter(DateTime time);
/// The number of bytes in this file.
int get size; int get size;
/// Returns the raw bytes of this file. Future<List<int>> contentsAsBytes();
List<int> contentsAsBytes();
/// Returns a gzipped representation of the contents of this file. Stream<List<int>> contentsAsStream();
List<int> contentsAsCompressedBytes() {
return gzip.encode(contentsAsBytes()); Stream<List<int>> contentsAsCompressedStream() {
return contentsAsStream().cast<List<int>>().transform<List<int>>(gzip.encoder);
} }
/// Copies the content into the provided file. /// Return the list of files this content depends on.
/// List<String> get fileDependencies => <String>[];
/// Requires that the `destination` directory already exists, but the target
/// file need not.
void copyToFile(File destination);
} }
/// File content to be copied to the device. // File content to be copied to the device.
class DevFSFileContent extends DevFSContent { class DevFSFileContent extends DevFSContent {
DevFSFileContent(this.file); DevFSFileContent(this.file);
...@@ -106,6 +102,9 @@ class DevFSFileContent extends DevFSContent { ...@@ -106,6 +102,9 @@ class DevFSFileContent extends DevFSContent {
} }
} }
@override
List<String> get fileDependencies => <String>[_getFile().path];
@override @override
bool get isModified { bool get isModified {
final FileStat _oldFileStat = _fileStat; final FileStat _oldFileStat = _fileStat;
...@@ -136,12 +135,10 @@ class DevFSFileContent extends DevFSContent { ...@@ -136,12 +135,10 @@ class DevFSFileContent extends DevFSContent {
} }
@override @override
List<int> contentsAsBytes() => _getFile().readAsBytesSync().cast<int>(); Future<List<int>> contentsAsBytes() => _getFile().readAsBytes();
@override @override
void copyToFile(File destination) { Stream<List<int>> contentsAsStream() => _getFile().openRead();
_getFile().copySync(destination.path);
}
} }
/// Byte content to be copied to the device. /// Byte content to be copied to the device.
...@@ -178,15 +175,14 @@ class DevFSByteContent extends DevFSContent { ...@@ -178,15 +175,14 @@ class DevFSByteContent extends DevFSContent {
int get size => _bytes.length; int get size => _bytes.length;
@override @override
List<int> contentsAsBytes() => _bytes; Future<List<int>> contentsAsBytes() async => _bytes;
@override @override
void copyToFile(File destination) { Stream<List<int>> contentsAsStream() =>
destination.writeAsBytesSync(contentsAsBytes()); 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 { class DevFSStringContent extends DevFSByteContent {
DevFSStringContent(String string) DevFSStringContent(String string)
: _string = string, : _string = string,
...@@ -207,30 +203,76 @@ class DevFSStringContent extends DevFSByteContent { ...@@ -207,30 +203,76 @@ class DevFSStringContent extends DevFSByteContent {
} }
} }
class DevFSOperations { /// Abstract DevFS operations interface.
DevFSOperations(this.vmService, this.fsName) abstract class DevFSOperations {
: httpAddress = vmService.httpAddress; Future<Uri> create(String fsName);
Future<dynamic> destroy(String fsName);
final VMService vmService; Future<dynamic> writeFile(String fsName, Uri deviceUri, DevFSContent content);
final String fsName; }
final Uri httpAddress;
final HttpClient _client = HttpClient();
static const int kMaxInFlight = 6; /// An implementation of [DevFSOperations] that speaks to the
/// vm service.
class ServiceProtocolDevFSOperations implements DevFSOperations {
ServiceProtocolDevFSOperations(this.vmService);
int _inFlight = 0; final VMService vmService;
Map<Uri, DevFSContent> _outstanding;
Completer<void> _completer;
@override
Future<Uri> create(String fsName) async { Future<Uri> create(String fsName) async {
final Map<String, dynamic> response = await vmService.vm.createDevFS(fsName); final Map<String, dynamic> response = await vmService.vm.createDevFS(fsName);
return Uri.parse(response['uri']); return Uri.parse(response['uri']);
} }
Future<void> destroy(String fsName) async { @override
Future<dynamic> destroy(String fsName) async {
await vmService.vm.deleteDevFS(fsName); 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;
static const int kMaxInFlight = 6;
int _inFlight = 0;
Map<Uri, DevFSContent> _outstanding;
Completer<void> _completer;
final HttpClient _client = HttpClient();
Future<void> write(Map<Uri, DevFSContent> entries) async { Future<void> write(Map<Uri, DevFSContent> entries) async {
_client.maxConnectionsPerHost = kMaxInFlight; _client.maxConnectionsPerHost = kMaxInFlight;
_completer = Completer<void>(); _completer = Completer<void>();
...@@ -259,9 +301,9 @@ class DevFSOperations { ...@@ -259,9 +301,9 @@ class DevFSOperations {
final HttpClientRequest request = await _client.putUrl(httpAddress); final HttpClientRequest request = await _client.putUrl(httpAddress);
request.headers.removeAll(HttpHeaders.acceptEncodingHeader); request.headers.removeAll(HttpHeaders.acceptEncodingHeader);
request.headers.add('dev_fs_name', fsName); request.headers.add('dev_fs_name', fsName);
request.headers.add('dev_fs_uri_b64', request.headers.add('dev_fs_uri_b64', base64.encode(utf8.encode('$deviceUri')));
base64.encode(utf8.encode(deviceUri.toString()))); final Stream<List<int>> contents = content.contentsAsCompressedStream();
request.add(content.contentsAsCompressedBytes()); await request.addStream(contents);
final HttpClientResponse response = await request.close(); final HttpClientResponse response = await request.close();
await response.drain<void>(); await response.drain<void>();
} catch (error, trace) { } catch (error, trace) {
...@@ -275,14 +317,6 @@ class DevFSOperations { ...@@ -275,14 +317,6 @@ class DevFSOperations {
} }
} }
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. // Basic statistics for DevFS update operation.
class UpdateFSReport { class UpdateFSReport {
UpdateFSReport({ UpdateFSReport({
...@@ -319,7 +353,8 @@ class DevFS { ...@@ -319,7 +353,8 @@ class DevFS {
this.fsName, this.fsName,
this.rootDirectory, { this.rootDirectory, {
String packagesFilePath, String packagesFilePath,
}) : _operations = DevFSOperations(serviceProtocol, fsName), }) : _operations = ServiceProtocolDevFSOperations(serviceProtocol),
_httpWriter = _DevFSHttpWriter(fsName, serviceProtocol),
_packagesFilePath = packagesFilePath ?? fs.path.join(rootDirectory.path, kPackagesFileName); _packagesFilePath = packagesFilePath ?? fs.path.join(rootDirectory.path, kPackagesFileName);
DevFS.operations( DevFS.operations(
...@@ -327,9 +362,11 @@ class DevFS { ...@@ -327,9 +362,11 @@ class DevFS {
this.fsName, this.fsName,
this.rootDirectory, { this.rootDirectory, {
String packagesFilePath, String packagesFilePath,
}) : _packagesFilePath = packagesFilePath ?? fs.path.join(rootDirectory.path, kPackagesFileName); }) : _httpWriter = null,
_packagesFilePath = packagesFilePath ?? fs.path.join(rootDirectory.path, kPackagesFileName);
final DevFSOperations _operations; final DevFSOperations _operations;
final _DevFSHttpWriter _httpWriter;
final String fsName; final String fsName;
final Directory rootDirectory; final Directory rootDirectory;
String _packagesFilePath; String _packagesFilePath;
...@@ -452,7 +489,7 @@ class DevFS { ...@@ -452,7 +489,7 @@ class DevFS {
printTrace('Updating files'); printTrace('Updating files');
if (dirtyEntries.isNotEmpty) { if (dirtyEntries.isNotEmpty) {
try { try {
await _operations.write(dirtyEntries); await _httpWriter.write(dirtyEntries);
} on SocketException catch (socketException, stackTrace) { } on SocketException catch (socketException, stackTrace) {
printTrace('DevFS sync failed. Lost connection to device: $socketException'); printTrace('DevFS sync failed. Lost connection to device: $socketException');
throw DevFSException('Lost connection to device.', socketException, stackTrace); throw DevFSException('Lost connection to device.', socketException, stackTrace);
......
...@@ -52,7 +52,7 @@ Future<void> _buildAssets( ...@@ -52,7 +52,7 @@ Future<void> _buildAssets(
final Map<String, DevFSContent> assetEntries = final Map<String, DevFSContent> assetEntries =
Map<String, DevFSContent>.from(assets.entries); 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 appName = fuchsiaProject.project.manifest.appName;
final String outDir = getFuchsiaBuildDirectory(); final String outDir = getFuchsiaBuildDirectory();
......
...@@ -120,7 +120,8 @@ class ResidentWebRunner extends ResidentRunner { ...@@ -120,7 +120,8 @@ class ResidentWebRunner extends ResidentRunner {
if (build != 0) { if (build != 0) {
throwToolExit('Error: Failed to build asset bundle'); 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 // Step 2: Start an HTTP server
_server = WebAssetServer(flutterProject, target, ipv6); _server = WebAssetServer(flutterProject, target, ipv6);
......
...@@ -119,7 +119,7 @@ class WebDevice extends Device { ...@@ -119,7 +119,7 @@ class WebDevice extends Device {
if (build != 0) { if (build != 0) {
throwToolExit('Error: Failed to build asset bundle'); throwToolExit('Error: Failed to build asset bundle');
} }
writeBundle(fs.directory(getAssetBuildDirectory()), assetBundle.entries); await writeBundle(fs.directory(getAssetBuildDirectory()), assetBundle.entries);
_package = package; _package = package;
_server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); _server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
......
...@@ -74,7 +74,7 @@ $fontsSection ...@@ -74,7 +74,7 @@ $fontsSection
final String entryKey = 'packages/$packageName/$packageFont'; final String entryKey = 'packages/$packageName/$packageFont';
expect(bundle.entries.containsKey(entryKey), true); expect(bundle.entries.containsKey(entryKey), true);
expect( expect(
utf8.decode(bundle.entries[entryKey].contentsAsBytes()), utf8.decode(await bundle.entries[entryKey].contentsAsBytes()),
packageFont, packageFont,
); );
} }
...@@ -82,14 +82,14 @@ $fontsSection ...@@ -82,14 +82,14 @@ $fontsSection
for (String localFont in localFonts) { for (String localFont in localFonts) {
expect(bundle.entries.containsKey(localFont), true); expect(bundle.entries.containsKey(localFont), true);
expect( expect(
utf8.decode(bundle.entries[localFont].contentsAsBytes()), utf8.decode(await bundle.entries[localFont].contentsAsBytes()),
localFont, localFont,
); );
} }
} }
expect( expect(
json.decode(utf8.decode(bundle.entries['FontManifest.json'].contentsAsBytes())), json.decode(utf8.decode(await bundle.entries['FontManifest.json'].contentsAsBytes())),
json.decode(expectedAssetManifest), json.decode(expectedAssetManifest),
); );
} }
......
...@@ -79,14 +79,14 @@ $assetsSection ...@@ -79,14 +79,14 @@ $assetsSection
final String entryKey = Uri.encodeFull('packages/$packageName/$asset'); final String entryKey = Uri.encodeFull('packages/$packageName/$asset');
expect(bundle.entries.containsKey(entryKey), true, reason: 'Cannot find key on bundle: $entryKey'); expect(bundle.entries.containsKey(entryKey), true, reason: 'Cannot find key on bundle: $entryKey');
expect( expect(
utf8.decode(bundle.entries[entryKey].contentsAsBytes()), utf8.decode(await bundle.entries[entryKey].contentsAsBytes()),
asset, asset,
); );
} }
} }
expect( expect(
utf8.decode(bundle.entries['AssetManifest.json'].contentsAsBytes()), utf8.decode(await bundle.entries['AssetManifest.json'].contentsAsBytes()),
expectedAssetManifest, expectedAssetManifest,
); );
} }
...@@ -126,11 +126,11 @@ $assetsSection ...@@ -126,11 +126,11 @@ $assetsSection
expect(bundle.entries.length, 3); // LICENSE, AssetManifest, FontManifest expect(bundle.entries.length, 3); // LICENSE, AssetManifest, FontManifest
const String expectedAssetManifest = '{}'; const String expectedAssetManifest = '{}';
expect( expect(
utf8.decode(bundle.entries['AssetManifest.json'].contentsAsBytes()), utf8.decode(await bundle.entries['AssetManifest.json'].contentsAsBytes()),
expectedAssetManifest, expectedAssetManifest,
); );
expect( expect(
utf8.decode(bundle.entries['FontManifest.json'].contentsAsBytes()), utf8.decode(await bundle.entries['FontManifest.json'].contentsAsBytes()),
'[]', '[]',
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -153,11 +153,11 @@ $assetsSection ...@@ -153,11 +153,11 @@ $assetsSection
expect(bundle.entries.length, 3); // LICENSE, AssetManifest, FontManifest expect(bundle.entries.length, 3); // LICENSE, AssetManifest, FontManifest
const String expectedAssetManifest = '{}'; const String expectedAssetManifest = '{}';
expect( expect(
utf8.decode(bundle.entries['AssetManifest.json'].contentsAsBytes()), utf8.decode(await bundle.entries['AssetManifest.json'].contentsAsBytes()),
expectedAssetManifest, expectedAssetManifest,
); );
expect( expect(
utf8.decode(bundle.entries['FontManifest.json'].contentsAsBytes()), utf8.decode(await bundle.entries['FontManifest.json'].contentsAsBytes()),
'[]', '[]',
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
......
...@@ -50,7 +50,7 @@ void main() { ...@@ -50,7 +50,7 @@ void main() {
expect(bundle.entries.length, 1); expect(bundle.entries.length, 1);
const String expectedAssetManifest = '{}'; const String expectedAssetManifest = '{}';
expect( expect(
utf8.decode(bundle.entries['AssetManifest.json'].contentsAsBytes()), utf8.decode(await bundle.entries['AssetManifest.json'].contentsAsBytes()),
expectedAssetManifest, expectedAssetManifest,
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
......
...@@ -76,7 +76,7 @@ flutter: ...@@ -76,7 +76,7 @@ flutter:
// The main asset file, /a/b/c/foo, and its variants exist. // The main asset file, /a/b/c/foo, and its variants exist.
for (String asset in assets) { for (String asset in assets) {
expect(bundle.entries.containsKey(asset), true); 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(); fs.file(fixPath('a/b/c/foo')).deleteSync();
...@@ -88,7 +88,7 @@ flutter: ...@@ -88,7 +88,7 @@ flutter:
expect(bundle.entries.containsKey('a/b/c/foo'), false); expect(bundle.entries.containsKey('a/b/c/foo'), false);
for (String asset in assets.skip(1)) { for (String asset in assets.skip(1)) {
expect(bundle.entries.containsKey(asset), true); 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>{ }, overrides: <Type, Generator>{
FileSystem: () => testFileSystem, FileSystem: () => testFileSystem,
......
...@@ -7,7 +7,6 @@ import 'dart:async'; ...@@ -7,7 +7,6 @@ import 'dart:async';
import 'package:flutter_tools/src/asset.dart'; import 'package:flutter_tools/src/asset.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/convert.dart';
import 'src/common.dart'; import 'src/common.dart';
import 'src/context.dart'; import 'src/context.dart';
...@@ -66,5 +65,5 @@ void main() { ...@@ -66,5 +65,5 @@ void main() {
} }
Future<String> getValueAsString(String key, AssetBundle asset) async { Future<String> getValueAsString(String key, AssetBundle asset) async {
return utf8.decode(asset.entries[key].contentsAsBytes()); return String.fromCharCodes(await asset.entries[key].contentsAsBytes());
} }
...@@ -4,17 +4,15 @@ ...@@ -4,17 +4,15 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; // ignore: dart_io_import
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:file/memory.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/file_system.dart';
import 'package:flutter_tools/src/base/io.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/devfs.dart';
import 'package:flutter_tools/src/vmservice.dart'; import 'package:flutter_tools/src/vmservice.dart';
import 'package:json_rpc_2/json_rpc_2.dart' as rpc; import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
import 'package:mockito/mockito.dart';
import 'src/common.dart'; import 'src/common.dart';
import 'src/context.dart'; import 'src/context.dart';
...@@ -22,39 +20,17 @@ import 'src/mocks.dart'; ...@@ -22,39 +20,17 @@ import 'src/mocks.dart';
void main() { void main() {
FileSystem fs; FileSystem fs;
MockPlatform mockPlatform; String filePath;
Directory tempDir;
setUp(() { String basePath;
fs = MemoryFileSystem(style: FileSystemStyle.posix); DevFS devFS;
mockPlatform = MockPlatform();
when(mockPlatform.pathSeparator).thenReturn('/'); setUpAll(() {
when(mockPlatform.isWindows).thenReturn(false); fs = MemoryFileSystem();
filePath = fs.path.join('lib', 'foo.txt');
}); });
group('DevFSContent', () { 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', () { test('bytes', () {
final DevFSByteContent content = DevFSByteContent(<int>[4, 5, 6]); final DevFSByteContent content = DevFSByteContent(<int>[4, 5, 6]);
expect(content.bytes, orderedEquals(<int>[4, 5, 6])); expect(content.bytes, orderedEquals(<int>[4, 5, 6]));
...@@ -65,7 +41,6 @@ void main() { ...@@ -65,7 +41,6 @@ void main() {
expect(content.isModified, isTrue); expect(content.isModified, isTrue);
expect(content.isModified, isFalse); expect(content.isModified, isFalse);
}); });
test('string', () { test('string', () {
final DevFSStringContent content = DevFSStringContent('some string'); final DevFSStringContent content = DevFSStringContent('some string');
expect(content.string, 'some string'); expect(content.string, 'some string');
...@@ -83,9 +58,7 @@ void main() { ...@@ -83,9 +58,7 @@ void main() {
expect(content.isModified, isTrue); expect(content.isModified, isTrue);
expect(content.isModified, isFalse); expect(content.isModified, isFalse);
}); });
testUsingContext('file', () async { testUsingContext('file', () async {
final String filePath = fs.path.join('lib', 'foo.txt');
final File file = fs.file(filePath); final File file = fs.file(filePath);
final DevFSFileContent content = DevFSFileContent(file); final DevFSFileContent content = DevFSFileContent(file);
expect(content.isModified, isFalse); expect(content.isModified, isFalse);
...@@ -100,9 +73,10 @@ void main() { ...@@ -100,9 +73,10 @@ void main() {
expect(content.isModifiedAfter(null), isTrue); expect(content.isModifiedAfter(null), isTrue);
file.writeAsBytesSync(<int>[2, 3, 4], flush: true); file.writeAsBytesSync(<int>[2, 3, 4], flush: true);
expect(content.fileDependencies, <String>[filePath]);
expect(content.isModified, isTrue); expect(content.isModified, isTrue);
expect(content.isModified, isFalse); expect(content.isModified, isFalse);
expect(content.contentsAsBytes(), <int>[2, 3, 4]); expect(await content.contentsAsBytes(), <int>[2, 3, 4]);
updateFileModificationTime(file.path, fiveSecondsAgo, 0); updateFileModificationTime(file.path, fiveSecondsAgo, 0);
expect(content.isModified, isFalse); expect(content.isModified, isFalse);
expect(content.isModified, isFalse); expect(content.isModified, isFalse);
...@@ -113,56 +87,36 @@ void main() { ...@@ -113,56 +87,36 @@ void main() {
expect(content.isModified, isFalse); expect(content.isModified, isFalse);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
}, skip: platform.isWindows); // Still flaky, but only on CI :( }, skip: Platform.isWindows); // TODO(jonahwilliams): fix or disable this functionality.
}); });
group('devfs remote', () { group('devfs remote', () {
DevFS devFS; MockVMService vmService;
MockResidentCompiler residentCompiler; final MockResidentCompiler residentCompiler = MockResidentCompiler();
MockDevFSOperations mockDevFSOperations;
int created; setUpAll(() async {
int destroyed; tempDir = _newTempDir(fs);
List<String> writtenFiles; basePath = tempDir.path;
bool exists; vmService = MockVMService();
await vmService.setUp();
setUp(() async { });
mockDevFSOperations = MockDevFSOperations(); tearDownAll(() async {
devFS = DevFS.operations(mockDevFSOperations, 'test', fs.currentDirectory); await vmService.tearDown();
residentCompiler = MockResidentCompiler(); _cleanupTempDirs();
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()));
});
}); });
testUsingContext('create dev file system', () async { testUsingContext('create dev file system', () async {
// simulate workspace // simulate workspace
final String filePath = fs.path.join('lib', 'foo.txt'); final File file = fs.file(fs.path.join(basePath, filePath));
final File file = fs.file(filePath);
await file.parent.create(recursive: true); await file.parent.create(recursive: true);
file.writeAsBytesSync(<int>[1, 2, 3]); file.writeAsBytesSync(<int>[1, 2, 3]);
// simulate package // simulate package
await _createPackage(fs, 'somepkg', 'somefile.txt'); 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); expect(devFS.assetPathsToEvict, isEmpty);
final UpdateFSReport report = await devFS.update( final UpdateFSReport report = await devFS.update(
...@@ -172,8 +126,9 @@ void main() { ...@@ -172,8 +126,9 @@ void main() {
trackWidgetCreation: false, trackWidgetCreation: false,
invalidatedFiles: <Uri>[], invalidatedFiles: <Uri>[],
); );
vmService.expectMessages(<String>[
expect(writtenFiles.single, contains('foo.txt.dill')); 'writeFile test lib/foo.txt.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty); expect(devFS.assetPathsToEvict, isEmpty);
expect(report.syncedBytes, 22); expect(report.syncedBytes, 22);
expect(report.success, true); expect(report.success, true);
...@@ -182,8 +137,9 @@ void main() { ...@@ -182,8 +137,9 @@ void main() {
}); });
testUsingContext('delete dev file system', () async { testUsingContext('delete dev file system', () async {
expect(vmService.messages, isEmpty, reason: 'prior test timeout');
await devFS.destroy(); await devFS.destroy();
expect(destroyed, 1); vmService.expectMessages(<String>['destroy test']);
expect(devFS.assetPathsToEvict, isEmpty); expect(devFS.assetPathsToEvict, isEmpty);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
...@@ -191,27 +147,26 @@ void main() { ...@@ -191,27 +147,26 @@ void main() {
testUsingContext('cleanup preexisting file system', () async { testUsingContext('cleanup preexisting file system', () async {
// simulate workspace // simulate workspace
final String filePath = fs.path.join('lib', 'foo.txt'); final File file = fs.file(fs.path.join(basePath, filePath));
final File file = fs.file(filePath);
await file.parent.create(recursive: true); await file.parent.create(recursive: true);
file.writeAsBytesSync(<int>[1, 2, 3]); file.writeAsBytesSync(<int>[1, 2, 3]);
// simulate package // simulate package
await _createPackage(fs, 'somepkg', 'somefile.txt'); await _createPackage(fs, 'somepkg', 'somefile.txt');
devFS = DevFS(vmService, 'test', tempDir);
await devFS.create(); await devFS.create();
expect(created, 1); vmService.expectMessages(<String>['create test']);
expect(devFS.assetPathsToEvict, isEmpty); expect(devFS.assetPathsToEvict, isEmpty);
// Try to create again. // Try to create again.
await devFS.create(); await devFS.create();
expect(created, 2); vmService.expectMessages(<String>['create test', 'destroy test', 'create test']);
expect(destroyed, 1);
expect(devFS.assetPathsToEvict, isEmpty); expect(devFS.assetPathsToEvict, isEmpty);
// Really destroy. // Really destroy.
await devFS.destroy(); await devFS.destroy();
expect(destroyed, 2); vmService.expectMessages(<String>['destroy test']);
expect(devFS.assetPathsToEvict, isEmpty); expect(devFS.assetPathsToEvict, isEmpty);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
...@@ -219,20 +174,113 @@ void main() { ...@@ -219,20 +174,113 @@ void main() {
}); });
} }
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;
@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();
});
});
}
class MockPlatform extends Mock implements Platform {} 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>{}; 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 { 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) { if (doubleSlash) {
// Force two separators into the path. // 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); final File pkgFile = fs.file(pkgFilePath);
await pkgFile.parent.create(recursive: true); await pkgFile.parent.create(recursive: true);
...@@ -242,5 +290,6 @@ Future<void> _createPackage(FileSystem fs, String pkgName, String pkgFileName, { ...@@ -242,5 +290,6 @@ Future<void> _createPackage(FileSystem fs, String pkgName, String pkgFileName, {
_packages.forEach((String pkgName, Uri pkgUri) { _packages.forEach((String pkgName, Uri pkgUri) {
sb.writeln('$pkgName:$pkgUri'); sb.writeln('$pkgName:$pkgUri');
}); });
fs.file('.packages').writeAsStringSync(sb.toString()); fs.file(fs.path.join(_tempDirs[0].path, '.packages')).writeAsStringSync(sb.toString());
} }
...@@ -14,6 +14,7 @@ import 'package:flutter_tools/src/base/io.dart'; ...@@ -14,6 +14,7 @@ import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/compile.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/device.dart';
import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/devices.dart';
import 'package:flutter_tools/src/ios/simulators.dart'; import 'package:flutter_tools/src/ios/simulators.dart';
...@@ -477,6 +478,30 @@ class BasicMock { ...@@ -477,6 +478,30 @@ class BasicMock {
} }
} }
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 { class MockResidentCompiler extends BasicMock implements ResidentCompiler {
@override @override
......
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