1 /*
   2  * Copyright (c) 2002, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  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
  21  * questions.
  22  */
  23 
  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.Arrays;
  42 import java.util.ArrayList;
  43 import java.util.Collection;
  44 import java.util.Collections;
  45 import java.util.EnumMap;
  46 import java.util.HashMap;
  47 import java.util.List;
  48 import java.util.Map;
  49 import java.util.function.Function;
  50 
  51 
  52 /**
  53  * Test framework for running javadoc and performing tests on the resulting output.
  54  *
  55  * <p>
  56  * Tests are typically written as subtypes of JavadocTester, with a main
  57  * method that creates an instance of the test class and calls the runTests()
  58  * method. The runTests() methods calls all the test methods declared in the class,
  59  * and then calls a method to print a summary, and throw an exception if
  60  * any of the test methods reported a failure.
  61  *
  62  * <p>
  63  * Test methods are identified with a @Test annotation. They have no parameters.
  64  * The name of the method is not important, but if you have more than one, it is
  65  * recommended that the names be meaningful and suggestive of the test case
  66  * contained therein.
  67  *
  68  * <p>
  69  * Typically, a test method will invoke javadoc, and then perform various
  70  * checks on the results. The standard checks are:
  71  *
  72  * <dl>
  73  * <dt>checkExitCode
  74  * <dd>Check the exit code returned from javadoc.
  75  * <dt>checkOutput
  76  * <dd>Perform a series of checks on the contents on a file or output stream
  77  *     generated by javadoc.
  78  *     The checks can be either that a series of strings are found or are not found.
  79  * <dt>checkFiles
  80  * <dd>Perform a series of checks on the files generated by javadoc.
  81  *     The checks can be that a series of files are found or are not found.
  82  * </dl>
  83  *
  84  * <pre><code>
  85  *  public class MyTester extends JavadocTester {
  86  *      public static void main(String... args) throws Exception {
  87  *          MyTester tester = new MyTester();
  88  *          tester.runTests();
  89  *      }
  90  *
  91  *      // test methods...
  92  *      @Test
  93  *      void test() {
  94  *          javadoc(<i>args</i>);
  95  *          checkExit(Exit.OK);
  96  *          checkOutput(<i>file</i>, true,
  97  *              <i>strings-to-find</i>);
  98  *          checkOutput(<i>file</i>, false,
  99  *              <i>strings-to-not-find</i>);
 100  *      }
 101  *  }
 102  * </code></pre>
 103  *
 104  * <p>
 105  * If javadoc is run more than once in a test method, you can compare the
 106  * results that are generated with the diff method. Since files written by
 107  * javadoc typically contain a timestamp, you may want to use the -notimestamp
 108  * option if you are going to compare the results from two runs of javadoc.
 109  *
 110  * <p>
 111  * If you have many calls of checkOutput that are very similar, you can write
 112  * your own check... method to reduce the amount of duplication. For example,
 113  * if you want to check that many files contain the same string, you could
 114  * write a method that takes a varargs list of files and calls checkOutput
 115  * on each file in turn with the string to be checked.
 116  *
 117  * <p>
 118  * You can also write you own custom check methods, which can use
 119  * readFile to get the contents of a file generated by javadoc,
 120  * and then use pass(...) or fail(...) to report whether the check
 121  * succeeded or not.
 122  *
 123  * <p>
 124  * You can have many separate test methods, each identified with a @Test
 125  * annotation. However, you should <b>not</b> assume they will be called
 126  * in the order declared in your source file.  If the order of a series
 127  * of javadoc invocations is important, do that within a single method.
 128  * If the invocations are independent, for better clarity, use separate
 129  * test methods, each with their own set of checks on the results.
 130  *
 131  * @author Doug Kramer
 132  * @author Jamie Ho
 133  * @author Jonathan Gibbons (rewrite)
 134  */
 135 public abstract class JavadocTester {
 136 
 137     public static final String FS = System.getProperty("file.separator");
 138     public static final String PS = System.getProperty("path.separator");
 139     public static final String NL = System.getProperty("line.separator");
 140 
 141     public enum Output {
 142         /** The name of the output stream from javadoc. */
 143         OUT,
 144         /** The name for any output written to System.out. */
 145         STDOUT,
 146         /** The name for any output written to System.err. */
 147         STDERR
 148     }
 149 
 150     /** The output directory used in the most recent call of javadoc. */
 151     protected File outputDir;
 152 
 153     /** The exit code of the most recent call of javadoc. */
 154     private int exitCode;
 155 
 156     /** The output generated by javadoc to the various writers and streams. */
 157     private final Map<Output, String> outputMap = new EnumMap<>(Output.class);
 158 
 159     /** A cache of file content, to avoid reading files unnecessarily. */
 160     private final Map<File,SoftReference<String>> fileContentCache = new HashMap<>();
 161 
 162     /** Stream used for logging messages. */
 163     protected final PrintStream out = System.out;
 164 
 165     /** The directory containing the source code for the test. */
 166     public static final String testSrc = System.getProperty("test.src");
 167 
 168     /**
 169      * Get the path for a source file in the test source directory.
 170      * @param path the path of a file or directory in the source directory
 171      * @return the full path of the specified file
 172      */
 173     public static String testSrc(String path) {
 174         return new File(testSrc, path).getPath();
 175     }
 176 
 177     /**
 178      * Alternatives for checking the contents of a directory.
 179      */
 180     public enum DirectoryCheck {
 181         /**
 182          * Check that the directory is empty.
 183          */
 184         EMPTY((file, name) -> true),
 185         /**
 186          * Check that the directory does not contain any HTML files,
 187          * such as may have been generated by a prior run of javadoc
 188          * using this directory.
 189          * For now, the check is only performed on the top level directory.
 190          */
 191         NO_HTML_FILES((file, name) -> name.endsWith(".html")),
 192         /**
 193          * No check is performed on the directory contents.
 194          */
 195         NONE(null) { @Override void check(File dir) { } };
 196 
 197         /** The filter used to detect that files should <i>not</i> be present. */
 198         FilenameFilter filter;
 199 
 200         DirectoryCheck(FilenameFilter f) {
 201             filter = f;
 202         }
 203 
 204         void check(File dir) {
 205             if (dir.isDirectory()) {
 206                 String[] contents = dir.list(filter);
 207                 if (contents == null)
 208                     throw new Error("cannot list directory: " + dir);
 209                 if (contents.length > 0) {
 210                     System.err.println("Found extraneous files in dir:" + dir.getAbsolutePath());
 211                     for (String x : contents) {
 212                         System.err.println(x);
 213                     }
 214                     throw new Error("directory has unexpected content: " + dir);
 215                 }
 216             }
 217         }
 218     }
 219 
 220     private DirectoryCheck outputDirectoryCheck = DirectoryCheck.EMPTY;
 221 
 222     /** The current subtest number. Incremented when checking(...) is called. */
 223     private int numTestsRun = 0;
 224 
 225     /** The number of subtests passed. Incremented when passed(...) is called. */
 226     private int numTestsPassed = 0;
 227 
 228     /** The current run of javadoc. Incremented when javadoc is called. */
 229     private int javadocRunNum = 0;
 230 
 231     /** Marker annotation for test methods to be invoked by runTests. */
 232     @Retention(RetentionPolicy.RUNTIME)
 233     @interface Test { }
 234 
 235     /**
 236      * Run all methods annotated with @Test, followed by printSummary.
 237      * Typically called on a tester object in main()
 238      * @throws Exception if any errors occurred
 239      */
 240     public void runTests() throws Exception {
 241         runTests(m -> new Object[0]);
 242     }
 243 
 244     /**
 245      * Run all methods annotated with @Test, followed by printSummary.
 246      * Typically called on a tester object in main()
 247      * @param f a function which will be used to provide arguments to each
 248      *          invoked method
 249      * @throws Exception if any errors occurred
 250      */
 251     public void runTests(Function<Method, Object[]> f) throws Exception {
 252         for (Method m: getClass().getDeclaredMethods()) {
 253             Annotation a = m.getAnnotation(Test.class);
 254             if (a != null) {
 255                 try {
 256                     out.println("Running test " + m.getName());
 257                     m.invoke(this, f.apply(m));
 258                 } catch (InvocationTargetException e) {
 259                     Throwable cause = e.getCause();
 260                     throw (cause instanceof Exception) ? ((Exception) cause) : e;
 261                 }
 262                 out.println();
 263             }
 264         }
 265         printSummary();
 266     }
 267 
 268     /**
 269      * Run javadoc.
 270      * The output directory used by this call and the final exit code
 271      * will be saved for later use.
 272      * To aid the reader, it is recommended that calls to this method
 273      * put each option and the arguments it takes on a separate line.
 274      *
 275      * Example:
 276      * <pre><code>
 277      *  javadoc("-d", "out",
 278      *          "-sourcepath", testSrc,
 279      *          "-notimestamp",
 280      *          "pkg1", "pkg2", "pkg3/C.java");
 281      * </code></pre>
 282      *
 283      * @param args the arguments to pass to javadoc
 284      */
 285     public void javadoc(String... args) {
 286         outputMap.clear();
 287         fileContentCache.clear();
 288 
 289         javadocRunNum++;
 290         if (javadocRunNum == 1) {
 291             out.println("Running javadoc...");
 292         } else {
 293             out.println("Running javadoc (run "
 294                                     + javadocRunNum + ")...");
 295         }
 296         outputDir = new File(".");
 297         for (int i = 0; i < args.length - 2; i++) {
 298             if (args[i].equals("-d")) {
 299                 outputDir = new File(args[++i]);
 300                 break;
 301             }
 302         }
 303         out.println("args: " + Arrays.toString(args));
 304 //        log.setOutDir(outputDir);
 305 
 306         outputDirectoryCheck.check(outputDir);
 307 
 308         // This is the sole stream used by javadoc
 309         WriterOutput outOut = new WriterOutput();
 310 
 311         // These are to catch output to System.out and System.err,
 312         // in case these are used instead of the primary streams
 313         StreamOutput sysOut = new StreamOutput(System.out, System::setOut);
 314         StreamOutput sysErr = new StreamOutput(System.err, System::setErr);
 315 
 316         try {
 317             exitCode = jdk.javadoc.internal.tool.Main.execute(args, outOut.pw);
 318         } finally {
 319             outputMap.put(Output.STDOUT, sysOut.close());
 320             outputMap.put(Output.STDERR, sysErr.close());
 321             outputMap.put(Output.OUT, outOut.close());
 322         }
 323 
 324         outputMap.forEach((name, text) -> {
 325             if (!text.isEmpty()) {
 326                 out.println("javadoc " + name + ":");
 327                 out.println(text);
 328             }
 329         });
 330     }
 331 
 332     /**
 333      * Set the kind of check for the initial contents of the output directory
 334      * before javadoc is run.
 335      * The filter should return true for files that should <b>not</b> appear.
 336      * @param c the kind of check to perform
 337      */
 338     public void setOutputDirectoryCheck(DirectoryCheck c) {
 339         outputDirectoryCheck = c;
 340     }
 341 
 342     /**
 343      * The exit codes returned by the javadoc tool.
 344      * @see jdk.javadoc.internal.tool.Main.Result
 345      */
 346     public enum Exit {
 347         OK(0),        // Javadoc completed with no errors.
 348         ERROR(1),     // Completed but reported errors.
 349         CMDERR(2),    // Bad command-line arguments
 350         SYSERR(3),    // System error or resource exhaustion.
 351         ABNORMAL(4);  // Javadoc terminated abnormally
 352 
 353         Exit(int code) {
 354             this.code = code;
 355         }
 356 
 357         final int code;
 358 
 359         @Override
 360         public String toString() {
 361             return name() + '(' + code + ')';
 362         }
 363     }
 364 
 365     /**
 366      * Check the exit code of the most recent call of javadoc.
 367      *
 368      * @param expected the exit code that is required for the test
 369      * to pass.
 370      */
 371     public void checkExit(Exit expected) {
 372         checking("check exit code");
 373         if (exitCode == expected.code) {
 374             passed("return code " + exitCode);
 375         } else {
 376             failed("return code " + exitCode +"; expected " + expected);
 377         }
 378     }
 379 
 380     /**
 381      * Check for content in (or not in) the generated output.
 382      * Within the search strings, the newline character \n
 383      * will be translated to the platform newline character sequence.
 384      * @param path a path within the most recent output directory
 385      *  or the name of one of the output buffers, identifying
 386      *  where to look for the search strings.
 387      * @param expectedFound true if all of the search strings are expected
 388      *  to be found, or false if the file is not expected to be found
 389      * @param strings the strings to be searched for
 390      */
 391     public void checkFileAndOutput(String path, boolean expectedFound, String... strings) {
 392         if (expectedFound) {
 393             checkOutput(path, true, strings);
 394         } else {
 395             checkFiles(false, path);
 396         }
 397     }
 398 
 399     /**
 400      * Check for content in (or not in) the generated output.
 401      * Within the search strings, the newline character \n
 402      * will be translated to the platform newline character sequence.
 403      * @param path a path within the most recent output directory
 404      *  or the name of one of the output buffers, identifying
 405      *  where to look for the search strings.
 406      * @param expectedFound true if all of the search strings are expected
 407      *  to be found, or false if all of the strings are expected to be
 408      *  not found
 409      * @param strings the strings to be searched for
 410      */
 411     public void checkOutput(String path, boolean expectedFound, String... strings) {
 412         // Read contents of file
 413         String fileString;
 414         try {
 415             fileString = readFile(outputDir, path);
 416         } catch (Error e) {
 417             checking("Read file");
 418             failed("Error reading file: " + e);
 419             return;
 420         }
 421         checkOutput(path, fileString, expectedFound, strings);
 422     }
 423 
 424     /**
 425      * Check for content in (or not in) the one of the output streams written by
 426      * javadoc. Within the search strings, the newline character \n
 427      * will be translated to the platform newline character sequence.
 428      * @param output the output stream to check
 429      * @param expectedFound true if all of the search strings are expected
 430      *  to be found, or false if all of the strings are expected to be
 431      *  not found
 432      * @param strings the strings to be searched for
 433      */
 434     public void checkOutput(Output output, boolean expectedFound, String... strings) {
 435         checkOutput(output.toString(), outputMap.get(output), expectedFound, strings);
 436     }
 437 
 438     private void checkOutput(String path, String fileString, boolean expectedFound, String... strings) {
 439         for (String stringToFind : strings) {
 440 //            log.logCheckOutput(path, expectedFound, stringToFind);
 441             checking("checkOutput");
 442             // Find string in file's contents
 443             boolean isFound = findString(fileString, stringToFind);
 444             if (isFound == expectedFound) {
 445                 passed(path + ": following text " + (isFound ? "found:" : "not found:") + "\n"
 446                         + stringToFind + "\n");
 447             } else {
 448                 failed(path + ": following text " + (isFound ? "found:" : "not found:") + "\n"
 449                         + stringToFind + "\n");
 450             }
 451         }
 452     }
 453 
 454     /**
 455      * Get the content of the one of the output streams written by
 456      * javadoc.
 457      */
 458     public String getOutput(Output output) {
 459         return outputMap.get(output);
 460     }
 461 
 462     /**
 463      * Get the content of the one of the output streams written by
 464      * javadoc.
 465      */
 466     public List<String> getOutputLines(Output output) {
 467         String text = outputMap.get(output);
 468         return (text == null) ? Collections.emptyList() : Arrays.asList(text.split(NL));
 469     }
 470 
 471     /**
 472      * Check for files in (or not in) the generated output.
 473      * @param expectedFound true if all of the files are expected
 474      *  to be found, or false if all of the files are expected to be
 475      *  not found
 476      * @param paths the files to check, within the most recent output directory.
 477      * */
 478     public void checkFiles(boolean expectedFound, String... paths) {
 479         checkFiles(expectedFound, Arrays.asList(paths));
 480     }
 481 
 482     /**
 483      * Check for files in (or not in) the generated output.
 484      * @param expectedFound true if all of the files are expected
 485      *  to be found, or false if all of the files are expected to be
 486      *  not found
 487      * @param paths the files to check, within the most recent output directory.
 488      * */
 489     public void checkFiles(boolean expectedFound, Collection<String> paths) {
 490         for (String path: paths) {
 491 //            log.logCheckFile(path, expectedFound);
 492             checking("checkFile");
 493             File file = new File(outputDir, path);
 494             boolean isFound = file.exists();
 495             if (isFound == expectedFound) {
 496                 passed(path + ": file " + (isFound ? "found:" : "not found:") + "\n");
 497             } else {
 498                 failed(path + ": file " + (isFound ? "found:" : "not found:") + "\n");
 499             }
 500         }
 501     }
 502 
 503     /**
 504      * Check that a series of strings are found in order in a file in
 505      * the generated output.
 506      * @param path the file to check
 507      * @param strings  the strings whose order to check
 508      */
 509     public void checkOrder(String path, String... strings) {
 510         String fileString = readOutputFile(path);
 511         int prevIndex = -1;
 512         for (String s : strings) {
 513             s = s.replace("\n", NL); // normalize new lines
 514             int currentIndex = fileString.indexOf(s, prevIndex + 1);
 515             checking(s + " at index " + currentIndex);
 516             if (currentIndex == -1) {
 517                 failed(s + " not found.");
 518                 continue;
 519             }
 520             if (currentIndex > prevIndex) {
 521                 passed(s + " is in the correct order");
 522             } else {
 523                 failed("file: " + path + ": " + s + " is in the wrong order.");
 524             }
 525             prevIndex = currentIndex;
 526         }
 527     }
 528 
 529     /**
 530      * Ensures that a series of strings appear only once, in the generated output,
 531      * noting that, this test does not exhaustively check for all other possible
 532      * duplicates once one is found.
 533      * @param path the file to check
 534      * @param strings ensure each are unique
 535      */
 536     public void checkUnique(String path, String... strings) {
 537         String fileString = readOutputFile(path);
 538         for (String s : strings) {
 539             int currentIndex = fileString.indexOf(s);
 540             checking(s + " at index " + currentIndex);
 541             if (currentIndex == -1) {
 542                 failed(s + " not found.");
 543                 continue;
 544             }
 545             int nextindex = fileString.indexOf(s, currentIndex + s.length());
 546             if (nextindex == -1) {
 547                 passed(s + " is unique");
 548             } else {
 549                 failed(s + " is not unique, found at " + nextindex);
 550             }
 551         }
 552     }
 553 
 554     /**
 555      * Compare a set of files in each of two directories.
 556      *
 557      * @param baseDir1 the directory containing the first set of files
 558      * @param baseDir2 the directory containing the second set of files
 559      * @param files the set of files to be compared
 560      */
 561     public void diff(String baseDir1, String baseDir2, String... files) {
 562         File bd1 = new File(baseDir1);
 563         File bd2 = new File(baseDir2);
 564         for (String file : files) {
 565             diff(bd1, bd2, file);
 566         }
 567     }
 568 
 569     /**
 570      * A utility to copy a directory from one place to another.
 571      *
 572      * @param targetDir the directory to copy.
 573      * @param destDir the destination to copy the directory to.
 574      */
 575     // TODO: convert to using java.nio.Files.walkFileTree
 576     public void copyDir(String targetDir, String destDir) {
 577         try {
 578             File targetDirObj = new File(targetDir);
 579             File destDirParentObj = new File(destDir);
 580             File destDirObj = new File(destDirParentObj, targetDirObj.getName());
 581             if (! destDirParentObj.exists()) {
 582                 destDirParentObj.mkdir();
 583             }
 584             if (! destDirObj.exists()) {
 585                 destDirObj.mkdir();
 586             }
 587             String[] files = targetDirObj.list();
 588             for (String file : files) {
 589                 File srcFile = new File(targetDirObj, file);
 590                 File destFile = new File(destDirObj, file);
 591                 if (srcFile.isFile()) {
 592                     out.println("Copying " + srcFile + " to " + destFile);
 593                     copyFile(destFile, srcFile);
 594                 } else if(srcFile.isDirectory()) {
 595                     copyDir(srcFile.getAbsolutePath(), destDirObj.getAbsolutePath());
 596                 }
 597             }
 598         } catch (IOException exc) {
 599             throw new Error("Could not copy " + targetDir + " to " + destDir);
 600         }
 601     }
 602 
 603     /**
 604      * Copy source file to destination file.
 605      *
 606      * @param destfile the destination file
 607      * @param srcfile the source file
 608      * @throws IOException
 609      */
 610     public void copyFile(File destfile, File srcfile) throws IOException {
 611         Files.copy(srcfile.toPath(), destfile.toPath());
 612     }
 613 
 614     /**
 615      * Read a file from the output directory.
 616      *
 617      * @param fileName  the name of the file to read
 618      * @return          the file in string format
 619      */
 620     public String readOutputFile(String fileName) throws Error {
 621         return readFile(outputDir, fileName);
 622     }
 623 
 624     protected String readFile(String fileName) throws Error {
 625         return readFile(outputDir, fileName);
 626     }
 627 
 628     protected String readFile(String baseDir, String fileName) throws Error {
 629         return readFile(new File(baseDir), fileName);
 630     }
 631 
 632     /**
 633      * Read the file and return it as a string.
 634      *
 635      * @param baseDir   the directory in which to locate the file
 636      * @param fileName  the name of the file to read
 637      * @return          the file in string format
 638      */
 639     private String readFile(File baseDir, String fileName) throws Error {
 640         try {
 641             File file = new File(baseDir, fileName);
 642             SoftReference<String> ref = fileContentCache.get(file);
 643             String content = (ref == null) ? null : ref.get();
 644             if (content != null)
 645                 return content;
 646 
 647             content = new String(Files.readAllBytes(file.toPath()));
 648             fileContentCache.put(file, new SoftReference<>(content));
 649             return content;
 650         } catch (FileNotFoundException e) {
 651             throw new Error("File not found: " + fileName + ": " + e);
 652         } catch (IOException e) {
 653             throw new Error("Error reading file: " + fileName + ": " + e);
 654         }
 655     }
 656 
 657     protected void checking(String message) {
 658         numTestsRun++;
 659         print("Starting subtest " + numTestsRun, message);
 660     }
 661 
 662     protected void passed(String message) {
 663         numTestsPassed++;
 664         print("Passed", message);
 665     }
 666 
 667     protected void failed(String message) {
 668         print("FAILED", message);
 669     }
 670 
 671     private void print(String prefix, String message) {
 672         if (message.isEmpty())
 673             out.println(prefix);
 674         else {
 675             out.print(prefix);
 676             out.print(": ");
 677             out.println(message.replace("\n", NL));
 678         }
 679     }
 680 
 681     /**
 682      * Print a summary of the test results.
 683      */
 684     protected void printSummary() {
 685         String javadocRuns = (javadocRunNum <= 1) ? ""
 686                 : ", in " + javadocRunNum + " runs of javadoc";
 687 
 688         if (numTestsRun != 0 && numTestsPassed == numTestsRun) {
 689             // Test passed
 690             out.println();
 691             out.println("All " + numTestsPassed + " subtests passed" + javadocRuns);
 692         } else {
 693             // Test failed
 694             throw new Error((numTestsRun - numTestsPassed)
 695                     + " of " + (numTestsRun)
 696                     + " subtests failed"
 697                     + javadocRuns);
 698         }
 699     }
 700 
 701     /**
 702      * Search for the string in the given file and return true
 703      * if the string was found.
 704      *
 705      * @param fileString    the contents of the file to search through
 706      * @param stringToFind  the string to search for
 707      * @return              true if the string was found
 708      */
 709     private boolean findString(String fileString, String stringToFind) {
 710         // javadoc (should) always use the platform newline sequence,
 711         // but in the strings to find it is more convenient to use the Java
 712         // newline character. So we translate \n to NL before we search.
 713         stringToFind = stringToFind.replace("\n", NL);
 714         return fileString.contains(stringToFind);
 715     }
 716 
 717     /**
 718      * Compare the two given files.
 719      *
 720      * @param baseDir1 the directory in which to locate the first file
 721      * @param baseDir2 the directory in which to locate the second file
 722      * @param file the file to compare in the two base directories
 723      * @param throwErrorIFNoMatch flag to indicate whether or not to throw
 724      * an error if the files do not match.
 725      * @return true if the files are the same and false otherwise.
 726      */
 727     private void diff(File baseDir1, File baseDir2, String file) {
 728         String file1Contents = readFile(baseDir1, file);
 729         String file2Contents = readFile(baseDir2, file);
 730         checking("diff " + new File(baseDir1, file) + ", " + new File(baseDir2, file));
 731         if (file1Contents.trim().compareTo(file2Contents.trim()) == 0) {
 732             passed("files are equal");
 733         } else {
 734             failed("files differ");
 735         }
 736     }
 737 
 738     /**
 739      * Utility class to simplify the handling of temporarily setting a
 740      * new stream for System.out or System.err.
 741      */
 742     private static class StreamOutput {
 743         // functional interface to set a stream.
 744         private interface Initializer {
 745             void set(PrintStream s);
 746         }
 747 
 748         private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
 749         private final PrintStream ps = new PrintStream(baos);
 750         private final PrintStream prev;
 751         private final Initializer init;
 752 
 753         StreamOutput(PrintStream s, Initializer init) {
 754             prev = s;
 755             init.set(ps);
 756             this.init = init;
 757         }
 758 
 759         String close() {
 760             init.set(prev);
 761             ps.close();
 762             return baos.toString();
 763         }
 764     }
 765 
 766     /**
 767      * Utility class to simplify the handling of creating an in-memory PrintWriter.
 768      */
 769     private static class WriterOutput {
 770         private final StringWriter sw = new StringWriter();
 771         final PrintWriter pw = new PrintWriter(sw);
 772         String close() {
 773             pw.close();
 774             return sw.toString();
 775         }
 776     }
 777 
 778 
 779 //    private final Logger log = new Logger();
 780 
 781     //--------- Logging --------------------------------------------------------
 782     //
 783     // This class writes out the details of calls to checkOutput and checkFile
 784     // in a canonical way, so that the resulting file can be checked against
 785     // similar files from other versions of JavadocTester using the same logging
 786     // facilities.
 787 
 788     static class Logger {
 789         private static final int PREFIX = 40;
 790         private static final int SUFFIX = 20;
 791         private static final int MAX = PREFIX + SUFFIX;
 792         List<String> tests = new ArrayList<>();
 793         String outDir;
 794         String rootDir = rootDir();
 795 
 796         static String rootDir() {
 797             File f = new File(".").getAbsoluteFile();
 798             while (!new File(f, ".hg").exists())
 799                 f = f.getParentFile();
 800             return f.getPath();
 801         }
 802 
 803         void setOutDir(File outDir) {
 804             this.outDir = outDir.getPath();
 805         }
 806 
 807         void logCheckFile(String file, boolean positive) {
 808             // Strip the outdir because that will typically not be the same
 809             if (file.startsWith(outDir + "/"))
 810                 file = file.substring(outDir.length() + 1);
 811             tests.add(file + " " + positive);
 812         }
 813 
 814         void logCheckOutput(String file, boolean positive, String text) {
 815             // Compress the string to be displayed in the log file
 816             String simpleText = text.replaceAll("\\s+", " ").replace(rootDir, "[ROOT]");
 817             if (simpleText.length() > MAX)
 818                 simpleText = simpleText.substring(0, PREFIX)
 819                         + "..." + simpleText.substring(simpleText.length() - SUFFIX);
 820             // Strip the outdir because that will typically not be the same
 821             if (file.startsWith(outDir + "/"))
 822                 file = file.substring(outDir.length() + 1);
 823             // The use of text.hashCode ensure that all of "text" is taken into account
 824             tests.add(file + " " + positive + " " + text.hashCode() + " " + simpleText);
 825         }
 826 
 827         void write() {
 828             // sort the log entries because the subtests may not be executed in the same order
 829             tests.sort((a, b) -> a.compareTo(b));
 830             try (BufferedWriter bw = new BufferedWriter(new FileWriter("tester.log"))) {
 831                 for (String t: tests) {
 832                     bw.write(t);
 833                     bw.newLine();
 834                 }
 835             } catch (IOException e) {
 836                 throw new Error("problem writing log: " + e);
 837             }
 838         }
 839     }
 840 }