blob: 2ab28e9eee59521b15d555c95d41a69199162c78 [file] [log] [blame]
// 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.
library dart2js.serialization_model_test;
import 'dart:async';
import 'dart:io';
import 'package:async_helper/async_helper.dart';
import 'package:expect/expect.dart';
import 'package:compiler/src/closure.dart';
import 'package:compiler/src/commandline_options.dart';
import 'package:compiler/src/compiler.dart';
import 'package:compiler/src/elements/elements.dart';
import 'package:compiler/src/filenames.dart';
import 'package:compiler/src/serialization/equivalence.dart';
import 'package:compiler/src/tree/nodes.dart';
import 'package:compiler/src/universe/class_set.dart';
import '../memory_compiler.dart';
import 'helper.dart';
import 'test_data.dart';
import 'test_helper.dart';
main(List<String> args) {
asyncTest(() async {
Arguments arguments = new Arguments.from(args);
SerializedData serializedData =
await serializeDartCore(arguments: arguments);
if (arguments.filename != null) {
Uri entryPoint = Uri.base.resolve(nativeToUriPath(arguments.filename));
await checkModels(serializedData, entryPoint);
} else {
Uri entryPoint = Uri.parse('memory:main.dart');
arguments.forEachTest(TESTS, (int index, Test test) async {
print('==============================================================');
print(test.sourceFiles);
await checkModels(
serializedData,
entryPoint,
memorySourceFiles: test.sourceFiles,
verbose: arguments.verbose);
});
}
});
}
Future checkModels(
SerializedData serializedData,
Uri entryPoint,
{Map<String, String> memorySourceFiles: const <String, String>{},
bool verbose: false}) async {
print('------------------------------------------------------------------');
print('compile normal');
print('------------------------------------------------------------------');
Compiler compilerNormal = compilerFor(
memorySourceFiles: memorySourceFiles,
options: [Flags.analyzeOnly]);
compilerNormal.resolution.retainCachesForTesting = true;
await compilerNormal.run(entryPoint);
compilerNormal.phase = Compiler.PHASE_DONE_RESOLVING;
compilerNormal.world.populate();
compilerNormal.backend.onResolutionComplete();
print('------------------------------------------------------------------');
print('compile deserialized');
print('------------------------------------------------------------------');
Compiler compilerDeserialized = compilerFor(
memorySourceFiles: serializedData.toMemorySourceFiles(memorySourceFiles),
resolutionInputs: serializedData.toUris(),
options: [Flags.analyzeOnly]);
compilerDeserialized.resolution.retainCachesForTesting = true;
await compilerDeserialized.run(entryPoint);
compilerDeserialized.phase = Compiler.PHASE_DONE_RESOLVING;
compilerDeserialized.world.populate();
compilerDeserialized.backend.onResolutionComplete();
checkAllImpacts(
compilerNormal, compilerDeserialized,
verbose: verbose);
checkSets(
compilerNormal.resolverWorld.directlyInstantiatedClasses,
compilerDeserialized.resolverWorld.directlyInstantiatedClasses,
"Directly instantiated classes mismatch",
areElementsEquivalent,
verbose: verbose);
checkSets(
compilerNormal.resolverWorld.instantiatedTypes,
compilerDeserialized.resolverWorld.instantiatedTypes,
"Instantiated types mismatch",
areTypesEquivalent,
verbose: verbose);
checkSets(
compilerNormal.resolverWorld.isChecks,
compilerDeserialized.resolverWorld.isChecks,
"Is-check mismatch",
areTypesEquivalent,
verbose: verbose);
checkSets(
compilerNormal.enqueuer.resolution.processedElements,
compilerDeserialized.enqueuer.resolution.processedElements,
"Processed element mismatch",
areElementsEquivalent,
onSameElement: (a, b) {
checkElements(
compilerNormal, compilerDeserialized, a, b, verbose: verbose);
},
verbose: verbose);
checkClassHierarchyNodes(
compilerNormal,
compilerDeserialized,
compilerNormal.world.getClassHierarchyNode(
compilerNormal.coreClasses.objectClass),
compilerDeserialized.world.getClassHierarchyNode(
compilerDeserialized.coreClasses.objectClass),
verbose: verbose);
}
void checkElements(
Compiler compiler1, Compiler compiler2,
Element element1, Element element2,
{bool verbose: false}) {
if (element1.isFunction ||
element1.isConstructor ||
(element1.isField && element1.isInstanceMember)) {
AstElement astElement1 = element1;
AstElement astElement2 = element2;
ClosureClassMap closureData1 =
compiler1.closureToClassMapper.computeClosureToClassMapping(
astElement1.resolvedAst);
ClosureClassMap closureData2 =
compiler2.closureToClassMapper.computeClosureToClassMapping(
astElement2.resolvedAst);
checkElementIdentities(closureData1, closureData2,
'$element1.closureElement',
closureData1.closureElement, closureData2.closureElement);
checkElementIdentities(closureData1, closureData2,
'$element1.closureClassElement',
closureData1.closureClassElement, closureData2.closureClassElement);
checkElementIdentities(closureData1, closureData2,
'$element1.callElement',
closureData1.callElement, closureData2.callElement);
check(closureData1, closureData2,
'$element1.thisLocal',
closureData1.thisLocal, closureData2.thisLocal,
areLocalsEquivalent);
checkMaps(
closureData1.freeVariableMap,
closureData2.freeVariableMap,
"$element1.freeVariableMap",
areLocalsEquivalent,
areCapturedVariablesEquivalent,
verbose: verbose);
checkMaps(
closureData1.capturingScopes,
closureData2.capturingScopes,
"$element1.capturingScopes",
areNodesEquivalent,
areClosureScopesEquivalent,
verbose: verbose,
keyToString: nodeToString);
checkSets(
closureData1.variablesUsedInTryOrGenerator,
closureData2.variablesUsedInTryOrGenerator,
"$element1.variablesUsedInTryOrGenerator",
areLocalsEquivalent,
verbose: verbose);
}
}
void checkMixinUses(
Compiler compiler1, Compiler compiler2,
ClassElement class1, ClassElement class2,
{bool verbose: false}) {
checkSets(
compiler1.world.mixinUsesOf(class1),
compiler2.world.mixinUsesOf(class2),
"Mixin uses of $class1 vs $class2",
areElementsEquivalent,
verbose: verbose);
}
void checkClassHierarchyNodes(
Compiler compiler1,
Compiler compiler2,
ClassHierarchyNode node1, ClassHierarchyNode node2,
{bool verbose: false}) {
if (verbose) {
print('Checking $node1 vs $node2');
}
Expect.isTrue(
areElementsEquivalent(node1.cls, node2.cls),
"Element identity mismatch for ${node1.cls} vs ${node2.cls}.");
Expect.equals(
node1.isDirectlyInstantiated,
node2.isDirectlyInstantiated,
"Value mismatch for 'isDirectlyInstantiated' "
"for ${node1.cls} vs ${node2.cls}.");
Expect.equals(
node1.isIndirectlyInstantiated,
node2.isIndirectlyInstantiated,
"Value mismatch for 'isIndirectlyInstantiated' "
"for ${node1.cls} vs ${node2.cls}.");
// TODO(johnniwinther): Enforce a canonical and stable order on direct
// subclasses.
for (ClassHierarchyNode child in node1.directSubclasses) {
bool found = false;
for (ClassHierarchyNode other in node2.directSubclasses) {
if (areElementsEquivalent(child.cls, other.cls)) {
checkClassHierarchyNodes(compiler1, compiler2,
child, other, verbose: verbose);
found = true;
break;
}
}
if (!found) {
Expect.isFalse(child.isInstantiated,
'Missing subclass ${child.cls} of ${node1.cls}');
}
}
checkMixinUses(compiler1, compiler2, node1.cls, node2.cls, verbose: verbose);
}
void checkSets(
Iterable set1,
Iterable set2,
String messagePrefix,
bool sameElement(a, b),
{bool failOnUnfound: true,
bool verbose: false,
void onSameElement(a, b)}) {
List common = [];
List unfound = [];
Set remaining = computeSetDifference(
set1, set2, common, unfound,
sameElement: sameElement,
checkElements: onSameElement);
StringBuffer sb = new StringBuffer();
sb.write("$messagePrefix:");
if (verbose) {
sb.write("\n Common:\n ${common.join('\n ')}");
}
if (unfound.isNotEmpty || verbose) {
sb.write("\n Unfound:\n ${unfound.join('\n ')}");
}
if (remaining.isNotEmpty || verbose) {
sb.write("\n Extra: \n ${remaining.join('\n ')}");
}
String message = sb.toString();
if (unfound.isNotEmpty || remaining.isNotEmpty) {
if (failOnUnfound || remaining.isNotEmpty) {
Expect.fail(message);
} else {
print(message);
}
} else if (verbose) {
print(message);
}
}
String defaultToString(obj) => '$obj';
void checkMaps(
Map map1,
Map map2,
String messagePrefix,
bool sameKey(a, b),
bool sameValue(a, b),
{bool failOnUnfound: true,
bool failOnMismatch: true,
bool verbose: false,
String keyToString(key): defaultToString,
String valueToString(key): defaultToString}) {
List common = [];
List unfound = [];
List<List> mismatch = <List>[];
Set remaining = computeSetDifference(
map1.keys, map2.keys, common, unfound,
sameElement: sameKey,
checkElements: (k1, k2) {
var v1 = map1[k1];
var v2 = map2[k2];
if (!sameValue(v1, v2)) {
mismatch.add([k1, k2]);
}
});
StringBuffer sb = new StringBuffer();
sb.write("$messagePrefix:");
if (verbose) {
sb.write("\n Common: \n");
for (List pair in common) {
var k1 = pair[0];
var k2 = pair[1];
var v1 = map1[k1];
var v2 = map2[k2];
sb.write(" key1 =${keyToString(k1)}\n");
sb.write(" key2 =${keyToString(k2)}\n");
sb.write(" value1=${valueToString(v1)}\n");
sb.write(" value2=${valueToString(v2)}\n");
}
}
if (unfound.isNotEmpty || verbose) {
sb.write("\n Unfound: \n");
for (var k1 in unfound) {
var v1 = map1[k1];
sb.write(" key1 =${keyToString(k1)}\n");
sb.write(" value1=${valueToString(v1)}\n");
}
}
if (remaining.isNotEmpty || verbose) {
sb.write("\n Extra: \n");
for (var k2 in remaining) {
var v2 = map2[k2];
sb.write(" key2 =${keyToString(k2)}\n");
sb.write(" value2=${valueToString(v2)}\n");
}
}
if (mismatch.isNotEmpty || verbose) {
sb.write("\n Mismatch: \n");
for (List pair in mismatch) {
var k1 = pair[0];
var k2 = pair[1];
var v1 = map1[k1];
var v2 = map2[k2];
sb.write(" key1 =${keyToString(k1)}\n");
sb.write(" key2 =${keyToString(k2)}\n");
sb.write(" value1=${valueToString(v1)}\n");
sb.write(" value2=${valueToString(v2)}\n");
}
}
String message = sb.toString();
if (unfound.isNotEmpty || mismatch.isNotEmpty || remaining.isNotEmpty) {
if ((unfound.isNotEmpty && failOnUnfound) ||
(mismatch.isNotEmpty && failOnMismatch) ||
remaining.isNotEmpty) {
Expect.fail(message);
} else {
print(message);
}
} else if (verbose) {
print(message);
}
}
bool areLocalsEquivalent(Local a, Local b) {
if (a == b) return true;
if (a == null || b == null) return false;
if (a is Element) {
return b is Element && areElementsEquivalent(a as Element, b as Element);
} else {
return a.runtimeType == b.runtimeType &&
areElementsEquivalent(a.executableContext, b.executableContext);
}
}
bool areCapturedVariablesEquivalent(CapturedVariable a, CapturedVariable b) {
if (a == b) return true;
if (a == null || b == null) return false;
if (a is ClosureFieldElement && b is ClosureFieldElement) {
return areElementsEquivalent(a.closureClass, b.closureClass) &&
areLocalsEquivalent(a.local, b.local);
} else if (a is BoxFieldElement && b is BoxFieldElement) {
return areElementsEquivalent(a.variableElement, b.variableElement) &&
areLocalsEquivalent(a.box, b.box);
}
return false;
}
bool areClosureScopesEquivalent(ClosureScope a, ClosureScope b) {
if (a == b) return true;
if (a == null || b == null) return false;
if (!areLocalsEquivalent(a.boxElement, b.boxElement)) {
return false;
}
checkMaps(a.capturedVariables, b.capturedVariables,
'ClosureScope.capturedVariables',
areLocalsEquivalent,
areElementsEquivalent);
checkSets(a.boxedLoopVariables, b.boxedLoopVariables,
'ClosureScope.boxedLoopVariables',
areElementsEquivalent);
return true;
}
String nodeToString(Node node) {
String text = '$node';
if (text.length > 40) {
return '(${node.runtimeType}) ${text.substring(0, 37)}...';
}
return '(${node.runtimeType}) $text';
}