blob: 90cde97f25cdf9d679edf824a0715cd6e99a357f [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/common.dart';
import 'package:compiler/src/constants/values.dart';
import 'package:compiler/src/compiler.dart';
import 'package:compiler/src/deferred_load.dart';
import 'package:compiler/src/elements/elements.dart';
import 'package:compiler/src/filenames.dart';
import 'package:compiler/src/js_backend/js_backend.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';
/// Number of tests that are not part of the automatic test grouping.
int SKIP_COUNT = 2;
/// Number of groups that the [TESTS] are split into.
int SPLIT_COUNT = 5;
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));
SerializationResult result =
await measure('${entryPoint}', 'serialize', () {
return serialize(entryPoint,
memorySourceFiles: serializedData.toMemorySourceFiles(),
resolutionInputs: serializedData.toUris(),
dataUri: Uri.parse('memory:test.data'));
});
await checkModels(entryPoint,
sourceFiles: serializedData
.toMemorySourceFiles(result.serializedData.toMemorySourceFiles()),
resolutionInputs:
serializedData.toUris(result.serializedData.toUris()));
} else {
Uri entryPoint = Uri.parse('memory:main.dart');
await arguments.forEachTest(serializedData, TESTS, checkModels);
}
printMeasurementResults();
});
}
Future checkModels(Uri entryPoint,
{Map<String, String> sourceFiles: const <String, String>{},
List<Uri> resolutionInputs,
int index,
Test test,
bool verbose: false}) async {
String testDescription = test != null ? test.name : '${entryPoint}';
String id = index != null ? '$index: ' : '';
String title = '${id}${testDescription}';
Compiler compilerNormal = await measure(title, 'compile normal', () async {
Compiler compilerNormal = compilerFor(
memorySourceFiles: sourceFiles, options: [Flags.analyzeOnly]);
compilerNormal.resolution.retainCachesForTesting = true;
await compilerNormal.run(entryPoint);
compilerNormal.phase = Compiler.PHASE_DONE_RESOLVING;
compilerNormal.openWorld.closeWorld();
compilerNormal.backend.onResolutionComplete();
compilerNormal.deferredLoadTask
.onResolutionComplete(compilerNormal.mainFunction);
return compilerNormal;
});
Compiler compilerDeserialized =
await measure(title, 'compile deserialized', () async {
Compiler compilerDeserialized = compilerFor(
memorySourceFiles: sourceFiles,
resolutionInputs: resolutionInputs,
options: [Flags.analyzeOnly]);
compilerDeserialized.resolution.retainCachesForTesting = true;
await compilerDeserialized.run(entryPoint);
compilerDeserialized.phase = Compiler.PHASE_DONE_RESOLVING;
compilerDeserialized.openWorld.closeWorld();
compilerDeserialized.backend.onResolutionComplete();
compilerDeserialized.deferredLoadTask
.onResolutionComplete(compilerDeserialized.mainFunction);
return compilerDeserialized;
});
return measure(title, 'check models', () async {
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.closedWorld
.getClassHierarchyNode(compilerNormal.coreClasses.objectClass),
compilerDeserialized.closedWorld.getClassHierarchyNode(
compilerDeserialized.coreClasses.objectClass),
verbose: verbose);
Expect.equals(
compilerNormal.enabledInvokeOn,
compilerDeserialized.enabledInvokeOn,
"Compiler.enabledInvokeOn mismatch");
Expect.equals(
compilerNormal.enabledFunctionApply,
compilerDeserialized.enabledFunctionApply,
"Compiler.enabledFunctionApply mismatch");
Expect.equals(
compilerNormal.enabledRuntimeType,
compilerDeserialized.enabledRuntimeType,
"Compiler.enabledRuntimeType mismatch");
Expect.equals(
compilerNormal.hasIsolateSupport,
compilerDeserialized.hasIsolateSupport,
"Compiler.hasIsolateSupport mismatch");
Expect.equals(
compilerNormal.deferredLoadTask.isProgramSplit,
compilerDeserialized.deferredLoadTask.isProgramSplit,
"isProgramSplit mismatch");
Map<ConstantValue, OutputUnit> constants1 =
compilerNormal.deferredLoadTask.outputUnitForConstantsForTesting;
Map<ConstantValue, OutputUnit> constants2 =
compilerDeserialized.deferredLoadTask.outputUnitForConstantsForTesting;
checkSets(
constants1.keys,
constants2.keys,
'deferredLoadTask._outputUnitForConstants.keys',
areConstantValuesEquivalent,
failOnUnfound: false,
failOnExtra: false,
onSameElement: (ConstantValue value1, ConstantValue value2) {
OutputUnit outputUnit1 = constants1[value1];
OutputUnit outputUnit2 = constants2[value2];
checkOutputUnits(
outputUnit1,
outputUnit2,
'for ${value1.toStructuredText()} '
'vs ${value2.toStructuredText()}');
}, onUnfoundElement: (ConstantValue value1) {
OutputUnit outputUnit1 = constants1[value1];
Expect.isTrue(outputUnit1.isMainOutput,
"Missing deferred constant: ${value1.toStructuredText()}");
}, onExtraElement: (ConstantValue value2) {
OutputUnit outputUnit2 = constants2[value2];
Expect.isTrue(outputUnit2.isMainOutput,
"Extra deferred constant: ${value2.toStructuredText()}");
}, elementToString: (a) {
return '${a.toStructuredText()} -> ${constants1[a]}/${constants2[a]}';
});
});
}
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);
if (element1 is MemberElement && element2 is MemberElement) {
MemberElement member1 = element1.implementation;
MemberElement member2 = element2.implementation;
checkSets(member1.nestedClosures, member2.nestedClosures,
"$member1.nestedClosures", areElementsEquivalent, verbose: verbose,
onSameElement: (a, b) {
LocalFunctionElement localFunction1 = a.expression;
LocalFunctionElement localFunction2 = b.expression;
checkElementIdentities(localFunction1, localFunction2, 'enclosingClass',
localFunction1.enclosingClass, localFunction2.enclosingClass);
testResolvedAstEquivalence(localFunction1.resolvedAst,
localFunction2.resolvedAst, const CheckStrategy());
});
}
}
JavaScriptBackend backend1 = compiler1.backend;
JavaScriptBackend backend2 = compiler2.backend;
Expect.equals(
backend1.inlineCache.getCurrentCacheDecisionForTesting(element1),
backend2.inlineCache.getCurrentCacheDecisionForTesting(element2),
"Inline cache decision mismatch for $element1 vs $element2");
checkElementOutputUnits(compiler1, compiler2, element1, element2);
}
void checkMixinUses(Compiler compiler1, Compiler compiler2, ClassElement class1,
ClassElement class2,
{bool verbose: false}) {
checkSets(
compiler1.closedWorld.mixinUsesOf(class1),
compiler2.closedWorld.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) {
if (child.isInstantiated) {
print('Missing subclass ${child.cls} of ${node1.cls} '
'in ${node2.directSubclasses}');
print(compiler1.closedWorld
.dump(verbose ? compiler1.coreClasses.objectClass : node1.cls));
print(compiler2.closedWorld
.dump(verbose ? compiler2.coreClasses.objectClass : node2.cls));
}
Expect.isFalse(
child.isInstantiated,
'Missing subclass ${child.cls} of ${node1.cls} in '
'${node2.directSubclasses}');
}
}
checkMixinUses(compiler1, compiler2, node1.cls, node2.cls, verbose: verbose);
}
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';
}
void checkElementOutputUnits(Compiler compiler1, Compiler compiler2,
Element element1, Element element2) {
OutputUnit outputUnit1 =
compiler1.deferredLoadTask.getOutputUnitForElementForTesting(element1);
OutputUnit outputUnit2 =
compiler2.deferredLoadTask.getOutputUnitForElementForTesting(element2);
checkOutputUnits(outputUnit1, outputUnit2, 'for $element1 vs $element2');
}
void checkOutputUnits(
OutputUnit outputUnit1, OutputUnit outputUnit2, String message) {
if (outputUnit1 == null && outputUnit2 == null) return;
check(outputUnit1, outputUnit2, 'OutputUnit.isMainOutput $message',
outputUnit1.isMainOutput, outputUnit2.isMainOutput);
checkSetEquivalence(
outputUnit1,
outputUnit2,
'OutputUnit.imports $message',
outputUnit1.imports,
outputUnit2.imports,
(a, b) => areElementsEquivalent(a.declaration, b.declaration));
}