Unverified Commit 584e4f25 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] cleanup to devfs Operations (#58332)

removes globals from devFS operations and cleans up testing to be context free. Removes unused and unnecessarily temp directory in testing.
parent bb6c1e05
......@@ -12,13 +12,13 @@ import 'asset.dart';
import 'base/context.dart';
import 'base/file_system.dart';
import 'base/io.dart';
import 'base/logger.dart';
import 'base/net.dart';
import 'base/os.dart';
import 'build_info.dart';
import 'bundle.dart';
import 'compile.dart';
import 'convert.dart' show base64, utf8;
import 'globals.dart' as globals;
import 'vmservice.dart';
class DevFSConfig {
......@@ -52,9 +52,6 @@ abstract class DevFSContent {
) {
return osUtils.gzipLevel1Stream(contentsAsStream());
}
/// Return the list of files this content depends on.
List<String> get fileDependencies => <String>[];
}
// File content to be copied to the device.
......@@ -71,7 +68,7 @@ class DevFSFileContent extends DevFSContent {
}
if (file is Link) {
// The link target.
return globals.fs.file(file.resolveSymbolicLinksSync());
return file.fileSystem.file(file.resolveSymbolicLinksSync());
}
return file as File;
}
......@@ -92,7 +89,7 @@ class DevFSFileContent extends DevFSContent {
if (_fileStat != null && _fileStat.type == FileSystemEntityType.link) {
// Resolve, stat, and maybe cache the symlink target.
final String resolved = file.resolveSymbolicLinksSync();
final File linkTarget = globals.fs.file(resolved);
final File linkTarget = file.fileSystem.file(resolved);
// Stat the link target.
final FileStat fileStat = linkTarget.statSync();
if (fileStat.type == FileSystemEntityType.notFound) {
......@@ -102,14 +99,8 @@ class DevFSFileContent extends DevFSContent {
_linkTarget = linkTarget;
}
}
if (_fileStat == null) {
globals.printError('Unable to get status of file "${file.path}": file not found.');
}
}
@override
List<String> get fileDependencies => <String>[_getFile().path];
@override
bool get isModified {
final FileStat _oldFileStat = _fileStat;
......@@ -211,37 +202,6 @@ class DevFSStringContent extends DevFSByteContent {
}
}
/// 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 vm_service.VmService vmService;
@override
Future<Uri> create(String fsName) async {
final vm_service.Response response = await vmService.createDevFS(fsName);
return Uri.parse(response.json['uri'] as String);
}
@override
Future<dynamic> destroy(String fsName) async {
await vmService.deleteDevFS(fsName);
}
@override
Future<dynamic> writeFile(String fsName, Uri deviceUri, DevFSContent content) async {
throw UnsupportedError('Use the HTTP devFS api.');
}
}
class DevFSException implements Exception {
DevFSException(this.message, [this.error, this.stackTrace]);
final String message;
......@@ -254,17 +214,20 @@ class _DevFSHttpWriter {
this.fsName,
vm_service.VmService serviceProtocol, {
@required OperatingSystemUtils osUtils,
@required HttpClient httpClient,
@required Logger logger,
})
: httpAddress = serviceProtocol.httpAddress,
_client = (context.get<HttpClientFactory>() == null)
? HttpClient()
: context.get<HttpClientFactory>()(),
_osUtils = osUtils;
_client = httpClient,
_osUtils = osUtils,
_logger = logger;
final String fsName;
final Uri httpAddress;
final HttpClient _client;
final OperatingSystemUtils _osUtils;
final Logger _logger;
final String fsName;
final Uri httpAddress;
static const int kMaxInFlight = 6;
......@@ -309,20 +272,18 @@ class _DevFSHttpWriter {
await request.addStream(contents);
final HttpClientResponse response = await request.close();
response.listen((_) => null,
onError: (dynamic error) { globals.printTrace('error: $error'); },
cancelOnError: true);
onError: (dynamic error) {
_logger.printTrace('error: $error');
},
cancelOnError: true,
);
break;
} catch (error, trace) { // ignore: avoid_catches_without_on_clauses
// We treat OSError as an Exception.
// See: https://github.com/dart-lang/sdk/issues/40934
if (error is! Exception && error is! OSError) {
rethrow;
}
} on Exception catch (error, trace) {
if (!_completer.isCompleted) {
globals.printTrace('Error writing "$deviceUri" to DevFS: $error');
_logger.printTrace('Error writing "$deviceUri" to DevFS: $error');
if (retry > 0) {
retry--;
globals.printTrace('trying again in a few - $retry more attempts left');
_logger.printTrace('trying again in a few - $retry more attempts left');
await Future<void>.delayed(const Duration(milliseconds: 500));
continue;
}
......@@ -378,31 +339,34 @@ class DevFS {
this.fsName,
this.rootDirectory, {
@required OperatingSystemUtils osUtils,
@visibleForTesting bool disableUpload = false,
}) : _operations = ServiceProtocolDevFSOperations(serviceProtocol),
@required Logger logger,
@required FileSystem fileSystem,
HttpClient httpClient,
}) : _vmService = serviceProtocol,
_logger = logger,
_fileSystem = fileSystem,
_httpWriter = _DevFSHttpWriter(
fsName,
serviceProtocol,
osUtils: osUtils,
),
_disableUpload = disableUpload;
DevFS.operations(
this._operations,
this.fsName,
this.rootDirectory,
) : _httpWriter = null,
_disableUpload = false;
logger: logger,
httpClient: httpClient ?? ((context.get<HttpClientFactory>() == null)
? HttpClient()
: context.get<HttpClientFactory>()())
);
final DevFSOperations _operations;
final vm_service.VmService _vmService;
final _DevFSHttpWriter _httpWriter;
final Logger _logger;
final FileSystem _fileSystem;
final String fsName;
final Directory rootDirectory;
final Set<String> assetPathsToEvict = <String>{};
List<Uri> sources = <Uri>[];
DateTime lastCompiled;
PackageConfig lastPackageConfig;
final bool _disableUpload;
Uri _baseUri;
Uri get baseUri => _baseUri;
......@@ -418,26 +382,28 @@ class DevFS {
}
Future<Uri> create() async {
globals.printTrace('DevFS: Creating new filesystem on the device ($_baseUri)');
_logger.printTrace('DevFS: Creating new filesystem on the device ($_baseUri)');
try {
_baseUri = await _operations.create(fsName);
final vm_service.Response response = await _vmService.createDevFS(fsName);
_baseUri = Uri.parse(response.json['uri'] as String);
} on vm_service.RPCError catch (rpcException) {
// 1001 is kFileSystemAlreadyExists in //dart/runtime/vm/json_stream.h
if (rpcException.code != 1001) {
rethrow;
}
globals.printTrace('DevFS: Creating failed. Destroying and trying again');
_logger.printTrace('DevFS: Creating failed. Destroying and trying again');
await destroy();
_baseUri = await _operations.create(fsName);
final vm_service.Response response = await _vmService.createDevFS(fsName);
_baseUri = Uri.parse(response.json['uri'] as String);
}
globals.printTrace('DevFS: Created new filesystem on the device ($_baseUri)');
_logger.printTrace('DevFS: Created new filesystem on the device ($_baseUri)');
return _baseUri;
}
Future<void> destroy() async {
globals.printTrace('DevFS: Deleting filesystem on the device ($_baseUri)');
await _operations.destroy(fsName);
globals.printTrace('DevFS: Deleted filesystem on the device ($_baseUri)');
_logger.printTrace('DevFS: Deleting filesystem on the device ($_baseUri)');
await _vmService.deleteDevFS(fsName);
_logger.printTrace('DevFS: Deleted filesystem on the device ($_baseUri)');
}
/// Updates files on the device.
......@@ -465,17 +431,17 @@ class DevFS {
lastPackageConfig = packageConfig;
// Update modified files
final String assetBuildDirPrefix = _asUriPath(getAssetBuildDirectory());
final Map<Uri, DevFSContent> dirtyEntries = <Uri, DevFSContent>{};
int syncedBytes = 0;
if (bundle != null && !skipAssets) {
globals.printTrace('Scanning asset files');
_logger.printTrace('Scanning asset files');
final String assetBuildDirPrefix = _asUriPath(getAssetBuildDirectory());
// We write the assets into the AssetBundle working dir so that they
// are in the same location in DevFS and the iOS simulator.
final String assetDirectory = getAssetBuildDirectory();
bundle.entries.forEach((String archivePath, DevFSContent content) {
final Uri deviceUri = globals.fs.path.toUri(globals.fs.path.join(assetDirectory, archivePath));
final Uri deviceUri = _fileSystem.path.toUri(_fileSystem.path.join(assetDirectory, archivePath));
if (deviceUri.path.startsWith(assetBuildDirPrefix)) {
archivePath = deviceUri.path.substring(assetBuildDirPrefix.length);
}
......@@ -496,7 +462,7 @@ class DevFS {
// On a full restart, or on an initial compile for the attach based workflow,
// this will produce a full dill. Subsequent invocations will produce incremental
// dill files that depend on the invalidated files.
globals.printTrace('Compiling dart to kernel with ${invalidatedFiles.length} updated files');
_logger.printTrace('Compiling dart to kernel with ${invalidatedFiles.length} updated files');
final CompilerOutput compilerOutput = await generator.recompile(
mainUri,
invalidatedFiles,
......@@ -516,34 +482,32 @@ class DevFS {
if (!bundleFirstUpload) {
final String compiledBinary = compilerOutput?.outputFilename;
if (compiledBinary != null && compiledBinary.isNotEmpty) {
final Uri entryUri = globals.fs.path.toUri(projectRootPath != null
? globals.fs.path.relative(pathToReload, from: projectRootPath)
final Uri entryUri = _fileSystem.path.toUri(projectRootPath != null
? _fileSystem.path.relative(pathToReload, from: projectRootPath)
: pathToReload,
);
final DevFSFileContent content = DevFSFileContent(globals.fs.file(compiledBinary));
final DevFSFileContent content = DevFSFileContent(_fileSystem.file(compiledBinary));
syncedBytes += content.size;
dirtyEntries[entryUri] = content;
}
}
globals.printTrace('Updating files');
_logger.printTrace('Updating files');
if (dirtyEntries.isNotEmpty) {
try {
if (!_disableUpload) {
await _httpWriter.write(dirtyEntries);
}
await _httpWriter.write(dirtyEntries);
} on SocketException catch (socketException, stackTrace) {
globals.printTrace('DevFS sync failed. Lost connection to device: $socketException');
_logger.printTrace('DevFS sync failed. Lost connection to device: $socketException');
throw DevFSException('Lost connection to device.', socketException, stackTrace);
} on Exception catch (exception, stackTrace) {
globals.printError('Could not update files on device: $exception');
_logger.printError('Could not update files on device: $exception');
throw DevFSException('Sync failed', exception, stackTrace);
}
}
globals.printTrace('DevFS: Sync finished');
_logger.printTrace('DevFS: Sync finished');
return UpdateFSReport(success: true, syncedBytes: syncedBytes,
invalidatedSourcesCount: invalidatedFiles.length);
}
}
/// Converts a platform-specific file path to a platform-independent URL path.
String _asUriPath(String filePath) => globals.fs.path.toUri(filePath).path + '/';
/// Converts a platform-specific file path to a platform-independent URL path.
String _asUriPath(String filePath) => _fileSystem.path.toUri(filePath).path + '/';
}
......@@ -293,6 +293,8 @@ class FlutterDevice {
fsName,
rootDirectory,
osUtils: globals.os,
fileSystem: globals.fs,
logger: globals.logger,
);
return devFS.create();
}
......
......@@ -4,345 +4,259 @@
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/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/net.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:mockito/mockito.dart';
import 'package:package_config/package_config.dart';
import 'package:vm_service/vm_service.dart' as vm_service;
import '../src/common.dart';
import '../src/context.dart';
import '../src/mocks.dart';
final FakeVmServiceRequest createDevFSRequest = FakeVmServiceRequest(
method: '_createDevFS',
args: <String, Object>{
'fsName': 'test',
},
jsonResponse: <String, Object>{
'uri': Uri.parse('test').toString(),
}
);
void main() {
FileSystem fs;
String filePath;
Directory tempDir;
String basePath;
setUpAll(() {
fs = MemoryFileSystem.test();
filePath = fs.path.join('lib', 'foo.txt');
testWithoutContext('DevFSByteContent', () {
final DevFSByteContent content = 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);
});
group('DevFSContent', () {
test('bytes', () {
final DevFSByteContent content = 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 = 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);
});
testUsingContext('file', () async {
final File file = fs.file(filePath);
final DevFSFileContent content = DevFSFileContent(file);
expect(content.isModified, isFalse);
expect(content.isModified, isFalse);
file.parent.createSync(recursive: true);
file.writeAsBytesSync(<int>[1, 2, 3], flush: true);
final DateTime fiveSecondsAgo = file.statSync().modified.subtract(const Duration(seconds: 5));
expect(content.isModifiedAfter(fiveSecondsAgo), isTrue);
expect(content.isModifiedAfter(fiveSecondsAgo), isTrue);
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(await content.contentsAsBytes(), <int>[2, 3, 4]);
updateFileModificationTime(file.path, fiveSecondsAgo, 0);
expect(content.isModified, isFalse);
expect(content.isModified, isFalse);
file.deleteSync();
expect(content.isModified, isTrue);
expect(content.isModified, isFalse);
expect(content.isModified, isFalse);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
}, skip: Platform.isWindows); // TODO(jonahwilliams): fix or disable this functionality.
testWithoutContext('DevFSStringContent', () {
final DevFSStringContent content = 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('mocked http client', () {
HttpOverrides savedHttpOverrides;
HttpClient httpClient;
OperatingSystemUtils osUtils;
setUpAll(() {
tempDir = _newTempDir(fs);
basePath = tempDir.path;
savedHttpOverrides = HttpOverrides.current;
httpClient = MockOddlyFailingHttpClient();
HttpOverrides.global = MyHttpOverrides(httpClient);
osUtils = MockOperatingSystemUtils();
});
testWithoutContext('DevFSFileContent', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final File file = fileSystem.file('foo.txt');
final DevFSFileContent content = DevFSFileContent(file);
expect(content.isModified, isFalse);
expect(content.isModified, isFalse);
tearDownAll(() async {
HttpOverrides.global = savedHttpOverrides;
});
file.parent.createSync(recursive: true);
file.writeAsBytesSync(<int>[1, 2, 3], flush: true);
final List<dynamic> exceptions = <dynamic>[
Exception('Connection resert by peer'),
const OSError('Connection reset by peer'),
];
for (final dynamic exception in exceptions) {
testUsingContext('retry uploads when failure: $exception', () async {
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');
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[
FakeVmServiceRequest(
method: '_createDevFS',
args: <String, Object>{
'fsName': 'test',
},
jsonResponse: <String, Object>{
'uri': Uri.parse('test').toString(),
}
)
],
);
setHttpAddress(Uri.parse('http://localhost'), fakeVmServiceHost.vmService);
reset(httpClient);
final MockHttpClientRequest httpRequest = MockHttpClientRequest();
when(httpRequest.headers).thenReturn(MockHttpHeaders());
when(httpClient.putUrl(any)).thenAnswer((Invocation invocation) {
return Future<HttpClientRequest>.value(httpRequest);
});
final MockHttpClientResponse httpClientResponse = MockHttpClientResponse();
int nRequest = 0;
const int kFailedAttempts = 5;
when(httpRequest.close()).thenAnswer((Invocation invocation) {
if (nRequest++ < kFailedAttempts) {
throw exception;
}
return Future<HttpClientResponse>.value(httpClientResponse);
});
final DevFS devFS = DevFS(
fakeVmServiceHost.vmService,
'test',
tempDir,
osUtils: osUtils,
);
await devFS.create();
final MockResidentCompiler residentCompiler = MockResidentCompiler();
final UpdateFSReport report = await devFS.update(
mainUri: Uri.parse('lib/foo.txt'),
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
trackWidgetCreation: false,
invalidatedFiles: <Uri>[],
packageConfig: PackageConfig.empty,
);
expect(report.syncedBytes, 22);
expect(report.success, isTrue);
verify(httpClient.putUrl(any)).called(kFailedAttempts + 1);
verify(httpRequest.close()).called(kFailedAttempts + 1);
verify(osUtils.gzipLevel1Stream(any)).called(kFailedAttempts + 1);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
HttpClientFactory: () => () => httpClient,
ProcessManager: () => FakeProcessManager.any(),
});
}
});
final DateTime fiveSecondsAgo = file.statSync().modified.subtract(const Duration(seconds: 5));
expect(content.isModifiedAfter(fiveSecondsAgo), isTrue);
expect(content.isModifiedAfter(fiveSecondsAgo), isTrue);
expect(content.isModifiedAfter(null), isTrue);
group('devfs remote', () {
DevFS devFS;
file.writeAsBytesSync(<int>[2, 3, 4], flush: true);
setUpAll(() async {
tempDir = _newTempDir(fs);
basePath = tempDir.path;
});
expect(content.isModified, isTrue);
expect(content.isModified, isFalse);
expect(await content.contentsAsBytes(), <int>[2, 3, 4]);
setUp(() {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[
FakeVmServiceRequest(
method: '_createDevFS',
args: <String, Object>{
'fsName': 'test',
},
jsonResponse: <String, Object>{
'uri': Uri.parse('test').toString(),
}
)
],
);
setHttpAddress(Uri.parse('http://localhost'), fakeVmServiceHost.vmService);
devFS = DevFS(
fakeVmServiceHost.vmService,
'test',
tempDir,
osUtils: FakeOperatingSystemUtils(),
// TODO(jonahwilliams): remove and prevent usage of http writer.
disableUpload: true,
);
});
expect(content.isModified, isFalse);
expect(content.isModified, isFalse);
tearDownAll(() async {
_cleanupTempDirs();
});
file.deleteSync();
expect(content.isModified, isTrue);
expect(content.isModified, isFalse);
expect(content.isModified, isFalse);
});
testUsingContext('reports unsuccessful compile when errors are returned', () async {
await devFS.create();
final DateTime previousCompile = devFS.lastCompiled;
final RealMockResidentCompiler residentCompiler = RealMockResidentCompiler();
when(residentCompiler.recompile(
any,
any,
outputPath: anyNamed('outputPath'),
packageConfig: anyNamed('packageConfig'),
)).thenAnswer((Invocation invocation) {
return Future<CompilerOutput>.value(const CompilerOutput('example', 2, <Uri>[]));
});
final UpdateFSReport report = await devFS.update(
mainUri: Uri.parse('lib/foo.txt'),
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
trackWidgetCreation: false,
invalidatedFiles: <Uri>[],
packageConfig: PackageConfig.empty,
);
expect(report.success, false);
expect(devFS.lastCompiled, previousCompile);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
testWithoutContext('DevFS retries uploads when connection resert by peer', () async {
final HttpClient httpClient = MockHttpClient();
final FileSystem fileSystem = MemoryFileSystem.test();
final OperatingSystemUtils osUtils = MockOperatingSystemUtils();
final MockResidentCompiler residentCompiler = MockResidentCompiler();
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[createDevFSRequest],
);
setHttpAddress(Uri.parse('http://localhost'), fakeVmServiceHost.vmService);
final MockHttpClientRequest httpRequest = MockHttpClientRequest();
when(httpRequest.headers).thenReturn(MockHttpHeaders());
when(httpClient.putUrl(any)).thenAnswer((Invocation invocation) {
return Future<HttpClientRequest>.value(httpRequest);
});
final MockHttpClientResponse httpClientResponse = MockHttpClientResponse();
int nRequest = 0;
const int kFailedAttempts = 5;
when(httpRequest.close()).thenAnswer((Invocation invocation) {
if (nRequest++ < kFailedAttempts) {
throw const OSError('Connection Reset by peer');
}
return Future<HttpClientResponse>.value(httpClientResponse);
});
testUsingContext('correctly updates last compiled time when compilation does not fail', () async {
// simulate package
final File sourceFile = await _createPackage(fs, 'somepkg', 'main.dart');
await devFS.create();
final DateTime previousCompile = devFS.lastCompiled;
final RealMockResidentCompiler residentCompiler = RealMockResidentCompiler();
when(residentCompiler.recompile(
any,
any,
outputPath: anyNamed('outputPath'),
packageConfig: anyNamed('packageConfig'),
)).thenAnswer((Invocation invocation) {
fs.file('example').createSync();
return Future<CompilerOutput>.value(CompilerOutput('example', 0, <Uri>[sourceFile.uri]));
});
final UpdateFSReport report = await devFS.update(
mainUri: Uri.parse('lib/main.dart'),
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
trackWidgetCreation: false,
invalidatedFiles: <Uri>[],
packageConfig: PackageConfig.empty,
);
expect(report.success, true);
expect(devFS.lastCompiled, isNot(previousCompile));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
HttpClient: () => () => HttpClient(),
ProcessManager: () => FakeProcessManager.any(),
when(residentCompiler.recompile(
any,
any,
outputPath: anyNamed('outputPath'),
packageConfig: anyNamed('packageConfig'),
)).thenAnswer((Invocation invocation) async {
fileSystem.file('lib/foo.dill')
..createSync(recursive: true)
..writeAsBytesSync(<int>[1, 2, 3, 4, 5]);
return const CompilerOutput('lib/foo.dill', 0, <Uri>[]);
});
final DevFS devFS = DevFS(
fakeVmServiceHost.vmService,
'test',
fileSystem.currentDirectory,
osUtils: osUtils,
fileSystem: fileSystem,
logger: BufferLogger.test(),
httpClient: httpClient,
);
await devFS.create();
final UpdateFSReport report = await devFS.update(
mainUri: Uri.parse('lib/foo.txt'),
dillOutputPath: 'lib/foo.dill',
generator: residentCompiler,
pathToReload: 'lib/foo.txt.dill',
trackWidgetCreation: false,
invalidatedFiles: <Uri>[],
packageConfig: PackageConfig.empty,
);
expect(report.syncedBytes, 5);
expect(report.success, isTrue);
verify(httpClient.putUrl(any)).called(kFailedAttempts + 1);
verify(httpRequest.close()).called(kFailedAttempts + 1);
verify(osUtils.gzipLevel1Stream(any)).called(kFailedAttempts + 1);
});
}
class RealMockResidentCompiler extends Mock implements ResidentCompiler {}
testWithoutContext('DevFS reports unsuccessful compile when errors are returned', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[createDevFSRequest],
);
setHttpAddress(Uri.parse('http://localhost'), fakeVmServiceHost.vmService);
final DevFS devFS = DevFS(
fakeVmServiceHost.vmService,
'test',
fileSystem.currentDirectory,
fileSystem: fileSystem,
logger: BufferLogger.test(),
osUtils: FakeOperatingSystemUtils(),
httpClient: MockHttpClient(),
);
await devFS.create();
final DateTime previousCompile = devFS.lastCompiled;
final MockResidentCompiler residentCompiler = MockResidentCompiler();
when(residentCompiler.recompile(
any,
any,
outputPath: anyNamed('outputPath'),
packageConfig: anyNamed('packageConfig'),
)).thenAnswer((Invocation invocation) async {
return const CompilerOutput('lib/foo.dill', 2, <Uri>[]);
});
final List<Directory> _tempDirs = <Directory>[];
final Map <String, Uri> _packages = <String, Uri>{};
final UpdateFSReport report = await devFS.update(
mainUri: Uri.parse('lib/foo.txt'),
generator: residentCompiler,
dillOutputPath: 'lib/foo.dill',
pathToReload: 'lib/foo.txt.dill',
trackWidgetCreation: false,
invalidatedFiles: <Uri>[],
packageConfig: PackageConfig.empty,
);
expect(report.success, false);
expect(devFS.lastCompiled, previousCompile);
});
Directory _newTempDir(FileSystem fs) {
final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_devfs${_tempDirs.length}_test.');
_tempDirs.add(tempDir);
return tempDir;
}
testWithoutContext('DevFS correctly updates last compiled time when compilation does not fail', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[createDevFSRequest],
);
final HttpClient httpClient = MockHttpClient();
final MockHttpClientRequest httpRequest = MockHttpClientRequest();
when(httpRequest.headers).thenReturn(MockHttpHeaders());
when(httpClient.putUrl(any)).thenAnswer((Invocation invocation) {
return Future<HttpClientRequest>.value(httpRequest);
});
final MockHttpClientResponse httpClientResponse = MockHttpClientResponse();
when(httpRequest.close()).thenAnswer((Invocation invocation) async {
return httpClientResponse;
});
void _cleanupTempDirs() {
while (_tempDirs.isNotEmpty) {
tryToDelete(_tempDirs.removeLast());
}
}
final DevFS devFS = DevFS(
fakeVmServiceHost.vmService,
'test',
fileSystem.currentDirectory,
fileSystem: fileSystem,
logger: BufferLogger.test(),
osUtils: FakeOperatingSystemUtils(),
httpClient: httpClient,
);
await devFS.create();
final DateTime previousCompile = devFS.lastCompiled;
final MockResidentCompiler residentCompiler = MockResidentCompiler();
when(residentCompiler.recompile(
any,
any,
outputPath: anyNamed('outputPath'),
packageConfig: anyNamed('packageConfig'),
)).thenAnswer((Invocation invocation) async {
fileSystem.file('example').createSync();
return const CompilerOutput('lib/foo.txt.dill', 0, <Uri>[]);
});
Future<File> _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 = StringBuffer();
_packages.forEach((String pkgName, Uri pkgUri) {
sb.writeln('$pkgName:$pkgUri');
final UpdateFSReport report = await devFS.update(
mainUri: Uri.parse('lib/main.dart'),
generator: residentCompiler,
dillOutputPath: 'lib/foo.dill',
pathToReload: 'lib/foo.txt.dill',
trackWidgetCreation: false,
invalidatedFiles: <Uri>[],
packageConfig: PackageConfig.empty,
);
expect(report.success, true);
expect(devFS.lastCompiled, isNot(previousCompile));
});
return fs.file(fs.path.join(_tempDirs[0].path, '.packages'))
..writeAsStringSync(sb.toString());
}
class MyHttpOverrides extends HttpOverrides {
MyHttpOverrides(this._httpClient);
@override
HttpClient createHttpClient(SecurityContext context) {
return _httpClient;
}
final HttpClient _httpClient;
}
class MockOddlyFailingHttpClient extends Mock implements HttpClient {}
class MockHttpClientRequest extends Mock implements HttpClientRequest {}
class MockHttpHeaders extends Mock implements HttpHeaders {}
class MockHttpClientResponse extends Mock implements HttpClientResponse {}
class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {}
class MockVMService extends Mock implements vm_service.VmService {}
class MockResidentCompiler extends Mock implements ResidentCompiler {}
......@@ -85,16 +85,6 @@ CommandRunner<void> createTestCommandRunner([ FlutterCommand command ]) {
return runner;
}
/// Updates [path] to have a modification time [seconds] from now.
void updateFileModificationTime(
String path,
DateTime baseTime,
int seconds,
) {
final DateTime modificationTime = baseTime.add(Duration(seconds: seconds));
globals.fs.file(path).setLastModifiedSync(modificationTime);
}
/// Matcher for functions that throw [AssertionError].
final Matcher throwsAssertionError = throwsA(isA<AssertionError>());
......
......@@ -16,7 +16,6 @@ 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/globals.dart' as globals;
import 'package:flutter_tools/src/ios/devices.dart';
......@@ -600,31 +599,6 @@ 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
void accept() { }
......
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