| // Copyright (c) 2023, 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 'package:front_end/src/api_unstable/vm.dart'; |
| import 'package:front_end/src/api_prototype/constant_evaluator.dart' |
| show SimpleErrorReporter; |
| import 'package:kernel/target/targets.dart'; |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/kernel.dart'; |
| import 'package:kernel/verifier.dart'; |
| import 'package:path/path.dart' as path; |
| import 'package:test/test.dart'; |
| import 'package:vm/target_os.dart'; |
| import 'package:vm/modular/target/vm.dart' show VmTarget; |
| import 'package:vm/transformations/unreachable_code_elimination.dart' |
| show PlatformConstError, transformComponent; |
| import 'package:vm/transformations/vm_constant_evaluator.dart'; |
| |
| import '../common_test_utils.dart'; |
| |
| final String pkgVmDir = Platform.script.resolve('../..').toFilePath(); |
| |
| class TestErrorReporter extends SimpleErrorReporter { |
| final reportedMessages = <String>[]; |
| |
| TestErrorReporter(); |
| |
| @override |
| void reportMessage(Uri? uri, int offset, String message) { |
| final buffer = StringBuffer(); |
| if (offset >= 0) { |
| if (uri != null) { |
| buffer |
| ..write(uri.pathSegments.last) |
| ..write(':'); |
| } |
| buffer |
| ..write(offset) |
| ..write(' '); |
| } |
| buffer |
| ..write('Constant evaluation error: ') |
| ..write(message); |
| reportedMessages.add(buffer.toString()); |
| } |
| } |
| |
| class TestCase { |
| final TargetOS os; |
| final bool debug; |
| final bool enableAsserts; |
| final bool throws; |
| |
| const TestCase(this.os, |
| {required this.debug, required this.enableAsserts, this.throws = false}); |
| |
| String postfix() { |
| String result = '.${os.name}'; |
| if (debug) { |
| result += '.debug'; |
| } |
| if (enableAsserts) { |
| result += '.withAsserts'; |
| } |
| return result; |
| } |
| } |
| |
| class TestOptions { |
| static const Option<bool?> debug = Option('--debug', BoolValue(null)); |
| |
| static const Option<bool?> enableAsserts = |
| Option('--enable-asserts', BoolValue(null)); |
| |
| static const Option<String?> targetOS = Option('--target-os', StringValue()); |
| |
| static const List<Option> options = [debug, enableAsserts, targetOS]; |
| } |
| |
| runTestCase(Uri source, TestCase testCase) async { |
| final target = new VmTarget(new TargetFlags()); |
| Component component = await compileTestCaseToKernelProgram(source, |
| target: target, |
| environmentDefines: { |
| 'test.define.debug': testCase.debug ? 'true' : 'false', |
| 'test.define.enableAsserts': testCase.enableAsserts ? 'true' : 'false', |
| }); |
| |
| final reporter = TestErrorReporter(); |
| final evaluator = VMConstantEvaluator.create(target, component, testCase.os, |
| enableAsserts: testCase.enableAsserts, errorReporter: reporter); |
| late String actual; |
| if (testCase.throws) { |
| try { |
| component = transformComponent( |
| target, component, evaluator, testCase.enableAsserts); |
| final kernel = |
| kernelLibraryToString(component.mainMethod!.enclosingLibrary); |
| fail("Expected compilation failure, got:\n$kernel"); |
| } on PlatformConstError catch (e) { |
| final buffer = StringBuffer(); |
| for (final message in reporter.reportedMessages) { |
| buffer.writeln(message); |
| } |
| buffer |
| ..write('Member: ') |
| ..writeln(e.member.name); |
| final uri = e.uri; |
| if (uri != null) { |
| buffer |
| ..write('File: ') |
| ..writeln(uri.pathSegments.last); |
| } |
| if (e.offset >= 0) { |
| buffer |
| ..write('Offset: ') |
| ..writeln(e.offset); |
| } |
| buffer |
| ..write('Message: ') |
| ..writeln(e.message); |
| actual = buffer.toString(); |
| } |
| } else { |
| component = transformComponent( |
| target, component, evaluator, testCase.enableAsserts); |
| if (reporter.reportedMessages.isNotEmpty) { |
| fail('Expected no errors, got:\n${reporter.reportedMessages.join('\n')}'); |
| } |
| verifyComponent( |
| target, VerificationStage.afterGlobalTransformations, component); |
| actual = kernelLibraryToString(component.mainMethod!.enclosingLibrary); |
| } |
| compareResultWithExpectationsFile(source, actual, |
| expectFilePostfix: testCase.postfix()); |
| } |
| |
| void runWithTargetOS(ParsedOptions? parsedOptions, void Function(TargetOS) fn) { |
| TargetOS? specified; |
| if (parsedOptions != null) { |
| final s = TestOptions.targetOS.read(parsedOptions); |
| if (s != null) { |
| specified = TargetOS.fromString(s); |
| if (specified == null) { |
| fail('Failure parsing options: unknown target OS $s'); |
| } |
| } |
| } |
| if (specified != null) { |
| fn(specified); |
| } else { |
| for (final targetOS in TargetOS.values) { |
| fn(targetOS); |
| } |
| } |
| } |
| |
| void runWithBool(Option<bool?> option, ParsedOptions? parsedOptions, |
| void Function(bool) fn) { |
| bool? specified; |
| if (parsedOptions != null) { |
| specified = option.read(parsedOptions); |
| } |
| if (specified != null) { |
| fn(specified); |
| } else { |
| for (final value in [true, false]) { |
| fn(value); |
| } |
| } |
| } |
| |
| void runTest(String path, Uri uri, {bool throws = false}) { |
| ParsedOptions? options; |
| final optionsFile = File('$path.options'); |
| if (optionsFile.existsSync()) { |
| options = ParsedOptions.parse( |
| ParsedOptions.readOptionsFile(optionsFile.readAsStringSync()), |
| TestOptions.options); |
| } |
| |
| runWithTargetOS(options, (os) { |
| runWithBool(TestOptions.enableAsserts, options, (enableAsserts) { |
| runWithBool(TestOptions.debug, options, (debug) { |
| final testCase = TestCase(os, |
| debug: debug, enableAsserts: enableAsserts, throws: throws); |
| test('$path${testCase.postfix()}', () => runTestCase(uri, testCase)); |
| }); |
| }); |
| }); |
| } |
| |
| main() { |
| group('platform-use-transformation', () { |
| final testCasesPath = path.join( |
| pkgVmDir, 'testcases', 'transformations', 'vm_constant_evaluator'); |
| |
| group('successes', () { |
| final successCasesPath = path.join(testCasesPath, 'successes'); |
| for (var entry in Directory(successCasesPath) |
| .listSync(recursive: true, followLinks: false) |
| .reversed) { |
| if (entry.path.endsWith('.dart')) { |
| runTest(entry.path, entry.uri); |
| } |
| } |
| }); |
| |
| group('failures', () { |
| final errorCasesPath = path.join(testCasesPath, 'errors'); |
| |
| for (var entry in Directory(errorCasesPath) |
| .listSync(recursive: true, followLinks: false) |
| .reversed) { |
| if (entry.path.endsWith('.dart')) { |
| runTest(entry.path, entry.uri, throws: true); |
| } |
| } |
| }); |
| }); |
| } |