| // 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'; | |
| } |