8154482: javadoc tool must support legacy doclet and taglet

Reviewed-by: jjg
This commit is contained in:
Kumar Srinivasan 2016-04-29 15:35:51 -07:00
parent 9290ce0c7b
commit 4be9fb29fa
9 changed files with 564 additions and 131 deletions

View file

@ -1,4 +1,4 @@
doclet.build_version=Standard Doclet version {0} doclet.build_version=Standard Doclet (Old) version {0}
doclet.Contents=Contents doclet.Contents=Contents
doclet.Overview=Overview doclet.Overview=Overview
doclet.Window_Overview=Overview List doclet.Window_Overview=Overview List

View file

@ -1,4 +1,4 @@
doclet.build_version=Standard Doclet (Next) version {0} doclet.build_version=Standard Doclet version {0}
doclet.Contents=Contents doclet.Contents=Contents
doclet.Overview=Overview doclet.Overview=Overview
doclet.Window_Overview=Overview List doclet.Window_Overview=Overview List

View file

@ -550,7 +550,7 @@ public class DocEnv {
// Messager should be replaced by a more general // Messager should be replaced by a more general
// compilation environment. This can probably // compilation environment. This can probably
// subsume DocEnv as well. // subsume DocEnv as well.
messager.exit(); throw new Messager.ExitJavadoc();
} }
/** /**

View file

@ -59,13 +59,6 @@ public class Main {
* @return The return code. * @return The return code.
*/ */
public static int execute(String... args) { public static int execute(String... args) {
// NOTE: the following should be removed when the old doclet
// is removed.
if (args != null && args.length > 0 && "-Xold".equals(args[0])) {
String[] nargs = new String[args.length - 1];
System.arraycopy(args, 1, nargs, 0, nargs.length);
return com.sun.tools.javadoc.Main.execute(nargs);
}
Start jdoc = new Start(); Start jdoc = new Start();
return jdoc.begin(args); return jdoc.begin(args);
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -139,7 +139,7 @@ public class Messager extends Log implements Reporter {
} }
} }
public class ExitJavadoc extends Error { public static class ExitJavadoc extends Error {
private static final long serialVersionUID = 0; private static final long serialVersionUID = 0;
} }
@ -416,15 +416,6 @@ public class Messager extends Log implements Reporter {
} }
} }
/**
* Force program exit, e.g., from a fatal error.
* <p>
* TODO: This method does not really belong here.
*/
public void exit() {
throw new ExitJavadoc();
}
private void report(DiagnosticType type, String pos, String msg) { private void report(DiagnosticType type, String pos, String msg) {
switch (type) { switch (type) {
case ERROR: case ERROR:

View file

@ -66,6 +66,7 @@ import jdk.javadoc.doclet.Doclet.Option;
import jdk.javadoc.doclet.DocletEnvironment; import jdk.javadoc.doclet.DocletEnvironment;
import static com.sun.tools.javac.main.Option.*; import static com.sun.tools.javac.main.Option.*;
/** /**
* Main program of Javadoc. * Main program of Javadoc.
* Previously named "Main". * Previously named "Main".
@ -79,6 +80,12 @@ import static com.sun.tools.javac.main.Option.*;
* @author Neal Gafter (rewrite) * @author Neal Gafter (rewrite)
*/ */
public class Start extends ToolOption.Helper { public class Start extends ToolOption.Helper {
private static final Class<?> OldStdDoclet =
com.sun.tools.doclets.standard.Standard.class;
private static final Class<?> StdDoclet =
jdk.javadoc.internal.doclets.standard.Standard.class;
/** Context for this invocation. */ /** Context for this invocation. */
private final Context context; private final Context context;
@ -193,18 +200,26 @@ public class Start extends ToolOption.Helper {
if (foot != null) if (foot != null)
messager.notice(foot); messager.notice(foot);
if (exit) exit(); if (exit)
throw new Messager.ExitJavadoc();
} }
/**
* Exit
*/
private void exit() {
messager.exit();
}
/** /**
* Main program - external wrapper * Main program - external wrapper. In order to maintain backward
* CLI compatibility, we dispatch to the old tool or the old doclet's
* Start mechanism, based on the options present on the command line
* with the following precedence:
* 1. presence of -Xold, dispatch to old tool
* 2. doclet variant, if old, dispatch to old Start
* 3. taglet variant, if old, dispatch to old Start
*
* Thus the presence of -Xold switches the tool, soon after command files
* if any, are expanded, this is performed here, noting that the messager
* is available at this point in time.
* The doclet/taglet tests are performed in the begin method, further on,
* this is to minimize argument processing and most importantly the impact
* of class loader creation, needed to detect the doclet/taglet class variants.
*/ */
int begin(String... argv) { int begin(String... argv) {
// Preprocess @file arguments // Preprocess @file arguments
@ -212,14 +227,18 @@ public class Start extends ToolOption.Helper {
argv = CommandLine.parse(argv); argv = CommandLine.parse(argv);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
messager.error("main.cant.read", e.getMessage()); messager.error("main.cant.read", e.getMessage());
exit(); throw new Messager.ExitJavadoc();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(System.err); e.printStackTrace(System.err);
exit(); throw new Messager.ExitJavadoc();
} }
List<String> argList = Arrays.asList(argv); if (argv.length > 0 && "-Xold".equals(argv[0])) {
boolean ok = begin(argList, Collections.<JavaFileObject> emptySet()); messager.warning("main.legacy_api");
String[] nargv = Arrays.copyOfRange(argv, 1, argv.length);
return com.sun.tools.javadoc.Main.execute(nargv);
}
boolean ok = begin(Arrays.asList(argv), Collections.<JavaFileObject> emptySet());
return ok ? 0 : 1; return ok ? 0 : 1;
} }
@ -231,11 +250,11 @@ public class Start extends ToolOption.Helper {
List<String> opts = new ArrayList<>(); List<String> opts = new ArrayList<>();
for (String opt: options) for (String opt: options)
opts.add(opt); opts.add(opt);
return begin(opts, fileObjects); return begin(opts, fileObjects);
} }
private boolean begin(List<String> options, Iterable<? extends JavaFileObject> fileObjects) { private boolean begin(List<String> options, Iterable<? extends JavaFileObject> fileObjects) {
fileManager = context.get(JavaFileManager.class); fileManager = context.get(JavaFileManager.class);
if (fileManager == null) { if (fileManager == null) {
JavacFileManager.preRegister(context); JavacFileManager.preRegister(context);
@ -244,9 +263,8 @@ public class Start extends ToolOption.Helper {
((BaseFileManager) fileManager).autoClose = true; ((BaseFileManager) fileManager).autoClose = true;
} }
} }
// locale and doclet needs to be determined first // locale, doclet and maybe taglet, needs to be determined first
docletClass = preProcess(fileManager, options); docletClass = preProcess(fileManager, options);
if (jdk.javadoc.doclet.Doclet.class.isAssignableFrom(docletClass)) { if (jdk.javadoc.doclet.Doclet.class.isAssignableFrom(docletClass)) {
// no need to dispatch to old, safe to init now // no need to dispatch to old, safe to init now
initMessager(); initMessager();
@ -257,7 +275,7 @@ public class Start extends ToolOption.Helper {
exc.printStackTrace(); exc.printStackTrace();
if (!apiMode) { if (!apiMode) {
error("main.could_not_instantiate_class", docletClass); error("main.could_not_instantiate_class", docletClass);
messager.exit(); throw new Messager.ExitJavadoc();
} }
throw new ClientCodeException(exc); throw new ClientCodeException(exc);
} }
@ -267,6 +285,7 @@ public class Start extends ToolOption.Helper {
= new com.sun.tools.javadoc.Start(context); = new com.sun.tools.javadoc.Start(context);
return ostart.begin(docletClass, options, fileObjects); return ostart.begin(docletClass, options, fileObjects);
} }
warn("main.legacy_api");
String[] array = options.toArray(new String[options.size()]); String[] array = options.toArray(new String[options.size()]);
return com.sun.tools.javadoc.Main.execute(array) == 0; return com.sun.tools.javadoc.Main.execute(array) == 0;
} }
@ -459,6 +478,11 @@ public class Start extends ToolOption.Helper {
String userDocletPath = null; String userDocletPath = null;
String userDocletName = null; String userDocletName = null;
// taglet specifying arguments, since tagletpath is a doclet
// functionality, assume they are repeated and inspect all.
List<File> userTagletPath = new ArrayList<>();
List<String> userTagletNames = new ArrayList<>();
// Step 1: loop through the args, set locale early on, if found. // Step 1: loop through the args, set locale early on, if found.
for (int i = 0 ; i < argv.size() ; i++) { for (int i = 0 ; i < argv.size() ; i++) {
String arg = argv.get(i); String arg = argv.get(i);
@ -484,13 +508,20 @@ public class Start extends ToolOption.Helper {
} else { } else {
userDocletPath += File.pathSeparator + argv.get(i); userDocletPath += File.pathSeparator + argv.get(i);
} }
} else if ("-taglet".equals(arg)) {
userTagletNames.add(argv.get(i + 1));
} else if ("-tagletpath".equals(arg)) {
for (String pathname : argv.get(i + 1).split(File.pathSeparator)) {
userTagletPath.add(new File(pathname));
} }
} }
// Step 2: a doclet has already been provided, }
// nothing more to do.
// Step 2: a doclet is provided, nothing more to do.
if (docletClass != null) { if (docletClass != null) {
return docletClass; return docletClass;
} }
// Step 3: doclet name specified ? if so find a ClassLoader, // Step 3: doclet name specified ? if so find a ClassLoader,
// and load it. // and load it.
if (userDocletName != null) { if (userDocletName != null) {
@ -506,38 +537,80 @@ public class Start extends ToolOption.Helper {
try { try {
((StandardJavaFileManager)fileManager).setLocation(DOCLET_PATH, paths); ((StandardJavaFileManager)fileManager).setLocation(DOCLET_PATH, paths);
} catch (IOException ioe) { } catch (IOException ioe) {
panic("main.doclet_no_classloader_found", ioe); error("main.doclet_could_not_set_location", paths);
return null; // keep compiler happy throw new Messager.ExitJavadoc();
} }
} }
cl = fileManager.getClassLoader(DOCLET_PATH); cl = fileManager.getClassLoader(DOCLET_PATH);
if (cl == null) { if (cl == null) {
// despite doclet specified on cmdline no classloader found! // despite doclet specified on cmdline no classloader found!
panic("main.doclet_no_classloader_found", userDocletName); error("main.doclet_no_classloader_found", userDocletName);
return null; // keep compiler happy throw new Messager.ExitJavadoc();
}
} }
try { try {
Class<?> klass = cl.loadClass(userDocletName); Class<?> klass = cl.loadClass(userDocletName);
ensureReadable(klass); ensureReadable(klass);
return klass; return klass;
} catch (ClassNotFoundException cnfe) { } catch (ClassNotFoundException cnfe) {
panic("main.doclet_class_not_found", userDocletName); error("main.doclet_class_not_found", userDocletName);
return null; // keep compiler happy throw new Messager.ExitJavadoc();
} }
} }
}
// Step 4: we have a doclet, try loading it, otherwise // Step 4: we have a doclet, try loading it
// return back the standard doclet
if (docletName != null) { if (docletName != null) {
try { try {
return Class.forName(docletName, true, getClass().getClassLoader()); return Class.forName(docletName, true, getClass().getClassLoader());
} catch (ClassNotFoundException cnfe) { } catch (ClassNotFoundException cnfe) {
panic("main.doclet_class_not_found", userDocletName); error("main.doclet_class_not_found", userDocletName);
return null; // happy compiler, should not happen throw new Messager.ExitJavadoc();
} }
} else {
return jdk.javadoc.internal.doclets.standard.Standard.class;
} }
// Step 5: we don't have a doclet specified, do we have taglets ?
if (!userTagletNames.isEmpty() && hasOldTaglet(userTagletNames, userTagletPath)) {
// found a bogey, return the old doclet
return OldStdDoclet;
}
// finally
return StdDoclet;
}
/*
* This method returns true iff it finds a legacy taglet, but for
* all other conditions including errors it returns false, allowing
* nature to take its own course.
*/
private boolean hasOldTaglet(List<String> tagletNames, List<File> tagletPaths) {
if (!fileManager.hasLocation(TAGLET_PATH)) {
try {
((StandardJavaFileManager) fileManager).setLocation(TAGLET_PATH, tagletPaths);
} catch (IOException ioe) {
error("main.doclet_could_not_set_location", tagletPaths);
throw new Messager.ExitJavadoc();
}
}
ClassLoader cl = fileManager.getClassLoader(TAGLET_PATH);
if (cl == null) {
// no classloader found!
error("main.doclet_no_classloader_found", tagletNames.get(0));
throw new Messager.ExitJavadoc();
}
for (String tagletName : tagletNames) {
try {
Class<?> klass = cl.loadClass(tagletName);
ensureReadable(klass);
if (com.sun.tools.doclets.Taglet.class.isAssignableFrom(klass)) {
return true;
}
} catch (ClassNotFoundException cnfe) {
error("main.doclet_class_not_found", tagletName);
throw new Messager.ExitJavadoc();
}
}
return false;
} }
private void parseArgs(List<String> args, List<String> javaNames) { private void parseArgs(List<String> args, List<String> javaNames) {
@ -595,16 +668,14 @@ public class Start extends ToolOption.Helper {
usage(true); usage(true);
} }
// a terminal call, will not return
void panic(String key, Object... args) {
error(key, args);
messager.exit();
}
void error(String key, Object... args) { void error(String key, Object... args) {
messager.error(key, args); messager.error(key, args);
} }
void warn(String key, Object... args) {
messager.warning(key, args);
}
/** /**
* indicate an option with no arguments was given. * indicate an option with no arguments was given.
*/ */

View file

@ -73,7 +73,8 @@ main.Xusage=\
\ given module. <other-module> may be ALL-UNNAMED to require\n\ \ given module. <other-module> may be ALL-UNNAMED to require\n\
\ the unnamed module.\n\ \ the unnamed module.\n\
\ -Xmodule:<module-name> Specify a module to which the classes being compiled belong.\n\ \ -Xmodule:<module-name> Specify a module to which the classes being compiled belong.\n\
\ -Xpatch:<path> Specify location of module class files to patch\n \ -Xpatch:<path> Specify location of module class files to patch\n\
\ -Xold Invoke the legacy javadoc tool\n
main.Xusage.foot=\ main.Xusage.foot=\
These options are non-standard and subject to change without notice. These options are non-standard and subject to change without notice.
@ -96,6 +97,7 @@ For example, on the JDK Classic or HotSpot VMs, add the option -J-Xmx\n\
such as -J-Xmx32m. such as -J-Xmx32m.
main.done_in=[done in {0} ms] main.done_in=[done in {0} ms]
main.more_than_one_doclet_specified_0_and_1=More than one doclet specified ({0} and {1}). main.more_than_one_doclet_specified_0_and_1=More than one doclet specified ({0} and {1}).
main.doclet_could_not_set_location=Could not set location for {0}
main.doclet_no_classloader_found=Could not obtain classloader to load {0} main.doclet_no_classloader_found=Could not obtain classloader to load {0}
main.could_not_instantiate_class=Could not instantiate class {0} main.could_not_instantiate_class=Could not instantiate class {0}
main.doclet_class_not_found=Cannot find doclet class {0} main.doclet_class_not_found=Cannot find doclet class {0}
@ -109,10 +111,15 @@ main.release.bootclasspath.conflict=option {0} cannot be used together with -rel
main.unsupported.release.version=release version {0} not supported main.unsupported.release.version=release version {0} not supported
main.release.not.standard.file.manager=-release option specified, but the provided JavaFileManager is not a StandardJavaFileManager. main.release.not.standard.file.manager=-release option specified, but the provided JavaFileManager is not a StandardJavaFileManager.
main.unknown.error=an unknown error has occurred main.unknown.error=an unknown error has occurred
main.legacy_api=The old Doclet and Taglet APIs in the packages\n\
com.sun.javadoc, com.sun.tools.doclets and their implementations\n\
are planned to be removed in a future JDK release. These\n\
components have been superseded by the new APIs in jdk.javadoc.doclet.\n\
Users are strongly recommended to migrate to the new APIs.\n
javadoc.class_not_found=Class {0} not found. javadoc.class_not_found=Class {0} not found.
javadoc.error=error javadoc.error=error
javadoc.warning=warning javadoc.warning=warning
javadoc.error.msg={0}: error - {1} javadoc.error.msg={0}: error - {1}
javadoc.warning.msg={0}: warning - {1} javadoc.warning.msg={0}: warning - {1}
javadoc.note.msg = {1} javadoc.note.msg = {1}

View file

@ -23,96 +23,349 @@
/* /*
* @test * @test
* @bug 8035473 * @bug 8035473 8154482
* @summary make sure the new doclet is invoked by default, and -Xold * @summary make sure the javadoc tool responds correctly to Xold,
* old doclets and taglets.
* @library /tools/lib
* @build toolbox.ToolBox toolbox.TestRunner
* @run main EnsureNewOldDoclet
*/ */
import java.io.*; import java.io.*;
import java.util.ArrayList; import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.sun.javadoc.Tag;
import com.sun.source.doctree.DocTree;
import toolbox.*;
/** /**
* Dummy javadoc comment. * This test ensures the doclet responds correctly when given
* various conditions that force a fall back to the old javadoc
* tool. The following condition in the order described will
* force a dispatch to the old tool, -Xold, old doclet and old taglet.
*
*/ */
public class EnsureNewOldDoclet { public class EnsureNewOldDoclet extends TestRunner {
final File javadoc; final ToolBox tb;
final File testSrc; final File testSrc;
final String thisClassName; final Path javadocPath;
final ExecTask task;
final String testClasses;
final PrintStream ostream;
final static Pattern Expected1 = Pattern.compile("^Standard Doclet \\(Next\\) version.*"); final static String CLASS_NAME = "EnsureNewOldDoclet";
final static Pattern Expected2 = Pattern.compile("^Standard Doclet version.*"); final static String OLD_DOCLET_CLASS_NAME = CLASS_NAME + "$OldDoclet";
final static String NEW_DOCLET_CLASS_NAME = CLASS_NAME + "$NewDoclet"; //unused
final static String OLD_TAGLET_CLASS_NAME = CLASS_NAME + "$OldTaglet";
final static String NEW_TAGLET_CLASS_NAME = CLASS_NAME + "$NewTaglet";
public EnsureNewOldDoclet() { final static Pattern OLD_HEADER = Pattern.compile("^Standard Doclet \\(Old\\) version.*");
File javaHome = new File(System.getProperty("java.home")); final static Pattern NEW_HEADER = Pattern.compile("^Standard Doclet version.*");
if (javaHome.getName().endsWith("jre"))
javaHome = javaHome.getParentFile();
javadoc = new File(new File(javaHome, "bin"), "javadoc"); final static String OLD_DOCLET_MARKER = "OLD_DOCLET_MARKER";
testSrc = new File(System.getProperty("test.src")); final static String OLD_TAGLET_MARKER = "Registered: OldTaglet";
thisClassName = EnsureNewOldDoclet.class.getName();
final static String NEW_DOCLET_MARKER = "NEW_DOCLET_MARKER";
final static String NEW_TAGLET_MARKER = "Registered Taglet " + CLASS_NAME + "\\$NewTaglet";
final static Pattern WARN_TEXT = Pattern.compile("Users are strongly recommended to migrate" +
" to the new APIs.");
final static String OLD_DOCLET_ERROR = "java.lang.NoSuchMethodException: " +
CLASS_NAME +"\\$NewTaglet";
final static Pattern NEW_DOCLET_ERROR = Pattern.compile(".*java.lang.ClassCastException.*Taglet " +
CLASS_NAME + "\\$OldTaglet.*");
final static String OLD_STDDOCLET = "com.sun.tools.doclets.standard.Standard";
final static String NEW_STDDOCLET = "jdk.javadoc.internal.doclets.standard.Standard";
public EnsureNewOldDoclet() throws Exception {
super(System.err);
ostream = System.err;
testClasses = System.getProperty("test.classes");
tb = new ToolBox();
javadocPath = tb.getJDKTool("javadoc");
task = new ExecTask(tb, javadocPath);
testSrc = new File("Foo.java");
generateSample(testSrc);
}
void generateSample(File testSrc) throws Exception {
String nl = System.getProperty("line.separator");
String src = Arrays.asList(
"/**",
" * A test class to test javadoc. Nothing more nothing less.",
" */",
" public class Foo{}").stream().collect(Collectors.joining(nl));
tb.writeFile(testSrc.getPath(), src);
} }
public static void main(String... args) throws Exception { public static void main(String... args) throws Exception {
EnsureNewOldDoclet test = new EnsureNewOldDoclet(); new EnsureNewOldDoclet().runTests();
test.run1();
test.run2();
} }
// make sure new doclet is invoked by default // input: nothing, default mode
void run1() throws Exception { // outcome: new tool and new doclet
List<String> output = doTest(javadoc.getPath(), @Test
"-classpath", ".", // insulates us from ambient classpath public void testDefault() throws Exception {
"-Xdoclint:none", setArgs("-classpath", ".", // insulates us from ambient classpath
"-package", testSrc.toString());
new File(testSrc, thisClassName + ".java").getPath()); Task.Result tr = task.run(Task.Expect.SUCCESS);
System.out.println(output); List<String> out = tr.getOutputLines(Task.OutputKind.STDOUT);
for (String x : output) { checkOutput(testName, out, NEW_HEADER);
if (Expected1.matcher(x).matches()) { }
// input: -Xold
// outcome: old tool
@Test
public void testXold() throws Exception {
setArgs("-Xold",
"-classpath", ".", // ambient classpath insulation
testSrc.toString());
Task.Result tr = task.run(Task.Expect.SUCCESS);
List<String> out = tr.getOutputLines(Task.OutputKind.STDOUT);
List<String> err = tr.getOutputLines(Task.OutputKind.STDERR);
checkOutput(testName, out, OLD_HEADER);
checkOutput(testName, err, WARN_TEXT);
}
// input: old doclet
// outcome: old tool
@Test
public void testOldDoclet() throws Exception {
setArgs("-classpath", ".", // ambient classpath insulation
"-doclet",
OLD_DOCLET_CLASS_NAME,
"-docletpath",
testClasses,
testSrc.toString());
Task.Result tr = task.run(Task.Expect.SUCCESS);
List<String> out = tr.getOutputLines(Task.OutputKind.STDOUT);
List<String> err = tr.getOutputLines(Task.OutputKind.STDERR);
checkOutput(testName, out, OLD_DOCLET_MARKER);
checkOutput(testName, err, WARN_TEXT);
}
// input: old taglet
// outcome: old tool
@Test
public void testOldTaglet() throws Exception {
setArgs("-classpath", ".", // ambient classpath insulation
"-taglet",
OLD_TAGLET_CLASS_NAME,
"-tagletpath",
testClasses,
testSrc.toString());
Task.Result tr = task.run(Task.Expect.SUCCESS);
List<String> out = tr.getOutputLines(Task.OutputKind.STDOUT);
List<String> err = tr.getOutputLines(Task.OutputKind.STDERR);
checkOutput(testName, out, OLD_TAGLET_MARKER);
checkOutput(testName, err, WARN_TEXT);
}
// input: new doclet and old taglet
// outcome: new doclet with failure
@Test
public void testNewDocletOldTaglet() throws Exception {
setArgs("-classpath", ".", // ambient classpath insulation
"-doclet",
NEW_STDDOCLET,
"-taglet",
OLD_TAGLET_CLASS_NAME,
"-tagletpath",
testClasses,
testSrc.toString());
Task.Result tr = task.run(Task.Expect.FAIL, 1);
//Task.Result tr = task.run();
List<String> out = tr.getOutputLines(Task.OutputKind.STDOUT);
List<String> err = tr.getOutputLines(Task.OutputKind.STDERR);
checkOutput(testName, out, NEW_HEADER);
checkOutput(testName, err, NEW_DOCLET_ERROR);
}
// input: old doclet and old taglet
// outcome: old doclet and old taglet should register
@Test
public void testOldDocletOldTaglet() throws Exception {
setArgs("-classpath", ".", // ambient classpath insulation
"-doclet",
OLD_STDDOCLET,
"-taglet",
OLD_TAGLET_CLASS_NAME,
"-tagletpath",
testClasses,
testSrc.toString());
Task.Result tr = task.run(Task.Expect.SUCCESS);
List<String> out = tr.getOutputLines(Task.OutputKind.STDOUT);
List<String> err = tr.getOutputLines(Task.OutputKind.STDERR);
checkOutput(testName, out, OLD_HEADER);
checkOutput(testName, out, OLD_TAGLET_MARKER);
checkOutput(testName, err, WARN_TEXT);
}
// input: new doclet and new taglet
// outcome: new doclet and new taglet should register
@Test
public void testNewDocletNewTaglet() throws Exception {
setArgs("-classpath", ".", // ambient classpath insulation
"-doclet",
NEW_STDDOCLET,
"-taglet",
NEW_TAGLET_CLASS_NAME,
"-tagletpath",
testClasses,
testSrc.toString());
Task.Result tr = task.run(Task.Expect.SUCCESS);
List<String> out = tr.getOutputLines(Task.OutputKind.STDOUT);
List<String> err = tr.getOutputLines(Task.OutputKind.STDERR);
checkOutput(testName, out, NEW_HEADER);
checkOutput(testName, out, NEW_TAGLET_MARKER);
}
// input: old doclet and new taglet
// outcome: old doclet and error
@Test
public void testOldDocletNewTaglet() throws Exception {
setArgs("-classpath", ".", // ambient classpath insulation
"-doclet",
OLD_STDDOCLET,
"-taglet",
NEW_TAGLET_CLASS_NAME,
"-tagletpath",
testClasses,
testSrc.toString());
Task.Result tr = task.run(Task.Expect.FAIL, 1);
List<String> out = tr.getOutputLines(Task.OutputKind.STDOUT);
List<String> err = tr.getOutputLines(Task.OutputKind.STDERR);
checkOutput(testName, out, OLD_HEADER);
checkOutput(testName, err, WARN_TEXT);
checkOutput(testName, err, OLD_DOCLET_ERROR);
}
void setArgs(String... args) {
ostream.println("cmds: " + Arrays.asList(args));
task.args(args);
}
void checkOutput(String testCase, List<String> content, String toFind) throws Exception {
checkOutput(testCase, content, Pattern.compile(".*" + toFind + ".*"));
}
void checkOutput(String testCase, List<String> content, Pattern toFind) throws Exception {
ostream.println("---" + testCase + "---");
content.stream().forEach(x -> System.out.println(x));
for (String x : content) {
ostream.println(x);
if (toFind.matcher(x).matches()) {
return; return;
} }
} }
throw new Exception("run1: Expected string not found:"); throw new Exception(testCase + ": Expected string not found: " + toFind);
} }
// make sure the old doclet is invoked with -Xold public static class OldDoclet extends com.sun.javadoc.Doclet {
void run2() throws Exception { public static boolean start(com.sun.javadoc.RootDoc root) {
List<String> output = doTest(javadoc.getPath(), System.out.println(OLD_DOCLET_MARKER);
"-Xold", return true;
"-classpath", ".", // insulates us from ambient classpath
"-Xdoclint:none",
"-package",
new File(testSrc, thisClassName + ".java").getPath());
for (String x : output) {
if (Expected2.matcher(x).matches()) {
throw new Exception("run2: Expected string not found");
}
return;
} }
} }
/** public static class OldTaglet implements com.sun.tools.doclets.Taglet {
* More dummy comments.
*/ public static void register(Map map) {
List<String> doTest(String... args) throws Exception { EnsureNewOldDoclet.OldTaglet tag = new OldTaglet();
List<String> output = new ArrayList<>(); com.sun.tools.doclets.Taglet t = (com.sun.tools.doclets.Taglet) map.get(tag.getName());
// run javadoc in separate process to ensure doclet executed under System.out.println(OLD_TAGLET_MARKER);
// normal user conditions w.r.t. classloader }
Process p = new ProcessBuilder()
.command(args) @Override
.redirectErrorStream(true) public boolean inField() {
.start(); return true;
try (BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()))) { }
String line = in.readLine();
while (line != null) { @Override
output.add(line.trim()); public boolean inConstructor() {
line = in.readLine(); return true;
}
@Override
public boolean inMethod() {
return true;
}
@Override
public boolean inOverview() {
return true;
}
@Override
public boolean inPackage() {
return true;
}
@Override
public boolean inType() {
return true;
}
@Override
public boolean isInlineTag() {
return true;
}
@Override
public String getName() {
return "OldTaglet";
}
@Override
public String toString(Tag tag) {
return getName();
}
@Override
public String toString(Tag[] tags) {
return getName();
} }
} }
int rc = p.waitFor();
if (rc != 0) public static class NewTaglet implements jdk.javadoc.doclet.taglet.Taglet {
throw new Exception("javadoc failed, rc:" + rc);
return output; @Override
public Set<Location> getAllowedLocations() {
return Collections.emptySet();
}
@Override
public boolean isInlineTag() {
return true;
}
@Override
public String getName() {
return "NewTaglet";
}
@Override
public String toString(DocTree tag) {
return tag.toString();
}
@Override
public String toString(List<? extends DocTree> tags) {
return tags.toString();
}
} }
} }

View file

@ -0,0 +1,118 @@
/*
* Copyright (c) 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.
*
* 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 toolbox;
import java.io.PrintStream;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Function;
/**
* Utility class to manage and execute sub-tests within a test.
*
* This class does the following:
* i. invokes those test methods annotated with @Test
* ii. keeps track of successful and failed tests
* iii. throws an Exception if any test fails.
* iv. provides a test summary at the end of the run.
*
* Tests must extend this class, annotate the test methods
* with @Test and call one of the runTests method.
*/
public abstract class TestRunner {
/** Marker annotation for test cases. */
@Retention(RetentionPolicy.RUNTIME)
public @interface Test { }
int testCount = 0;
int errorCount = 0;
public String testName = null;
final PrintStream out;
/**
* Constructs the Object.
* @param out the PrintStream to print output to.
*/
public TestRunner(PrintStream out) {
this.out = out;
}
/**
* Invoke all methods annotated with @Test.
* @throws java.lang.Exception
*/
public void runTests() throws Exception {
runTests(f -> new Object[0]);
}
/**
* Invoke all methods annotated with @Test.
* @param f a lambda expression to specify arguments.
* @throws java.lang.Exception
*/
public void runTests(Function<Method, Object[]> f) throws Exception {
for (Method m : getClass().getDeclaredMethods()) {
Annotation a = m.getAnnotation(Test.class);
if (a != null) {
testName = m.getName();
try {
testCount++;
out.println("test: " + testName);
m.invoke(this, f.apply(m));
} catch (InvocationTargetException e) {
errorCount++;
Throwable cause = e.getCause();
out.println("Exception: " + e.getCause());
cause.printStackTrace(out);
}
out.println();
}
}
if (testCount == 0) {
throw new Error("no tests found");
}
StringBuilder summary = new StringBuilder();
if (testCount != 1) {
summary.append(testCount).append(" tests");
}
if (errorCount > 0) {
if (summary.length() > 0) {
summary.append(", ");
}
summary.append(errorCount).append(" errors");
}
out.println(summary);
if (errorCount > 0) {
throw new Exception(errorCount + " errors found");
}
}
}