blob: 781b46bc9a66ff75a72ca261513716acec42caae [file] [log] [blame]
// Copyright (c) 2015, 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:async_helper/async_helper.dart';
import 'package:expect/expect.dart';
import 'package:compiler/src/commandline_options.dart';
import 'package:compiler/src/filenames.dart';
import 'package:compiler/src/io/source_information.dart';
import 'package:compiler/src/js/js_debug.dart';
import 'package:js_ast/js_ast.dart';
import '../helpers/sourcemap_helper.dart';
typedef CodePointWhiteListFunction WhiteListFunction(
String configuration, String file);
typedef bool CodePointWhiteListFunction(CodePoint codePoint);
CodePointWhiteListFunction emptyWhiteListFunction(String config, String file) {
return emptyWhiteList;
}
bool emptyWhiteList(CodePoint codePoint) => false;
main(List<String> arguments) {
test(arguments);
}
void test(List<String> arguments,
{WhiteListFunction whiteListFunction: emptyWhiteListFunction}) {
Set<String> configurations = new Set<String>();
Map<String, Uri> tests = <String, Uri>{};
if (!parseArguments(arguments, configurations, tests)) {
return;
}
asyncTest(() async {
bool errorsFound = false;
for (String file in tests.keys) {
print('==$file=========================================================');
for (String config in configurations) {
List<String> options = TEST_CONFIGURATIONS[config];
print('---$config----------------------------------------------------');
Uri uri = tests[file];
TestResult result = await runTests(config, file, uri, options);
if (result.missingCodePointsMap.isNotEmpty) {
errorsFound =
result.printMissingCodePoints(whiteListFunction(config, file));
}
if (result.multipleNodesMap.isNotEmpty) {
result.printMultipleNodes();
errorsFound = true;
}
if (result.multipleOffsetsMap.isNotEmpty) {
result.printMultipleOffsets();
errorsFound = true;
}
}
}
Expect.isFalse(
errorsFound,
"Errors found. "
"Run the test with a URI option, "
"`source_mapping_test_viewer [--out=<uri>] [configs] [tests]`, to "
"create a html visualization of the missing code points.");
});
}
bool parseArguments(
List<String> arguments, Set<String> configurations, Map<String, Uri> tests,
{bool measure: false}) {
Set<String> extra = arguments.contains('--file') ? new Set<String>() : null;
for (String argument in arguments) {
if (!parseArgument(argument, configurations, tests, extra)) {
return false;
}
}
if (configurations.isEmpty) {
configurations.addAll(TEST_CONFIGURATIONS.keys);
if (!measure) {
configurations.remove('old');
}
}
if (extra != null) {
for (String file in extra) {
Uri uri = Uri.base.resolve(nativeToUriPath(file));
tests[uri.pathSegments.last] = uri;
}
}
if (tests.isEmpty) {
tests.addAll(TEST_FILES);
}
if (arguments.contains('--exclude')) {
List<String> filesToRemove = new List<String>.from(tests.keys);
tests.clear();
tests.addAll(TEST_FILES);
filesToRemove.forEach(tests.remove);
}
return true;
}
/// Parse [argument] for a valid configuration or test-file option.
///
/// On success, the configuration name is added to [configurations] or the
/// test-file name is added to [testFiles], and `true` is returned.
/// On failure, a message is printed and `false` is returned.
///
/// Unmatching arguments are added to [files] is provided.
bool parseArgument(String argument, Set<String> configurations,
Map<String, Uri> tests, Set<String> extra) {
if (argument.startsWith('-')) {
// Skip options.
return true;
} else if (TEST_CONFIGURATIONS.containsKey(argument)) {
configurations.add(argument);
} else if (TEST_FILES.containsKey(argument)) {
tests[argument] = TEST_FILES[argument];
} else if (extra != null) {
extra.add(argument);
} else {
print("Unknown configuration or test file '$argument'. "
"Must be one of '${TEST_CONFIGURATIONS.keys.join("', '")}' or "
"'${TEST_FILES.keys.join("', '")}'.");
return false;
}
return true;
}
const Map<String, List<String>> TEST_CONFIGURATIONS = const {
'ast': const [
'--use-new-source-info',
'--use-old-frontend',
],
'kernel': const [],
'old': const [
'--use-old-frontend',
],
};
final Map<String, Uri> TEST_FILES = _computeTestFiles();
Map<String, Uri> _computeTestFiles() {
Map<String, Uri> map = <String, Uri>{};
Directory dataDir = new Directory.fromUri(
Uri.base.resolve('tests/compiler/dart2js/sourcemaps/data/'));
for (File file in dataDir.listSync()) {
Uri uri = file.uri;
map[uri.pathSegments.last] = uri;
}
return map;
}
Future<TestResult> runTests(
String config, String filename, Uri uri, List<String> options,
{bool verbose: true}) async {
SourceMapProcessor processor = new SourceMapProcessor(uri);
SourceMaps sourceMaps = await processor.process(
[Flags.useContentSecurityPolicy, Flags.disableInlining]..addAll(options),
verbose: verbose);
TestResult result = new TestResult(config, filename, processor);
for (SourceMapInfo info in sourceMaps.elementSourceMapInfos.values) {
if (info.element.library.canonicalUri.scheme == 'dart') continue;
result.userInfoList.add(info);
Iterable<CodePoint> missingCodePoints =
info.codePoints.where((c) => c.isMissing);
if (missingCodePoints.isNotEmpty) {
result.missingCodePointsMap[info] = missingCodePoints;
}
Map<int, Set<SourceLocation>> offsetToLocationsMap =
<int, Set<SourceLocation>>{};
for (Node node in info.nodeMap.nodes) {
info.nodeMap[node]
.forEach((int targetOffset, List<SourceLocation> sourceLocations) {
if (sourceLocations.length > 1) {
Map<Node, List<SourceLocation>> multipleMap = result.multipleNodesMap
.putIfAbsent(info, () => <Node, List<SourceLocation>>{});
multipleMap[node] = sourceLocations;
} else {
offsetToLocationsMap
.putIfAbsent(targetOffset, () => new Set<SourceLocation>())
.addAll(sourceLocations);
}
});
}
offsetToLocationsMap
.forEach((int targetOffset, Set<SourceLocation> sourceLocations) {
if (sourceLocations.length > 1) {
Map<int, Set<SourceLocation>> multipleMap = result.multipleOffsetsMap
.putIfAbsent(info, () => <int, Set<SourceLocation>>{});
multipleMap[targetOffset] = sourceLocations;
}
});
}
return result;
}
class TestResult {
final String config;
final String file;
final SourceMapProcessor processor;
List<SourceMapInfo> userInfoList = <SourceMapInfo>[];
Map<SourceMapInfo, Iterable<CodePoint>> missingCodePointsMap =
<SourceMapInfo, Iterable<CodePoint>>{};
/// For each [SourceMapInfo] a map from JS node to multiple source locations
/// associated with the node.
Map<SourceMapInfo, Map<Node, List<SourceLocation>>> multipleNodesMap =
<SourceMapInfo, Map<Node, List<SourceLocation>>>{};
/// For each [SourceMapInfo] a map from JS offset to multiple source locations
/// associated with the offset.
Map<SourceMapInfo, Map<int, Set<SourceLocation>>> multipleOffsetsMap =
<SourceMapInfo, Map<int, Set<SourceLocation>>>{};
TestResult(this.config, this.file, this.processor);
bool printMissingCodePoints(
[CodePointWhiteListFunction codePointWhiteList = emptyWhiteList]) {
bool allWhiteListed = true;
missingCodePointsMap.forEach((info, missingCodePoints) {
print("Missing code points for ${info.element} in '$file' "
"in config '$config':");
for (CodePoint codePoint in missingCodePoints) {
if (codePointWhiteList(codePoint)) {
print(" $codePoint (white-listed)");
} else {
print(" $codePoint");
allWhiteListed = false;
}
}
});
return !allWhiteListed;
}
void printMultipleNodes() {
multipleNodesMap.forEach((info, multipleMap) {
multipleMap.forEach((node, sourceLocations) {
print('Multiple source locations:\n ${sourceLocations.join('\n ')}\n'
'for `${nodeToString(node)}` in ${info.element} in '
'$file.');
});
});
}
void printMultipleOffsets() {
multipleOffsetsMap.forEach((info, multipleMap) {
multipleMap.forEach((targetOffset, sourceLocations) {
print('Multiple source locations:\n ${sourceLocations.join('\n ')}\n'
'for offset $targetOffset in ${info.element} in $file.');
});
});
}
}