langtools/test/com/sun/javadoc/lib/JavadocTester.java
changeset 24399 af1a0220d0fa
parent 24217 25b12d4d4192
child 25004 b33effe4f252
equal deleted inserted replaced
24398:601a611d0aee 24399:af1a0220d0fa
    19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    20  * or visit www.oracle.com if you need additional information or have any
    20  * or visit www.oracle.com if you need additional information or have any
    21  * questions.
    21  * questions.
    22  */
    22  */
    23 
    23 
    24 import java.io.*;
    24 import java.io.BufferedWriter;
       
    25 import java.io.ByteArrayOutputStream;
       
    26 import java.io.File;
       
    27 import java.io.FileNotFoundException;
       
    28 import java.io.FileWriter;
       
    29 import java.io.FilenameFilter;
       
    30 import java.io.IOException;
       
    31 import java.io.PrintStream;
       
    32 import java.io.PrintWriter;
       
    33 import java.io.StringWriter;
       
    34 import java.lang.annotation.Annotation;
       
    35 import java.lang.annotation.Retention;
       
    36 import java.lang.annotation.RetentionPolicy;
       
    37 import java.lang.ref.SoftReference;
       
    38 import java.lang.reflect.InvocationTargetException;
       
    39 import java.lang.reflect.Method;
       
    40 import java.nio.file.Files;
       
    41 import java.util.ArrayList;
       
    42 import java.util.EnumMap;
       
    43 import java.util.HashMap;
       
    44 import java.util.List;
       
    45 import java.util.Map;
    25 
    46 
    26 
    47 
    27 /**
    48 /**
    28  * Runs javadoc and then runs regression tests on the resulting output.
    49  * Test framework for running javadoc and performing tests on the resulting output.
    29  * This class currently contains three tests:
    50  *
    30  * <ul>
    51  * <p>
    31  * <li> String search: Reads each file, complete with newlines,
    52  * Tests are typically written as subtypes of JavadocTester, with a main
    32  *      into a string.  Lets you search for strings that contain
    53  * method that creates an instance of the test class and calls the runTests()
    33  *      newlines.  String matching is case-sensitive.
    54  * method. The runTests() methods calls all the test methods declared in the class,
    34  *      You can run javadoc multiple times with different arguments,
    55  * and then calls a method to print a summary, and throw an exception if
    35  *      generating output into different destination directories, and
    56  * any of the test methods reported a failure.
    36  *      then perform a different array of tests on each one.
    57  *
    37  *      To do this, the run method accepts a test array for testing
    58  * <p>
    38  *      that a string is found, and a negated test array for testing
    59  * Test methods are identified with a @Test annotation. They have no parameters.
    39  *      that a string is not found.
    60  * The name of the method is not important, but if you have more than one, it is
    40  * <li> Run diffs: Iterate through the list of given file pairs
    61  * recommended that the names be meaningful and suggestive of the test case
    41  *      and diff the pairs.
    62  * contained therein.
    42  * <li> Check exit code: Check the exit code of Javadoc and
    63  *
    43  *      record whether the test passed or failed.
    64  * <p>
    44  * </ul>
    65  * Typically, a test method will invoke javadoc, and then perform various
       
    66  * checks on the results. The standard checks are:
       
    67  *
       
    68  * <dl>
       
    69  * <dt>checkExitCode
       
    70  * <dd>Check the exit code returned from javadoc.
       
    71  * <dt>checkOutput
       
    72  * <dd>Perform a series of checks on the contents on a file or output stream
       
    73  *     generated by javadoc.
       
    74  *     The checks can be either that a series of strings are found or are not found.
       
    75  * <dt>checkFiles
       
    76  * <dd>Perform a series of checks on the files generated by javadoc.
       
    77  *     The checks can be that a series of files are found or are not found.
       
    78  * </dl>
       
    79  *
       
    80  * <pre><code>
       
    81  *  public class MyTester extends JavadocTester {
       
    82  *      public static void main(String... args) throws Exception {
       
    83  *          MyTester tester = new MyTester();
       
    84  *          tester.runTests();
       
    85  *      }
       
    86  *
       
    87  *      // test methods...
       
    88  *      @Test
       
    89  *      void test() {
       
    90  *          javadoc(<i>args</i>);
       
    91  *          checkExit(Exit.OK);
       
    92  *          checkOutput(<i>file</i>, true,
       
    93  *              <i>strings-to-find</i>);
       
    94  *          checkOutput(<i>file</i>, false,
       
    95  *              <i>strings-to-not-find</i>);
       
    96  *      }
       
    97  *  }
       
    98  * </code></pre>
       
    99  *
       
   100  * <p>
       
   101  * If javadoc is run more than once in a test method, you can compare the
       
   102  * results that are generated with the diff method. Since files written by
       
   103  * javadoc typically contain a timestamp, you may want to use the -notimestamp
       
   104  * option if you are going to compare the results from two runs of javadoc.
       
   105  *
       
   106  * <p>
       
   107  * If you have many calls of checkOutput that are very similar, you can write
       
   108  * your own check... method to reduce the amount of duplication. For example,
       
   109  * if you want to check that many files contain the same string, you could
       
   110  * write a method that takes a varargs list of files and calls checkOutput
       
   111  * on each file in turn with the string to be checked.
       
   112  *
       
   113  * <p>
       
   114  * You can also write you own custom check methods, which can use
       
   115  * readFile to get the contents of a file generated by javadoc,
       
   116  * and then use pass(...) or fail(...) to report whether the check
       
   117  * succeeded or not.
       
   118  *
       
   119  * <p>
       
   120  * You can have many separate test methods, each identified with a @Test
       
   121  * annotation. However, you should <b>not</b> assume they will be called
       
   122  * in the order declared in your source file.  If the order of a series
       
   123  * of javadoc invocations is important, do that within a single method.
       
   124  * If the invocations are independent, for better clarity, use separate
       
   125  * test methods, each with their own set of checks on the results.
    45  *
   126  *
    46  * @author Doug Kramer
   127  * @author Doug Kramer
    47  * @author Jamie Ho
   128  * @author Jamie Ho
    48  * @since 1.4.2
   129  * @author Jonathan Gibbons (rewrite)
    49  */
   130  */
    50 public abstract class JavadocTester {
   131 public abstract class JavadocTester {
    51 
   132 
    52     protected static final String NL = System.getProperty("line.separator");
   133     public static final String FS = System.getProperty("file.separator");
    53     protected static final String FS = System.getProperty("file.separator");
   134     public static final String PS = System.getProperty("path.separator");
    54 
   135     public static final String NL = System.getProperty("line.separator");
    55     protected static final String SRC_DIR = System.getProperty("test.src", ".");
   136 
    56     protected static final String JAVA_VERSION = System.getProperty("java.version");
   137     public enum Output {
    57     protected static final String OUTPUT_DIR = "out";
   138         /** The name for error output from javadoc. */
    58     protected static final String[][] NO_TEST = new String[][] {};
   139         ERROR,
    59     protected static final String[] NO_FILE_TEST = new String[] {};
   140         /** The name for the notice output from javadoc. */
    60 
   141         NOTICE,
    61     /**
   142         /** The name for the warning output  from javadoc. */
    62      * Use this as the file name in the test array when you want to search
   143         WARNING,
    63      * for a string in the error output.
   144         /** The name for any output written to System.out. */
    64      */
   145         STDOUT,
    65     public static final String ERROR_OUTPUT = "ERROR_OUTPUT";
   146         /** The name for any output written to System.err. */
    66 
   147         STDERR
    67     /**
   148     }
    68      * Use this as the file name in the test array when you want to search
   149 
    69      * for a string in the notice output.
   150     /** The output directory used in the most recent call of javadoc. */
    70      */
   151     protected File outputDir;
    71     public static final String NOTICE_OUTPUT = "NOTICE_OUTPUT";
   152 
    72 
   153     /** The exit code of the most recent call of javadoc. */
    73     /**
   154     private int exitCode;
    74      * Use this as the file name in the test array when you want to search
   155 
    75      * for a string in the warning output.
   156     /** The output generated by javadoc to the various writers and streams. */
    76      */
   157     private final Map<Output, String> outputMap = new EnumMap<>(Output.class);
    77     public static final String WARNING_OUTPUT = "WARNING_OUTPUT";
   158 
    78 
   159     /** A cache of file content, to avoid reading files unnecessarily. */
    79     /**
   160     private final Map<File,SoftReference<String>> fileContentCache = new HashMap<>();
    80      * Use this as the file name in the test array when you want to search
   161 
    81      * for a string in standard output.
   162     /** Stream used for logging messages. */
    82      */
   163     private final PrintStream out = System.out;
    83     public static final String STANDARD_OUTPUT = "STANDARD_OUTPUT";
   164 
    84 
   165     /** The directory containing the source code for the test. */
    85     /**
   166     public static final String testSrc = System.getProperty("test.src");
    86      * The default doclet.
   167 
    87      */
   168     /**
    88     public static final String DEFAULT_DOCLET_CLASS = "com.sun.tools.doclets.formats.html.HtmlDoclet";
   169      * Get the path for a source file in the test source directory.
    89     public static final String DEFAULT_DOCLET_CLASS_OLD = "com.sun.tools.doclets.standard.Standard";
   170      * @param path the path of a file or directory in the source directory
    90 
   171      * @return the full path of the specified file
    91     /**
   172      */
    92      * The writer to write error messages.
   173     public static String testSrc(String path) {
    93      */
   174         return new File(testSrc, path).getPath();
    94     public StringWriter errors;
   175     }
    95 
       
    96     /**
       
    97      * The writer to write notices.
       
    98      */
       
    99     public StringWriter notices;
       
   100 
       
   101     /**
       
   102      * The writer to write warnings.
       
   103      */
       
   104     public StringWriter warnings;
       
   105 
       
   106     /**
       
   107      * The buffer of warning output.
       
   108      */
       
   109     public StringBuffer standardOut;
       
   110 
       
   111     /**
       
   112      * The output directory.
       
   113      */
       
   114     private File outputDir;
       
   115 
   176 
   116     /**
   177     /**
   117      * Alternatives for checking the contents of a directory.
   178      * Alternatives for checking the contents of a directory.
   118      */
   179      */
   119     enum DirectoryCheck {
   180     public enum DirectoryCheck {
   120         /**
   181         /**
   121          * Check that the directory is empty.
   182          * Check that the directory is empty.
   122          */
   183          */
   123         EMPTY((file, name) -> true),
   184         EMPTY((file, name) -> true),
   124         /**
   185         /**
   151         }
   212         }
   152     }
   213     }
   153 
   214 
   154     private DirectoryCheck outputDirectoryCheck = DirectoryCheck.EMPTY;
   215     private DirectoryCheck outputDirectoryCheck = DirectoryCheck.EMPTY;
   155 
   216 
   156     /**
   217     /** The current subtest number. Incremented when checking(...) is called. */
   157      * The current subtest number.
   218     private int numTestsRun = 0;
   158      */
   219 
   159     private static int numTestsRun = 0;
   220     /** The number of subtests passed. Incremented when passed(...) is called. */
   160 
   221     private int numTestsPassed = 0;
   161     /**
   222 
   162      * The number of subtests passed.
   223     /** The current run of javadoc. Incremented when javadoc is called. */
   163      */
   224     private int javadocRunNum = 0;
   164     private static int numTestsPassed = 0;
   225 
   165 
   226     /** The name of the standard doclet. */
   166     /**
   227     // This ought not to be necessary; there ought to be a javadoc entry point
   167      * The current run of javadoc
   228     // that does not require this to be know externally.
   168      */
   229     private static final String standardDocletClassName =
   169     private static int javadocRunNum = 0;
   230             "com.sun.tools.doclets.standard.Standard";
   170 
   231 
   171     /**
   232     /** Marker annotation for test methods to be invoked by runTests. */
   172      * Construct a JavadocTester.
   233     @Retention(RetentionPolicy.RUNTIME)
   173      */
   234     @interface Test { }
   174     public JavadocTester() {
   235 
   175     }
   236     /**
   176 
   237      * Run all methods annotated with @Test, followed by printSummary.
   177     /**
   238      * Typically called on a tester object in main()
   178      * Execute the tests.
   239      * @throws Exception if any errors occurred
   179      *
   240      */
   180      * @param args             the arguments to pass to Javadoc
   241     public void runTests() throws Exception {
   181      * @param testArray        the array of tests
   242         for (Method m: getClass().getDeclaredMethods()) {
   182      * @param negatedTestArray the array of negated tests
   243             Annotation a = m.getAnnotation(Test.class);
   183      * @return                 the return code for the execution of Javadoc
   244             if (a != null) {
   184      */
   245                 try {
   185     public int run(String[] args,
   246                     out.println("Running test " + m.getName());
   186             String[][] testArray, String[][] negatedTestArray) {
   247                     m.invoke(this, new Object[] { });
   187         int returnCode = runJavadoc(args);
   248                 } catch (InvocationTargetException e) {
   188         runTestsOnHTML(testArray, negatedTestArray);
   249                     Throwable cause = e.getCause();
   189         return returnCode;
   250                     throw (cause instanceof Exception) ? ((Exception) cause) : e;
   190     }
   251                 }
   191 
   252                 out.println();
   192     /**
   253             }
   193      * Execute the tests.
   254         }
   194      *
   255         printSummary();
   195      * @param args                 the arguments to pass to Javadoc
   256     }
   196      * @param testArray            the array of tests
   257 
   197      * @param negatedTestArray     the array of negated tests
   258     /**
   198      * @param fileTestArray        the array of file tests
   259      * Run javadoc.
   199      * @param negatedFileTestArray the array of negated file tests
   260      * The output directory used by this call and the final exit code
   200      * @return                     the return code for the execution of Javadoc
   261      * will be saved for later use.
   201      */
   262      * To aid the reader, it is recommended that calls to this method
   202     public int run(String[] args,
   263      * put each option and the arguments it takes on a separate line.
   203             String[][] testArray, String[][] negatedTestArray,
   264      *
   204             String[] fileTestArray, String[] negatedFileTestArray) {
   265      * Example:
   205         int returnCode = runJavadoc(args);
   266      * <pre><code>
   206         runTestsOnHTML(testArray, negatedTestArray);
   267      *  javadoc("-d", "out",
   207         runTestsOnFile(fileTestArray, negatedFileTestArray);
   268      *          "-sourcepath", testSrc,
   208         return returnCode;
   269      *          "-notimestamp",
   209     }
   270      *          "pkg1", "pkg2", "pkg3/C.java");
   210 
   271      * </code></pre>
   211     /**
   272      *
   212      * Execute Javadoc using the default doclet.
   273      * @param args the arguments to pass to javadoc
   213      *
   274      */
   214      * @param args  the arguments to pass to Javadoc
   275     public void javadoc(String... args) {
   215      * @return      the return code from the execution of Javadoc
   276         outputMap.clear();
   216      */
   277         fileContentCache.clear();
   217     public int runJavadoc(String[] args) {
   278 
   218         float javaVersion = Float.parseFloat(JAVA_VERSION.substring(0,3));
       
   219         String docletClass = javaVersion < 1.5 ?
       
   220             DEFAULT_DOCLET_CLASS_OLD : DEFAULT_DOCLET_CLASS;
       
   221         return runJavadoc(docletClass, args);
       
   222     }
       
   223 
       
   224 
       
   225     /**
       
   226      * Execute Javadoc.
       
   227      *
       
   228      * @param docletClass the doclet being tested.
       
   229      * @param args  the arguments to pass to Javadoc
       
   230      * @return      the return code from the execution of Javadoc
       
   231      */
       
   232     public int runJavadoc(String docletClass, String[] args) {
       
   233         javadocRunNum++;
   279         javadocRunNum++;
   234         if (javadocRunNum == 1) {
   280         if (javadocRunNum == 1) {
   235             System.out.println("\n" + "Running javadoc...");
   281             out.println("Running javadoc...");
   236         } else {
   282         } else {
   237             System.out.println("\n" + "Running javadoc (run "
   283             out.println("Running javadoc (run "
   238                                     + javadocRunNum + ")...");
   284                                     + javadocRunNum + ")...");
   239         }
   285         }
   240         initOutputBuffers();
       
   241         outputDir = new File(".");
   286         outputDir = new File(".");
   242         for (int i = 0; i < args.length - 2; i++) {
   287         for (int i = 0; i < args.length - 2; i++) {
   243             if (args[i].equals("-d")) {
   288             if (args[i].equals("-d")) {
   244                 outputDir = new File(args[++i]);
   289                 outputDir = new File(args[++i]);
   245                 break;
   290                 break;
   246             }
   291             }
   247         }
   292         }
       
   293 //        log.setOutDir(outputDir);
   248 
   294 
   249         outputDirectoryCheck.check(outputDir);
   295         outputDirectoryCheck.check(outputDir);
   250 
   296 
   251         ByteArrayOutputStream stdout = new ByteArrayOutputStream();
   297         // These are the primary streams used by javadoc
   252         PrintStream prevOut = System.out;
   298         WriterOutput errOut = new WriterOutput();
   253         System.setOut(new PrintStream(stdout));
   299         WriterOutput warnOut = new WriterOutput();
   254 
   300         WriterOutput noticeOut = new WriterOutput();
   255         ByteArrayOutputStream stderr = new ByteArrayOutputStream();
   301         // These are to catch output to System.out and System.err,
   256         PrintStream prevErr = System.err;
   302         // in case these are used instead of the primary streams
   257         System.setErr(new PrintStream(stderr));
   303         StreamOutput sysOut = new StreamOutput(System.out, System::setOut);
   258 
   304         StreamOutput sysErr = new StreamOutput(System.err, System::setErr);
   259         int returnCode = com.sun.tools.javadoc.Main.execute(
   305 
   260                 "javadoc",
   306         try {
   261                 new PrintWriter(errors, true),
   307             exitCode = com.sun.tools.javadoc.Main.execute(
   262                 new PrintWriter(warnings, true),
   308                     "javadoc",
   263                 new PrintWriter(notices, true),
   309                     errOut.pw, warnOut.pw, noticeOut.pw,
   264                 docletClass,
   310                     standardDocletClassName,
   265                 getClass().getClassLoader(),
   311                     args);
   266                 args);
   312         } finally {
   267         System.setOut(prevOut);
   313             outputMap.put(Output.STDOUT, sysOut.close());
   268         standardOut = new StringBuffer(stdout.toString());
   314             outputMap.put(Output.STDERR, sysErr.close());
   269         System.setErr(prevErr);
   315             outputMap.put(Output.ERROR, errOut.close());
   270         errors.write(NL + stderr.toString());
   316             outputMap.put(Output.WARNING, warnOut.close());
   271 
   317             outputMap.put(Output.NOTICE, noticeOut.close());
   272         printJavadocOutput();
   318         }
   273         return returnCode;
   319 
   274     }
   320         outputMap.forEach((name, text) -> {
   275 
   321             if (!text.isEmpty()) {
   276     /**
   322                 out.println("javadoc " + name + ":");
   277      * Set a filter to check the initial contents of the output directory
   323                 out.println(text);
       
   324             }
       
   325         });
       
   326     }
       
   327 
       
   328     /**
       
   329      * Set the kind of check for the initial contents of the output directory
   278      * before javadoc is run.
   330      * before javadoc is run.
   279      * The filter should return true for files that should <b>not</b> appear.
   331      * The filter should return true for files that should <b>not</b> appear.
   280      */
   332      * @param c the kind of check to perform
   281     public void setCheckOutputDirectoryCheck(DirectoryCheck c) {
   333      */
       
   334     public void setOutputDirectoryCheck(DirectoryCheck c) {
   282         outputDirectoryCheck = c;
   335         outputDirectoryCheck = c;
   283     }
   336     }
   284 
   337 
   285     /**
   338     public enum Exit {
   286      * Create new string writer buffers
   339         OK(0),
   287      */
   340         FAILED(1);
   288     private void initOutputBuffers() {
   341 
   289         errors   = new StringWriter();
   342         Exit(int code) {
   290         notices  = new StringWriter();
   343             this.code = code;
   291         warnings = new StringWriter();
   344         }
   292     }
   345 
   293 
   346         final int code;
   294     /**
   347     }
   295      * Run array of tests on the resulting HTML.
   348 
   296      * This method accepts a testArray for testing that a string is found
   349     /**
   297      * and a negatedTestArray for testing that a string is not found.
   350      * Check the exit code of the most recent call of javadoc.
   298      *
   351      *
   299      * @param testArray         the array of tests
   352      * @param expected the exit code that is required for the test
   300      * @param negatedTestArray  the array of negated tests
   353      * to pass.
   301      */
   354      */
   302     public void runTestsOnHTML(String[][] testArray, String[][] negatedTestArray) {
   355     public void checkExit(Exit expected) {
   303         runTestsOnHTML(testArray, false);
   356         checking("check exit code");
   304         runTestsOnHTML(negatedTestArray, true);
   357         if (exitCode == expected.code) {
   305     }
   358             passed("return code " + exitCode);
   306 
   359         } else {
   307     /**
   360             failed("return code " + exitCode +"; expected " + expected.code + " (" + expected + ")");
   308      * Run array of tests on the generated files.
   361         }
   309      * This method accepts a fileTestArray for testing if a file is generated
   362     }
   310      * and a negatedFileTestArray for testing if a file is not found.
   363 
   311      * The files are relative to the most recent output directory specified
   364     /**
   312      * with -d.
   365      * Check for content in (or not in) the generated output.
   313      *
   366      * Within the search strings, the newline character \n
   314      * @param fileTestArray         the array of file tests
   367      * will be translated to the platform newline character sequence.
   315      * @param negatedFileTestArray  the array of negated file tests
   368      * @param path a path within the most recent output directory
   316      */
   369      *  or the name of one of the output buffers, identifying
   317     public void runTestsOnFile(String[] fileTestArray, String[] negatedFileTestArray) {
   370      *  where to look for the search strings.
   318         runTestsOnFile(outputDir, fileTestArray, false);
   371      * @param expectedFound true if all of the search strings are expected
   319         runTestsOnFile(outputDir, negatedFileTestArray, true);
   372      *  to be found, or false if all of the strings are expected to be
   320     }
   373      *  not found
   321 
   374      * @param strings the strings to be searched for
   322     /**
   375      */
   323      * Run the array of tests on the resulting HTML.
   376     public void checkOutput(String path, boolean expectedFound, String... strings) {
   324      * The files are relative to the most recent output directory specified
   377         // Read contents of file
   325      * with -d.
   378         String fileString;
   326      *
   379         try {
   327      * @param testArray the array of tests
   380             fileString = readFile(outputDir, path);
   328      * @param isNegated true if test is negated; false otherwise
   381         } catch (Error e) {
   329      */
   382             if (!expectedFound) {
   330     private void runTestsOnHTML(String[][] testArray , boolean isNegated) {
   383                 failed("Error reading file: " + e);
   331         for (String[] test : testArray) {
   384                 return;
   332             numTestsRun++;
   385             }
   333             System.out.print("Running subtest #" + numTestsRun + "... ");
   386             throw e;
   334             // Get string to find
   387         }
   335             String stringToFind = test[1];
   388         checkOutput(path, fileString, expectedFound, strings);
   336             // Read contents of file into a string
   389     }
   337             String fileString;
   390 
   338             try {
   391     /**
   339                 fileString = readFileToString(outputDir, test[0]);
   392      * Check for content in (or not in) the one of the output streams written by
   340             } catch (Error e) {
   393      * javadoc. Within the search strings, the newline character \n
   341                 if (isNegated) {
   394      * will be translated to the platform newline character sequence.
   342                     System.out.println( "FAILED, due to " + e + "\n");
   395      * @param output the output stream to check
   343                     continue;
   396      * @param expectedFound true if all of the search strings are expected
   344                 }
   397      *  to be found, or false if all of the strings are expected to be
   345                 throw e;
   398      *  not found
   346             }
   399      * @param strings the strings to be searched for
       
   400      */
       
   401     public void checkOutput(Output output, boolean expectedFound, String... strings) {
       
   402         checkOutput(output.toString(), outputMap.get(output), expectedFound, strings);
       
   403     }
       
   404 
       
   405     private void checkOutput(String path, String fileString, boolean expectedFound, String... strings) {
       
   406         for (String stringToFind : strings) {
       
   407 //            log.logCheckOutput(path, expectedFound, stringToFind);
       
   408             checking("checkOutput");
   347             // Find string in file's contents
   409             // Find string in file's contents
   348             boolean isFound = findString(fileString, stringToFind);
   410             boolean isFound = findString(fileString, stringToFind);
   349             if ((isNegated && !isFound) || (!isNegated && isFound)) {
   411             if (isFound == expectedFound) {
   350                 numTestsPassed += 1;
   412                 passed(path + ": " + (isFound ? "found:" : "not found:") + "\n"
   351                 System.out.println("Passed" + "\n"
   413                         + stringToFind + "\n");
   352                         + (isNegated ? "not found:" : "found:") + "\n"
       
   353                         + stringToFind + " in " + test[0] + "\n");
       
   354             } else {
   414             } else {
   355                 System.out.println("FAILED, when searching for:" + "\n"
   415                 failed(path + ": " + (isFound ? "found:" : "not found:") + "\n"
   356                         + stringToFind
   416                         + stringToFind + "\n");
   357                         + " in " + test[0] + "\n");
   417             }
   358             }
   418         }
   359         }
   419     }
   360     }
   420 
   361 
   421     /**
   362     /**
   422      * Check for files in (or not in) the generated output.
   363      * Run the array of file tests on the generated files.
   423      * @param expectedFound true if all of the files are expected
   364      *
   424      *  to be found, or false if all of the files are expected to be
   365      * @param testArray the array of file tests
   425      *  not found
   366      * @param isNegated true if test is negated; false otherwise
   426      * @param paths the files to check, within the most recent output directory.
   367      */
   427      * */
   368     private void runTestsOnFile(File baseDir, String[] testArray, boolean isNegated) {
   428     public void checkFiles(boolean expectedFound, String... paths) {
   369         for (String fileName : testArray) {
   429         for (String path: paths) {
   370             numTestsRun++;
   430 //            log.logCheckFile(path, expectedFound);
   371             String failedString = "FAILED: file (" + fileName + ") found" + "\n";
   431             checking("checkFile");
   372             String passedString = "Passed" + "\n" +
   432             File file = new File(outputDir, path);
   373                     "file (" + fileName + ") not found" + "\n";
   433             boolean isFound = file.exists();
   374             System.out.print("Running subtest #" + numTestsRun + "... ");
   434             if (isFound == expectedFound) {
   375             try {
   435                 passed(path + ": " + (isFound ? "found:" : "not found:") + "\n");
   376                 File file = new File(baseDir, fileName);
   436             } else {
   377                 if ((file.exists() && !isNegated) || (!file.exists() && isNegated)) {
   437                 failed(path + ": " + (isFound ? "found:" : "not found:") + "\n");
   378                     numTestsPassed += 1;
   438             }
   379                     System.out.println(passedString);
   439         }
   380                 } else {
   440     }
   381                     System.out.println(failedString);
   441 
   382                 }
   442     /**
   383             } catch (Error e) {
   443      * Check that a series of strings are found in order in a file in
   384                 System.err.println(e);
   444      * the generated output.
   385             }
   445      * @param path the file to check
   386         }
   446      * @param strings  the strings whose order to check
   387     }
   447      */
   388 
   448     public void checkOrder(String path, String... strings) {
   389     /**
   449         String fileString = readOutputFile(path);
   390      * Iterate through the list of given file pairs and diff each file.
   450         int prevIndex = -1;
       
   451         for (String s : strings) {
       
   452             int currentIndex = fileString.indexOf(s);
       
   453             checking(s + " at index " + currentIndex);
       
   454             if (currentIndex >= prevIndex) {
       
   455                 passed(s + "is in the correct order");
       
   456             } else {
       
   457                 failed(s + " is in the wrong order.");
       
   458             }
       
   459             prevIndex = currentIndex;
       
   460         }
       
   461     }
       
   462 
       
   463     /**
       
   464      * Compare a set of files in each of two directories.
   391      *
   465      *
   392      * @param baseDir1 the directory containing the first set of files
   466      * @param baseDir1 the directory containing the first set of files
   393      * @param baseDir2 the directory containing the second set of files
   467      * @param baseDir2 the directory containing the second set of files
   394      * @param files the set of files to be compared
   468      * @param files the set of files to be compared
   395      * @throws Error if any differences are found between
   469      */
   396      * file pairs.
   470     public void diff(String baseDir1, String baseDir2, String... files) {
   397      */
       
   398     public void runDiffs(String baseDir1, String baseDir2, String[] files) throws Error {
       
   399         runDiffs(baseDir1, baseDir2, files, true);
       
   400     }
       
   401 
       
   402     /**
       
   403      * Iterate through the list of given file pairs and diff each file.
       
   404      *
       
   405      * @param baseDir1 the directory containing the first set of files
       
   406      * @param baseDir2 the directory containing the second set of files
       
   407      * @param files the set of files to be compared
       
   408      * @param throwErrorIfNoMatch flag to indicate whether or not to throw
       
   409      * an error if the files do not match.
       
   410      *
       
   411      * @throws Error if any differences are found between
       
   412      * file pairs and throwErrorIfNoMatch is true.
       
   413      */
       
   414     public void runDiffs(String baseDir1, String baseDir2, String[] files, boolean throwErrorIfNoMatch) throws Error {
       
   415         File bd1 = new File(baseDir1);
   471         File bd1 = new File(baseDir1);
   416         File bd2 = new File(baseDir2);
   472         File bd2 = new File(baseDir2);
   417         for (String file : files) {
   473         for (String file : files) {
   418             diff(bd1, bd2, file, throwErrorIfNoMatch);
   474             diff(bd1, bd2, file);
   419         }
   475         }
   420     }
   476     }
   421 
   477 
   422     /**
   478     /**
   423      * Check the exit code of Javadoc and record whether the test passed
   479      * A utility to copy a directory from one place to another.
   424      * or failed.
   480      *
   425      *
   481      * @param targetDir the directory to copy.
   426      * @param expectedExitCode The exit code that is required for the test
   482      * @param destDir the destination to copy the directory to.
   427      * to pass.
   483      */
   428      * @param actualExitCode The actual exit code from the previous run of
   484     // TODO: convert to using java.nio.Files.walkFileTree
   429      * Javadoc.
   485     public void copyDir(String targetDir, String destDir) {
   430      */
   486         try {
   431     public void checkExitCode(int expectedExitCode, int actualExitCode) {
   487             File targetDirObj = new File(targetDir);
   432         numTestsRun++;
   488             File destDirParentObj = new File(destDir);
   433         if (expectedExitCode == actualExitCode) {
   489             File destDirObj = new File(destDirParentObj, targetDirObj.getName());
   434             System.out.println( "Passed" + "\n" + " got return code " +
   490             if (! destDirParentObj.exists()) {
   435                 actualExitCode);
   491                 destDirParentObj.mkdir();
   436             numTestsPassed++;
   492             }
   437         } else {
   493             if (! destDirObj.exists()) {
   438             System.out.println( "FAILED: expected return code " +
   494                 destDirObj.mkdir();
   439                 expectedExitCode + " but got " + actualExitCode);
   495             }
   440         }
   496             String[] files = targetDirObj.list();
   441     }
   497             for (String file : files) {
   442 
   498                 File srcFile = new File(targetDirObj, file);
   443     /**
   499                 File destFile = new File(destDirObj, file);
   444      * Print a summary of the test results.
   500                 if (srcFile.isFile()) {
   445      */
   501                     out.println("Copying " + srcFile + " to " + destFile);
   446     protected void printSummary() {
   502                     copyFile(destFile, srcFile);
   447         if ( numTestsRun != 0 && numTestsPassed == numTestsRun ) {
   503                 } else if(srcFile.isDirectory()) {
   448             // Test passed
   504                     copyDir(srcFile.getAbsolutePath(), destDirObj.getAbsolutePath());
   449             System.out.println("\n" + "All " + numTestsPassed
   505                 }
   450                                              + " subtests passed");
   506             }
   451         } else {
   507         } catch (IOException exc) {
   452             // Test failed
   508             throw new Error("Could not copy " + targetDir + " to " + destDir);
   453             throw new Error("\n" + (numTestsRun - numTestsPassed)
   509         }
   454                                     + " of " + (numTestsRun)
   510     }
   455                                     + " subtests failed\n");
   511 
   456         }
   512     /**
   457     }
   513      * Copy source file to destination file.
   458 
   514      *
   459     /**
   515      * @param destfile the destination file
   460      * Print the output stored in the buffers.
   516      * @param srcfile the source file
   461      */
   517      * @throws IOException
   462     protected void printJavadocOutput() {
   518      */
   463         System.out.println(STANDARD_OUTPUT + " : \n" + getStandardOutput());
   519     public void copyFile(File destfile, File srcfile) throws IOException {
   464         System.err.println(ERROR_OUTPUT + " : \n" + getErrorOutput());
   520         Files.copy(srcfile.toPath(), destfile.toPath());
   465         System.err.println(WARNING_OUTPUT + " : \n" + getWarningOutput());
   521     }
   466         System.out.println(NOTICE_OUTPUT + " : \n" + getNoticeOutput());
   522 
   467     }
   523     /**
   468 
   524      * Read a file from the output directory.
   469     /**
       
   470      * Read the file and return it as a string.
       
   471      *
   525      *
   472      * @param fileName  the name of the file to read
   526      * @param fileName  the name of the file to read
   473      * @return          the file in string format
   527      * @return          the file in string format
   474      */
   528      */
   475     public String readFileToString(String fileName) throws Error {
   529     public String readOutputFile(String fileName) throws Error {
   476         return readFileToString(outputDir, fileName);
   530         return readFile(outputDir, fileName);
       
   531     }
       
   532 
       
   533     protected String readFile(String fileName) throws Error {
       
   534         return readFile(outputDir, fileName);
       
   535     }
       
   536 
       
   537     protected String readFile(String baseDir, String fileName) throws Error {
       
   538         return readFile(new File(baseDir), fileName);
   477     }
   539     }
   478 
   540 
   479     /**
   541     /**
   480      * Read the file and return it as a string.
   542      * Read the file and return it as a string.
   481      *
   543      *
   482      * @param baseDir   the directory in which to locate the file
   544      * @param baseDir   the directory in which to locate the file
   483      * @param fileName  the name of the file to read
   545      * @param fileName  the name of the file to read
   484      * @return          the file in string format
   546      * @return          the file in string format
   485      */
   547      */
   486     private String readFileToString(File baseDir, String fileName) throws Error {
   548     private String readFile(File baseDir, String fileName) throws Error {
   487         switch (fileName) {
       
   488             case ERROR_OUTPUT:
       
   489                 return getErrorOutput();
       
   490             case NOTICE_OUTPUT:
       
   491                 return getNoticeOutput();
       
   492             case WARNING_OUTPUT:
       
   493                 return getWarningOutput();
       
   494             case STANDARD_OUTPUT:
       
   495                 return getStandardOutput();
       
   496         }
       
   497         try {
   549         try {
   498             File file = new File(baseDir, fileName);
   550             File file = new File(baseDir, fileName);
   499             if ( !file.exists() ) {
   551             SoftReference<String> ref = fileContentCache.get(file);
   500                 System.out.println("\n" + "FILE DOES NOT EXIST: " + fileName);
   552             String content = (ref == null) ? null : ref.get();
   501             }
   553             if (content != null)
   502             char[] allChars;
   554                 return content;
   503             try (BufferedReader in = new BufferedReader(new FileReader(file))) {
   555 
   504                 // Create an array of characters the size of the file
   556             content = new String(Files.readAllBytes(file.toPath()));
   505                 allChars = new char[(int)file.length()];
   557             fileContentCache.put(file, new SoftReference(content));
   506                 // Read the characters into the allChars array
   558             return content;
   507                 in.read(allChars, 0, (int)file.length());
       
   508             }
       
   509 
       
   510             // Convert to a string
       
   511             String allCharsString = new String(allChars);
       
   512             return allCharsString;
       
   513         } catch (FileNotFoundException e) {
   559         } catch (FileNotFoundException e) {
   514             System.err.println(e);
   560             System.err.println(e);
   515             throw new Error("File not found: " + fileName);
   561             throw new Error("File not found: " + fileName);
   516         } catch (IOException e) {
   562         } catch (IOException e) {
   517             System.err.println(e);
   563             System.err.println(e);
   518             throw new Error("Error reading file: " + fileName);
   564             throw new Error("Error reading file: " + fileName);
   519         }
   565         }
   520     }
   566     }
   521 
   567 
       
   568     protected void checking(String message) {
       
   569         numTestsRun++;
       
   570         print("Starting subtest " + numTestsRun, message);
       
   571     }
       
   572 
       
   573     protected void passed(String message) {
       
   574         numTestsPassed++;
       
   575         print("Passed", message);
       
   576     }
       
   577 
       
   578     protected void failed(String message) {
       
   579         print("FAILED", message);
       
   580     }
       
   581 
       
   582     private void print(String prefix, String message) {
       
   583         if (message.isEmpty())
       
   584             out.println(prefix);
       
   585         else {
       
   586             out.print(prefix);
       
   587             out.print(": ");
       
   588             out.println(message.replace("\n", NL));
       
   589         }
       
   590     }
       
   591 
       
   592     /**
       
   593      * Print a summary of the test results.
       
   594      */
       
   595     protected void printSummary() {
       
   596 //        log.write();
       
   597         if (numTestsRun != 0 && numTestsPassed == numTestsRun) {
       
   598             // Test passed
       
   599             out.println();
       
   600             out.println("All " + numTestsPassed + " subtests passed");
       
   601         } else {
       
   602             // Test failed
       
   603             throw new Error((numTestsRun - numTestsPassed)
       
   604                     + " of " + (numTestsRun)
       
   605                     + " subtests failed");
       
   606         }
       
   607     }
       
   608 
       
   609     /**
       
   610      * Search for the string in the given file and return true
       
   611      * if the string was found.
       
   612      *
       
   613      * @param fileString    the contents of the file to search through
       
   614      * @param stringToFind  the string to search for
       
   615      * @return              true if the string was found
       
   616      */
       
   617     private boolean findString(String fileString, String stringToFind) {
       
   618         // javadoc (should) always use the platform newline sequence,
       
   619         // but in the strings to find it is more convenient to use the Java
       
   620         // newline character. So we translate \n to NL before we search.
       
   621         stringToFind = stringToFind.replace("\n", NL);
       
   622         return fileString.contains(stringToFind);
       
   623     }
       
   624 
   522     /**
   625     /**
   523      * Compare the two given files.
   626      * Compare the two given files.
   524      *
   627      *
   525      * @param baseDir1 the directory in which to locate the first file
   628      * @param baseDir1 the directory in which to locate the first file
   526      * @param baseDir2 the directory in which to locate the second file
   629      * @param baseDir2 the directory in which to locate the second file
   527      * @param file the file to compare in the two base directories
   630      * @param file the file to compare in the two base directories
   528      * @param throwErrorIFNoMatch flag to indicate whether or not to throw
   631      * @param throwErrorIFNoMatch flag to indicate whether or not to throw
   529      * an error if the files do not match.
   632      * an error if the files do not match.
   530      * @return true if the files are the same and false otherwise.
   633      * @return true if the files are the same and false otherwise.
   531      */
   634      */
   532     private boolean diff(File baseDir1, File baseDir2, String file,
   635     private void diff(File baseDir1, File baseDir2, String file) {
   533             boolean throwErrorIFNoMatch) throws Error {
   636         String file1Contents = readFile(baseDir1, file);
   534         String file1Contents = readFileToString(baseDir1, file);
   637         String file2Contents = readFile(baseDir2, file);
   535         String file2Contents = readFileToString(baseDir2, file);
   638         checking("diff " + new File(baseDir1, file) + ", " + new File(baseDir2, file));
   536         numTestsRun++;
       
   537         if (file1Contents.trim().compareTo(file2Contents.trim()) == 0) {
   639         if (file1Contents.trim().compareTo(file2Contents.trim()) == 0) {
   538             System.out.println("Diff successful: " + new File(baseDir1, file) + ", " + new File(baseDir2, file));
   640             passed("files are equal");
   539             numTestsPassed++;
       
   540             return true;
       
   541         } else if (throwErrorIFNoMatch) {
       
   542             throw new Error("Diff failed: " + new File(baseDir1, file) + ", " + new File(baseDir2, file));
       
   543         } else {
   641         } else {
   544             return false;
   642             failed("files differ");
   545         }
   643         }
   546     }
   644     }
   547 
   645 
   548     /**
   646     /**
   549      * Search for the string in the given file and return true
   647      * Utility class to simplify the handling of temporarily setting a
   550      * if the string was found.
   648      * new stream for System.out or System.err.
   551      *
   649      */
   552      * @param fileString    the contents of the file to search through
   650     private static class StreamOutput {
   553      * @param stringToFind  the string to search for
   651         // functional interface to set a stream.
   554      * @return              true if the string was found
   652         private interface Initializer {
   555      */
   653             void set(PrintStream s);
   556     private boolean findString(String fileString, String stringToFind) {
   654         }
   557         return fileString.contains(stringToFind.replace("\n", NL));
   655 
   558     }
   656         private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
   559 
   657         private final PrintStream ps = new PrintStream(baos);
   560 
   658         private final PrintStream prev;
   561     /**
   659         private final Initializer init;
   562      * Return the standard output.
   660 
   563      * @return the standard output
   661         StreamOutput(PrintStream s, Initializer init) {
   564      */
   662             prev = s;
   565     public String getStandardOutput() {
   663             init.set(ps);
   566         return standardOut.toString();
   664             this.init = init;
   567     }
   665         }
   568 
   666 
   569     /**
   667         String close() {
   570      * Return the error output.
   668             init.set(prev);
   571      * @return the error output
   669             ps.close();
   572      */
   670             return baos.toString();
   573     public String getErrorOutput() {
   671         }
   574         return errors.getBuffer().toString();
   672     }
   575     }
   673 
   576 
   674     /**
   577     /**
   675      * Utility class to simplify the handling of creating an in-memory PrintWriter.
   578      * Return the notice output.
   676      */
   579      * @return the notice output
   677     private static class WriterOutput {
   580      */
   678         private final StringWriter sw = new StringWriter();
   581     public String getNoticeOutput() {
   679         final PrintWriter pw = new PrintWriter(sw);
   582         return notices.getBuffer().toString();
   680         String close() {
   583     }
   681             pw.close();
   584 
   682             return sw.toString();
   585     /**
   683         }
   586      * Return the warning output.
   684     }
   587      * @return the warning output
   685 
   588      */
   686 
   589     public String getWarningOutput() {
   687 //    private final Logger log = new Logger();
   590         return warnings.getBuffer().toString();
   688 
   591     }
   689     //--------- Logging --------------------------------------------------------
   592 
   690     //
   593     /**
   691     // This class writes out the details of calls to checkOutput and checkFile
   594      * A utility to copy a directory from one place to another.
   692     // in a canonical way, so that the resulting file can be checked against
   595      * We may possibly want to move this to our doclet toolkit in
   693     // similar files from other versions of JavadocTester using the same logging
   596      * the near future and maintain it from there.
   694     // facilities.
   597      *
   695 
   598      * @param targetDir the directory to copy.
   696     static class Logger {
   599      * @param destDir the destination to copy the directory to.
   697         private static final int PREFIX = 40;
   600      */
   698         private static final int SUFFIX = 20;
   601     public static void copyDir(String targetDir, String destDir) {
   699         private static final int MAX = PREFIX + SUFFIX;
   602         if (targetDir.endsWith("SCCS")) {
   700         List<String> tests = new ArrayList<>();
   603             return;
   701         String outDir;
   604         }
   702         String rootDir = rootDir();
   605         try {
   703 
   606             File targetDirObj = new File(targetDir);
   704         static String rootDir() {
   607             File destDirParentObj = new File(destDir);
   705             File f = new File(".").getAbsoluteFile();
   608             File destDirObj = new File(destDirParentObj, targetDirObj.getName());
   706             while (!new File(f, ".hg").exists())
   609             if (! destDirParentObj.exists()) {
   707                 f = f.getParentFile();
   610                 destDirParentObj.mkdir();
   708             return f.getPath();
   611             }
   709         }
   612             if (! destDirObj.exists()) {
   710 
   613                 destDirObj.mkdir();
   711         void setOutDir(File outDir) {
   614             }
   712             this.outDir = outDir.getPath();
   615             String[] files = targetDirObj.list();
   713         }
   616             for (String file : files) {
   714 
   617                 File srcFile = new File(targetDirObj, file);
   715         void logCheckFile(String file, boolean positive) {
   618                 File destFile = new File(destDirObj, file);
   716             // Strip the outdir because that will typically not be the same
   619                 if (srcFile.isFile()) {
   717             if (file.startsWith(outDir + "/"))
   620                     System.out.println("Copying " + srcFile + " to " + destFile);
   718                 file = file.substring(outDir.length() + 1);
   621                     copyFile(destFile, srcFile);
   719             tests.add(file + " " + positive);
   622                 } else if(srcFile.isDirectory()) {
   720         }
   623                     copyDir(srcFile.getAbsolutePath(), destDirObj.getAbsolutePath());
   721 
       
   722         void logCheckOutput(String file, boolean positive, String text) {
       
   723             // Compress the string to be displayed in the log file
       
   724             String simpleText = text.replaceAll("\\s+", " ").replace(rootDir, "[ROOT]");
       
   725             if (simpleText.length() > MAX)
       
   726                 simpleText = simpleText.substring(0, PREFIX)
       
   727                         + "..." + simpleText.substring(simpleText.length() - SUFFIX);
       
   728             // Strip the outdir because that will typically not be the same
       
   729             if (file.startsWith(outDir + "/"))
       
   730                 file = file.substring(outDir.length() + 1);
       
   731             // The use of text.hashCode ensure that all of "text" is taken into account
       
   732             tests.add(file + " " + positive + " " + text.hashCode() + " " + simpleText);
       
   733         }
       
   734 
       
   735         void write() {
       
   736             // sort the log entries because the subtests may not be executed in the same order
       
   737             tests.sort((a, b) -> a.compareTo(b));
       
   738             try (BufferedWriter bw = new BufferedWriter(new FileWriter("tester.log"))) {
       
   739                 for (String t: tests) {
       
   740                     bw.write(t);
       
   741                     bw.newLine();
   624                 }
   742                 }
   625             }
   743             } catch (IOException e) {
   626         } catch (IOException exc) {
   744                 throw new Error("problem writing log: " + e);
   627             throw new Error("Could not copy " + targetDir + " to " + destDir);
   745             }
   628         }
       
   629     }
       
   630 
       
   631     /**
       
   632      * Copy source file to destination file.
       
   633      *
       
   634      * @param destfile the destination file
       
   635      * @param srcfile the source file
       
   636      * @throws SecurityException
       
   637      * @throws IOException
       
   638      */
       
   639     public static void copyFile(File destfile, File srcfile)
       
   640         throws IOException {
       
   641         byte[] bytearr = new byte[512];
       
   642         int len;
       
   643         FileInputStream input = new FileInputStream(srcfile);
       
   644         File destDir = destfile.getParentFile();
       
   645         destDir.mkdirs();
       
   646         FileOutputStream output = new FileOutputStream(destfile);
       
   647         try {
       
   648             while ((len = input.read(bytearr)) != -1) {
       
   649                 output.write(bytearr, 0, len);
       
   650             }
       
   651         } catch (FileNotFoundException | SecurityException exc) {
       
   652         } finally {
       
   653             input.close();
       
   654             output.close();
       
   655         }
   746         }
   656     }
   747     }
   657 }
   748 }