blob: 1f02688181abea5f2b8315a71896002676d83281 [file] [log] [blame]
// 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.
import 'dart:async';
import 'dart:convert';
import 'dart:io' as io show Process, ProcessSignal;
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/asset.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/build_info.dart';
import 'package:flutter_tools/src/build_system/targets/shader_compiler.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/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';
import '../src/logging_logger.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);
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 (final 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,
shaderCompiler: const FakeShaderCompiler(),
);
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,
shaderCompiler: const FakeShaderCompiler(),
);
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,
shaderCompiler: const FakeShaderCompiler(),
);
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,
shaderCompiler: const FakeShaderCompiler(),
);
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,
shaderCompiler: const FakeShaderCompiler(),
);
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,
shaderCompiler: const FakeShaderCompiler(),
);
expect(report.success, true);
expect(report.compileDuration, const Duration(seconds: 3));
expect(report.transferDuration, const Duration(seconds: 5));
});
testUsingContext('DevFS actually starts compile before processing bundle', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[createDevFSRequest],
httpAddress: Uri.parse('http://localhost'),
);
final LoggingLogger logger = LoggingLogger();
final DevFS devFS = DevFS(
fakeVmServiceHost.vmService,
'test',
fileSystem.currentDirectory,
fileSystem: fileSystem,
logger: logger,
osUtils: FakeOperatingSystemUtils(),
httpClient: FakeHttpClient.any(),
);
await devFS.create();
final MemoryIOSink frontendServerStdIn = MemoryIOSink();
Stream<List<int>> frontendServerStdOut() async* {
int processed = 0;
while (true) {
while (frontendServerStdIn.writes.length == processed) {
await Future<dynamic>.delayed(const Duration(milliseconds: 5));
}
String? boundaryKey;
while (processed < frontendServerStdIn.writes.length) {
final List<int> data = frontendServerStdIn.writes[processed];
final String stringData = utf8.decode(data);
if (stringData.startsWith('compile ')) {
yield utf8.encode('result abc1\nline1\nline2\nabc1\nabc1 lib/foo.txt.dill 0\n');
} else if (stringData.startsWith('recompile ')) {
final String line = stringData.split('\n').first;
final int spaceDelim = line.lastIndexOf(' ');
boundaryKey = line.substring(spaceDelim + 1);
} else if (boundaryKey != null && stringData.startsWith(boundaryKey)) {
yield utf8.encode('result abc2\nline1\nline2\nabc2\nabc2 lib/foo.txt.dill 0\n');
} else {
throw Exception('Saw $data ($stringData)');
}
processed++;
}
}
}
Stream<List<int>> frontendServerStdErr() async* {
// Output nothing on stderr.
}
final AnsweringFakeProcessManager fakeProcessManager = AnsweringFakeProcessManager(frontendServerStdOut(), frontendServerStdErr(), frontendServerStdIn);
final StdoutHandler generatorStdoutHandler = StdoutHandler(logger: testLogger, fileSystem: fileSystem);
final DefaultResidentCompiler residentCompiler = DefaultResidentCompiler(
'sdkroot',
buildMode: BuildMode.debug,
logger: logger,
processManager: fakeProcessManager,
artifacts: Artifacts.test(),
platform: FakePlatform(),
fileSystem: fileSystem,
stdoutHandler: generatorStdoutHandler,
);
fileSystem.file('lib/foo.txt.dill').createSync(recursive: true);
final UpdateFSReport report1 = 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,
bundle: FakeBundle(),
shaderCompiler: const FakeShaderCompiler(),
);
expect(report1.success, true);
logger.messages.clear();
final UpdateFSReport report2 = 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,
bundle: FakeBundle(),
shaderCompiler: const FakeShaderCompiler(),
);
expect(report2.success, true);
final int processingBundleIndex = logger.messages.indexOf('Processing bundle.');
final int bundleProcessingDoneIndex = logger.messages.indexOf('Bundle processing done.');
final int compileLibMainIndex = logger.messages.indexWhere((String element) => element.startsWith('<- recompile lib/main.dart '));
expect(processingBundleIndex, greaterThanOrEqualTo(0));
expect(bundleProcessingDoneIndex, greaterThanOrEqualTo(0));
expect(compileLibMainIndex, greaterThanOrEqualTo(0));
expect(bundleProcessingDoneIndex, greaterThan(compileLibMainIndex));
});
group('Shader compilation', () {
late FileSystem fileSystem;
late ProcessManager processManager;
setUp(() {
fileSystem = MemoryFileSystem.test();
processManager = FakeProcessManager.any();
});
testUsingContext('DevFS recompiles shaders', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[createDevFSRequest],
httpAddress: Uri.parse('http://localhost'),
);
final BufferLogger logger = BufferLogger.test();
final DevFS devFS = DevFS(
fakeVmServiceHost.vmService,
'test',
fileSystem.currentDirectory,
fileSystem: fileSystem,
logger: logger,
osUtils: FakeOperatingSystemUtils(),
httpClient: FakeHttpClient.any(),
);
await devFS.create();
final FakeResidentCompiler residentCompiler = FakeResidentCompiler()
..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>[]);
};
final FakeBundle bundle = FakeBundle()
..entries['foo.frag'] = DevFSByteContent(<int>[1, 2, 3, 4])
..entries['not.frag'] = DevFSByteContent(<int>[1, 2, 3, 4])
..entryKinds['foo.frag'] = AssetKind.shader;
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,
shaderCompiler: const FakeShaderCompiler(),
bundle: bundle,
);
expect(report.success, true);
expect(devFS.shaderPathsToEvict, <String>{'foo.frag'});
expect(devFS.assetPathsToEvict, <String>{'not.frag'});
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('DevFS tracks when FontManifest is updated', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[createDevFSRequest],
httpAddress: Uri.parse('http://localhost'),
);
final BufferLogger logger = BufferLogger.test();
final DevFS devFS = DevFS(
fakeVmServiceHost.vmService,
'test',
fileSystem.currentDirectory,
fileSystem: fileSystem,
logger: logger,
osUtils: FakeOperatingSystemUtils(),
httpClient: FakeHttpClient.any(),
);
await devFS.create();
expect(devFS.didUpdateFontManifest, false);
final FakeResidentCompiler residentCompiler = FakeResidentCompiler()
..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>[]);
};
final FakeBundle bundle = FakeBundle()
..entries['FontManifest.json'] = DevFSByteContent(<int>[1, 2, 3, 4]);
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,
shaderCompiler: const FakeShaderCompiler(),
bundle: bundle,
);
expect(report.success, true);
expect(devFS.shaderPathsToEvict, <String>{});
expect(devFS.assetPathsToEvict, <String>{'FontManifest.json'});
expect(devFS.didUpdateFontManifest, true);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
});
}
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, bool checkDartPluginRegistry = false, File? dartPluginRegistrant}) {
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;
}
}
class FakeBundle extends AssetBundle {
@override
List<File> get additionalDependencies => <File>[];
@override
Future<int> build({String manifestPath = defaultManifestPath, String? assetDirPath, String? packagesPath, bool deferredComponentsEnabled = false, TargetPlatform? targetPlatform}) async {
return 0;
}
@override
Map<String, Map<String, DevFSContent>> get deferredComponentsEntries => <String, Map<String, DevFSContent>>{};
@override
final Map<String, DevFSContent> entries = <String, DevFSContent>{};
@override
final Map<String, AssetKind> entryKinds = <String, AssetKind>{};
@override
List<File> get inputFiles => <File>[];
@override
bool needsBuild({String manifestPath = defaultManifestPath}) {
return true;
}
@override
bool wasBuiltOnce() {
return false;
}
}
class AnsweringFakeProcessManager implements ProcessManager {
AnsweringFakeProcessManager(this.stdout, this.stderr, this.stdin);
final Stream<List<int>> stdout;
final Stream<List<int>> stderr;
final IOSink stdin;
@override
bool canRun(dynamic executable, {String? workingDirectory}) {
return true;
}
@override
bool killPid(int pid, [io.ProcessSignal signal = io.ProcessSignal.sigterm]) {
return true;
}
@override
Future<ProcessResult> run(List<Object> command, {String? workingDirectory, Map<String, String>? environment, bool includeParentEnvironment = true, bool runInShell = false, Encoding? stdoutEncoding = systemEncoding, Encoding? stderrEncoding = systemEncoding}) async {
throw UnimplementedError();
}
@override
ProcessResult runSync(List<Object> command, {String? workingDirectory, Map<String, String>? environment, bool includeParentEnvironment = true, bool runInShell = false, Encoding? stdoutEncoding = systemEncoding, Encoding? stderrEncoding = systemEncoding}) {
throw UnimplementedError();
}
@override
Future<Process> start(List<Object> command, {String? workingDirectory, Map<String, String>? environment, bool includeParentEnvironment = true, bool runInShell = false, ProcessStartMode mode = ProcessStartMode.normal}) async {
return AnsweringFakeProcess(stdout, stderr, stdin);
}
}
class AnsweringFakeProcess implements io.Process {
AnsweringFakeProcess(this.stdout,this.stderr, this.stdin);
@override
final Stream<List<int>> stdout;
@override
final Stream<List<int>> stderr;
@override
final IOSink stdin;
@override
Future<int> get exitCode async => 0;
@override
bool kill([io.ProcessSignal signal = io.ProcessSignal.sigterm]) {
return true;
}
@override
int get pid => 42;
}
class FakeShaderCompiler implements DevelopmentShaderCompiler {
const FakeShaderCompiler();
@override
void configureCompiler(
TargetPlatform? platform, {
required ImpellerStatus impellerStatus,
}) { }
@override
Future<DevFSContent> recompileShader(DevFSContent inputShader) async {
return DevFSByteContent(await inputShader.contentsAsBytes());
}
}