blob: 0a02e8a9f76aeb8e97260afb8f88b7a50b375db2 [file] [log] [blame]
// 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();
}