Fixes issue #702. Adds benchmarking capability to GWT as an extension to JUnit.
Patch by: tobyr
Review by: mmendez
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@813 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/build.xml b/build.xml
index d00b037..0cecb31 100755
--- a/build.xml
+++ b/build.xml
@@ -6,7 +6,7 @@
<!-- "build" is the default when subprojects are directly targetted -->
<property name="target" value="build" />
- <target name="dist" depends="dev, user, servlet, jni, doc, samples" description="Run the distributions">
+ <target name="dist" depends="dev, user, servlet, tools, jni, doc, samples" description="Run the distributions">
<gwt.ant dir="distro-source" />
</target>
@@ -18,6 +18,10 @@
<gwt.ant dir="user" />
</target>
+ <target name="tools" depends="buildtools, user" description="Run tools">
+ <gwt.ant dir="tools" />
+ </target>
+
<target name="servlet" depends="buildtools, user" description="Run servlet">
<gwt.ant dir="servlet" />
</target>
diff --git a/dev/core/src/com/google/gwt/dev/generator/ast/BaseNode.java b/dev/core/src/com/google/gwt/dev/generator/ast/BaseNode.java
new file mode 100644
index 0000000..ef26c7c
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/generator/ast/BaseNode.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2007 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.generator.ast;
+
+/**
+ * A simple base class for implementing an AST node.
+ */
+public abstract class BaseNode implements Node {
+
+ public abstract String toCode();
+
+ public String toString() {
+ return toCode();
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/generator/ast/Expression.java b/dev/core/src/com/google/gwt/dev/generator/ast/Expression.java
new file mode 100644
index 0000000..db851ca
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/generator/ast/Expression.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2007 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.generator.ast;
+
+/**
+ * A Node that represents a Java expression. An expression is a parsable value
+ * that is a subset of a statement. For example,
+ *
+ * <ul> <li>foo( a, b )</li> <li>14</li> <li>11 / 3</li> <li>x</li> </ul>
+ *
+ * are all Expressions.
+ */
+public class Expression extends BaseNode {
+
+ String code;
+
+ public Expression() {
+ code = "";
+ }
+
+ public Expression(String code) {
+ this.code = code;
+ }
+
+ public String toCode() {
+ return code;
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/generator/ast/ForLoop.java b/dev/core/src/com/google/gwt/dev/generator/ast/ForLoop.java
new file mode 100644
index 0000000..c048be4
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/generator/ast/ForLoop.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2007 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.generator.ast;
+
+import java.util.List;
+
+/**
+ * A Node that represents a for loop.
+ */
+public class ForLoop implements Statements {
+
+ StatementsList body;
+
+ String initializer;
+
+ String label;
+
+ String step;
+
+ String test;
+
+ /**
+ * Creates a ForLoop with a null body.
+ *
+ */
+ public ForLoop(String initializer, String test, String step) {
+ this(initializer, test, step, null);
+ }
+
+ /**
+ * Constructs a new ForLoop node.
+ *
+ * @param initializer The initializer Expression.
+ * @param test The test Expression.
+ * @param step The step Expression. May be null.
+ * @param statements The statements for the body of the loop.
+ * May be null.
+ */
+ public ForLoop(String initializer, String test, String step,
+ Statements statements) {
+ this.initializer = initializer;
+ this.test = test;
+ this.step = step;
+ this.body = new StatementsList();
+
+ if (statements != null) {
+ body.getStatements().add(statements);
+ }
+ }
+
+ public List getStatements() {
+ return body.getStatements();
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public String toCode() {
+ String loop = "for ( " + initializer + "; " + test + "; " + step + " ) {\n"
+ +
+ body.toCode() + "\n" +
+ "}\n";
+
+ return label != null ? label + ": " + loop : loop;
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/generator/ast/MethodCall.java b/dev/core/src/com/google/gwt/dev/generator/ast/MethodCall.java
new file mode 100644
index 0000000..8b0a2c3
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/generator/ast/MethodCall.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2007 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.generator.ast;
+
+import java.util.List;
+
+/**
+ * A Node that represents a method call Expression, for example,
+ * foo( a, b, c ).
+ */
+public class MethodCall extends Expression {
+
+ List arguments;
+
+ String name;
+
+ /**
+ * Creates a new MethodCall Expression.
+ *
+ * @param name The name of the method. This must contain the qualified
+ * target expression if it is not implicitly this. For example, "foo.bar".
+ *
+ * @param arguments The list of Expressions that are the arguments for the
+ * call.
+ */
+ public MethodCall(String name, List arguments) {
+ this.name = name;
+ this.arguments = arguments;
+
+ StringBuffer call = new StringBuffer(name + "(");
+
+ if (arguments != null) {
+ call.append(" ");
+ for (int i = 0; i < arguments.size(); ++i) {
+ call.append(arguments.get(i));
+ if (i < arguments.size() - 1) {
+ call.append(", ");
+ }
+ }
+ call.append(" ");
+ }
+
+ call.append(")");
+ super.code = call.toString();
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/generator/ast/Node.java b/dev/core/src/com/google/gwt/dev/generator/ast/Node.java
new file mode 100644
index 0000000..b254865
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/generator/ast/Node.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2007 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.generator.ast;
+
+/**
+ * An AST node. Must be able to return it's code representation as a String.
+ *
+ */
+public interface Node {
+
+ /**
+ * The Java code representation of this Node.
+ *
+ * @return a non-null String.
+ */
+ public String toCode();
+}
diff --git a/dev/core/src/com/google/gwt/dev/generator/ast/Statement.java b/dev/core/src/com/google/gwt/dev/generator/ast/Statement.java
new file mode 100644
index 0000000..84b2653
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/generator/ast/Statement.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2007 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.generator.ast;
+
+import java.util.List;
+import java.util.Arrays;
+
+/**
+ * A Node that represents a single Java statement.
+ */
+public class Statement extends BaseNode implements Statements {
+
+ String code;
+
+ Expression expression;
+
+ private List list;
+
+ /**
+ * Creates a new statement from a String of code representing an Expression.
+ * Automatically appends a semicolon to <code>code</code>.
+ *
+ * @param code An Expression. Should not end with a semicolon.
+ */
+ public Statement(String code) {
+ this.code = code;
+ this.list = Arrays.asList(new Statement[]{this});
+ }
+
+ /**
+ * Creates a new statement from an Expression.
+ *
+ * @param expression A non-null Expression.
+ */
+ public Statement(Expression expression) {
+ this.expression = expression;
+ this.list = Arrays.asList(new Statement[]{this});
+ }
+
+ /**
+ * Returns this single Statement as a List of Statements of size, one.
+ *
+ */
+ public List getStatements() {
+ return list;
+ }
+
+ public String toCode() {
+ if (expression != null) {
+ return expression.toCode() + ";";
+ } else {
+ return code + ";";
+ }
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/generator/ast/Statements.java b/dev/core/src/com/google/gwt/dev/generator/ast/Statements.java
new file mode 100644
index 0000000..e383c8f
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/generator/ast/Statements.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2007 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.generator.ast;
+
+import java.util.List;
+
+/**
+ * Represents one or more groups of Statements. Can optionally be added to.
+ *
+ */
+public interface Statements extends Node {
+
+ /**
+ * Returns a list of Statements.
+ *
+ * @return a non-null list of Statements.
+ */
+ public List getStatements();
+}
diff --git a/dev/core/src/com/google/gwt/dev/generator/ast/StatementsList.java b/dev/core/src/com/google/gwt/dev/generator/ast/StatementsList.java
new file mode 100644
index 0000000..dbbf0b9
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/generator/ast/StatementsList.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2007 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.generator.ast;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * An implementation of <code>Statements</code> that is composed of a list of
+ * <code>Statements</code>.
+ */
+public class StatementsList extends BaseNode implements Statements {
+
+ List/*<Statements>*/ statements;
+
+ /**
+ * Creates a new StatementsList with no Statements.
+ *
+ */
+ public StatementsList() {
+ statements = new ArrayList();
+ }
+
+ /**
+ * Returns the Statements that are in this list.
+ *
+ */
+ public List getStatements() {
+ return statements;
+ }
+
+ public String toCode() {
+ StringBuffer code = new StringBuffer();
+ for (Iterator it = statements.iterator(); it.hasNext();) {
+ Statements stmts = (Statements) it.next();
+ code.append(stmts.toCode()).append("\n");
+ }
+ return code.toString();
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/generator/ast/WhileLoop.java b/dev/core/src/com/google/gwt/dev/generator/ast/WhileLoop.java
new file mode 100644
index 0000000..d1e34ff
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/generator/ast/WhileLoop.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2007 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.generator.ast;
+
+import java.util.List;
+
+/**
+ * A Node that represents a Java while loop.
+ */
+public class WhileLoop implements Statements {
+
+ StatementsList body;
+
+ String test;
+
+ /**
+ * Creates a new while loop with <code>test</code> as the test Expression.
+ * The WhileLoop has an empty body.
+ *
+ * @param test An Expression that must be of type boolean. Must be non-null.
+ */
+ public WhileLoop(String test) {
+ this.test = test;
+ this.body = new StatementsList();
+ }
+
+ public List getStatements() {
+ return body.getStatements();
+ }
+
+ public String toCode() {
+ return "while ( " + test + " ) {\n" +
+ body.toCode() + "\n" +
+ "}\n";
+ }
+}
diff --git a/distro-source/common.ant.xml b/distro-source/common.ant.xml
index 9daa178..e5904f5 100755
--- a/distro-source/common.ant.xml
+++ b/distro-source/common.ant.xml
@@ -8,10 +8,12 @@
<patternset id="chmod.executables">
<include name="*Creator" />
+ <include name="benchmarkViewer" />
</patternset>
<patternset id="chmod.not.executables">
<exclude name="*Creator" />
+ <exclude name="benchmarkViewer" />
</patternset>
<target name="filter" description="Filters distro files for versioning">
diff --git a/distro-source/linux/build.xml b/distro-source/linux/build.xml
index ac158c4..28a0ca4 100755
--- a/distro-source/linux/build.xml
+++ b/distro-source/linux/build.xml
@@ -11,6 +11,7 @@
<tarfileset file="${gwt.build.lib}/gwt-dev-${dist.platform}.jar" prefix="${project.distname}" />
<tarfileset file="${gwt.build.lib}/gwt-user.jar" prefix="${project.distname}" />
<tarfileset file="${gwt.build.lib}/gwt-servlet.jar" prefix="${project.distname}" />
+ <tarfileset file="${gwt.build.lib}/gwt-benchmark-viewer.jar" prefix="${project.distname}" />
<!-- jni libs-->
<tarfileset dir="${gwt.build.jni}/${dist.platform}" prefix="${project.distname}" />
diff --git a/distro-source/linux/src/benchmarkViewer b/distro-source/linux/src/benchmarkViewer
new file mode 100755
index 0000000..6530de5
--- /dev/null
+++ b/distro-source/linux/src/benchmarkViewer
@@ -0,0 +1,3 @@
+#!/bin/sh
+APPDIR=`dirname $0`;
+java -Dcom.google.gwt.junit.reportPath="$1" -cp "$APPDIR/gwt-user.jar:$APPDIR/gwt-dev-linux.jar:$APPDIR/gwt-benchmark-viewer.jar" com.google.gwt.dev.GWTShell com.google.gwt.junit.viewer.ReportViewer/ReportViewer.html;
diff --git a/distro-source/mac/build.xml b/distro-source/mac/build.xml
index 96c5217..00f111f 100755
--- a/distro-source/mac/build.xml
+++ b/distro-source/mac/build.xml
@@ -11,6 +11,7 @@
<tarfileset file="${gwt.build.lib}/gwt-dev-${dist.platform}.jar" prefix="${project.distname}" />
<tarfileset file="${gwt.build.lib}/gwt-user.jar" prefix="${project.distname}" />
<tarfileset file="${gwt.build.lib}/gwt-servlet.jar" prefix="${project.distname}" />
+ <tarfileset file="${gwt.build.lib}/gwt-benchmark-viewer.jar" prefix="${project.distname}" />
<!-- jni libs-->
<tarfileset dir="${gwt.build.jni}/${dist.platform}" prefix="${project.distname}" />
diff --git a/distro-source/mac/src/benchmarkViewer b/distro-source/mac/src/benchmarkViewer
new file mode 100755
index 0000000..d82dbf5
--- /dev/null
+++ b/distro-source/mac/src/benchmarkViewer
@@ -0,0 +1,3 @@
+#!/bin/sh
+APPDIR=`dirname $0`;
+java -Dcom.google.gwt.junit.reportPath="$1" -XstartOnFirstThread -cp "$APPDIR/gwt-user.jar:$APPDIR/gwt-dev-mac.jar:$APPDIR/gwt-benchmark-viewer.jar" com.google.gwt.dev.GWTShell com.google.gwt.junit.viewer.ReportViewer/ReportViewer.html;
diff --git a/distro-source/windows/build.xml b/distro-source/windows/build.xml
index 9939c4c..8fce756 100755
--- a/distro-source/windows/build.xml
+++ b/distro-source/windows/build.xml
@@ -11,6 +11,7 @@
<zipfileset file="${gwt.build.lib}/gwt-dev-${dist.platform}.jar" prefix="${project.distname}" />
<zipfileset file="${gwt.build.lib}/gwt-user.jar" prefix="${project.distname}" />
<zipfileset file="${gwt.build.lib}/gwt-servlet.jar" prefix="${project.distname}" />
+ <zipfileset file="${gwt.build.lib}/gwt-benchmark-viewer.jar" prefix="${project.distname}" />
<!-- jni libs-->
<zipfileset dir="${gwt.build.jni}/${dist.platform}" prefix="${project.distname}" />
diff --git a/distro-source/windows/src/benchmarkViewer.cmd b/distro-source/windows/src/benchmarkViewer.cmd
new file mode 100755
index 0000000..81753bf
--- /dev/null
+++ b/distro-source/windows/src/benchmarkViewer.cmd
@@ -0,0 +1 @@
+@java -Dcom.google.gwt.junit.reportPath="%1" -cp "%~dp0/gwt-user.jar;%~dp0/gwt-dev-windows.jar;%~dp0/gwt-benchmark-viewer.jar" com.google.gwt.dev.GWTShell com.google.gwt.junit.viewer.ReportViewer/ReportViewer.html;
diff --git a/doc/build.xml b/doc/build.xml
index d9015ca..74bc02b 100644
--- a/doc/build.xml
+++ b/doc/build.xml
@@ -9,7 +9,7 @@
<!-- Platform shouldn't matter here, just picking one -->
<property.ensure name="gwt.dev.jar" location="${gwt.build.lib}/gwt-dev-linux.jar" />
- <property name="USER_PKGS" value="com.google.gwt.core.client;com.google.gwt.core.ext;com.google.gwt.core.ext.typeinfo;com.google.gwt.i18n.client;com.google.gwt.json.client;com.google.gwt.junit.client;com.google.gwt.user.client;com.google.gwt.user.client.rpc;com.google.gwt.user.client.ui;com.google.gwt.user.server.rpc;com.google.gwt.xml.client;com.google.gwt.http.client" />
+ <property name="USER_PKGS" value="com.google.gwt.core.client;com.google.gwt.core.ext;com.google.gwt.core.ext.typeinfo;com.google.gwt.i18n.client;com.google.gwt.json.client;com.google.gwt.junit.client;com.google.gwt.user.client;com.google.gwt.user.client.rpc;com.google.gwt.user.client.ui;com.google.gwt.user.server.rpc;com.google.gwt.xml.client;com.google.gwt.http.client;com.google.gwt.junit.viewer.client" />
<property name="LANG_PKGS" value="java.lang;java.util" />
<property name="DOC_PKGS" value="com.google.gwt.doc" />
@@ -29,6 +29,7 @@
<pathelement location="${gwt.user.jar}" />
<pathelement location="${gwt.dev.jar}" />
<pathelement location="${gwt.tools.lib}/junit/junit-3.8.1.jar" />
+ <pathelement location="${gwt.tools.lib}/jfreechart/jfreechart-1.0.3.jar" />
</path>
<!--
@@ -245,7 +246,7 @@
<arg value="-sourcepath" />
<arg pathref="USER_SOURCE_PATH" />
<arg value="-examplepackages" />
- <arg value="com.google.gwt.examples;com.google.gwt.examples.i18n;com.google.gwt.examples.http.client;com.google.gwt.examples.rpc.server" />
+ <arg value="com.google.gwt.examples;com.google.gwt.examples.i18n;com.google.gwt.examples.http.client;com.google.gwt.examples.rpc.server;com.google.gwt.examples.benchmarks" />
<arg value="-packages" />
<arg value="${USER_PKGS}" />
</java>
diff --git a/doc/src/com/google/gwt/doc/DeveloperGuide.java b/doc/src/com/google/gwt/doc/DeveloperGuide.java
index 23bdc1d..51c4699 100644
--- a/doc/src/com/google/gwt/doc/DeveloperGuide.java
+++ b/doc/src/com/google/gwt/doc/DeveloperGuide.java
@@ -2229,6 +2229,34 @@
public static class JUnitAsync {
}
+ /**
+ * GWT's <a href="http://www.junit.org">JUnit</a> integration provides
+ * special support for creating and reporting on benchmarks. Specifically,
+ * GWT has introduced a new {@link com.google.gwt.junit.client.Benchmark}
+ * class which provides built-in facilities for common benchmarking needs.
+ *
+ * To take advantage of benchmarking support, take the following steps:
+ * <ol>
+ * <li>Review the documentation on
+ * {@link com.google.gwt.junit.client.Benchmark}. Take a look at the
+ * example benchmark code.</li>
+ * <li>Create your own benchmark by subclassing
+ * {@link com.google.gwt.junit.client.Benchmark}.
+ * Execute your benchmark like you would any normal JUnit test. By default,
+ * the test results are written to a report XML file in your working
+ * directory.</li>
+ * <li>Run <code>benchmarkViewer</code> to browse visualizations
+ * (graphs/charts) of your report data. The <code>benchmarkViewer</code> is
+ * a GWT tool in the root of your GWT installation directory that displays
+ * benchmark reports.</li>
+ * </ol>
+ *
+ * @title Benchmarking
+ * @synopsis How to use GWT's JUnit support to create and report on
+ * benchmarks to help you optimize your code.
+ */
+ public static class JUnitBenchmarking {
+ }
}
/**
diff --git a/eclipse/tools/benchmark-viewer/.classpath b/eclipse/tools/benchmark-viewer/.classpath
new file mode 100644
index 0000000..3b6e066
--- /dev/null
+++ b/eclipse/tools/benchmark-viewer/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/gwt-user"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/jfreechart/jfreechart-1.0.3.jar"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/eclipse/tools/benchmark-viewer/.project b/eclipse/tools/benchmark-viewer/.project
new file mode 100644
index 0000000..5e12fb6
--- /dev/null
+++ b/eclipse/tools/benchmark-viewer/.project
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>benchmark-viewer</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+ <linkedResources>
+ <link>
+ <name>src</name>
+ <type>2</type>
+ <locationURI>GWT_ROOT/tools/benchmark-viewer/src</locationURI>
+ </link>
+ </linkedResources>
+</projectDescription>
diff --git a/eclipse/user/.checkstyle b/eclipse/user/.checkstyle
index b705c56..a3f85e6 100644
--- a/eclipse/user/.checkstyle
+++ b/eclipse/user/.checkstyle
@@ -1,11 +1,11 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<fileset-config file-format-version="1.2.0" simple-config="false">
- <fileset name="Java source for production code" enabled="true" check-config-name="GWT Checks" local="false">
- <file-match-pattern match-pattern=".*src.*\.java" include-pattern="true"/>
- </fileset>
- <fileset name="Java source for test cases" enabled="true" check-config-name="GWT Checks for Tests" local="false">
- <file-match-pattern match-pattern=".*test.*com[/\\]google[/\\].*\.java$" include-pattern="true"/>
- <file-match-pattern match-pattern=".*test.*test[/\\].*\.java$" include-pattern="true"/>
- </fileset>
- <filter name="NonSrcDirs" enabled="true"/>
-</fileset-config>
+<?xml version="1.0" encoding="UTF-8"?>
+<fileset-config file-format-version="1.2.0" simple-config="false">
+ <fileset name="Java source for production code" enabled="true" check-config-name="GWT Checks" local="false">
+ <file-match-pattern match-pattern=".*src.*\.java" include-pattern="true"/>
+ </fileset>
+ <fileset name="Java source for test cases" enabled="true" check-config-name="GWT Checks (Tests)" local="false">
+ <file-match-pattern match-pattern=".*test.*com[/\\]google[/\\].*\.java$" include-pattern="true"/>
+ <file-match-pattern match-pattern=".*test.*test[/\\].*\.java$" include-pattern="true"/>
+ </fileset>
+ <filter name="NonSrcDirs" enabled="true"/>
+</fileset-config>
diff --git a/eclipse/user/.classpath b/eclipse/user/.classpath
index 3b1d4bc..2591d4c 100644
--- a/eclipse/user/.classpath
+++ b/eclipse/user/.classpath
@@ -4,9 +4,10 @@
<classpathentry kind="src" path="core/javadoc"/>
<classpathentry kind="src" path="core/test"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
- <classpathentry exported="true" sourcepath="/GWT_TOOLS/lib/apache/tapestry-util-text-4.0.2-src.zip" kind="var" path="GWT_TOOLS/lib/apache/tapestry-util-text-4.0.2.jar"/>
- <classpathentry exported="true" sourcepath="/GWT_TOOLS/lib/junit/junit-3.8.1-src.zip" kind="var" path="GWT_TOOLS/lib/junit/junit-3.8.1.jar"/>
+ <classpathentry exported="true" kind="var" path="GWT_TOOLS/lib/apache/tapestry-util-text-4.0.2.jar" sourcepath="/GWT_TOOLS/lib/apache/tapestry-util-text-4.0.2-src.zip"/>
+ <classpathentry exported="true" kind="var" path="GWT_TOOLS/lib/junit/junit-3.8.1.jar" sourcepath="/GWT_TOOLS/lib/junit/junit-3.8.1-src.zip"/>
<classpathentry exported="true" kind="var" path="GWT_TOOLS/lib/tomcat/servlet-api-2.4.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/gwt-dev-linux"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/eclipse/jdt-3.1.1.jar" sourcepath="/GWT_TOOLS/lib/eclipse/jdt-3.1.1-src.zip"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/eclipse/user/.project b/eclipse/user/.project
index 3bd7525..26f6979 100644
--- a/eclipse/user/.project
+++ b/eclipse/user/.project
@@ -24,7 +24,7 @@
<link>
<name>core</name>
<type>2</type>
- <location>GWT_ROOT/user</location>
+ <locationURI>GWT_ROOT/user</locationURI>
</link>
</linkedResources>
</projectDescription>
diff --git a/tools/benchmark-viewer/build.xml b/tools/benchmark-viewer/build.xml
new file mode 100755
index 0000000..8b78fce
--- /dev/null
+++ b/tools/benchmark-viewer/build.xml
@@ -0,0 +1,95 @@
+<project name="benchmark-viewer" default="build" basedir=".">
+ <property name="gwt.root" location="../.." />
+ <property name="project.tail" value="tools/benchmark-viewer" />
+ <import file="${gwt.root}/common.ant.xml" />
+
+ <!--
+ Default hosted mode test cases
+ -->
+ <fileset id="default.hosted.tests" dir="${javac.junit.out}">
+ <include name="**/*Test.class" />
+ </fileset>
+
+ <!--
+ Default web mode test cases
+ -->
+ <fileset id="default.web.tests" dir="${javac.junit.out}">
+ <include name="**/*Test.class" />
+ </fileset>
+
+ <!-- Platform shouldn't matter here, just picking one -->
+ <property.ensure name="gwt.dev.jar" location="${gwt.build.lib}/gwt-dev-linux.jar" />
+ <property.ensure name="gwt.user.jar" location="${gwt.build.lib}/gwt-user.jar" />
+
+ <target name="compile" description="Compile all class files">
+ <mkdir dir="${javac.out}" />
+ <gwt.javac>
+ <classpath>
+ <pathelement location="${gwt.tools.lib}/tomcat/servlet-api-2.4.jar" />
+ <pathelement location="${gwt.tools.lib}/junit/junit-3.8.1.jar" />
+ <pathelement location="${gwt.tools.lib}/jfreechart/jfreechart-1.0.3.jar" />
+ <pathelement location="${gwt.dev.jar}" />
+ <pathelement location="${gwt.user.jar}" />
+ </classpath>
+ </gwt.javac>
+ </target>
+
+ <target name="compile.tests" description="Compiles the test code for this project">
+ <mkdir dir="${javac.junit.out}" />
+ <gwt.javac srcdir="test" destdir="${javac.junit.out}">
+ <classpath>
+ <pathelement location="${javac.out}" />
+ <pathelement location="${gwt.tools.lib}/junit/junit-3.8.1.jar" />
+ <pathelement location="${gwt.dev.staging.jar}" />
+ </classpath>
+ </gwt.javac>
+ </target>
+
+ <target name="build" depends="compile" description="Build and package this project">
+ <mkdir dir="${gwt.build.lib}" />
+ <gwt.jar>
+ <fileset dir="src" excludes="**/package.html" />
+ <fileset dir="${javac.out}" />
+ <zipfileset src="${gwt.tools.lib}/jfreechart/gnujaxp.jar"/>
+ <zipfileset src="${gwt.tools.lib}/jfreechart/itext-1.4.6.jar"/>
+ <zipfileset src="${gwt.tools.lib}/jfreechart/jcommon-1.0.6.jar"/>
+ <zipfileset src="${gwt.tools.lib}/jfreechart/jfreechart-1.0.3.jar"/>
+ </gwt.jar>
+ </target>
+
+ <target name="checkstyle" description="Static analysis of source">
+ <gwt.checkstyle>
+ <fileset dir="src"/>
+ </gwt.checkstyle>
+ </target>
+
+ <target name="remoteweb-test" description="Run a remoteweb test at the given host and path">
+ <echo message="Performing remote browser testing at rmi://${gwt.remote.browser}" />
+ <gwt.junit test.args="-port ${gwt.junit.port} -out www -web -remoteweb rmi://${gwt.remote.browser}" test.out="${junit.out}/${gwt.remote.browser}" test.cases="default.web.tests" />
+ </target>
+
+ <target name="test" depends="compile, compile.tests" description="Run hosted-mode, web-mode and remoteweb tests for this project.">
+ <property.ensure name="distro.built" location="${gwt.dev.staging.jar}" message="GWT must be built before performing any tests. This can be fixed by running ant in the ${gwt.root} directory." />
+
+ <!--
+ Run hosted and web mode tests for the platform on which this build
+ is executing
+ -->
+ <parallel threadcount="1">
+ <gwt.junit test.args="-port ${gwt.junit.port}" test.out="${junit.out}/${build.host.platform}-hosted-mode" test.cases="default.hosted.tests" />
+
+ <gwt.junit test.args="-port ${gwt.junit.port} -out www -web" test.out="${junit.out}/${build.host.platform}-web-mode" test.cases="default.web.tests" />
+
+ <!--
+ Run remote browser testing for the comma delimited list of remote browsers
+ -->
+ <foreach list="${gwt.remote.browsers}" delimiter="," parallel="true" maxThreads="1" target="remoteweb-test" param="gwt.remote.browser" />
+ </parallel>
+ </target>
+
+ <target name="clean" description="Cleans this project's intermediate and output files">
+ <delete dir="${project.build}" />
+ <delete file="${project.lib}" />
+ </target>
+</project>
+
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/ReportViewer.gwt.xml b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/ReportViewer.gwt.xml
new file mode 100644
index 0000000..75bca80
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/ReportViewer.gwt.xml
@@ -0,0 +1,15 @@
+<!-- -->
+<!-- Copyright 2007 Google Inc. All Rights Reserved. -->
+<!-- Deferred binding rules for browser selection. -->
+<!-- -->
+<module>
+ <inherits name="com.google.gwt.user.User"/>
+ <inherits name="com.google.gwt.http.HTTP"/>
+
+ <source path="client"/>
+
+ <entry-point class="com.google.gwt.junit.viewer.client.ReportViewer"/>
+
+ <servlet path='/test_reports' class='com.google.gwt.junit.viewer.server.ReportServerImpl'/>
+ <servlet path='/test_images' class='com.google.gwt.junit.viewer.server.ReportImageServer'/>
+</module>
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/Benchmark.java b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/Benchmark.java
new file mode 100644
index 0000000..7ff901c
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/Benchmark.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2007 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.viewer.client;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+import java.util.List;
+
+/**
+ * A data object for Benchmark.
+ */
+public class Benchmark implements IsSerializable {
+
+ private String className;
+
+ private String description;
+
+ private String name;
+
+ /**
+ * @gwt.typeArgs <com.google.gwt.junit.viewer.client.Result>
+ */
+ private List/*<Result>*/ results;
+
+ private String sourceCode;
+
+ public String getClassName() {
+ return className;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public List/*<Result>*/ getResults() {
+ return results;
+ }
+
+ public String getSourceCode() {
+ return sourceCode;
+ }
+
+ public void setClassName(String className) {
+ this.className = className;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setResults(List results) {
+ this.results = results;
+ }
+
+ public void setSourceCode(String sourceCode) {
+ this.sourceCode = sourceCode;
+ }
+}
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/BrowserInfo.java b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/BrowserInfo.java
new file mode 100644
index 0000000..f40b85c
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/BrowserInfo.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2007 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.viewer.client;
+
+/**
+ * Provides information about a browser (vendor,version,operating system,etc...)
+ * based on user agent and other easily accessible information.
+ *
+ * This is not meant to be a "detect script" to implement browser workarounds,
+ * but rather a "pretty printer" for the browser information.
+ *
+ * This code is a derivation of Browser Detect v2.1.6 documentation:
+ * http://www.dithered.com/javascript/browser_detect/index.html license:
+ * http://creativecommons.org/licenses/by/1.0/ code by Chris Nott
+ * (chris[at]dithered[dot]com)
+ *
+ * It has been transliterated from JavaScript to Java with additional changes
+ * along the way.
+ */
+public class BrowserInfo {
+
+ /**
+ * Retrieves a "pretty" version of the browser version information.
+ *
+ * @param userAgent - The HTTP user agent string.
+ * @return A pretty-printed version of the browser including the a) vendor b)
+ * version c) and operating system
+ */
+ public static String getBrowser(String userAgent) {
+
+ userAgent = userAgent.toLowerCase();
+
+ // browser engine name
+ boolean isGecko = userAgent.indexOf("gecko") != -1
+ && userAgent.indexOf("safari") == -1;
+ boolean isAppleWebKit = userAgent.indexOf("applewebkit") != -1;
+
+ // browser name
+ boolean isKonqueror = userAgent.indexOf("konqueror") != -1;
+ boolean isSafari = userAgent.indexOf("safari") != - 1;
+ boolean isOmniweb = userAgent.indexOf("omniweb") != - 1;
+ boolean isOpera = userAgent.indexOf("opera") != -1;
+ boolean isIcab = userAgent.indexOf("icab") != -1;
+ boolean isAol = userAgent.indexOf("aol") != -1;
+ boolean isIE = userAgent.indexOf("msie") != -1 && !isOpera && (
+ userAgent.indexOf("webtv") == -1);
+ boolean isMozilla = isGecko && userAgent.indexOf("gecko/") + 14 == userAgent
+ .length();
+ boolean isFirefox = userAgent.indexOf("firefox/") != -1
+ || userAgent.indexOf("firebird/") != -1;
+ boolean isNS = isGecko ? userAgent.indexOf("netscape") != -1 :
+ userAgent.indexOf("mozilla") != -1 &&
+ ! isOpera &&
+ ! isSafari &&
+ userAgent.indexOf("spoofer") == -1 &&
+ userAgent.indexOf("compatible") == -1 &&
+ userAgent.indexOf("webtv") == -1 &&
+ userAgent.indexOf("hotjava") == -1;
+
+ // spoofing and compatible browsers
+ boolean isIECompatible = userAgent.indexOf("msie") != -1 && !isIE;
+ boolean isNSCompatible = userAgent.indexOf("mozilla") != -1 && !isNS
+ && !isMozilla;
+
+ // rendering engine versions
+ String geckoVersion = isGecko ? userAgent.substring(
+ userAgent.lastIndexOf("gecko/") + 6,
+ userAgent.lastIndexOf("gecko/") + 14) : "-1";
+ String equivalentMozilla = isGecko ? userAgent
+ .substring(userAgent.indexOf("rv:") + 3) : "-1";
+ String appleWebKitVersion = isAppleWebKit ? userAgent
+ .substring(userAgent.indexOf("applewebkit/") + 12) : "-1";
+
+ // float versionMinor = parseFloat(navigator.appVersion);
+ String versionMinor = "";
+
+ // correct version number
+ if (isGecko && !isMozilla) {
+ versionMinor = userAgent.substring(
+ userAgent.indexOf("/", userAgent.indexOf("gecko/") + 6) + 1);
+ } else if (isMozilla) {
+ versionMinor = userAgent.substring(userAgent.indexOf("rv:") + 3);
+ } else if (isIE) {
+ versionMinor = userAgent.substring(userAgent.indexOf("msie ") + 5);
+ } else if (isKonqueror) {
+ versionMinor = userAgent.substring(userAgent.indexOf("konqueror/") + 10);
+ } else if (isSafari) {
+ versionMinor = userAgent.substring(userAgent.lastIndexOf("safari/") + 7);
+ } else if (isOmniweb) {
+ versionMinor = userAgent.substring(userAgent.lastIndexOf("omniweb/") + 8);
+ } else if (isOpera) {
+ versionMinor = userAgent.substring(userAgent.indexOf("opera") + 6);
+ } else if (isIcab) {
+ versionMinor = userAgent.substring(userAgent.indexOf("icab") + 5);
+ }
+
+ String version = getVersion(versionMinor);
+
+ // dom support
+ // boolean isDOM1 = (document.getElementById);
+ // boolean isDOM2Event = (document.addEventListener && document.removeEventListener);
+
+ // css compatibility mode
+ // this.mode = document.compatMode ? document.compatMode : "BackCompat";
+
+ // platform
+ boolean isWin = userAgent.indexOf("win") != -1;
+ boolean isWin32 = isWin && userAgent.indexOf("95") != -1 ||
+ userAgent.indexOf("98") != -1 ||
+ userAgent.indexOf("nt") != -1 ||
+ userAgent.indexOf("win32") != -1 ||
+ userAgent.indexOf("32bit") != -1 ||
+ userAgent.indexOf("xp") != -1;
+
+ boolean isMac = userAgent.indexOf("mac") != -1;
+ boolean isUnix = userAgent.indexOf("unix") != -1 ||
+ userAgent.indexOf("sunos") != -1 ||
+ userAgent.indexOf("bsd") != -1 ||
+ userAgent.indexOf("x11") != -1;
+
+ boolean isLinux = userAgent.indexOf("linux") != -1;
+
+ // specific browser shortcuts
+ /*
+ this.isNS4x = (this.isNS && this.versionMajor == 4);
+ this.isNS40x = (this.isNS4x && this.versionMinor < 4.5);
+ this.isNS47x = (this.isNS4x && this.versionMinor >= 4.7);
+ this.isNS4up = (this.isNS && this.versionMinor >= 4);
+ this.isNS6x = (this.isNS && this.versionMajor == 6);
+ this.isNS6up = (this.isNS && this.versionMajor >= 6);
+ this.isNS7x = (this.isNS && this.versionMajor == 7);
+ this.isNS7up = (this.isNS && this.versionMajor >= 7);
+
+ this.isIE4x = (this.isIE && this.versionMajor == 4);
+ this.isIE4up = (this.isIE && this.versionMajor >= 4);
+ this.isIE5x = (this.isIE && this.versionMajor == 5);
+ this.isIE55 = (this.isIE && this.versionMinor == 5.5);
+ this.isIE5up = (this.isIE && this.versionMajor >= 5);
+ this.isIE6x = (this.isIE && this.versionMajor == 6);
+ this.isIE6up = (this.isIE && this.versionMajor >= 6);
+
+ this.isIE4xMac = (this.isIE4x && this.isMac);
+ */
+
+ String name = isGecko ? "Gecko" :
+ isAppleWebKit ? "Apple WebKit" :
+ isKonqueror ? "Konqueror" :
+ isSafari ? "Safari" :
+ isOpera ? "Opera" :
+ isIE ? "IE" :
+ isMozilla ? "Mozilla" :
+ isFirefox ? "Firefox" :
+ isNS ? "Netscape" : "";
+
+ name += " " + version + " on " +
+ (isWin ? "Windows" :
+ isMac ? "Mac" :
+ isUnix ? "Unix" :
+ isLinux ? "Linux" : "Unknown");
+
+ return name;
+ }
+
+ // Reads the version from a string which begins with a version number
+ // and contains additional character data
+ private static String getVersion(String versionPlusCruft) {
+ for (int index = 0; index < versionPlusCruft.length(); ++index) {
+ char c = versionPlusCruft.charAt(index);
+ if (c != '.' && ! Character.isDigit(c)) {
+ return versionPlusCruft.substring(0, index);
+ }
+ }
+ return versionPlusCruft;
+ }
+}
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/Category.java b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/Category.java
new file mode 100644
index 0000000..627a05f
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/Category.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2007 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.viewer.client;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+import java.util.List;
+
+/**
+ * A data object for Category.
+ */
+public class Category implements IsSerializable {
+
+ /**
+ * @gwt.typeArgs <com.google.gwt.junit.viewer.client.Benchmark>
+ */
+ private List benchmarks;
+
+ private String description;
+
+ private String name;
+
+ public List getBenchmarks() {
+ return benchmarks;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setBenchmarks(List benchmarks) {
+ this.benchmarks = benchmarks;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/Report.java b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/Report.java
new file mode 100644
index 0000000..6429191
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/Report.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2007 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.viewer.client;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+import java.util.List;
+import java.util.Date;
+
+/**
+ * A data object for Report.
+ */
+public class Report implements IsSerializable {
+
+ /**
+ * @gwt.typeArgs <com.google.gwt.junit.viewer.client.Category>
+ */
+ private List/*<Category>*/ categories;
+
+ private Date date;
+
+ private String dateString; // Temporary addition until we get better date
+
+ // formatting in GWT
+ private String gwtVersion;
+
+ private String id;
+
+ public List/*<Category>*/ getCategories() {
+ return categories;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public String getDateString() {
+ return dateString;
+ }
+
+ public String getGwtVersion() {
+ return gwtVersion;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public ReportSummary getSummary() {
+ int numTests = 0;
+ boolean testsPassed = true;
+
+ for (int i = 0; i < categories.size(); ++i) {
+ Category c = (Category) categories.get(i);
+ List benchmarks = c.getBenchmarks();
+ numTests += benchmarks.size();
+ }
+
+ return new ReportSummary(id, date, dateString, numTests, testsPassed);
+ }
+
+ public void setCategories(List categories) {
+ this.categories = categories;
+ }
+
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ public void setDateString(String dateString) {
+ this.dateString = dateString;
+ }
+
+ public void setGwtVersion(String gwtVersion) {
+ this.gwtVersion = gwtVersion;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+}
+
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/ReportServer.java b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/ReportServer.java
new file mode 100644
index 0000000..a50e2c7
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/ReportServer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2007 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.viewer.client;
+
+import com.google.gwt.user.client.rpc.RemoteService;
+
+import java.util.List;
+
+/**
+ * Provides Benchmark report summaries and details. This service must be running
+ * in order to view the reports via ReportViewer.
+ *
+ * @see com.google.gwt.junit.viewer.server.ReportServerImpl
+ * @see ReportViewer
+ */
+public interface ReportServer extends RemoteService {
+
+ /**
+ * Returns the full details of the specified report.
+ *
+ * @param reportId The id of the report. Originates from the ReportSummary.
+ * @return the matching Report, or null if the Report could not be found.
+ */
+ public Report getReport(String reportId);
+
+ /**
+ * Returns a list of summaries of all the Benchmark reports.
+ *
+ * @return a non-null list of ReportSummary
+ */
+ public List/*<ReportSummary>*/ getReportSummaries();
+}
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/ReportServerAsync.java b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/ReportServerAsync.java
new file mode 100644
index 0000000..ccd4f1c
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/ReportServerAsync.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2007 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.viewer.client;
+
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+/**
+ * The asynchronous interface for ReportServer.
+ *
+ * @see ReportServer
+ */
+public interface ReportServerAsync {
+
+ public void getReport(String reportId, AsyncCallback callback);
+
+ public void getReportSummaries(AsyncCallback callback);
+}
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/ReportSummary.java b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/ReportSummary.java
new file mode 100644
index 0000000..7e8d89e
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/ReportSummary.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2007 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.viewer.client;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+import java.util.Date;
+
+/**
+ * A data object summarizing the results of a report.
+ */
+public class ReportSummary implements IsSerializable {
+
+ private boolean allTestsSucceeded;
+
+ private Date date;
+
+ // A temporary addition until we get better date formatting in GWT user
+ private String dateString;
+
+ private String id;
+
+ // in GWT
+ private int numTests;
+
+ public ReportSummary() {
+ }
+
+ public ReportSummary(String id, Date date, String dateString, int numTests,
+ boolean allTestsSucceeded) {
+ this.id = id;
+ this.date = date;
+ this.dateString = dateString;
+ this.numTests = numTests;
+ this.allTestsSucceeded = allTestsSucceeded;
+ }
+
+ public boolean allTestsSucceeded() {
+ return allTestsSucceeded;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public String getDateString() {
+ return dateString;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public int getNumTests() {
+ return numTests;
+ }
+}
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/ReportViewer.java b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/ReportViewer.java
new file mode 100644
index 0000000..33782c6
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/ReportViewer.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright 2007 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.viewer.client;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.CellPanel;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.FlexTable;
+import com.google.gwt.user.client.ui.ClickListener;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment;
+import com.google.gwt.user.client.ui.HasVerticalAlignment;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.rpc.ServiceDefTarget;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.http.client.URL;
+
+import java.util.List;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.ArrayList;
+import java.util.Map;
+
+/**
+ * The application for viewing benchmark reports. In order for the ReportViewer
+ * to operate correctly, you must have both the {@link ReportServer} RPC and
+ * {@link com.google.gwt.junit.viewer.server.ReportImageServer} servlets up and
+ * running within a servlet container.
+ *
+ * <code>ReportViewer's</code> GWT XML module is configured to start these
+ * servlets by default. Just start <code>ReportViewer</code> in hosted mode, and
+ * GWT will run them within its own embedded servlet engine. For example,
+ *
+ * <pre>java -cp <classpath> com.google.gwt.dev.GWTShell -out
+ * ReportViewerShell/www
+ * com.google.gwt.junit.viewer.ReportViewer/ReportViewer.html</pre>
+ *
+ * You can configure the location where ReportServer reads the benchmark reports
+ * from by setting the system property named in
+ * {@link com.google.gwt.junit.client.Benchmark#REPORT_PATH}.
+ */
+public class ReportViewer implements EntryPoint {
+
+ private static class MutableBool {
+
+ boolean value;
+
+ MutableBool(boolean value) {
+ this.value = value;
+ }
+ }
+
+ private static final String baseUrl = GWT.getModuleBaseURL();
+
+ private static final String imageServer = baseUrl + "test_images/";
+
+ HTML detailsLabel;
+
+ Report report;
+
+ VerticalPanel reportPanel;
+
+ ReportServerAsync reportServer;
+
+ FlexTable reportTable;
+
+ HTML statusLabel;
+
+ List/*<ReportSummary>*/ summaries;
+
+ VerticalPanel summariesPanel;
+
+ FlexTable summariesTable;
+
+ CellPanel topPanel;
+
+ public void onModuleLoad() {
+
+ init();
+
+ // Asynchronously load the summaries
+ ServiceDefTarget target = (ServiceDefTarget) GWT.create(ReportServer.class);
+ target.setServiceEntryPoint(GWT.getModuleBaseURL() + "test_reports");
+ reportServer = (ReportServerAsync) target;
+
+ reportServer.getReportSummaries(new AsyncCallback() {
+ public void onFailure(Throwable caught) {
+ String msg = "<p>" + caught.toString() + "</p>" +
+ "<p>Is your path to the reports correct?</p>";
+ statusLabel.setHTML(msg);
+ }
+
+ public void onSuccess(Object result) {
+ summaries = (List/*<ReportSummary>*/) result;
+ if (summaries != null) {
+ if (summaries.size() == 0) {
+ statusLabel.setText(
+ "There are no benchmark reports available in this folder.");
+ }
+ Collections.sort(summaries, new Comparator() {
+ public int compare(Object o1, Object o2) {
+ ReportSummary r1 = (ReportSummary) o1;
+ ReportSummary r2 = (ReportSummary) o2;
+ return r2.getDate().compareTo(r1.getDate()); // most recent first
+ }
+ });
+ displaySummaries();
+ }
+ }
+ });
+ }
+
+ private FlexTable createReportTable() {
+
+ FlexTable topTable = new FlexTable();
+
+ FlexTable tempReportTable = new FlexTable();
+ tempReportTable.setBorderWidth(1);
+ tempReportTable.setCellPadding(5);
+ tempReportTable.setWidget(0, 0, new Label("Date Created"));
+ tempReportTable.setWidget(0, 1, new Label("GWT Version"));
+
+ if (report == null) {
+ tempReportTable
+ .setWidget(1, 0, new Label("No currently selected report."));
+ tempReportTable.getFlexCellFormatter().setColSpan(1, 0, 3);
+ return tempReportTable;
+ }
+
+ detailsLabel.setHTML("<h3>" + report.getId() + " details </h3>");
+ tempReportTable.setWidget(1, 0, new Label(report.getDateString()));
+ tempReportTable.setWidget(1, 1, new Label(report.getGwtVersion()));
+
+ // topTable.setWidget( 0, 0, tempReportTable );
+ int currentRow = 1;
+
+ Collections.sort(report.getCategories(), new Comparator() {
+ public int compare(Object o1, Object o2) {
+ Category c1 = (Category) o1;
+ Category c2 = (Category) o2;
+ return c1.getName().compareTo(c2.getName());
+ }
+ }); // Should be done once in the RPC
+
+ for (int i = 0; i < report.getCategories().size(); ++i) {
+ Category c = (Category) report.getCategories().get(i);
+
+ if (!c.getName().equals("")) {
+ FlexTable categoryTable = new FlexTable();
+ categoryTable.setBorderWidth(0);
+ categoryTable.setCellPadding(5);
+ categoryTable.setText(0, 0, c.getName());
+ categoryTable.getFlexCellFormatter()
+ .setStyleName(0, 0, "benchmark-category");
+
+ categoryTable.setWidget(0, 1, new Label("Description"));
+ categoryTable.setWidget(1, 0, new Label(c.getName()));
+ categoryTable.setWidget(1, 1, new Label(c.getDescription()));
+
+ topTable.setWidget(currentRow++, 0, categoryTable);
+ }
+
+ Collections.sort(c.getBenchmarks(), new Comparator() {
+ public int compare(Object o1, Object o2) {
+ Benchmark b1 = (Benchmark) o1;
+ Benchmark b2 = (Benchmark) o2;
+ return b1.getName().compareTo(b2.getName());
+ }
+ }); // Should be done once in the RPC
+
+ for (int j = 0; j < c.getBenchmarks().size(); ++j) {
+ Benchmark benchmark = (Benchmark) c.getBenchmarks().get(j);
+
+ FlexTable benchmarkTable = new FlexTable();
+ benchmarkTable.setBorderWidth(0);
+ benchmarkTable.setCellPadding(5);
+ benchmarkTable.setText(0, 0, benchmark.getName());
+ // benchmarkTable.setText( 0, 1, benchmark.getDescription());
+ benchmarkTable.setWidget(1, 0,
+ new HTML("<pre>" + benchmark.getSourceCode() + "</pre>"));
+ benchmarkTable.getFlexCellFormatter()
+ .setStyleName(0, 0, "benchmark-name");
+ // benchmarkTable.getFlexCellFormatter().setStyleName( 0, 1, "benchmark-description" );
+ benchmarkTable.getFlexCellFormatter()
+ .setStyleName(1, 0, "benchmark-code");
+
+ // TODO(tobyr) Provide detailed benchmark information.
+ // Following commented code is a step in that direction.
+/*
+ benchmarkTable.setWidget( 0, 1, new Label( "Description"));
+ benchmarkTable.setWidget( 0, 2, new Label( "Class Name"));
+ benchmarkTable.setWidget( 0, 3, new Label( "Source Code"));
+ benchmarkTable.setWidget( 1, 0, new Label( benchmark.getName()));
+ benchmarkTable.setWidget( 1, 1, new Label( benchmark.getDescription()));
+ benchmarkTable.setWidget( 1, 2, new Label( benchmark.getClassName()));
+ benchmarkTable.setWidget( 1, 3, new HTML( "<pre>" + benchmark.getSourceCode() + "</pre>"));
+*/
+ topTable.setWidget(currentRow++, 0, benchmarkTable);
+
+ FlexTable resultsTable = new FlexTable();
+ resultsTable.setBorderWidth(0);
+ resultsTable.setCellPadding(5);
+ FlexTable.FlexCellFormatter resultsFormatter = resultsTable
+ .getFlexCellFormatter();
+ topTable.setWidget(currentRow++, 0, resultsTable);
+
+ Collections.sort(benchmark.getResults(), new Comparator() {
+ public int compare(Object o1, Object o2) {
+ Result r1 = (Result) o1;
+ Result r2 = (Result) o2;
+ return r1.getAgent().compareTo(r2.getAgent());
+ }
+ }); // Should be done once in the RPC
+
+ final List trialsTables = new ArrayList();
+ final MutableBool isVisible = new MutableBool(false);
+
+ Button visibilityButton = new Button("Show Data", new ClickListener() {
+ public void onClick(Widget sender) {
+ isVisible.value = !isVisible.value;
+ for (int i = 0; i < trialsTables.size(); ++i) {
+ Widget w = (Widget) trialsTables.get(i);
+ w.setVisible(isVisible.value);
+ }
+ String name = isVisible.value ? "Hide Data" : "Show Data";
+ ((Button) sender).setText(name);
+ }
+ });
+
+ for (int k = 0; k < benchmark.getResults().size(); ++k) {
+ Result result = (Result) benchmark.getResults().get(k);
+
+ /*
+ resultsTable.setWidget( 0, 0, new Label( "Result Agent"));
+ resultsTable.setWidget( 0, 1, new Label( "Host"));
+ resultsTable.setWidget( 0, 2, new Label( "Graph"));
+ resultsTable.setWidget( 1, 0, new Label( result.getAgent()));
+ resultsTable.setWidget( 1, 1, new Label( result.getHost()));
+ */
+
+ resultsTable.setWidget(0, k, new Image(getImageUrl(report.getId(),
+ c.getName(), benchmark.getClassName(), benchmark.getName(),
+ result.getAgent())));
+
+ /*
+ FlexTable allTrialsTable = new FlexTable();
+ allTrialsTable.setBorderWidth(1);
+ allTrialsTable.setCellPadding(5);
+ FlexTable.CellFormatter allTrialsFormatter = allTrialsTable
+ .getFlexCellFormatter();
+ topTable.setWidget(currentRow++, 0, allTrialsTable);
+ allTrialsTable.setWidget(0, k, trialsTable);
+ allTrialsFormatter
+ .setAlignment(0, k, HasHorizontalAlignment.ALIGN_CENTER,
+ HasVerticalAlignment.ALIGN_TOP);
+ */
+
+ resultsFormatter
+ .setAlignment(2, k, HasHorizontalAlignment.ALIGN_LEFT,
+ HasVerticalAlignment.ALIGN_TOP);
+
+ // A table of straight data for all trials for an agent
+ FlexTable trialsTable = new FlexTable();
+ trialsTable.setVisible(false);
+ trialsTables.add(trialsTable);
+ trialsTable.setBorderWidth(1);
+ trialsTable.setCellPadding(5);
+
+ if (k == 0) {
+ resultsTable.setWidget(1, k, visibilityButton);
+ resultsFormatter.setColSpan(1, k, benchmark.getResults().size());
+ resultsFormatter
+ .setAlignment(1, k, HasHorizontalAlignment.ALIGN_LEFT,
+ HasVerticalAlignment.ALIGN_MIDDLE);
+ }
+
+ resultsTable.setWidget(2, k, trialsTable);
+
+ List trials = result.getTrials();
+ int numTrials = trials.size();
+ int numVariables = ((Trial) trials.get(0)).getVariables().size();
+
+ Trial sampleTrial = (Trial) trials.get(0);
+ Map variables = sampleTrial.getVariables();
+ List variableNames = new ArrayList(variables.keySet());
+ Collections.sort(variableNames);
+
+ // Write out the variable column headers
+ for (int varIndex = 0; varIndex < numVariables; ++varIndex) {
+ String varName = (String) variableNames.get(varIndex);
+ trialsTable.setWidget(0, varIndex, new HTML(varName));
+ }
+
+ // Timing header
+ trialsTable.setWidget(0, numVariables, new HTML("Timing (ms)"));
+
+ // Write out all the trial data
+ for (int l = 0; l < numTrials; ++l) {
+ Trial trial = (Trial) trials.get(l);
+
+ // Write the variable values
+ for (int varIndex = 0; varIndex < numVariables; ++varIndex) {
+ String varName = (String) variableNames.get(varIndex);
+ String varValue = (String) trial.getVariables().get(varName);
+ trialsTable.setWidget(l + 1, varIndex, new HTML(varValue));
+ }
+
+ // Write out the timing data
+ String data = null;
+ if (trial.getException() != null) {
+ data = trial.getException();
+ } else {
+ data = trial.getRunTimeMillis() + "";
+ }
+ trialsTable.setWidget(l + 1, numVariables, new HTML(data));
+ }
+ }
+ }
+ }
+
+ return topTable;
+ }
+
+ private FlexTable createSummariesTable() {
+
+ FlexTable tempSummariesTable = new FlexTable();
+ tempSummariesTable.setCellPadding(5);
+ tempSummariesTable.setBorderWidth(1);
+ tempSummariesTable.setWidget(0, 0, new Label("Id"));
+ tempSummariesTable.setWidget(0, 1, new Label("Date Created"));
+ tempSummariesTable.setWidget(0, 2, new Label("Tests"));
+ // tempSummariesTable.setWidget( 0, 3, new Label( "Succeeded"));
+
+ if (summaries == null) {
+ tempSummariesTable.setWidget(1, 0, new Label("Fetching reports..."));
+ tempSummariesTable.getFlexCellFormatter().setColSpan(1, 0, 4);
+ return tempSummariesTable;
+ }
+
+ for (int i = 0; i < summaries.size(); ++i) {
+ ReportSummary summary = (ReportSummary) summaries.get(i);
+ int index = i + 1;
+ Label idLabel = new Label(summary.getId());
+ idLabel.addClickListener(new ClickListener() {
+ public void onClick(Widget w) {
+ getReportDetails(((Label) w).getText());
+ }
+ });
+ tempSummariesTable.setWidget(index, 0, idLabel);
+ tempSummariesTable
+ .setWidget(index, 1, new Label(summary.getDateString()));
+ tempSummariesTable
+ .setWidget(index, 2, new Label(summary.getNumTests() + ""));
+ // tempSummariesTable.setWidget( index, 3, new Label(summary.allTestsSucceeded() + ""));
+ }
+
+ return tempSummariesTable;
+ }
+
+ private void displayReport() {
+ FlexTable table = createReportTable();
+ reportPanel.remove(reportTable);
+ reportTable = table;
+ reportPanel.insert(reportTable, 1);
+ }
+
+// private native String getDocumentLocation() /*-{
+// return window.location;
+// }-*/;
+
+ private void displaySummaries() {
+ FlexTable table = createSummariesTable();
+ summariesPanel.remove(summariesTable);
+ summariesTable = table;
+ summariesPanel.insert(summariesTable, 1);
+ }
+
+ private String encode(String str) {
+ if (str.equals("")) {
+ return str;
+ }
+ return URL.encodeComponent(str);
+ }
+
+ private String getImageUrl(String report, String category, String testClass,
+ String testMethod, String agent) {
+ return imageServer + encode(report) + "/" +
+ encode(category) + "/" +
+ encode(testClass) + "/" +
+ encode(testMethod) + "/" +
+ encode(agent);
+ }
+
+ /**
+ * Loads report details asynchronously for a given report.
+ *
+ * @param id the non-null id of the report
+ */
+ private void getReportDetails(String id) {
+ statusLabel.setText("Retrieving the report...");
+ reportServer.getReport(id, new AsyncCallback() {
+ public void onFailure(Throwable caught) {
+ statusLabel.setText(caught.toString());
+ }
+
+ public void onSuccess(Object result) {
+ report = (Report) result;
+ statusLabel.setText("Finished fetching report details.");
+ displayReport();
+ }
+ });
+ }
+
+ private void init() {
+ topPanel = new VerticalPanel();
+
+ summariesPanel = new VerticalPanel();
+ summariesPanel.add(new HTML("<h3>Benchmark Reports</h3>"));
+ summariesTable = createSummariesTable();
+ summariesPanel.add(summariesTable);
+
+ reportPanel = new VerticalPanel();
+ detailsLabel = new HTML("<h3>Report Details</h3>");
+ reportPanel.add(detailsLabel);
+ reportTable = createReportTable();
+ // reportPanel.add( reportTable );
+
+ topPanel.add(summariesPanel);
+ CellPanel spacerPanel = new HorizontalPanel();
+ spacerPanel.setSpacing(10);
+ spacerPanel.add(new Label());
+ topPanel.add(spacerPanel);
+ topPanel.add(reportPanel);
+ final RootPanel root = RootPanel.get();
+
+ root.add(topPanel);
+
+ statusLabel = new HTML("Select a report.");
+ root.add(statusLabel);
+ }
+}
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/Result.java b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/Result.java
new file mode 100644
index 0000000..79453d5
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/Result.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2007 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.viewer.client;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+import java.util.List;
+
+/**
+ * A data object for Benchmark results.
+ */
+public class Result implements IsSerializable {
+
+ private String agent;
+
+ private String host;
+
+ private List trials;
+
+ public Result() {
+ }
+
+ public String getAgent() {
+ return agent;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public List getTrials() {
+ return trials;
+ }
+
+ public void setAgent(String agent) {
+ this.agent = agent;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public void setTrials(List trials) {
+ this.trials = trials;
+ }
+}
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/Trial.java b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/Trial.java
new file mode 100644
index 0000000..b949db0
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/client/Trial.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2007 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.viewer.client;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * A data object for Trial.
+ */
+public class Trial implements IsSerializable {
+
+ String exception;
+
+ double runTimeMillis;
+
+ Map/*<String,String>*/ variables;
+
+ public Trial() {
+ this.variables = new HashMap();
+ }
+
+ public String getException() {
+ return exception;
+ }
+
+ public double getRunTimeMillis() {
+ return runTimeMillis;
+ }
+
+ /**
+ * Returns the names and values of the variables used in the test. If there
+ * were no variables, the map is empty.
+ */
+ public Map getVariables() {
+ return variables;
+ }
+
+ public void setException(String exception) {
+ this.exception = exception;
+ }
+
+ public void setRunTimeMillis(double runTimeMillis) {
+ this.runTimeMillis = runTimeMillis;
+ }
+}
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/public/ReportViewer.html b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/public/ReportViewer.html
new file mode 100644
index 0000000..0d43991
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/public/ReportViewer.html
@@ -0,0 +1,9 @@
+<html>
+ <head>
+ <meta name='gwt:module' content='com.google.gwt.junit.viewer.ReportViewer'>
+ <title>ReportViewer</title>
+ </head>
+ <body bgcolor="white">
+ <script language="javascript" src="gwt.js"></script>
+ </body>
+</html>
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/BenchmarkXml.java b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/BenchmarkXml.java
new file mode 100644
index 0000000..184e7ed
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/BenchmarkXml.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2007 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.viewer.server;
+
+import com.google.gwt.junit.viewer.client.Benchmark;
+
+import org.w3c.dom.Element;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Converts an XML element to a Benchmark object.
+ *
+ */
+class BenchmarkXml {
+
+ public static Benchmark fromXml( Element element ) {
+ Benchmark benchmark = new Benchmark();
+ benchmark.setClassName(element.getAttribute( "class" ));
+ benchmark.setName(element.getAttribute( "name" ));
+ benchmark.setDescription(element.getAttribute( "description" ));
+
+ List children = ReportXml.getElementChildren( element, "result" );
+ benchmark.setResults( new ArrayList/*<Result>*/(children.size()));
+ for ( int i = 0; i < children.size(); ++i ) {
+ benchmark.getResults().add( ResultXml.fromXml( (Element) children.get( i )));
+ }
+
+ Element code = ReportXml.getElementChild( element, "source_code" );
+ if ( code != null ) {
+ benchmark.setSourceCode( ReportXml.getText(code) );
+ }
+
+ return benchmark;
+ }
+}
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/CategoryXml.java b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/CategoryXml.java
new file mode 100644
index 0000000..a06bbea
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/CategoryXml.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2007 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.viewer.server;
+
+import com.google.gwt.junit.viewer.client.Category;
+
+import org.w3c.dom.Element;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Converts an XML Element to a Category object.
+ *
+ */
+class CategoryXml {
+ public static Category fromXml( Element element ) {
+ Category category = new Category();
+ category.setName(element.getAttribute( "name" ));
+ category.setDescription(element.getAttribute( "description" ));
+
+ List/*<Element>*/ children = ReportXml.getElementChildren( element, "benchmark" );
+ category.setBenchmarks(new ArrayList/*<Benchmark>*/( children.size()));
+ for ( int i = 0; i < children.size(); ++i ) {
+ category.getBenchmarks().add( BenchmarkXml.fromXml( (Element) children.get( i )));
+ }
+
+ return category;
+ }
+}
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/ReportDatabase.java b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/ReportDatabase.java
new file mode 100644
index 0000000..b91a7d6
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/ReportDatabase.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2007 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.viewer.server;
+
+import com.google.gwt.junit.viewer.client.Report;
+import com.google.gwt.junit.viewer.client.ReportSummary;
+import com.google.gwt.junit.client.Benchmark;
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+import org.w3c.dom.Document;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.DocumentBuilder;
+
+/**
+ * Serves up benchmark reports created during JUnit execution.
+ *
+ * The benchmark reports are read from the path specified by the system property
+ * named <code>Benchmark.REPORT_PATH</code>. In the case the property is not set,
+ * they are read from the user's current working directory.
+ *
+ */
+public class ReportDatabase {
+
+ public static class BadPathException extends RuntimeException {
+ String path;
+ public BadPathException( String path ) {
+ super("The path " + path + " does not exist." );
+ this.path = path;
+ }
+ public String getPath() {
+ return path;
+ }
+ }
+
+ private static class ReportEntry {
+ private ReportSummary summary;
+ private Report report;
+ private long lastModified;
+ public ReportEntry( Report report, ReportSummary summary, long lastModified ) {
+ this.report = report;
+ this.summary = summary;
+ this.lastModified = lastModified;
+ }
+ }
+
+ private static class ReportFile {
+ File file;
+ long lastModified;
+ ReportFile( File f ) {
+ this.file = f;
+ this.lastModified = f.lastModified();
+ }
+ }
+
+ /**
+ * The amount of time to go between report updates.
+ *
+ */
+ private static final int UPDATE_DURATION_MILLIS = 30000;
+
+ private static ReportDatabase database = new ReportDatabase();
+
+ public static ReportDatabase getInstance() {
+ return database;
+ }
+
+ private static String getReportId( File f ) {
+ return f.getName();
+ }
+
+ /**
+ * A list of all reports by id.
+ */
+ private Map/*<String,ReportEntry>*/ reports = new HashMap/*<String,ReportEntry>*/();
+
+ /**
+ * The last time we updated our reports.
+ *
+ */
+ private long lastUpdateMillis = -1L;
+
+ /**
+ * Lock for updating from file system.
+ * (Guarantees a single update while not holding reportsLock open).
+ *
+ */
+ private Object updateLock = new Object();
+
+ /**
+ * Are we currently undergoing updating?
+ *
+ */
+ private boolean updating = false;
+
+ /**
+ * Lock for reports.
+ *
+ */
+ private Object reportsLock = new Object();
+
+ /**
+ * The path to read benchmark reports from.
+ *
+ */
+ private final String reportPath;
+
+ private ReportDatabase() throws BadPathException {
+ String path = System.getProperty( Benchmark.REPORT_PATH );
+ if ( path == null || path.trim().equals( "" ) ) {
+ path = System.getProperty( "user.dir" );
+ }
+ reportPath = path;
+
+ if (! new File(reportPath).exists()) {
+ throw new BadPathException(reportPath);
+ }
+ }
+
+ public Report getReport( String reportId ) {
+ synchronized ( reportsLock ) {
+ ReportEntry entry = (ReportEntry)reports.get( reportId );
+ return entry == null ? null : entry.report;
+ }
+ }
+
+ public List/*<ReportSummary>*/ getReportSummaries() {
+
+ /**
+ * There are probably ways to make this faster, but I've taken
+ * basic precautions to try to make this scale ok with multiple clients.
+ */
+
+ boolean update = false;
+
+ // See if we need to do an update
+ // Go ahead and let others continue reading, even if an update is required.
+ synchronized ( updateLock ) {
+ if ( ! updating ) {
+ long currentTime = System.currentTimeMillis();
+
+ if ( currentTime > lastUpdateMillis + UPDATE_DURATION_MILLIS ) {
+ update = updating = true;
+ }
+ }
+ }
+
+ if ( update ) {
+ updateReports();
+ }
+
+ synchronized ( reportsLock ) {
+ List/*<ReportSummary>*/ summaries = new ArrayList/*<ReportSummary>*/( reports.size() );
+ for ( Iterator it = reports.values().iterator(); it.hasNext(); ) {
+ ReportEntry entry = (ReportEntry) it.next();
+ summaries.add( entry.summary );
+ }
+ return summaries;
+ }
+ }
+
+ private void updateReports() {
+
+ File path = new File( reportPath );
+
+ File[] files = path.listFiles( new FilenameFilter() {
+ public boolean accept( File f, String name ) {
+ return name.startsWith("report-") && name.endsWith( ".xml" );
+ }
+ } );
+
+ Map filesToUpdate = new HashMap();
+ Map filesById = new HashMap();
+ for ( int i = 0; i < files.length; ++i ) {
+ File f = files[ i ];
+ filesById.put( getReportId( f ), new ReportFile( f ) );
+ }
+
+ // Lock temporarily so we can determine what needs updating
+ // (This could be a read-lock - not a general read-write lock,
+ // if we moved dead report removal outside of this critical section).
+ synchronized ( reportsLock ) {
+
+ // Add reports which need to be updated or are new
+ for ( int i = 0; i < files.length; ++i ) {
+ File file = files[ i ];
+ String reportId = getReportId( file );
+ ReportEntry entry = (ReportEntry) reports.get( reportId );
+ if ( entry == null || entry.lastModified < file.lastModified() ) {
+ filesToUpdate.put( reportId, null );
+ }
+ }
+
+ // Remove reports which no longer exist
+ for ( Iterator it = reports.keySet().iterator(); it.hasNext(); ) {
+ String id = (String)it.next();
+ if ( filesById.get(id) == null) {
+ it.remove();
+ }
+ }
+ }
+
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setIgnoringElementContentWhitespace( true );
+ factory.setIgnoringComments( true );
+ DocumentBuilder builder = factory.newDocumentBuilder();
+
+ for ( Iterator it = filesToUpdate.keySet().iterator(); it.hasNext(); ) {
+ String id = (String)it.next();
+ ReportFile reportFile = (ReportFile) filesById.get(id);
+ String filePath = reportFile.file.getAbsolutePath();
+ Document doc = builder.parse(filePath);
+ Report report = ReportXml.fromXml( doc.getDocumentElement() );
+ report.setId(id);
+ ReportSummary summary = report.getSummary();
+ long lastModified = new File(filePath).lastModified();
+ filesToUpdate.put(id, new ReportEntry( report, summary, lastModified));
+ }
+
+ // Update the reports
+ synchronized ( reportsLock ) {
+ for ( Iterator it = filesToUpdate.keySet().iterator(); it.hasNext(); ) {
+ String id = (String)it.next();
+ reports.put( id, filesToUpdate.get( id ));
+ }
+ }
+ } catch ( Exception e ) {
+ // Even if we got an error, we'll just try again on the next update
+ // This might happen if a report has only been partially written, for
+ // example.
+ e.printStackTrace();
+ }
+
+ synchronized ( updateLock ) {
+ updating = false;
+ lastUpdateMillis = System.currentTimeMillis();
+ }
+ }
+}
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/ReportImageServer.java b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/ReportImageServer.java
new file mode 100644
index 0000000..e8752bc
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/ReportImageServer.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright 2007 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.viewer.server;
+
+import com.google.gwt.junit.viewer.client.Report;
+import com.google.gwt.junit.viewer.client.Category;
+import com.google.gwt.junit.viewer.client.Benchmark;
+import com.google.gwt.junit.viewer.client.Result;
+import com.google.gwt.junit.viewer.client.Trial;
+import com.google.gwt.junit.viewer.client.BrowserInfo;
+
+import org.jfree.data.category.DefaultCategoryDataset;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.encoders.EncoderUtil;
+import org.jfree.chart.encoders.ImageFormat;
+import org.jfree.chart.plot.PlotOrientation;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.InputStream;
+import java.io.ByteArrayInputStream;
+import java.net.URLDecoder;
+import java.awt.image.BufferedImage;
+import java.awt.Font;
+import java.util.List;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.TreeMap;
+import java.util.ArrayList;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.ServletException;
+
+/**
+ * Serves up report images for the ReportViewer application. Generates the
+ * charts/graphs which contain the benchmarking data for a report.
+ *
+ * <p>This servlet requires the name of the report file, the category, the
+ * benchmark class, the test method, and the browser agent.<p>
+ *
+ * <p>An Example URI:<pre>
+ * /com.google.gwt.junit.viewer.ReportViewer/test_images/
+ * report-12345.xml/
+ * RemoveCategory/
+ * com.google.gwt.junit.client.ArrayListAndVectorBenchmark/
+ * testArrayListRemoves/
+ * Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.12) Gecko/20050920/
+ * </pre></p>
+ */
+public class ReportImageServer extends HttpServlet {
+
+ private static final String charset = "UTF-8";
+
+ private static void copy(InputStream in, OutputStream out)
+ throws IOException {
+ byte[] buf = new byte[512];
+
+ while (true) {
+ int bytesRead = in.read(buf);
+ if (bytesRead == -1) {
+ break;
+ }
+ out.write(buf, 0, bytesRead);
+ }
+ }
+
+ private JFreeChart createChart(String testName, Result result, String title) {
+
+ // Display the trial data - we might need meta information from the result
+ // that tells us how many total variables there are, what types they are of,
+ // etc....
+ List trials = result.getTrials();
+ Trial firstTrial = (Trial) trials.get(0);
+
+ // Pick the domain and series variables for our graph.
+ // Right now we only handle up to two "user" variables.
+ // We set the domain variable to the be the one containing the most unique values.
+ int numVariables = firstTrial.getVariables().size();
+
+ String domainVariable = null;
+ String seriesVariable = null;
+
+ Map/*<String,Set<String>>*/ variableValues = null;
+
+ if (numVariables == 1) {
+ domainVariable = (String) firstTrial.getVariables().keySet().iterator()
+ .next();
+ } else {
+ // TODO(tobyr): Do something smarter, like allow the user to specify which variables
+ // are domain and series, along with the variables which are held constant.
+
+ variableValues = new HashMap();
+
+ for (int i = 0; i < trials.size(); ++i) {
+ Trial trial = (Trial) trials.get(i);
+ Map variables = trial.getVariables();
+
+ for (Iterator it = variables.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ String variable = (String) entry.getKey();
+ String value = (String) entry.getValue();
+ Set set = (Set) variableValues.get(variable);
+ if (set == null) {
+ set = new TreeSet();
+ variableValues.put(variable, set);
+ }
+ set.add(value);
+ }
+ }
+
+ TreeMap numValuesMap = new TreeMap();
+
+ for (Iterator it = variableValues.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ String variable = (String) entry.getKey();
+ Set values = (Set) entry.getValue();
+ Integer numValues = new Integer(values.size());
+ List variables = (List) numValuesMap.get(numValues);
+ if (variables == null) {
+ variables = new ArrayList();
+ numValuesMap.put(numValues, variables);
+ }
+ variables.add(variable);
+ }
+
+ if (numValuesMap.values().size() > 0) {
+ domainVariable = (String) ((List) numValuesMap
+ .get(numValuesMap.lastKey())).get(0);
+ seriesVariable = (String) ((List) numValuesMap
+ .get(numValuesMap.firstKey())).get(0);
+ }
+ }
+
+ String valueTitle = "time (ms)"; // This axis is time across all charts.
+
+ if (numVariables == 0) {
+ // Show a bar graph, with a single centered simple bar
+ // 0 variables means there is only 1 trial
+ Trial trial = (Trial) trials.iterator().next();
+
+ DefaultCategoryDataset data = new DefaultCategoryDataset();
+ data.addValue(trial.getRunTimeMillis(), "result", "result");
+
+ return ChartFactory.createBarChart(title, testName, valueTitle,
+ data, PlotOrientation.VERTICAL, false, false, false);
+ } else if (numVariables == 1) {
+
+ // Show a line graph with only 1 series
+ // Or.... choose between a line graph and a bar graph depending upon whether the
+ // type of the domain is numeric.
+
+ XYSeriesCollection data = new XYSeriesCollection();
+
+ XYSeries series = new XYSeries(domainVariable);
+
+ for (Iterator it = trials.iterator(); it.hasNext();) {
+ Trial trial = (Trial) it.next();
+ if ( trial.getException() != null) {
+ continue;
+ }
+ double time = trial.getRunTimeMillis();
+ String domainValue = (String) trial.getVariables().get(domainVariable);
+ series.add(Double.parseDouble(domainValue), time);
+ }
+
+ data.addSeries(series);
+
+ return ChartFactory.createXYLineChart(title, domainVariable, valueTitle,
+ data, PlotOrientation.VERTICAL, false, false, false);
+ } else if (numVariables == 2) {
+ // Show a line graph with multiple series
+ XYSeriesCollection data = new XYSeriesCollection();
+
+ Set seriesValues = (Set) variableValues.get(seriesVariable);
+
+ for (Iterator it = seriesValues.iterator(); it.hasNext();) {
+ String seriesValue = (String) it.next();
+ XYSeries series = new XYSeries(seriesValue);
+
+ for (Iterator trialsIt = trials.iterator(); trialsIt.hasNext();) {
+ Trial trial = (Trial) trialsIt.next();
+ if ( trial.getException() != null) {
+ continue;
+ }
+ Map variables = trial.getVariables();
+ if (variables.get(seriesVariable).equals(seriesValue)) {
+ double time = trial.getRunTimeMillis();
+ String domainValue = (String) trial.getVariables()
+ .get(domainVariable);
+ series.add(Double.parseDouble(domainValue), time);
+ }
+ }
+ data.addSeries(series);
+ }
+
+ return ChartFactory.createXYLineChart(title, domainVariable, valueTitle,
+ data, PlotOrientation.VERTICAL, true, true, false);
+ }
+
+ return null;
+
+ // Sample JFreeChart code for creating certain charts:
+ // Leaving this around until we can handle multivariate charts in dimensions
+ // greater than two.
+
+ // Code for creating a category data set - probably better with a bar chart instead of line chart
+ /*
+ DefaultCategoryDataset data = new DefaultCategoryDataset();
+ String series = domainVariable;
+
+ for ( Iterator it = trials.iterator(); it.hasNext(); ) {
+ Trial trial = (Trial) it.next();
+ double time = trial.getRunTimeMillis();
+ String domainValue = (String) trial.getVariables().get( domainVariable );
+ data.addValue( time, series, domainValue );
+ }
+
+ String title = "";
+ String categoryTitle = domainVariable;
+ PlotOrientation orientation = PlotOrientation.VERTICAL;
+
+ chart = ChartFactory.createLineChart( title, categoryTitle, valueTitle, data, orientation, true, true, false );
+ */
+
+ /*
+ DefaultCategoryDataset data = new DefaultCategoryDataset();
+ String series1 = "firefox";
+ String series2 = "ie";
+
+ data.addValue( 1.0, series1, "1024");
+ data.addValue( 2.0, series1, "2048");
+ data.addValue( 4.0, series1, "4096");
+ data.addValue( 8.0, series1, "8192");
+
+ data.addValue( 2.0, series2, "1024");
+ data.addValue( 4.0, series2, "2048");
+ data.addValue( 8.0, series2, "4096");
+ data.addValue( 16.0, series2,"8192");
+
+ String title = "";
+ String categoryTitle = "size";
+ PlotOrientation orientation = PlotOrientation.VERTICAL;
+
+ chart = ChartFactory.createLineChart( title, categoryTitle, valueTitle, data, orientation, true, true, false );
+ */
+ }
+
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ handleRequest(request, response);
+ }
+
+ public void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ handleRequest(request, response);
+ }
+
+ private Benchmark getBenchmarkByName(List benchmarks, String name) {
+ for (Iterator it = benchmarks.iterator(); it.hasNext();) {
+ Benchmark benchmark = (Benchmark) it.next();
+ if (benchmark.getName().equals(name)) {
+ return benchmark;
+ }
+ }
+ return null;
+ }
+
+ private Category getCategoryByName(List categories, String categoryName) {
+ for (Iterator catIt = categories.iterator(); catIt.hasNext();) {
+ Category category = (Category) catIt.next();
+ if (category.getName().equals(categoryName)) {
+ return category;
+ }
+ }
+ return null;
+ }
+
+ private Result getResultsByAgent(List results, String agent) {
+ for (Iterator it = results.iterator(); it.hasNext();) {
+ Result result = (Result) it.next();
+ if (result.getAgent().equals(agent)) {
+ return result;
+ }
+ }
+ return null;
+ }
+
+ private void handleRequest(HttpServletRequest request,
+ HttpServletResponse response)
+ throws IOException, ServletException {
+
+ String uri = request.getRequestURI();
+ String requestString = uri.split("test_images/")[1];
+ String[] requestParams = requestString.split("/");
+
+ String reportName = URLDecoder.decode(requestParams[0], charset);
+ String categoryName = URLDecoder.decode(requestParams[1], charset);
+ String className = URLDecoder.decode(requestParams[2], charset);
+ String testName = URLDecoder.decode(requestParams[3], charset);
+ String agent = URLDecoder.decode(requestParams[4], charset);
+
+ ReportDatabase db = ReportDatabase.getInstance();
+ Report report = db.getReport(reportName);
+ List categories = report.getCategories();
+ Category category = getCategoryByName(categories, categoryName);
+ List benchmarks = category.getBenchmarks();
+ Benchmark benchmark = getBenchmarkByName(benchmarks, testName);
+ List results = benchmark.getResults();
+ Result result = getResultsByAgent(results, agent);
+
+ String title = BrowserInfo.getBrowser(agent);
+ JFreeChart chart = null;
+
+ try {
+ chart = createChart(testName, result, title);
+
+ if (chart == null) {
+ super.doGet(request, response);
+ return;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ chart.getTitle().setFont(Font.decode("Arial"));
+
+ // Try to fit all the graphs into a 1024 window, with a min of 240 and a max of 480
+ final int graphWidth = Math
+ .max(240, Math.min(480, (1024 - 10 * results.size()) / results.size()));
+ BufferedImage img = chart.createBufferedImage(graphWidth, 240);
+ byte[] image = EncoderUtil.encode(img, ImageFormat.PNG);
+
+ response.setContentType("image/png");
+
+ OutputStream output = response.getOutputStream();
+ copy(new ByteArrayInputStream(image), output);
+ }
+}
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/ReportServerImpl.java b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/ReportServerImpl.java
new file mode 100644
index 0000000..f40ded6
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/ReportServerImpl.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2007 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.viewer.server;
+
+import com.google.gwt.user.server.rpc.RemoteServiceServlet;
+import com.google.gwt.junit.viewer.client.ReportServer;
+import com.google.gwt.junit.viewer.client.Report;
+
+import java.util.List;
+
+/**
+ * Implements the ReportServer RPC interface.
+ */
+public class ReportServerImpl extends RemoteServiceServlet
+ implements ReportServer {
+
+ public Report getReport(String reportId) {
+ return ReportDatabase.getInstance().getReport(reportId);
+ }
+
+ /**
+ * @gwt.typeArgs <com.google.gwt.junit.viewer.client.ReportSummary>
+ */
+ public List/*<ReportSummary>*/ getReportSummaries() {
+ return ReportDatabase.getInstance().getReportSummaries();
+ }
+}
\ No newline at end of file
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/ReportXml.java b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/ReportXml.java
new file mode 100644
index 0000000..6a3dee0
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/ReportXml.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2007 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.viewer.server;
+
+import com.google.gwt.junit.viewer.client.Report;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Node;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Date;
+
+/**
+ * Hydrates a Report from its XML representation.
+ */
+class ReportXml {
+
+ /**
+ * Hydrates a Report from its XML representation.
+ *
+ * @param element The XML element to hydrate from.
+ * @return a new report (with null id)
+ */
+ public static Report fromXml(Element element) {
+
+ Report report = new Report();
+ String dateString = element.getAttribute("date");
+
+ try {
+ DateFormat format = DateFormat.getDateTimeInstance();
+ Date d = format.parse(dateString);
+ report.setDate(d);
+ report.setDateString(format.format(d));
+ } catch (ParseException e) {
+ // let date remain null if it doesn't parse correctly
+ }
+
+ report.setGwtVersion(element.getAttribute("gwt_version"));
+
+ List/*<Element>*/ children = getElementChildren(element, "category");
+ report.setCategories(new ArrayList/*<Category>*/(children.size()));
+ for (int i = 0; i < children.size(); ++i) {
+ report.getCategories()
+ .add(CategoryXml.fromXml((Element) children.get(i)));
+ }
+
+ return report;
+ }
+
+ static Element getElementChild(Element e, String name) {
+ NodeList children = e.getElementsByTagName(name);
+ return children.getLength() == 0 ? null : (Element) children.item(0);
+ }
+
+ static List/*<Element>*/ getElementChildren(Element e, String name) {
+ NodeList children = e.getElementsByTagName(name);
+ int numElements = children.getLength();
+ List/*<Element>*/ elements = new ArrayList/*<Element>*/(numElements);
+ for (int i = 0; i < children.getLength(); ++i) {
+ Node n = children.item(i);
+ elements.add((Element) n);
+ }
+ return elements;
+ }
+
+ static String getText(Element e) {
+ Node n = e.getFirstChild();
+ return n == null ? null : n.getNodeValue();
+ }
+}
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/ResultXml.java b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/ResultXml.java
new file mode 100644
index 0000000..cef6932
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/ResultXml.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2007 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.viewer.server;
+
+import com.google.gwt.junit.viewer.client.Result;
+
+import org.w3c.dom.Element;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Hydrates a benchmark Result from an XML Element.
+ *
+ */
+public class ResultXml {
+ public static Result fromXml( Element element ) {
+ Result result = new Result();
+ result.setAgent(element.getAttribute( "agent" ));
+ result.setHost(element.getAttribute( "host" ));
+
+ List/*<Element>*/ children = ReportXml.getElementChildren( element, "trial" );
+
+ ArrayList trials = new ArrayList( children.size() );
+ result.setTrials(trials);
+
+ for ( int i = 0; i < children.size(); ++i ) {
+ trials.add( TrialXml.fromXml( (Element) children.get(i) ));
+ }
+
+ // TODO(tobyr) Put some type information in here for the variables
+
+ return result;
+ }
+}
diff --git a/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/TrialXml.java b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/TrialXml.java
new file mode 100644
index 0000000..530f8cd
--- /dev/null
+++ b/tools/benchmark-viewer/src/com/google/gwt/junit/viewer/server/TrialXml.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2007 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.viewer.server;
+
+import com.google.gwt.junit.viewer.client.Trial;
+
+import org.w3c.dom.Element;
+
+import java.util.List;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Hydrates a benchmark Trial from an XML Element.
+ *
+ */
+class TrialXml {
+
+ public static Trial fromXml( Element element ) {
+ Trial trial = new Trial();
+
+ String timing = element.getAttribute( "timing" );
+
+ if ( timing != null ) {
+ trial.setRunTimeMillis(Double.parseDouble( timing ));
+ }
+
+ Element exception = ReportXml.getElementChild( element, "exception" );
+ if ( exception != null ) {
+ trial.setException( ReportXml.getText( exception ) );
+ }
+
+ List elements = ReportXml.getElementChildren( element, "variable" );
+
+ Map variables = trial.getVariables();
+
+ for ( Iterator it = elements.iterator(); it.hasNext(); ) {
+ Element e = (Element) it.next();
+ String name = e.getAttribute( "name" );
+ String value = e.getAttribute( "value" );
+ variables.put( name, value ) ;
+ }
+
+ return trial;
+ }
+}
diff --git a/tools/build.xml b/tools/build.xml
new file mode 100755
index 0000000..f17e778
--- /dev/null
+++ b/tools/build.xml
@@ -0,0 +1,15 @@
+<project name="tools" default="build" basedir=".">
+ <property name="gwt.root" location=".." />
+ <property name="project.tail" value="tools" />
+ <import file="${gwt.root}/common.ant.xml" />
+
+ <!-- "build" is the default when subprojects are directly targetted -->
+ <property name="target" value="build" />
+
+ <target name="build" depends="benchmark-viewer" description="Builds all targets"/>
+
+ <target name="benchmark-viewer" depends="" description="Run benchmark-viewer">
+ <gwt.ant dir="benchmark-viewer" />
+ </target>
+</project>
+
diff --git a/user/build.xml b/user/build.xml
index 7c2fa75..2957fd7 100755
--- a/user/build.xml
+++ b/user/build.xml
@@ -9,6 +9,11 @@
<fileset id="default.hosted.tests" dir="${javac.junit.out}"
includes="${gwt.junit.testcase.includes}">
<!--
+ Requires manual testing, because it generates intentional failures.
+ -->
+ <exclude name="com/google/gwt/junit/client/ParallelRemoteTest.class" />
+
+ <!--
Causes a security dialog to popup and subsequently blocks testing
-->
<exclude name="com/google/gwt/user/client/ui/FormPanelTest.class" />
@@ -95,6 +100,7 @@
<classpath>
<pathelement location="${gwt.tools.lib}/tomcat/servlet-api-2.4.jar" />
<pathelement location="${gwt.tools.lib}/junit/junit-3.8.1.jar" />
+ <pathelement location="${gwt.tools.lib}/jfreechart/jfreechart-1.0.3.jar" />
<pathelement location="${gwt.dev.jar}" />
</classpath>
</gwt.javac>
diff --git a/user/javadoc/com/google/gwt/examples/Benchmarks.gwt.xml b/user/javadoc/com/google/gwt/examples/Benchmarks.gwt.xml
new file mode 100644
index 0000000..b61f09d
--- /dev/null
+++ b/user/javadoc/com/google/gwt/examples/Benchmarks.gwt.xml
@@ -0,0 +1,3 @@
+<module>
+ <source path='benchmarks'/>
+</module>
diff --git a/user/javadoc/com/google/gwt/examples/benchmarks/AllocBenchmark.java b/user/javadoc/com/google/gwt/examples/benchmarks/AllocBenchmark.java
new file mode 100644
index 0000000..54e4c58
--- /dev/null
+++ b/user/javadoc/com/google/gwt/examples/benchmarks/AllocBenchmark.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2007 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.examples.benchmarks;
+
+import com.google.gwt.junit.client.Category;
+import com.google.gwt.junit.client.Benchmark;
+
+/**
+ * Provides profile statistics on allocation times for different kinds of
+ * objects.
+ *
+ * @gwt.benchmark.category com.google.gwt.user.client.ui.AllocBenchmark.AllocCategory
+ *
+ */
+public class AllocBenchmark extends Benchmark {
+
+ /**
+ * @gwt.benchmark.name Allocation Benchmarks
+ * @gwt.benchmark.description A series of benchmarks that tests the impact of
+ * different kinds of allocations.
+ *
+ */
+ class AllocCategory implements Category {
+ }
+
+ private static final int numAllocs = 1000;
+
+ public String getModuleName() {
+ return "com.google.gwt.examples.Benchmarks";
+ }
+
+ /**
+ * Allocates java.lang.Object in a for loop 1,000 times.
+ *
+ * The current version of the compiler lifts the declaration of obj outside
+ * of this loop and also does constant folding of numAllocs.
+ * Also, this loop allocs the GWT JS mirror for java.lang.Object
+ * <em>NOT</em> an empty JS object, for example.
+ *
+ */
+ public void testJavaObjectAlloc() {
+ for ( int i = 0; i < numAllocs; ++i ) {
+ Object obj = new Object();
+ }
+ }
+
+ /**
+ * Compares GWT mirror allocations of java.lang.Object to an empty JS object.
+ */
+ public native void testJsniObjectAlloc1() /*-{
+ for (var i = 0; i < @com.google.gwt.examples.benchmarks.AllocBenchmark::numAllocs; ++i ) {
+ var obj = {}; // An empty JS object alloc
+ }
+ }-*/;
+
+ /**
+ * Like version 1, but also folds the constant being used in the iteration.
+ */
+ public native void testJsniObjectAlloc2() /*-{
+ for (var i = 0; i < 1000; ++i ) {
+ var obj = {}; // An empty JS object alloc
+ }
+ }-*/;
+
+ /**
+ * Like version 2, but hoists the variable declaration from the loop.
+ */
+ public native void testJsniObjectAlloc3() /*-{
+ var obj;
+ for (var i = 0; i < 1000; ++i ) {
+ obj = {}; // An empty JS object alloc
+ }
+ }-*/;
+
+ /**
+ * Like version 3, but counts down (and in a slightly different range).
+ */
+ public native void testJsniObjectAlloc4() /*-{
+ var obj;
+ for (var i = 1000; i > 0; --i ) {
+ obj = {}; // An empty JS object alloc
+ }
+ }-*/;
+}
+
diff --git a/user/javadoc/com/google/gwt/examples/benchmarks/ArrayListAndVectorBenchmark.java b/user/javadoc/com/google/gwt/examples/benchmarks/ArrayListAndVectorBenchmark.java
new file mode 100644
index 0000000..9a34662
--- /dev/null
+++ b/user/javadoc/com/google/gwt/examples/benchmarks/ArrayListAndVectorBenchmark.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2007 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.examples.benchmarks;
+
+import com.google.gwt.junit.client.IntRange;
+import com.google.gwt.junit.client.Benchmark;
+import com.google.gwt.junit.client.Operator;
+import com.google.gwt.junit.client.Range;
+
+import java.util.Vector;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Iterator;
+
+/**
+ * Benchmarks common operations on both ArrayLists and Vectors.
+ * This test covers appends, inserts, and removes for various sizes
+ * and positions on both ArrayLists and Vectors.
+ *
+ */
+public class ArrayListAndVectorBenchmark extends Benchmark {
+
+ /**
+ * Many profiled widgets have position dependent insert/remove code.
+ * <code>Position</code> is a helper class meant to capture the positional
+ * information for these sorts of operations.
+ */
+ protected static class Position {
+
+ public static final Position BEGIN = new Position("at the beginning");
+ public static final Position END = new Position("at the end");
+ public static final Position NONE = new Position("no location specified");
+ public static final Position VARIED = new Position("in varied locations");
+
+ public static final Range positions = new Range() {
+ public Iterator iterator() {
+ return Arrays.asList( new Position[] {BEGIN, END, NONE, VARIED } ).iterator();
+ }
+ };
+
+ public static final Range positions2 = new Range() {
+ public Iterator iterator() {
+ return Arrays.asList( new Position[] {BEGIN, END, VARIED } ).iterator();
+ }
+ };
+
+ private String label;
+
+ /**
+ * Constructor for <code>Position</code>.
+ */
+ public Position(String label) {
+ this.label = label;
+ }
+
+ public String toString() {
+ return " " + label;
+ }
+ }
+
+ private static final int PRIME = 3001;
+
+ final IntRange insertRemoveRange = new IntRange(64, Integer.MAX_VALUE,
+ Operator.MULTIPLY, 2);
+
+ final IntRange baseRange = new IntRange(512, Integer.MAX_VALUE,
+ Operator.MULTIPLY, 2);
+
+ ArrayList list;
+ Vector vector;
+ int index = 0;
+
+ public String getModuleName() {
+ return "com.google.gwt.examples.Benchmarks";
+ }
+
+ /**
+ * Appends <code>size</code> items to an empty ArrayList.
+ * @gwt.benchmark.param size -limit = baseRange
+ */
+ public void testArrayListAdds( Integer size ) {
+ int num = size.intValue();
+ for (int i = 0; i < num; i++) {
+ list.add("hello");
+ }
+ }
+
+ // Required for JUnit
+ public void testArrayListAdds() {
+ }
+
+ /**
+ * Performs <code>size</code> gets on an ArrayList of size, <code>size</code>.
+ * @gwt.benchmark.param size -limit = baseRange
+ */
+ public void testArrayListGets( Integer size ) {
+ int num = size.intValue();
+ for (int i = 0; i < num; i++) {
+ list.get(i);
+ }
+ }
+
+ // Required for JUnit
+ public void testArrayListGets() {
+ }
+
+ /**
+ * Performs <code>size</code> inserts at position, <code>where</code>, on an
+ * empty ArrayList.
+ * @gwt.benchmark.param where = Position.positions
+ * @gwt.benchmark.param size -limit = insertRemoveRange
+ */
+ public void testArrayListInserts( Position where, Integer size ) {
+ insertIntoCollection(size, where, list);
+ }
+
+ // Required for JUnit
+ public void testArrayListInserts() {
+ }
+
+ /**
+ * Performs <code>size</code> removes at position, <code>where</code>, on an
+ * ArrayList of size, <code>size</code>.
+ * @gwt.benchmark.param where = Position.positions2
+ * @gwt.benchmark.param size -limit = insertRemoveRange
+ */
+ public void testArrayListRemoves(Position where, Integer size) {
+ removeFromCollection(size, where, list);
+ }
+
+ // Required for JUnit
+ public void testArrayListRemoves() {
+ }
+
+ /**
+ * Appends <code>size</code> items to an empty Vector.
+ * @gwt.benchmark.param size -limit = baseRange
+ */
+ public void testVectorAdds( Integer size ) {
+ int num = size.intValue();
+ for (int i = 0; i < num; i++) {
+ vector.add("hello");
+ }
+ }
+
+ // Required for JUnit
+ public void testVectorAdds() {
+ }
+
+ /**
+ * Performs <code>size</code> gets on a Vector of size, <code>size</code>.
+ * @gwt.benchmark.param size -limit = baseRange
+ */
+ public void testVectorGets( Integer size ) {
+ int num = size.intValue();
+ for (int i = 0; i < num; i++) {
+ vector.get(i);
+ }
+ }
+
+ // Required for JUnit
+ public void testVectorGets() {
+ }
+
+ /**
+ * Performs <code>size</code> inserts at position, <code>where</code>, on an
+ * empty Vector.
+ * @gwt.benchmark.param where = Position.positions
+ * @gwt.benchmark.param size -limit = insertRemoveRange
+ */
+ public void testVectorInserts(Position where, Integer size) {
+ insertIntoCollection( size, where, vector );
+ }
+
+ // Required for JUnit
+ public void testVectorInserts() {
+ }
+
+ /**
+ * Performs <code>size</code> removes at position, <code>where</code>, on a
+ * Vector of size, <code>size</code>.
+ * @gwt.benchmark.param where = Position.positions2
+ * @gwt.benchmark.param size -limit = insertRemoveRange
+ */
+ public void testVectorRemoves( Position where, Integer size ) {
+ removeFromCollection( size, where, vector );
+ }
+
+ // Required for JUnit
+ public void testVectorRemoves() {
+ }
+
+ void beginArrayListAdds( Integer size ) {
+ list = new ArrayList();
+ }
+
+ void beginArrayListGets( Integer size ) {
+ createArrayList( size );
+ }
+
+ void beginArrayListInserts(Position where, Integer size) {
+ list = new ArrayList();
+ index = 0;
+ }
+
+ void beginArrayListRemoves(Position where, Integer size) {
+ beginArrayListInserts(where, size);
+ testArrayListInserts(where, size);
+ }
+
+ void beginVectorAdds(Integer size) {
+ vector = new Vector();
+ }
+
+ void beginVectorGets( Integer size ) {
+ createVector( size );
+ }
+
+ void beginVectorInserts(Position where, Integer size) {
+ vector = new Vector(); index = 0;
+ }
+
+ void beginVectorRemoves(Position where, Integer size) {
+ beginVectorInserts(where,size); testVectorInserts(where,size);
+ }
+
+ private void createArrayList( Integer size ) {
+ beginArrayListAdds( size );
+ testArrayListAdds( size );
+ }
+
+ private void createVector( Integer size ) {
+ beginVectorAdds( size );
+ testVectorAdds( size );
+ }
+
+ private void insertIntoCollection(Integer size, Position where, List v) {
+ int num = size.intValue();
+ for (int i = 0; i < num; i++) {
+ if (where == Position.NONE ) {
+ v.add("hello");
+ } else if (where == Position.BEGIN) {
+ v.add(0, "hello");
+ } else if (where == Position.END) {
+ v.add(v.size(), "hello");
+ } else if (where == Position.VARIED) {
+ v.add(index, "hello");
+ index += PRIME;
+ index %= v.size();
+ }
+ }
+ }
+
+ private int removeFromCollection(Integer size, Position where, List v) {
+ int num = size.intValue();
+ for (int i = 0; i < num; i++) {
+ if (where == Position.NONE) {
+ throw new RuntimeException("cannot remove with no position");
+ } else if (where == Position.BEGIN) {
+ v.remove(0);
+ } else if (where == Position.END) {
+ v.remove(v.size() - 1);
+ } else if (where == Position.VARIED) {
+ v.remove(index);
+ index += PRIME;
+ int currentSize = v.size();
+ if ( currentSize > 0 ) {
+ index %= v.size();
+ }
+ }
+ }
+ return index;
+ }
+}
diff --git a/user/src/com/google/gwt/junit/JUnit.gwt.xml b/user/src/com/google/gwt/junit/JUnit.gwt.xml
index 12703be..4a22ebf 100644
--- a/user/src/com/google/gwt/junit/JUnit.gwt.xml
+++ b/user/src/com/google/gwt/junit/JUnit.gwt.xml
@@ -1,5 +1,5 @@
<!-- -->
-<!-- Copyright 2006 Google Inc. All Rights Reserved. -->
+<!-- Copyright 2007 Google Inc. All Rights Reserved. -->
<!-- Deferred binding rules for browser selection. -->
<!-- -->
<!-- Do not inherit this module directly. Running GWTTestCase under JUnit -->
@@ -15,6 +15,10 @@
<when-type-assignable class="com.google.gwt.junit.client.GWTTestCase"/>
</generate-with>
+ <generate-with class="com.google.gwt.junit.rebind.BenchmarkGenerator">
+ <when-type-assignable class="com.google.gwt.junit.client.Benchmark"/>
+ </generate-with>
+
<servlet path='/junithost' class='com.google.gwt.junit.server.JUnitHostImpl'/>
</module>
diff --git a/user/src/com/google/gwt/junit/JUnitMessageQueue.java b/user/src/com/google/gwt/junit/JUnitMessageQueue.java
index 8545b79..cbba383 100644
--- a/user/src/com/google/gwt/junit/JUnitMessageQueue.java
+++ b/user/src/com/google/gwt/junit/JUnitMessageQueue.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -15,136 +15,198 @@
*/
package com.google.gwt.junit;
-import java.util.HashMap;
+import com.google.gwt.junit.client.TestResults;
+
+import java.util.List;
+import java.util.ArrayList;
import java.util.Map;
+import java.util.HashMap;
/**
- * A message queue to pass data between {@link JUnitShell} and
- * {@link com.google.gwt.junit.server.JUnitHostImpl} in a thread-safe manner.
- *
- * <p>
- * The public methods are called by the servlet to find out what test to execute
- * next, and to report the results of the last test to run.
- * </p>
- *
- * <p>
- * The protected methods are called by the shell to fetch test results and drive
- * the next test the client should run.
- * </p>
+ * A message queue to pass data between {@link JUnitShell} and {@link
+ * com.google.gwt.junit.server.JUnitHostImpl} in a thread-safe manner.
+ *
+ * <p> The public methods are called by the servlet to find out what test to
+ * execute next, and to report the results of the last test to run. </p>
+ *
+ * <p> The protected methods are called by the shell to fetch test results and
+ * drive the next test the client should run. </p>
*/
public class JUnitMessageQueue {
/**
- * Maps the name of a test class to the method that should be run. Access must
- * be synchronized.
+ * Tracks which test each client is requesting.
+ *
+ * Key = client-id (e.g. agent+host) Value = the index of the current
+ * requested test
*/
- private final Map nameMap = new HashMap();
+ private Map/*<String,Integer>*/ clientTestRequests
+ = new HashMap/*<String,Integer>*/();
/**
- * Maps the name of a test class to the last results to be reported. Access
- * must be synchronized.
+ * The index of the current test being executed.
*/
- private final Map resultsMap = new HashMap();
+ private int currentTestIndex = -1;
/**
- * Only instantiatable within this package.
+ * The number of TestCase clients executing in parallel.
+ */
+ private int numClients = 1;
+
+ /**
+ * The lock used to synchronize access around testMethod, clientTestRequests,
+ * and currentTestIndex.
+ */
+ private Object readTestLock = new Object();
+
+ /**
+ * The lock used to synchronize access around testResults.
+ */
+ private Object resultsLock = new Object();
+
+ /**
+ * The name of the test method to execute. We don't need the class, because
+ * the remote TestCases already knows the class.
+ */
+ private String testMethod;
+
+ /**
+ * The results for the current test method.
+ */
+ private List/*<TestResults>*/ testResults = new ArrayList/*<TestResults>*/();
+
+ /**
+ * Creates a message queue with one client.
+ *
+ * @see JUnitMessageQueue#JUnitMessageQueue(int)
*/
JUnitMessageQueue() {
}
/**
- * Called by the servlet to query for for the next method to test.
- *
- * @param testClassName The name of the test class.
- * @param timeout How long to wait for an answer.
- * @return The next test to run, or <code>null</code> if
- * <code>timeout</code> is exceeded or the next test does not match
- * <code>testClassName</code>.
+ * Only instantiatable within this package.
+ *
+ * @param numClients The number of parallel clients being served by this
+ * queue.
*/
- public String getNextTestName(String testClassName, long timeout) {
- synchronized (nameMap) {
+ JUnitMessageQueue(int numClients) {
+ this.numClients = numClients;
+ }
+
+ /**
+ * Called by the servlet to query for for the next method to test.
+ *
+ * @param testClassName The name of the test class.
+ * @param timeout How long to wait for an answer.
+ * @return The next test to run, or <code>null</code> if <code>timeout</code>
+ * is exceeded or the next test does not match <code>testClassName</code>.
+ */
+ public String getNextTestName(String clientId, String testClassName,
+ long timeout) {
+ synchronized (readTestLock) {
long stopTime = System.currentTimeMillis() + timeout;
- while (!nameMap.containsKey(testClassName)) {
+
+ while (!testIsAvailableFor(clientId)) {
long timeToWait = stopTime - System.currentTimeMillis();
if (timeToWait < 1) {
return null;
}
try {
- nameMap.wait(timeToWait);
+ readTestLock.wait(timeToWait);
} catch (InterruptedException e) {
// just abort
return null;
}
}
- return (String) nameMap.remove(testClassName);
+ bumpClientTestRequest(clientId);
+
+ return testMethod;
}
}
/**
* Called by the servlet to report the results of the last test to run.
- *
+ *
* @param testClassName The name of the test class.
- * @param t The exception thrown by the last test, or <code>null</code> if
- * the test completed without error.
+ * @param results The result of running the test.
*/
- public void reportResults(String testClassName, Throwable t) {
- synchronized (resultsMap) {
- resultsMap.put(testClassName, t);
+ public void reportResults(String testClassName, TestResults results) {
+
+ // TODO(tobyr): testClassName is not needed now
+ synchronized (resultsLock) {
+ testResults.add(results);
}
}
/**
- * Called by the shell to fetch the results of a completed test.
- *
+ * Fetches the results of a completed test.
+ *
* @param testClassName The name of the test class.
- * @return An exception thrown from a failed test, or <code>null</code> if
+ * @return An getException thrown from a failed test, or <code>null</code> if
* the test completed without error.
*/
- Throwable getResult(String testClassName) {
- synchronized (resultsMap) {
- return (Throwable) resultsMap.remove(testClassName);
- }
- }
-
- /**
- * Called by the shell to see if the servlet has begun running the current
- * test.
- *
- * @param testClassName
- * @return <code>true</code> if the servlet has not yet fetched the next
- * test name, otherwise <code>false</code>.
- */
- boolean hasNextTestName(String testClassName) {
- synchronized (nameMap) {
- return nameMap.containsKey(testClassName);
- }
+ List/*<TestResults>*/ getResults(String testClassName) {
+ // TODO(tobyr): testClassName is not needed now
+ return (List/*<TestResult>*/) testResults;
}
/**
* Called by the shell to see if the currently-running test has completed.
- *
+ *
* @param testClassName The name of the test class.
* @return If the test has completed, <code>true</code>, otherwise
* <code>false</code>.
*/
boolean hasResult(String testClassName) {
- synchronized (resultsMap) {
- return resultsMap.containsKey(testClassName);
+
+ // TODO(tobyr): testClassName is not needed now
+ synchronized (resultsLock) {
+ return testResults.size() == numClients;
}
}
/**
* Called by the shell to set the name of the next method to run for this test
* class.
- *
+ *
* @param testClassName The name of the test class.
- * @param testName The name of the method to run.
+ * @param testName The name of the method to run.
*/
void setNextTestName(String testClassName, String testName) {
- synchronized (nameMap) {
- nameMap.put(testClassName, testName);
- nameMap.notifyAll();
+
+ // TODO(tobyr): testClassName is not needed now
+ synchronized (readTestLock) {
+ testMethod = testName;
+ ++currentTestIndex;
+ testResults = new ArrayList/*<TestResults>*/(numClients);
+ readTestLock.notifyAll();
}
}
+
+ /**
+ * Sets the number of clients that will be executing the JUnit tests in
+ * parallel.
+ *
+ * @param numClients must be > 0
+ */
+ void setNumClients(int numClients) {
+ this.numClients = numClients;
+ }
+
+ // This method requires that readTestLock is being held for the duration.
+ private void bumpClientTestRequest(String clientId) {
+ Integer index = (Integer) clientTestRequests.get(clientId);
+ clientTestRequests.put(clientId, new Integer(index.intValue() + 1));
+ }
+
+ // This method requires that readTestLock is being held for the duration.
+ private boolean testIsAvailableFor(String clientId) {
+ Integer index = (Integer) clientTestRequests.get(clientId);
+ if (index == null) {
+ index = new Integer(0);
+ clientTestRequests.put(clientId, index);
+ }
+ return index.intValue() == currentTestIndex;
+ }
}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index ace04af..b70c481 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -23,7 +23,11 @@
import com.google.gwt.dev.shell.BrowserWidgetHost;
import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
import com.google.gwt.junit.client.TimeoutException;
+import com.google.gwt.junit.client.TestResults;
+import com.google.gwt.junit.client.Trial;
+import com.google.gwt.junit.client.Benchmark;
import com.google.gwt.junit.remote.BrowserManager;
+import com.google.gwt.junit.benchmarks.BenchmarkReport;
import com.google.gwt.util.tools.ArgHandlerFlag;
import com.google.gwt.util.tools.ArgHandlerString;
@@ -33,41 +37,60 @@
import java.rmi.Naming;
import java.util.ArrayList;
+import java.util.List;
+import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.io.File;
/**
* This class is responsible for hosting JUnit test case execution. There are
* three main pieces to the JUnit system.
- *
- * <ul>
- * <li>Test environment</li>
- * <li>Client classes</li>
- * <li>Server classes</li>
- * </ul>
- *
- * <p>
- * The test environment consists of this class and the non-translatable version
- * of {@link com.google.gwt.junit.client.GWTTestCase}. These two classes
- * integrate directly into the real JUnit test process.
- * </p>
- *
- * <p>
- * The client classes consist of the translatable version of
- * {@link com.google.gwt.junit.client.GWTTestCase}, translatable JUnit classes,
- * and the user's own {@link com.google.gwt.junit.client.GWTTestCase}-derived
- * class. The client communicates to the server via RPC.
- * </p>
- *
- * <p>
- * The server consists of {@link com.google.gwt.junit.server.JUnitHostImpl}, an
- * RPC servlet which communicates back to the test environment through a
- * {@link JUnitMessageQueue}, thus closing the loop.
- * </p>
+ *
+ * <ul> <li>Test environment</li> <li>Client classes</li> <li>Server
+ * classes</li> </ul>
+ *
+ * <p> The test environment consists of this class and the non-translatable
+ * version of {@link com.google.gwt.junit.client.GWTTestCase}. These two classes
+ * integrate directly into the real JUnit test process. </p>
+ *
+ * <p> The client classes consist of the translatable version of {@link
+ * com.google.gwt.junit.client.GWTTestCase}, translatable JUnit classes, and the
+ * user's own {@link com.google.gwt.junit.client.GWTTestCase}-derived class. The
+ * client communicates to the server via RPC. </p>
+ *
+ * <p> The server consists of {@link com.google.gwt.junit.server.JUnitHostImpl},
+ * an RPC servlet which communicates back to the test environment through a
+ * {@link JUnitMessageQueue}, thus closing the loop. </p>
*/
public class JUnitShell extends GWTShell {
/**
+ * Executes shutdown logic for JUnitShell
+ *
+ * Sadly, there's no simple way to know when all unit tests have finished
+ * executing. So this class is registered as a VM shutdown hook so that work
+ * can be done at the end of testing - for example, writing out the reports.
+ */
+ private class Shutdown implements Runnable {
+
+ public void run() {
+ try {
+ String reportPath = System.getProperty(Benchmark.REPORT_PATH);
+ if (reportPath == null || reportPath.trim().equals("")) {
+ reportPath = System.getProperty("user.dir");
+ }
+ report.generate(reportPath + File.separator + "report-"
+ + new Date().getTime() + ".xml");
+ } catch (Exception e) {
+ // It really doesn't matter how we got here.
+ // Regardless of the failure, the VM is shutting down.
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
* This is a system property that, when set, emulates command line arguments.
*/
private static final String PROP_GWT_ARGS = "gwt.args";
@@ -84,6 +107,10 @@
*/
private static final int TEST_BEGIN_TIMEOUT_MILLIS = 30000;
+ // A larger value when debugging the unit test framework, so you
+ // don't get spurious timeouts.
+ // private static final int TEST_BEGIN_TIMEOUT_MILLIS = 200000;
+
/**
* Singleton object for hosting unit tests. All test case instances executed
* by the TestRunner will use the single unitTestShell.
@@ -98,7 +125,7 @@
/**
* Called by {@link com.google.gwt.junit.server.JUnitHostImpl} to get an
* interface into the test process.
- *
+ *
* @return The {@link JUnitMessageQueue} interface that belongs to the
* singleton {@link JUnitShell}, or <code>null</code> if no such
* singleton exists.
@@ -111,9 +138,23 @@
}
/**
+ * Called by {@link com.google.gwt.junit.rebind.JUnitTestCaseStubGenerator} to
+ * add test meta data to the test report.
+ *
+ * @return The {@link BenchmarkReport} that belongs to the singleton {@link
+ * JUnitShell}, or <code>null</code> if no such singleton exists.
+ */
+ public static BenchmarkReport getReport() {
+ if (unitTestShell == null) {
+ return null;
+ }
+ return unitTestShell.report;
+ }
+
+ /**
* Entry point for {@link com.google.gwt.junit.client.GWTTestCase}. Gets or
- * creates the singleton {@link JUnitShell} and invokes its
- * {@link #runTestImpl(String, TestCase, TestResult)}.
+ * creates the singleton {@link JUnitShell} and invokes its {@link
+ * #runTestImpl(String, TestCase, TestResult)}.
*/
public static void runTest(String moduleName, TestCase testCase,
TestResult testResult) throws UnableToCompleteException {
@@ -121,7 +162,8 @@
}
/**
- * Lazily initialize the singleton JUnitShell.
+ * Retrieves the JUnitShell. This should only be invoked during TestRunner
+ * execution of JUnit tests.
*/
private static JUnitShell getUnitTestShell() {
if (unitTestShell == null) {
@@ -130,10 +172,17 @@
if (!shell.processArgs(args)) {
throw new RuntimeException("Invalid shell arguments");
}
+
+ shell.messageQueue = new JUnitMessageQueue(shell.numClients);
+
if (!shell.startUp()) {
throw new RuntimeException("Shell failed to start");
}
+
+ shell.report = new BenchmarkReport(shell.getTopLogger());
unitTestShell = shell;
+
+ Runtime.getRuntime().addShutdownHook(new Thread(shell. new Shutdown()));
}
return unitTestShell;
@@ -157,7 +206,19 @@
/**
* Portal to interact with the servlet.
*/
- private JUnitMessageQueue messageQueue = new JUnitMessageQueue();
+ private JUnitMessageQueue messageQueue;
+
+ /**
+ * The number of test clients executing in parallel. With -remoteweb, users
+ * can specify a number of parallel test clients, but by default we only have
+ * 1.
+ */
+ private int numClients = 1;
+
+ /**
+ * The result of benchmark runs.
+ */
+ private BenchmarkReport report;
/**
* What type of test we're running; Local hosted, local web, or remote web.
@@ -168,7 +229,7 @@
* The time at which the current test will fail if the client has not yet
* started the test.
*/
- private long testBeginTimout;
+ private long testBeginTimeout;
/**
* Class name of the current/last test case to run.
@@ -219,8 +280,13 @@
public boolean setString(String str) {
try {
- BrowserManager browserManager = (BrowserManager) Naming.lookup(str);
- runStyle = new RunStyleRemoteWeb(JUnitShell.this, browserManager);
+ String[] urls = str.split(",");
+ numClients = urls.length;
+ BrowserManager[] browserManagers = new BrowserManager[ numClients ];
+ for (int i = 0; i < numClients; ++i) {
+ browserManagers[i] = (BrowserManager) Naming.lookup(urls[i]);
+ }
+ runStyle = new RunStyleRemoteWeb(JUnitShell.this, browserManagers);
} catch (Exception e) {
System.err.println("Error connecting to browser manager at " + str);
e.printStackTrace();
@@ -308,12 +374,14 @@
* to complete.
*/
protected boolean notDone() {
+ /*
if (messageQueue.hasNextTestName(testCaseClassName)
- && testBeginTimout < System.currentTimeMillis()) {
+ && testBeginTimeout < System.currentTimeMillis()) {
throw new TimeoutException(
"The browser did not contact the server within "
+ TEST_BEGIN_TIMEOUT_MILLIS + "ms.");
}
+ */
if (messageQueue.hasResult(testCaseClassName)) {
return false;
@@ -356,18 +424,51 @@
try {
// Set a timeout period to automatically fail if the servlet hasn't been
// contacted; something probably went wrong (the module failed to load?)
- testBeginTimout = System.currentTimeMillis() + TEST_BEGIN_TIMEOUT_MILLIS;
+ // testBeginTimeout = System.currentTimeMillis() + TEST_BEGIN_TIMEOUT_MILLIS;
pumpEventLoop();
} catch (TimeoutException e) {
testResult.addError(testCase, e);
return;
}
- Throwable result = messageQueue.getResult(testCaseClassName);
- if (result instanceof AssertionFailedError) {
- testResult.addFailure(testCase, (AssertionFailedError) result);
- } else if (result != null) {
- testResult.addError(testCase, result);
+ List/*JUnitMessageQueue.TestResult*/ results = messageQueue
+ .getResults(testCaseClassName);
+
+ if (results == null) {
+ return;
+ }
+
+ boolean parallelTesting = numClients > 1;
+
+ for (int i = 0; i < results.size(); ++i) {
+ TestResults result = (TestResults) results.get(i);
+ Trial firstTrial = (Trial) result.getTrials().get(0);
+ Throwable exception = firstTrial.getException();
+
+ // In the case that we're running multiple clients at once, we need to
+ // let the user know the browser in which the failure happened
+ if (parallelTesting && exception != null) {
+ String msg = "Remote test failed at " + result.getHost() + " on " + result.getAgent();
+ if (exception instanceof AssertionFailedError) {
+ AssertionFailedError newException = new AssertionFailedError(msg + "\n" + exception.getMessage());
+ newException.setStackTrace(exception.getStackTrace());
+ exception = newException;
+ } else {
+ exception = new RuntimeException(msg, exception);
+ }
+ }
+
+ // A "successful" failure
+ if (exception instanceof AssertionFailedError) {
+ testResult.addFailure(testCase, (AssertionFailedError) exception);
+ } else if (exception != null) {
+ // A real failure
+ testResult.addError(testCase, exception);
+ }
+
+ if (testCase instanceof Benchmark) {
+ report.addBenchmarkResults(testCase, result);
+ }
}
}
@@ -382,7 +483,8 @@
// Match either a non-whitespace, non start of quoted string, or a
// quoted string that can have embedded, escaped quoting characters
//
- Pattern pattern = Pattern.compile("[^\\s\"]+|\"[^\"\\\\]*(\\\\.[^\"\\\\]*)*\"");
+ Pattern pattern = Pattern
+ .compile("[^\\s\"]+|\"[^\"\\\\]*(\\\\.[^\"\\\\]*)*\"");
Matcher matcher = pattern.matcher(args);
while (matcher.find()) {
argList.add(matcher.group());
diff --git a/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java b/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java
index 13c5d8c..b7f5505 100644
--- a/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java
+++ b/user/src/com/google/gwt/junit/RunStyleRemoteWeb.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -31,34 +31,44 @@
private static final int INITIAL_KEEPALIVE_MS = 5000;
private static final int PING_KEEPALIVE_MS = 2000;
- /**
- * A remote browser manager.
- */
- private final BrowserManager browserManager;
+ // Larger values when debugging the unit test framework, so you
+ // don't get spurious timeouts.
+ // private static final int INITIAL_KEEPALIVE_MS = 500000;
+ // private static final int PING_KEEPALIVE_MS = 200000;
/**
- * A local reference to a remote browser process.
+ * Remote browser managers.
*/
- private int remoteToken = 0;
+ private final BrowserManager[] browserManagers;
+
+ /**
+ * References to the remote browser processes.
+ */
+ private int[] remoteTokens;
/**
* The containing shell.
*/
private final JUnitShell shell;
+ private boolean running = false;
+
/**
* @param shell the containing shell
*/
- public RunStyleRemoteWeb(JUnitShell shell, BrowserManager browserManager) {
+ public RunStyleRemoteWeb(JUnitShell shell, BrowserManager[] browserManagers) {
this.shell = shell;
- this.browserManager = browserManager;
+ this.browserManagers = browserManagers;
+ this.remoteTokens = new int[ browserManagers.length ];
}
public void maybeLaunchModule(String moduleName, boolean forceLaunch)
throws UnableToCompleteException {
- if (forceLaunch || remoteToken == 0) {
+
+ if (forceLaunch || !running) {
shell.compileForWebMode(moduleName);
String localhost;
+
try {
localhost = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
@@ -66,28 +76,40 @@
}
String url = "http://" + localhost + ":" + shell.getPort() + "/"
+ moduleName;
+
try {
- if (remoteToken > 0) {
- browserManager.killBrowser(remoteToken);
- remoteToken = 0;
+ for ( int i = 0; i < remoteTokens.length; ++i ) {
+ int remoteToken = remoteTokens[ i ];
+ BrowserManager mgr = browserManagers[ i ];
+ if ( remoteToken != 0 ) {
+ mgr.killBrowser(remoteToken);
+ }
+ remoteTokens[ i ] = mgr.launchNewBrowser(url, INITIAL_KEEPALIVE_MS);
}
- remoteToken = browserManager.launchNewBrowser(url, INITIAL_KEEPALIVE_MS);
} catch (Exception e) {
shell.getTopLogger().log(TreeLogger.ERROR,
"Error launching remote browser", e);
throw new UnableToCompleteException();
}
+
+ running = true;
}
}
public boolean wasInterrupted() {
- if (remoteToken > 0) {
- try {
- browserManager.keepAlive(remoteToken, PING_KEEPALIVE_MS);
- } catch (Exception e) {
- shell.getTopLogger().log(TreeLogger.WARN,
- "Unexpected exception keeping remote browser alive", e);
- return true;
+ for ( int i = 0; i < remoteTokens.length; ++i ) {
+ int remoteToken = remoteTokens[ i ];
+ BrowserManager mgr = browserManagers[ i ];
+ if (remoteToken > 0) {
+ try {
+ mgr.keepAlive(remoteToken, PING_KEEPALIVE_MS);
+ } catch (Exception e) {
+ // TODO(tobyr): We're failing on the first exception, rather than
+ // collecting them, but that's probably OK for now.
+ shell.getTopLogger().log(TreeLogger.WARN,
+ "Unexpected exception keeping remote browser alive", e);
+ return true;
+ }
}
}
return false;
diff --git a/user/src/com/google/gwt/junit/benchmarks/BenchmarkReport.java b/user/src/com/google/gwt/junit/benchmarks/BenchmarkReport.java
new file mode 100644
index 0000000..ffbe2b9
--- /dev/null
+++ b/user/src/com/google/gwt/junit/benchmarks/BenchmarkReport.java
@@ -0,0 +1,605 @@
+/*
+ * Copyright 2007 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.benchmarks;
+
+import com.google.gwt.core.ext.typeinfo.HasMetaData;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.junit.rebind.BenchmarkGenerator;
+import com.google.gwt.junit.client.TestResults;
+import com.google.gwt.junit.client.Trial;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
+import org.eclipse.jdt.internal.compiler.IProblemFactory;
+import org.eclipse.jdt.internal.compiler.SourceElementParser;
+import org.eclipse.jdt.internal.compiler.ISourceElementRequestor;
+import org.eclipse.jdt.internal.compiler.SourceElementRequestorAdapter;
+import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
+import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
+
+import java.io.IOException;
+import java.io.FileOutputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileReader;
+import java.io.BufferedReader;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+import java.text.DateFormat;
+import java.text.BreakIterator;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.dom.DOMSource;
+
+/**
+ * Generates a detailed report that contains the results of all of the
+ * benchmark-related unit tests executed during a unit test session. The primary
+ * user of this class is JUnitShell.
+ *
+ * The report is in XML format. To view the XML reports, use benchmarkViewer.
+ *
+ */
+public class BenchmarkReport {
+
+ /**
+ * Converts a set of test results for a single benchmark method into XML.
+ */
+ private class BenchmarkXml {
+
+ private MetaData metaData;
+
+ private List/*<JUnitMessageQueue.TestResult>*/ results;
+
+ private TestCase test;
+
+ BenchmarkXml(TestCase test,
+ List/*<JUnitMessageQueue.TestResult>*/ results) {
+ this.test = test;
+ this.results = results;
+ Map/*<String,MetaData>*/ methodMetaData
+ = (Map/*<String,MetaData>*/) testMetaData
+ .get(test.getClass().toString());
+ metaData = (MetaData) methodMetaData.get(test.getName());
+ }
+
+ Element toElement(Document doc) {
+ Element benchmark = doc.createElement("benchmark");
+ benchmark.setAttribute("class", test.getClass().getName());
+ benchmark.setAttribute("name", metaData.getTestName());
+ benchmark.setAttribute("description", metaData.getTestDescription());
+
+ String sourceCode = metaData.getSourceCode();
+ if (sourceCode != null) {
+ Element sourceCodeElement = doc.createElement("source_code");
+ sourceCodeElement.appendChild(doc.createTextNode(sourceCode));
+ benchmark.appendChild(sourceCodeElement);
+ }
+
+ // TODO(tobyr): create target_code element
+
+ for (Iterator it = results.iterator(); it.hasNext();) {
+ TestResults result = (TestResults) it.next();
+ benchmark.appendChild(toElement(doc, result));
+ }
+
+ return benchmark;
+ }
+
+ private Element toElement(Document doc, TestResults result) {
+ Element resultElement = doc.createElement("result");
+ resultElement.setAttribute("host", result.getHost());
+ resultElement.setAttribute("agent", result.getAgent());
+
+ List trials = result.getTrials();
+
+ for (Iterator it = trials.iterator(); it.hasNext();) {
+ Trial trial = (Trial) it.next();
+ Element trialElement = toElement(doc, trial);
+ resultElement.appendChild(trialElement);
+ }
+
+ return resultElement;
+ }
+
+ private Element toElement(Document doc, Trial trial) {
+ Element trialElement = doc.createElement("trial");
+
+ Map variables = trial.getVariables();
+
+ for (Iterator it = variables.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ Object name = entry.getKey();
+ Object value = entry.getValue();
+ Element variableElement = doc.createElement("variable");
+ variableElement.setAttribute("name", name.toString());
+ variableElement.setAttribute("value", value.toString());
+ trialElement.appendChild(variableElement);
+ }
+
+ trialElement
+ .setAttribute("timing", String.valueOf(trial.getRunTimeMillis()));
+
+ Throwable exception = trial.getException();
+
+ if (exception != null) {
+ Element exceptionElement = doc.createElement("exception");
+ exceptionElement.appendChild(doc.createTextNode(exception.toString()));
+ trialElement.appendChild(exceptionElement);
+ }
+
+ return trialElement;
+ }
+ }
+
+ /**
+ * Parses a .java source file to get the source code for methods.
+ *
+ * This Parser takes some shortcuts based on the fact that it's only being
+ * used to locate test methods for unit tests. (For example, only requiring a
+ * method name instead of a full type signature for lookup).
+ *
+ * TODO(tobyr) I think that I might be able to replace all this code with a
+ * call to the existing metadata interface. Check declEnd/declStart in
+ * JAbstractMethod.
+ */
+ private static class Parser {
+
+ static class MethodBody {
+
+ int declarationEnd; // the character index of the end of the method
+
+ int declarationStart; // the character index of the start of the method
+
+ String source;
+ }
+
+ private MethodBody currentMethod; // Only used during the visitor
+
+ // But it's less painful
+ private Map/*<String,MethodBody>*/ methods
+ = new HashMap/*<String,MethodBody>*/();
+
+ // Contains the contents of the entire source file
+ private char[] sourceContents;
+
+ Parser(JClassType klass) throws IOException {
+
+ Map settings = new HashMap();
+ settings.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_1_4);
+ settings.put(CompilerOptions.OPTION_TargetPlatform,
+ CompilerOptions.VERSION_1_4);
+ settings.put(CompilerOptions.OPTION_DocCommentSupport,
+ CompilerOptions.ENABLED);
+ CompilerOptions options = new CompilerOptions(settings);
+
+ IProblemFactory problemFactory = new DefaultProblemFactory(
+ Locale.getDefault());
+
+ // Save off the bounds of any method that is a test method
+ ISourceElementRequestor requestor = new SourceElementRequestorAdapter() {
+ public void enterMethod(MethodInfo methodInfo) {
+ String name = new String(methodInfo.name);
+ if (name.startsWith("test")) {
+ currentMethod = new MethodBody();
+ currentMethod.declarationStart = methodInfo.declarationStart;
+ methods.put(name, currentMethod);
+ }
+ }
+
+ public void exitMethod(int declarationEnd, int defaultValueStart,
+ int defaultValueEnd) {
+ if (currentMethod != null) {
+ currentMethod.declarationEnd = declarationEnd;
+ currentMethod = null;
+ }
+ }
+ };
+
+ boolean reportLocalDeclarations = true;
+ boolean optimizeStringLiterals = true;
+
+ SourceElementParser parser = new SourceElementParser(requestor,
+ problemFactory, options, reportLocalDeclarations,
+ optimizeStringLiterals);
+
+ File sourceFile = findSourceFile(klass);
+ sourceContents = read(sourceFile);
+ CompilationUnit unit = new CompilationUnit(sourceContents,
+ sourceFile.getName(), null);
+
+ parser.parseCompilationUnit(unit, true);
+ }
+
+ /**
+ * Returns the source code for the method of the given name.
+ *
+ * @return null if the source code for the method can not be located
+ */
+ public String getMethod(JMethod method) {
+ /*
+ MethodBody methodBody = (MethodBody)methods.get( method.getName() );
+ if ( methodBody == null ) {
+ return null;
+ }
+ if ( methodBody.source == null ) {
+ methodBody.source = new String(sourceContents,
+ methodBody.declarationStart, methodBody.declarationEnd - methodBody.
+ declarationStart + 1);
+ }
+ return methodBody.source;
+ */
+ return new String(sourceContents, method.getDeclStart(),
+ method.getDeclEnd() - method.getDeclStart() + 1);
+ }
+ }
+
+ /**
+ * Converts an entire report into XML.
+ */
+ private class ReportXml {
+
+ private Map/*<String,Element>*/ categoryElementMap
+ = new HashMap/*<String,Element>*/();
+
+ private Date date = new Date();
+
+ private String version = "unknown";
+
+ /**
+ * Locates or creates the category element by the specified name.
+ *
+ * @param doc The document to search
+ * @return The matching category element
+ */
+ private Element getCategoryElement(Document doc, Element report,
+ String name) {
+ Element e = (Element) categoryElementMap.get(name);
+
+ if (e != null) {
+ return e;
+ }
+
+ Element categoryElement = doc.createElement("category");
+ categoryElementMap.put(name, categoryElement);
+ CategoryImpl category = (CategoryImpl) testCategories.get(name);
+ categoryElement.setAttribute("name", category.getName());
+ categoryElement.setAttribute("description", category.getDescription());
+
+ report.appendChild(categoryElement);
+
+ return categoryElement;
+ }
+
+ Element toElement(Document doc) {
+ Element report = doc.createElement("gwt_benchmark_report");
+ String dateString = DateFormat.getDateTimeInstance().format(date);
+ report.setAttribute("date", dateString);
+ report.setAttribute("gwt_version", version);
+
+ // - Add each test result into the report.
+ // - Add the category for the test result, if necessary.
+ for (Iterator entryIt = testResults.entrySet().iterator();
+ entryIt.hasNext();) {
+ Map.Entry entry = (Map.Entry) entryIt.next();
+ TestCase test = (TestCase) entry.getKey();
+ List/*<JUnitMessageQueue.TestResult>*/ results
+ = (List/*<JUnitMessageQueue.TestResult>*/) entry.getValue();
+ BenchmarkXml xml = new BenchmarkXml(test, results);
+ Element categoryElement = getCategoryElement(doc, report,
+ xml.metaData.getCategory().getClassName());
+ categoryElement.appendChild(xml.toElement(doc));
+ }
+
+ return report;
+ }
+ }
+
+ private static final String GWT_BENCHMARK_CATEGORY = "gwt.benchmark.category";
+
+ private static final String GWT_BENCHMARK_DESCRIPTION
+ = "gwt.benchmark.description";
+
+ private static final String GWT_BENCHMARK_NAME = "gwt.benchmark.name";
+
+ private static File findSourceFile(JClassType klass) {
+ final char separator = File.separator.charAt(0);
+ String filePath = klass.getPackage().getName().replace('.', separator) +
+ separator + klass.getSimpleSourceName() + ".java";
+ String[] paths = getClassPath();
+
+ for (int i = 0; i < paths.length; ++i) {
+ File maybeSourceFile = new File(paths[i] + separator + filePath);
+
+ if (maybeSourceFile.exists()) {
+ return maybeSourceFile;
+ }
+ }
+
+ return null;
+ }
+
+ private static String[] getClassPath() {
+ String path = System.getProperty("java.class.path");
+ return path.split(File.pathSeparator);
+ }
+
+ private static String getSimpleMetaData(HasMetaData hasMetaData,
+ String name) {
+ String[][] allValues = hasMetaData.getMetaData(name);
+
+ if (allValues == null) {
+ return null;
+ }
+
+ StringBuffer result = new StringBuffer();
+
+ for (int i = 0; i < allValues.length; ++i) {
+ String[] values = allValues[i];
+ for (int j = 0; j < values.length; ++j) {
+ result.append(values[j]);
+ result.append(" ");
+ }
+ }
+
+ String resultString = result.toString().trim();
+ return resultString.equals("") ? null : resultString;
+ }
+
+ private static char[] read(File f) throws IOException {
+ // TODO(tobyr) Can be done oh so much faster by just reading directly into
+ // a char[]
+
+ BufferedReader reader = new BufferedReader(new FileReader(f));
+ StringBuffer source = new StringBuffer((int) f.length());
+
+ while (true) {
+ String line = reader.readLine();
+ if (line == null) {
+ break;
+ }
+ source.append(line);
+ source.append("\n");
+ }
+
+ char[] buf = new char[ source.length() ];
+ source.getChars(0, buf.length, buf, 0);
+
+ return buf;
+ }
+
+ private Map/*<String,Map<CategoryImpl>*/ testCategories
+ = new HashMap/*<String,CategoryImpl>*/();
+
+ private Map/*<String,Map<String,MetaData>>*/ testMetaData
+ = new HashMap/*<String,Map<String,MetaData>>*/();
+
+ private Map/*<TestCase,List<JUnitMessageQueue.TestResult>>*/ testResults
+ = new HashMap/*<TestCase,JUnitMessageQueue.List<TestResult>>*/();
+
+ private TypeOracle typeOracle;
+
+ private TreeLogger logger;
+
+ public BenchmarkReport( TreeLogger logger ) {
+ this.logger = logger;
+ }
+
+ /**
+ * Adds the Benchmark to the report. All of the metadata about the benchmark
+ * (category, name, description, etc...) is recorded from the TypeOracle.
+ */
+ public void addBenchmark(JClassType benchmarkClass, TypeOracle typeOracle) {
+
+ this.typeOracle = typeOracle;
+ String categoryType = getSimpleMetaData(benchmarkClass,
+ GWT_BENCHMARK_CATEGORY);
+
+ Map zeroArgMethods = BenchmarkGenerator
+ .getNotOverloadedTestMethods(benchmarkClass);
+ Map/*<String,JMethod>*/ parameterizedMethods = BenchmarkGenerator
+ .getParameterizedTestMethods(benchmarkClass, TreeLogger.NULL);
+ List/*<JMethod>*/ testMethods = new ArrayList/*<JMethod>*/(
+ zeroArgMethods.size() + parameterizedMethods.size());
+ testMethods.addAll(zeroArgMethods.values());
+ testMethods.addAll(parameterizedMethods.values());
+
+ Map/*<String,MetaData>*/ metaDataMap
+ = (Map/*<String,MetaData>*/) testMetaData
+ .get(benchmarkClass.toString());
+ if (metaDataMap == null) {
+ metaDataMap = new HashMap/*<String,MetaData>*/();
+ testMetaData.put(benchmarkClass.toString(), metaDataMap);
+ }
+
+ Parser parser = null;
+
+ try {
+ parser = new Parser(benchmarkClass);
+ } catch (IOException e) {
+ // if we fail to create the parser for some reason, we'll have to just
+ // deal with a null parser.
+ logger.log(TreeLogger.WARN,
+ "Unable to parse the code for " + benchmarkClass, e);
+ }
+
+ // Add all of the benchmark methods
+ for (int i = 0; i < testMethods.size(); ++i) {
+ JMethod method = (JMethod) testMethods.get(i);
+ String methodName = method.getName();
+ String methodCategoryType = getSimpleMetaData(method,
+ GWT_BENCHMARK_CATEGORY);
+ if (methodCategoryType == null) {
+ methodCategoryType = categoryType;
+ }
+ CategoryImpl methodCategory = getCategory(methodCategoryType);
+ String sourceCode = parser == null ? null : parser.getMethod(method);
+ StringBuffer summary = new StringBuffer();
+ StringBuffer comment = new StringBuffer();
+ getComment(sourceCode, summary, comment);
+
+ MetaData metaData = new MetaData(benchmarkClass.toString(), methodName,
+ sourceCode, methodCategory, methodName, summary.toString());
+ metaDataMap.put(methodName, metaData);
+ }
+ }
+
+ public void addBenchmarkResults(TestCase test, TestResults results) {
+ List/*<TestResults>*/ currentResults = (List/*<TestResults>*/) testResults
+ .get(test);
+ if (currentResults == null) {
+ currentResults = new ArrayList/*<TestResults>*/();
+ testResults.put(test, currentResults);
+ }
+ currentResults.add(results);
+ }
+
+ /**
+ * Generates reports for all of the benchmarks which were added to the
+ * generator.
+ *
+ * @param outputPath The path to write the reports to.
+ * @throws IOException If anything goes wrong writing to outputPath
+ */
+ public void generate(String outputPath)
+ throws IOException, TransformerException, ParserConfigurationException {
+
+ // Don't generate a new report if no tests were actually run.
+ if (testResults.size() == 0) {
+ return;
+ }
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+
+ Document doc = builder.newDocument();
+ doc.appendChild(new ReportXml().toElement(doc));
+
+ // TODO(tobyr) Looks like indenting is busted
+ // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6296446
+ // Not a big deal, since we don't intend to read the XML by hand anyway
+ TransformerFactory transformerFactory = TransformerFactory.newInstance();
+ // Think this can be used with JDK 1.5
+ // transformerFactory.setAttribute( "indent-number", new Integer(2) );
+ Transformer serializer = transformerFactory.newTransformer();
+ serializer.setOutputProperty(OutputKeys.METHOD, "xml");
+ serializer.setOutputProperty(OutputKeys.INDENT, "yes");
+ serializer
+ .setOutputProperty("{ http://xml.apache.org/xslt }indent-amount", "2");
+ BufferedOutputStream docOut = new BufferedOutputStream(
+ new FileOutputStream(outputPath));
+ serializer.transform(new DOMSource(doc), new StreamResult(docOut));
+ docOut.close();
+ }
+
+ private CategoryImpl getCategory(String name) {
+ CategoryImpl c = (CategoryImpl) testCategories.get(name);
+
+ if (c != null) {
+ return c;
+ }
+
+ String categoryName = "";
+ String categoryDescription = "";
+
+ if (name != null) {
+ JClassType categoryType = typeOracle.findType(name);
+
+ if (categoryType != null) {
+ categoryName = getSimpleMetaData(categoryType, GWT_BENCHMARK_NAME);
+ categoryDescription = getSimpleMetaData(categoryType,
+ GWT_BENCHMARK_DESCRIPTION);
+ }
+ }
+
+ c = new CategoryImpl(name, categoryName, categoryDescription);
+ testCategories.put(name, c);
+ return c;
+ }
+
+ /**
+ * Parses out the JavaDoc comment from a string of source code. Returns the
+ * first sentence summary in <code>summary</code> and the body of the entire
+ * comment (including the summary) in <code>comment</code>.
+ */
+ private void getComment(String sourceCode, StringBuffer summary,
+ StringBuffer comment) {
+
+ if (sourceCode == null) {
+ return;
+ }
+
+ summary.setLength(0);
+ comment.setLength(0);
+
+ String regex = "/\\*\\*(.(?!}-\\*/))*\\*/";
+
+ Pattern p = Pattern.compile(regex, Pattern.DOTALL);
+ Matcher m = p.matcher(sourceCode);
+
+ if (! m.find()) {
+ return;
+ }
+
+ String commentStr = m.group();
+
+ p = Pattern.compile(
+ "(/\\*\\*\\s*)" + // The comment header
+ "(((\\s*\\**\\s*)([^\n\r]*)[\n\r]+)*)" // The comment body
+ );
+
+ m = p.matcher(commentStr);
+
+ if (! m.find()) {
+ return;
+ }
+
+ String stripped = m.group(2);
+
+ p = Pattern.compile("^\\p{Blank}*\\**\\p{Blank}*", Pattern.MULTILINE);
+ String bareComment = p.matcher(stripped).replaceAll("");
+
+ BreakIterator iterator = BreakIterator.getSentenceInstance();
+ iterator.setText(bareComment);
+ int firstSentenceEnd = iterator.next();
+ if (firstSentenceEnd == BreakIterator.DONE) {
+ summary.append(bareComment);
+ } else {
+ summary.append(bareComment.substring(0, firstSentenceEnd));
+ }
+
+ comment.append(bareComment);
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/junit/benchmarks/CategoryImpl.java b/user/src/com/google/gwt/junit/benchmarks/CategoryImpl.java
new file mode 100644
index 0000000..6086768
--- /dev/null
+++ b/user/src/com/google/gwt/junit/benchmarks/CategoryImpl.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2007 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.benchmarks;
+
+/**
+ * Benchmark Category information. Part of the overall MetaData for a
+ * Benchmark. It is the backing store for com.google.gwt.junit.client.Category.
+ */
+class CategoryImpl {
+
+ private String className;
+ private String description;
+ private String name;
+
+ public CategoryImpl( String className, String name, String description ) {
+ this.className = className;
+ this.name = name;
+ this.description = description;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/user/src/com/google/gwt/junit/benchmarks/MetaData.java b/user/src/com/google/gwt/junit/benchmarks/MetaData.java
new file mode 100644
index 0000000..646e292
--- /dev/null
+++ b/user/src/com/google/gwt/junit/benchmarks/MetaData.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2007 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.benchmarks;
+
+/**
+ * The benchmark metadata for a single benchmark method.
+ */
+class MetaData {
+
+ private CategoryImpl category;
+
+ private String className;
+
+ private String methodName;
+
+ private String sourceCode;
+
+ private String testDescription;
+
+ private String testName;
+
+ public MetaData(String className, String methodName, String sourceCode,
+ CategoryImpl category, String testName, String testDescription) {
+ this.className = className;
+ this.methodName = methodName;
+ this.sourceCode = sourceCode;
+ this.category = category;
+ this.testName = testName;
+ this.testDescription = testDescription;
+ }
+
+ public boolean equals(Object obj) {
+ if (! (obj instanceof MetaData)) {
+ return false;
+ }
+
+ MetaData md = (MetaData) obj;
+
+ return md.className.equals(className) && md.methodName.equals(methodName);
+ }
+
+ public CategoryImpl getCategory() {
+ return category;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public String getMethodName() {
+ return methodName;
+ }
+
+ public String getSourceCode() {
+ return sourceCode;
+ }
+
+ public String getTestDescription() {
+ return testDescription;
+ }
+
+ public String getTestName() {
+ return testName;
+ }
+
+ public int hashCode() {
+ int result;
+ result = (className != null ? className.hashCode() : 0);
+ result = 29 * result + (methodName != null ? methodName.hashCode() : 0);
+ return result;
+ }
+}
+
diff --git a/user/src/com/google/gwt/junit/client/Benchmark.java b/user/src/com/google/gwt/junit/client/Benchmark.java
new file mode 100644
index 0000000..174bfcc
--- /dev/null
+++ b/user/src/com/google/gwt/junit/client/Benchmark.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2007 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;
+
+/**
+ * A type of {@link com.google.gwt.junit.client.GWTTestCase} which specifically
+ * records performance results. {@link com.google.gwt.junit.client.Benchmark}s
+ * have additional functionality above and beyond GWT's JUnit support for
+ * standard <code>TestCases</code>.
+ *
+ * <ul>
+ * <li>In a single <code>JUnit</code> run, the results of all executed
+ * benchmarks are collected and stored in an XML report viewable with the
+ * <code>benchmarkViewer</code>.</li>
+ *
+ * <li>GWT automatically removes jitter from your benchmark methods by running
+ * them for a minimum period of time (150ms). GWT also optionally limits your
+ * benchmark execution to a maximum period of time (1000ms).</li>
+ *
+ * <li>GWT supports "begin" and "end" test methods that separate setup and
+ * teardown costs from the actual work being benchmarked. Simply name your
+ * functions "begin[TestMethodName]" and "end[TestMethodName]" and they will
+ * be executed before and after every execution of your test method. The
+ * timings of these setup methods are not included in the test results.</li>
+ *
+ * <li>GWT supports test methods that have parameters. GWT will execute each
+ * benchmark method multiple times in order to exhaustively test all the possible
+ * combinations of parameter values. For each parameter that your test method
+ * accepts, it should document it with the annotation,
+ * <code>@gwt.benchmark.param</code>.
+ *
+ * <p>The syntax for gwt.benchmark.param is
+ * <code><param name> = <Iterable></code>. For example,
+ *
+ * <pre>
+ * @gwt.benchmark.param where = java.util.Arrays.asList(
+ * new Position[] { Position.BEGIN, Position.END, Position.VARIED } )
+ * @gwt.benchmark.param size -limit = insertRemoveRange
+ * public void testArrayListRemoves(Position where, Integer size) { ... }
+ * </pre></p>
+ *
+ * <p>In this example, the annotated function is executed with all the possible
+ * permutations of <code>Position = (BEGIN, END, and VARIED)</code> and
+ * <code>insertRemoveRange = IntRange( 64, Integer.MAX_VALUE, "*", 2 )</code>.
+ * </p>
+ *
+ * <p>This particular example also demonstrates how GWT can automatically limit
+ * the number of executions of your test. Your final parameter (in this example,
+ * size) can optionally be decorated with -limit to indicate to GWT that
+ * it should stop executing additional permutations of the test when the
+ * execution time becomes too long (over 1000ms). So, in this example,
+ * for each value of <code>Position</code>, <code>testArrayListRemoves</code>
+ * will be executed for increasing values of <code>size</code> (beginning with
+ * 64 and increasing in steps of 2), until either it reaches
+ * <code>Integer.MAX_VALUE</code> or the execution time for the last
+ * permutation is > 1000ms.</p>
+ * </li>
+ * </ul>
+ *
+ * <p>{@link Benchmark}s support the following annotations on each test method
+ * in order to decorate each test with additional information useful for
+ * reporting.</p>
+ *
+ * <ul>
+ * <li>@gwt.benchmark.category - The class name of the {@link Category} the
+ * benchmark belongs to. This property may also be set at the
+ * {@link com.google.gwt.junit.client.Benchmark} class level.</li>
+ * </ul>
+ *
+ * <h2>Examples of benchmarking in action</h2>
+ *
+ * <h3>A simple benchmark example</h3>
+ * {@link com.google.gwt.examples.benchmarks.AllocBenchmark} is a simple example
+ * of a basic benchmark that doesn't take advantage of most of benchmarking's
+ * advanced features.
+ *
+ * {@example com.google.gwt.examples.benchmarks.AllocBenchmark}
+ *
+ * <h3>An advanced benchmark example</h3>
+ * {@link com.google.gwt.examples.benchmarks.ArrayListAndVectorBenchmark} is a more
+ * sophisticated example of benchmarking. It demonstrates the use of "begin"
+ * and "end" test methods, parameterized test methods, and automatic
+ * test execution limits.
+ *
+ * {@example com.google.gwt.examples.benchmarks.ArrayListAndVectorBenchmark}
+ */
+public abstract class Benchmark extends GWTTestCase {
+
+ /**
+ * The name of the system property that specifies the location
+ * where benchmark reports are both written to and read from.
+ * Its value is <code>com.google.gwt.junit.reportPath</code>.
+ *
+ * If this system property is not set, the path defaults to the user's
+ * current working directory.
+ */
+ public static final String REPORT_PATH = "com.google.gwt.junit.reportPath";
+}
diff --git a/user/src/com/google/gwt/junit/client/Category.java b/user/src/com/google/gwt/junit/client/Category.java
new file mode 100644
index 0000000..6ef1147
--- /dev/null
+++ b/user/src/com/google/gwt/junit/client/Category.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2007 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;
+
+/**
+ * A benchmark category. {@link com.google.gwt.junit.client.Benchmark}s which
+ * use the GWT annotation, <code>@gwt.benchmark.category</code>, must set it to
+ * a class which implements this interface.
+ *
+ * <p>The following GWT annotations can be set on a <code>Category</code>:
+ *
+ * <ul>
+ * <li><code>@gwt.benchmark.name</code> The name of the <code>Category</code>
+ * </li>
+ * <li><code>@gwt.benchmark.description</code> The description of the
+ * <code>Category</code></li>
+ * </ul>
+ * </p>
+ *
+ */
+public interface Category {
+}
diff --git a/user/src/com/google/gwt/junit/client/GWTTestCase.java b/user/src/com/google/gwt/junit/client/GWTTestCase.java
index ceb7c83..69574b2 100644
--- a/user/src/com/google/gwt/junit/client/GWTTestCase.java
+++ b/user/src/com/google/gwt/junit/client/GWTTestCase.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -43,7 +43,7 @@
/**
* Add a checkpoint message to the current test. If this test fails, all
- * checkpoint messages will be appended to the exception description. This can
+ * checkpoint messages will be appended to the getException description. This can
* be useful in web mode for determining how far test execution progressed
* before a failure occurs.
*
@@ -64,7 +64,7 @@
* pin down where exceptions are originating.
*
* @return <code>true</code> for normal JUnit behavior, or
- * <code>false</code> to disable normal JUnit exception reporting
+ * <code>false</code> to disable normal JUnit getException reporting
*/
public boolean catchExceptions() {
return true;
@@ -123,8 +123,8 @@
* <ol>
* <li> If {@link #finishTest()} is called before the delay period expires,
* the test will succeed.</li>
- * <li> If any exception escapes from an event handler during the delay
- * period, the test will error with the thrown exception.</li>
+ * <li> If any getException escapes from an event handler during the delay
+ * period, the test will error with the thrown getException.</li>
* <li> If the delay period expires and neither of the above has happened, the
* test will error with a {@link TimeoutException}. </li>
* </ol>
@@ -171,6 +171,17 @@
}
/**
+ * Returns the overall test results for this unit test.
+ *
+ * These TestResults are more comprehensive than JUnit's default test results,
+ * and are automatically collected by GWT's testing infrastructure.
+ */
+ protected final TestResults getTestResults() {
+ // implemented in the translatable version of this class
+ return null;
+ }
+
+ /**
* Runs the test via the {@link JUnitShell} environment.
*/
protected final void runTest() throws Throwable {
diff --git a/user/src/com/google/gwt/junit/client/IntRange.java b/user/src/com/google/gwt/junit/client/IntRange.java
new file mode 100644
index 0000000..f2e47f3
--- /dev/null
+++ b/user/src/com/google/gwt/junit/client/IntRange.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2007 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;
+
+import java.util.Iterator;
+
+/**
+ * A {@link com.google.gwt.junit.client.Range} that iterates over a start and
+ * end value by a stepping function. Typically used by benchmarks to supply a
+ * range of values over an integral parameter, such as size or length.
+ *
+ */
+public class IntRange implements Range {
+
+ /**
+ * Implementation of the Iterator.
+ *
+ */
+ private static class IntRangeIterator extends RangeIterator {
+
+ int end;
+
+ Operator operator;
+
+ int start;
+
+ int step;
+
+ int value;
+
+ IntRangeIterator(IntRange r) {
+ this.value = this.start = r.start;
+ this.end = r.end;
+ this.operator = r.operator;
+ if (operator == null) {
+ throw new IllegalArgumentException("operator must be \"*\" or \"+\"");
+ }
+ this.step = r.step;
+ }
+
+ public boolean hasNext() {
+ return value <= end;
+ }
+
+ public Object next() {
+ int currentValue = value;
+ value = step();
+ return new Integer(currentValue);
+ }
+
+ public int step() {
+ if (operator == Operator.MULTIPLY) {
+ return value * step;
+ } else {
+ return value + step;
+ }
+ }
+ }
+
+ int end;
+
+ Operator operator;
+
+ int start;
+
+ int step;
+
+ /**
+ * Creates a new range that produces Iterators which begin at
+ * <code>start</code>, end at <code>end</code> and increment by the
+ * stepping function described by <code>operator</code> and
+ * <code>step</code>.
+ *
+ * @param start Initial starting value, inclusive.
+ * @param end Ending value, inclusive.
+ * @param operator The function used to step.
+ * @param step The amount to step by, for each iteration.
+ */
+ public IntRange(int start, int end, Operator operator, int step) {
+ this.start = start;
+ this.end = end;
+ this.operator = operator;
+ this.step = step;
+ if (step <= 0) {
+ throw new IllegalArgumentException("step must be > 0");
+ }
+ }
+
+ public Iterator iterator() {
+ return new IntRangeIterator(this);
+ }
+}
diff --git a/user/src/com/google/gwt/junit/client/Operator.java b/user/src/com/google/gwt/junit/client/Operator.java
new file mode 100644
index 0000000..f773a0a
--- /dev/null
+++ b/user/src/com/google/gwt/junit/client/Operator.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2007 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;
+
+/**
+ * A mathematical operator used in {@link com.google.gwt.junit.client.Range}s
+ * to indicate the stepping function.
+ */
+public final class Operator {
+
+ /**
+ * The standard multiplication operator.
+ */
+ public static Operator MULTIPLY = new Operator( "*" );
+
+ /**
+ * The standard addition operator.
+ */
+ public static Operator ADD = new Operator( "+" );
+
+ private String value;
+
+ private Operator(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Returns the textual representation of the <code>Operator</code>.
+ *
+ * @return a non-null {@link String}
+ */
+ public String toString() {
+ return value;
+ }
+}
diff --git a/user/src/com/google/gwt/junit/client/Range.java b/user/src/com/google/gwt/junit/client/Range.java
new file mode 100644
index 0000000..dd1eb98
--- /dev/null
+++ b/user/src/com/google/gwt/junit/client/Range.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2007 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;
+
+import java.util.Iterator;
+
+/**
+ * A range of values for a Benchmark parameter.
+ *
+ * A Range produces an Iterator that contains all of the values that a Benchmark
+ * parameter should be tested over.
+ *
+ * Range is unlikely to provide any extra semantics above what you would get
+ * with java.util.Iterable, but it was introduced before GWT's JDK 1.5 support.
+ *
+ */
+public interface Range {
+ Iterator iterator();
+}
diff --git a/user/src/com/google/gwt/junit/client/RangeIterator.java b/user/src/com/google/gwt/junit/client/RangeIterator.java
new file mode 100644
index 0000000..8c37eec
--- /dev/null
+++ b/user/src/com/google/gwt/junit/client/RangeIterator.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2007 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;
+
+import java.util.Iterator;
+
+/**
+ * A base class useful for implementing Iterators for Ranges.
+ *
+ */
+public abstract class RangeIterator implements Iterator {
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/user/src/com/google/gwt/junit/client/TestResults.java b/user/src/com/google/gwt/junit/client/TestResults.java
new file mode 100644
index 0000000..85d74c1
--- /dev/null
+++ b/user/src/com/google/gwt/junit/client/TestResults.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2007 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;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Encapsulates the results of the execution of a single benchmark. A TestResult
+ * is constructed transparently within a benchmark and reported back to the
+ * JUnit RPC server, JUnitHost. It's then shared (via JUnitMessageQueue) with
+ * JUnitShell and aggregated in BenchmarkReport with other TestResults.
+ *
+ * @skip
+ * @see com.google.gwt.junit.client.impl.JUnitHost
+ * @see com.google.gwt.junit.JUnitMessageQueue
+ * @see com.google.gwt.junit.JUnitShell
+ * @see com.google.gwt.junit.benchmarks.BenchmarkReport
+ */
+public class TestResults implements IsSerializable {
+
+ // Computed at the server, via http header
+ String agent;
+
+ String host;
+
+ /**
+ * The URL of the document on the browser (document.location). This is used to
+ * locate the *cache.html document containing the generated JavaScript for the
+ * test. In the case of hosted mode, this points (uselessly) to the nocache
+ * file, because there is no generated JavaScript.
+ *
+ * Apparently, we can't get this value on the server-side because of the goofy
+ * way HTTP_REFERER is set by different browser implementations of
+ * XMLHttpRequest.
+ */
+ String sourceRef;
+
+ /**
+ * @gwt.typeArgs <com.google.gwt.junit.client.Trial>
+ */
+ List/*<Trial>*/ trials;
+
+ public TestResults() {
+ trials = new ArrayList();
+ }
+
+ public String getAgent() {
+ return agent;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public String getSourceRef() {
+ return sourceRef;
+ }
+
+ public List getTrials() {
+ return trials;
+ }
+
+ public void setAgent(String agent) {
+ this.agent = agent;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public void setSourceRef(String sourceRef) {
+ this.sourceRef = sourceRef;
+ }
+
+ public void setTrials(List trials) {
+ this.trials = trials;
+ }
+
+ public String toString() {
+ return "trials: " + trials + ", sourceRef: " + sourceRef + ", agent: "
+ + agent + ", host: " + host;
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/junit/client/Trial.java b/user/src/com/google/gwt/junit/client/Trial.java
new file mode 100644
index 0000000..6e8b71a
--- /dev/null
+++ b/user/src/com/google/gwt/junit/client/Trial.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2007 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;
+
+import com.google.gwt.junit.client.impl.ExceptionWrapper;
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * The result of a single trial-run of a single benchmark method. Each Trial
+ * contains the results of running a benchmark method with one set of
+ * values for its parameters. TestResults for a method will contain Trials
+ * for all permutations of the parameter values. For test methods without
+ * parameters, there is only 1 trial result.
+ *
+ * @skip
+ */
+public class Trial implements IsSerializable {
+
+ ExceptionWrapper exceptionWrapper;
+
+ double runTimeMillis;
+
+ // Deserialized from exceptionWrapper on the server-side
+ transient Throwable exception;
+
+ /**
+ * @gwt.typeArgs <java.lang.String,java.lang.String>
+ */
+ Map/*<String,String>*/ variables;
+
+ /**
+ * Creates a new Trial.
+ *
+ * @param runTimeMillis The amount of time spent executing the test
+ * @param exceptionWrapper The wrapped getException thrown by the the last
+ * test, or <code>null</code> if the last test
+ * completed successfully.
+ */
+ public Trial(Map/*<String,String>*/ variables, double runTimeMillis,
+ ExceptionWrapper exceptionWrapper) {
+ this.variables = variables;
+ this.runTimeMillis = runTimeMillis;
+ this.exceptionWrapper = exceptionWrapper;
+ }
+
+ public Trial() {
+ this.variables = new HashMap();
+ }
+
+ public Throwable getException() {
+ return exception;
+ }
+
+ public ExceptionWrapper getExceptionWrapper() {
+ return exceptionWrapper;
+ }
+
+ public double getRunTimeMillis() {
+ return runTimeMillis;
+ }
+
+ /**
+ * Returns the names and values of the variables used in the test. If there
+ * were no variables, the map is empty.
+ */
+ public Map getVariables() {
+ return variables;
+ }
+
+ public void setException(Throwable exception) {
+ this.exception = exception;
+ }
+
+ public void setExceptionWrapper(ExceptionWrapper exceptionWrapper) {
+ this.exceptionWrapper = exceptionWrapper;
+ }
+
+ public void setRunTimeMillis(double runTimeMillis) {
+ this.runTimeMillis = runTimeMillis;
+ }
+
+ public String toString() {
+ return "variables: " + variables + ", exceptionWrapper: " + exceptionWrapper
+ + ", runTimeMillis: " + runTimeMillis;
+ }
+}
diff --git a/user/src/com/google/gwt/junit/client/impl/JUnitHost.java b/user/src/com/google/gwt/junit/client/impl/JUnitHost.java
index ba040c2..4e76afe 100644
--- a/user/src/com/google/gwt/junit/client/impl/JUnitHost.java
+++ b/user/src/com/google/gwt/junit/client/impl/JUnitHost.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -16,6 +16,7 @@
package com.google.gwt.junit.client.impl;
import com.google.gwt.user.client.rpc.RemoteService;
+import com.google.gwt.junit.client.TestResults;
/**
* An interface for {@link com.google.gwt.junit.client.GWTTestCase} to communicate with the test process
@@ -36,9 +37,8 @@
* run.
*
* @param testClassName The class name of the calling test case.
- * @param ew The wrapped exception thrown by the the last test, or
- * <code>null</code> if the last test completed successfully.
+ * @param results The results of executing the test
* @return the name of the next method to run.
*/
- String reportResultsAndGetNextMethod(String testClassName, ExceptionWrapper ew);
+ String reportResultsAndGetNextMethod(String testClassName, TestResults results);
}
diff --git a/user/src/com/google/gwt/junit/client/impl/JUnitHostAsync.java b/user/src/com/google/gwt/junit/client/impl/JUnitHostAsync.java
index dee9c01..41fb319 100644
--- a/user/src/com/google/gwt/junit/client/impl/JUnitHostAsync.java
+++ b/user/src/com/google/gwt/junit/client/impl/JUnitHostAsync.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -16,6 +16,7 @@
package com.google.gwt.junit.client.impl;
import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.junit.client.TestResults;
/**
* The asynchronous version of {@link JUnitHost}.
@@ -24,24 +25,21 @@
/**
* Gets the name of next method to run.
- *
+ *
* @param testClassName The class name of the calling test case.
- * @param callBack The object that will receive the name of the next method to
- * run.
+ * @param callBack The object that will receive the name of the next
+ * method to run.
*/
void getFirstMethod(String testClassName, AsyncCallback callBack);
/**
* Reports results for the last method run and gets the name of next method to
* run.
- *
+ *
* @param testClassName The class name of the calling test case.
- * @param ew The wrapped exception thrown by the the last test, or
- * <code>null</code> if the last test completed successfully.
- * @param callBack The object that will receive the name of the next method to
- * run.
+ * @param results The results of the test.
+ * @param callBack The object that will receive the name of the next
+ * method to run.
*/
- void reportResultsAndGetNextMethod(String testClassName, ExceptionWrapper ew,
- AsyncCallback callBack);
-
+ void reportResultsAndGetNextMethod(String testClassName, TestResults results, AsyncCallback callBack);
}
diff --git a/user/src/com/google/gwt/junit/client/impl/PermutationIterator.java b/user/src/com/google/gwt/junit/client/impl/PermutationIterator.java
new file mode 100644
index 0000000..fce580c
--- /dev/null
+++ b/user/src/com/google/gwt/junit/client/impl/PermutationIterator.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2007 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.junit.client.Range;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Iterates over all the possible permutations available in a list of
+ * {@link com.google.gwt.junit.client.Range}s.
+ *
+ * <p>The simplest way to iterate over the permutations of multiple iterators
+ * is in a nested for loop. The PermutationIterator turns that for loop inside
+ * out into a single iterator, which enables you to access each permutation
+ * in a piecemeal fashion.</p>
+ *
+ */
+public class PermutationIterator implements Iterator {
+
+ /**
+ * A single permutation of all the iterators. Contains the current value
+ * of each iterator for the permutation.
+ *
+ */
+ public static class Permutation {
+ private List values;
+ public Permutation( List values ) {
+ this.values = new ArrayList( values );
+ }
+ public List getValues() {
+ return values;
+ }
+ public String toString() {
+ return values.toString();
+ }
+ }
+
+ private static class ListRange implements Range {
+ private List list;
+ public ListRange( List list ) {
+ this.list = list;
+ }
+ public Iterator iterator() {
+ return list.iterator();
+ }
+ }
+ public static void main( String[] args ) {
+ List ranges = Arrays.asList(
+ new Range[] {
+ new ListRange( Arrays.asList( new String[] {"a", "b", "c" } ) ),
+ new ListRange( Arrays.asList( new String[] {"1", "2", "3" } ) ),
+ new ListRange( Arrays.asList( new String[] {"alpha", "beta", "gamma", "delta" } ) ),
+ }
+ );
+
+ System.out.println("Testing normal iteration.");
+ for ( Iterator it = new PermutationIterator(ranges); it.hasNext(); ) {
+ Permutation p = (Permutation) it.next();
+ System.out.println(p);
+ }
+
+ System.out.println("\nTesting skipping iteration.");
+
+ Iterator skipIterator = Arrays.asList( new String[] {"alpha", "beta", "gamma", "delta" } ).iterator();
+ boolean skipped = true;
+ String skipValue = null;
+ for ( PermutationIterator it = new PermutationIterator(ranges); it.hasNext(); ) {
+ Permutation p = (Permutation) it.next();
+
+ if ( skipped ) {
+ if ( skipIterator.hasNext() ) {
+ skipValue = (String) skipIterator.next();
+ skipped = false;
+ }
+ }
+
+ System.out.println(p);
+
+ String value = (String) p.getValues().get(p.getValues().size() - 1);
+
+ if ( value.equals(skipValue) ) {
+ it.skipCurrentRange();
+ skipped = true;
+ }
+ }
+ }
+ private boolean firstRun = true;
+ private List iterators;
+ private boolean maybeHaveMore = true;
+ private List ranges;
+
+ private boolean rangeSkipped = false;
+
+ private List values;
+
+ /**
+ * Constructs a new PermutationIterator that provides the values for each
+ * possible permutation of <code>ranges</code>.
+ *
+ * @param ranges non-null. Each {@link com.google.gwt.junit.client.Range}
+ * must have at least one element. ranges.size() must be > 1
+ *
+ * TODO(tobyr) Consider if empty Ranges ever make sense in the context of
+ * permutations.
+ *
+ */
+ public PermutationIterator( List ranges ) {
+ this.ranges = ranges;
+
+ iterators = new ArrayList();
+
+ for ( int i = 0; i < ranges.size(); ++i ) {
+ Range r = ( Range ) ranges.get( i );
+ iterators.add( r.iterator() );
+ }
+
+ values = new ArrayList();
+ }
+
+ /**
+ * Returns a new <code>Permutation</code> containing the values of the next
+ * permutation.
+ *
+ * @return a non-null <code>Permutation</code>
+ */
+ public boolean hasNext() {
+
+ if ( ! maybeHaveMore ) {
+ return false;
+ }
+
+ // Walk the iterators from bottom to top checking to see if any still have
+ // any available values
+
+ for ( int currentIterator = iterators.size() - 1; currentIterator >= 0; --currentIterator ) {
+ Iterator it = (Iterator) iterators.get( currentIterator );
+ if ( it.hasNext() ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public Object next() {
+ assert hasNext() : "No more available permutations in this iterator.";
+
+ if ( firstRun ) {
+
+ // Initialize all of our iterators and values on the first run
+ for ( int i = 0; i < iterators.size(); ++i ) {
+ Iterator it = ( Iterator ) iterators.get( i );
+ values.add( it.next() );
+ }
+ firstRun = false;
+ return new Permutation( values );
+ }
+
+ if ( rangeSkipped ) {
+ rangeSkipped = false;
+ return new Permutation( values );
+ }
+
+ // Walk through the iterators from bottom to top, finding the first one
+ // which has a value available. Increment it, reset all of the subsequent
+ // iterators, and then return the current permutation.
+ for ( int currentIteratorIndex = iterators.size() - 1; currentIteratorIndex >= 0; --currentIteratorIndex ) {
+ Iterator it = (Iterator) iterators.get( currentIteratorIndex );
+ if ( it.hasNext() ) {
+ values.set( currentIteratorIndex, it.next() );
+ for ( int i = currentIteratorIndex + 1; i < iterators.size(); ++i ) {
+ Range resetRange = (Range) ranges.get( i );
+ Iterator resetIterator = resetRange.iterator();
+ iterators.set(i, resetIterator);
+ values.set( i, resetIterator.next() );
+ }
+
+ return new Permutation( values );
+ }
+ }
+
+ throw new AssertionError( "Assertion failed - Couldn't find a non-empty iterator." );
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Skips the remaining set of values in the bottom
+ * {@link com.google.gwt.junit.client.Range}. This method affects the results
+ * of both hasNext() and next().
+ *
+ */
+ public void skipCurrentRange() {
+
+ rangeSkipped = true;
+
+ for ( int currentIteratorIndex = iterators.size() - 2; currentIteratorIndex >= 0; --currentIteratorIndex ) {
+ Iterator it = (Iterator) iterators.get( currentIteratorIndex );
+ if ( it.hasNext() ) {
+ values.set( currentIteratorIndex, it.next() );
+ for ( int i = currentIteratorIndex + 1; i < iterators.size(); ++i ) {
+ Range resetRange = (Range) ranges.get( i );
+ Iterator resetIterator = resetRange.iterator();
+ iterators.set( i, resetIterator );
+ values.set( i, resetIterator.next() );
+ }
+ return;
+ }
+ }
+
+ maybeHaveMore = false;
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/junit/rebind/BenchmarkGenerator.java b/user/src/com/google/gwt/junit/rebind/BenchmarkGenerator.java
new file mode 100644
index 0000000..9fec6c1
--- /dev/null
+++ b/user/src/com/google/gwt/junit/rebind/BenchmarkGenerator.java
@@ -0,0 +1,589 @@
+/*
+ * Copyright 2007 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.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.junit.JUnitShell;
+import com.google.gwt.dev.generator.ast.ForLoop;
+import com.google.gwt.dev.generator.ast.MethodCall;
+import com.google.gwt.dev.generator.ast.Statement;
+import com.google.gwt.dev.generator.ast.Statements;
+import com.google.gwt.dev.generator.ast.StatementsList;
+import com.google.gwt.user.rebind.SourceWriter;
+
+import java.util.Map;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Implements a generator for Benchmark classes. Benchmarks require additional
+ * code generation above and beyond standard JUnit tests.
+ */
+public class BenchmarkGenerator extends JUnitTestCaseStubGenerator {
+
+ private static class MutableBoolean {
+ boolean value;
+ }
+
+ private static final String BEGIN_PREFIX = "begin";
+
+ private static final String BENCHMARK_PARAM_META = "gwt.benchmark.param";
+
+ private static final String EMPTY_FUNC = "__emptyFunc";
+
+ private static final String END_PREFIX = "end";
+
+ private static final String ESCAPE_LOOP = "__escapeLoop";
+
+ /**
+ * Returns all the zero-argument JUnit test methods that do not have
+ * overloads.
+ *
+ * @return Map<String,JMethod>
+ */
+ public static Map getNotOverloadedTestMethods(JClassType requestedClass) {
+ Map methods = getAllMethods(requestedClass, new MethodFilter() {
+ public boolean accept(JMethod method) {
+ return isJUnitTestMethod(method, true);
+ }
+ });
+
+ for (Iterator it = methods.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ List methodOverloads = (List) entry.getValue();
+ if (methodOverloads.size() > 1) {
+ it.remove();
+ continue;
+ }
+ entry.setValue(methodOverloads.get(0));
+ }
+
+ return methods;
+ }
+
+ /**
+ * Returns all the JUnit test methods that are overloaded test methods with
+ * parameters. Does not include the zero-argument test methods.
+ *
+ * @return Map<String,JMethod>
+ */
+ public static Map getParameterizedTestMethods(JClassType requestedClass,
+ TreeLogger logger) {
+
+ Map testMethods = getAllMethods(requestedClass, new MethodFilter() {
+ public boolean accept(JMethod method) {
+ return isJUnitTestMethod(method, true);
+ }
+ });
+
+ // Remove all non-overloaded test methods
+ for (Iterator it = testMethods.entrySet().iterator(); it.hasNext();) {
+
+ Map.Entry entry = (Map.Entry) it.next();
+ String name = (String) entry.getKey();
+ List methods = (List) entry.getValue();
+
+ if (methods.size() > 2) {
+ String msg = requestedClass + "." + name
+ + " has more than one overloaded version.\n" +
+ "It will not be included in the test case execution.";
+ logger.log(TreeLogger.WARN, msg, null);
+ it.remove();
+ continue;
+ }
+
+ if (methods.size() == 1) {
+ JMethod method = (JMethod) methods.get(0);
+ if (method.getParameters().length != 0) {
+ /* User probably goofed - otherwise why create a test method with
+ * arguments but not the corresponding no-argument version? Would be
+ * better if our benchmarking system didn't require the no-argument
+ * test to make the benchmarks run correctly (JUnit artifact).
+ */
+ String msg = requestedClass + "." + name
+ + " does not have a zero-argument overload.\n" +
+ "It will not be included in the test case execution.";
+ logger.log(TreeLogger.WARN, msg, null);
+ }
+ // Only a zero-argument version, we don't need to process it.
+ it.remove();
+ continue;
+ }
+
+ JMethod method1 = (JMethod) methods.get(0);
+ JMethod method2 = (JMethod) methods.get(1);
+ JMethod noArgMethod = null;
+ JMethod overloadedMethod = null;
+
+ if (method1.getParameters().length == 0) {
+ noArgMethod = method1;
+ } else {
+ overloadedMethod = method1;
+ }
+
+ if (method2.getParameters().length == 0) {
+ noArgMethod = method2;
+ } else {
+ overloadedMethod = method2;
+ }
+
+ if (noArgMethod == null) {
+ String msg = requestedClass + "." + name
+ + " does not have a zero-argument overload.\n" +
+ "It will not be included in the test case execution.";
+ logger.log(TreeLogger.WARN, msg, null);
+ it.remove();
+ continue;
+ }
+
+ entry.setValue(overloadedMethod);
+ }
+
+ return testMethods;
+ }
+
+ private static JMethod getBeginMethod(JClassType type, String name) {
+ StringBuffer methodName = new StringBuffer(name);
+ methodName.replace(0, "test".length(), BEGIN_PREFIX);
+ return getMethod(type, methodName.toString());
+ }
+
+ private static JMethod getEndMethod(JClassType type, String name) {
+ StringBuffer methodName = new StringBuffer(name);
+ methodName.replace(0, "test".length(), END_PREFIX);
+ return getMethod(type, methodName.toString());
+ }
+
+ private static JMethod getMethod(JClassType type, MethodFilter filter) {
+ Map map = getAllMethods(type, filter);
+ Set entrySet = map.entrySet();
+ if (entrySet.size() == 0) {
+ return null;
+ }
+ List methods = (List) ((Map.Entry) entrySet.iterator().next()).getValue();
+ return (JMethod) methods.get(0);
+ }
+
+ private static JMethod getMethod(JClassType type, final String name) {
+ return getMethod(type, new MethodFilter() {
+ public boolean accept(JMethod method) {
+ return method.getName().equals(name);
+ }
+ });
+ }
+
+ public void writeSource() throws UnableToCompleteException {
+ super.writeSource();
+
+ // Needed for benchmarking the overhead of the function call to the
+ // benchmark
+ generateEmptyFunc(getSourceWriter());
+
+ implementZeroArgTestMethods();
+ implementParameterizedTestMethods();
+ JUnitShell.getReport().addBenchmark(getRequestedClass(), getTypeOracle());
+ }
+
+ /**
+ * Generates benchmarking code which wraps <code>stmts</code> The timing
+ * result is a double in units of milliseconds. It's value is placed in the
+ * variable named, <code>timeMillisName</code>.
+ *
+ * @return The set of Statements containing the benchmark code along with the
+ * wrapped <code>stmts</code>
+ */
+ private Statements benchmark(Statements stmts, String timeMillisName,
+ boolean generateEscape, Statements recordCode, Statements breakCode) {
+ Statements benchmarkCode = new StatementsList();
+ List benchStatements = benchmarkCode.getStatements();
+
+ ForLoop loop = new ForLoop("int numLoops = 1", "true", "");
+ benchStatements.add(loop);
+ List loopStatements = loop.getStatements();
+
+ loopStatements
+ .add(new Statement("long start = System.currentTimeMillis()"));
+ ForLoop runLoop = new ForLoop("int i = 0", "i < numLoops", "++i", stmts);
+ loopStatements.add(runLoop);
+
+ // Put the rest of the code in 1 big statement to simplify things
+ String benchCode =
+ "long duration = System.currentTimeMillis() - start;\n\n" +
+
+ "if ( duration < 150 ) {\n" +
+ " numLoops += numLoops;\n" +
+ " continue;\n" +
+ "}\n\n" +
+
+ "double durationMillis = duration * 1.0;\n" +
+ "double numLoopsAsDouble = numLoops * 1.0;\n" +
+ timeMillisName + " = durationMillis / numLoopsAsDouble";
+
+ loopStatements.add(new Statement(benchCode));
+
+ if (recordCode != null) {
+ loopStatements.add(recordCode);
+ }
+
+ if (generateEscape) {
+ loopStatements.add(new Statement(
+ "if ( numLoops == 1 && duration > 1000 ) {\n" +
+ breakCode.toString() + "\n" +
+ "}\n\n"
+ ));
+ }
+
+ loopStatements.add(new Statement("break"));
+
+ return benchmarkCode;
+ }
+
+ /**
+ * Generates code that executes <code>statements</code> for all possible
+ * values of <code>params</code>. Exports a label named ESCAPE_LOOP that
+ * points to the the "inner loop" that should be escaped to for a limited
+ * variable.
+ *
+ * @return the generated code
+ */
+ private Statements executeForAllValues(JParameter[] methodParams, Map params,
+ Statements statements) {
+ Statements root = new StatementsList();
+ Statements currentContext = root;
+
+ // Profile the setup and teardown costs for this test method
+ // but only if 1 of them exists.
+ for (int i = 0; i < methodParams.length; ++i) {
+ JParameter methodParam = methodParams[i];
+ String paramName = methodParam.getName();
+ String paramValue = (String) params.get(paramName);
+
+ String iteratorName = "it_" + paramName;
+ String initializer = "java.util.Iterator " + iteratorName + " = "
+ + paramValue + ".iterator()";
+ ForLoop loop = new ForLoop(initializer, iteratorName + ".hasNext()", "");
+ if (i == methodParams.length - 1) {
+ loop.setLabel(ESCAPE_LOOP);
+ }
+ currentContext.getStatements().add(loop);
+ String typeName = methodParam.getType().getQualifiedSourceName();
+ loop.getStatements().add(new Statement(typeName + " " + paramName + " = ("
+ + typeName + ") " + iteratorName + ".next()"));
+ currentContext = loop;
+ }
+
+ currentContext.getStatements().add(statements);
+
+ return root;
+ }
+
+ private Statements genBenchTarget(JMethod beginMethod, JMethod endMethod,
+ List paramNames, Statements test) {
+ Statements statements = new StatementsList();
+ List statementsList = statements.getStatements();
+
+ if (beginMethod != null) {
+ statementsList.add(
+ new Statement(new MethodCall(beginMethod.getName(), paramNames)));
+ }
+
+ statementsList.add(test);
+
+ if (endMethod != null) {
+ statementsList
+ .add(new Statement(new MethodCall(endMethod.getName(), null)));
+ }
+
+ return statements;
+ }
+
+ /**
+ * Generates an empty JSNI function to help us benchmark function call
+ * overhead.
+ *
+ * We prevent our empty function call from being inlined by the compiler by
+ * making it a JSNI call. This works as of 1.3 RC 2, but smarter versions of
+ * the compiler may be able to inline JSNI.
+ *
+ * Things actually get pretty squirrely in general when benchmarking function
+ * call overhead, because, depending upon the benchmark, the compiler may
+ * inline the benchmark into our benchmark loop, negating the cost we thought
+ * we were measuring.
+ *
+ * The best way to deal with this is for users to write micro-benchmarks such
+ * that the micro-benchmark does significantly more work than a function call.
+ * For example, if micro-benchmarking a function call, perform the function
+ * call 100K times within the microbenchmark itself.
+ */
+ private void generateEmptyFunc(SourceWriter writer) {
+ writer.println("private native void " + EMPTY_FUNC + "() /*-{");
+ writer.println("}-*/;");
+ writer.println();
+ }
+
+ private Map/*<String,String>*/ getParamMetaData(JMethod method,
+ MutableBoolean isBounded) throws UnableToCompleteException {
+ Map/*<String,String>*/ params = new HashMap/*<String,String>*/();
+
+ String[][] allValues = method.getMetaData(BENCHMARK_PARAM_META);
+
+ if (allValues == null) {
+ return params;
+ }
+
+ for (int i = 0; i < allValues.length; ++i) {
+ String[] values = allValues[i];
+ StringBuffer result = new StringBuffer();
+ for (int j = 0; j < values.length; ++j) {
+ result.append(values[j]);
+ result.append(" ");
+ }
+ String expr = result.toString();
+ String[] lhsAndRhs = expr.split("=");
+ String paramName = lhsAndRhs[0].trim();
+ String[] nameExprs = paramName.split(" ");
+ if (nameExprs.length > 1 && nameExprs[1].equals("-limit")) {
+ paramName = nameExprs[0];
+ // Make sure this is the last parameter
+ JParameter[] parameters = method.getParameters();
+ if (! parameters[parameters.length - 1].getName().equals(paramName)) {
+ JClassType cls = method.getEnclosingType();
+ String msg = "Error at " + cls + "." + method.getName() + "\n" +
+ "Only the last parameter of a method can be marked with the -limit flag.";
+ logger.log(TreeLogger.ERROR, msg, null);
+ throw new UnableToCompleteException();
+ }
+
+ isBounded.value = true;
+ }
+ String paramValue = lhsAndRhs[1].trim();
+ params.put(paramName, paramValue);
+ }
+
+ return params;
+ }
+
+ private void implementParameterizedTestMethods() throws
+ UnableToCompleteException {
+
+ Map/*<String,JMethod>*/ parameterizedMethods = getParameterizedTestMethods(
+ getRequestedClass(), logger);
+ SourceWriter sw = getSourceWriter();
+ JClassType type = getRequestedClass();
+
+ // For each test method, benchmark its:
+ // a) overhead (setup + teardown + loop + function calls) and
+ // b) execution time
+ // for all possible parameter values
+ for (Iterator it = parameterizedMethods.entrySet().iterator();
+ it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ String name = (String) entry.getKey();
+ JMethod method = (JMethod) entry.getValue();
+ JMethod beginMethod = getBeginMethod(type, name);
+ JMethod endMethod = getEndMethod(type, name);
+
+ sw.println("public void " + name + "() {");
+ sw.indent();
+ sw.println(" delayTestFinish( 2000 );");
+ sw.println();
+
+ MutableBoolean isBounded = new MutableBoolean();
+ Map params = getParamMetaData(method, isBounded);
+ validateParams(method, params);
+
+ JParameter[] methodParams = method.getParameters();
+ List paramNames = new ArrayList(methodParams.length);
+ for (int i = 0; i < methodParams.length; ++i) {
+ paramNames.add(methodParams[i].getName());
+ }
+
+ List paramValues = new ArrayList(methodParams.length);
+ for (int i = 0; i < methodParams.length; ++i) {
+ paramValues.add(params.get(methodParams[i].getName()));
+ }
+
+ sw.print( "final java.util.List ranges = java.util.Arrays.asList( new com.google.gwt.junit.client.Range[] { " );
+
+ for (int i = 0; i < paramNames.size(); ++i) {
+ String paramName = (String) paramNames.get(i);
+ sw.print( (String) params.get(paramName) );
+ if (i != paramNames.size() - 1) {
+ sw.print( ",");
+ } else {
+ sw.println( "} );" );
+ }
+ sw.print( " " );
+ }
+
+ sw.println(
+ "final com.google.gwt.junit.client.impl.PermutationIterator permutationIt = new com.google.gwt.junit.client.impl.PermutationIterator( ranges );\n" +
+ "com.google.gwt.user.client.DeferredCommand.addCommand( new com.google.gwt.user.client.IncrementalCommand() {\n" +
+ " public boolean execute() {\n" +
+ " delayTestFinish( 10000 );\n" +
+ " if ( permutationIt.hasNext() ) {\n" +
+ " com.google.gwt.junit.client.impl.PermutationIterator.Permutation permutation = (com.google.gwt.junit.client.impl.PermutationIterator.Permutation) permutationIt.next();\n"
+ );
+
+ for (int i = 0; i < methodParams.length; ++i) {
+ JParameter methodParam = methodParams[i];
+ String typeName = methodParam.getType().getQualifiedSourceName();
+ String paramName = (String) paramNames.get(i);
+ sw.println( " " + typeName + " " + paramName + " = (" +
+ typeName + ") permutation.getValues().get(" + i + ");");
+ }
+
+ final String setupTimingName = "__setupTiming";
+ final String testTimingName = "__testTiming";
+
+ sw.println("double " + setupTimingName + " = 0;");
+ sw.println("double " + testTimingName + " = 0;");
+
+ Statements setupBench = genBenchTarget(beginMethod, endMethod, paramNames,
+ new Statement(new MethodCall(EMPTY_FUNC, null)));
+ Statements testBench = genBenchTarget(beginMethod, endMethod, paramNames,
+ new Statement(new MethodCall(method.getName(), paramNames)));
+
+ StringBuffer recordResultsCode = new StringBuffer(
+ "com.google.gwt.junit.client.TestResults results = getTestResults();\n" +
+ "com.google.gwt.junit.client.Trial trial = new com.google.gwt.junit.client.Trial();\n" +
+ "trial.setRunTimeMillis( " + testTimingName + " - " + setupTimingName + " );\n" +
+ "java.util.Map variables = trial.getVariables();\n");
+
+ for (int i = 0; i < paramNames.size(); ++i) {
+ String paramName = (String) paramNames.get(i);
+ recordResultsCode.append("variables.put( \"")
+ .append(paramName)
+ .append("\", ")
+ .append(paramName)
+ .append(".toString() );\n");
+ }
+
+ recordResultsCode.append("results.getTrials().add( trial )");
+ Statements recordCode = new Statement(recordResultsCode.toString());
+
+ Statements breakCode = new Statement( " permutationIt.skipCurrentRange()" );
+ setupBench = benchmark(setupBench, setupTimingName, false, null, breakCode);
+ testBench = benchmark(testBench, testTimingName, isBounded.value, recordCode, breakCode);
+
+ Statements testAndSetup = new StatementsList();
+ testAndSetup.getStatements().addAll(setupBench.getStatements());
+ testAndSetup.getStatements().addAll(testBench.getStatements());
+
+ sw.println( testAndSetup.toString() );
+
+ sw.println(
+ " return true;\n" +
+ " }\n" +
+ " finishTest();\n" +
+ " return false;\n" +
+ " }\n" +
+ "} );\n"
+ );
+
+ sw.outdent();
+ sw.println("}");
+ }
+ }
+
+ /**
+ * Overrides the zero-arg test methods that don't have any
+ * overloaded/parameterized versions.
+ *
+ * TODO(tobyr) This code shares a lot of similarity with
+ * implementParameterizedTestMethods and they should probably be refactored
+ * into a single function.
+ */
+ private void implementZeroArgTestMethods() {
+ Map zeroArgMethods = getNotOverloadedTestMethods(getRequestedClass());
+ SourceWriter sw = getSourceWriter();
+ JClassType type = getRequestedClass();
+
+ for (Iterator it = zeroArgMethods.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ String name = (String) entry.getKey();
+ JMethod method = (JMethod) entry.getValue();
+ JMethod beginMethod = getBeginMethod(type, name);
+ JMethod endMethod = getEndMethod(type, name);
+
+ sw.println("public void " + name + "() {");
+ sw.indent();
+
+ final String setupTimingName = "__setupTiming";
+ final String testTimingName = "__testTiming";
+
+ sw.println("double " + setupTimingName + " = 0;");
+ sw.println("double " + testTimingName + " = 0;");
+
+ Statements setupBench = genBenchTarget(beginMethod, endMethod,
+ Collections.EMPTY_LIST,
+ new Statement(new MethodCall(EMPTY_FUNC, null)));
+
+ StatementsList testStatements = new StatementsList();
+ testStatements.getStatements().add(
+ new Statement(new MethodCall("super." + method.getName(), null)));
+ Statements testBench = genBenchTarget(beginMethod, endMethod,
+ Collections.EMPTY_LIST, testStatements);
+
+ String recordResultsCode =
+ "com.google.gwt.junit.client.TestResults results = getTestResults();\n" +
+ "com.google.gwt.junit.client.Trial trial = new com.google.gwt.junit.client.Trial();\n" +
+ "trial.setRunTimeMillis( " + testTimingName + " - " + setupTimingName + " );\n" +
+ "results.getTrials().add( trial )";
+
+ Statements breakCode = new Statement( " break " + ESCAPE_LOOP );
+
+ setupBench = benchmark(setupBench, setupTimingName, false, null, breakCode);
+ testBench = benchmark(testBench, testTimingName, true,
+ new Statement(recordResultsCode), breakCode);
+ ForLoop loop = (ForLoop) testBench.getStatements().get(0);
+ loop.setLabel(ESCAPE_LOOP);
+
+ sw.println(setupBench.toString());
+ sw.println(testBench.toString());
+
+ sw.outdent();
+ sw.println("}");
+ }
+ }
+
+ private void validateParams(JMethod method, Map params)
+ throws UnableToCompleteException {
+ JParameter[] methodParams = method.getParameters();
+ for (int i = 0; i < methodParams.length; ++i) {
+ JParameter methodParam = methodParams[i];
+ String paramName = methodParam.getName();
+ String paramValue = (String) params.get(paramName);
+
+ if (paramValue == null) {
+ String msg = "Could not find the meta data attribute "
+ + BENCHMARK_PARAM_META +
+ " for the parameter " + paramName + " on method " + method
+ .getName();
+ logger.log(TreeLogger.ERROR, msg, null);
+ throw new UnableToCompleteException();
+ }
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/junit/rebind/JUnitTestCaseStubGenerator.java b/user/src/com/google/gwt/junit/rebind/JUnitTestCaseStubGenerator.java
index 68a1b9b..2f71036 100644
--- a/user/src/com/google/gwt/junit/rebind/JUnitTestCaseStubGenerator.java
+++ b/user/src/com/google/gwt/junit/rebind/JUnitTestCaseStubGenerator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -23,21 +23,143 @@
import com.google.gwt.core.ext.typeinfo.JMethod;
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.core.ext.typeinfo.JParameter;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import java.io.PrintWriter;
-import java.util.HashSet;
+import java.util.Map;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.HashMap;
/**
* 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 JUnitTestCaseStubGenerator extends Generator {
- private static final String GWT_TESTCASE_CLASS_NAME = GWTTestCase.class.getName();
+ interface MethodFilter {
+ public boolean accept( JMethod method );
+ }
+
+ private static final String GWT_TESTCASE_CLASS_NAME = "com.google.gwt.junit.client.GWTTestCase";
+
+ /**
+ * Returns the method names for the set of methods that are strictly JUnit
+ * test methods (have no arguments).
+ *
+ * @param requestedClass
+ */
+ public static String[] getTestMethodNames(JClassType requestedClass) {
+ return (String[]) getAllMethods( requestedClass, new MethodFilter() {
+ public boolean accept(JMethod method) {
+ return isJUnitTestMethod(method,false);
+ }
+ } ).keySet().toArray( new String[] {} );
+ }
+
+ /**
+ * Like JClassType.getMethod( String name ), except:
+ *
+ * <li>it accepts a filter</li>
+ * <li>it searches the inheritance hierarchy (includes subclasses)</li>
+ *
+ * For methods which are overriden, only the most derived implementations are included.
+ *
+ * @param type The type to search. Must not be null
+ * @return Map<String.List<JMethod>> The set of matching methods. Will not be null.
+ */
+ static Map getAllMethods( JClassType type, MethodFilter filter ) {
+ Map methods = new HashMap/*<String,List<JMethod>>*/();
+ JClassType cls = type;
+
+ while (cls != null) {
+ JMethod[] clsDeclMethods = cls.getMethods();
+
+ // For every method, include it iff our filter accepts it
+ // and we don't already have a matching method
+ for (int i = 0, n = clsDeclMethods.length; i < n; ++i) {
+
+ JMethod declMethod = clsDeclMethods[i];
+
+ if ( ! filter.accept(declMethod) ) {
+ continue;
+ }
+
+ List list = (List)methods.get(declMethod.getName());
+
+ if (list == null) {
+ list = new ArrayList();
+ methods.put(declMethod.getName(),list);
+ list.add(declMethod);
+ continue;
+ }
+
+ JParameter[] declParams = declMethod.getParameters();
+
+ for (int j = 0; j < list.size(); ++j) {
+ JMethod method = (JMethod)list.get(j);
+ JParameter[] parameters = method.getParameters();
+ if ( ! equals( declParams, parameters )) {
+ list.add(declMethod );
+ }
+ }
+ }
+ cls = cls.getSuperclass();
+ }
+
+ return methods;
+ }
+
+ /**
+ * Returns true if the method is considered to be a valid JUnit test method.
+ * The criteria are that the method's name begin with "test", have public
+ * access, and not be static. You must choose to include or exclude methods
+ * which have arguments.
+ *
+ */
+ static boolean isJUnitTestMethod(JMethod method, boolean acceptArgs) {
+ if (!method.getName().startsWith("test")) {
+ return false;
+ }
+
+ if (!method.isPublic() || method.isStatic()) {
+ return false;
+ }
+
+ return acceptArgs || method.getParameters().length == 0 && ! acceptArgs;
+ }
+
+ /**
+ * Returns true iff the two sets of parameters are of the same lengths and types.
+ *
+ * @param params1 must not be null
+ * @param params2 must not be null
+ */
+ private static boolean equals( JParameter[] params1, JParameter[] params2 ) {
+ if ( params1.length != params2.length ) {
+ return false;
+ }
+ for ( int i = 0; i < params1.length; ++i ) {
+ if ( params1[ i ].getType() != params2[ i ].getType() ) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ String qualifiedStubClassName;
+ String simpleStubClassName;
+ String typeName;
+ TreeLogger logger;
+ String packageName;
+
+ private JClassType requestedClass;
+ private SourceWriter sourceWriter;
+ private TypeOracle typeOracle;
/**
* Create a new type that statisfies the rebind request.
@@ -45,40 +167,62 @@
public String generate(TreeLogger logger, GeneratorContext context,
String typeName) throws UnableToCompleteException {
- TypeOracle typeOracle = context.getTypeOracle();
+ if ( ! init( logger, context, typeName ) ) {
+ return qualifiedStubClassName;
+ }
+
+ writeSource();
+ sourceWriter.commit( logger );
+
+ return qualifiedStubClassName;
+ }
+
+ public JClassType getRequestedClass() {
+ return requestedClass;
+ }
+
+ public SourceWriter getSourceWriter() {
+ return sourceWriter;
+ }
+
+ public TypeOracle getTypeOracle() {
+ return typeOracle;
+ }
+
+ boolean init(TreeLogger logger, GeneratorContext context,String typeName) throws
+ UnableToCompleteException {
+
+ this.typeName = typeName;
+ this.logger = logger;
+ typeOracle = context.getTypeOracle();
assert typeOracle != null;
- JClassType requestedClass;
try {
requestedClass = typeOracle.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);
+ + "'; please see the log, as this usually indicates a previous error ",
+ e);
throw new UnableToCompleteException();
}
// Get the stub class name, and see if its source file exists.
//
- String simpleStubClassName = getSimpleStubClassName(requestedClass);
+ simpleStubClassName = getSimpleStubClassName(requestedClass);
+ packageName = requestedClass.getPackage().getName();
+ qualifiedStubClassName = packageName + "." + simpleStubClassName;
- String packageName = requestedClass.getPackage().getName();
- String qualifiedStubClassName = packageName + "." + simpleStubClassName;
+ sourceWriter = getSourceWriter(logger, context, packageName,
+ simpleStubClassName, requestedClass.getQualifiedSourceName());
- SourceWriter sw = getSourceWriter(logger, context, packageName,
- simpleStubClassName, requestedClass.getQualifiedSourceName());
- if (sw == null) {
- return qualifiedStubClassName;
- }
+ return sourceWriter != null;
+ }
+ void writeSource() throws UnableToCompleteException {
String[] testMethods = getTestMethodNames(requestedClass);
- writeGetNewTestCase(simpleStubClassName, sw);
- writeDoRunTestMethod(testMethods, sw);
- writeGetTestName(typeName, sw);
-
- sw.commit(logger);
-
- return qualifiedStubClassName;
+ writeGetNewTestCase(simpleStubClassName, sourceWriter);
+ writeDoRunTestMethod(testMethods, sourceWriter);
+ writeGetTestName(typeName, sourceWriter);
}
/**
@@ -87,7 +231,7 @@
private String getSimpleStubClassName(JClassType baseClass) {
return "__" + baseClass.getSimpleSourceName() + "_unitTestImpl";
}
-
+
private SourceWriter getSourceWriter(TreeLogger logger, GeneratorContext ctx,
String packageName, String className, String superclassName) {
@@ -104,70 +248,6 @@
return composerFactory.createSourceWriter(ctx, printWriter);
}
- /**
- * Given a class return all methods that are considered JUnit test methods up
- * to but not including the declared methods of the class named
- * GWT_TESTCASE_CLASS_NAME.
- */
- private String[] getTestMethodNames(JClassType requestedClass) {
- HashSet testMethodNames = new HashSet();
- JClassType cls = requestedClass;
-
- while (true) {
- // We do not consider methods in the GWT superclass or above
- //
- if (isGWTTestCaseClass(cls)) {
- break;
- }
-
- JMethod[] clsDeclMethods = cls.getMethods();
- for (int i = 0, n = clsDeclMethods.length; i < n; ++i) {
- JMethod declMethod = clsDeclMethods[i];
-
- // Skip methods that are not JUnit test methods.
- //
- if (!isJUnitTestMethod(declMethod)) {
- continue;
- }
-
- if (testMethodNames.contains(declMethod.getName())) {
- continue;
- }
-
- testMethodNames.add(declMethod.getName());
- }
-
- cls = cls.getSuperclass();
- }
-
- return (String[]) testMethodNames.toArray(new String[testMethodNames.size()]);
- }
-
- /**
- * Returns true if the class is the special GWT Test Case derived class.
- */
- private boolean isGWTTestCaseClass(JClassType cls) {
- return cls.getQualifiedSourceName().equalsIgnoreCase(
- GWT_TESTCASE_CLASS_NAME);
- }
-
- /**
- * Returns true if the method is considered to be a valid JUnit test method.
- * The criteria are that the method's name begin with "test", have public
- * access, and not be static.
- */
- private boolean isJUnitTestMethod(JMethod method) {
- if (!method.getName().startsWith("test")) {
- return false;
- }
-
- if (!method.isPublic() || method.isStatic()) {
- return false;
- }
-
- return true;
- }
-
private void writeDoRunTestMethod(String[] testMethodNames, SourceWriter sw) {
sw.println();
sw.println("protected final void doRunTest(String name) throws Throwable {");
diff --git a/user/src/com/google/gwt/junit/server/JUnitHostImpl.java b/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
index 67eb6e8..e62a581 100644
--- a/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
+++ b/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -20,41 +20,50 @@
import com.google.gwt.junit.client.impl.ExceptionWrapper;
import com.google.gwt.junit.client.impl.JUnitHost;
import com.google.gwt.junit.client.impl.StackTraceWrapper;
+import com.google.gwt.junit.client.TestResults;
+import com.google.gwt.junit.client.Trial;
import com.google.gwt.user.client.rpc.InvocationException;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
/**
- * An RPC servlet that serves as a proxy to GWTUnitTestShell. Enables
+ * An RPC servlet that serves as a proxy to JUnitTestShell. Enables
* communication between the unit test code running in a browser and the real
* test process.
*/
public class JUnitHostImpl extends RemoteServiceServlet implements JUnitHost {
/**
+ * A maximum timeout to wait for the test system to respond with the next
+ * test. Practically speaking, the test system should respond nearly instantly
+ * if there are furthur tests to run.
+ */
+ private static final int TIME_TO_WAIT_FOR_TESTNAME = 300000;
+
+ // DEBUG timeout
+ // TODO(tobyr) Make this configurable
+ // private static final int TIME_TO_WAIT_FOR_TESTNAME = 500000;
+
+ /**
* A hook into GWTUnitTestShell, the underlying unit test process.
*/
private static JUnitMessageQueue sHost = null;
/**
- * A maximum timeout to wait for the test system to respond with the next
- * test. Practically speaking, the test system should respond nearly instantly
- * if there are furthur tests to run.
- */
- private static final int TIME_TO_WAIT_FOR_TESTNAME = 5000;
-
- /**
- * Tries to grab the GWTUnitTestShell sHost environment to communicate with the
- * real test process.
+ * Tries to grab the GWTUnitTestShell sHost environment to communicate with
+ * the real test process.
*/
private static synchronized JUnitMessageQueue getHost() {
if (sHost == null) {
sHost = JUnitShell.getMessageQueue();
if (sHost == null) {
throw new InvocationException(
- "Unable to find JUnitShell; is this servlet running under GWTTestCase?");
+ "Unable to find JUnitShell; is this servlet running under GWTTestCase?");
}
}
return sHost;
@@ -72,14 +81,27 @@
}
public String getFirstMethod(String testClassName) {
- return getHost().getNextTestName(testClassName, TIME_TO_WAIT_FOR_TESTNAME);
+ return getHost().getNextTestName(getClientId(), testClassName,
+ TIME_TO_WAIT_FOR_TESTNAME);
}
public String reportResultsAndGetNextMethod(String testClassName,
- ExceptionWrapper ew) {
+ TestResults results) {
JUnitMessageQueue host = getHost();
- host.reportResults(testClassName, deserialize(ew));
- return host.getNextTestName(testClassName, TIME_TO_WAIT_FOR_TESTNAME);
+ HttpServletRequest request = getThreadLocalRequest();
+ String agent = request.getHeader("User-Agent");
+ results.setAgent(agent);
+ String machine = request.getRemoteHost();
+ results.setHost(machine);
+ List trials = results.getTrials();
+ for (int i = 0; i < trials.size(); ++i) {
+ Trial trial = (Trial) trials.get(i);
+ ExceptionWrapper ew = trial.getExceptionWrapper();
+ trial.setException(deserialize(ew));
+ }
+ host.reportResults(testClassName, results);
+ return host.getNextTestName(getClientId(), testClassName,
+ TIME_TO_WAIT_FOR_TESTNAME);
}
/**
@@ -97,14 +119,14 @@
try {
// try ExType(String, Throwable)
Constructor ctor = exClass.getDeclaredConstructor(new Class[]{
- String.class, Throwable.class});
+ String.class, Throwable.class});
ctor.setAccessible(true);
ex = (Throwable) ctor.newInstance(new Object[]{ew.message, cause});
} catch (Throwable e) {
// try ExType(String)
try {
Constructor ctor = exClass
- .getDeclaredConstructor(new Class[]{String.class});
+ .getDeclaredConstructor(new Class[]{String.class});
ctor.setAccessible(true);
ex = (Throwable) ctor.newInstance(new Object[]{ew.message});
ex.initCause(cause);
@@ -112,7 +134,7 @@
// try ExType(Throwable)
try {
Constructor ctor = exClass
- .getDeclaredConstructor(new Class[]{Throwable.class});
+ .getDeclaredConstructor(new Class[]{Throwable.class});
ctor.setAccessible(true);
ex = (Throwable) ctor.newInstance(new Object[]{cause});
setField(exClass, "detailMessage", ex, ew.message);
@@ -126,8 +148,8 @@
setField(exClass, "detailMessage", ex, ew.message);
} catch (Throwable e4) {
// we're out of options
- this.log("Failed to deserialize exception of type '"
- + ew.typeName + "'; no available constructor", e4);
+ this.log("Failed to deserialize getException of type '"
+ + ew.typeName + "'; no available constructor", e4);
// fall through
}
@@ -136,8 +158,9 @@
}
} catch (Throwable e) {
- this.log("Failed to deserialize exception of type '" + ew.typeName + "'",
- e);
+ this.log(
+ "Failed to deserialize getException of type '" + ew.typeName + "'",
+ e);
}
if (ex == null) {
@@ -154,13 +177,14 @@
private StackTraceElement deserialize(StackTraceWrapper stw) {
StackTraceElement ste = null;
Object[] args = new Object[]{
- stw.className, stw.methodName, stw.fileName, new Integer(stw.lineNumber)};
+ stw.className, stw.methodName, stw.fileName,
+ new Integer(stw.lineNumber)};
try {
try {
// Try the 4-arg ctor (JRE 1.5)
Constructor ctor = StackTraceElement.class
- .getDeclaredConstructor(new Class[]{
- String.class, String.class, String.class, int.class});
+ .getDeclaredConstructor(new Class[]{
+ String.class, String.class, String.class, int.class});
ctor.setAccessible(true);
ste = (StackTraceElement) ctor.newInstance(args);
} catch (NoSuchMethodException e) {
@@ -191,4 +215,13 @@
return result;
}
+ /**
+ * Returns a "client id" for the current request.
+ */
+ private String getClientId() {
+ HttpServletRequest request = getThreadLocalRequest();
+ String agent = request.getHeader("User-Agent");
+ String machine = request.getRemoteHost();
+ return machine + " / " + agent;
+ }
}
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 adee751..4e22764 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -76,4 +76,7 @@
impl.finishTest();
}
+ protected final TestResults getTestResults() {
+ return impl.getTestResults();
+ }
}
diff --git a/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTTestCaseImpl.java b/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTTestCaseImpl.java
index d727217..164fd5f 100644
--- a/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTTestCaseImpl.java
+++ b/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/impl/GWTTestCaseImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -22,6 +22,9 @@
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.junit.client.TestResults;
+import com.google.gwt.junit.client.Trial;
import java.util.ArrayList;
import java.util.List;
@@ -173,6 +176,17 @@
private KillTimer timer;
/**
+ * The time the test began execution;
+ */
+ private long testBeginMillis;
+
+ /**
+ * Collective test results.
+ *
+ */
+ private TestResults results = new TestResults();
+
+ /**
* Constructs a new GWTTestCaseImpl that is paired one-to-one with a
* {@link GWTTestCase}.
*
@@ -288,28 +302,69 @@
* @param ex The results of this test.
*/
private void reportResultsAndRunNextMethod(Throwable ex) {
- testIsFinished = true;
-
- resetAsyncState();
+ List trials = results.getTrials();
if (serverless) {
// That's it, we're done
return;
}
+
+ // TODO(tobyr) - Consider making this logic polymorphic which will remove
+ // instanceof test
+ //
+ // If this is not a benchmark, we have to create a fake trial run
+ if ( ! (outer instanceof com.google.gwt.junit.client.Benchmark) ) {
+ Trial trial = new Trial();
+ long testDurationMillis = System.currentTimeMillis() - testBeginMillis;
+ trial.setRunTimeMillis( testDurationMillis );
- ExceptionWrapper ew = null;
- if (ex != null) {
- ew = new ExceptionWrapper(ex);
- if (checkPoints != null) {
- for (int i = 0, c = checkPoints.size(); i < c; ++i) {
- ew.message += "\n" + checkPoints.get(i);
+ if (ex != null) {
+ ExceptionWrapper ew = new ExceptionWrapper(ex);
+ if (checkPoints != null) {
+ for (int i = 0, c = checkPoints.size(); i < c; ++i) {
+ ew.message += "\n" + checkPoints.get(i);
+ }
}
+ trial.setExceptionWrapper( ew );
+ }
+
+ trials.add( trial );
+ }
+ // If this was a benchmark, we need to handle exceptions specially
+ else {
+ // If an exception occurred, it happened without the trial being recorded
+ // We, unfortunately, don't know the trial parameters at this point.
+ // We should consider putting the exception handling code directly into
+ // the generated Benchmark subclasses.
+ if (ex != null) {
+ ExceptionWrapper ew = new ExceptionWrapper(ex);
+ if (checkPoints != null) {
+ for (int i = 0, c = checkPoints.size(); i < c; ++i) {
+ ew.message += "\n" + checkPoints.get(i);
+ }
+ }
+ Trial trial = new Trial();
+ trial.setExceptionWrapper( ew );
+ trials.add( trial );
}
}
- junitHost.reportResultsAndGetNextMethod(outer.getTestName(), ew,
- junitHostListener);
+
+ results.setSourceRef( getDocumentLocation() );
+ testIsFinished = true;
+ resetAsyncState();
+ String testName = outer.getTestName();
+ junitHost.reportResultsAndGetNextMethod(testName, results, junitHostListener);
}
+ public TestResults getTestResults() {
+ return results;
+ }
+
+ private native String getDocumentLocation() /*-{
+ return $doc.location.toString();
+ }-*/;
+
+
/**
* Cleans up any asynchronous mode state.
*/
@@ -334,6 +389,11 @@
*/
private void runTest() {
Throwable caught = null;
+
+ testBeginMillis = System.currentTimeMillis();
+ results = new TestResults();
+
+
if (shouldCatchExceptions()) {
// Make sure no exceptions escape
GWT.setUncaughtExceptionHandler(this);
diff --git a/user/test/com/google/gwt/junit/client/ParallelRemoteTest.java b/user/test/com/google/gwt/junit/client/ParallelRemoteTest.java
new file mode 100644
index 0000000..05c29a1
--- /dev/null
+++ b/user/test/com/google/gwt/junit/client/ParallelRemoteTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2007 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;
+
+/**
+ * This class tests the -remoteweb parallel execution features in GWT's JUnit
+ * support. This test should not be part of the automatically run test suite,
+ * because it intentionally generates failures at different browser clients.
+ *
+ * What we're looking for in the output of this test is that the failures
+ * additionally contain the host and browser at which the test failed.
+ *
+ * To run this test correctly, you should be using the -remoteweb option
+ * with at least three different clients.
+ *
+ */
+public class ParallelRemoteTest extends GWTTestCase {
+
+ public String getModuleName() {
+ return "com.google.gwt.junit.JUnit";
+ }
+
+ public void testAssertFailsOnNotIE() {
+ String agent = getAgent().toLowerCase();
+ if ( agent.indexOf( "msie") == -1 ) {
+ fail( "Browser is not IE." );
+ }
+ }
+
+ public void testAssertFailsOnNotSafari() {
+ String agent = getAgent().toLowerCase();
+ if ( agent.indexOf( "safari") == -1 ) {
+ fail( "Browser is not Safari." );
+ }
+ }
+
+ public void testExceptionFailsOnNotIE() {
+ String agent = getAgent().toLowerCase();
+ if ( agent.indexOf( "msie") == -1 ) {
+ throw new RuntimeException( "Browser is not IE." );
+ }
+ }
+
+ public void testExceptionFailsOnNotSafari() {
+ String agent = getAgent().toLowerCase();
+ if ( agent.indexOf( "safari") == -1 ) {
+ throw new RuntimeException( "Browser is not Safari." );
+ }
+ }
+
+ private native String getAgent() /*-{
+ return navigator.userAgent.toString();
+ }-*/;
+}
diff --git a/user/test/com/google/gwt/user/client/ui/ArrayListAndVectorProfile.java b/user/test/com/google/gwt/user/client/ui/ArrayListAndVectorProfile.java
deleted file mode 100644
index c3b328b..0000000
--- a/user/test/com/google/gwt/user/client/ui/ArrayListAndVectorProfile.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2007 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.user.client.ui;
-
-import java.util.ArrayList;
-import java.util.Vector;
-
-/**
- * Profile class.
- */
-public class ArrayListAndVectorProfile extends WidgetProfile {
-
- private static final int UPPER_BOUND = 40000;
- private static final int LOWER_BOUND = 1024;
-
- public void testTiming() throws Exception {
-
- for (int i = LOWER_BOUND; i < UPPER_BOUND; i = i * 2) {
- testTiming(i);
- }
-
- throw new Exception("Finished profiling");
- }
-
- public void testTiming(int i) {
- arrayListTiming(i);
- vectorTiming(i);
- }
-
- public void vectorTiming(int num) {
- resetTimer();
- Vector v = new Vector();
- for (int i = 0; i < num; i++) {
- v.add("hello");
- }
- timing("vector | add(" + num + ")");
- resetTimer();
- for (int k = 0; k < num; k++) {
- v.get(k);
- }
-
- timing("vector | get(" + num + ")");
- }
-
- public void arrayListTiming(int num) {
- resetTimer();
- ArrayList v = new ArrayList();
- for (int i = 0; i < num; i++) {
- v.add("hello");
- }
- timing("arrayList | add(" + num + ")");
- resetTimer();
- for (int k = 0; k < num; k++) {
- v.get(k);
- }
-
- timing("arrayList | get(" + num + ")");
- }
-}