mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
This reverts commit 445505d6f2d4a0b658385f0ef735d3a826ca49ab.
This commit is contained in:
parent
b7bd5768c6
commit
27876e09be
@ -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);
|
||||
content.copyToFile(outputFile);
|
||||
final List<int> data = await content.contentsAsBytes();
|
||||
outputFile.writeAsBytesSync(data);
|
||||
}
|
||||
|
||||
Future<void> run(List<String> args) async {
|
||||
@ -70,10 +71,12 @@ Future<void> run(List<String> args) async {
|
||||
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]);
|
||||
|
||||
@ -147,7 +147,7 @@ Future<void> build({
|
||||
if (assets == null)
|
||||
throwToolExit('Error building assets', exitCode: 1);
|
||||
|
||||
assemble(
|
||||
await assemble(
|
||||
buildMode: buildMode,
|
||||
assetBundle: assets,
|
||||
kernelContent: kernelContent,
|
||||
@ -182,14 +182,14 @@ Future<AssetBundle> buildAssets({
|
||||
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 @@ void assemble({
|
||||
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());
|
||||
}));
|
||||
}
|
||||
|
||||
@ -242,7 +242,7 @@ class TestCommand extends FastFlutterCommand {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,25 +38,21 @@ abstract class DevFSContent {
|
||||
/// 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);
|
||||
|
||||
@ -106,6 +102,9 @@ class DevFSFileContent extends DevFSContent {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> get fileDependencies => <String>[_getFile().path];
|
||||
|
||||
@override
|
||||
bool get isModified {
|
||||
final FileStat _oldFileStat = _fileStat;
|
||||
@ -136,12 +135,10 @@ class DevFSFileContent extends DevFSContent {
|
||||
}
|
||||
|
||||
@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 @@ class DevFSByteContent extends DevFSContent {
|
||||
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 DevFSStringContent extends DevFSByteContent {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ class DevFSOperations {
|
||||
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 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.
|
||||
class UpdateFSReport {
|
||||
UpdateFSReport({
|
||||
@ -319,7 +353,8 @@ class DevFS {
|
||||
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 @@ class DevFS {
|
||||
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 @@ class DevFS {
|
||||
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);
|
||||
|
||||
@ -52,7 +52,7 @@ Future<void> _buildAssets(
|
||||
|
||||
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();
|
||||
|
||||
@ -120,7 +120,8 @@ class ResidentWebRunner extends ResidentRunner {
|
||||
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);
|
||||
|
||||
@ -119,7 +119,7 @@ class WebDevice extends Device {
|
||||
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);
|
||||
|
||||
@ -74,7 +74,7 @@ $fontsSection
|
||||
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 @@ $fontsSection
|
||||
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),
|
||||
);
|
||||
}
|
||||
|
||||
@ -79,14 +79,14 @@ $assetsSection
|
||||
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 @@ $assetsSection
|
||||
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 @@ $assetsSection
|
||||
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>{
|
||||
|
||||
@ -50,7 +50,7 @@ void main() {
|
||||
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>{
|
||||
|
||||
@ -76,7 +76,7 @@ flutter:
|
||||
// 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 @@ flutter:
|
||||
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,
|
||||
|
||||
@ -7,7 +7,6 @@ import 'dart:async';
|
||||
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 @@ void main() {
|
||||
}
|
||||
|
||||
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 @@
|
||||
|
||||
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 @@ import 'src/mocks.dart';
|
||||
|
||||
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 @@ void main() {
|
||||
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 @@ void main() {
|
||||
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 @@ void main() {
|
||||
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 @@ void main() {
|
||||
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 @@ void main() {
|
||||
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 @@ void main() {
|
||||
});
|
||||
|
||||
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 @@ void main() {
|
||||
|
||||
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 @@ 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;
|
||||
|
||||
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 @@ Future<void> _createPackage(FileSystem fs, String pkgName, String pkgFileName, {
|
||||
_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());
|
||||
}
|
||||
|
||||
|
||||
@ -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/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 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 {
|
||||
@override
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user