|  | // 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. | 
|  |  | 
|  | // @dart = 2.7 | 
|  |  | 
|  | import 'dart:async'; | 
|  | import 'dart:io'; | 
|  | import 'package:_fe_analyzer_shared/src/util/filenames.dart'; | 
|  | import 'package:async_helper/async_helper.dart'; | 
|  | import 'package:expect/expect.dart'; | 
|  | import 'package:compiler/src/commandline_options.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 { | 
|  | 'kernel': const [], | 
|  | }; | 
|  |  | 
|  | 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('pkg/compiler/test/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(['--csp', Flags.disableInlining, ...options], verbose: verbose); | 
|  | TestResult result = new TestResult(config, filename, processor); | 
|  | for (SourceMapInfo info in sourceMaps.elementSourceMapInfos.values) { | 
|  | if (info.element.library.canonicalUri.isScheme('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.'); | 
|  | }); | 
|  | }); | 
|  | } | 
|  | } |