Overhauls the generated code that is required by GWTTestCase to function.
Benefits are:
- Better stack traces
- Reduces generated code
- Simplifies the flow
Previously, each module following classes was generated:
1. GwtRunnerImplXYZ (e.g. GWTRunnerImplSafari) extends GWTRunner adds a method
that can instantiate a test by its name. Also adds getUserAgentProperty method
which requires a new class to be generated for each permutation.
2. __SomeTestClassName_unitTestImpl (__EditorTest_unitTestImpl) that extends user
test class and add a method that can call a test method by its name.
The switching in these generated methods were based on a long chain if else java
statements that are making string comparison.
There is some potential performance impact but more importantly this was causing
stack traces that was confusing as test methods can be potentially inlined and disappear
from stack trace where the generated class name was showing up instead of the real test
class name.
This alternative solution uses a different approach for generating the necessary code
so that methods will not be inlined and real test class will not be replaced.
Following is new set of class generated:
1. GwtRunnerProxyImpl implements GWTRunnerProxy and adds createTestAccessor method
that returns a js object that provides a function map for calling constructor and
test methods indexed by name.
2. GwtRunnerProxyImplXyz (e.g. GwtRunnerProxyImplSafari) that extends generated
GwtRunnerProxyImpl and implements getUserAgentPropery for each permutation.
As a side benefit it looks this approach also provides slight performance improvements
compared to previous one.
Change-Id: Id3f9dbb2dbec9bc89cd415e4d3189e63c8e77b73
Review-Link: https://gwt-review.googlesource.com/#/c/1960/
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@11531 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/junit/JUnit.gwt.xml b/user/src/com/google/gwt/junit/JUnit.gwt.xml
index 0e30089..cfc2a90 100644
--- a/user/src/com/google/gwt/junit/JUnit.gwt.xml
+++ b/user/src/com/google/gwt/junit/JUnit.gwt.xml
@@ -24,12 +24,8 @@
<source path="client"/>
- <generate-with class="com.google.gwt.junit.rebind.GWTRunnerGenerator">
- <when-type-is class="com.google.gwt.junit.client.impl.GWTRunner"/>
- </generate-with>
-
- <generate-with class="com.google.gwt.junit.rebind.JUnitTestCaseStubGenerator">
- <when-type-assignable class="com.google.gwt.junit.client.GWTTestCase"/>
+ <generate-with class="com.google.gwt.junit.rebind.GWTRunnerProxyGenerator">
+ <when-type-is class="com.google.gwt.junit.client.impl.GWTRunnerProxy"/>
</generate-with>
<!-- We want to provide consistent stack traces across all browsers. -->
diff --git a/user/src/com/google/gwt/junit/client/impl/GWTRunnerProxy.java b/user/src/com/google/gwt/junit/client/impl/GWTRunnerProxy.java
new file mode 100644
index 0000000..942fed7
--- /dev/null
+++ b/user/src/com/google/gwt/junit/client/impl/GWTRunnerProxy.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2013 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.junit.client.impl;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.SingleJsoImpl;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * A proxy generated by code generator to provide access to user agent property and reflective
+ * capabilities for GWTTestCase.
+ */
+public interface GWTRunnerProxy {
+
+ /**
+ * A helper to provide create/execute functionality over GWTTestCase using names.
+ */
+ @SingleJsoImpl(JsniTestAccessor.class)
+ interface TestAccessor {
+ GWTTestCase newInstance(String className) throws Throwable;
+ Object invoke(GWTTestCase object, String className, String methodName) throws Throwable;
+ }
+
+ /**
+ * Creates a new {@link TestAccessor} for the module under test.
+ */
+ TestAccessor createTestAccessor();
+
+ /**
+ * Return the user agent property.
+ */
+ String getUserAgentProperty();
+
+
+ /**
+ * A JSNI + codegen based {@link TestAccessor} implementation.
+ */
+ class JsniTestAccessor extends JavaScriptObject implements TestAccessor {
+
+ protected JsniTestAccessor() { /* empty */}
+
+ @Override
+ public final GWTTestCase newInstance(String className) {
+ return (GWTTestCase) invoke(null, className, "new");
+ }
+
+ @Override
+ public final native Object invoke(GWTTestCase o, String className, String methodName) /*-{
+ return this[className][methodName](o);
+ }-*/;
+ }
+}
diff --git a/user/src/com/google/gwt/junit/rebind/GWTRunnerGenerator.java b/user/src/com/google/gwt/junit/rebind/GWTRunnerGenerator.java
deleted file mode 100644
index 9b20780..0000000
--- a/user/src/com/google/gwt/junit/rebind/GWTRunnerGenerator.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * 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.junit.rebind;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.core.ext.BadPropertyValueException;
-import com.google.gwt.core.ext.ConfigurationProperty;
-import com.google.gwt.core.ext.Generator;
-import com.google.gwt.core.ext.GeneratorContext;
-import com.google.gwt.core.ext.SelectionProperty;
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.core.ext.typeinfo.JClassType;
-import com.google.gwt.core.ext.typeinfo.NotFoundException;
-import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.junit.client.GWTTestCase;
-import com.google.gwt.junit.client.GWTTestCase.TestModuleInfo;
-import com.google.gwt.junit.client.impl.GWTRunner;
-import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;
-import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
-import com.google.gwt.user.rebind.SourceWriter;
-
-import java.io.PrintWriter;
-import java.util.Set;
-import java.util.TreeSet;
-
-/**
- * This class generates a stub class for classes that derive from GWTTestCase.
- * This stub class provides the necessary bridge between our Hosted or Hybrid
- * mode classes and the JUnit system.
- */
-public class GWTRunnerGenerator extends Generator {
-
- private static final String GWT_RUNNER_NAME = GWTRunner.class.getName();
-
- private static String getPackagePrefix(JClassType classType) {
- String name = classType.getPackage().getName();
- return (name.length() == 0) ? name : (name + '.');
- }
-
- /**
- * Create a new type that satisfies the rebind request.
- */
- @Override
- public String generate(TreeLogger logger, GeneratorContext context,
- String typeName) throws UnableToCompleteException {
-
- if (!GWT_RUNNER_NAME.equals(typeName)) {
- logger.log(TreeLogger.ERROR, "This generator may only be used with "
- + GWT_RUNNER_NAME, null);
- throw new UnableToCompleteException();
- }
-
- JClassType requestedClass;
- try {
- requestedClass = context.getTypeOracle().getType(typeName);
- } catch (NotFoundException e) {
- logger.log(
- TreeLogger.ERROR,
- "Could not find type '"
- + typeName
- + "'; please see the log, as this usually indicates a previous error ",
- e);
- throw new UnableToCompleteException();
- }
-
- String moduleName;
- try {
- ConfigurationProperty prop = context.getPropertyOracle().getConfigurationProperty(
- "junit.moduleName");
- moduleName = prop.getValues().get(0);
- } catch (BadPropertyValueException e) {
- logger.log(TreeLogger.ERROR,
- "Could not resolve junit.moduleName property", e);
- throw new UnableToCompleteException();
- }
-
- String userAgent;
- try {
- SelectionProperty prop = context.getPropertyOracle().getSelectionProperty(
- logger, "user.agent");
- userAgent = prop.getCurrentValue();
- } catch (BadPropertyValueException e) {
- logger.log(TreeLogger.ERROR, "Could not resolve user.agent property", e);
- throw new UnableToCompleteException();
- }
-
- // Get the stub class name, and see if its source file exists.
- //
- String generatedClass = requestedClass.getName().replace('.', '_') + "Impl"
- + userAgent;
- String packageName = requestedClass.getPackage().getName();
- String qualifiedStubClassName = packageName + "." + generatedClass;
-
- SourceWriter sourceWriter = getSourceWriter(logger, context, packageName,
- generatedClass, GWT_RUNNER_NAME);
-
- if (sourceWriter != null) {
- // Check the global set of active tests for this module.
- TestModuleInfo moduleInfo = GWTTestCase.getTestsForModule(moduleName);
- Set<TestInfo> moduleTests = (moduleInfo == null) ? null : moduleInfo.getTests();
- Set<String> testClasses;
- if (moduleTests == null || moduleTests.isEmpty()) {
- // Fall back to pulling in all types in the module.
- JClassType[] allTestTypes = getAllPossibleTestTypes(context.getTypeOracle());
- testClasses = getTestTypesForModule(logger, moduleName, allTestTypes);
- } else {
- // Must use sorted set to prevent nondeterminism.
- testClasses = new TreeSet<String>();
- for (TestInfo testInfo : moduleTests) {
- testClasses.add(testInfo.getTestClass());
- }
- }
- writeCreateNewTestCaseMethod(testClasses, sourceWriter);
- writeGetUserAgentPropertyMethod(userAgent, sourceWriter);
- sourceWriter.commit(logger);
- }
-
- return qualifiedStubClassName;
- }
-
- private JClassType[] getAllPossibleTestTypes(TypeOracle typeOracle) {
- JClassType gwtTestType = typeOracle.findType(GWTTestCase.class.getName());
- if (gwtTestType != null) {
- return gwtTestType.getSubtypes();
- } else {
- return new JClassType[0];
- }
- }
-
- private SourceWriter getSourceWriter(TreeLogger logger, GeneratorContext ctx,
- String packageName, String className, String superclassName) {
- PrintWriter printWriter = ctx.tryCreate(logger, packageName, className);
- if (printWriter == null) {
- return null;
- }
-
- ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(
- packageName, className);
- composerFactory.setSuperclass(superclassName);
- composerFactory.addImport(GWTTestCase.class.getName());
- composerFactory.addImport(GWT.class.getName());
- return composerFactory.createSourceWriter(ctx, printWriter);
- }
-
- private Set<String> getTestTypesForModule(TreeLogger logger,
- String moduleName, JClassType[] allTestTypes) {
- // Must use sorted set to prevent nondeterminism.
- Set<String> testClasses = new TreeSet<String>();
- for (JClassType classType : allTestTypes) {
- if (!classType.isPublic() || classType.isAbstract()
- || !classType.isDefaultInstantiable()) {
- continue;
- }
-
- String className = getPackagePrefix(classType)
- + classType.getName().replace('.', '$');
-
- try {
- Class<?> testClass = Class.forName(className);
- GWTTestCase instantiated = (GWTTestCase) testClass.newInstance();
- if (!moduleName.equals(instantiated.getModuleName())) {
- continue;
- }
- } catch (Throwable e) {
- logger.log(
- TreeLogger.INFO,
- "Error determining if test class '"
- + className
- + "' is a part of the current module; skipping; expect subsequent errors if this test class is run",
- e);
- continue;
- }
- testClasses.add(classType.getQualifiedSourceName());
- }
- return testClasses;
- }
-
- private void writeCreateNewTestCaseMethod(Set<String> testClasses,
- SourceWriter sw) {
- sw.println();
- sw.println("protected final GWTTestCase createNewTestCase(String testClass) {");
- sw.indent();
- boolean isFirst = true;
- for (String className : testClasses) {
- if (isFirst) {
- isFirst = false;
- } else {
- sw.print("else ");
- }
-
- sw.println("if (testClass.equals(\"" + className + "\")) {");
- sw.indentln("return GWT.create(" + className + ".class);");
- sw.println("}");
- }
- sw.println("return null;");
- sw.outdent();
- sw.println("}");
- }
-
- private void writeGetUserAgentPropertyMethod(String userAgent, SourceWriter sw) {
- sw.println();
- sw.println("protected final String getUserAgentProperty() {");
- sw.indent();
- sw.println("return \"" + userAgent + "\";");
- sw.outdent();
- sw.println("}");
- }
-}
diff --git a/user/src/com/google/gwt/junit/rebind/GWTRunnerProxyGenerator.java b/user/src/com/google/gwt/junit/rebind/GWTRunnerProxyGenerator.java
new file mode 100644
index 0000000..0739f5a
--- /dev/null
+++ b/user/src/com/google/gwt/junit/rebind/GWTRunnerProxyGenerator.java
@@ -0,0 +1,286 @@
+/*
+ * 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.junit.rebind;
+
+import static com.google.gwt.junit.rebind.JUnitTestCaseStubGenerator.isJUnitTestMethod;
+
+import com.google.gwt.core.ext.BadPropertyValueException;
+import com.google.gwt.core.ext.ConfigurationProperty;
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.SelectionProperty;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JAbstractMethod;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.junit.client.GWTTestCase.TestModuleInfo;
+import com.google.gwt.junit.client.impl.GWTRunnerProxy;
+import com.google.gwt.junit.client.impl.GWTRunnerProxy.JsniTestAccessor;
+import com.google.gwt.junit.client.impl.JUnitHost.TestInfo;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * This class generates a JSNI based {@link GWTRunnerProxy} implementation.
+ * <p>
+ * For each gwt module following classes will be generated:
+ * <li>GwtRunnerProxyImpl abstract class that implements createTestAccessor using JSNI</li>
+ * <li>GwtRunnerProxyImplXyz (e.g. GwtRunnerProxyImplSafari) that extends GwtRunnerProxyImpl and
+ * implements getUserAgentPropery</li>
+ */
+public class GWTRunnerProxyGenerator extends Generator {
+
+ private static final String PROXY = GWTRunnerProxy.class.getCanonicalName();
+ private static final String JSNI_TEST_ACCESSOR = JsniTestAccessor.class.getCanonicalName();
+
+ private static final JType[] NO_PARAMS = new JType[0];
+
+ private static String getPackagePrefix(JClassType classType) {
+ String name = classType.getPackage().getName();
+ return (name.length() == 0) ? name : (name + '.');
+ }
+
+ /**
+ * Create a new type that satisfies the rebind request.
+ */
+ @Override
+ public String generate(TreeLogger logger, GeneratorContext context, String typeName)
+ throws UnableToCompleteException {
+ if (!PROXY.equals(typeName)) {
+ logger.log(TreeLogger.ERROR, "This generator may only be used with " + PROXY, null);
+ throw new UnableToCompleteException();
+ }
+ JClassType requestedClass;
+ try {
+ requestedClass = context.getTypeOracle().getType(typeName);
+ } catch (NotFoundException e) {
+ logger.log(
+ TreeLogger.ERROR,
+ "Could not find type '"
+ + typeName
+ + "'; please see the log, as this usually indicates a previous error ",
+ e);
+ throw new UnableToCompleteException();
+ }
+
+ String moduleName;
+ try {
+ ConfigurationProperty prop = context.getPropertyOracle().getConfigurationProperty(
+ "junit.moduleName");
+ moduleName = prop.getValues().get(0);
+ } catch (BadPropertyValueException e) {
+ logger.log(TreeLogger.ERROR,
+ "Could not resolve junit.moduleName property", e);
+ throw new UnableToCompleteException();
+ }
+
+ String userAgent;
+ try {
+ SelectionProperty prop = context.getPropertyOracle().getSelectionProperty(
+ logger, "user.agent");
+ userAgent = prop.getCurrentValue();
+ } catch (BadPropertyValueException e) {
+ logger.log(TreeLogger.ERROR, "Could not resolve user.agent property", e);
+ throw new UnableToCompleteException();
+ }
+
+ String packageName = requestedClass.getPackage().getName();
+
+ // Generate the base class shared across different permutations:
+ String generatedBaseClass = requestedClass.getName().replace('.', '_') + "Impl";
+ SourceWriter sourceWriter =
+ getSourceWriter(logger, context, packageName, generatedBaseClass, null, null);
+ if (sourceWriter != null) {
+ writeMethodCreateTestAccessor(logger, context, moduleName, sourceWriter);
+ sourceWriter.commit(logger);
+ }
+
+ // Generate the actual class for each permutation"
+ String generatedClass = generatedBaseClass + userAgent;
+ sourceWriter =
+ getSourceWriter(logger, context, packageName, generatedClass, generatedBaseClass, PROXY);
+ if (sourceWriter != null) {
+ writeGetUserAgentPropertyMethod(userAgent, sourceWriter);
+ sourceWriter.commit(logger);
+ }
+
+ return packageName + "." + generatedClass;
+ }
+
+ /**
+ * Will generate following:
+ * <pre>
+ * public native final JsniTestAccessor createTestAccessor() /*-{
+ * return {
+ * 'a.b.c.X' = {
+ * 'new' : function(test) {
+ * return @a.b.c.X::new();
+ * },
+ * 'testMethod1' = function(test) {
+ * return test.@a.b.c.X::testMethod1();
+ * },
+ * 'testMethod2' = function(test) {
+ * return test.@a.b.c.X::testMethod1();
+ * },
+ * },
+ *
+ * 'k.l.m.Y' = {
+ * ...
+ * },
+ *
+ * ...
+ * };
+ * }-{@literal*}/;
+ * </pre>
+ */
+ private void writeMethodCreateTestAccessor(
+ TreeLogger logger, GeneratorContext context, String moduleName, SourceWriter sw) {
+ Set<JClassType> testClasses = getTestClasses(logger, context, moduleName);
+ sw.println("public native final %s createTestAccessor() /*-{", JSNI_TEST_ACCESSOR);
+ sw.indent();
+ sw.println("return {");
+ for (JClassType jClassType : testClasses) {
+ sw.println("'%s': {", jClassType.getQualifiedBinaryName());
+ writeFunctionMap(sw, jClassType);
+ sw.println("},");
+ }
+ sw.println("};");
+ sw.outdent();
+ sw.println("}-*/;");
+ }
+
+ private void writeFunctionMap(SourceWriter sw, JClassType jClassType) {
+ writeFunction(sw, jClassType.findConstructor(NO_PARAMS), true);
+ for (JMethod method : getTestMethods(jClassType)) {
+ writeFunction(sw, method, false);
+ }
+ }
+
+ private void writeFunction(SourceWriter sw, JAbstractMethod method, boolean isConstructor) {
+ // Static method are also valid test methods
+ String object = (isConstructor || method.isMethod().isStatic()) ? "" : "test.";
+ String call = object + method.getJsniSignature();
+ String methodName = isConstructor ? "new" : method.getName();
+ sw.println("'%s' : function(test) { return %s(); },", methodName, call);
+ }
+
+ private void writeGetUserAgentPropertyMethod(String userAgent, SourceWriter sw) {
+ sw.println("public final String getUserAgentProperty() {");
+ sw.indentln("return \"" + userAgent + "\";");
+ sw.println("}");
+ }
+
+ private Set<JClassType> getTestClasses(
+ TreeLogger logger, GeneratorContext context, String moduleName) {
+ // Check the global set of active tests for this module.
+ TestModuleInfo moduleInfo = GWTTestCase.getTestsForModule(moduleName);
+ Set<TestInfo> moduleTests = (moduleInfo == null) ? null : moduleInfo.getTests();
+ if (moduleTests == null || moduleTests.isEmpty()) {
+ // Fall back to pulling in all types in the module.
+ JClassType[] allTestTypes = getAllPossibleTestTypes(context.getTypeOracle());
+ return getTestTypesForModule(logger, moduleName, allTestTypes);
+ } else {
+ Set<JClassType> testClasses = new LinkedHashSet<JClassType>();
+ for (TestInfo testInfo : moduleTests) {
+ testClasses.add(context.getTypeOracle().findType(testInfo.getTestClass()));
+ }
+ return testClasses;
+ }
+ }
+
+ private JClassType[] getAllPossibleTestTypes(TypeOracle typeOracle) {
+ JClassType gwtTestType = typeOracle.findType(GWTTestCase.class.getName());
+ if (gwtTestType != null) {
+ return gwtTestType.getSubtypes();
+ } else {
+ return new JClassType[0];
+ }
+ }
+
+ private SourceWriter getSourceWriter(TreeLogger logger,
+ GeneratorContext ctx,
+ String packageName,
+ String className,
+ String superclassName,
+ String interfaceName) {
+ PrintWriter printWriter = ctx.tryCreate(logger, packageName, className);
+ if (printWriter == null) {
+ return null;
+ }
+
+ ClassSourceFileComposerFactory composerFactory =
+ new ClassSourceFileComposerFactory(packageName, className);
+ if (superclassName != null) {
+ composerFactory.setSuperclass(superclassName);
+ }
+ if (interfaceName != null) {
+ composerFactory.addImplementedInterface(interfaceName);
+ }
+ return composerFactory.createSourceWriter(ctx, printWriter);
+ }
+
+ private Set<JClassType> getTestTypesForModule(
+ TreeLogger logger, String moduleName, JClassType[] allTestTypes) {
+ // Must use sorted set to prevent nondeterminism.
+ Set<JClassType> testClasses = new TreeSet<JClassType>();
+ for (JClassType classType : allTestTypes) {
+ if (!classType.isPublic() || classType.isAbstract() || !classType.isDefaultInstantiable()) {
+ continue;
+ }
+
+ String className = getPackagePrefix(classType) + classType.getName().replace('.', '$');
+
+ try {
+ Class<?> testClass = Class.forName(className);
+ GWTTestCase instantiated = (GWTTestCase) testClass.newInstance();
+ if (!moduleName.equals(instantiated.getModuleName())) {
+ continue;
+ }
+ } catch (Throwable e) {
+ logger.log(TreeLogger.INFO, "Error determining if test class '" + className
+ + "' is a part of the current module; skipping; expect subsequent errors "
+ + "if this test class is run", e);
+ continue;
+ }
+ testClasses.add(classType);
+ }
+ return testClasses;
+ }
+
+ private static List<JMethod> getTestMethods(JClassType requestedClass) {
+ List<JMethod> list = new ArrayList<JMethod>();
+ for (JClassType cls = requestedClass; cls != null; cls = cls.getSuperclass()) {
+ for (JMethod declMethod : cls.getMethods()) {
+ if (isJUnitTestMethod(declMethod, false)) {
+ list.add(declMethod);
+ }
+ }
+ }
+ return list;
+ }
+}
diff --git a/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/GWTTestCase.java b/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/GWTTestCase.java
index fd0c49a..ebf0382 100644
--- a/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/GWTTestCase.java
+++ b/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/GWTTestCase.java
@@ -110,6 +110,16 @@
*/
private TestCaseUncaughtExceptionHandler uncaughtHandler;
+ /**
+ * Name of the test class.
+ */
+ private String testClass;
+
+ public void init(String testClass, String testName) {
+ this.testClass = testClass;
+ setName(testName);
+ }
+
// CHECKSTYLE_OFF
/**
* Actually run the user's test. Called from {@link GWTRunner}.
@@ -171,6 +181,11 @@
// No tearDown call here; we do it from reportResults.
}
+ @Override
+ protected void doRunTest(String name) throws Throwable {
+ GWTRunner.get().executeTestMethod(this, testClass, name);
+ }
+
public void setForcePureJava(boolean forcePureJava) {
// Ignore completely. The test is being run in GWT mode,
// hence assumed not to be pure Java.
diff --git a/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java b/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java
index 1b0a2af..af0cfe3 100644
--- a/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java
+++ b/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTRunner.java
@@ -20,6 +20,7 @@
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.http.client.UrlBuilder;
import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.junit.client.impl.GWTRunnerProxy.TestAccessor;
import com.google.gwt.junit.client.impl.JUnitHost.ClientInfo;
import com.google.gwt.junit.client.impl.JUnitHost.InitialResponse;
import com.google.gwt.junit.client.impl.JUnitHost.TestBlock;
@@ -41,7 +42,7 @@
* are reported back through {@link #junitHost}, and the next method to run is
* returned. This process repeats until the next method to run is null.
*/
-public abstract class GWTRunner implements EntryPoint {
+public class GWTRunner implements EntryPoint {
private final class InitialResponseListener implements
AsyncCallback<InitialResponse> {
@@ -194,6 +195,9 @@
*/
private boolean serverless = false;
+ private String userAgentProperty;
+ private TestAccessor testAccessor;
+
// TODO(FINDBUGS): can this be a private constructor to avoid multiple
// instances?
public GWTRunner() {
@@ -209,6 +213,10 @@
}
public void onModuleLoad() {
+ GWTRunnerProxy proxy = GWT.create(GWTRunnerProxy.class);
+ userAgentProperty = proxy.getUserAgentProperty();
+ testAccessor = proxy.createTestAccessor();
+
clientInfo = new ClientInfo(parseQueryParamInteger(
SESSIONID_QUERY_PARAM, -1), getUserAgentProperty());
maxRetryCount = parseQueryParamInteger(RETRYCOUNT_QUERY_PARAM, 3);
@@ -233,7 +241,7 @@
// That's it, we're done
return;
}
- if (result != null && failureMessage != null) {
+ if (failureMessage != null) {
RuntimeException ex = new RuntimeException(failureMessage);
result.setException(ex);
} else if (result.exceptionWrapper != null) {
@@ -274,16 +282,19 @@
}
/**
- * Implemented by the generated subclass. Creates an instance of the specified
- * test class by fully qualified name.
+ * Executes a test on provided test class instance.
*/
- protected abstract GWTTestCase createNewTestCase(String testClass);
+ public void executeTestMethod(GWTTestCase testCase, String className, String methodName)
+ throws Throwable {
+ testAccessor.invoke(testCase, className, methodName);
+ }
/**
- * Implemented by the generated subclass. Get the value of the user agent
- * property.
+ * Get the value of the user agent property.
*/
- protected abstract String getUserAgentProperty();
+ protected String getUserAgentProperty() {
+ return userAgentProperty;
+ }
private TestBlock checkForQueryParamTestToRun() {
String testClass = Window.Location.getParameter(TESTCLASS_QUERY_PARAM);
@@ -359,22 +370,17 @@
// Dynamically create a new test case.
TestInfo currentTest = getCurrentTest();
GWTTestCase testCase = null;
- Throwable caught = null;
try {
- testCase = createNewTestCase(currentTest.getTestClass());
+ testCase = testAccessor.newInstance(currentTest.getTestClass());
} catch (Throwable e) {
- caught = e;
- }
- if (testCase == null) {
RuntimeException ex = new RuntimeException(currentTest
- + ": could not instantiate the requested class", caught);
+ + ": could not instantiate the requested class", e);
JUnitResult result = new JUnitResult();
result.setException(ex);
reportResultsAndGetNextMethod(result);
return;
}
-
- testCase.setName(currentTest.getTestMethod());
+ testCase.init(currentTest.getTestClass(), currentTest.getTestMethod());
testCase.__doRunTest();
}