Author: amitman...@google.com Date: Wed Feb 4 14:50:42 2009 New Revision: 4627
Added: releases/1.6/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameFinderTest.java Modified: releases/1.6/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java releases/1.6/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java releases/1.6/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameComparatorTest.java releases/1.6/dev/core/test/com/google/gwt/dev/shell/GeneratedClassnameTest.java releases/1.6/user/build.xml releases/1.6/user/test/com/google/gwt/dev/jjs/test/CoverageTest.java releases/1.6/user/test/com/google/gwt/dev/shell/rewrite/client/EmmaClassLoadingTest.java Log: This patch expands Gwt's ability to successfully create classMappings, necessary when running Emma. The following types of classes can now be handled correctly: (a) A top level class that is not the main type in a java file (e.g., Test$1Foo produced from Bar.java) (b) A class that is nested multiple-levels deep (e.g., Test$Foo$1Bar) (c) An anonmyous class that extends a named local class (e.g., Test$1Foo) Also adds a test target to enable running all hosted mode tests in Emma mode. All tests pass except CoverageTest.java that fails because of a bug in javac in both OpenJDK and Sun's Java6. Patch by: amitmanjhi Review by: jat (desk review) Modified: releases/1.6/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java ============================================================================== --- releases/1.6/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java (original) +++ releases/1.6/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java Wed Feb 4 14:50:42 2009 @@ -15,11 +15,13 @@ */ package com.google.gwt.dev.javac; +import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.dev.asm.ClassReader; import com.google.gwt.dev.asm.Opcodes; import com.google.gwt.dev.asm.commons.EmptyVisitor; import com.google.gwt.dev.jdt.TypeRefVisitor; import com.google.gwt.dev.shell.CompilingClassLoader; +import com.google.gwt.dev.util.Util; import org.eclipse.jdt.core.compiler.CategorizedProblem; import org.eclipse.jdt.internal.compiler.ASTVisitor; @@ -30,6 +32,9 @@ import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope; import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -46,25 +51,129 @@ */ public abstract class CompilationUnit { - static class AnonymousClassVisitor extends EmptyVisitor { - /* - * array of classNames of inner clases that aren't synthetic classes. - */ - List<String> classNames = new ArrayList<String>(); + /** + * Encapsulates the functionality to find all nested classes of this class + * that have compiler-generated names. All class bytes are loaded from the + * disk and then analyzed using ASM. + */ + static class GeneratedClassnameFinder { + private static class AnonymousClassVisitor extends EmptyVisitor { + /* + * array of classNames of inner clases that aren't synthetic classes. + */ + List<String> classNames = new ArrayList<String>(); - public List<String> getInnerClassNames() { - return classNames; + public List<String> getInnerClassNames() { + return classNames; + } + + @Override + public void visitInnerClass(String name, String outerName, + String innerName, int access) { + if ((access & Opcodes.ACC_SYNTHETIC) == 0) { + classNames.add(name); + } + } } - @Override - public void visitInnerClass(String name, String outerName, - String innerName, int access) { - if ((access & Opcodes.ACC_SYNTHETIC) == 0) { - classNames.add(name); + private final List<String> classesToScan; + private final TreeLogger logger; + private final String mainClass; + private String mainUrlBase = null; + + GeneratedClassnameFinder(TreeLogger logger, String mainClass) { + assert mainClass != null; + this.mainClass = mainClass; + classesToScan = new ArrayList<String>(); + classesToScan.add(mainClass); + this.logger = logger; + } + + List<String> getClassNames() { + // using a list because presumably there will not be many generated + // classes + List<String> allGeneratedClasses = new ArrayList<String>(); + for (int i = 0; i < classesToScan.size(); i++) { + String lookupName = classesToScan.get(i); + byte classBytes[] = getClassBytes(lookupName); + if (classBytes == null) { + /* + * Weird case: javac might generate a name and reference the class in + * the bytecode but decide later that the class is unnecessary. In the + * bytecode, a null is passed for the class. + */ + continue; + } + + /* + * Add the class to the list only if it can be loaded to get around the + * javac weirdness issue where javac refers a class but does not + * generate it. + */ + if (CompilingClassLoader.isClassnameGenerated(lookupName) + && !allGeneratedClasses.contains(lookupName)) { + allGeneratedClasses.add(lookupName); + } + AnonymousClassVisitor cv = new AnonymousClassVisitor(); + new ClassReader(classBytes).accept(cv, 0); + List<String> innerClasses = cv.getInnerClassNames(); + for (String innerClass : innerClasses) { + // The innerClass has to be an inner class of the lookupName + if (!innerClass.startsWith(mainClass + "$")) { + continue; + } + /* + * TODO (amitmanjhi): consider making this a Set if necessary for + * performance + */ + // add the class to classes + if (!classesToScan.contains(innerClass)) { + classesToScan.add(innerClass); + } + } } + Collections.sort(allGeneratedClasses, new GeneratedClassnameComparator()); + return allGeneratedClasses; } - } + /* + * Load classBytes from disk. Check if the classBytes are loaded from the + * same location as the location of the mainClass. + */ + private byte[] getClassBytes(String slashedName) { + URL url = Thread.currentThread().getContextClassLoader().getResource( + slashedName + ".class"); + if (url == null) { + logger.log(TreeLogger.DEBUG, "Unable to find " + slashedName + + " on the classPath"); + return null; + } + String urlStr = url.toExternalForm(); + if (slashedName.equals(mainClass)) { + // initialize the mainUrlBase for later use. + mainUrlBase = urlStr.substring(0, urlStr.lastIndexOf('/')); + } else { + assert mainUrlBase != null; + if (!mainUrlBase.equals(urlStr.substring(0, urlStr.lastIndexOf('/')))) { + logger.log(TreeLogger.DEBUG, "Found " + slashedName + " at " + urlStr + + " The base location is different from that of " + mainUrlBase + + " Not loading"); + return null; + } + } + + // url != null, we found it on the class path. + try { + URLConnection conn = url.openConnection(); + return Util.readURLConnectionAsBytes(conn); + } catch (IOException ignored) { + logger.log(TreeLogger.DEBUG, "Unable to load " + urlStr + + ", in trying to load " + slashedName); + // Fall through. + } + return null; + } + } /** * Tracks the state of a compilation unit through the compile and recompile * process. @@ -167,26 +276,31 @@ private State state = State.FRESH; /* - * Check if the unit has one or more anonymous classes. 'javac' below refers - * to the compiler that was used to compile the java files on disk. Returns - * true if our heuristic for constructing the anonymous class mappings worked. + * Check if the unit has one or more classes with generated names. 'javac' + * below refers to the compiler that was used to compile the java files on + * disk. Returns true if our heuristic for constructing the anonymous class + * mappings worked. */ - public boolean constructAnonymousClassMappings(byte classBytes[]) { + public boolean constructAnonymousClassMappings(TreeLogger logger) { // map from the name in javac to the name in jdt anonymousClassMap = new HashMap<String, String>(); - List<String> javacClasses = getJavacClassNames(classBytes); - List<String> jdtClasses = getJdtClassNames(); - if (javacClasses.size() == jdtClasses.size()) { + for (String topLevelClass : getTopLevelClasses()) { + // Generate a mapping for each top-level class separately + List<String> javacClasses = new GeneratedClassnameFinder(logger, + topLevelClass).getClassNames(); + List<String> jdtClasses = getJdtClassNames(topLevelClass); + if (javacClasses.size() != jdtClasses.size()) { + anonymousClassMap = Collections.emptyMap(); + return false; + } int size = javacClasses.size(); for (int i = 0; i < size; i++) { if (!javacClasses.get(i).equals(jdtClasses.get(i))) { anonymousClassMap.put(javacClasses.get(i), jdtClasses.get(i)); } } - return true; } - anonymousClassMap = Collections.emptyMap(); - return false; + return true; } public boolean createdClassMapping() { @@ -383,30 +497,26 @@ } } - private List<String> getJavacClassNames(byte classBytes[]) { - AnonymousClassVisitor cv = new AnonymousClassVisitor(); - new ClassReader(classBytes).accept(cv, 0); - List<String> classNames = cv.getInnerClassNames(); - List<String> namesToRemove = new ArrayList<String>(); - for (String className : classNames) { - if (!CompilingClassLoader.isClassnameGenerated(className)) { - namesToRemove.add(className); + private List<String> getJdtClassNames(String topLevelClass) { + List<String> classNames = new ArrayList<String>(); + for (CompiledClass cc : getCompiledClasses()) { + if (isAnonymousClass(cc) + && cc.getBinaryName().startsWith(topLevelClass + "$")) { + classNames.add(cc.getBinaryName()); } } - classNames.removeAll(namesToRemove); Collections.sort(classNames, new GeneratedClassnameComparator()); return classNames; } - private List<String> getJdtClassNames() { - List<String> classNames = new ArrayList<String>(); + private List<String> getTopLevelClasses() { + List<String> topLevelClasses = new ArrayList<String>(); for (CompiledClass cc : getCompiledClasses()) { - if (isAnonymousClass(cc)) { - classNames.add(cc.getBinaryName()); + if (cc.getEnclosingClass() == null) { + topLevelClasses.add(cc.binaryName); } } - Collections.sort(classNames, new GeneratedClassnameComparator()); - return classNames; + return topLevelClasses; } /** Modified: releases/1.6/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java ============================================================================== --- releases/1.6/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java (original) +++ releases/1.6/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java Wed Feb 4 14:50:42 2009 @@ -361,7 +361,7 @@ */ private static byte[] javaScriptHostBytes; - private static final Pattern GENERATED_CLASSNAME_PATTERN = Pattern.compile(".+\\$\\d+(\\$.*)?"); + private static final Pattern GENERATED_CLASSNAME_PATTERN = Pattern.compile(".+\\$\\d.*"); static { for (Class<?> c : BRIDGE_CLASSES) { @@ -386,9 +386,10 @@ /** * Checks if the class names is generated. Accepts any classes whose names - * match .+$\d+($.*)? (handling named classes within anonymous classes). - * Checks if the class or any of its enclosing classes are anonymous or - * synthetic. + * match .+$\d.* (handling named classes within anonymous classes and + * multiple named classes of the same name in a class, but in different + * methods). Checks if the class or any of its enclosing classes are anonymous + * or synthetic. * <p> * If new compilers have different conventions for anonymous and synthetic * classes, this code needs to be updated. @@ -670,7 +671,7 @@ lookupClassName); CompilationUnit unit = (compiledClass == null) - ? getUnitForClassName(className) : compiledClass.getUnit(); + ? getUnitForClassName(lookupClassName) : compiledClass.getUnit(); if (emmaAvailable) { /* * build the map for anonymous classes. Do so only if unit has anonymous @@ -682,20 +683,12 @@ if (unit != null && !unit.isSuperSource() && unit.hasAnonymousClasses() && jsniMethods != null && jsniMethods.size() > 0 && !unit.createdClassMapping()) { - String mainLookupClassName = unit.getTypeName().replace('.', '/'); - byte mainClassBytes[] = emmaStrategy.getEmmaClassBytes(null, - mainLookupClassName, 0); - if (mainClassBytes != null) { - if (!unit.constructAnonymousClassMappings(mainClassBytes)) { - logger.log(TreeLogger.ERROR, - "Our heuristic for mapping anonymous classes between compilers " - + "failed. Unsafe to continue because the wrong jsni code " - + "could end up running. className = " + className); - return null; - } - } else { - logger.log(TreeLogger.ERROR, "main class bytes is null for unit = " - + unit + ", mainLookupClassName = " + mainLookupClassName); + if (!unit.constructAnonymousClassMappings(logger)) { + logger.log(TreeLogger.ERROR, + "Our heuristic for mapping anonymous classes between compilers " + + "failed. Unsafe to continue because the wrong jsni code " + + "could end up running. className = " + className); + return null; } } } @@ -717,7 +710,8 @@ * find it on disk. Typically this is a synthetic class added by the * compiler. */ - if (typeHasCompilationUnit(className) && isClassnameGenerated(className)) { + if (typeHasCompilationUnit(lookupClassName) + && isClassnameGenerated(className)) { /* * modification time = 0 ensures that whatever is on the disk is always * loaded. @@ -758,17 +752,19 @@ /** * Returns the compilationUnit corresponding to the className. For nested * classes, the unit corresponding to the top level type is returned. + * + * Since a file might have several top-level types, search using classFileMap. */ private CompilationUnit getUnitForClassName(String className) { String mainTypeName = className; int index = mainTypeName.length(); - CompilationUnit unit = null; - while (unit == null && index != -1) { + CompiledClass cc = null; + while (cc == null && index != -1) { mainTypeName = mainTypeName.substring(0, index); - unit = compilationState.getCompilationUnitMap().get(mainTypeName); + cc = compilationState.getClassFileMap().get(mainTypeName); index = mainTypeName.lastIndexOf('$'); } - return unit; + return cc == null ? null : cc.getUnit(); } private void injectJsniMethods(CompilationUnit unit) { Modified: releases/1.6/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameComparatorTest.java ============================================================================== --- releases/1.6/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameComparatorTest.java (original) +++ releases/1.6/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameComparatorTest.java Wed Feb 4 14:50:42 2009 @@ -1,3 +1,18 @@ +/* + * 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 junit.framework.TestCase; @@ -38,8 +53,21 @@ } public void testMixedNames() { - String original[] = {"Foo", "Foo$1", "Foo$1xyz", "Foo$2", "Foo$xyz"}; - String expected[] = {"Foo", "Foo$1", "Foo$2", "Foo$1xyz", "Foo$xyz"}; + String original[] = { + "Foo", "Foo$1", "Foo$1Bar", "Foo$2Bar", "Foo$2", "Foo$xyz"}; + String expected[] = { + "Foo", "Foo$1", "Foo$2", "Foo$1Bar", "Foo$2Bar", "Foo$xyz"}; + Arrays.sort(original, new GeneratedClassnameComparator()); + for (int i = 0; i < original.length; i++) { + assertEquals("index = " + i, expected[i], original[i]); + } + } + + public void testMultipleToplevelClasses() { + String original[] = { + "Foo$1", "Foo$2", "Bar$1", "Bar$3", "Foo$2$1", "Bar$2$1"}; + String expected[] = { + "Bar$1", "Bar$3", "Foo$1", "Foo$2", "Bar$2$1", "Foo$2$1"}; Arrays.sort(original, new GeneratedClassnameComparator()); for (int i = 0; i < original.length; i++) { assertEquals("index = " + i, expected[i], original[i]); Added: releases/1.6/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameFinderTest.java ============================================================================== --- (empty file) +++ releases/1.6/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameFinderTest.java Wed Feb 4 14:50:42 2009 @@ -0,0 +1,371 @@ +/* + * 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.core.ext.TreeLogger; +import com.google.gwt.dev.javac.CompilationUnit.GeneratedClassnameFinder; +import com.google.gwt.dev.util.log.PrintWriterTreeLogger; + +import junit.framework.TestCase; + +import java.util.List; + +/** + * Test-cases to check that we indeed obtain the correct list of nested types + * with generated classNames by examining bytecodes using ASM. + * + */ +public class GeneratedClassnameFinderTest extends TestCase { + enum EnumClass { + A, B, C, + } + + static class MainClass { + static class NestedClass { + void foo() { + TestInterface c = new TestInterface() { + public void foo() { + } + }; + EnumClass et = EnumClass.A; + switch (et) { + case A: + break; + } + TestInterface d = new TestInterface() { + public void foo() { + } + }; + } + } + + void foo() { + TestInterface a = new TestInterface() { + public void foo() { + } + }; + EnumClass et = EnumClass.A; + switch (et) { + case A: + break; + } + TestInterface b = new TestInterface() { + public void foo() { + } + }; + } + } + interface TestInterface { + void foo(); + } + + static final TreeLogger logger = new PrintWriterTreeLogger(); + + public void test() { + String mainClassName = this.getClass().getName().replace('.', '/'); + assertEquals( + 4, + new GeneratedClassnameFinder(logger, mainClassName).getClassNames().size()); + assertEquals(0, new GeneratedClassnameFinder(logger, mainClassName + + "$EnumClass").getClassNames().size()); + assertEquals(0, new GeneratedClassnameFinder(logger, mainClassName + + "$TestInterface").getClassNames().size()); + assertEquals(4, new GeneratedClassnameFinder(logger, mainClassName + + "$MainClass").getClassNames().size()); + assertEquals(2, new GeneratedClassnameFinder(logger, mainClassName + + "$MainClass$NestedClass").getClassNames().size()); + } + + public void testAnonymous() { + assertEquals(1, new AnonymousTester().getGeneratedClasses().size()); + } + + public void testEnum() { + assertEquals(0, new EnumTester().getGeneratedClasses().size()); + } + + public void testJavacWeirdness() { + List<String> classNames = new JavacWeirdnessTester().getGeneratedClasses(); + assertEquals(3, classNames.size()); + assertTrue(classNames.get(0) + " should not contain Foo", + classNames.get(0).indexOf("Foo") == -1); + assertTrue(classNames.get(1) + " should contain Foo", + classNames.get(1).indexOf("Foo") != -1); + assertTrue(classNames.get(2) + " should contain Foo", + classNames.get(2).indexOf("Foo") != -1); + } + + public void testNamedLocal() { + assertEquals(2, new NamedLocalTester().getGeneratedClasses().size()); + } + + public void testNested() { + assertEquals(2, new NestedTester().getGeneratedClasses().size()); + } + + public void testStatic() { + assertEquals(0, new StaticTester().getGeneratedClasses().size()); + } + + public void testTopLevel() { + assertEquals(1, new TopLevelTester().getGeneratedClasses().size()); + } + +} + +/** + * For testing a class containing an anonymous inner class. + */ +class AnonymousTester { + interface TestInterface { + void foo(); + } + + void foo() { + TestInterface a = new TestInterface() { + public void foo() { + } + }; + a.foo(); + } + + List<String> getGeneratedClasses() { + return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger, + this.getClass().getName().replace('.', '/'))).getClassNames(); + } +} + +/** + * For testing a class with an Enum (for which javac generates a synthetic + * class). + */ +class EnumTester { + enum EnumClass { + A, B, C, + } + + void foo() { + EnumClass et = EnumClass.A; + switch (et) { + case A: + break; + } + } + + List<String> getGeneratedClasses() { + return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger, + this.getClass().getName().replace('.', '/'))).getClassNames(); + } +} + +/** + * Javac generates weird code for the following class. It passes a synthetic + * class ...Tester$1 as a first parameter to constructors of Fuji and Granny. + * Normally, it generates the synthetic class, but in this case, it decides not + * to generate the class. However, the bytecode still has reference to the + * synthetic class -- it just passes null for the synthetic class. + * + * This code also tests for an anonymous class extending a named local class. + */ +class JavacWeirdnessTester { + private abstract static class Apple implements Fruit { + } + + private static interface Fruit { + } + + private static class Fuji extends Apple { + } + + private static class Granny extends Apple { + } + + private static volatile boolean TRUE = true; + + List<String> getGeneratedClasses() { + return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger, + this.getClass().getName().replace('.', '/'))).getClassNames(); + } + + private void assertEquals(Object a, Object b) { + } + + private void testArrayStore() { + Apple[] apple = TRUE ? new Granny[3] : new Apple[3]; + Apple g = TRUE ? (Apple) new Granny() : (Apple) new Fuji(); + Apple a = apple[0] = g; + assertEquals(g, a); + } + + private void testDeadTypes() { + if (false) { + new Object() { + }.toString(); + + class Foo { + void a() { + } + } + new Foo().a(); + } + } + + private void testLocalClasses() { + class Foo { + public Foo(int j) { + assertEquals(1, j); + }; + } + final int i; + new Foo(i = 1) { + { + assertEquals(1, i); + } + }; + assertEquals(1, i); + } + + private void testReturnStatementInCtor() { + class Foo { + int i; + + Foo(int i) { + this.i = i; + if (i == 0) { + return; + } else if (i == 1) { + return; + } + return; + } + } + assertEquals(new Foo(0).i, 0); + } +} + +/** + * For testing a class with a generated name like $1Foo. + */ +class NamedLocalTester { + void foo1() { + if (false) { + class Foo { + void foo() { + } + } + new Foo().foo(); + } + } + + void foo2() { + class Foo { + void foo() { + } + } + new Foo().foo(); + } + + void foo3() { + class Foo { + void foo() { + } + } + new Foo().foo(); + } + + List<String> getGeneratedClasses() { + return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger, + this.getClass().getName().replace('.', '/'))).getClassNames(); + } +} + +/** + * For testing that nested classes are examined recursively for classes with + * generated names. + */ +class NestedTester { + class MainClass { + class NestedClass { + void foo() { + class Foo { + void bar() { + } + } + new Foo().bar(); + } + } + + void foo() { + class Foo { + void bar() { + } + } + new Foo().bar(); + } + } + + List<String> getGeneratedClasses() { + return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger, + this.getClass().getName().replace('.', '/'))).getClassNames(); + } +} + +/** + * For testing classes with private static members (javac generates a synthetic + * class here but the jdt does not). + */ +class StaticTester { + private abstract static class Apple implements Fruit { + } + + private static interface Fruit { + void bar(); + } + + private static class Fuji extends Apple { + public void bar() { + } + } + + private static class Granny extends Apple { + public void bar() { + } + } + + List<String> getGeneratedClasses() { + return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger, + this.getClass().getName().replace('.', '/'))).getClassNames(); + } + +} + +/** + * For testing that a class with a generated name inside another top-level class + * is found. + */ +class TopLevelTester { + public void foo() { + GeneratedClassnameFinderTest.TestInterface a = new GeneratedClassnameFinderTest.TestInterface() { + public void foo() { + } + }; + } + + List<String> getGeneratedClasses() { + return (new GeneratedClassnameFinder(GeneratedClassnameFinderTest.logger, + this.getClass().getName().replace('.', '/'))).getClassNames(); + } +} Modified: releases/1.6/dev/core/test/com/google/gwt/dev/shell/GeneratedClassnameTest.java ============================================================================== --- releases/1.6/dev/core/test/com/google/gwt/dev/shell/GeneratedClassnameTest.java (original) +++ releases/1.6/dev/core/test/com/google/gwt/dev/shell/GeneratedClassnameTest.java Wed Feb 4 14:50:42 2009 @@ -11,8 +11,8 @@ public void testGeneratedClassnames() { String namesToAccept[] = { "Test$1", "Test$10", "Test$Foo$1", "Test$1$Foo", "Test$10$Foo", - "$$345", "Test$1$Foo$"}; - String namesToReject[] = {"Test1", "$345", "Test$2Foo", "Test$Foo$1Bar"}; + "$$345", "Test$1$Foo$", "Test$1Foo", "Test$2Foo", "Test$Foo$1Bar"}; + String namesToReject[] = {"Test1", "TestFoo", "Test$Foo$Bar", "$345"}; for (String name : namesToAccept) { assertTrue("className = " + name + " should have been accepted", Modified: releases/1.6/user/build.xml ============================================================================== --- releases/1.6/user/build.xml (original) +++ releases/1.6/user/build.xml Wed Feb 4 14:50:42 2009 @@ -12,6 +12,11 @@ <fileset id="default.emma.tests" dir="${javac.junit.out}" includes="**/EmmaClassLoadingTest.class" /> + + <fileset id="default.hosted.emma.tests" dir="${javac.junit.out}" + excludes="**/*jjs.test.CoverageTest.class" includes="**/*Test.class" /> + <!-- everything succeeds except CoverageTest.java. It fails due to a javac bug in sun/OpenJDK's Java. See the file contents for details --> + <!-- Default web mode test cases --> @@ -96,6 +101,15 @@ </gwt.junit> </target> + <target name="test.hosted.emma" depends="compile, compile.tests" description="Run all hosted-mode tests in emma mode."> + <gwt.junit test.args="${test.args}" test.out="${junit.out}/${build.host.platform}-hosted-mode" test.cases="default.hosted.emma.tests" > + <extraclasspaths> + <pathelement location="${gwt.build}/out/dev/core/bin-test" /> + <pathelement location="${gwt.tools.redist}/emma/emma.jar" /> + </extraclasspaths> + </gwt.junit> + </target> + <target name="test.hosted" depends="compile, compile.tests" description="Run only hosted-mode tests for this project."> <gwt.junit test.args="${test.args}" test.out="${junit.out}/${build.host.platform}-hosted-mode" test.cases="default.hosted.tests" > <extraclasspaths> @@ -107,7 +121,7 @@ <pathelement location="${gwt.build}/out/dev/core/bin-test" /> <pathelement location="${gwt.tools.redist}/emma/emma.jar" /> </extraclasspaths> - </gwt.junit> + </gwt.junit> </target> <target name="test.web" depends="compile, compile.tests" description="Run only web-mode tests for this project."> Modified: releases/1.6/user/test/com/google/gwt/dev/jjs/test/CoverageTest.java ============================================================================== --- releases/1.6/user/test/com/google/gwt/dev/jjs/test/CoverageTest.java (original) +++ releases/1.6/user/test/com/google/gwt/dev/jjs/test/CoverageTest.java Wed Feb 4 14:50:42 2009 @@ -95,6 +95,12 @@ new InnerSub().new InnerSubSub().fda(); new SecondMain().new FunkyInner(); + /* + * The statement below causes a javac bug in openJdk and sun's java 6. It + * produces incorrect bytecode that fails with a java.lang.VerifyError -- + * see Google's internal issue 1628473. This is likely to be an hindrance + * if and when GWT attempts to read bytecode directly. + */ new NamedLocal().new NamedLocalSub().foo(); } Modified: releases/1.6/user/test/com/google/gwt/dev/shell/rewrite/client/EmmaClassLoadingTest.java ============================================================================== --- releases/1.6/user/test/com/google/gwt/dev/shell/rewrite/client/EmmaClassLoadingTest.java (original) +++ releases/1.6/user/test/com/google/gwt/dev/shell/rewrite/client/EmmaClassLoadingTest.java Wed Feb 4 14:50:42 2009 @@ -16,6 +16,9 @@ package com.google.gwt.dev.shell.rewrite.client; import com.google.gwt.junit.client.GWTTestCase; +import com.google.gwt.user.client.Timer; + +import junit.framework.TestCase; /** * Test-case to check if the jsni blocks are mapped correctly between the @@ -31,7 +34,7 @@ */ public class EmmaClassLoadingTest extends GWTTestCase { - enum EnumTest { + enum EnumClass { A, B, C, } @@ -40,7 +43,7 @@ } private static String messages[] = { - "a foo", "b foo", "enum A", "d foo", "e foo"}; + "1a foo", "1b foo", "1enum A", "1d foo", "1e foo"}; private static int logCount = 0; @@ -56,14 +59,14 @@ public void test1() { TestInterface a = new TestInterface() { public void foo() { - log("a foo"); + log("1a foo"); } }; a.foo(); TestInterface b = new TestInterface() { public native void foo() /*-{ - @com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("b foo"); + @com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("1b foo"); }-*/; }; b.foo(); @@ -75,10 +78,10 @@ }-*/; }; } - EnumTest et = EnumTest.A; + EnumClass et = EnumClass.A; switch (et) { case A: - log("enum A"); + log("1enum A"); break; case B: log("ANY_FOO_2"); @@ -90,7 +93,7 @@ TestInterface d = new TestInterface() { public native void foo() /*-{ - @com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("d foo"); + @com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("1d foo"); }-*/; }; d.foo(); @@ -103,8 +106,193 @@ */ TestInterface e = new TestInterface() { public native void foo() /*-{ - @com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("e foo"); + @com.google.gwt.dev.shell.rewrite.client.EmmaClassLoadingTest::log(Ljava/lang/String;)("1e foo"); }-*/; }; - } + } + + public void test2() { + SecondTopLevelClass second = new SecondTopLevelClass(); + SecondTopLevelClass.InnerClass.test(); + } + + public void test3() { + ThirdTopLevelClass third = new ThirdTopLevelClass(); + third.test(); + } + + public void test4() { + FourthTopLevelClass fourth = new FourthTopLevelClass(); + fourth.test(); + } + +} + +/** + * Check that the algorithm correctly maps named inner classes. In this example, + * jdt generates $1Foo and $2Foo whereas javac generates $2Foo and $3Foo. + * + */ +class FourthTopLevelClass extends TestCase { + private static String messages[] = {"4a foo"}; + + private static int logCount = 0; + + private static void log(String msg) { + assertEquals(messages[logCount++], msg); + } + + void test() { + test1(); + test2(); + }; + + private void test1() { + if (false) { + class Foo { + public native void foo() /*-{ + @com.google.gwt.dev.shell.rewrite.client.FourthTopLevelClass::log(Ljava/lang/String;)("ANY_FOO"); + }-*/; + } + new Foo().foo(); + } + } + + private void test2() { + class Foo { + public native void foo() /*-{ + @com.google.gwt.dev.shell.rewrite.client.FourthTopLevelClass::log(Ljava/lang/String;)("4a foo"); + }-*/; + } + new Foo().foo(); + } + + /* + * Added a test3() method so that when the test fails, it fails quickly. + * Instead of timing out, the AssertEquals fails + */ + private void test3() { + class Foo { + public native void foo() /*-{ + @com.google.gwt.dev.shell.rewrite.client.FourthTopLevelClass::log(Ljava/lang/String;)("4b foo"); + }-*/; + } + new Foo().foo(); + } +} + +/** + * Check if GWT is able to correctly compile cases when there are multiple + * top-level classes and when there is a need to traverse inner classes. This + * class's test method simply mirrors the test methods of EmmaClassLoadingTest. + * + */ +class SecondTopLevelClass extends TestCase { + + static class InnerClass { + /** + * Test that mapping is constructed for something which is not in the main + * unit as well. + */ + static void test() { + EmmaClassLoadingTest.TestInterface a = new EmmaClassLoadingTest.TestInterface() { + public void foo() { + log("2a foo"); + } + }; + a.foo(); + + EmmaClassLoadingTest.TestInterface b = new EmmaClassLoadingTest.TestInterface() { + public native void foo() /*-{ + @com.google.gwt.dev.shell.rewrite.client.SecondTopLevelClass::log(Ljava/lang/String;)("2b foo"); + }-*/; + }; + b.foo(); + + if (false) { + EmmaClassLoadingTest.TestInterface c = new EmmaClassLoadingTest.TestInterface() { + public native void foo() /*-{ + @com.google.gwt.dev.shell.rewrite.client.SecondTopLevelClass::log(Ljava/lang/String;)("ANY_FOO_1"); + }-*/; + }; + } + EmmaClassLoadingTest.EnumClass et = EmmaClassLoadingTest.EnumClass.A; + switch (et) { + case A: + log("2enum A"); + break; + case B: + log("ANY_FOO_2"); + break; + case C: + log("ANY_FOO_3"); + break; + } + + EmmaClassLoadingTest.TestInterface d = new EmmaClassLoadingTest.TestInterface() { + public native void foo() /*-{ + @com.google.gwt.dev.shell.rewrite.client.SecondTopLevelClass::log(Ljava/lang/String;)("2d foo"); + }-*/; + }; + d.foo(); + + /* + * jdt generates $1 (a), $2 (b), $3 (d), $4 (e). javac generates $1 (a), + * $2 (b), $3 (c), $4 (d), $5 (e), $6 (synthetic). Added e so that the + * test fails quickly. Otherwise, it had to wait for a time-out (in + * looking through jdt generated code for non-existent jsni methods of $4) + * to fail. + */ + EmmaClassLoadingTest.TestInterface e = new EmmaClassLoadingTest.TestInterface() { + public native void foo() /*-{ + @com.google.gwt.dev.shell.rewrite.client.SecondTopLevelClass::log(Ljava/lang/String;)("2e foo"); + }-*/; + }; + } + } + + private static String messages[] = { + "2a foo", "2b foo", "2enum A", "2d foo", "2e foo"}; + + private static int logCount = 0; + + private static void log(String msg) { + assertEquals(messages[logCount++], msg); + } +} + +/** + * Check that the mapping algorithm is not confused by the presence of other + * inner classes. + */ +class ThirdTopLevelClass extends TestCase { + + private static String messages[] = {"3a foo"}; + + private static int logCount = 0; + + private static void log(String msg) { + assertEquals(messages[logCount++], msg); + } + + void test() { + Timer t1 = new Timer() { + @Override + public void run() { + } + }; + + EmmaClassLoadingTest.TestInterface a = new EmmaClassLoadingTest.TestInterface() { + public native void foo() /*-{ + @com.google.gwt.dev.shell.rewrite.client.ThirdTopLevelClass::log(Ljava/lang/String;)("3a foo"); + }-*/; + }; + a.foo(); + + Timer t2 = new Timer() { + @Override + public void run() { + } + }; + } } --~--~---------~--~----~------------~-------~--~----~ http://groups.google.com/group/Google-Web-Toolkit-Contributors -~----------~----~----~----~------~----~------~--~---