blob: 864df5d637652fcbe66c061c1fb5fafa944f8006 [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.dev.javac;
import com.google.gwt.dev.javac.Dependencies.Ref;
import com.google.gwt.dev.javac.testing.impl.JavaResourceBase;
import com.google.gwt.dev.javac.testing.impl.MockJavaResource;
import com.google.gwt.dev.javac.testing.impl.MockResourceOracle;
import com.google.gwt.dev.jjs.SourceOrigin;
import com.google.gwt.dev.js.JsParser;
import com.google.gwt.dev.js.JsSourceGenerationVisitor;
import com.google.gwt.dev.js.ast.JsFunction;
import com.google.gwt.dev.js.ast.JsRootScope;
import com.google.gwt.dev.js.ast.JsStatement;
import com.google.gwt.dev.js.ast.JsVisitor;
import com.google.gwt.dev.util.DefaultTextOutput;
import com.google.gwt.dev.util.TextOutput;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.collect.Lists;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Tests {@link CompilationState}.
*/
public class CompilationStateTest extends CompilationStateTestBase {
private static final MockJavaResource FOO_DIFF_API =
new MockJavaResource("test.Foo") {
@Override
public CharSequence getContent() {
StringBuffer code = new StringBuffer();
code.append("package test;\n");
code.append("public class Foo {\n");
code.append(" public String value() { return \"Foo\"; }\n");
code.append(" public String value2() { return \"Foo2\"; }\n");
code.append("}\n");
return code;
}
};
private static final MockJavaResource FOO_SAME_API =
new MockJavaResource("test.Foo") {
@Override
public CharSequence getContent() {
StringBuffer code = new StringBuffer();
code.append("package test;\n");
code.append("public class Foo {\n");
code.append(" public String value() { return \"Foo2\"; }\n");
code.append("}\n");
return code;
}
};
private static String parseJs(String js) throws Exception {
List<JsStatement> parsed =
JsParser.parse(SourceOrigin.UNKNOWN, JsRootScope.INSTANCE,
new StringReader(js));
TextOutput text = new DefaultTextOutput(false);
JsVisitor generator = new JsSourceGenerationVisitor(text);
generator.acceptList(parsed);
return text.toString();
}
public void testAddGeneratedCompilationUnit() {
validateCompilationState();
// Add a unit and ensure it shows up.
addGeneratedUnits(JavaResourceBase.FOO);
validateCompilationState(Shared.getTypeName(JavaResourceBase.FOO));
rebuildCompilationState();
validateCompilationState();
}
/* test that a generated unit, if unchanged, is reused */
public void testCaching() {
testCaching(JavaResourceBase.FOO);
}
/* test that multiple generated units, if unchanged, are reused */
public void testCachingOfMultipleUnits() {
testCaching(JavaResourceBase.BAR, JavaResourceBase.FOO);
}
public void testCompileError() {
oracle.add(JavaResourceBase.BAR);
rebuildCompilationState();
CompilationUnit badUnit =
state.getCompilationUnitMap().get(
Shared.getTypeName(JavaResourceBase.BAR));
assertTrue(badUnit.isError());
Set<CompilationUnit> goodUnits =
new HashSet<CompilationUnit>(state.getCompilationUnits());
goodUnits.remove(badUnit);
assertUnitsChecked(goodUnits);
}
public void testCompileWithGeneratedUnits() {
assertUnitsChecked(state.getCompilationUnits());
addGeneratedUnits(JavaResourceBase.FOO);
assertUnitsChecked(state.getCompilationUnits());
}
public void testCompileWithGeneratedUnitsError() {
assertUnitsChecked(state.getCompilationUnits());
addGeneratedUnits(JavaResourceBase.BAR);
CompilationUnit badUnit =
state.getCompilationUnitMap().get(
Shared.getTypeName(JavaResourceBase.BAR));
assertTrue(badUnit.isError());
Set<CompilationUnit> goodUnits =
new HashSet<CompilationUnit>(state.getCompilationUnits());
goodUnits.remove(badUnit);
assertUnitsChecked(goodUnits);
}
public void testCompileWithGeneratedUnitsErrorAndDepedentGeneratedUnit() {
assertUnitsChecked(state.getCompilationUnits());
MockJavaResource badFoo =
new MockJavaResource(Shared.getTypeName(JavaResourceBase.FOO)) {
@Override
public CharSequence getContent() {
return "compilation error LOL!";
}
};
oracle.add(badFoo);
rebuildCompilationState();
addGeneratedUnits(JavaResourceBase.BAR);
CompilationUnit badUnit =
state.getCompilationUnitMap().get(Shared.getTypeName(badFoo));
assertTrue(badUnit.isError());
CompilationUnit invalidUnit =
state.getCompilationUnitMap().get(
Shared.getTypeName(JavaResourceBase.BAR));
assertTrue(invalidUnit.isError());
Set<CompilationUnit> goodUnits =
new HashSet<CompilationUnit>(state.getCompilationUnits());
goodUnits.remove(badUnit);
goodUnits.remove(invalidUnit);
assertUnitsChecked(goodUnits);
}
/*
* test if things work correctly when a generated unit can't be reused, but
* another generated unit it depends on can be reused
*/
public void testComplexCacheInvalidation() {
testCachingOverMultipleRefreshes(new MockJavaResource[]{
JavaResourceBase.FOO, JavaResourceBase.BAR},
new MockJavaResource[]{
JavaResourceBase.FOO,
new TweakedMockJavaResource(JavaResourceBase.BAR)},
Collections.singleton(JavaResourceBase.FOO.getTypeName()));
}
public void testInitialization() {
assertUnitsChecked(state.getCompilationUnits());
}
public void testInvalidation() {
testCachingOverMultipleRefreshes(
new MockJavaResource[]{JavaResourceBase.FOO},
new MockJavaResource[]{new TweakedMockJavaResource(JavaResourceBase.FOO)},
Collections.<String> emptySet());
}
public void testInvalidationNonstructuralDep() {
testCachingOverMultipleRefreshes(new MockJavaResource[]{
JavaResourceBase.FOO, JavaResourceBase.BAR}, new MockJavaResource[]{
FOO_SAME_API, JavaResourceBase.BAR},
Collections.singleton(JavaResourceBase.BAR.getTypeName()));
}
public void testInvalidationOfMultipleUnits() {
testCachingOverMultipleRefreshes(new MockJavaResource[]{
JavaResourceBase.FOO, JavaResourceBase.BAR}, new MockJavaResource[]{
new TweakedMockJavaResource(JavaResourceBase.FOO),
new TweakedMockJavaResource(JavaResourceBase.BAR)},
Collections.<String> emptySet());
}
public void testInvalidationStructuralDep() {
testCachingOverMultipleRefreshes(new MockJavaResource[]{
JavaResourceBase.FOO, JavaResourceBase.BAR}, new MockJavaResource[]{
FOO_DIFF_API, JavaResourceBase.BAR}, Collections.<String> emptySet());
}
public void testInvalidationWhenSourceUnitsChange() {
/*
* Steps: (i) Check compilation state. (ii) Add generated units. (iii)
* Change unit in source oracle. (iv) Refresh oracle. (v) Add same generated
* units. (v) Check that there is no reuse.
*/
validateCompilationState();
oracle.add(JavaResourceBase.FOO);
rebuildCompilationState();
// add generated units
addGeneratedUnits(JavaResourceBase.BAR);
assertUnitsChecked(state.getCompilationUnits());
CompilationUnit oldBar =
state.getCompilationUnitMap().get(JavaResourceBase.BAR.getTypeName());
assertNotNull(oldBar);
// change unit in source oracle
oracle.replace(FOO_DIFF_API);
rebuildCompilationState();
/*
* Add same generated units. Verify that the original units are not used.
*/
addGeneratedUnits(JavaResourceBase.BAR);
assertUnitsChecked(state.getCompilationUnits());
CompilationUnit newBar =
state.getCompilationUnitMap().get(JavaResourceBase.BAR.getTypeName());
assertNotNull(newBar);
assertNotSame(oldBar, newBar);
}
public void testMethodArgs() {
MockJavaResource resource = new MockJavaResource("test.MethodArgsTest") {
@Override
public CharSequence getContent() {
StringBuffer code = new StringBuffer();
code.append("package test;\n");
code.append("public abstract class MethodArgsTest {\n");
code.append(" public abstract void anAbstractMethod(String aArg1);\n");
code.append(" public native void aNativeMethod(String nArg1, int nArg2);\n");
code.append(" public void aMethod(String mArg1, int mArg2, char mArg3) {}\n");
code.append(" public void aNoArgMethod() {}\n");
code.append("}\n");
return code;
}
};
validateCompilationState();
oracle.add(resource);
rebuildCompilationState();
validateCompilationState();
Map<String, CompilationUnit> unitMap = state.getCompilationUnitMap();
MethodArgNamesLookup methodArgs =
unitMap.get(Shared.getTypeName(resource)).getMethodArgs();
String[] methods = methodArgs.getMethods();
assertEquals(3, methods.length);
Arrays.sort(methods);
assertEquals("test.MethodArgsTest.aMethod(Ljava/lang/String;IC)V",
methods[0]);
assertEquals("test.MethodArgsTest.aNativeMethod(Ljava/lang/String;I)V",
methods[1]);
assertEquals("test.MethodArgsTest.anAbstractMethod(Ljava/lang/String;)V",
methods[2]);
String[] names;
names = methodArgs.lookup(methods[0]);
assertEquals(3, names.length);
assertEquals("mArg1", names[0]);
assertEquals("mArg2", names[1]);
assertEquals("mArg3", names[2]);
names = methodArgs.lookup(methods[1]);
assertEquals(2, names.length);
assertEquals("nArg1", names[0]);
assertEquals("nArg2", names[1]);
names = methodArgs.lookup(methods[2]);
assertEquals(1, names.length);
assertEquals("aArg1", names[0]);
}
public void testSerializeCompilationUnit() throws Exception {
MockJavaResource resource = new MockJavaResource("test.SerializationTest") {
@Override
public CharSequence getContent() {
StringBuffer code = new StringBuffer();
code.append("package test;\n");
code.append("public abstract class SerializationTest {\n");
code.append(" public static native boolean getTrue() /*-{ return true; }-*/;\n");
code.append(" public abstract String methodArgsTest(int arg1, Object arg2);");
code.append(" public final String toString() { return \"SerializationTest\"; }\n");
code.append("}\n");
return code;
}
};
String resourceTypeName = Shared.getTypeName(resource);
validateCompilationState();
oracle.add(resource);
rebuildCompilationState();
validateCompilationState();
Map<String, CompilationUnit> unitMap = state.getCompilationUnitMap();
validateSerializedTestUnit(resource, unitMap.get(resourceTypeName));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
for (CompilationUnit unit : unitMap.values()) {
Util.writeObjectToStream(outputStream, unitMap.get(unit.getTypeName()));
}
int numUnits = unitMap.size();
byte[] streamData = outputStream.toByteArray();
ByteArrayInputStream inputStream = new ByteArrayInputStream(streamData);
Map<String, CompilationUnit> newUnitMap =
new HashMap<String, CompilationUnit>();
for (int i = 0; i < numUnits; i++) {
CompilationUnit unit =
Util.readStreamAsObject(inputStream, CompilationUnit.class);
newUnitMap.put(unit.getTypeName(), unit);
}
// Validate all deserialized units against the original units
assertEquals(unitMap.size(), newUnitMap.size());
for (CompilationUnit unit : unitMap.values()) {
CompilationUnit newUnit = newUnitMap.get(unit.getTypeName());
validateSerializedUnit(unit, newUnit);
}
// Validate the SerializationTest resource with a specific test.
validateSerializedTestUnit(resource, newUnitMap.get(resourceTypeName));
}
public void testSourceOracleAdd() {
validateCompilationState();
int size = state.getCompilationUnits().size();
oracle.add(JavaResourceBase.FOO);
rebuildCompilationState();
assertEquals(size + 1, state.getCompilationUnits().size());
validateCompilationState();
}
public void testSourceOracleBasic() {
validateCompilationState();
}
public void testSourceOracleEmpty() {
oracle = new MockResourceOracle();
rebuildCompilationState();
validateCompilationState();
}
public void testSourceOracleRemove() {
validateCompilationState();
int size = state.getCompilationUnits().size();
oracle.remove(JavaResourceBase.MAP.getPath());
rebuildCompilationState();
assertEquals(size - 1, state.getCompilationUnits().size());
validateCompilationState();
}
public void testSourceOracleReplace() {
validateCompilationState();
int size = state.getCompilationUnits().size();
oracle.replace(new TweakedMockJavaResource(JavaResourceBase.OBJECT));
rebuildCompilationState();
assertEquals(size, state.getCompilationUnits().size());
validateCompilationState();
}
public void testSourceOracleReplaceWithSame() {
validateCompilationState();
int size = state.getCompilationUnits().size();
oracle.replace(JavaResourceBase.OBJECT);
rebuildCompilationState();
assertEquals(size, state.getCompilationUnits().size());
validateCompilationState();
}
private void testCaching(MockJavaResource... resources) {
Set<String> reusedTypes = new HashSet<String>();
for (MockJavaResource resource : resources) {
reusedTypes.add(resource.getTypeName());
}
testCachingOverMultipleRefreshes(resources, resources, reusedTypes);
}
/**
* Test caching logic for generated units during refreshes. Steps:
* <ol>
* <li>Verify that there were no generated units before</li>
* <li>Add 'initialSet' generatedUnits over a refresh cycle</li>
* <li>Add 'updatedSet' generatedUnits over a refresh cycle</li>
* <li>Add 'updatedSet' generatedUnits over the second refresh cycle</li>
* </ol>
*
* @param initialSet CompilationUnits that are generated the first time.
* @param updatedSet CompilationUnits that are generated the next time.
* @param reusedTypes Main type of the units that can be reused between the
* initialSet and updatedSet.
* @param numInvalidated Number of types invalidated from graveyardUnits.
*/
private void testCachingOverMultipleRefreshes(MockJavaResource[] initialSet,
MockJavaResource[] updatedSet, Set<String> reusedTypes) {
// Add 'initialSet' generatedUnits on the first cycle.
rebuildCompilationState();
assertEquals(oracle.getResources().size(),
state.getCompilationUnits().size());
addGeneratedUnits(initialSet);
Map<String, CompilationUnit> units1 =
new HashMap<String, CompilationUnit>(state.getCompilationUnitMap());
assertEquals(oracle.getResources().size() + initialSet.length,
units1.size());
assertUnitsChecked(units1.values());
// Add 'updatedSet' generatedUnits on the second cycle.
rebuildCompilationState();
assertEquals(oracle.getResources().size(),
state.getCompilationUnits().size());
addGeneratedUnits(updatedSet);
Map<String, CompilationUnit> units2 =
new HashMap<String, CompilationUnit>(state.getCompilationUnitMap());
assertEquals(oracle.getResources().size() + updatedSet.length,
units2.size());
assertUnitsChecked(units2.values());
// Validate that only 'reusedTypes' are reused.
for (MockJavaResource resource : updatedSet) {
String typeName = resource.getTypeName();
if (reusedTypes.contains(typeName)) {
assertSame(units1.get(typeName), units2.get(typeName));
} else {
assertNotSame(units1.get(typeName), units2.get(typeName));
}
}
// Add 'updatedSet' generatedUnits on the third cycle.
rebuildCompilationState();
assertEquals(oracle.getResources().size(),
state.getCompilationUnits().size());
addGeneratedUnits(updatedSet);
Map<String, CompilationUnit> units3 =
new HashMap<String, CompilationUnit>(state.getCompilationUnitMap());
assertEquals(oracle.getResources().size() + updatedSet.length,
units3.size());
assertUnitsChecked(units3.values());
// Validate that all generatedUnits are reused.
for (MockJavaResource resource : updatedSet) {
String typeName = resource.getTypeName();
assertSame(units2.get(typeName), units3.get(typeName));
}
}
private void validateSerializedTestUnit(MockJavaResource resource,
CompilationUnit unit) throws Exception {
assertNotNull(unit);
assertEquals(resource.getLastModified(), unit.getLastModified());
assertEquals(resource.getString(), unit.getSource());
// dependencies
Dependencies deps = unit.getDependencies();
assertObjectInDeps(deps.qualified, deps.simple, "java.lang.Object",
"Object");
assertObjectInDeps(deps.qualified, deps.simple, "java.lang.String",
"String");
assertObjectInDeps(deps.qualified, deps.simple, "test.SerializationTest",
"SerializationTest");
// method args lookup
MethodArgNamesLookup lookup = unit.getMethodArgs();
String methods[] = lookup.getMethods();
assertEquals(1, methods.length);
assertEquals(
"test.SerializationTest.methodArgsTest(ILjava/lang/Object;)Ljava/lang/String;",
methods[0]);
// JSNI methods
List<JsniMethod> jsniMethods = unit.getJsniMethods();
assertEquals(1, jsniMethods.size());
JsniMethod jsniMethod = jsniMethods.get(0);
String[] paramNames = jsniMethod.paramNames();
assertEquals(0, paramNames.length);
assertEquals("@test.SerializationTest::getTrue()", jsniMethod.name());
JsFunction jsniFunction = jsniMethod.function();
String origFunction = parseJs("function() { return true; }");
String newFunction = parseJs("function() " + jsniFunction.getBody().toSource());
assertEquals(origFunction, newFunction);
// Compiled classes
List<CompiledClass> compiledClasses =
Lists.create(unit.getCompiledClasses());
assertEquals(1, compiledClasses.size());
CompiledClass compiledClass = compiledClasses.get(0);
byte[] byteCode = compiledClass.getBytes();
assertNotNull(byteCode);
assertEquals("test", compiledClass.getPackageName());
assertEquals("test/SerializationTest", compiledClass.getInternalName());
assertSame(unit, compiledClass.getUnit());
}
private void assertObjectInDeps(Map<String, Ref> qualified,
Map<String, Ref> simple, String qualifiedName, String simpleName) {
Ref qualifiedRef = qualified.get(qualifiedName);
assertNotNull(qualifiedRef);
assertEquals(qualifiedName, qualifiedRef.getInternalName().replace("/", "."));
Ref simpleRef = simple.get(simpleName);
assertNotNull(simpleRef);
assertEquals(qualifiedName,simpleRef.getInternalName().replace("/", "."));
}
private void validateSerializedUnit(CompilationUnit originalUnit,
CompilationUnit newUnit) throws Exception {
// Compare the compiled classes
Map<String, CompiledClass> origMap = new HashMap<String, CompiledClass>();
for (CompiledClass cc : originalUnit.getCompiledClasses()) {
origMap.put(cc.getInternalName(), cc);
}
Map<String, CompiledClass> newMap = new HashMap<String, CompiledClass>();
for (CompiledClass cc : newUnit.getCompiledClasses()) {
newMap.put(cc.getInternalName(), cc);
}
assertEquals(origMap.size(), newMap.size());
for (String name : origMap.keySet()) {
assertEquals(name, newMap.get(name).getInternalName());
}
// Compare the dependencies
Map<String, Ref> origQualified = originalUnit.getDependencies().qualified;
Map<String, Ref> newQualified = newUnit.getDependencies().qualified;
for (String name : origQualified.keySet()) {
Ref origRef = origQualified.get(name);
Ref newRef = newQualified.get(name);
if (origRef == null) {
assertNull(newRef);
} else {
assertNotNull(newRef);
assertEquals(origRef.getSignatureHash(), newRef.getSignatureHash());
}
}
// Compare the source
assertEquals(originalUnit.getSource(), newUnit.getSource());
// Compare JSNI Methods
List<JsniMethod> origJsniMethods = originalUnit.getJsniMethods();
Map<String, JsniMethod> newJsniMethods = new HashMap<String, JsniMethod>();
for (JsniMethod jsniMethod : newUnit.getJsniMethods()) {
newJsniMethods.put(jsniMethod.name(), jsniMethod);
}
for (JsniMethod origMethod : origJsniMethods) {
JsniMethod newMethod = newJsniMethods.get(origMethod.name());
assertNotNull(newMethod);
assertEquals(origMethod.paramNames().length,
newMethod.paramNames().length);
for (int i = 0; i < origMethod.paramNames().length; i++) {
assertEquals(origMethod.paramNames()[i], newMethod.paramNames()[i]);
}
assertEquals(parseJs("function() " + origMethod.function().getBody()),
parseJs("function() " + newMethod.function().getBody()));
// Need to test deserialization of origMethod.function()?
}
}
/**
* Java resource that modifies its content with a terminating newline.
*/
public static class TweakedMockJavaResource extends MockJavaResource {
private MockJavaResource original;
public TweakedMockJavaResource(MockJavaResource original) {
super(original.getTypeName());
this.original = original;
}
@Override
public CharSequence getContent() {
return original.getContent() + "\n";
}
}
}