-
Notifications
You must be signed in to change notification settings - Fork 5.9k
/
minified_names_test.dart
152 lines (130 loc) · 5.55 KB
/
minified_names_test.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// 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.
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 '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'}:');
D8Result result = await runWithD8(
memorySourceFiles: {'main.dart': test.code},
options: minified ? [Flags.minify] : []);
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();
}