blob: 4a0ef0d3fb384ca2191c45f54b33acc847bff6af [file] [log] [blame]
// Copyright (c) 2016, 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:async';
import 'dart:io';
import 'package:_fe_analyzer_shared/src/testing/annotated_code_helper.dart';
import 'package:compiler/compiler_api.dart' as api;
import 'package:expect/async_helper.dart';
import 'package:expect/expect.dart';
import 'package:source_maps/source_maps.dart';
import 'package:compiler/src/util/memory_compiler.dart';
const List<String> TESTS = const <String>[
'''
@{main}main() {
@{main}}
''',
'''
@{main}main() {
@{main}throw '';
@{main}}
''',
'''
@{main}main() {
@{main}return 0;
@{main}}
''',
'''
import 'package:expect/expect.dart';
@{main}main() {
@{main}test();
@{main}}
@pragma('dart2js:noInline')
@{test}test() {
@{test}}
''',
];
class Test {
final String annotatedCode;
final String code;
final List<SourceLocation> expectedLocations;
Test(this.annotatedCode, this.code, this.expectedLocations);
}
Test processTestCode(String code) {
List<SourceLocation> expectedLocations = <SourceLocation>[];
AnnotatedCode annotatedCode = AnnotatedCode.fromText(code);
for (Annotation annotation in annotatedCode.annotations) {
String methodName = annotation.text;
expectedLocations.add(
SourceLocation(methodName, annotation.lineNo, annotation.columnNo),
);
}
return Test(code, annotatedCode.sourceCode, expectedLocations);
}
void main(List<String> arguments) {
bool verbose = false;
bool printJs = false;
bool writeJs = false;
List<int>? indices;
for (String arg in arguments) {
if (arg == '-v') {
verbose = true;
} else if (arg == '--print-js') {
printJs = true;
} else if (arg == '--write-js') {
writeJs = true;
} else {
int? index = int.tryParse(arg);
if (index != null) {
indices ??= <int>[];
if (index < 0 || index >= TESTS.length) {
print('Index $index out of bounds: [0;${TESTS.length - 1}]');
} else {
indices.add(index);
}
}
}
}
if (indices == null) {
indices = List<int>.generate(TESTS.length, (i) => i);
}
asyncTest(() async {
for (int index in indices!) {
await runTest(
index,
processTestCode(TESTS[index]),
printJs: printJs,
writeJs: writeJs,
verbose: verbose,
);
}
});
}
Future runTest(
int index,
Test test, {
bool printJs = false,
required bool writeJs,
bool verbose = false,
}) async {
print("--$index------------------------------------------------------------");
print("Compiling dart2js\n ${test.annotatedCode}");
OutputCollector collector = OutputCollector();
List<String> options = <String>['--out=out.js', '--source-map=out.js.map'];
CompilationResult compilationResult = await runCompiler(
entryPoint: Uri.parse('memory:main.dart'),
memorySourceFiles: {'main.dart': test.code},
outputProvider: collector,
options: options,
);
Expect.isTrue(
compilationResult.isSuccess,
"Unsuccessful compilation of test:\n${test.code}",
);
String sourceMapText = collector.getOutput('', api.OutputType.sourceMap)!;
final sourceMap = parse(sourceMapText) as SingleMapping;
if (writeJs) {
File(
'out.js',
).writeAsStringSync(collector.getOutput('', api.OutputType.js)!);
File('out.js.map').writeAsStringSync(sourceMapText);
}
Set<SourceLocation> expectedLocations = test.expectedLocations.toSet();
List<SourceLocation> actualLocations = <SourceLocation>[];
List<SourceLocation> extraLocations = <SourceLocation>[];
for (TargetLineEntry targetLineEntry in sourceMap.lines) {
for (TargetEntry targetEntry in targetLineEntry.entries) {
if (targetEntry.sourceUrlId != null &&
sourceMap.urls[targetEntry.sourceUrlId!] == 'memory:main.dart') {
String? methodName;
if (targetEntry.sourceNameId != null) {
methodName = sourceMap.names[targetEntry.sourceNameId!];
}
SourceLocation location = SourceLocation(
methodName,
targetEntry.sourceLine! + 1,
targetEntry.sourceColumn! + 1,
);
actualLocations.add(location);
if (!expectedLocations.remove(location)) {
extraLocations.add(location);
}
}
}
}
if (expectedLocations.isNotEmpty) {
print('--Missing source locations:---------------------------------------');
AnnotatedCode annotatedCode = AnnotatedCode(test.code, test.code, []);
expectedLocations.forEach(
(l) => annotatedCode.addAnnotation(
l.lineNo,
l.columnNo,
'/*',
l.methodName!,
'*/',
),
);
print(annotatedCode.toText());
print('------------------------------------------------------------------');
Expect.isTrue(
expectedLocations.isEmpty,
"Missing source locations:\n${test.code}\n"
"Actual:\n${actualLocations.join('\n')}\n"
"Missing:\n${expectedLocations.join('\n')}\n",
);
}
if (extraLocations.isNotEmpty) {
print('--Extra source locations:-----------------------------------------');
AnnotatedCode annotatedCode = AnnotatedCode(test.code, test.code, []);
extraLocations.forEach(
(l) => annotatedCode.addAnnotation(
l.lineNo,
l.columnNo,
'/*',
l.methodName!,
'*/',
),
);
print(annotatedCode.toText());
print('------------------------------------------------------------------');
Expect.isTrue(
extraLocations.isEmpty,
"Extra source locations:\n${test.code}\n"
"Actual:\n${actualLocations.join('\n')}\n"
"Extra:\n${extraLocations.join('\n')}\n",
);
}
}
class SourceLocation {
final String? methodName;
final int lineNo;
final int columnNo;
SourceLocation(this.methodName, this.lineNo, this.columnNo);
@override
int get hashCode =>
methodName.hashCode * 13 + lineNo.hashCode * 17 + columnNo.hashCode * 19;
@override
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! SourceLocation) return false;
return methodName == other.methodName &&
lineNo == other.lineNo &&
columnNo == other.columnNo;
}
@override
String toString() => '$methodName:$lineNo:$columnNo';
}