| // Copyright (c) 2018, 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 'dart:convert'; |
| |
| import 'package:args/args.dart'; |
| import 'package:async_helper/async_helper.dart'; |
| import 'package:compiler/src/commandline_options.dart'; |
| import 'package:dart2js_tools/src/dart2js_mapping.dart'; |
| |
| import '../helpers/d8_helper.dart'; |
| import '../helpers/memory_compiler.dart'; |
| import 'package:expect/expect.dart'; |
| |
| void main(List<String> args) { |
| ArgParser argParser = new ArgParser(allowTrailingOptions: true); |
| argParser.addFlag('continued', abbr: 'c', defaultsTo: false); |
| ArgResults argResults = argParser.parse(args); |
| Directory dataDir = |
| new Directory.fromUri(Platform.script.resolve('minified')); |
| print('Input folder: ${dataDir.uri}'); |
| asyncTest(() async { |
| bool continuing = false; |
| await for (FileSystemEntity entity in dataDir.list()) { |
| String name = entity.uri.pathSegments.last; |
| if (!name.endsWith('.dart')) continue; |
| if (argResults.rest.isNotEmpty && |
| !argResults.rest.contains(name) && |
| !continuing) { |
| continue; |
| } |
| print('\ninput $name'); |
| await runTest(await new File.fromUri(entity.uri).readAsString()); |
| if (argResults['continued']) continuing = true; |
| } |
| }); |
| } |
| |
| // Object to hold the expectations of a individual minified-name test. |
| class MinifiedNameTest { |
| /// Pattern used to find a minified name in the error message. |
| final RegExp pattern; |
| |
| /// The kind of minified name, it can be global, instance, or other |
| /// (the first two correspond to the two namespaces that contain extra data in |
| /// the source-map file). |
| final String _kind; |
| |
| /// Whether the minified name is from the global namespace. |
| bool get isGlobal => _kind == 'global'; |
| |
| /// Whether the minified name is from the instance namespace. |
| bool get isInstance => _kind == 'instance'; |
| |
| /// The deobfuscated name we expect to find. |
| final String expectedName; |
| |
| /// The actual test code. |
| final String code; |
| |
| MinifiedNameTest(this.pattern, this._kind, this.expectedName, this.code); |
| } |
| |
| RegExp _patternMatcher = new RegExp("// Error pattern: (.*)\n"); |
| RegExp _kindMatcher = new RegExp("// Kind of minified name: (.*)\n"); |
| RegExp _nameMatcher = new RegExp("// Expected deobfuscated name: (.*)\n"); |
| |
| Future runTest(String code) async { |
| var patternMatch = _patternMatcher.firstMatch(code); |
| Expect.isNotNull(patternMatch, "Could not find the error pattern."); |
| var pattern = new RegExp(patternMatch.group(1)); |
| var kindMatch = _kindMatcher.firstMatch(code); |
| Expect.isNotNull(kindMatch, "Could not find the expected minified kind."); |
| var kind = kindMatch.group(1); |
| |
| // TODO(sigmund): add support for "other" when we encode symbol information |
| // directly for each field and local variable. |
| const validKinds = const ['global', 'instance']; |
| Expect.isTrue(validKinds.contains(kind), |
| "Invalid kind: $kind, please use one of $validKinds"); |
| |
| var nameMatch = _nameMatcher.firstMatch(code); |
| Expect.isNotNull(nameMatch, "Could not find the expected deobfuscated name."); |
| var expectedName = nameMatch.group(1); |
| var test = new MinifiedNameTest(pattern, kind, expectedName, code); |
| print('expectations: ${pattern.pattern} $kind $expectedName'); |
| await checkExpectation(test, false); |
| await checkExpectation(test, true); |
| } |
| |
| checkExpectation(MinifiedNameTest test, bool minified) async { |
| print('-- ${minified ? 'minified' : 'not-minified'}:'); |
| var options = [ |
| Flags.testMode, |
| '--libraries-spec=$sdkLibrariesSpecificationUri', |
| if (minified) Flags.minify, |
| ]; |
| D8Result result = await runWithD8( |
| memorySourceFiles: {'main.dart': test.code}, options: options); |
| String stdout = result.runResult.stdout; |
| String error = _extractError(stdout); |
| print(' error: $error'); |
| Expect.isNotNull(error, 'Couldn\'t find the error message in $stdout'); |
| |
| var match = test.pattern.firstMatch(error); |
| Expect.isNotNull( |
| match, |
| 'Error didn\'t match the test pattern' |
| '\nerror: $error\npattern:${test.pattern}'); |
| var name = match.group(1); |
| print(' obfuscated-name: $name'); |
| Expect.isNotNull(name, 'Error didn\'t contain a name\nerror: $error'); |
| |
| if (name.startsWith('minified:')) { |
| name = name.substring(9); |
| } |
| |
| var sourceMap = '${result.outputPath}.map'; |
| var json = jsonDecode(await new File(sourceMap).readAsString()); |
| |
| var mapping = Dart2jsMapping.json(json); |
| Expect.isTrue(mapping.globalNames.isNotEmpty, |
| "Source-map doesn't contain minified-names"); |
| |
| var actualName; |
| if (test.isGlobal) { |
| actualName = mapping.globalNames[name]; |
| Expect.isNotNull(actualName, "'$name' not in global name map"); |
| } else if (test.isInstance) { |
| // In non-minified mode some errors show the original name |
| // rather than the selector name (e.g. m1 instead of m1$0 in a |
| // NoSuchMethodError), and because of that the name might not be on the |
| // table. |
| // |
| // TODO(sigmund): consider making all errors show the internal name, or |
| // include a marker to make it easier to distinguish. |
| actualName = mapping.instanceNames[name] ?? name; |
| } else { |
| Expect.fail('unexpected'); |
| } |
| print(' actual-name: $actualName'); |
| Expect.equals(test.expectedName, actualName); |
| print(' PASS!!'); |
| } |
| |
| /// Returns the portion of the output that corresponds to the error message. |
| /// |
| /// Note: some errors can span multiple lines. |
| String _extractError(String stdout) { |
| var firstStackFrame = stdout.indexOf('\n at'); |
| if (firstStackFrame == -1) return null; |
| var errorMarker = stdout.indexOf('^') + 1; |
| return stdout.substring(errorMarker, firstStackFrame).trim(); |
| } |