diff --git a/make/Makefile b/make/Makefile index 10cb4e8f..d8bd9bc6 100644 --- a/make/Makefile +++ b/make/Makefile @@ -34,6 +34,7 @@ all: build test #---------------------------------------------------------------------- +include jtdiff.gmk include jtreg.gmk include $(TOPDIR)/test/*.gmk include $(TOPDIR)/test/*/*.gmk diff --git a/make/jtdiff.gmk b/make/jtdiff.gmk new file mode 100644 index 00000000..30fcf930 --- /dev/null +++ b/make/jtdiff.gmk @@ -0,0 +1,78 @@ +# +# Copyright (c) 1999, 2016, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +#---------------------------------------------------------------------- +# +# compile com.sun.javatest.diff + +JAVAFILES.com.sun.javatest.diff := \ + $(shell $(FIND) $(JAVADIR)/com/sun/javatest/diff -name \*.java ) + +$(BUILDDIR)/classes.com.sun.javatest.diff.ok: \ + $(JAVAFILES.com.sun.javatest.diff) \ + $(BUILDDIR)/classes.com.sun.javatest.regtest.ok + CLASSPATH="$(CLASSDIR)$(PS)$(JAVATEST_JAR)$(PS)$(ANT_JAR)" \ + $(REGTEST_TOOL_JAVAC) $(REGTEST_TOOL_JAVAC_OPTIONS) \ + -d $(CLASSDIR) \ + -encoding ASCII \ + $(JAVAFILES.com.sun.javatest.diff) + echo "classes built at `date`" > $@ + +TARGETS.com.sun.javatest.diff += $(BUILDDIR)/classes.com.sun.javatest.diff.ok + +#---------------------------------------------------------------------- +# +# resources required for com.sun.javatest.diff + +RESOURCES.com.sun.javatest.diff = \ + $(CLASSDIR)/com/sun/javatest/diff/i18n.properties + +TARGETS.com.sun.javatest.diff += $(RESOURCES.com.sun.javatest.diff) + +#---------------------------------------------------------------------- +# +# include jtdiff in jtreg.jar + +PKGS.JAR.jtreg += com.sun.javatest.diff +TARGETS.JAR.jtreg += $(TARGETS.com.sun.javatest.diff) + +#---------------------------------------------------------------------- +# +# executable scripts + +$(JTREG_IMAGEDIR)/bin/jtdiff: $(SRCSHAREBINDIR)/jtdiff.sh + $(MKDIR) -p $(@D) + $(RM) $@ + $(CP) $< $@ + $(CHMOD) a+x,a-w $@ + +TARGETS.ZIP.jtreg += \ + $(JTREG_IMAGEDIR)/bin/jtdiff + +#---------------------------------------------------------------------- + +TESTS += $(TESTS.jtdiff) + + diff --git a/make/jtreg.gmk b/make/jtreg.gmk index 147863f9..97bc054d 100644 --- a/make/jtreg.gmk +++ b/make/jtreg.gmk @@ -51,7 +51,7 @@ $(BUILDDIR)/classes.com.sun.javatest.regtest.agent.ok: \ $(JAVAFILES.com.sun.javatest.regtest-agentvm) echo "classes built at `date`" > $@ -### The following files are for the jtreg tool +### The following files are for the jtreg and jtdiff tools JAVAFILES.com.sun.javatest.regtest-tools := \ $(shell $(FIND) $(JAVADIR)/com/sun/javatest/regtest -name agent -prune -o -name \*.java -print ) diff --git a/src/share/bin/jtdiff.sh b/src/share/bin/jtdiff.sh new file mode 100644 index 00000000..f8fb8ab8 --- /dev/null +++ b/src/share/bin/jtdiff.sh @@ -0,0 +1,123 @@ +#!/bin/sh +# +# Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +# Usage: +# jtdiff ...args.... +# +# jtdiff requires a version of Java equivalent to JDK 1.5.0 or higher. + +# $JT_HOME can be used to specify the jtdiff installation directory +# (e.g. /usr/local/java/jct-tools/3.2.2) +# +# $JT_JAVA is used to specify the version of java to use when running JavaTest +# (e.g. /usr/local/java/jdk1.5.0/solaris-sparc/bin/java) +# +# jtdiff also provides an Ant task for direct invocation from Ant. + +# Determine jtdiff/JavaTest installation directory +if [ -n "$JT_HOME" ]; then + if [ ! -r $JT_HOME/lib/jtreg.jar ];then + echo "Invalid JT_HOME=$JT_HOME. Cannot find or read $JT_HOME/lib/jtreg.jar" + exit 1; + fi +else + # Deduce where script is installed + # - should work on most derivatives of Bourne shell, like ash, bash, ksh, + # sh, zsh, etc, including on Windows, MKS (ksh) and Cygwin (ash or bash) + if type -p type 1>/dev/null 2>&1 && test -z "`type -p type`" ; then + myname=`type -p "$0"` + elif type type 1>/dev/null 2>&1 ; then + myname=`type "$0" | sed -e 's/^.* is a tracked alias for //' -e 's/^.* is //'` + elif whence whence 1>/dev/null 2>&1 ; then + myname=`whence "$0"` + fi + mydir=`dirname "$myname"` + p=`cd "$mydir" ; pwd` + while [ -n "$p" -a "$p" != "/" ]; do + if [ -r "$p"/lib/jtreg.jar ]; then JT_HOME="$p" ; break; fi + p=`dirname "$p"` + done + if [ -z "$JT_HOME" ]; then + echo "Cannot determine JT_HOME; please set it explicitly"; exit 1 + fi +fi + +# Normalize JT_HOME if using Cygwin +case "`uname -s`" in + CYGWIN* ) cygwin=1 ; JT_HOME=`cygpath -a -m "$JT_HOME"` ;; +esac + + +# Separate out -J* options for the JVM +# Unset IFS and use newline as arg separator to preserve spaces in args +DUALCASE=1 # for MKS: make case statement case-sensitive (6709498) +saveIFS="$IFS" +nl=' +' +for i in "$@" ; do + IFS= + if [ -n "$cygwin" ]; then i=`echo $i | sed -e 's|/cygdrive/\([A-Za-z]\)/|\1:/|'` ; fi + case $i in + -J* ) javaOpts=$javaOpts$nl`echo $i | sed -e 's/^-J//'` ;; + * ) jtdiffOpts=$jtdiffOpts$nl$i ;; + esac + IFS="$saveIFS" +done +unset DUALCASE + +# Determine java for jtdiff, from JT_JAVA, JAVA_HOME, java +if [ -n "$JT_JAVA" ]; then + if [ -d "$JT_JAVA" ]; then + JT_JAVA="$JT_JAVA/bin/java" + fi +elif [ -n "$JAVA_HOME" ]; then + JT_JAVA="$JAVA_HOME/bin/java" +else + JT_JAVA=java +fi + +# Verify java version (1.)5 or newer used to run jtdiff +version=`"$JT_JAVA" -classpath "${JT_HOME}/lib/jtreg.jar" com.sun.javatest.regtest.agent.GetSystemProperty java.version 2>&1 | + grep 'java.version=' | sed -e 's/^.*=//' -e 's/^1\.//' -e 's/\([1-9][0-9]*\).*/\1/'` + +if [ -z "$version" ]; then + echo "Cannot determine version of java to run jtdiff" + exit 1; +elif [ "$version" -lt 5 ]; then + echo "java version 5 or later is required to run jtdiff" + exit 1; +fi + +# And finally ... + +IFS=$nl + +"${JT_JAVA:-${JAVA_HOME:+$JAVA_HOME/bin/}java}" \ + $javaOpts \ + -Dprogram=`basename "$0"` \ + -cp "${JT_HOME}/lib/jtreg.jar" \ + com.sun.javatest.diff.Main \ + $jtdiffOpts diff --git a/src/share/classes/com/sun/javatest/diff/Diff.java b/src/share/classes/com/sun/javatest/diff/Diff.java new file mode 100644 index 00000000..1ddb8e41 --- /dev/null +++ b/src/share/classes/com/sun/javatest/diff/Diff.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.javatest.diff; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import com.sun.javatest.Status; +import com.sun.javatest.TestResult; +import com.sun.javatest.TestSuite; +import com.sun.javatest.WorkDirectory; +import com.sun.javatest.util.I18NResourceBundle; + +public abstract class Diff { + + public abstract boolean report(File outFile) throws Fault, InterruptedException; + + protected boolean diff(List files, File outFile) + throws Fault, InterruptedException { + this.outFile = outFile; + List list = new ArrayList<>(); + for (File f: files) + list.add(open(f)); + + PrintWriter prevOut = out; + if (outFile != null) { + try { + out = new PrintWriter(new BufferedWriter(new FileWriter(outFile))); // FIXME don't want to use PrintWriter + } catch (IOException e) { + throw new Fault(i18n, "diff.cantOpenFile", outFile, e); + } + } + + try { + initComparator(); + + initReporter(); + reporter.setTitle(title); + reporter.setComparator(comparator); + reporter.setReaders(list); + + List testCounts = new ArrayList<>(); + MultiMap table = new MultiMap<>(); + for (DiffReader r: list) { + int index = table.addColumn(r.getFile().getPath()); + int[] counts = new int[Status.NUM_STATES]; + for (TestResult tr: r) { + table.addRow(index, tr.getTestName(), tr); + counts[tr.getStatus().getType()]++; + } + testCounts.add(counts); + } + reporter.setTestCounts(testCounts); + + try { + reporter.write(table); + } catch (IOException e) { + throw new Fault(i18n, "diff.ioError", e); + } + + return (reporter.diffs == 0); + } finally { + if (out != prevOut) { +// try { + out.close(); +// } catch (IOException e) { +// throw new Fault(i18n, "main.ioError", e); +// } + out = prevOut; + } + } + } + + protected void initFormat() { + if (format == null && outFile != null) { + String name = outFile.getName(); + int dot = name.lastIndexOf("."); + if (dot != -1) + format = name.substring(dot + 1).toLowerCase(); + } + } + + protected void initReporter() throws Fault { + if (reporter == null) { + try { + initFormat(); + if (format != null && format.equals("html")) + reporter = new HTMLReporter(out); + else + reporter = new SimpleReporter(out); + } catch (IOException e) { + throw new Fault(i18n, "diff.cantOpenReport", e); + } + } + } + + protected void initComparator() { + if (comparator == null) + comparator = new StatusComparator(includeReason); + } + + protected DiffReader open(File f) throws Fault { + if (!f.exists()) + throw new Fault(i18n, "main.cantFindFile", f); + + try { + if (WorkDirectoryReader.accepts(f)) + return new WorkDirectoryReader(f); + + if (ReportReader.accepts(f)) + return new ReportReader(f); + + throw new Fault(i18n, "main.unrecognizedFile", f); + + } catch (TestSuite.Fault + | WorkDirectory.Fault + | IOException e) { + throw new Fault(i18n, "main.cantOpenFile", f, e); + } + + } + + protected File outFile; + protected PrintWriter out; + protected Comparator comparator; + protected Reporter reporter; + protected boolean includeReason; + protected String format; + protected String title; + private static final I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(Diff.class); +} diff --git a/src/share/classes/com/sun/javatest/diff/DiffReader.java b/src/share/classes/com/sun/javatest/diff/DiffReader.java new file mode 100644 index 00000000..642be6ce --- /dev/null +++ b/src/share/classes/com/sun/javatest/diff/DiffReader.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.javatest.diff; + +import java.io.File; + +import com.sun.javatest.TestResult; + +/** + * Interface for reading a series of test-status results. + */ +public interface DiffReader extends Iterable { + File getWorkDirectory(); + String getFileType(); + File getFile(); +} + diff --git a/src/share/classes/com/sun/javatest/diff/Fault.java b/src/share/classes/com/sun/javatest/diff/Fault.java new file mode 100644 index 00000000..5fefa0fb --- /dev/null +++ b/src/share/classes/com/sun/javatest/diff/Fault.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.javatest.diff; + +import com.sun.javatest.util.I18NResourceBundle; + +/** + * Exception to report a problem while executing in Main. + */ +public class Fault extends Exception { + + static final long serialVersionUID = 1607979458544175906L; + + Fault(I18NResourceBundle i18n, String s, Object... args) { + super(i18n.getString(s, args)); + } +} diff --git a/src/share/classes/com/sun/javatest/diff/HTMLReporter.java b/src/share/classes/com/sun/javatest/diff/HTMLReporter.java new file mode 100644 index 00000000..cdf4f9bb --- /dev/null +++ b/src/share/classes/com/sun/javatest/diff/HTMLReporter.java @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.javatest.diff; + +import java.io.File; +import java.io.IOException; +import java.io.Writer; +import java.util.Date; +import java.util.Map; + +import com.sun.javatest.Status; +import com.sun.javatest.TestResult; +import com.sun.javatest.util.I18NResourceBundle; + +import static com.sun.javatest.util.HTMLWriter.*; + +/* + * TODO: import CSS + * TODO: links to .jtr files + */ + +/** + * Report differences to an HTML file. + */ +public class HTMLReporter extends Reporter { + + /** Creates a new instance of HTMLReporter */ + public HTMLReporter(Writer out) throws IOException { + this.out = new HTMLWriter(out, DOCTYPE); + this.out.setI18NResourceBundle(i18n); + } + + public void write(MultiMap table) throws IOException { + this.table = table; + size = table.getColumns(); + + startReport(title); + + out.startTag(H1); + if (title == null) + out.writeI18N("html.head.notitle"); + else + out.writeI18N("html.head.title", title); + out.endTag(H1); + + writeIndexTable(); + writeMainTable(); + writeSummary(); + + endReport(); + } + + protected void startReport(String title) throws IOException { + out.startTag(HTML); + writeHead(title); + out.startTag(BODY); + } + + protected void endReport() throws IOException { + out.startTag(HR); + out.writeI18N("html.generatedAt", new Date()); + out.endTag(BODY); + + out.endTag(HTML); + out.flush(); + } + + protected void writeHead(String title) throws IOException { + out.startTag(HEAD); + out.startTag(TITLE); + if (title == null) + out.writeI18N("html.head.notitle"); + else + out.writeI18N("html.head.title", title); + out.endTag(TITLE); + out.startTag(STYLE); + out.writeAttr(TYPE, "text/css"); + out.write("\n"); + out.write("table { background-color:white }"); + out.write("tr.head { background-color:#dddddd }"); + out.write("tr.odd { background-color:#eeeeee }"); + out.write("tr.even { background-color:white } "); + out.write("td { padding: 0 .5em }"); + out.write("td.pass { background-color:#ddffdd } "); + out.write("td.fail { background-color:#ffdddd } "); + out.write("td.error { background-color:#ddddff } "); + out.write("td.notRun { background-color:#dddddd } "); + out.write("th { padding: 0 .5em }"); + out.write("hr { margin-top:30px; }"); + out.write("\n"); + out.endTag(STYLE); + out.endTag(HEAD); + + } + + private void writeIndexTable() throws IOException { + out.startTag(H2); + out.writeI18N("html.head.sets"); + out.endTag(H2); + + out.startTag(TABLE); + out.writeAttr(FRAME, BOX); + out.writeAttr(RULES, GROUPS); + out.startTag(THEAD); + out.startTag(TR); + out.writeAttr(CLASS, HEAD); + out.startTag(TH); + out.writeI18N("html.th.set"); + out.endTag(TH); + out.startTag(TH); + out.writeI18N("html.th.location"); + out.endTag(TH); + writeIndexTableInfoHeadings(); +// out.startTag(TH); +// out.writeI18N("html.th.type"); +// out.endTag(TH); + for (int c = 0; c < Status.NUM_STATES; c++) { + out.startTag(TH); + switch (c) { + case Status.PASSED: + out.writeI18N("html.th.pass"); + break; + case Status.FAILED: + out.writeI18N("html.th.fail"); + break; + case Status.ERROR: + out.writeI18N("html.th.error"); + break; + default: + out.writeI18N("html.th.notRun"); + break; + } + out.endTag(TH); + } + out.startTag(TH); + out.writeI18N("html.th.total"); + out.endTag(TH); + out.endTag(TR); + out.endTag(THEAD); + + out.startTag(TBODY); + for (int i = 0; i < size; i++) { + out.startTag(TR); + out.writeAttr(CLASS, (i % 2 == 0 ? EVEN : ODD)); + out.startTag(TD); + out.write(String.valueOf(i + 1)); + out.endTag(TD); + out.startTag(TD); + out.write(table.getColumnName(i)); + out.endTag(TD); + writeIndexTableInfoValues(table.getColumnName(i)); +// out.startTag(TD); +// out.write("??"); +// out.endTag(TD); + int total = 0; + int[] counts = testCounts.get(i); + for (int c = 0; c < Status.NUM_STATES; c++) { + out.startTag(TD); + if (counts[c] > 0) + out.write(String.valueOf(counts[c])); + else + out.writeEntity(" "); + total += counts[c]; + out.endTag(TD); + } + out.startTag(TD); + out.write(String.valueOf(total)); + out.endTag(TD); + out.endTag(TR); + } + out.endTag(TBODY); + out.endTag(TABLE); + } + + protected void writeIndexTableInfoHeadings() throws IOException { + } + + protected void writeIndexTableInfoValues(String name) throws IOException { + } + + private void writeMainTable() throws IOException { + diffs = 0; + for (Map.Entry> e: table.entrySet()) { + String testName = e.getKey(); + MultiMap.Entry result = e.getValue(); + if (result.allEqual(comparator)) + continue; + if (diffs == 0) { + out.startTag(H2); + out.writeI18N("html.head.differences"); + out.endTag(H2); + out.startTag(TABLE); + out.writeAttr(FRAME, BOX); + out.writeAttr(RULES, GROUPS); + out.startTag(THEAD); + out.startTag(TR); + out.writeAttr(CLASS, HEAD); + out.startTag(TH); + out.writeI18N("html.th.test"); + out.endTag(TH); + for (int i = 0; i < result.getSize(); i++) { + out.startTag(TH); + if (compact) + out.write(String.valueOf(i + 1)); + else + out.writeI18N("html.th.setN", i + 1); + out.endTag(TH); + } + out.endTag(TR); + out.endTag(THEAD); + out.startTag(TBODY); + } + out.startTag(TR); + out.writeAttr(CLASS, (diffs % 2 == 0 ? EVEN : ODD)); + out.startTag(TD); + out.write(testName); + out.endTag(TD); + for (int i = 0; i < result.getSize(); i++) { + TestResult tr = result.get(i); + File trFile = (tr == null ? null : tr.getFile()); + if (trFile == null) { + File wd = readers.get(i).getWorkDirectory(); + if (wd != null) + trFile = new File(wd, tr.getWorkRelativePath()); + } + out.startTag(TD); + Status s = (tr == null ? null : tr.getStatus()); + out.writeAttr(CLASS, getClassAttr(s)); + String text = getText(s); + if (trFile != null && trFile.exists()) { + out.startTag(A); + out.writeAttr(HREF, trFile.toURI().toString()); + if (text.startsWith("&")) + out.writeEntity(text); + else + out.write(text); + out.endTag(A); + } else { + if (text.startsWith("&")) + out.writeEntity(text); + else + out.write(text); + } + out.endTag(TD); + } + out.endTag(TR); + diffs++; + } + if (diffs > 0) { + out.endTag(TBODY); + out.endTag(TABLE); + } + } + + private void writeSummary() throws IOException { + out.startTag(P); + if (diffs == 0) + out.writeI18N("html.diffs.none"); + else + out.writeI18N("html.diffs.count", diffs); + out.endTag(P); + } + + protected String getClassAttr(Status s) { + switch (s == null ? Status.NOT_RUN : s.getType()) { + case Status.PASSED: + return PASS; + case Status.FAILED: + return FAIL; + case Status.ERROR: + return ERROR; + default: + return NOT_RUN; + } + } + + protected String getText(Status s) { + if (statusStrings == null) { + statusStrings = new String[Status.NUM_STATES]; + if (compact) { + statusStrings[Status.PASSED] = i18n.getString("html.pass.compact"); + statusStrings[Status.FAILED] = i18n.getString("html.fail.compact"); + statusStrings[Status.ERROR] = i18n.getString("html.error.compact"); + statusStrings[Status.NOT_RUN] = i18n.getString("html.notRun.compact"); + } else { + statusStrings[Status.PASSED] = i18n.getString("html.pass"); + statusStrings[Status.FAILED] = i18n.getString("html.fail"); + statusStrings[Status.ERROR] = i18n.getString("html.error"); + statusStrings[Status.NOT_RUN] = i18n.getString("html.notRun"); + } + } + return statusStrings[s == null ? Status.NOT_RUN : s.getType()]; + } + + private String[] statusStrings; + + protected final HTMLWriter out; + private MultiMap table; + private int size; + + private static final String DOCTYPE = ""; + + // HTML tags + private static final String THEAD = "thead"; + private static final String TBODY = "tbody"; + + // HTML attribute names + private static final String CLASS = "class"; + private static final String FRAME = "frame"; + private static final String RULES = "rules"; + + // HTML attribute values + private static final String BOX = "box"; + private static final String GROUPS = "groups"; + + // HTML class values + private static final String HEAD = "head"; + private static final String ODD = "odd"; + private static final String EVEN = "even"; + private static final String PASS = "pass"; + private static final String FAIL = "fail"; + private static final String ERROR = "error"; + private static final String NOT_RUN = "notRun"; + + private boolean compact = Boolean.TRUE.equals(Boolean.getBoolean("jtdiff.html.compact")); + private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(HTMLReporter.class); +} diff --git a/src/share/classes/com/sun/javatest/diff/HTMLWriter.java b/src/share/classes/com/sun/javatest/diff/HTMLWriter.java new file mode 100644 index 00000000..b44bf29f --- /dev/null +++ b/src/share/classes/com/sun/javatest/diff/HTMLWriter.java @@ -0,0 +1,559 @@ +/* + * Copyright (c) 1996, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.javatest.diff; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.Writer; +import java.net.URL; + +import com.sun.javatest.util.I18NResourceBundle; + +/** + * A class to facilitate writing HTML via a stream. + */ +public class HTMLWriter +{ + /** + * Create an HTMLWriter object, using a default doctype for HTML 3.2. + * @param out a Writer to which to write the generated HTML + * @throws IOException if there is a problem writing to the underlying stream + */ + public HTMLWriter(Writer out) throws IOException { + this(out, ""); + } + + /** + * Create an HTMLWriter object, using a specifed doctype header. + * @param out a Writer to which to write the generated HTML + * @param docType a string containing a doctype header for the HTML to be generetaed + * @throws IOException if there is a problem writing to the underlying stream + */ + public HTMLWriter(Writer out, String docType) throws IOException { + if (out instanceof BufferedWriter) + this.out = (BufferedWriter) out; + else + this.out = new BufferedWriter(out); + this.out.write(docType); + this.out.newLine(); + } + + /** + * Create an HTMLWriter object, using a specified bundle for l0calizing messages. + * @param out a Writer to which to write the generated HTML + * @param i18n a resource bundle to use to localize messages + * @throws IOException if there is a problem writing to the underlying stream + */ + public HTMLWriter(Writer out, I18NResourceBundle i18n) throws IOException { + this(out); + this.i18n = i18n; + } + + + /** + * Create an HTMLWriter object, using a specifed doctype header and + * using a specified bundle for l0calizing messages. + * @param out a Writer to which to write the generated HTML + * @param docType a string containing a doctype header for the HTML to be generetaed + * @param i18n a resource bundle to use to localize messages + * @throws IOException if there is a problem writing to the underlying stream + */ + public HTMLWriter(Writer out, String docType, I18NResourceBundle i18n) throws IOException { + this(out, docType); + this.i18n = i18n; + } + + /** + * Set the reource bundle to be used for localizing messages. + * @param i18n the resource bundle to be used for localizing messages + */ + public void setI18NResourceBundle(I18NResourceBundle i18n) { + this.i18n = i18n; + } + + /** + * Flush the stream, and the underlying output stream. + * @throws IOException if there is a problem writing to the underlying stream + */ + public void flush() throws IOException { + out.flush(); + } + + /** + * Close the stream, and the underlying output stream. + * @throws IOException if there is a problem closing the underlying stream + */ + public void close() throws IOException { + out.close(); + } + + /** + * Write a newline to the underlying output stream. + * @throws IOException if there is a problem writing to the underlying stream + */ + public void newLine() throws IOException { + out.newLine(); + } + + /** + * Start an HTML tag. If a prior tag has been started, it will + * be closed first. Once a tag has been opened, attributes for the + * tag may be written out, followed by body content before finally + * ending the tag. + * @param tag the tag to be started + * @throws IOException if there is a problem writing to the underlying stream + * @see #writeAttr + * @see #write + * @see #endTag + */ + public void startTag(String tag) throws IOException { + if (state == IN_TAG) { + out.write(">"); + state = IN_BODY; + } + newLine(); + out.write("<"); + out.write(tag); + state = IN_TAG; + } + + /** + * Finish an HTML tag. It is expected that a call to endTag will match + * a corresponding earlier call to startTag, but there is no formal check + * for this. + * @param tag the tag to be closed. + * @throws IOException if there is a problem writing to the underlying stream + */ + public void endTag(String tag) throws IOException { + if (state == IN_TAG) { + out.write(">"); + state = IN_BODY; + out.newLine(); + } + out.write(""); + //out.newLine(); // PATCHED, jjg + state = IN_BODY; + } + + /** + * Finish an empty element tag, such as a META, BASE or LINK tag. + * This is expected to correspond with a startTag. + * @param tag the tag which is being closed. this is only useful for + * validation, it is not written out + * @throws IllegalStateException if this call does not follow startTag + * (stream is not currently inside a tag) + * @throws IOException if there is a problem writing to the underlying stream + */ + public void endEmptyTag(String tag) throws IOException { + if (state != IN_TAG) + throw new IllegalStateException(); + + out.write(">"); + state = IN_BODY; + out.newLine(); + } + + /** + * Write an attribute for a tag. A tag must previously have been started. + * All tag attributes must be written before any body text is written. + * The value will be quoted if necessary when writing it to the underlying + * stream. No check is made that the attribute is valid for the current tag. + * @param name the name of the attribute to be written + * @param value the value of the attribute to be written + * @throws IllegalStateException if the stream is not in a state to + * write attributes -- e.g. if this call does not follow startTag or other + * calls of writteAttr + * @throws IOException if there is a problem writing to the underlying stream + */ + public void writeAttr(String name, String value) throws IOException { + if (state != IN_TAG) + throw new IllegalStateException(); + + out.write(" "); + out.write(name); + out.write("="); + boolean alpha = true; + for (int i = 0; i < value.length() && alpha; i++) + alpha = Character.isLetter(value.charAt(i)); + if (!alpha) + out.write("\""); + out.write(value); + if (!alpha) + out.write("\""); + } + + /** + * Write an attribute for a tag. A tag must previously have been started. + * All tag attributes must be written before any body text is written. + * The value will be quoted if necessary when writing it to the underlying + * stream. No check is made that the attribute is valid for the current tag. + * @param name the name of the attribute to be written + * @param value the value of the attribute to be written + * @throws IllegalStateException if the stream is not in a state to + * write attributes -- e.g. if this call does not follow startTag or other + * calls of writteAttr + * @throws IOException if there is a problem writing to the underlying stream + */ + public void writeAttr(String name, int value) throws IOException { + writeAttr(name, Integer.toString(value)); + } + + /** + * Write a line of text, followed by a newline. + * The text will be escaped as necessary. + * @param text the text to be written. + * @throws IOException if there is a problem closing the underlying stream + */ + public void writeLine(String text) throws IOException { + write(text); + out.newLine(); + } + + /** + * Write body text, escaping it as necessary. + * If this call follows a call of startTag, the open tag will be + * closed -- meaning that no more attributes can be written until another + * tag is started. If the text value is null, the current tag will still + * be closed, but no other text will be written. + * @param text the text to be written, may be null or zero length. + * @throws IOException if there is a problem writing to the underlying stream + */ + public void write(String text) throws IOException { + if (state == IN_TAG) { + out.write(">"); + state = IN_BODY; + } + + if (text == null) + return; + + // check to see if there are any special characters + boolean specialChars = false; + for (int i = 0; i < text.length() && !specialChars; i++) { + switch (text.charAt(i)) { + case '<': case '>': case '&': + specialChars = true; + } + } + + // if there are special characters write the string character at a time; + // otherwise, write it out as is + if (specialChars) { + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + switch (c) { + case '<': out.write("<"); break; + case '>': out.write(">"); break; + case '&': out.write("&"); break; + default: out.write(c); + } + } + } + else + out.write(text); + } + + /** + * Write a basic HTML entity, such as   or { . + * @param entity the entity to write + * @throws IOException if there is a problem writing to the underlying stream + */ + public void writeEntity(String entity) throws IOException { + if (state == IN_TAG) { + out.write(">"); + state = IN_BODY; + } + out.write(entity); + } + + /** + * Write an image tag, using a specified path for the image source attribute. + * @param imagePath the path for the image source + * @throws IOException if there is a problem closing the underlying stream + */ + public void writeImage(String imagePath) throws IOException { + startTag(IMAGE); + writeAttr(SRC, imagePath); + } + + /** + * Write an image tag, using a specified path for the image source attribute. + * @param imageURL the url for the image source + * @throws IOException if there is a problem closing the underlying stream + */ + public void writeImage(URL imageURL) throws IOException { + writeImage(imageURL.toString()); + } + + /** + * Write a hypertext link. + * @param anchor the target for the link + * @param body the body text for the link + * @throws IOException if there is a problem closing the underlying stream + */ + public void writeLink(String anchor, String body) throws IOException { + startTag(A); + writeAttr(HREF, anchor); + write(body); + endTag(A); + } + + /** + * Write a hypertext link. + * @param file the target for the link + * @param body the body text for the link + * @throws IOException if there is a problem closing the underlying stream + */ + public void writeLink(File file, String body) throws IOException { + startTag(A); + StringBuilder sb = new StringBuilder(); + String path = file.getPath().replace(File.separatorChar, '/'); + if (file.isAbsolute() && !path.startsWith("/")) + sb.append('/'); + sb.append(path); + writeAttr(HREF, sb.toString()); + write(body); + endTag(A); + } + + /** + * Write a hypertext link. + * @param file the target and body for the link + * @throws IOException if there is a problem closing the underlying stream + */ + public void writeLink(File file) throws IOException { + writeLink(file, file.getPath()); + } + + /** + * Write a hypertext link. + * @param url the target for the link + * @param body the body text for the link + * @throws IOException if there is a problem closing the underlying stream + */ + public void writeLink(URL url, String body) throws IOException { + startTag(A); + writeAttr(HREF, url.toString()); + write(body); + endTag(A); + } + + /** + * Write the destination marker for a hypertext link. + * @param anchor the destination marker for hypertext links + * @param body the body text for the marker + * @throws IOException if there is a problem closing the underlying stream + */ + public void writeLinkDestination(String anchor, String body) throws IOException { + startTag(A); + writeAttr(NAME, anchor); + write(body); + endTag(A); + } + + /** + * Write a parameter tag. + * @param name the name of the parameter + * @param value the value of the parameter + * @throws IOException if there is a problem closing the underlying stream + */ + public void writeParam(String name, String value) throws IOException { + startTag(PARAM); + writeAttr(NAME, name); + writeAttr(VALUE, value); + } + + /** + * Write a style attribute. + * @param value the value for the style atrtribute + * @throws IOException if there is a problem closing the underlying stream + */ + public void writeStyleAttr(String value) throws IOException { + writeAttr(STYLE, value); + } + + /** + * Write a localized message, using a specified resource bundle. + * @param i18n the resource bundle used to localize the message + * @param key the key for the message to be localized + * @throws IOException if there is a problem closing the underlying stream + */ + public void write(I18NResourceBundle i18n, String key) throws IOException { + write(i18n.getString(key)); + } + + /** + * Write a localized message, using a specified resource bundle. + * @param i18n the resource bundle used to localize the message + * @param key the key for the message to be localized + * @param arg an argument to be formatted into the localized message + * @throws IOException if there is a problem closing the underlying stream + */ + public void write(I18NResourceBundle i18n, String key, Object arg) throws IOException { + write(i18n.getString(key, arg)); + } + + /** + * Write a localized message, using a specified resource bundle. + * @param i18n the resource bundle used to localize the message + * @param key the key for the message to be localized + * @param args arguments to be formatted into the localized message + * @throws IOException if there is a problem closing the underlying stream + */ + public void write(I18NResourceBundle i18n, String key, Object[] args) throws IOException { + write(i18n.getString(key, args)); + } + + /** + * Write a localized message, using the default resource bundle. + * @param key the key for the message to be localized + * @throws IOException if there is a problem closing the underlying stream + */ + public void writeI18N(String key) throws IOException { + write(i18n.getString(key)); + } + + /** + * Write a localized message, using the default resource bundle. + * @param key the key for the message to be localized + * @param arg an argument to be formatted into the localized message + * @throws IOException if there is a problem closing the underlying stream + */ + public void writeI18N(String key, Object arg) throws IOException { + write(i18n.getString(key, arg)); + } + + /** + * Write a localized message, using the default resource bundle. + * @param key the key for the message to be localized + * @param args arguments to be formatted into the localized message + * @throws IOException if there is a problem closing the underlying stream + */ + public void writeI18N(String key, Object[] args) throws IOException { + write(i18n.getString(key, args)); + } + + /** The HTML "a" tag. */ + public static final String A = "a"; + /** The HTML "align" attribute. */ + public static final String ALIGN = "align"; + /** The HTML "b" tag. */ + public static final String B = "b"; + /** The HTML "body" tag. */ + public static final String BODY = "body"; + /** The HTML "border" attribute. */ + public static final String BORDER = "border"; + /** The HTML "br" tag. */ + public static final String BR = "br"; + /** The HTML "classid" attribute. */ + public static final String CLASSID = "classid"; + /** The HTML "code" tag. */ + public static final String CODE = "code"; + /** The HTML "color" attribte. */ + public static final String COLOR = "color"; + /** The HTML "col" attribute value. */ + public static final String COL = "col"; + /** The HTML "font" tag. */ + public static final String FONT = "font"; + /** The HTML "h1" tag. */ + public static final String H1 = "h1"; + /** The HTML "h2" tag. */ + public static final String H2 = "h2"; + /** The HTML "h3" tag. */ + public static final String H3 = "h3"; + /** The HTML "h4" tag. */ + public static final String H4 = "h4"; + /** The HTML "head" tag. */ + public static final String HEAD = "head"; + /** The HTML "href" attribute. */ + public static final String HREF = "href"; + /** The HTML "html" tag. */ + public static final String HTML = "html"; + /** The HTML "hr" tag. */ + public static final String HR = "hr"; + /** The HTML "i" tag. */ + public static final String I = "i"; + /** The HTML "image" tag. */ + public static final String IMAGE = "image"; + /** The HTML "left" attribute value. */ + public static final String LEFT = "left"; + /** The HTML "li" tag. */ + public static final String LI = "li"; + /** The HTML "link" tag. */ + public static final String LINK = "link"; + /** The HTML "name" attribute. */ + public static final String NAME = "name"; + /** The HTML "object" tag. */ + public static final String OBJECT = "object"; + /** The HTML "p" tag. */ + public static final String PARAM = "param"; + /** The HTML "param" tag. */ + public static final String P = "p"; + /** The HTML "rel" attribute value. */ + public static final String REL = "rel"; + /** The HTML "right" attribute value. */ + public static final String RIGHT = "right"; + /** The HTML "row" attribute value. */ + public static final String ROW = "row"; + /** The HTML "small" tag. */ + public static final String SMALL = "small"; + /** The HTML "src" attribute. */ + public static final String SRC = "src"; + /** The HTML "scope" attribute. */ + public static final String SCOPE = "scope"; + /** The HTML "style" attribute. */ + public static final String STYLE = "style"; + /** The HTML "table" tag. */ + public static final String TABLE = "table"; + /** The HTML "td" tag. */ + public static final String TD = "td"; + /** The HTML "title"attribute. */ + public static final String TITLE = "title"; + /** The HTML "th" tag. */ + public static final String TH = "th"; + /** The HTML "top" attribute value. */ + public static final String TOP = "top"; + /** The HTML "tr" tag. */ + public static final String TR = "tr"; + /** The HTML "type" attribute. */ + public static final String TYPE = "type"; + /** The HTML "ul" tag. */ + public static final String UL = "ul"; + /** The HTML "valign" attribute. */ + public static final String VALIGN = "valign"; + /** The HTML "value" attribute. */ + public static final String VALUE = "value"; + + + private BufferedWriter out; + private int state; + private I18NResourceBundle i18n; + private static final int IN_TAG = 1; + private static final int IN_BODY = 2; +} diff --git a/src/share/classes/com/sun/javatest/diff/Help.java b/src/share/classes/com/sun/javatest/diff/Help.java new file mode 100644 index 00000000..6b07af95 --- /dev/null +++ b/src/share/classes/com/sun/javatest/diff/Help.java @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2007, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.javatest.diff; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +import com.sun.javatest.regtest.tool.Option; +import com.sun.javatest.util.HelpTree; +import com.sun.javatest.util.I18NResourceBundle; +import com.sun.javatest.util.WrapWriter; + +/** + * Handles help options for main program + */ +public class Help { + + /** Creates a new instance of Help */ + public Help(List