OLD | NEW |
| (Empty) |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library dart2js.serialization_model_test; | |
6 import 'dart:async'; | |
7 import 'dart:io'; | |
8 import 'package:async_helper/async_helper.dart'; | |
9 import 'package:expect/expect.dart'; | |
10 import 'package:compiler/src/closure.dart'; | |
11 import 'package:compiler/src/commandline_options.dart'; | |
12 import 'package:compiler/src/compiler.dart'; | |
13 import 'package:compiler/src/elements/elements.dart'; | |
14 import 'package:compiler/src/filenames.dart'; | |
15 import 'package:compiler/src/js_backend/js_backend.dart'; | |
16 import 'package:compiler/src/serialization/equivalence.dart'; | |
17 import 'package:compiler/src/tree/nodes.dart'; | |
18 import 'package:compiler/src/universe/class_set.dart'; | |
19 import '../memory_compiler.dart'; | |
20 import 'helper.dart'; | |
21 import 'test_data.dart'; | |
22 import 'test_helper.dart'; | |
23 | |
24 main(List<String> args) { | |
25 asyncTest(() async { | |
26 Arguments arguments = new Arguments.from(args); | |
27 SerializedData serializedData = | |
28 await serializeDartCore(arguments: arguments); | |
29 if (arguments.filename != null) { | |
30 Uri entryPoint = Uri.base.resolve(nativeToUriPath(arguments.filename)); | |
31 await checkModels(entryPoint, | |
32 sourceFiles: serializedData.toMemorySourceFiles(), | |
33 resolutionInputs: serializedData.toUris()); | |
34 } else { | |
35 Uri entryPoint = Uri.parse('memory:main.dart'); | |
36 await arguments.forEachTest(serializedData, TESTS, checkModels); | |
37 } | |
38 }); | |
39 } | |
40 | |
41 Future checkModels( | |
42 Uri entryPoint, | |
43 {Map<String, String> sourceFiles: const <String, String>{}, | |
44 List<Uri> resolutionInputs, | |
45 int index, | |
46 Test test, | |
47 bool verbose: false}) async { | |
48 String testDescription = test != null ? test.name : '${entryPoint}'; | |
49 String id = index != null ? '$index: ' : ''; | |
50 print('------------------------------------------------------------------'); | |
51 print('compile normal ${id}${testDescription}'); | |
52 print('------------------------------------------------------------------'); | |
53 Compiler compilerNormal = compilerFor( | |
54 memorySourceFiles: sourceFiles, | |
55 options: [Flags.analyzeOnly]); | |
56 compilerNormal.resolution.retainCachesForTesting = true; | |
57 await compilerNormal.run(entryPoint); | |
58 compilerNormal.phase = Compiler.PHASE_DONE_RESOLVING; | |
59 compilerNormal.world.populate(); | |
60 compilerNormal.backend.onResolutionComplete(); | |
61 | |
62 print('------------------------------------------------------------------'); | |
63 print('compile deserialized ${id}${testDescription}'); | |
64 print('------------------------------------------------------------------'); | |
65 Compiler compilerDeserialized = compilerFor( | |
66 memorySourceFiles: sourceFiles, | |
67 resolutionInputs: resolutionInputs, | |
68 options: [Flags.analyzeOnly]); | |
69 compilerDeserialized.resolution.retainCachesForTesting = true; | |
70 await compilerDeserialized.run(entryPoint); | |
71 compilerDeserialized.phase = Compiler.PHASE_DONE_RESOLVING; | |
72 compilerDeserialized.world.populate(); | |
73 compilerDeserialized.backend.onResolutionComplete(); | |
74 | |
75 checkAllImpacts( | |
76 compilerNormal, compilerDeserialized, | |
77 verbose: verbose); | |
78 | |
79 checkSets( | |
80 compilerNormal.resolverWorld.directlyInstantiatedClasses, | |
81 compilerDeserialized.resolverWorld.directlyInstantiatedClasses, | |
82 "Directly instantiated classes mismatch", | |
83 areElementsEquivalent, | |
84 verbose: verbose); | |
85 | |
86 checkSets( | |
87 compilerNormal.resolverWorld.instantiatedTypes, | |
88 compilerDeserialized.resolverWorld.instantiatedTypes, | |
89 "Instantiated types mismatch", | |
90 areTypesEquivalent, | |
91 verbose: verbose); | |
92 | |
93 checkSets( | |
94 compilerNormal.resolverWorld.isChecks, | |
95 compilerDeserialized.resolverWorld.isChecks, | |
96 "Is-check mismatch", | |
97 areTypesEquivalent, | |
98 verbose: verbose); | |
99 | |
100 checkSets( | |
101 compilerNormal.enqueuer.resolution.processedElements, | |
102 compilerDeserialized.enqueuer.resolution.processedElements, | |
103 "Processed element mismatch", | |
104 areElementsEquivalent, | |
105 onSameElement: (a, b) { | |
106 checkElements( | |
107 compilerNormal, compilerDeserialized, a, b, verbose: verbose); | |
108 }, | |
109 verbose: verbose); | |
110 | |
111 checkClassHierarchyNodes( | |
112 compilerNormal, | |
113 compilerDeserialized, | |
114 compilerNormal.world.getClassHierarchyNode( | |
115 compilerNormal.coreClasses.objectClass), | |
116 compilerDeserialized.world.getClassHierarchyNode( | |
117 compilerDeserialized.coreClasses.objectClass), | |
118 verbose: verbose); | |
119 | |
120 Expect.equals(compilerNormal.enabledInvokeOn, | |
121 compilerDeserialized.enabledInvokeOn, | |
122 "Compiler.enabledInvokeOn mismatch"); | |
123 Expect.equals(compilerNormal.enabledFunctionApply, | |
124 compilerDeserialized.enabledFunctionApply, | |
125 "Compiler.enabledFunctionApply mismatch"); | |
126 Expect.equals(compilerNormal.enabledRuntimeType, | |
127 compilerDeserialized.enabledRuntimeType, | |
128 "Compiler.enabledRuntimeType mismatch"); | |
129 Expect.equals(compilerNormal.hasIsolateSupport, | |
130 compilerDeserialized.hasIsolateSupport, | |
131 "Compiler.hasIsolateSupport mismatch"); | |
132 } | |
133 | |
134 void checkElements( | |
135 Compiler compiler1, Compiler compiler2, | |
136 Element element1, Element element2, | |
137 {bool verbose: false}) { | |
138 if (element1.isFunction || | |
139 element1.isConstructor || | |
140 (element1.isField && element1.isInstanceMember)) { | |
141 AstElement astElement1 = element1; | |
142 AstElement astElement2 = element2; | |
143 ClosureClassMap closureData1 = | |
144 compiler1.closureToClassMapper.computeClosureToClassMapping( | |
145 astElement1.resolvedAst); | |
146 ClosureClassMap closureData2 = | |
147 compiler2.closureToClassMapper.computeClosureToClassMapping( | |
148 astElement2.resolvedAst); | |
149 | |
150 checkElementIdentities(closureData1, closureData2, | |
151 '$element1.closureElement', | |
152 closureData1.closureElement, closureData2.closureElement); | |
153 checkElementIdentities(closureData1, closureData2, | |
154 '$element1.closureClassElement', | |
155 closureData1.closureClassElement, closureData2.closureClassElement); | |
156 checkElementIdentities(closureData1, closureData2, | |
157 '$element1.callElement', | |
158 closureData1.callElement, closureData2.callElement); | |
159 check(closureData1, closureData2, | |
160 '$element1.thisLocal', | |
161 closureData1.thisLocal, closureData2.thisLocal, | |
162 areLocalsEquivalent); | |
163 checkMaps( | |
164 closureData1.freeVariableMap, | |
165 closureData2.freeVariableMap, | |
166 "$element1.freeVariableMap", | |
167 areLocalsEquivalent, | |
168 areCapturedVariablesEquivalent, | |
169 verbose: verbose); | |
170 checkMaps( | |
171 closureData1.capturingScopes, | |
172 closureData2.capturingScopes, | |
173 "$element1.capturingScopes", | |
174 areNodesEquivalent, | |
175 areClosureScopesEquivalent, | |
176 verbose: verbose, | |
177 keyToString: nodeToString); | |
178 checkSets( | |
179 closureData1.variablesUsedInTryOrGenerator, | |
180 closureData2.variablesUsedInTryOrGenerator, | |
181 "$element1.variablesUsedInTryOrGenerator", | |
182 areLocalsEquivalent, | |
183 verbose: verbose); | |
184 } | |
185 JavaScriptBackend backend1 = compiler1.backend; | |
186 JavaScriptBackend backend2 = compiler2.backend; | |
187 Expect.equals( | |
188 backend1.inlineCache.getCurrentCacheDecisionForTesting(element1), | |
189 backend2.inlineCache.getCurrentCacheDecisionForTesting(element2), | |
190 "Inline cache decision mismatch for $element1 vs $element2"); | |
191 } | |
192 | |
193 void checkMixinUses( | |
194 Compiler compiler1, Compiler compiler2, | |
195 ClassElement class1, ClassElement class2, | |
196 {bool verbose: false}) { | |
197 | |
198 checkSets( | |
199 compiler1.world.mixinUsesOf(class1), | |
200 compiler2.world.mixinUsesOf(class2), | |
201 "Mixin uses of $class1 vs $class2", | |
202 areElementsEquivalent, | |
203 verbose: verbose); | |
204 | |
205 } | |
206 | |
207 void checkClassHierarchyNodes( | |
208 Compiler compiler1, | |
209 Compiler compiler2, | |
210 ClassHierarchyNode node1, ClassHierarchyNode node2, | |
211 {bool verbose: false}) { | |
212 if (verbose) { | |
213 print('Checking $node1 vs $node2'); | |
214 } | |
215 Expect.isTrue( | |
216 areElementsEquivalent(node1.cls, node2.cls), | |
217 "Element identity mismatch for ${node1.cls} vs ${node2.cls}."); | |
218 Expect.equals( | |
219 node1.isDirectlyInstantiated, | |
220 node2.isDirectlyInstantiated, | |
221 "Value mismatch for 'isDirectlyInstantiated' " | |
222 "for ${node1.cls} vs ${node2.cls}."); | |
223 Expect.equals( | |
224 node1.isIndirectlyInstantiated, | |
225 node2.isIndirectlyInstantiated, | |
226 "Value mismatch for 'isIndirectlyInstantiated' " | |
227 "for ${node1.cls} vs ${node2.cls}."); | |
228 // TODO(johnniwinther): Enforce a canonical and stable order on direct | |
229 // subclasses. | |
230 for (ClassHierarchyNode child in node1.directSubclasses) { | |
231 bool found = false; | |
232 for (ClassHierarchyNode other in node2.directSubclasses) { | |
233 if (areElementsEquivalent(child.cls, other.cls)) { | |
234 checkClassHierarchyNodes(compiler1, compiler2, | |
235 child, other, verbose: verbose); | |
236 found = true; | |
237 break; | |
238 } | |
239 } | |
240 if (!found) { | |
241 if (child.isInstantiated) { | |
242 print('Missing subclass ${child.cls} of ${node1.cls} ' | |
243 'in ${node2.directSubclasses}'); | |
244 print(compiler1.world.dump( | |
245 verbose ? compiler1.coreClasses.objectClass : node1.cls)); | |
246 print(compiler2.world.dump( | |
247 verbose ? compiler2.coreClasses.objectClass : node2.cls)); | |
248 } | |
249 Expect.isFalse(child.isInstantiated, | |
250 'Missing subclass ${child.cls} of ${node1.cls} in ' | |
251 '${node2.directSubclasses}'); | |
252 } | |
253 } | |
254 checkMixinUses(compiler1, compiler2, node1.cls, node2.cls, verbose: verbose); | |
255 } | |
256 | |
257 bool areLocalsEquivalent(Local a, Local b) { | |
258 if (a == b) return true; | |
259 if (a == null || b == null) return false; | |
260 | |
261 if (a is Element) { | |
262 return b is Element && areElementsEquivalent(a as Element, b as Element); | |
263 } else { | |
264 return a.runtimeType == b.runtimeType && | |
265 areElementsEquivalent(a.executableContext, b.executableContext); | |
266 } | |
267 } | |
268 | |
269 bool areCapturedVariablesEquivalent(CapturedVariable a, CapturedVariable b) { | |
270 if (a == b) return true; | |
271 if (a == null || b == null) return false; | |
272 if (a is ClosureFieldElement && b is ClosureFieldElement) { | |
273 return areElementsEquivalent(a.closureClass, b.closureClass) && | |
274 areLocalsEquivalent(a.local, b.local); | |
275 } else if (a is BoxFieldElement && b is BoxFieldElement) { | |
276 return areElementsEquivalent(a.variableElement, b.variableElement) && | |
277 areLocalsEquivalent(a.box, b.box); | |
278 } | |
279 return false; | |
280 } | |
281 | |
282 bool areClosureScopesEquivalent(ClosureScope a, ClosureScope b) { | |
283 if (a == b) return true; | |
284 if (a == null || b == null) return false; | |
285 if (!areLocalsEquivalent(a.boxElement, b.boxElement)) { | |
286 return false; | |
287 } | |
288 checkMaps(a.capturedVariables, b.capturedVariables, | |
289 'ClosureScope.capturedVariables', | |
290 areLocalsEquivalent, | |
291 areElementsEquivalent); | |
292 checkSets(a.boxedLoopVariables, b.boxedLoopVariables, | |
293 'ClosureScope.boxedLoopVariables', | |
294 areElementsEquivalent); | |
295 return true; | |
296 } | |
297 | |
298 String nodeToString(Node node) { | |
299 String text = '$node'; | |
300 if (text.length > 40) { | |
301 return '(${node.runtimeType}) ${text.substring(0, 37)}...'; | |
302 } | |
303 return '(${node.runtimeType}) $text'; | |
304 } | |
OLD | NEW |