mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
493 lines
18 KiB
Dart
493 lines
18 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// @dart = 2.8
|
|
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
|
|
import 'package:file/file.dart';
|
|
import 'package:file/memory.dart';
|
|
import 'package:file_testing/file_testing.dart';
|
|
import 'package:flutter_tools/src/base/file_system.dart';
|
|
import 'package:flutter_tools/src/base/io.dart';
|
|
import 'package:flutter_tools/src/base/logger.dart';
|
|
import 'package:flutter_tools/src/base/os.dart';
|
|
import 'package:flutter_tools/src/base/platform.dart';
|
|
import 'package:flutter_tools/src/compile.dart';
|
|
import 'package:flutter_tools/src/devfs.dart';
|
|
import 'package:flutter_tools/src/vmservice.dart';
|
|
import 'package:package_config/package_config.dart';
|
|
import 'package:test/fake.dart';
|
|
|
|
import '../src/common.dart';
|
|
import '../src/context.dart';
|
|
import '../src/fake_http_client.dart';
|
|
import '../src/fake_vm_services.dart';
|
|
import '../src/fakes.dart';
|
|
|
|
final FakeVmServiceRequest createDevFSRequest = FakeVmServiceRequest(
|
|
method: '_createDevFS',
|
|
args: <String, Object>{
|
|
'fsName': 'test',
|
|
},
|
|
jsonResponse: <String, Object>{
|
|
'uri': Uri.parse('test').toString(),
|
|
}
|
|
);
|
|
|
|
const FakeVmServiceRequest failingCreateDevFSRequest = FakeVmServiceRequest(
|
|
method: '_createDevFS',
|
|
args: <String, Object>{
|
|
'fsName': 'test',
|
|
},
|
|
errorCode: RPCErrorCodes.kServiceDisappeared,
|
|
);
|
|
|
|
const FakeVmServiceRequest failingDeleteDevFSRequest = FakeVmServiceRequest(
|
|
method: '_deleteDevFS',
|
|
args: <String, dynamic>{'fsName': 'test'},
|
|
errorCode: RPCErrorCodes.kServiceDisappeared,
|
|
);
|
|
|
|
void main() {
|
|
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);
|
|
});
|
|
|
|
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);
|
|
});
|
|
|
|
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);
|
|
|
|
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.isModified, isTrue);
|
|
expect(content.isModified, isFalse);
|
|
expect(await content.contentsAsBytes(), <int>[2, 3, 4]);
|
|
|
|
expect(content.isModified, isFalse);
|
|
expect(content.isModified, isFalse);
|
|
|
|
file.deleteSync();
|
|
expect(content.isModified, isTrue);
|
|
expect(content.isModified, isFalse);
|
|
expect(content.isModified, isFalse);
|
|
});
|
|
|
|
testWithoutContext('DevFSStringCompressingBytesContent', () {
|
|
final DevFSStringCompressingBytesContent content =
|
|
DevFSStringCompressingBytesContent('uncompressed string');
|
|
|
|
expect(content.equals('uncompressed string'), isTrue);
|
|
expect(content.bytes, isNotNull);
|
|
expect(content.isModified, isTrue);
|
|
expect(content.isModified, isFalse);
|
|
});
|
|
|
|
testWithoutContext('DevFS create throws a DevFSException when vmservice disconnects unexpectedly', () async {
|
|
final FileSystem fileSystem = MemoryFileSystem.test();
|
|
final OperatingSystemUtils osUtils = FakeOperatingSystemUtils();
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[failingCreateDevFSRequest],
|
|
httpAddress: Uri.parse('http://localhost'),
|
|
);
|
|
|
|
final DevFS devFS = DevFS(
|
|
fakeVmServiceHost.vmService,
|
|
'test',
|
|
fileSystem.currentDirectory,
|
|
osUtils: osUtils,
|
|
fileSystem: fileSystem,
|
|
logger: BufferLogger.test(),
|
|
httpClient: FakeHttpClient.any(),
|
|
);
|
|
expect(() async => devFS.create(), throwsA(isA<DevFSException>()));
|
|
});
|
|
|
|
testWithoutContext('DevFS destroy is resilient to vmservice disconnection', () async {
|
|
final FileSystem fileSystem = MemoryFileSystem.test();
|
|
final OperatingSystemUtils osUtils = FakeOperatingSystemUtils();
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
createDevFSRequest,
|
|
failingDeleteDevFSRequest,
|
|
],
|
|
httpAddress: Uri.parse('http://localhost'),
|
|
);
|
|
|
|
final DevFS devFS = DevFS(
|
|
fakeVmServiceHost.vmService,
|
|
'test',
|
|
fileSystem.currentDirectory,
|
|
osUtils: osUtils,
|
|
fileSystem: fileSystem,
|
|
logger: BufferLogger.test(),
|
|
httpClient: FakeHttpClient.any(),
|
|
);
|
|
|
|
expect(await devFS.create(), isNotNull);
|
|
await devFS.destroy(); // Testing that this does not throw.
|
|
});
|
|
|
|
testWithoutContext('DevFS retries uploads when connection reset by peer', () async {
|
|
final FileSystem fileSystem = MemoryFileSystem.test();
|
|
final OperatingSystemUtils osUtils = OperatingSystemUtils(
|
|
fileSystem: fileSystem,
|
|
platform: FakePlatform(),
|
|
logger: BufferLogger.test(),
|
|
processManager: FakeProcessManager.any(),
|
|
);
|
|
final FakeResidentCompiler residentCompiler = FakeResidentCompiler();
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[createDevFSRequest],
|
|
httpAddress: Uri.parse('http://localhost'),
|
|
);
|
|
residentCompiler.onRecompile = (Uri mainUri, List<Uri> invalidatedFiles) async {
|
|
fileSystem.file('lib/foo.dill')
|
|
..createSync(recursive: true)
|
|
..writeAsBytesSync(<int>[1, 2, 3, 4, 5]);
|
|
return const CompilerOutput('lib/foo.dill', 0, <Uri>[]);
|
|
};
|
|
|
|
/// This output can change based on the host platform.
|
|
final List<List<int>> expectedEncoded = await osUtils.gzipLevel1Stream(
|
|
Stream<List<int>>.value(<int>[1, 2, 3, 4, 5]),
|
|
).toList();
|
|
|
|
final DevFS devFS = DevFS(
|
|
fakeVmServiceHost.vmService,
|
|
'test',
|
|
fileSystem.currentDirectory,
|
|
osUtils: osUtils,
|
|
fileSystem: fileSystem,
|
|
logger: BufferLogger.test(),
|
|
httpClient: FakeHttpClient.list(<FakeRequest>[
|
|
FakeRequest(Uri.parse('http://localhost'), method: HttpMethod.put, responseError: const OSError('Connection Reset by peer')),
|
|
FakeRequest(Uri.parse('http://localhost'), method: HttpMethod.put, responseError: const OSError('Connection Reset by peer')),
|
|
FakeRequest(Uri.parse('http://localhost'), method: HttpMethod.put, responseError: const OSError('Connection Reset by peer')),
|
|
FakeRequest(Uri.parse('http://localhost'), method: HttpMethod.put, responseError: const OSError('Connection Reset by peer')),
|
|
FakeRequest(Uri.parse('http://localhost'), method: HttpMethod.put, responseError: const OSError('Connection Reset by peer')),
|
|
// This is the value of `<int>[1, 2, 3, 4, 5]` run through `osUtils.gzipLevel1Stream`.
|
|
FakeRequest(Uri.parse('http://localhost'), method: HttpMethod.put, body: <int>[for (List<int> chunk in expectedEncoded) ...chunk])
|
|
]),
|
|
uploadRetryThrottle: Duration.zero,
|
|
);
|
|
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);
|
|
});
|
|
|
|
testWithoutContext('DevFS reports unsuccessful compile when errors are returned', () async {
|
|
final FileSystem fileSystem = MemoryFileSystem.test();
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[createDevFSRequest],
|
|
httpAddress: Uri.parse('http://localhost'),
|
|
);
|
|
|
|
final DevFS devFS = DevFS(
|
|
fakeVmServiceHost.vmService,
|
|
'test',
|
|
fileSystem.currentDirectory,
|
|
fileSystem: fileSystem,
|
|
logger: BufferLogger.test(),
|
|
osUtils: FakeOperatingSystemUtils(),
|
|
httpClient: FakeHttpClient.any(),
|
|
);
|
|
|
|
await devFS.create();
|
|
final DateTime previousCompile = devFS.lastCompiled;
|
|
|
|
final FakeResidentCompiler residentCompiler = FakeResidentCompiler();
|
|
residentCompiler.onRecompile = (Uri mainUri, List<Uri> invalidatedFiles) async {
|
|
return const CompilerOutput('lib/foo.dill', 2, <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);
|
|
});
|
|
|
|
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],
|
|
httpAddress: Uri.parse('http://localhost'),
|
|
);
|
|
|
|
final DevFS devFS = DevFS(
|
|
fakeVmServiceHost.vmService,
|
|
'test',
|
|
fileSystem.currentDirectory,
|
|
fileSystem: fileSystem,
|
|
logger: BufferLogger.test(),
|
|
osUtils: FakeOperatingSystemUtils(),
|
|
httpClient: FakeHttpClient.any(),
|
|
);
|
|
|
|
await devFS.create();
|
|
final DateTime previousCompile = devFS.lastCompiled;
|
|
|
|
final FakeResidentCompiler residentCompiler = FakeResidentCompiler();
|
|
residentCompiler.onRecompile = (Uri mainUri, List<Uri> invalidatedFiles) async {
|
|
fileSystem.file('lib/foo.txt.dill').createSync(recursive: true);
|
|
return const CompilerOutput('lib/foo.txt.dill', 0, <Uri>[]);
|
|
};
|
|
|
|
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));
|
|
});
|
|
|
|
testWithoutContext('DevFS can reset compilation time', () async {
|
|
final FileSystem fileSystem = MemoryFileSystem.test();
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[createDevFSRequest],
|
|
);
|
|
final LocalDevFSWriter localDevFSWriter = LocalDevFSWriter(fileSystem: fileSystem);
|
|
fileSystem.directory('test').createSync();
|
|
|
|
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 FakeResidentCompiler residentCompiler = FakeResidentCompiler();
|
|
residentCompiler.onRecompile = (Uri mainUri, List<Uri> invalidatedFiles) async {
|
|
fileSystem.file('lib/foo.txt.dill').createSync(recursive: true);
|
|
return const CompilerOutput('lib/foo.txt.dill', 0, <Uri>[]);
|
|
};
|
|
|
|
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,
|
|
devFSWriter: localDevFSWriter,
|
|
);
|
|
|
|
expect(report.success, true);
|
|
expect(devFS.lastCompiled, isNot(previousCompile));
|
|
|
|
devFS.resetLastCompiled();
|
|
expect(devFS.lastCompiled, previousCompile);
|
|
|
|
// Does not reset to report compile time.
|
|
devFS.resetLastCompiled();
|
|
expect(devFS.lastCompiled, previousCompile);
|
|
});
|
|
|
|
testWithoutContext('DevFS uses provided DevFSWriter instead of default HTTP writer', () async {
|
|
final FileSystem fileSystem = MemoryFileSystem.test();
|
|
final FakeDevFSWriter writer = FakeDevFSWriter();
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[createDevFSRequest],
|
|
);
|
|
|
|
final DevFS devFS = DevFS(
|
|
fakeVmServiceHost.vmService,
|
|
'test',
|
|
fileSystem.currentDirectory,
|
|
fileSystem: fileSystem,
|
|
logger: BufferLogger.test(),
|
|
osUtils: FakeOperatingSystemUtils(),
|
|
httpClient: FakeHttpClient.any(),
|
|
);
|
|
|
|
await devFS.create();
|
|
|
|
final FakeResidentCompiler residentCompiler = FakeResidentCompiler();
|
|
residentCompiler.onRecompile = (Uri mainUri, List<Uri> invalidatedFiles) async {
|
|
fileSystem.file('example').createSync();
|
|
return const CompilerOutput('lib/foo.txt.dill', 0, <Uri>[]);
|
|
};
|
|
|
|
expect(writer.written, false);
|
|
|
|
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,
|
|
devFSWriter: writer,
|
|
);
|
|
|
|
expect(report.success, true);
|
|
expect(writer.written, true);
|
|
});
|
|
|
|
testWithoutContext('Local DevFSWriter can copy and write files', () async {
|
|
final FileSystem fileSystem = MemoryFileSystem.test();
|
|
final File file = fileSystem.file('foo_bar')
|
|
..writeAsStringSync('goodbye');
|
|
final LocalDevFSWriter writer = LocalDevFSWriter(fileSystem: fileSystem);
|
|
|
|
await writer.write(<Uri, DevFSContent>{
|
|
Uri.parse('hello'): DevFSStringContent('hello'),
|
|
Uri.parse('goodbye'): DevFSFileContent(file),
|
|
}, Uri.parse('/foo/bar/devfs/'));
|
|
|
|
expect(fileSystem.file('/foo/bar/devfs/hello'), exists);
|
|
expect(fileSystem.file('/foo/bar/devfs/hello').readAsStringSync(), 'hello');
|
|
expect(fileSystem.file('/foo/bar/devfs/goodbye'), exists);
|
|
expect(fileSystem.file('/foo/bar/devfs/goodbye').readAsStringSync(), 'goodbye');
|
|
});
|
|
|
|
testWithoutContext('Local DevFSWriter turns FileSystemException into DevFSException', () async {
|
|
final FileExceptionHandler handler = FileExceptionHandler();
|
|
final FileSystem fileSystem = MemoryFileSystem.test(opHandle: handler.opHandle);
|
|
final LocalDevFSWriter writer = LocalDevFSWriter(fileSystem: fileSystem);
|
|
final File file = fileSystem.file('foo');
|
|
handler.addError(file, FileSystemOp.read, const FileSystemException('foo'));
|
|
|
|
await expectLater(() async => writer.write(<Uri, DevFSContent>{
|
|
Uri.parse('goodbye'): DevFSFileContent(file),
|
|
}, Uri.parse('/foo/bar/devfs/')), throwsA(isA<DevFSException>()));
|
|
});
|
|
|
|
testWithoutContext('DevFS correctly records the elapsed time', () async {
|
|
final FileSystem fileSystem = MemoryFileSystem.test();
|
|
// final FakeDevFSWriter writer = FakeDevFSWriter();
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[createDevFSRequest],
|
|
httpAddress: Uri.parse('http://localhost'),
|
|
);
|
|
|
|
final DevFS devFS = DevFS(
|
|
fakeVmServiceHost.vmService,
|
|
'test',
|
|
fileSystem.currentDirectory,
|
|
fileSystem: fileSystem,
|
|
logger: BufferLogger.test(),
|
|
osUtils: FakeOperatingSystemUtils(),
|
|
httpClient: FakeHttpClient.any(),
|
|
stopwatchFactory: FakeStopwatchFactory(stopwatches: <String, Stopwatch>{
|
|
'compile': FakeStopwatch()..elapsed = const Duration(seconds: 3),
|
|
'transfer': FakeStopwatch()..elapsed = const Duration(seconds: 5),
|
|
}),
|
|
);
|
|
|
|
await devFS.create();
|
|
|
|
final FakeResidentCompiler residentCompiler = FakeResidentCompiler();
|
|
residentCompiler.onRecompile = (Uri mainUri, List<Uri> invalidatedFiles) async {
|
|
fileSystem.file('lib/foo.txt.dill').createSync(recursive: true);
|
|
return const CompilerOutput('lib/foo.txt.dill', 0, <Uri>[]);
|
|
};
|
|
|
|
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(report.compileDuration, const Duration(seconds: 3));
|
|
expect(report.transferDuration, const Duration(seconds: 5));
|
|
});
|
|
}
|
|
|
|
class FakeResidentCompiler extends Fake implements ResidentCompiler {
|
|
Future<CompilerOutput> Function(Uri mainUri, List<Uri> invalidatedFiles) onRecompile;
|
|
|
|
@override
|
|
Future<CompilerOutput> recompile(Uri mainUri, List<Uri> invalidatedFiles, {String outputPath, PackageConfig packageConfig, String projectRootPath, FileSystem fs, bool suppressErrors = false}) {
|
|
return onRecompile?.call(mainUri, invalidatedFiles)
|
|
?? Future<CompilerOutput>.value(const CompilerOutput('', 1, <Uri>[]));
|
|
}
|
|
}
|
|
|
|
class FakeDevFSWriter implements DevFSWriter {
|
|
bool written = false;
|
|
|
|
@override
|
|
Future<void> write(Map<Uri, DevFSContent> entries, Uri baseUri, DevFSWriter parent) async {
|
|
written = true;
|
|
}
|
|
}
|