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 + ")"); - } -}