| // Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file | |
| // for details. 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:convert'; | |
| import 'dart:io'; | |
| import 'package:dartdev/src/executable_compiler.dart'; | |
| import 'package:path/path.dart' as p; | |
| import 'package:pub/pub.dart'; | |
| import 'package:test/test.dart'; | |
| import 'utils.dart'; | |
| void main() { | |
| group('ExecutableCompiler', () { | |
| test('pathOfSnapshot returns expected path format', () { | |
| final snapshotPath = ExecutableCompiler.pathOfSnapshot( | |
| workspaceRoot: '/my/workspace', | |
| packageName: 'foo', | |
| relativeScriptPath: p.join('bin', 'bar.dart'), | |
| ); | |
| expect( | |
| snapshotPath, | |
| p.normalize( | |
| p.join( | |
| '/my/workspace', | |
| '.dart_tool', | |
| 'dartdev', | |
| 'bin', | |
| 'foo', | |
| 'bin', | |
| 'bar.dart.snapshot', | |
| ), | |
| ), | |
| ); | |
| }); | |
| test('parseDepfile parses Ninja/Makefile depfiles', () { | |
| expect(ExecutableCompiler.parseDepfile('target: '), isEmpty); | |
| expect(ExecutableCompiler.parseDepfile('target: '), isEmpty); | |
| expect(ExecutableCompiler.parseDepfile('no colon'), isNull); | |
| expect(ExecutableCompiler.parseDepfile(r'target: foo.dart\'), isNull); | |
| expect( | |
| ExecutableCompiler.parseDepfile('target: foo.dart bar.dart\n'), | |
| ['foo.dart', 'bar.dart'], | |
| ); | |
| expect( | |
| ExecutableCompiler.parseDepfile(r'target: /path\ with/space.dart'), | |
| ['/path with/space.dart'], | |
| ); | |
| expect( | |
| ExecutableCompiler.parseDepfile(r'target: /path\\\ with/space.dart'), | |
| [r'/path\ with/space.dart'], | |
| ); | |
| expect( | |
| ExecutableCompiler.parseDepfile(r'target: c:\\foo\\bar.dart'), | |
| [r'c:\foo\bar.dart'], | |
| ); | |
| }); | |
| test( | |
| 'compiles executable and reuses snapshot when fresh via depfile', | |
| () async { | |
| final pDir = Directory.systemTemp.createTempSync('exec_compiler_test'); | |
| addTearDown(() => pDir.deleteSync(recursive: true)); | |
| final workspaceDir = p.join(pDir.path, 'workspace'); | |
| final pkgDir = p.join(pDir.path, 'pkg'); | |
| Directory(p.join(pkgDir, 'bin')).createSync(recursive: true); | |
| Directory(p.join(pkgDir, 'lib')).createSync(recursive: true); | |
| final helperFile = File(p.join(pkgDir, 'lib', 'helper.dart')); | |
| helperFile.writeAsStringSync( | |
| 'String get message => "Hello from dep!";', | |
| ); | |
| final scriptFile = File(p.join(pkgDir, 'bin', 'hello.dart')); | |
| scriptFile.writeAsStringSync(''' | |
| import 'package:dep/helper.dart'; | |
| void main() => print(message); | |
| '''); | |
| final dotDartTool = Directory(p.join(workspaceDir, '.dart_tool')); | |
| dotDartTool.createSync(recursive: true); | |
| final packageConfigFile = File( | |
| p.join(dotDartTool.path, 'package_config.json'), | |
| ); | |
| packageConfigFile.writeAsStringSync( | |
| jsonEncode({ | |
| 'configVersion': 2, | |
| 'packages': [ | |
| { | |
| 'name': 'dep', | |
| 'rootUri': p.toUri(pkgDir).toString(), | |
| 'packageUri': 'lib/', | |
| 'languageVersion': '3.0', | |
| }, | |
| ], | |
| }), | |
| ); | |
| final resolved = DartExecutableWithPackageConfig( | |
| executable: scriptFile.path, | |
| packageConfig: packageConfigFile.path, | |
| ); | |
| // First compilation creates snapshot and depfile | |
| final firstResult = await ExecutableCompiler.compile( | |
| resolvedExecutable: resolved, | |
| ); | |
| final expectedSnapshot = ExecutableCompiler.pathOfSnapshot( | |
| workspaceRoot: workspaceDir, | |
| packageName: 'dep', | |
| relativeScriptPath: p.join('bin', 'hello.dart'), | |
| ); | |
| expect(File(expectedSnapshot).existsSync(), isTrue); | |
| final depfile = File( | |
| '$expectedSnapshot${ExecutableCompiler.depfileExtension}', | |
| ); | |
| expect(depfile.existsSync(), isTrue); | |
| final deps = ExecutableCompiler.parseDepfile( | |
| depfile.readAsStringSync(), | |
| ); | |
| final canonicalDeps = deps?.map(p.canonicalize).toList(); | |
| expect(canonicalDeps, contains(p.canonicalize(scriptFile.path))); | |
| expect(canonicalDeps, contains(p.canonicalize(helperFile.path))); | |
| final firstModified = File(expectedSnapshot).lastModifiedSync(); | |
| // Second call reuses cached snapshot because no sources or config changed | |
| final secondResult = await ExecutableCompiler.compile( | |
| resolvedExecutable: resolved, | |
| ); | |
| expect(firstResult.executable, secondResult.executable); | |
| final secondModified = File(expectedSnapshot).lastModifiedSync(); | |
| expect(firstModified, equals(secondModified)); | |
| // Modifying an imported helper file causes recompilation! | |
| // Backdate the snapshot timestamp so helperFile is strictly newer. | |
| File(expectedSnapshot).setLastModifiedSync( | |
| DateTime.now().subtract(const Duration(seconds: 10)), | |
| ); | |
| helperFile.setLastModifiedSync(DateTime.now()); | |
| await ExecutableCompiler.compile( | |
| resolvedExecutable: resolved, | |
| ); | |
| final thirdModified = File(expectedSnapshot).lastModifiedSync(); | |
| expect(thirdModified.isAfter(firstModified), isTrue); | |
| // Updating package_config.json also causes recompilation | |
| File(expectedSnapshot).setLastModifiedSync( | |
| DateTime.now().subtract(const Duration(seconds: 10)), | |
| ); | |
| packageConfigFile.setLastModifiedSync(DateTime.now()); | |
| await ExecutableCompiler.compile( | |
| resolvedExecutable: resolved, | |
| ); | |
| final fourthModified = File(expectedSnapshot).lastModifiedSync(); | |
| expect(fourthModified.isAfter(thirdModified), isTrue); | |
| // The metadata records the configuration the snapshot was built with. | |
| final metadataFile = File( | |
| '$expectedSnapshot${ExecutableCompiler.metadataExtension}', | |
| ); | |
| expect(metadataFile.existsSync(), isTrue); | |
| expect(jsonDecode(metadataFile.readAsStringSync()), { | |
| 'version': ExecutableCompiler.metadataVersion, | |
| 'sdkVersion': ExecutableCompiler.sdkVersion, | |
| 'enabledExperiments': <String>[], | |
| }); | |
| // Asking for a different set of experiments recompiles, even though | |
| // nothing on disk changed. | |
| File(expectedSnapshot).setLastModifiedSync( | |
| DateTime.now().subtract(const Duration(seconds: 10)), | |
| ); | |
| final fourthModifiedBackdated = File( | |
| expectedSnapshot, | |
| ).lastModifiedSync(); | |
| await ExecutableCompiler.compile( | |
| resolvedExecutable: resolved, | |
| enabledExperiments: ['native-assets'], | |
| ); | |
| final fifthModified = File(expectedSnapshot).lastModifiedSync(); | |
| expect(fifthModified.isAfter(fourthModifiedBackdated), isTrue); | |
| expect(jsonDecode(metadataFile.readAsStringSync()), { | |
| 'version': ExecutableCompiler.metadataVersion, | |
| 'sdkVersion': ExecutableCompiler.sdkVersion, | |
| 'enabledExperiments': ['native-assets'], | |
| }); | |
| // ... and asking for them again reuses the snapshot. | |
| await ExecutableCompiler.compile( | |
| resolvedExecutable: resolved, | |
| enabledExperiments: ['native-assets'], | |
| ); | |
| expect( | |
| File(expectedSnapshot).lastModifiedSync(), | |
| equals(fifthModified), | |
| ); | |
| // A snapshot built by another SDK is not reused. | |
| File(expectedSnapshot).setLastModifiedSync( | |
| DateTime.now().subtract(const Duration(seconds: 10)), | |
| ); | |
| final fifthModifiedBackdated = File( | |
| expectedSnapshot, | |
| ).lastModifiedSync(); | |
| metadataFile.writeAsStringSync( | |
| jsonEncode({ | |
| 'version': ExecutableCompiler.metadataVersion, | |
| 'sdkVersion': 'some-other-sdk-version', | |
| 'enabledExperiments': ['native-assets'], | |
| }), | |
| ); | |
| await ExecutableCompiler.compile( | |
| resolvedExecutable: resolved, | |
| enabledExperiments: ['native-assets'], | |
| ); | |
| expect( | |
| File(expectedSnapshot).lastModifiedSync().isAfter( | |
| fifthModifiedBackdated, | |
| ), | |
| isTrue, | |
| ); | |
| }, | |
| ); | |
| test( | |
| 'sweepStaleTempDirs only removes aged-out temp directories', | |
| () async { | |
| final dir = Directory.systemTemp.createTempSync('exec_compiler_sweep'); | |
| addTearDown(() => dir.deleteSync(recursive: true)); | |
| Directory tempDir(String name) => | |
| Directory(p.join(dir.path, name))..createSync(); | |
| final abandoned = tempDir('tmpAbandoned'); | |
| File(p.join(abandoned.path, 'leftover.dill')).writeAsStringSync('junk'); | |
| final alsoAbandoned = tempDir('tmpOther'); | |
| // Directories not created by the compiler must never be touched, even | |
| // though they sit next to the snapshots. | |
| final unrelated = tempDir('bar'); | |
| final snapshot = File(p.join(dir.path, 'hello.dart.snapshot')) | |
| ..writeAsStringSync('dill'); | |
| // Nothing is old enough to be swept yet. | |
| ExecutableCompiler.sweepStaleTempDirs( | |
| dir, | |
| maxAge: const Duration(days: 365), | |
| ); | |
| expect(abandoned.existsSync(), isTrue); | |
| expect(alsoAbandoned.existsSync(), isTrue); | |
| // Wait briefly so file modification timestamps are strictly in the past. | |
| await Future.delayed(const Duration(milliseconds: 50)); | |
| // Everything has now aged out, but only the temp directories go. | |
| ExecutableCompiler.sweepStaleTempDirs(dir, maxAge: Duration.zero); | |
| expect(abandoned.existsSync(), isFalse); | |
| expect(alsoAbandoned.existsSync(), isFalse); | |
| expect(unrelated.existsSync(), isTrue); | |
| expect(snapshot.existsSync(), isTrue); | |
| }, | |
| ); | |
| test('compile removes the temp directory it created', () async { | |
| final pDir = Directory.systemTemp.createTempSync('exec_compiler_tmp'); | |
| addTearDown(() => pDir.deleteSync(recursive: true)); | |
| final workspaceDir = p.join(pDir.path, 'workspace'); | |
| final fakePubCache = p.join(pDir.path, 'cache'); | |
| final pkgDir = p.join(fakePubCache, 'hosted', 'pub.dev', 'dep-1.0.0'); | |
| Directory(p.join(pkgDir, 'bin')).createSync(recursive: true); | |
| final scriptFile = File(p.join(pkgDir, 'bin', 'hello.dart')); | |
| scriptFile.writeAsStringSync('void main() => print("Hello from dep!");'); | |
| final dotDartTool = Directory(p.join(workspaceDir, '.dart_tool')); | |
| dotDartTool.createSync(recursive: true); | |
| final packageConfigFile = File( | |
| p.join(dotDartTool.path, 'package_config.json'), | |
| ); | |
| packageConfigFile.writeAsStringSync( | |
| jsonEncode({ | |
| 'configVersion': 2, | |
| 'packages': [ | |
| { | |
| 'name': 'dep', | |
| 'rootUri': p.toUri(pkgDir).toString(), | |
| 'packageUri': 'lib/', | |
| 'languageVersion': '3.0', | |
| }, | |
| ], | |
| }), | |
| ); | |
| final snapshotPath = ExecutableCompiler.pathOfSnapshot( | |
| workspaceRoot: workspaceDir, | |
| packageName: 'dep', | |
| relativeScriptPath: p.join('bin', 'hello.dart'), | |
| ); | |
| await ExecutableCompiler.compile( | |
| resolvedExecutable: DartExecutableWithPackageConfig( | |
| executable: scriptFile.path, | |
| packageConfig: packageConfigFile.path, | |
| ), | |
| ); | |
| expect(File(snapshotPath).existsSync(), isTrue); | |
| final tempParentDir = Directory( | |
| ExecutableCompiler.pathOfTempDir(workspaceRoot: workspaceDir), | |
| ); | |
| expect(tempParentDir.existsSync(), isTrue); | |
| expect(tempParentDir.listSync(), isEmpty); | |
| }); | |
| test( | |
| 'throws CompilationException when compilation fails and cleans up stale snapshot', | |
| () async { | |
| final pDir = Directory.systemTemp.createTempSync( | |
| 'exec_compiler_err_test', | |
| ); | |
| addTearDown(() => pDir.deleteSync(recursive: true)); | |
| final workspaceDir = p.join(pDir.path, 'workspace'); | |
| final fakePubCache = p.join(pDir.path, 'cache'); | |
| final pkgDir = p.join(fakePubCache, 'hosted', 'pub.dev', 'dep-1.0.0'); | |
| Directory(p.join(pkgDir, 'bin')).createSync(recursive: true); | |
| final scriptFile = File(p.join(pkgDir, 'bin', 'broken.dart')); | |
| scriptFile.writeAsStringSync('void main() => print("working");'); | |
| final dotDartTool = Directory(p.join(workspaceDir, '.dart_tool')); | |
| dotDartTool.createSync(recursive: true); | |
| final packageConfigFile = File( | |
| p.join(dotDartTool.path, 'package_config.json'), | |
| ); | |
| packageConfigFile.writeAsStringSync( | |
| jsonEncode({ | |
| 'configVersion': 2, | |
| 'packages': [ | |
| { | |
| 'name': 'dep', | |
| 'rootUri': p.toUri(pkgDir).toString(), | |
| 'packageUri': 'lib/', | |
| 'languageVersion': '3.0', | |
| }, | |
| ], | |
| }), | |
| ); | |
| final resolved = DartExecutableWithPackageConfig( | |
| executable: scriptFile.path, | |
| packageConfig: packageConfigFile.path, | |
| ); | |
| // First compile succeeds and creates snapshot, depfile, and metadata. | |
| await ExecutableCompiler.compile(resolvedExecutable: resolved); | |
| final snapshotPath = ExecutableCompiler.pathOfSnapshot( | |
| workspaceRoot: workspaceDir, | |
| packageName: 'dep', | |
| relativeScriptPath: p.join('bin', 'broken.dart'), | |
| ); | |
| final snapshotFile = File(snapshotPath); | |
| final depfile = File( | |
| '$snapshotPath${ExecutableCompiler.depfileExtension}', | |
| ); | |
| final metadataFile = File( | |
| '$snapshotPath${ExecutableCompiler.metadataExtension}', | |
| ); | |
| expect(snapshotFile.existsSync(), isTrue); | |
| expect(depfile.existsSync(), isTrue); | |
| expect(metadataFile.existsSync(), isTrue); | |
| // Break the script and backdate the snapshot so recompilation runs. | |
| snapshotFile.setLastModifiedSync( | |
| DateTime.now().subtract(const Duration(seconds: 10)), | |
| ); | |
| scriptFile.writeAsStringSync('void main() { this is syntax error }'); | |
| await expectLater( | |
| () => ExecutableCompiler.compile( | |
| resolvedExecutable: resolved, | |
| ), | |
| throwsA( | |
| isA<CompilationException>().having( | |
| (e) => e.message, | |
| 'message', | |
| allOf( | |
| contains("Error: 'syntax' isn't a type."), | |
| isNot(contains('.snapshot.dill')), | |
| ), | |
| ), | |
| ), | |
| ); | |
| // Stale snapshot, depfile, and metadata should be removed, and no tempDir left. | |
| expect(snapshotFile.existsSync(), isFalse); | |
| expect(depfile.existsSync(), isFalse); | |
| expect(metadataFile.existsSync(), isFalse); | |
| expect( | |
| Directory(p.dirname(snapshotPath)).listSync().whereType<Directory>(), | |
| isEmpty, | |
| ); | |
| }, | |
| ); | |
| test('end-to-end dart run precompiles and executes dependency', () async { | |
| final pProject = project(name: 'foo'); | |
| final bar = project(name: 'bar'); | |
| pProject.file('pubspec.yaml', ''' | |
| name: foo | |
| environment: | |
| sdk: '^3.0.0' | |
| dependencies: | |
| bar: | |
| path: ${bar.dir.path} | |
| '''); | |
| bar.file('bin/hello.dart', ''' | |
| void main(List<String> args) { | |
| print("Hello from bar:hello \${args.join(' ')}"); | |
| } | |
| '''); | |
| final result = await pProject.run(['run', 'bar:hello', 'arg1', 'arg2']); | |
| expect(result.exitCode, 0); | |
| expect(result.stdout, contains('Hello from bar:hello arg1 arg2')); | |
| final snapshot = File( | |
| ExecutableCompiler.pathOfSnapshot( | |
| workspaceRoot: pProject.dirPath, | |
| packageName: 'bar', | |
| relativeScriptPath: p.join('bin', 'hello.dart'), | |
| ), | |
| ); | |
| expect(snapshot.existsSync(), isTrue); | |
| }); | |
| test( | |
| 'end-to-end dart run reports the built executable only with -v', | |
| () async { | |
| Future<ProcessResult> runFreshProject(List<String> arguments) { | |
| final pProject = project(name: 'foo'); | |
| final bar = project(name: 'bar'); | |
| pProject.file('pubspec.yaml', ''' | |
| name: foo | |
| environment: | |
| sdk: '^3.0.0' | |
| dependencies: | |
| bar: | |
| path: ${bar.dir.path} | |
| '''); | |
| bar.file('bin/hello.dart', ''' | |
| void main(List<String> args) { | |
| print("Hello from bar:hello"); | |
| } | |
| '''); | |
| return pProject.run(['run', ...arguments, 'bar:hello']); | |
| } | |
| final verboseResult = await runFreshProject(['-v']); | |
| expect(verboseResult.exitCode, 0); | |
| expect(verboseResult.stderr, contains('Built bar:hello.')); | |
| expect(verboseResult.stdout, isNot(contains('Built bar:hello.'))); | |
| final result = await runFreshProject([]); | |
| expect(result.exitCode, 0); | |
| expect(result.stderr, isNot(contains('Built'))); | |
| expect(result.stdout.trim(), 'Hello from bar:hello'); | |
| }, | |
| ); | |
| test('end-to-end dart run respects --verbosity=error', () async { | |
| final pProject = project(name: 'foo'); | |
| final bar = project(name: 'bar'); | |
| pProject.file('pubspec.yaml', ''' | |
| name: foo | |
| environment: | |
| sdk: '^3.0.0' | |
| dependencies: | |
| bar: | |
| path: ${bar.dir.path} | |
| '''); | |
| bar.file('bin/hello.dart', ''' | |
| void main(List<String> args) { | |
| print("Hello from bar:hello"); | |
| } | |
| '''); | |
| final result = await pProject.run([ | |
| 'run', | |
| '--verbosity=error', | |
| 'bar:hello', | |
| ]); | |
| expect(result.exitCode, 0); | |
| expect(result.stderr, isEmpty); | |
| expect(result.stdout.trim(), equals('Hello from bar:hello')); | |
| }); | |
| test( | |
| 'end-to-end dart run preserves Platform.script with spaces in path', | |
| () async { | |
| final pProject = project(name: 'foo'); | |
| final barDirWithSpace = Directory( | |
| p.join(pProject.dirPath, 'deps with space', 'bar'), | |
| )..createSync(recursive: true); | |
| File(p.join(barDirWithSpace.path, 'pubspec.yaml')).writeAsStringSync(''' | |
| name: bar | |
| environment: | |
| sdk: '^3.0.0' | |
| '''); | |
| Directory( | |
| p.join(barDirWithSpace.path, 'bin'), | |
| ).createSync(recursive: true); | |
| final scriptProbeFile = File( | |
| p.join(barDirWithSpace.path, 'bin', 'script_probe.dart'), | |
| ); | |
| scriptProbeFile.writeAsStringSync(''' | |
| import 'dart:io'; | |
| void main() { | |
| print("SCRIPT: \${Platform.script.toFilePath()}"); | |
| } | |
| '''); | |
| pProject.file('pubspec.yaml', ''' | |
| name: foo | |
| environment: | |
| sdk: '^3.0.0' | |
| dependencies: | |
| bar: | |
| path: ${barDirWithSpace.path} | |
| '''); | |
| final result = await pProject.run([ | |
| 'run', | |
| '--verbosity=error', | |
| 'bar:script_probe', | |
| ]); | |
| expect(result.exitCode, 0); | |
| final expectedPath = p.canonicalize(scriptProbeFile.path); | |
| expect(result.stdout, startsWith('SCRIPT: ')); | |
| expect( | |
| p.canonicalize(result.stdout.trim().substring('SCRIPT: '.length)), | |
| equals(expectedPath), | |
| ); | |
| expect(result.stdout, isNot(contains('.snapshot'))); | |
| expect(result.stdout, isNot(contains('"'))); | |
| }, | |
| ); | |
| test( | |
| 'incremental recompilation reuses existing snapshot when source is modified', | |
| () async { | |
| final pProject = project(name: 'foo'); | |
| final bar = project(name: 'bar'); | |
| pProject.file('pubspec.yaml', ''' | |
| name: foo | |
| environment: | |
| sdk: '^3.0.0' | |
| dependencies: | |
| bar: | |
| path: ${bar.dir.path} | |
| '''); | |
| bar.file('bin/hello.dart', ''' | |
| void main() { | |
| print("Hello 1"); | |
| } | |
| '''); | |
| var result = await pProject.run([ | |
| 'run', | |
| '--verbosity=error', | |
| 'bar:hello', | |
| ]); | |
| expect(result.exitCode, 0); | |
| expect(result.stdout.trim(), equals('Hello 1')); | |
| final snapshot = File( | |
| ExecutableCompiler.pathOfSnapshot( | |
| workspaceRoot: pProject.dirPath, | |
| packageName: 'bar', | |
| relativeScriptPath: p.join('bin', 'hello.dart'), | |
| ), | |
| ); | |
| expect(snapshot.existsSync(), isTrue); | |
| // Backdate snapshot before modifying source so timestamp is strictly newer. | |
| snapshot.setLastModifiedSync( | |
| DateTime.now().subtract(const Duration(seconds: 10)), | |
| ); | |
| // Modify the source code | |
| bar.file('bin/hello.dart', ''' | |
| void main() { | |
| print("Hello 2"); | |
| } | |
| '''); | |
| result = await pProject.run(['run', '--verbosity=error', 'bar:hello']); | |
| expect(result.exitCode, 0); | |
| expect(result.stdout.trim(), equals('Hello 2')); | |
| }, | |
| ); | |
| test( | |
| 'dart --enable-experiment=<exp> run caches and reuses experiment metadata', | |
| () async { | |
| final pProject = project(name: 'foo'); | |
| final bar = project(name: 'bar'); | |
| pProject.file('pubspec.yaml', ''' | |
| name: foo | |
| environment: | |
| sdk: '^3.0.0' | |
| dependencies: | |
| bar: | |
| path: ${bar.dir.path} | |
| '''); | |
| bar.file('bin/hello.dart', ''' | |
| void main() { | |
| print("Hello experiment"); | |
| } | |
| '''); | |
| // Pass --enable-experiment as a global/VM flag before 'run' | |
| final result = await pProject.run([ | |
| '--enable-experiment=test-experiment', | |
| 'run', | |
| '--verbosity=error', | |
| 'bar:hello', | |
| ]); | |
| expect(result.exitCode, 0); | |
| expect(result.stdout.trim(), equals('Hello experiment')); | |
| final snapshotPath = ExecutableCompiler.pathOfSnapshot( | |
| workspaceRoot: pProject.dirPath, | |
| packageName: 'bar', | |
| relativeScriptPath: p.join('bin', 'hello.dart'), | |
| ); | |
| final metadataFile = File( | |
| '$snapshotPath${ExecutableCompiler.metadataExtension}', | |
| ); | |
| expect(metadataFile.existsSync(), isTrue); | |
| expect(jsonDecode(metadataFile.readAsStringSync()), { | |
| 'version': ExecutableCompiler.metadataVersion, | |
| 'sdkVersion': ExecutableCompiler.sdkVersion, | |
| 'enabledExperiments': ['test-experiment'], | |
| }); | |
| }, | |
| ); | |
| test( | |
| 'scripts with the same basename in different subdirectories do not collide', | |
| () async { | |
| final pDir = Directory.systemTemp.createTempSync( | |
| 'exec_compiler_collision_test', | |
| ); | |
| addTearDown(() => pDir.deleteSync(recursive: true)); | |
| final workspaceDir = p.join(pDir.path, 'workspace'); | |
| final pkgDir = p.join(pDir.path, 'pkg'); | |
| Directory(p.join(pkgDir, 'bin')).createSync(recursive: true); | |
| Directory(p.join(pkgDir, 'tool')).createSync(recursive: true); | |
| final binScript = File(p.join(pkgDir, 'bin', 'main.dart')) | |
| ..writeAsStringSync('void main() => print("From bin/main.dart");'); | |
| final toolScript = File(p.join(pkgDir, 'tool', 'main.dart')) | |
| ..writeAsStringSync('void main() => print("From tool/main.dart");'); | |
| final dotDartTool = Directory(p.join(workspaceDir, '.dart_tool')) | |
| ..createSync(recursive: true); | |
| final packageConfigFile = File( | |
| p.join(dotDartTool.path, 'package_config.json'), | |
| ); | |
| packageConfigFile.writeAsStringSync( | |
| jsonEncode({ | |
| 'configVersion': 2, | |
| 'packages': [ | |
| { | |
| 'name': 'foo', | |
| 'rootUri': p.toUri(pkgDir).toString(), | |
| 'packageUri': 'lib/', | |
| 'languageVersion': '3.0', | |
| }, | |
| ], | |
| }), | |
| ); | |
| final resBin = await ExecutableCompiler.compile( | |
| resolvedExecutable: DartExecutableWithPackageConfig( | |
| executable: binScript.path, | |
| packageConfig: packageConfigFile.path, | |
| ), | |
| ); | |
| final resTool = await ExecutableCompiler.compile( | |
| resolvedExecutable: DartExecutableWithPackageConfig( | |
| executable: toolScript.path, | |
| packageConfig: packageConfigFile.path, | |
| ), | |
| ); | |
| expect(resBin.executable, isNot(equals(resTool.executable))); | |
| final binSnapshot = File( | |
| ExecutableCompiler.pathOfSnapshot( | |
| workspaceRoot: workspaceDir, | |
| packageName: 'foo', | |
| relativeScriptPath: p.join('bin', 'main.dart'), | |
| ), | |
| ); | |
| final toolSnapshot = File( | |
| ExecutableCompiler.pathOfSnapshot( | |
| workspaceRoot: workspaceDir, | |
| packageName: 'foo', | |
| relativeScriptPath: p.join('tool', 'main.dart'), | |
| ), | |
| ); | |
| expect(binSnapshot.existsSync(), isTrue); | |
| expect(toolSnapshot.existsSync(), isTrue); | |
| }, | |
| ); | |
| }); | |
| } |