| // 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 'dart:typed_data'; | |
| import 'package:args/args.dart'; | |
| 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/config.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/tools/asset_transformer.dart'; | |
| import 'package:flutter_tools/src/build_system/tools/shader_compiler.dart'; | |
| import 'package:flutter_tools/src/compile.dart'; | |
| import 'package:flutter_tools/src/devfs.dart'; | |
| import 'package:flutter_tools/src/flutter_manifest.dart'; | |
| import 'package:package_config/package_config.dart'; | |
| import 'package:test/fake.dart'; | |
| import 'package:vm_service/vm_service.dart' as vm_service; | |
| import '../src/common.dart'; | |
| import '../src/context.dart'; | |
| import '../src/fake_http_client.dart'; | |
| import '../src/fake_process_manager.dart'; | |
| import '../src/fake_vm_services.dart'; | |
| import '../src/fakes.dart'; | |
| import '../src/logging_logger.dart'; | |
| final createDevFSRequest = FakeVmServiceRequest( | |
| method: '_createDevFS', | |
| args: <String, Object>{'fsName': 'test'}, | |
| jsonResponse: <String, Object>{'uri': Uri.parse('test').toString()}, | |
| ); | |
| FakeVmServiceRequest failingCreateDevFSRequest = FakeVmServiceRequest( | |
| method: '_createDevFS', | |
| args: <String, Object>{'fsName': 'test'}, | |
| error: FakeRPCError(code: vm_service.RPCErrorKind.kServiceDisappeared.code), | |
| ); | |
| FakeVmServiceRequest failingDeleteDevFSRequest = FakeVmServiceRequest( | |
| method: '_deleteDevFS', | |
| args: <String, dynamic>{'fsName': 'test'}, | |
| error: FakeRPCError(code: vm_service.RPCErrorKind.kServiceDisappeared.code), | |
| ); | |
| void main() { | |
| testWithoutContext('DevFSByteContent', () { | |
| final content = DevFSByteContent(<int>[4, 5, 6]); | |
| expect(content.bytes, orderedEquals(<int>[4, 5, 6])); | |
| expect(content.isModified, isTrue); | |
| expect(content.isModified, isTrue); | |
| content.markClean(); | |
| expect(content.isModified, isFalse); | |
| expect(content.isModified, isFalse); | |
| }); | |
| testWithoutContext('DevFSStringContent', () { | |
| final content = DevFSStringContent('some string'); | |
| expect(content.string, 'some string'); | |
| expect(content.bytes, orderedEquals(utf8.encode('some string'))); | |
| expect(content.isModified, isTrue); | |
| expect(content.isModified, isTrue); | |
| content.markClean(); | |
| expect(content.isModified, isFalse); | |
| expect(content.isModified, isFalse); | |
| }); | |
| testWithoutContext('DevFSFileContent', () async { | |
| final FileSystem fileSystem = MemoryFileSystem.test(); | |
| final File file = fileSystem.file('foo.txt'); | |
| final content = DevFSFileContent(file); | |
| content.markClean(); | |
| 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, isTrue); | |
| content.markClean(); | |
| 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, isTrue); | |
| content.markClean(); | |
| expect(content.isModified, isFalse); | |
| expect(content.isModified, isFalse); | |
| }); | |
| testWithoutContext( | |
| 'DevFSFileContent.isModifiedAfter only depends on the file modification time', | |
| () async { | |
| final FileSystem fileSystem = MemoryFileSystem.test(); | |
| final File file = fileSystem.file('foo.txt')..writeAsBytesSync(<int>[1, 2, 3], flush: true); | |
| // A freshly constructed DevFSFileContent has no cached stat, as is the | |
| // case for the entries of a newly built asset bundle. Such an entry must | |
| // not be reported as modified when its file is older than the given time. | |
| final content = DevFSFileContent(file); | |
| final DateTime modified = file.statSync().modified; | |
| expect(content.isModifiedAfter(modified.add(const Duration(seconds: 5))), isFalse); | |
| expect(content.isModifiedAfter(modified.subtract(const Duration(seconds: 5))), isTrue); | |
| file.deleteSync(); | |
| expect(content.isModifiedAfter(modified.subtract(const Duration(seconds: 5))), isFalse); | |
| }, | |
| ); | |
| testWithoutContext('DevFSStringCompressingBytesContent', () { | |
| final content = DevFSStringCompressingBytesContent('uncompressed string'); | |
| expect(content.equals('uncompressed string'), isTrue); | |
| expect(content.bytes, isNotNull); | |
| expect(content.isModified, isTrue); | |
| expect(content.isModified, isTrue); | |
| content.markClean(); | |
| expect(content.isModified, isFalse); | |
| 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( | |
| requests: <VmServiceExpectation>[failingCreateDevFSRequest], | |
| httpAddress: Uri.parse('http://localhost'), | |
| ); | |
| final devFS = DevFS( | |
| fakeVmServiceHost.vmService, | |
| 'test', | |
| fileSystem.currentDirectory, | |
| osUtils: osUtils, | |
| fileSystem: fileSystem, | |
| logger: BufferLogger.test(), | |
| httpClient: FakeHttpClient.any(), | |
| processManager: FakeProcessManager.empty(), | |
| artifacts: Artifacts.test(), | |
| buildMode: BuildMode.debug, | |
| ); | |
| 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( | |
| requests: <VmServiceExpectation>[createDevFSRequest, failingDeleteDevFSRequest], | |
| httpAddress: Uri.parse('http://localhost'), | |
| ); | |
| final devFS = DevFS( | |
| fakeVmServiceHost.vmService, | |
| 'test', | |
| fileSystem.currentDirectory, | |
| osUtils: osUtils, | |
| fileSystem: fileSystem, | |
| logger: BufferLogger.test(), | |
| httpClient: FakeHttpClient.any(), | |
| processManager: FakeProcessManager.empty(), | |
| artifacts: Artifacts.test(), | |
| buildMode: BuildMode.debug, | |
| ); | |
| 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 osUtils = OperatingSystemUtils( | |
| fileSystem: fileSystem, | |
| platform: FakePlatform(), | |
| logger: BufferLogger.test(), | |
| processManager: FakeProcessManager.any(), | |
| ); | |
| final residentCompiler = FakeResidentCompiler(); | |
| final 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( | |
| 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, | |
| processManager: FakeProcessManager.empty(), | |
| artifacts: Artifacts.test(), | |
| buildMode: BuildMode.debug, | |
| ); | |
| 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( | |
| requests: <VmServiceExpectation>[createDevFSRequest], | |
| httpAddress: Uri.parse('http://localhost'), | |
| ); | |
| final devFS = DevFS( | |
| fakeVmServiceHost.vmService, | |
| 'test', | |
| fileSystem.currentDirectory, | |
| fileSystem: fileSystem, | |
| logger: BufferLogger.test(), | |
| osUtils: FakeOperatingSystemUtils(), | |
| httpClient: FakeHttpClient.any(), | |
| processManager: FakeProcessManager.empty(), | |
| artifacts: Artifacts.test(), | |
| buildMode: BuildMode.debug, | |
| ); | |
| await devFS.create(); | |
| final DateTime? previousCompile = devFS.lastCompiled; | |
| final 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( | |
| requests: <VmServiceExpectation>[createDevFSRequest], | |
| httpAddress: Uri.parse('http://localhost'), | |
| ); | |
| final devFS = DevFS( | |
| fakeVmServiceHost.vmService, | |
| 'test', | |
| fileSystem.currentDirectory, | |
| fileSystem: fileSystem, | |
| logger: BufferLogger.test(), | |
| osUtils: FakeOperatingSystemUtils(), | |
| httpClient: FakeHttpClient.any(), | |
| processManager: FakeProcessManager.empty(), | |
| artifacts: Artifacts.test(), | |
| buildMode: BuildMode.debug, | |
| ); | |
| await devFS.create(); | |
| final DateTime? previousCompile = devFS.lastCompiled; | |
| final 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( | |
| requests: <VmServiceExpectation>[createDevFSRequest], | |
| ); | |
| final localDevFSWriter = LocalDevFSWriter(fileSystem: fileSystem); | |
| fileSystem.directory('test').createSync(); | |
| final devFS = DevFS( | |
| fakeVmServiceHost.vmService, | |
| 'test', | |
| fileSystem.currentDirectory, | |
| fileSystem: fileSystem, | |
| logger: BufferLogger.test(), | |
| osUtils: FakeOperatingSystemUtils(), | |
| httpClient: HttpClient(), | |
| processManager: FakeProcessManager.empty(), | |
| artifacts: Artifacts.test(), | |
| buildMode: BuildMode.debug, | |
| ); | |
| await devFS.create(); | |
| final DateTime? previousCompile = devFS.lastCompiled; | |
| final 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 writer = FakeDevFSWriter(); | |
| final fakeVmServiceHost = FakeVmServiceHost( | |
| requests: <VmServiceExpectation>[createDevFSRequest], | |
| ); | |
| final devFS = DevFS( | |
| fakeVmServiceHost.vmService, | |
| 'test', | |
| fileSystem.currentDirectory, | |
| fileSystem: fileSystem, | |
| logger: BufferLogger.test(), | |
| osUtils: FakeOperatingSystemUtils(), | |
| httpClient: FakeHttpClient.any(), | |
| processManager: FakeProcessManager.empty(), | |
| artifacts: Artifacts.test(), | |
| buildMode: BuildMode.debug, | |
| ); | |
| await devFS.create(); | |
| final 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 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 handler = FileExceptionHandler(); | |
| final FileSystem fileSystem = MemoryFileSystem.test(opHandle: handler.opHandle); | |
| final 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( | |
| requests: <VmServiceExpectation>[createDevFSRequest], | |
| httpAddress: Uri.parse('http://localhost'), | |
| ); | |
| final 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), | |
| }, | |
| ), | |
| processManager: FakeProcessManager.empty(), | |
| artifacts: Artifacts.test(), | |
| buildMode: BuildMode.debug, | |
| ); | |
| await devFS.create(); | |
| final 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( | |
| requests: <VmServiceExpectation>[createDevFSRequest], | |
| httpAddress: Uri.parse('http://localhost'), | |
| ); | |
| final logger = LoggingLogger(); | |
| final devFS = DevFS( | |
| fakeVmServiceHost.vmService, | |
| 'test', | |
| fileSystem.currentDirectory, | |
| fileSystem: fileSystem, | |
| logger: logger, | |
| osUtils: FakeOperatingSystemUtils(), | |
| httpClient: FakeHttpClient.any(), | |
| processManager: FakeProcessManager.empty(), | |
| artifacts: Artifacts.test(), | |
| buildMode: BuildMode.debug, | |
| ); | |
| await devFS.create(); | |
| final frontendServerStdIn = MemoryIOSink(); | |
| Stream<List<int>> frontendServerStdOut() async* { | |
| var 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 fakeProcessManager = AnsweringFakeProcessManager( | |
| frontendServerStdOut(), | |
| frontendServerStdErr(), | |
| frontendServerStdIn, | |
| ); | |
| final generatorStdoutHandler = StdoutHandler(logger: testLogger, fileSystem: fileSystem); | |
| final residentCompiler = DefaultResidentCompiler( | |
| 'sdkroot', | |
| logger: logger, | |
| processManager: fakeProcessManager, | |
| artifacts: Artifacts.test(), | |
| platform: FakePlatform(), | |
| fileSystem: fileSystem, | |
| stdoutHandler: generatorStdoutHandler, | |
| shutdownHooks: FakeShutdownHooks(), | |
| config: Config.test(), | |
| buildInfo: BuildInfo.debug, | |
| ); | |
| 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', () { | |
| testWithoutContext('DevFS recompiles shaders', () async { | |
| final fileSystem = MemoryFileSystem.test(); | |
| final fakeVmServiceHost = FakeVmServiceHost( | |
| requests: <VmServiceExpectation>[createDevFSRequest], | |
| httpAddress: Uri.parse('http://localhost'), | |
| ); | |
| final logger = BufferLogger.test(); | |
| final devFS = DevFS( | |
| fakeVmServiceHost.vmService, | |
| 'test', | |
| fileSystem.currentDirectory, | |
| fileSystem: fileSystem, | |
| logger: logger, | |
| osUtils: FakeOperatingSystemUtils(), | |
| httpClient: FakeHttpClient.any(), | |
| config: Config.test(), | |
| processManager: FakeProcessManager.empty(), | |
| artifacts: Artifacts.test(), | |
| buildMode: BuildMode.debug, | |
| ); | |
| await devFS.create(); | |
| final 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 bundle = FakeBundle() | |
| ..entries['foo.frag'] = AssetBundleEntry( | |
| DevFSByteContent(<int>[1, 2, 3, 4]), | |
| kind: AssetKind.shader, | |
| transformers: const <AssetTransformerEntry>[], | |
| ) | |
| ..entries['not.frag'] = AssetBundleEntry( | |
| DevFSByteContent(<int>[1, 2, 3, 4]), | |
| kind: AssetKind.regular, | |
| transformers: const <AssetTransformerEntry>[], | |
| ); | |
| 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'}); | |
| }); | |
| testWithoutContext('DevFS tracks when FontManifest is updated', () async { | |
| final fileSystem = MemoryFileSystem.test(); | |
| final fakeVmServiceHost = FakeVmServiceHost( | |
| requests: <VmServiceExpectation>[createDevFSRequest], | |
| httpAddress: Uri.parse('http://localhost'), | |
| ); | |
| final logger = BufferLogger.test(); | |
| final devFS = DevFS( | |
| fakeVmServiceHost.vmService, | |
| 'test', | |
| fileSystem.currentDirectory, | |
| fileSystem: fileSystem, | |
| logger: logger, | |
| osUtils: FakeOperatingSystemUtils(), | |
| httpClient: FakeHttpClient.any(), | |
| config: Config.test(), | |
| processManager: FakeProcessManager.empty(), | |
| artifacts: Artifacts.test(), | |
| buildMode: BuildMode.debug, | |
| ); | |
| await devFS.create(); | |
| expect(devFS.didUpdateFontManifest, false); | |
| final 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 bundle = FakeBundle() | |
| ..entries['FontManifest.json'] = AssetBundleEntry( | |
| DevFSByteContent(<int>[1, 2, 3, 4]), | |
| kind: AssetKind.regular, | |
| transformers: const <AssetTransformerEntry>[], | |
| ); | |
| 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); | |
| }); | |
| }); | |
| group('Asset transformation', () { | |
| testWithoutContext('DevFS re-transforms assets with transformers during update', () async { | |
| final fileSystem = MemoryFileSystem.test(); | |
| final artifacts = Artifacts.test(); | |
| final devFSWriter = FakeDevFSWriter(); | |
| final processManager = FakeProcessManager.list(<FakeCommand>[ | |
| FakeCommand( | |
| command: <Pattern>[ | |
| artifacts.getArtifactPath(Artifact.engineDartBinary), | |
| 'run', | |
| 'increment', | |
| '--input=/.tmp_rand0/rand0/retransformerInput-asset.txt-transformOutput0.txt', | |
| '--output=/.tmp_rand0/rand0/retransformerInput-asset.txt-transformOutput1.txt', | |
| ], | |
| onRun: (List<String> command) { | |
| final ArgResults argParseResults = | |
| (ArgParser() | |
| ..addOption('input', mandatory: true) | |
| ..addOption('output', mandatory: true)) | |
| .parse(command); | |
| final File inputFile = fileSystem.file(argParseResults['input']); | |
| final File outputFile = fileSystem.file(argParseResults['output']); | |
| expect(inputFile, exists); | |
| outputFile | |
| ..createSync() | |
| ..writeAsBytesSync( | |
| Uint8List.fromList(inputFile.readAsBytesSync().map((int b) => b + 1).toList()), | |
| ); | |
| }, | |
| ), | |
| ]); | |
| final fakeVmServiceHost = FakeVmServiceHost( | |
| requests: <VmServiceExpectation>[createDevFSRequest], | |
| httpAddress: Uri.parse('http://localhost'), | |
| ); | |
| final logger = BufferLogger.test(); | |
| final devFS = DevFS( | |
| fakeVmServiceHost.vmService, | |
| 'test', | |
| fileSystem.currentDirectory, | |
| fileSystem: fileSystem, | |
| logger: logger, | |
| osUtils: FakeOperatingSystemUtils(), | |
| httpClient: FakeHttpClient.any(), | |
| config: Config.test(), | |
| processManager: processManager, | |
| artifacts: artifacts, | |
| buildMode: BuildMode.debug, | |
| ); | |
| await devFS.create(); | |
| final 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 bundle = FakeBundle() | |
| ..entries['asset.txt'] = AssetBundleEntry( | |
| DevFSByteContent(<int>[1, 2, 3, 4]), | |
| kind: AssetKind.regular, | |
| transformers: const <AssetTransformerEntry>[ | |
| AssetTransformerEntry(package: 'increment', args: <String>[]), | |
| ], | |
| ); | |
| 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: devFSWriter, | |
| shaderCompiler: const FakeShaderCompiler(), | |
| bundle: bundle, | |
| ); | |
| expect(processManager, hasNoRemainingExpectations); | |
| expect(report.success, true); | |
| expect(devFSWriter.entries, isNotNull); | |
| final assetUri = Uri(path: 'build/flutter_assets/asset.txt'); | |
| expect(devFSWriter.entries, contains(assetUri)); | |
| expect( | |
| await devFSWriter.entries![assetUri]!.contentsAsBytes(), | |
| containsAllInOrder(<int>[2, 3, 4, 5]), | |
| ); | |
| }); | |
| testWithoutContext('DevFS reports failure when asset transformation fails', () async { | |
| final fileSystem = MemoryFileSystem.test(); | |
| final artifacts = Artifacts.test(); | |
| final devFSWriter = FakeDevFSWriter(); | |
| final processManager = FakeProcessManager.list(<FakeCommand>[ | |
| FakeCommand( | |
| command: <Pattern>[ | |
| artifacts.getArtifactPath(Artifact.engineDartBinary), | |
| 'run', | |
| 'increment', | |
| '--input=/.tmp_rand0/rand0/retransformerInput-asset.txt-transformOutput0.txt', | |
| '--output=/.tmp_rand0/rand0/retransformerInput-asset.txt-transformOutput1.txt', | |
| ], | |
| exitCode: 1, | |
| ), | |
| ]); | |
| final fakeVmServiceHost = FakeVmServiceHost( | |
| requests: <VmServiceExpectation>[createDevFSRequest], | |
| httpAddress: Uri.parse('http://localhost'), | |
| ); | |
| final logger = BufferLogger.test(); | |
| final devFS = DevFS( | |
| fakeVmServiceHost.vmService, | |
| 'test', | |
| fileSystem.currentDirectory, | |
| fileSystem: fileSystem, | |
| logger: logger, | |
| osUtils: FakeOperatingSystemUtils(), | |
| httpClient: FakeHttpClient.any(), | |
| config: Config.test(), | |
| processManager: processManager, | |
| artifacts: artifacts, | |
| buildMode: BuildMode.debug, | |
| ); | |
| await devFS.create(); | |
| final 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 bundle = FakeBundle() | |
| ..entries['asset.txt'] = AssetBundleEntry( | |
| DevFSByteContent(<int>[1, 2, 3, 4]), | |
| kind: AssetKind.regular, | |
| transformers: const <AssetTransformerEntry>[ | |
| AssetTransformerEntry(package: 'increment', args: <String>[]), | |
| ], | |
| ); | |
| 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: devFSWriter, | |
| shaderCompiler: const FakeShaderCompiler(), | |
| bundle: bundle, | |
| ); | |
| expect(processManager, hasNoRemainingExpectations); | |
| expect( | |
| report.success, | |
| false, | |
| reason: 'DevFS update should fail since asset transformation failed.', | |
| ); | |
| expect( | |
| devFSWriter.entries, | |
| isNull, | |
| reason: 'DevFS should not have written anything since the update failed.', | |
| ); | |
| expect( | |
| logger.errorText, | |
| 'Transformer process terminated with non-zero exit code: 1\n' | |
| 'Transformer package: increment\n' | |
| 'Full command: Artifact.engineDartBinary run increment --input=/.tmp_rand0/rand0/retransformerInput-asset.txt-transformOutput0.txt --output=/.tmp_rand0/rand0/retransformerInput-asset.txt-transformOutput1.txt\n' | |
| 'stdout:\n' | |
| '\n' | |
| 'stderr:\n' | |
| '\n' | |
| 'Error updating bundle: AssetTransformationException: Failed to transform asset (Asset: asset.txt)\n', | |
| ); | |
| }); | |
| testWithoutContext( | |
| 'DevFS.updateBundle ensures all side effects are completed before returning (regression test for race condition)', | |
| () async { | |
| final FileSystem fileSystem = MemoryFileSystem.test(); | |
| final dirtyEntries = <Uri, DevFSContent>{}; | |
| final assetBundle = FakeBundle(); | |
| assetBundle.entries['shader.frag'] = AssetBundleEntry( | |
| DevFSStringContent('source'), | |
| kind: AssetKind.shader, | |
| transformers: const [], | |
| ); | |
| final shaderCompleter = Completer<DevFSContent>(); | |
| final shaderCompiler = DelayedFakeShaderCompiler(shaderCompleter.future); | |
| final assetTransformer = DevelopmentAssetTransformer( | |
| fileSystem: fileSystem, | |
| transformer: AssetTransformer( | |
| processManager: FakeProcessManager.any(), | |
| fileSystem: fileSystem, | |
| dartBinaryPath: 'dart', | |
| buildMode: BuildMode.debug, | |
| ), | |
| logger: BufferLogger.test(), | |
| ); | |
| final Future<int> updateFuture = DevFS.updateBundle( | |
| bundle: assetBundle, | |
| dirtyEntries: dirtyEntries, | |
| assetDirectory: 'assets', | |
| assetTransformer: assetTransformer, | |
| shaderCompiler: shaderCompiler, | |
| fileSystem: fileSystem, | |
| rootDirectoryPath: '/', | |
| assetPathsToEvict: <String>{}, | |
| shaderPathsToEvict: <String>{}, | |
| bundleFirstUpload: true, | |
| syncAllAssetsOnFirstUpload: true, | |
| ); | |
| // Complete the shader compilation. | |
| shaderCompleter.complete(DevFSStringContent('compiled')); | |
| // Wait for updateBundle to return. | |
| await updateFuture; | |
| // Verify side effects are visible immediately. | |
| // In the broken code, this could fail if updateBundle returned before the .then callback finished. | |
| expect(dirtyEntries, hasLength(1)); | |
| expect(await dirtyEntries.values.first.contentsAsBytes(), utf8.encode('compiled')); | |
| }, | |
| ); | |
| testWithoutContext( | |
| 'DevFS.updateBundle initializes isModified state of assets during first upload when sync is skipped', | |
| () async { | |
| final FileSystem fileSystem = MemoryFileSystem.test(); | |
| final dirtyEntries = <Uri, DevFSContent>{}; | |
| final assetBundle = FakeBundle(); | |
| final assetContent = DevFSByteContent(<int>[1, 2, 3, 4]); | |
| assetBundle.entries['asset.txt'] = AssetBundleEntry( | |
| assetContent, | |
| kind: AssetKind.regular, | |
| transformers: const [], | |
| ); | |
| const shaderCompiler = FakeShaderCompiler(); | |
| final assetTransformer = DevelopmentAssetTransformer( | |
| fileSystem: fileSystem, | |
| transformer: AssetTransformer( | |
| processManager: FakeProcessManager.any(), | |
| fileSystem: fileSystem, | |
| dartBinaryPath: 'dart', | |
| buildMode: BuildMode.debug, | |
| ), | |
| logger: BufferLogger.test(), | |
| ); | |
| // Perform the first upload with sync skipped. | |
| final int firstUploadSyncedBytes = await DevFS.updateBundle( | |
| bundle: assetBundle, | |
| dirtyEntries: dirtyEntries, | |
| assetDirectory: 'assets', | |
| assetTransformer: assetTransformer, | |
| shaderCompiler: shaderCompiler, | |
| fileSystem: fileSystem, | |
| rootDirectoryPath: '/', | |
| assetPathsToEvict: <String>{}, | |
| shaderPathsToEvict: <String>{}, | |
| bundleFirstUpload: true, | |
| ); | |
| expect(firstUploadSyncedBytes, 0); | |
| expect(dirtyEntries, isEmpty); | |
| // Perform a subsequent hot restart update (where bundleFirstUpload is false). | |
| // Since the asset has not been modified since the first upload, it should not be synced. | |
| final int secondUploadSyncedBytes = await DevFS.updateBundle( | |
| bundle: assetBundle, | |
| dirtyEntries: dirtyEntries, | |
| assetDirectory: 'assets', | |
| assetTransformer: assetTransformer, | |
| shaderCompiler: shaderCompiler, | |
| fileSystem: fileSystem, | |
| rootDirectoryPath: '/', | |
| assetPathsToEvict: <String>{}, | |
| shaderPathsToEvict: <String>{}, | |
| bundleFirstUpload: false, | |
| ); | |
| expect(secondUploadSyncedBytes, 0); | |
| expect(dirtyEntries, isEmpty); | |
| }, | |
| ); | |
| }); | |
| } | |
| class DelayedFakeShaderCompiler implements DevelopmentShaderCompiler { | |
| DelayedFakeShaderCompiler(this.future); | |
| final Future<DevFSContent> future; | |
| @override | |
| void configureCompiler(TargetPlatform? platform) {} | |
| @override | |
| Future<DevFSContent> recompileShader(DevFSContent inputShader) => future; | |
| @override | |
| bool areDependenciesModified(DevFSContent shaderContent) => false; | |
| } | |
| 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, | |
| bool recompileRestart = false, | |
| }) { | |
| return onRecompile?.call(mainUri, invalidatedFiles) ?? | |
| Future<CompilerOutput>.value(const CompilerOutput('', 1, <Uri>[])); | |
| } | |
| } | |
| class FakeDevFSWriter implements DevFSWriter { | |
| bool written = false; | |
| Map<Uri, DevFSContent>? entries; | |
| @override | |
| Future<void> write(Map<Uri, DevFSContent> entries, Uri baseUri, DevFSWriter parent) async { | |
| written = true; | |
| this.entries = entries; | |
| } | |
| } | |
| class FakeBundle extends AssetBundle { | |
| @override | |
| List<File> get additionalDependencies => <File>[]; | |
| @override | |
| Future<int> build({ | |
| FlutterHookResult? flutterHookResult, | |
| String manifestPath = defaultManifestPath, | |
| String? assetDirPath, | |
| String? packageConfigPath, | |
| bool deferredComponentsEnabled = false, | |
| TargetPlatform? targetPlatform, | |
| String? flavor, | |
| bool includeAssetsFromDevDependencies = false, | |
| }) async { | |
| return 0; | |
| } | |
| @override | |
| Map<String, Map<String, AssetBundleEntry>> get deferredComponentsEntries => | |
| <String, Map<String, AssetBundleEntry>>{}; | |
| @override | |
| final 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()); | |
| } | |
| @override | |
| bool areDependenciesModified(DevFSContent shaderContent) => false; | |
| } |