changeset 3365:93bec8d431c5

8154482: javadoc tool must support legacy doclet and taglet Reviewed-by: jjg
author ksrini
date Fri, 29 Apr 2016 15:35:51 -0700
parents 778c7b2dad22
children 2dc339da8a93
files src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/resources/standard.properties src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/DocEnv.java src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/Main.java src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/Messager.java src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/Start.java src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc.properties test/jdk/javadoc/tool/EnsureNewOldDoclet.java test/tools/lib/toolbox/TestRunner.java
diffstat 9 files changed, 564 insertions(+), 131 deletions(-) [+]
line wrap: on
line diff
--- a/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/resources/standard.properties	Fri Apr 29 14:18:09 2016 -0700
+++ b/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/resources/standard.properties	Fri Apr 29 15:35:51 2016 -0700
@@ -1,4 +1,4 @@
-doclet.build_version=Standard Doclet version {0}
+doclet.build_version=Standard Doclet (Old) version {0}
 doclet.Contents=Contents
 doclet.Overview=Overview
 doclet.Window_Overview=Overview List
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties	Fri Apr 29 14:18:09 2016 -0700
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties	Fri Apr 29 15:35:51 2016 -0700
@@ -1,4 +1,4 @@
-doclet.build_version=Standard Doclet (Next) version {0}
+doclet.build_version=Standard Doclet version {0}
 doclet.Contents=Contents
 doclet.Overview=Overview
 doclet.Window_Overview=Overview List
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/DocEnv.java	Fri Apr 29 14:18:09 2016 -0700
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/DocEnv.java	Fri Apr 29 15:35:51 2016 -0700
@@ -550,7 +550,7 @@
         // Messager should be replaced by a more general
         // compilation environment.  This can probably
         // subsume DocEnv as well.
-        messager.exit();
+        throw new Messager.ExitJavadoc();
     }
 
     /**
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/Main.java	Fri Apr 29 14:18:09 2016 -0700
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/Main.java	Fri Apr 29 15:35:51 2016 -0700
@@ -59,13 +59,6 @@
      * @return The return code.
      */
     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();
         return jdoc.begin(args);
     }
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/Messager.java	Fri Apr 29 14:18:09 2016 -0700
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/Messager.java	Fri Apr 29 15:35:51 2016 -0700
@@ -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.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -139,7 +139,7 @@
         }
     }
 
-    public class ExitJavadoc extends Error {
+    public static class ExitJavadoc extends Error {
         private static final long serialVersionUID = 0;
     }
 
@@ -416,15 +416,6 @@
         }
     }
 
-    /**
-     * 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) {
         switch (type) {
             case ERROR:
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/Start.java	Fri Apr 29 14:18:09 2016 -0700
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/Start.java	Fri Apr 29 15:35:51 2016 -0700
@@ -66,6 +66,7 @@
 import jdk.javadoc.doclet.DocletEnvironment;
 
 import static com.sun.tools.javac.main.Option.*;
+
 /**
  * Main program of Javadoc.
  * Previously named "Main".
@@ -79,6 +80,12 @@
  * @author Neal Gafter (rewrite)
  */
 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. */
     private final Context context;
 
@@ -193,18 +200,26 @@
         if (foot != null)
             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) {
         // Preprocess @file arguments
@@ -212,14 +227,18 @@
             argv = CommandLine.parse(argv);
         } catch (FileNotFoundException e) {
             messager.error("main.cant.read", e.getMessage());
-            exit();
+            throw new Messager.ExitJavadoc();
         } catch (IOException e) {
             e.printStackTrace(System.err);
-            exit();
+            throw new Messager.ExitJavadoc();
         }
 
-        List<String> argList = Arrays.asList(argv);
-        boolean ok = begin(argList, Collections.<JavaFileObject> emptySet());
+        if (argv.length > 0 && "-Xold".equals(argv[0])) {
+            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;
     }
 
@@ -231,11 +250,11 @@
         List<String> opts = new ArrayList<>();
         for (String opt: options)
             opts.add(opt);
+
         return begin(opts, fileObjects);
     }
 
     private boolean begin(List<String> options, Iterable<? extends JavaFileObject> fileObjects) {
-
         fileManager = context.get(JavaFileManager.class);
         if (fileManager == null) {
             JavacFileManager.preRegister(context);
@@ -244,9 +263,8 @@
                 ((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);
-
         if (jdk.javadoc.doclet.Doclet.class.isAssignableFrom(docletClass)) {
             // no need to dispatch to old, safe to init now
             initMessager();
@@ -257,7 +275,7 @@
                 exc.printStackTrace();
                 if (!apiMode) {
                     error("main.could_not_instantiate_class", docletClass);
-                    messager.exit();
+                    throw new Messager.ExitJavadoc();
                 }
                 throw new ClientCodeException(exc);
             }
@@ -267,6 +285,7 @@
                         = new com.sun.tools.javadoc.Start(context);
                 return ostart.begin(docletClass, options, fileObjects);
             }
+            warn("main.legacy_api");
             String[] array = options.toArray(new String[options.size()]);
             return com.sun.tools.javadoc.Main.execute(array) == 0;
         }
@@ -459,6 +478,11 @@
         String userDocletPath = 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.
         for (int i = 0 ; i < argv.size() ; i++) {
             String arg = argv.get(i);
@@ -470,7 +494,7 @@
                 oneArg(argv, i++);
                 if (userDocletName != null) {
                     usageError("main.more_than_one_doclet_specified_0_and_1",
-                               userDocletName, argv.get(i));
+                            userDocletName, argv.get(i));
                 }
                 if (docletName != null) {
                     usageError("main.more_than_one_doclet_specified_0_and_1",
@@ -484,13 +508,20 @@
                 } else {
                     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) {
             return docletClass;
         }
+
         // Step 3: doclet name specified ? if so find a ClassLoader,
         // and load it.
         if (userDocletName != null) {
@@ -506,38 +537,80 @@
                     try {
                         ((StandardJavaFileManager)fileManager).setLocation(DOCLET_PATH, paths);
                     } catch (IOException ioe) {
-                        panic("main.doclet_no_classloader_found", ioe);
-                        return null; // keep compiler happy
+                        error("main.doclet_could_not_set_location", paths);
+                        throw new Messager.ExitJavadoc();
                     }
                 }
                 cl = fileManager.getClassLoader(DOCLET_PATH);
                 if (cl == null) {
                     // despite doclet specified on cmdline no classloader found!
-                    panic("main.doclet_no_classloader_found", userDocletName);
-                    return null; // keep compiler happy
-                }
-                try {
-                    Class<?> klass = cl.loadClass(userDocletName);
-                    ensureReadable(klass);
-                    return klass;
-                } catch (ClassNotFoundException cnfe) {
-                    panic("main.doclet_class_not_found", userDocletName);
-                    return null; // keep compiler happy
+                    error("main.doclet_no_classloader_found", userDocletName);
+                    throw new Messager.ExitJavadoc();
                 }
             }
+            try {
+                Class<?> klass = cl.loadClass(userDocletName);
+                ensureReadable(klass);
+                return klass;
+            } catch (ClassNotFoundException cnfe) {
+                error("main.doclet_class_not_found", userDocletName);
+                throw new Messager.ExitJavadoc();
+            }
         }
-        // Step 4: we have a doclet, try loading it, otherwise
-        // return back the standard doclet
+
+        // Step 4: we have a doclet, try loading it
         if (docletName != null) {
             try {
                 return Class.forName(docletName, true, getClass().getClassLoader());
             } catch (ClassNotFoundException cnfe) {
-                panic("main.doclet_class_not_found", userDocletName);
-                return null; // happy compiler, should not happen
+                error("main.doclet_class_not_found", userDocletName);
+                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) {
@@ -595,14 +668,12 @@
         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) {
+        messager.error(key, args);
     }
 
-    void error(String key, Object... args) {
-        messager.error(key, args);
+    void warn(String key, Object... args)  {
+        messager.warning(key, args);
     }
 
     /**
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc.properties	Fri Apr 29 14:18:09 2016 -0700
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc.properties	Fri Apr 29 15:35:51 2016 -0700
@@ -73,7 +73,8 @@
 \                                   given module. <other-module> may be ALL-UNNAMED to require\n\
 \                                   the unnamed module.\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=\
 These options are non-standard and subject to change without notice.
@@ -96,6 +97,7 @@
 such as -J-Xmx32m.
 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.doclet_could_not_set_location=Could not set location for {0}
 main.doclet_no_classloader_found=Could not obtain classloader to load {0}
 main.could_not_instantiate_class=Could not instantiate class {0}
 main.doclet_class_not_found=Cannot find doclet class {0}
@@ -109,10 +111,15 @@
 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.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.error=error
 javadoc.warning=warning
-
 javadoc.error.msg={0}: error - {1}
 javadoc.warning.msg={0}: warning - {1}
 javadoc.note.msg = {1}
--- a/test/jdk/javadoc/tool/EnsureNewOldDoclet.java	Fri Apr 29 14:18:09 2016 -0700
+++ b/test/jdk/javadoc/tool/EnsureNewOldDoclet.java	Fri Apr 29 15:35:51 2016 -0700
@@ -23,96 +23,349 @@
 
 /*
  * @test
- * @bug 8035473
- * @summary make sure the new doclet is invoked by default, and -Xold
+ * @bug 8035473 8154482
+ * @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.util.ArrayList;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 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 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 Pattern Expected2 = Pattern.compile("^Standard Doclet version.*");
+    final static String CLASS_NAME = "EnsureNewOldDoclet";
+    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() {
-        File javaHome = new File(System.getProperty("java.home"));
-        if (javaHome.getName().endsWith("jre"))
-            javaHome = javaHome.getParentFile();
-        javadoc = new File(new File(javaHome, "bin"), "javadoc");
-        testSrc = new File(System.getProperty("test.src"));
-        thisClassName = EnsureNewOldDoclet.class.getName();
+    final static Pattern OLD_HEADER = Pattern.compile("^Standard Doclet \\(Old\\) version.*");
+    final static Pattern NEW_HEADER = Pattern.compile("^Standard Doclet version.*");
+
+
+    final static String OLD_DOCLET_MARKER = "OLD_DOCLET_MARKER";
+    final static String OLD_TAGLET_MARKER = "Registered: OldTaglet";
+
+    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 {
-        EnsureNewOldDoclet test = new EnsureNewOldDoclet();
-        test.run1();
-        test.run2();
+        new EnsureNewOldDoclet().runTests();
     }
 
-    // make sure new doclet is invoked by default
-    void run1() throws Exception {
-        List<String> output = doTest(javadoc.getPath(),
-                "-classpath", ".", // insulates us from ambient classpath
-                "-Xdoclint:none",
-                "-package",
-                new File(testSrc, thisClassName + ".java").getPath());
-        System.out.println(output);
-        for (String x : output) {
-            if (Expected1.matcher(x).matches()) {
+    // input: nothing, default mode
+    // outcome: new tool and new doclet
+    @Test
+    public void testDefault() throws Exception {
+        setArgs("-classpath", ".", // insulates us from ambient classpath
+                  testSrc.toString());
+        Task.Result tr = task.run(Task.Expect.SUCCESS);
+        List<String> out = tr.getOutputLines(Task.OutputKind.STDOUT);
+        checkOutput(testName, out, NEW_HEADER);
+    }
+
+    // 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;
             }
         }
-        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
-    void run2() throws Exception {
-        List<String> output = doTest(javadoc.getPath(),
-                "-Xold",
-                "-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 OldDoclet extends com.sun.javadoc.Doclet {
+        public static boolean start(com.sun.javadoc.RootDoc root) {
+            System.out.println(OLD_DOCLET_MARKER);
+            return true;
         }
     }
 
-    /**
-     * More dummy comments.
-     */
-    List<String> doTest(String... args) throws Exception {
-        List<String> output = new ArrayList<>();
-        // run javadoc in separate process to ensure doclet executed under
-        // normal user conditions w.r.t. classloader
-        Process p = new ProcessBuilder()
-                .command(args)
-                .redirectErrorStream(true)
-                .start();
-        try (BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
-            String line = in.readLine();
-            while (line != null) {
-                output.add(line.trim());
-                line = in.readLine();
-            }
+    public static class OldTaglet implements com.sun.tools.doclets.Taglet {
+
+        public static void register(Map map) {
+            EnsureNewOldDoclet.OldTaglet tag = new OldTaglet();
+            com.sun.tools.doclets.Taglet t = (com.sun.tools.doclets.Taglet) map.get(tag.getName());
+            System.out.println(OLD_TAGLET_MARKER);
         }
-        int rc = p.waitFor();
-        if (rc != 0)
-            throw new Exception("javadoc failed, rc:" + rc);
-        return output;
+
+        @Override
+        public boolean inField() {
+            return true;
+        }
+
+        @Override
+        public boolean inConstructor() {
+            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();
+        }
+    }
+
+    public static class NewTaglet implements jdk.javadoc.doclet.taglet.Taglet {
+
+        @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();
+        }
+
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/lib/toolbox/TestRunner.java	Fri Apr 29 15:35:51 2016 -0700
@@ -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");
+        }
+    }
+}