flutter/packages/flutter_tools/test/general.shard/devfs_test.dart
Dan Field c417c4623c
Refactor ShaderTarget to not explicitly mention impeller or Skia (#141460)
Refactors `ShaderTarget` to make it opaque as to whether it's using Impeller or SkSL and instead has it focus on the target platform it's generating for.

ImpellerC includes SkSL right now whether you ask for it or not. 

The tester target also might need SkSL or Vulkan depending on whether `--enable-impeller` is passed.
2024-01-31 21:30:02 +00:00

840 lines
30 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.
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/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'] = AssetBundleEntry(
DevFSByteContent(<int>[1, 2, 3, 4]),
kind: AssetKind.shader,
)
..entries['not.frag'] = AssetBundleEntry(
DevFSByteContent(<int>[1, 2, 3, 4]),
kind: AssetKind.regular,
);
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'] = AssetBundleEntry(
DevFSByteContent(<int>[1, 2, 3, 4]),
kind: AssetKind.regular,
);
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,
Uri? nativeAssetsYaml,
}) {
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,
String? flavor,
}) async {
return 0;
}
@override
Map<String, Map<String, AssetBundleEntry>> get deferredComponentsEntries => <String, Map<String, AssetBundleEntry>>{};
@override
final Map<String, AssetBundleEntry> entries = <String, AssetBundleEntry>{};
@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) { }
@override
Future<DevFSContent> recompileShader(DevFSContent inputShader) async {
return DevFSByteContent(await inputShader.contentsAsBytes());
}
}