blob: 8011e5b10da6c1f8e33f633c1d5aff598819026f [file] [log] [blame]
// 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);
}
}
});
});
}