flutter/packages/flutter_tools/test/devfs_test.dart
Chris Bracken 251e82d211
Migrate devfs tests to Dart 2 (#21285)
DevFS.update only runs in Dart 2 mode when the generator parameter is
supplied. In Dart 2 mode, both mainPath and pathToReload are required
parameters; this patch marks them as such.

generator is required for running in Dart 2. All call sites other than tests already explicitly set this value.

Note the statements on line 510 and line 516 for why mainPath and pathToReload are required.
2018-08-31 13:31:56 -07:00

547 lines
18 KiB
Dart

// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/asset.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_info.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 'src/common.dart';
import 'src/context.dart';
import 'src/mocks.dart';
void main() {
FileSystem fs;
String filePath;
String filePath2;
Directory tempDir;
String basePath;
DevFS devFS;
final AssetBundle assetBundle = AssetBundleFactory.defaultInstance.createBundle();
setUpAll(() {
fs = new MemoryFileSystem();
filePath = fs.path.join('lib', 'foo.txt');
filePath2 = fs.path.join('foo', 'bar.txt');
});
group('DevFSContent', () {
test('bytes', () {
final DevFSByteContent content = new DevFSByteContent(<int>[4, 5, 6]);
expect(content.bytes, orderedEquals(<int>[4, 5, 6]));
expect(content.isModified, isTrue);
expect(content.isModified, isFalse);
content.bytes = <int>[7, 8, 9, 2];
expect(content.bytes, orderedEquals(<int>[7, 8, 9, 2]));
expect(content.isModified, isTrue);
expect(content.isModified, isFalse);
});
test('string', () {
final DevFSStringContent content = new DevFSStringContent('some string');
expect(content.string, 'some string');
expect(content.bytes, orderedEquals(utf8.encode('some string')));
expect(content.isModified, isTrue);
expect(content.isModified, isFalse);
content.string = 'another string';
expect(content.string, 'another string');
expect(content.bytes, orderedEquals(utf8.encode('another string')));
expect(content.isModified, isTrue);
expect(content.isModified, isFalse);
content.bytes = utf8.encode('foo bar');
expect(content.string, 'foo bar');
expect(content.bytes, orderedEquals(utf8.encode('foo bar')));
expect(content.isModified, isTrue);
expect(content.isModified, isFalse);
});
});
group('devfs local', () {
final MockDevFSOperations devFSOperations = new MockDevFSOperations();
final MockResidentCompiler residentCompiler = new MockResidentCompiler();
setUpAll(() {
tempDir = _newTempDir(fs);
basePath = tempDir.path;
});
tearDownAll(_cleanupTempDirs);
testUsingContext('create dev file system', () async {
// simulate workspace
final File file = fs.file(fs.path.join(basePath, filePath));
await file.parent.create(recursive: true);
file.writeAsBytesSync(<int>[1, 2, 3]);
_packages['my_project'] = fs.path.toUri('lib');
// simulate package
await _createPackage(fs, 'somepkg', 'somefile.txt');
devFS = new DevFS.operations(devFSOperations, 'test', tempDir);
await devFS.create();
devFSOperations.expectMessages(<String>['create test']);
expect(devFS.assetPathsToEvict, isEmpty);
final int bytes = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
);
devFSOperations.expectMessages(<String>[
'writeFile test lib/foo.txt.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('add new file to local file system', () async {
final File file = fs.file(fs.path.join(basePath, filePath2));
await file.parent.create(recursive: true);
file.writeAsBytesSync(<int>[1, 2, 3, 4, 5, 6, 7]);
final int bytes = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
);
devFSOperations.expectMessages(<String>[
'writeFile test lib/foo.txt.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('modify existing file on local file system', () async {
int bytes = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
);
devFSOperations.expectMessages(<String>[
'writeFile test lib/foo.txt.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
final File file = fs.file(fs.path.join(basePath, filePath));
// Set the last modified time to 5 seconds in the past.
updateFileModificationTime(file.path, new DateTime.now(), -5);
bytes = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
);
devFSOperations.expectMessages(<String>[
'writeFile test lib/foo.txt.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
await file.writeAsBytes(<int>[1, 2, 3, 4, 5, 6]);
bytes = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
);
devFSOperations.expectMessages(<String>[
'writeFile test lib/foo.txt.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('delete a file from the local file system', () async {
final File file = fs.file(fs.path.join(basePath, filePath));
await file.delete();
final int bytes = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
);
devFSOperations.expectMessages(<String>[
'deleteFile test lib/foo.txt',
'writeFile test lib/foo.txt.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('add new package', () async {
await _createPackage(fs, 'newpkg', 'anotherfile.txt');
final int bytes = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
);
devFSOperations.expectMessages(<String>[
'writeFile test lib/foo.txt.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('add new package with double slashes in URI', () async {
const String packageName = 'doubleslashpkg';
await _createPackage(fs, packageName, 'somefile.txt', doubleSlash: true);
final Set<String> fileFilter = new Set<String>();
final List<Uri> pkgUris = <Uri>[fs.path.toUri(basePath)]..addAll(_packages.values);
for (Uri pkgUri in pkgUris) {
if (!pkgUri.isAbsolute) {
pkgUri = fs.path.toUri(fs.path.join(basePath, pkgUri.path));
}
fileFilter.addAll(fs.directory(pkgUri)
.listSync(recursive: true)
.whereType<File>()
.map<String>((File file) => canonicalizePath(file.path))
.toList());
}
final int bytes = await devFS.update(
mainPath: 'lib/foo.txt',
fileFilter: fileFilter,
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
);
devFSOperations.expectMessages(<String>[
'writeFile test lib/foo.txt.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('add an asset bundle', () async {
assetBundle.entries['a.txt'] = new DevFSStringContent('abc');
final int bytes = await devFS.update(
mainPath: 'lib/foo.txt',
bundle: assetBundle,
bundleDirty: true,
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
);
devFSOperations.expectMessages(<String>[
'writeFile test ${_inAssetBuildDirectory(fs, 'a.txt')}',
'writeFile test lib/foo.txt.dill',
]);
expect(devFS.assetPathsToEvict, unorderedMatches(<String>['a.txt']));
devFS.assetPathsToEvict.clear();
expect(bytes, 25);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('add a file to the asset bundle - bundleDirty', () async {
assetBundle.entries['b.txt'] = new DevFSStringContent('abcd');
final int bytes = await devFS.update(
mainPath: 'lib/foo.txt',
bundle: assetBundle,
bundleDirty: true,
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
);
// Expect entire asset bundle written because bundleDirty is true
devFSOperations.expectMessages(<String>[
'writeFile test ${_inAssetBuildDirectory(fs, 'a.txt')}',
'writeFile test ${_inAssetBuildDirectory(fs, 'b.txt')}',
'writeFile test lib/foo.txt.dill',
]);
expect(devFS.assetPathsToEvict, unorderedMatches(<String>[
'a.txt', 'b.txt']));
devFS.assetPathsToEvict.clear();
expect(bytes, 29);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('add a file to the asset bundle', () async {
assetBundle.entries['c.txt'] = new DevFSStringContent('12');
final int bytes = await devFS.update(
mainPath: 'lib/foo.txt',
bundle: assetBundle,
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
);
devFSOperations.expectMessages(<String>[
'writeFile test ${_inAssetBuildDirectory(fs, 'c.txt')}',
'writeFile test lib/foo.txt.dill',
]);
expect(devFS.assetPathsToEvict, unorderedMatches(<String>[
'c.txt']));
devFS.assetPathsToEvict.clear();
expect(bytes, 24);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('delete a file from the asset bundle', () async {
assetBundle.entries.remove('c.txt');
final int bytes = await devFS.update(
mainPath: 'lib/foo.txt',
bundle: assetBundle,
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
);
devFSOperations.expectMessages(<String>[
'deleteFile test ${_inAssetBuildDirectory(fs, 'c.txt')}',
'writeFile test lib/foo.txt.dill',
]);
expect(devFS.assetPathsToEvict, unorderedMatches(<String>['c.txt']));
devFS.assetPathsToEvict.clear();
expect(bytes, 22);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('delete all files from the asset bundle', () async {
assetBundle.entries.clear();
final int bytes = await devFS.update(
mainPath: 'lib/foo.txt',
bundle: assetBundle,
bundleDirty: true,
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
);
devFSOperations.expectMessages(<String>[
'deleteFile test ${_inAssetBuildDirectory(fs, 'a.txt')}',
'deleteFile test ${_inAssetBuildDirectory(fs, 'b.txt')}',
'writeFile test lib/foo.txt.dill',
]);
expect(devFS.assetPathsToEvict, unorderedMatches(<String>[
'a.txt', 'b.txt'
]));
devFS.assetPathsToEvict.clear();
expect(bytes, 22);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('delete dev file system', () async {
await devFS.destroy();
devFSOperations.expectMessages(<String>['destroy test']);
expect(devFS.assetPathsToEvict, isEmpty);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
});
group('devfs remote', () {
MockVMService vmService;
final MockResidentCompiler residentCompiler = new MockResidentCompiler();
setUpAll(() async {
tempDir = _newTempDir(fs);
basePath = tempDir.path;
vmService = new MockVMService();
await vmService.setUp();
});
tearDownAll(() async {
await vmService.tearDown();
_cleanupTempDirs();
});
testUsingContext('create dev file system', () async {
// simulate workspace
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 = new DevFS(vmService, 'test', tempDir);
await devFS.create();
vmService.expectMessages(<String>['create test']);
expect(devFS.assetPathsToEvict, isEmpty);
final int bytes = await devFS.update(
mainPath: 'lib/foo.txt',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
);
vmService.expectMessages(<String>[
'writeFile test lib/foo.txt.dill',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 22);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('delete dev file system', () async {
expect(vmService.messages, isEmpty, reason: 'prior test timeout');
await devFS.destroy();
vmService.expectMessages(<String>['destroy test']);
expect(devFS.assetPathsToEvict, isEmpty);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('cleanup preexisting file system', () async {
// simulate workspace
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 = new DevFS(vmService, 'test', tempDir);
await devFS.create();
vmService.expectMessages(<String>['create test']);
expect(devFS.assetPathsToEvict, isEmpty);
// Try to create again.
await devFS.create();
vmService.expectMessages(<String>['create test', 'destroy test', 'create test']);
expect(devFS.assetPathsToEvict, isEmpty);
// Really destroy.
await devFS.destroy();
vmService.expectMessages(<String>['destroy test']);
expect(devFS.assetPathsToEvict, isEmpty);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
});
}
class MockVMService extends BasicMock implements VMService {
Uri _httpAddress;
HttpServer _server;
MockVM _vm;
MockVMService() {
_vm = new MockVM(this);
}
@override
Uri get httpAddress => _httpAddress;
@override
VM get vm => _vm;
Future<Null> 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<Null>((List<int> value) {
request.response
..write('Got it')
..close();
});
});
}
Future<Null> tearDown() async {
await _server?.close();
}
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
class MockVM implements VM {
final MockVMService _service;
final Uri _baseUri = Uri.parse('file:///tmp/devfs/test');
bool _devFSExists = false;
static const int kFileSystemAlreadyExists = 1001;
MockVM(this._service);
@override
Future<Map<String, dynamic>> createDevFS(String fsName) async {
_service.messages.add('create $fsName');
if (_devFSExists) {
throw new 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<Null> _createPackage(FileSystem fs, String pkgName, String pkgFileName, { bool doubleSlash = false }) async {
final Directory pkgTempDir = _newTempDir(fs);
String pkgFilePath = fs.path.join(pkgTempDir.path, pkgName, 'lib', pkgFileName);
if (doubleSlash) {
// Force two separators into the path.
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);
pkgFile.writeAsBytesSync(<int>[11, 12, 13]);
_packages[pkgName] = fs.path.toUri(pkgFile.parent.path);
final StringBuffer sb = new StringBuffer();
_packages.forEach((String pkgName, Uri pkgUri) {
sb.writeln('$pkgName:$pkgUri');
});
fs.file(fs.path.join(_tempDirs[0].path, '.packages')).writeAsStringSync(sb.toString());
}
String _inAssetBuildDirectory(FileSystem fs, String filename) {
return '${fs.path.toUri(getAssetBuildDirectory()).path}/$filename';
}