| // Copyright (c) 2020, 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:io'; |
| import 'dart:math'; |
| |
| import 'package:code_assets/code_assets.dart'; |
| import 'package:dart2native/dart2native_macho.dart' show pipeStream; |
| import 'package:dart2native/macho.dart'; |
| import 'package:dartdev/src/commands/compile.dart' |
| show compileErrorExitCode, genericErrorExitCode; |
| import 'package:hooks_runner/hooks_runner.dart'; |
| import 'package:path/path.dart' as path; |
| import 'package:test/test.dart'; |
| |
| import '../utils.dart'; |
| |
| void main() { |
| ensureRunFromSdkBinDart(); |
| |
| group('compile -', defineCompileTests, timeout: longTimeout); |
| } |
| |
| const String soundNullSafetyMessage = 'Info: Compiling with sound null safety'; |
| const String soundNullSafetyWarning = |
| "Warning: Option '--sound-null-safety' is deprecated."; |
| |
| const String failedAssertionError = 'Failed assertion: line'; |
| String usingTargetOSMessage(OS targetOS) => |
| 'Specializing Platform getters for target OS $targetOS.'; |
| final String targetingHostOSMessage = usingTargetOSMessage(Target.current.os); |
| |
| void defineCompileTests() { |
| // AOT compilation is not available on IA32. |
| final bool isRunningOnIA32 = Target.current.architecture == Architecture.ia32; |
| |
| if (Platform.isMacOS) { |
| test('Compile exe for MacOS signing', () async { |
| final p = project(mainSrc: '''void main() {}'''); |
| final inFile = |
| path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myexe')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'exe', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stdout, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| |
| if (!MachOFile.containsSnapshot(File(outFile))) { |
| throw FormatException('Snapshot not found in standalone executable'); |
| } |
| |
| // Ensure that the exe can be signed. |
| final codeSigningProcess = await Process.start('codesign', [ |
| '-o', |
| 'runtime', |
| '-s', |
| '-', |
| outFile, |
| ]); |
| |
| final signingResult = await codeSigningProcess.exitCode; |
| expect(signingResult, 0); |
| }, skip: isRunningOnIA32); |
| |
| test('Changing snapshot contents fails to validate', () async { |
| final p = project(mainSrc: '''void main() {}'''); |
| final inFile = |
| path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myexe')); |
| final corruptedFile = |
| path.canonicalize(path.join(p.dirPath, 'myexe-corrupted')); |
| |
| var result = await p.run( |
| [ |
| 'compile', |
| 'exe', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stdout, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| |
| final macho = MachOFile.fromFile(File(outFile)); |
| final snapshotNote = macho.snapshotNote; |
| if (snapshotNote == null) { |
| throw FormatException('Snapshot not found in standalone executable'); |
| } |
| |
| if (macho.hasCodeSignature) { |
| // Verify the resulting executable using codesign. |
| result = Process.runSync('codesign', [ |
| '-v', |
| outFile, |
| ]); |
| |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| } else { |
| // Sign the executable first. |
| final codeSigningProcess = await Process.start('codesign', [ |
| '-o', |
| 'runtime', |
| '-s', |
| '-', |
| outFile, |
| ]); |
| |
| final signingResult = await codeSigningProcess.exitCode; |
| expect(signingResult, 0); |
| } |
| |
| // Pick a random range of bytes within the snapshot. |
| final rand = Random(); |
| final offset1 = rand.nextInt(snapshotNote.fileSize); |
| final offset2 = rand.nextInt(snapshotNote.fileSize); |
| final int start = snapshotNote.fileOffset + min(offset1, offset2); |
| final int size = max(offset1, offset2) - min(offset1, offset2); |
| |
| // Write the corrupted version of the executable, corrupting the bytes in |
| // the calculated range by incrementing them (modulo 256). |
| final original = File(outFile).openSync(); |
| final corrupted = File(corruptedFile).openSync(mode: FileMode.write); |
| await pipeStream(original, corrupted, numToWrite: start); |
| final bytesToCorrupt = original.readSync(size); |
| for (int i = 0; i < bytesToCorrupt.length; i++) { |
| bytesToCorrupt[i] = (bytesToCorrupt[i] + 1) % 256; |
| } |
| corrupted.writeFromSync(bytesToCorrupt); |
| await pipeStream(original, corrupted); |
| |
| // (Fail to) verify the resulting executable using codesign. |
| result = Process.runSync('codesign', [ |
| '-v', |
| corruptedFile, |
| ]); |
| |
| expect(result.stderr, isNotEmpty); |
| expect(result.exitCode, 1); |
| }, skip: isRunningOnIA32); |
| } |
| |
| // *** NOTE ***: These tests *must* be run with the `--use-sdk` option |
| // as they depend on a fully built SDK to resolve various snapshot files |
| // used by compilation. |
| test('Running from built SDK', () { |
| final Directory binDir = File(Platform.resolvedExecutable).parent; |
| expect(binDir.path, contains('bin')); |
| }); |
| |
| test('Implicit --help', () async { |
| final p = project(); |
| final result = await p.run( |
| [ |
| 'compile', |
| ], |
| ); |
| expect(result.stderr, contains('Compile Dart')); |
| expect(result.stderr, isNot(contains('js-dev'))); |
| expect(result.exitCode, 64); |
| }); |
| |
| test('--help', () async { |
| final p = project(); |
| final result = await p.run( |
| ['compile', '--help'], |
| ); |
| expect(result.stdout, contains('Compile Dart')); |
| expect( |
| result.stdout, |
| contains( |
| 'Usage: dart compile <subcommand> [arguments]', |
| ), |
| ); |
| |
| expect(result.stdout, contains('jit-snapshot')); |
| expect(result.stdout, contains('kernel')); |
| expect(result.stdout, contains('js')); |
| expect(result.stderr, isNot(contains('js-dev'))); |
| expect(result.stdout, contains('aot-snapshot')); |
| expect(result.stdout, contains('exe')); |
| expect(result.exitCode, 0); |
| }); |
| |
| test('--help --verbose', () async { |
| final p = project(); |
| final result = await p.run( |
| ['compile', '--help', '--verbose'], |
| ); |
| expect(result.stdout, contains('Compile Dart')); |
| expect(result.stdout, isNot(contains('js-dev'))); |
| expect( |
| result.stdout, |
| contains( |
| 'Usage: dart [vm-options] compile <subcommand> [arguments]', |
| ), |
| ); |
| expect(result.exitCode, 0); |
| }); |
| |
| test('Compile and run jit snapshot', () async { |
| final p = project(mainSrc: 'void main() { print("I love jit"); }'); |
| final outFile = path.join(p.dirPath, 'main.jit'); |
| var result = await p.run( |
| [ |
| 'compile', |
| 'jit-snapshot', |
| '-o', |
| outFile, |
| p.relativeFilePath, |
| ], |
| ); |
| expect(result.stderr, isNot(contains(soundNullSafetyMessage))); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| |
| result = await p.run(['run', 'main.jit']); |
| expect(result.stdout, contains('I love jit')); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| }); |
| |
| test('Compile and run jit snapshot with environment variables', () async { |
| final p = project(mainSrc: ''' |
| void main() { |
| print('1: ' + const String.fromEnvironment('foo')); |
| print('2: ' + const String.fromEnvironment('bar')); |
| }'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'main.jit')); |
| |
| var result = await p.run([ |
| 'compile', |
| 'jit-snapshot', |
| '-Dfoo=bar', |
| '--define=bar=foo', |
| '-o', |
| outFile, |
| '-v', |
| inFile, |
| ]); |
| expect(result.stdout, isNot(contains(targetingHostOSMessage))); |
| expect(result.stderr, isNot(contains(soundNullSafetyMessage))); |
| expect(result.exitCode, 0); |
| final file = File(outFile); |
| expect(file.existsSync(), true, reason: 'File not found: $outFile'); |
| |
| result = await p.run(['run', 'main.jit']); |
| |
| // Ensure the -D and --define arguments were processed correctly. |
| expect(result.stdout, contains('1: bar')); |
| expect(result.stdout, contains('2: foo')); |
| }); |
| |
| Future<void> basicCompileTest() async { |
| final p = project(mainSrc: 'void main() { print("I love executables"); }'); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'lib', 'main.exe')); |
| |
| var result = await p.run( |
| [ |
| 'compile', |
| 'exe', |
| '-v', |
| inFile, |
| ], |
| ); |
| |
| // Executables should be (host) OS-specific by default. |
| expect(result.stdout, contains(targetingHostOSMessage)); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| |
| if (Platform.isMacOS && Target.current.architecture == Architecture.arm64) { |
| // Also check that the resulting executable is properly signed on ARM64 |
| // macOS, since executables are required to be signed there and checking |
| // this prior to running the executable gives us a clearer test failure |
| // message if for some reason the generated signature was invalid. |
| result = await Process.run('codesign', [ |
| '-v', |
| outFile, |
| ]); |
| |
| printOnFailure( |
| 'Subcommand terminated with exit code ${result.exitCode}.'); |
| printOnFailure('Subcommand stdout:\n${result.stdout}'); |
| printOnFailure('Subcommand stderr:\n${result.stderr}'); |
| expect(result.exitCode, 0); |
| } |
| |
| result = Process.runSync( |
| outFile, |
| [], |
| ); |
| |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(result.stdout, contains('I love executables')); |
| } |
| |
| test('Compile and run executable', basicCompileTest, skip: isRunningOnIA32); |
| |
| test('Compile to executable disabled on IA32', () async { |
| final p = project(mainSrc: 'void main() { print("I love executables"); }'); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'exe', |
| inFile, |
| ], |
| ); |
| |
| expect(result.stderr, |
| "'dart compile exe' is not supported on x86 architectures.\n"); |
| expect(result.exitCode, 64); |
| }, skip: !isRunningOnIA32); |
| |
| test('Compile to AOT snapshot disabled on IA32', () async { |
| final p = project(mainSrc: 'void main() { print("I love executables"); }'); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'aot-snapshot', |
| inFile, |
| ], |
| ); |
| |
| expect(result.stderr, |
| "'dart compile aot-snapshot' is not supported on x86 architectures.\n"); |
| expect(result.exitCode, 64); |
| }, skip: !isRunningOnIA32); |
| |
| test('Compile and run executable with options', () async { |
| final p = project( |
| mainSrc: 'void main() {print(const String.fromEnvironment("life"));}'); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myexe')); |
| |
| var result = await p.run( |
| [ |
| 'compile', |
| 'exe', |
| '-v', |
| '--define', |
| 'life=42', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stdout, contains(targetingHostOSMessage)); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| |
| result = Process.runSync( |
| outFile, |
| [], |
| ); |
| |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(result.stdout, contains('42')); |
| }, skip: isRunningOnIA32); |
| |
| test('Regression test for https://github.com/dart-lang/sdk/issues/45347', |
| () async { |
| final p = project(mainSrc: ''' |
| import "dart:developer"; |
| import "dart:isolate"; |
| void main() { |
| final id = Service.getIsolateId(Isolate.current)?? "NA"; |
| } |
| '''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myexe')); |
| |
| var result = await p.run( |
| [ |
| 'compile', |
| 'exe', |
| '-v', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| |
| result = Process.runSync( |
| outFile, |
| [], |
| ); |
| |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile and run aot snapshot', () async { |
| final p = project(mainSrc: 'void main() { print("I love AOT"); }'); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'main.aot')); |
| |
| var result = await p.run( |
| [ |
| 'compile', |
| 'aot-snapshot', |
| '-v', |
| '-o', |
| 'main.aot', |
| inFile, |
| ], |
| ); |
| |
| // AOT snapshots should be OS-specific by default. |
| expect(result.stdout, contains(targetingHostOSMessage)); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| |
| final Directory binDir = File(Platform.resolvedExecutable).parent; |
| result = Process.runSync( |
| path.join(binDir.path, 'dartaotruntime'), |
| [outFile], |
| ); |
| |
| expect(result.stdout, contains('I love AOT')); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile and run kernel snapshot', () async { |
| final p = project(mainSrc: 'void main() { print("I love kernel"); }'); |
| final outFile = path.join(p.dirPath, 'main.dill'); |
| var result = await p.run( |
| [ |
| 'compile', |
| 'kernel', |
| '-v', |
| '-o', |
| outFile, |
| p.relativeFilePath, |
| ], |
| ); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| expect(result.stdout, isNot(contains(targetingHostOSMessage))); |
| expect(result.stderr, isNot(contains(soundNullSafetyMessage))); |
| expect(result.exitCode, 0); |
| |
| result = await p.run(['run', 'main.dill']); |
| expect(result.stdout, contains('I love kernel')); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| }); |
| |
| test('Compile JS', () async { |
| final p = project(mainSrc: ''' |
| void main() { |
| print('1: ' + const String.fromEnvironment('foo')); |
| print('2: ' + const String.fromEnvironment('bar')); |
| }'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'main.js')); |
| |
| final result = await p.run([ |
| 'compile', |
| 'js', |
| '-m', |
| '-Dfoo=bar', |
| '--define=bar=foo', |
| '-o', |
| outFile, |
| '-v', |
| inFile, |
| ]); |
| expect(result.stdout, isNot(contains(targetingHostOSMessage))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| final file = File(outFile); |
| expect(file.existsSync(), true, reason: 'File not found: $outFile'); |
| |
| // Ensure the -D and --define arguments were processed correctly. |
| final contents = file.readAsStringSync(); |
| expect(contents.contains('1: bar'), true); |
| expect(contents.contains('2: foo'), true); |
| }); |
| |
| test('Compile JS DDC', () async { |
| final p = project(mainSrc: ''' |
| void main() { |
| print('1: ' + const String.fromEnvironment('foo')); |
| print('2: ' + const String.fromEnvironment('bar')); |
| }'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'main.js')); |
| |
| final result = await p.run([ |
| 'compile', |
| 'js-dev', |
| '-Dfoo=bar', |
| '--define=bar=foo', |
| '-o', |
| outFile, |
| inFile, |
| ]); |
| expect(result.stdout, isNot(contains(targetingHostOSMessage))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| final file = File(outFile); |
| expect(file.existsSync(), true, reason: 'File not found: $outFile'); |
| |
| final contents = file.readAsStringSync(); |
| expect(contents.contains('"1: " + "bar"'), true); |
| expect(contents.contains('"2: " + "foo"'), true); |
| }); |
| |
| test('Compile exe with error', () async { |
| final p = project(mainSrc: ''' |
| void main() { |
| int? i; |
| i.isEven; |
| } |
| '''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myexe')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'exe', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stdout, isEmpty); |
| expect(result.stderr, contains('Error: ')); |
| // The CFE doesn't print to stderr, so all output is piped to stderr, even |
| // including info-only output: |
| expect(result.stderr, isNot(contains(soundNullSafetyMessage))); |
| expect(result.exitCode, compileErrorExitCode); |
| expect(File(outFile).existsSync(), false, |
| reason: 'File not found: $outFile'); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile exe with sound null safety', () async { |
| final p = project(mainSrc: '''void main() {}'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myexe')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'exe', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stdout, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile and run exe (default sound null safety)', () async { |
| final p = project(mainSrc: '''void main() { |
| print((<int?>[] is List<int>) ? 'oh no' : 'sound'); |
| }'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myexe')); |
| |
| var result = await p.run( |
| [ |
| 'compile', |
| 'exe', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stdout, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| |
| result = Process.runSync( |
| outFile, |
| [], |
| ); |
| |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(result.stdout, contains('sound')); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile and run exe with DART_VM_OPTIONS', () async { |
| final p = project(mainSrc: ''' |
| import 'dart:math'; |
| void main() { |
| print(Random().nextInt(1000)); |
| }'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myexe')); |
| |
| var result = await p.run( |
| [ |
| 'compile', |
| 'exe', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stdout, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| |
| // Verify CSV options are processed. |
| result = Process.runSync( |
| outFile, |
| [], |
| environment: <String, String>{ |
| 'DART_VM_OPTIONS': '--help,--verbose', |
| }, |
| ); |
| |
| expect(result.stderr, isEmpty); |
| // vm_name is a verbose flag and will only be shown if --verbose is |
| // processed. |
| expect(result.stdout, contains('vm_name')); |
| expect(result.exitCode, 0); |
| |
| // Verify non-help options work. |
| // |
| // Regression test for https://github.com/dart-lang/sdk/issues/55767 |
| result = Process.runSync( |
| outFile, |
| [], |
| environment: <String, String>{ |
| 'DART_VM_OPTIONS': '--random_seed=42', |
| }, |
| ); |
| |
| expect(result.stderr, isEmpty); |
| // This value should be consistent as long as --random_seed is processed. |
| expect(result.stdout, contains('64')); |
| expect(result.exitCode, 0); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile exe without info', () async { |
| final p = project(mainSrc: '''void main() {}'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myexe')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'exe', |
| '--verbosity=warning', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| // Only printed when -v/--verbose is used, not --verbosity. |
| expect(result.stdout, isNot(contains(targetingHostOSMessage))); |
| expect(result.stdout, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile exe without warnings', () async { |
| final p = project(mainSrc: ''' |
| void main() { |
| int i = 0; |
| i?.isEven; |
| } |
| '''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myexe')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'exe', |
| '--verbosity=error', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| // Only printed when -v/--verbose is used, not --verbosity. |
| expect(result.stdout, isNot(contains(targetingHostOSMessage))); |
| expect(result.stdout, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile exe with asserts', () async { |
| final p = project(mainSrc: ''' |
| void main() { |
| assert(int.parse('1') == 2); |
| } |
| '''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myexe')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'exe', |
| '--enable-asserts', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| // Only printed when -v/--verbose is used, not --verbosity. |
| expect(result.stdout, isNot(contains(targetingHostOSMessage))); |
| expect(result.stdout, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| |
| final runResult = await Process.run(outFile, []); |
| expect(runResult.stdout, isEmpty); |
| expect(runResult.stderr, contains(failedAssertionError)); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile exe from kernel', () async { |
| final p = project(mainSrc: ''' |
| void main() {} |
| '''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final dillOutFile = path.canonicalize(path.join(p.dirPath, 'mydill')); |
| final exeOutFile = path.canonicalize(path.join(p.dirPath, 'myexe')); |
| |
| var result = await p.run( |
| [ |
| 'compile', |
| 'kernel', |
| '-o', |
| dillOutFile, |
| inFile, |
| ], |
| ); |
| expect(result.exitCode, 0); |
| expect( |
| File(dillOutFile).existsSync(), |
| true, |
| reason: 'File not found: $dillOutFile', |
| ); |
| |
| result = await p.run( |
| [ |
| 'compile', |
| 'exe', |
| '-o', |
| exeOutFile, |
| dillOutFile, |
| ], |
| ); |
| |
| expect(result.exitCode, 0); |
| expect( |
| File(exeOutFile).existsSync(), |
| true, |
| reason: 'File not found: $exeOutFile', |
| ); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile wasm with wrong output filename', () async { |
| final p = project(mainSrc: 'void main() {}'); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final result = await p.run( |
| [ |
| 'compile', |
| 'wasm', |
| '-o', |
| 'foo', |
| inFile, |
| ], |
| ); |
| |
| expect(result.stderr, |
| contains('Error: The output file "foo" does not end with ".wasm"')); |
| expect(result.exitCode, genericErrorExitCode); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile wasm with error', () async { |
| final p = project(mainSrc: ''' |
| void main() { |
| int? i; |
| i.isEven; |
| } |
| '''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'my.wasm')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'wasm', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stderr, contains('Error: ')); |
| // The CFE doesn't print to stderr, so all output is piped to stderr, even |
| // including info-only output: |
| expect(result.stderr, isNot(contains(soundNullSafetyMessage))); |
| expect(result.exitCode, compileErrorExitCode); |
| expect(File(outFile).existsSync(), false, |
| reason: 'File not found: $outFile'); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile JS with sound null safety', () async { |
| final p = project(mainSrc: '''void main() {}'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myjs')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'js', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stdout, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stdout, isNot(contains(soundNullSafetyWarning))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile JS with sound null safety flag', () async { |
| final p = project(mainSrc: '''void main() {}'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myjs')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'js', |
| '--sound-null-safety', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stdout, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stdout, contains(soundNullSafetyWarning)); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile JS DDC with sound null safety', () async { |
| final p = project(mainSrc: '''void main() {}'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myjs')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'js-dev', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stdout, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile JS without info', () async { |
| final p = project(mainSrc: '''void main() {}'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myjs')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'js', |
| '--verbosity=warning', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stdout, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| }); |
| |
| test('Compile JS without warnings', () async { |
| final p = project(mainSrc: ''' |
| void main() { |
| int i = 0; |
| i?.isEven; |
| } |
| '''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myjs')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'js', |
| '--verbosity=error', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stdout, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| }); |
| |
| test('Compile AOT snapshot with sound null safety', () async { |
| final p = project(mainSrc: '''void main() {}'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myaot')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'aot-snapshot', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stdout, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile AOT snapshot without info', () async { |
| final p = project(mainSrc: '''void main() {}'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myaot')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'aot-snapshot', |
| '--verbosity=warning', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| // Only printed when -v/--verbose is used, not --verbosity. |
| expect(result.stdout, isNot(contains(targetingHostOSMessage))); |
| expect(result.stdout, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile AOT snapshot without warnings', () async { |
| final p = project(mainSrc: ''' |
| void main() { |
| int i = 0; |
| i?.isEven; |
| } |
| '''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myaot')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'aot-snapshot', |
| '--verbosity=error', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| // Only printed when -v/--verbose is used, not --verbosity. |
| expect(result.stdout, isNot(contains(targetingHostOSMessage))); |
| expect(result.stdout, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile AOT snapshot with asserts', () async { |
| final p = project(mainSrc: ''' |
| void main() { |
| assert(int.parse('1') == 2); |
| } |
| '''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myaot')); |
| |
| var result = await p.run( |
| [ |
| 'compile', |
| 'aot-snapshot', |
| '--enable-asserts', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| // Only printed when -v/--verbose is used, not --verbosity. |
| expect(result.stdout, isNot(contains(targetingHostOSMessage))); |
| expect(result.stdout, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| |
| final Directory binDir = File(Platform.resolvedExecutable).parent; |
| result = await Process.run( |
| path.join(binDir.path, 'dartaotruntime'), |
| [outFile], |
| ); |
| expect(result.stdout, isEmpty); |
| expect(result.stderr, contains(failedAssertionError)); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile AOT snapshot from kernel', () async { |
| final p = project(mainSrc: ''' |
| void main() {} |
| '''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final dillOutFile = path.canonicalize(path.join(p.dirPath, 'mydill')); |
| final aotOutFile = path.canonicalize(path.join(p.dirPath, 'myaot')); |
| |
| var result = await p.run( |
| [ |
| 'compile', |
| 'kernel', |
| '-o', |
| dillOutFile, |
| inFile, |
| ], |
| ); |
| expect(result.exitCode, 0); |
| expect( |
| File(dillOutFile).existsSync(), |
| true, |
| reason: 'File not found: $dillOutFile', |
| ); |
| |
| result = await p.run( |
| [ |
| 'compile', |
| 'aot-snapshot', |
| '-o', |
| aotOutFile, |
| dillOutFile, |
| ], |
| ); |
| |
| expect(result.exitCode, 0); |
| expect( |
| File(aotOutFile).existsSync(), |
| true, |
| reason: 'File not found: $aotOutFile', |
| ); |
| }, skip: isRunningOnIA32); |
| |
| test('Compile kernel with invalid output directory', () async { |
| final p = project(mainSrc: '''void main() {}'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'kernel', |
| '--verbosity=warning', |
| '-o', |
| '/somewhere/nowhere/test.dill', |
| inFile, |
| ], |
| ); |
| expect( |
| result.stderr, |
| predicate( |
| (dynamic o) => '$o'.contains('Unable to open file'), |
| ), |
| ); |
| expect(result.exitCode, genericErrorExitCode); |
| }); |
| |
| test('Compile kernel with invalid trailing argument', () async { |
| final p = project(mainSrc: '''void main() {}'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'mydill')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'kernel', |
| '--verbosity=warning', |
| '-o', |
| outFile, |
| inFile, |
| 'invalid-arg', |
| ], |
| ); |
| |
| expect(result.stdout, isEmpty); |
| expect( |
| result.stderr, |
| predicate( |
| (dynamic o) => |
| '$o'.contains('Unexpected arguments after Dart entry point.'), |
| ), |
| ); |
| expect(result.exitCode, 64); |
| expect(File(outFile).existsSync(), false, reason: 'File found: $outFile'); |
| }); |
| |
| test('Compile kernel with default (sound null safety)', () async { |
| final p = project(mainSrc: '''void main() {}'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'mydill')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'kernel', |
| '-v', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stdout, isNot(contains(targetingHostOSMessage))); |
| expect(result.stderr, isNot(contains(soundNullSafetyMessage))); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| }); |
| |
| test('Compile kernel with --sound-null-safety', () async { |
| final p = project(mainSrc: '''void main() { |
| print((<int?>[] is List<int>) ? 'oh no' : 'sound'); |
| }'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'mydill')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'kernel', |
| '--sound-null-safety', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stderr, isNot(contains(soundNullSafetyMessage))); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| }); |
| |
| test('Compile kernel without info', () async { |
| final p = project(mainSrc: '''void main() {}'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'mydill')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'kernel', |
| '--verbosity=warning', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stdout, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| }); |
| |
| test('Compile kernel without warning', () async { |
| final p = project(mainSrc: ''' |
| void main() { |
| int i; |
| i?.isEven; |
| }'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'mydill')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'kernel', |
| '--verbosity=error', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stderr, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stderr, contains('must be assigned before it can be used')); |
| expect(result.exitCode, compileErrorExitCode); |
| }); |
| |
| test('Compile JIT snapshot with default (sound null safety)', () async { |
| final p = project(mainSrc: '''void main() {}'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myjit')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'jit-snapshot', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stderr, isNot(contains(soundNullSafetyMessage))); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| }); |
| |
| test('Compile JIT snapshot with (default sound null safety)', () async { |
| final p = project(mainSrc: '''void main() { |
| print((<int?>[] is List<int>) ? 'oh no' : 'sound'); |
| }'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myjit')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'jit-snapshot', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stderr, isNot(contains(soundNullSafetyMessage))); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| }); |
| |
| test('Compile JIT snapshot with training args', () async { |
| final p = |
| project(mainSrc: '''void main(List<String> args) => print(args);'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myjit')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'jit-snapshot', |
| '-o', |
| outFile, |
| inFile, |
| 'foo', |
| // Ensure training args aren't parsed by the CLI. |
| // See https://github.com/dart-lang/sdk/issues/49302 |
| '-e', |
| '--foobar=bar', |
| ], |
| ); |
| |
| expect(result.stderr, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stdout, |
| predicate((dynamic o) => '$o'.contains('[foo, -e, --foobar=bar]'))); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| }); |
| |
| test('Compile JIT snapshot without info', () async { |
| final p = project(mainSrc: '''void main() {}'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myjit')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'jit-snapshot', |
| '--verbosity=warning', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stderr, isNot(contains(soundNullSafetyMessage))); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| }); |
| |
| test('Compile JIT snapshot without warnings', () async { |
| final p = project(mainSrc: ''' |
| void main() { |
| int i; |
| i?.isEven; |
| }'''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myjit')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| 'jit-snapshot', |
| '--verbosity=error', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stderr, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stderr, contains('must be assigned before it can be used')); |
| expect(result.exitCode, 254); |
| }); |
| |
| test('Compile JIT snapshot with asserts', () async { |
| final p = project(mainSrc: ''' |
| void main() { |
| assert(int.parse('1') == 2); |
| } |
| '''); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = path.canonicalize(path.join(p.dirPath, 'myjit')); |
| |
| var result = await p.run( |
| [ |
| 'compile', |
| 'jit-snapshot', |
| '--enable-asserts', |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| // Only printed when -v/--verbose is used, not --verbosity. |
| expect(result.stdout, isNot(contains(targetingHostOSMessage))); |
| expect(result.stdout, isNot(contains(soundNullSafetyMessage))); |
| expect(result.stderr, contains(failedAssertionError)); |
| expect(result.exitCode, genericErrorExitCode); |
| |
| result = await p.run( |
| ['--enable-asserts', outFile], |
| ); |
| expect(result.stdout, isEmpty); |
| expect(result.stderr, contains(failedAssertionError)); |
| }); |
| |
| // Tests for --depfile for compiling to AOT snapshots, executables and |
| // kernel. |
| group('depfiles', () { |
| Future<void> testDepFileGeneration(String subcommand) async { |
| final p = project(mainSrc: '''void main() {}'''); |
| final inFile = |
| path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final outFile = |
| path.canonicalize(path.join(p.dirPath, 'output.$subcommand')); |
| final depFile = |
| path.canonicalize(path.join(p.dirPath, 'output.$subcommand.d')); |
| |
| final result = await p.run( |
| [ |
| 'compile', |
| subcommand, |
| '--depfile', |
| depFile, |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| |
| expect(File(depFile).existsSync(), isTrue); |
| |
| final depFileContent = File(depFile).readAsStringSync(); |
| |
| String escapePath(String path) => |
| path.replaceAll('\\', '\\\\').replaceAll(' ', '\\ '); |
| |
| expect(depFileContent, startsWith('${escapePath(outFile)}: ')); |
| expect(depFileContent, contains(escapePath(inFile))); |
| } |
| |
| test('compile aot-snapshot', () => testDepFileGeneration('aot-snapshot'), |
| skip: isRunningOnIA32); |
| test('compile exe', () => testDepFileGeneration('exe'), |
| skip: isRunningOnIA32); |
| test('compile kernel', () => testDepFileGeneration('kernel'), |
| skip: isRunningOnIA32); |
| }); |
| } |