8276892: Provide a way to emulate exceptional situations in FileManager when using JavadocTester

Reviewed-by: prappo
This commit is contained in:
Jonathan Gibbons 2021-12-24 01:48:13 +00:00
parent 22c15ddae5
commit d52392c15e
7 changed files with 653 additions and 10 deletions

View File

@ -45,7 +45,7 @@ doclet.exception.write.file=Error writing file: {0}\n\
\t({1})
doclet.exception.read.resource=Error reading system resource: {0}\n\
\t({1})
doclet.internal.exception=An internal exception has occurred. \n\
doclet.internal.exception=An internal exception has occurred.\n\
\t({0})
doclet.internal.report.bug=\
Please file a bug against the javadoc tool via the Java bug reporting page\n\

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -24,7 +24,12 @@
*/
package jdk.javadoc.internal.tool;
import javax.tools.JavaFileManager;
import javax.tools.StandardJavaFileManager;
import java.io.PrintWriter;
import java.util.Objects;
import com.sun.tools.javac.util.Context;
/**
* Provides external entry points (tool and programmatic) for the javadoc program.
@ -37,11 +42,6 @@ import java.io.PrintWriter;
public class Main {
/**
* This constructor should never be called.
*/
private Main() { throw new AssertionError(); }
/**
* The main entry point called by the launcher. This will call
* System.exit with an appropriate return value.
@ -88,6 +88,65 @@ public class Main {
return jdoc.begin(args).exitCode;
}
// builder-style API to run javadoc
private PrintWriter outWriter;
private PrintWriter errWriter;
private StandardJavaFileManager fileManager;
/**
* Creates a default builder to run javadoc.
*/
public Main() { }
/**
* Sets the output and error streams to be used when running javadoc.
* The streams may be the same; they must not be {@code null}.
*
* @param outWriter the output stream
* @param errWriter the error stream
*
* @return this object
*/
public Main setStreams(PrintWriter outWriter, PrintWriter errWriter) {
this.outWriter = Objects.requireNonNull(outWriter);
this.errWriter = Objects.requireNonNull(errWriter);
return this;
}
/**
* Sets the file manager to be used when running javadoc.
* A value of {@code null} means to use the default file manager.
*
* @param fileManager the file manager to use
*
* @return this object
*/
public Main setFileManager(StandardJavaFileManager fileManager) {
this.fileManager = fileManager;
return this;
}
/**
* Runs javadoc with preconfigured values and a given set of arguments.
* Any errors will be reported to the error stream, or to {@link System#err}
* if no error stream has been specified with {@code setStreams}.
*
* @param args the arguments
*
* @return a value indicating the success or otherwise of the run
*/
public Result run(String... args) {
Context context = null;
if (fileManager != null) {
context = new Context();
context.put(JavaFileManager.class, fileManager);
}
Start jdoc = new Start(context, null, outWriter, errWriter, null, null);
return jdoc.begin(args);
}
public enum Result {
/** completed with no errors */
OK(0),

View File

@ -36,6 +36,7 @@ import java.util.Comparator;
import java.util.IllformedLocaleException;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
@ -520,7 +521,21 @@ public class Start {
}
if (fileManager instanceof BaseFileManager bfm) {
// standard file manager: use direct support for handling options
bfm.handleOptions(options.fileManagerOptions());
} else {
// unrecognized file manager:
for (Map.Entry<com.sun.tools.javac.main.Option, String> e: options.fileManagerOptions().entrySet()) {
String optName = e.getKey().getPrimaryName();
String optValue = e.getValue();
try {
if (!fileManager.handleOption(optName, List.of(optValue).iterator())) {
log.error("main.unknown.option.for.filemanager", optName);
}
} catch (IllegalArgumentException ex) {
log.error("main.bad.arg.for.filemanager.option", optName, ex.getMessage());
}
}
}
String mr = com.sun.tools.javac.main.Option.MULTIRELEASE.primaryName;

View File

@ -302,6 +302,8 @@ main.warnings.Werror=warnings found and -Werror specified
main.unknown.error=an unknown error has occurred
main.internal.error=an internal error has occurred
main.unexpected.exception=an unexpected exception was caught: {0}
main.unknown.option.for.filemanager=option not supported by file manager: {0}
main.bad.arg.for.filemanager.option=bad value for file manager option {0}: "{1}"
doclet.internal.report.bug=\
Please file a bug against the javadoc tool via the Java bug reporting page\n\
(http://bugreport.java.com) after checking the Bug Database (http://bugs.java.com)\n\

View File

@ -58,6 +58,7 @@ import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.tools.StandardJavaFileManager;
/**
@ -245,6 +246,7 @@ public abstract class JavadocTester {
private boolean automaticCheckLinks = true;
private boolean automaticCheckUniqueOUT = true;
private boolean useStandardStreams = false;
private StandardJavaFileManager fileManager = null;
/** The current subtest number. Incremented when checking(...) is called. */
private int numTestsRun = 0;
@ -371,9 +373,17 @@ public abstract class JavadocTester {
StreamOutput sysErr = new StreamOutput(System.err, System::setErr);
try {
exitCode = useStandardStreams
? jdk.javadoc.internal.tool.Main.execute(args) // use sysOut, sysErr
: jdk.javadoc.internal.tool.Main.execute(args, outOut.pw); // default
jdk.javadoc.internal.tool.Main main = new jdk.javadoc.internal.tool.Main();
if (useStandardStreams) {
// use sysOut, sysErr
} else {
// default: use single explicit stream
main.setStreams(outOut.pw, outOut.pw);
}
if (fileManager != null) {
main.setFileManager(fileManager);
}
exitCode = main.run(args).exitCode;
} finally {
outputMap.put(Output.STDOUT, sysOut.close());
outputMap.put(Output.STDERR, sysErr.close());
@ -442,6 +452,15 @@ public abstract class JavadocTester {
useStandardStreams = b;
}
/**
* Sets the file manager to use for subsequent invocations of javadoc.
* If {@code null}, a default file manager will be created and used
* for each invocation.
*/
public void setFileManager(StandardJavaFileManager fm) {
fileManager = fm;
}
/**
* The exit codes returned by the javadoc tool.
* @see jdk.javadoc.internal.tool.Main.Result

View File

@ -0,0 +1,306 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javadoc.tester;
import javax.tools.FileObject;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.function.BiFunction;
import java.util.function.Predicate;
/**
* A builder to create "test file managers" that can return "test file objects".
* All such objects can throw user-provided exceptions when specified methods
* are called. This is done by registering "handlers" to be associated with individual
* methods.
*
* The file objects that are returned as "test file objects" are filtered by a predicate
* on the file object.
*
* Note that "test file objects" passed as arguments to methods on the "test file manager"
* that created them are unwrapped, and replaced by the original file object.
* This ensures that the underlying file manager sees the underlying file objects,
* for cases when the identity of the file objects is important.
* However, it does mean that methods on file objects called internally by a
* file manager will not throw any user-provided exceptions.
*
* For now, the handlers for a file object are simply grouped by predicate and then by
* method, and the group of methods used for a "test file object" is determined by the
* first predicate that matches.
* An alternative, more expensive, implementation would be to group the handlers
* by method and predicate and then dynamically build the set of methods to be used for
* a file object by filtering the methods by their applicable predicate.
*/
public class TestJavaFileManagerBuilder {
private final StandardJavaFileManager fm;
private Map<Method, BiFunction<JavaFileManager, Object[], Throwable>> fileManagerHandlers;
private record FileObjectHandlers(Predicate<JavaFileObject> filter,
Map<Method, BiFunction<JavaFileObject, Object[], Throwable>> handlers) { }
private final List<FileObjectHandlers> fileObjectHandlers;
public TestJavaFileManagerBuilder(StandardJavaFileManager fm) {
this.fm = fm;
fileManagerHandlers = Collections.emptyMap();
fileObjectHandlers = new ArrayList<>();
}
/**
* Provides functions to be called when given file manager methods are called.
* The function should either return an exception to be thrown, or {@code null}
* to indicate that no exception should be thrown.
*
* <p>It is an error for any function to return a checked exception that is not
* declared by the method. This error will result in {@link UndeclaredThrowableException}
* being thrown when the method is called.
*
* @param handlers a map giving the function to be called before a file manager method is invoked
*
* @return this object
*
* @throws IllegalArgumentException if any key in the map of handlers is a method that is not
* declared in {@code JavaFileManager}
*/
public TestJavaFileManagerBuilder handle(Map<Method, BiFunction<JavaFileManager, Object[], Throwable>> handlers) {
handlers.forEach((m, h) -> {
if (!JavaFileManager.class.isAssignableFrom(m.getDeclaringClass())) {
throw new IllegalArgumentException(("not a method on JavaFileManager: " + m));
}
});
fileManagerHandlers = handlers;
return this;
}
/**
* Provides functions to be called when given file object methods are called,
* for file objects that match a given predicate.
* The function should either return an exception to be thrown, or {@code null}
* to indicate that no exception should be thrown.
*
* <p>It is an error for the function to return a checked exception that is not
* declared by the method. This error will result in {@link UndeclaredThrowableException}
* being thrown when the method is called.
*
* <p>When subsequently finding the handlers to be used for a particular file object, the various
* predicates passed to this method will be tested in the order that they were registered.
* The handlers associated with the first matching predicate will be used.
*
* @apiNote Examples of predicates include:
* <ul>
* <li>using {@code .equals} or {@link JavaFileObject#isNameCompatible(String, JavaFileObject.Kind)}
* to match a specific file object,
* <li>using string or regular expression operations on the name or URI of the file object,
* <li>using {@code Path} operations on the file object's {@link StandardJavaFileManager#asPath(FileObject) path}.
* </ul>
*
* @param filter the predicate used to identify file objects for which the handlers are applicable
* @param handlers a map giving the function to be called before a file object method is invoked
*
* @return this object
*
* @throws IllegalArgumentException if any key in the map is a method that is not declared in a class
* that is assignable to {@code FileObject}
*/
public TestJavaFileManagerBuilder handle(Predicate<JavaFileObject> filter,
Map<Method, BiFunction<JavaFileObject, Object[], Throwable>> handlers) {
handlers.forEach((m, h) -> {
if (!FileObject.class.isAssignableFrom(m.getDeclaringClass())) {
throw new IllegalArgumentException(("not a method on FileObject: " + m));
}
});
fileObjectHandlers.add(new FileObjectHandlers(filter, handlers));
return this;
}
/**
* {@return a file manager configured with the given handlers}
*/
public StandardJavaFileManager build() {
return (StandardJavaFileManager) Proxy.newProxyInstance(getClass().getClassLoader(),
new Class<?>[] { StandardJavaFileManager.class },
new JavaFileManager_InvocationHandler());
}
/**
* An invocation handler for "test file managers", which provides "test file objects"
* that may be configured to invoke functions to handle selected methods.
*/
private class JavaFileManager_InvocationHandler implements InvocationHandler {
// a cache of "real file object" -> "proxy file object".
Map<JavaFileObject, JavaFileObject> cache = new WeakHashMap<>();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = handleMethod(fm, method, unwrap(args));
if (result instanceof Iterable iterable) {
// All methods on StandardJavaFileManager that return Iterable<T> for some T
// are such that T is one of ? extends [Java]FileObject, ? extends File, ? extends Path.
// If the result is empty, return it unchanged; otherwise check the first
// element to determine the type of the iterable, and if it is an iterable of
// file objects, post-process the result to use proxy file objects where appropriate.
// Note 1: this assumes that no methods return a mixture of FileObject and JavaFileObject.
// Note 2: all file objects returned by the standard file manager are instances of javaFileObject
Iterator<?> iter = iterable.iterator();
if (iter.hasNext() && iter.next() instanceof JavaFileObject) {
List<JavaFileObject> list = new ArrayList<>();
for (JavaFileObject jfo : (Iterable<JavaFileObject>) iterable) {
list.add(wrap(jfo));
}
return list;
} else {
return result;
}
} else if (result instanceof JavaFileObject jfo) {
return wrap(jfo);
} else {
return result;
}
}
/**
* Returns a proxy file object that either calls handler functions for specific methods
* or delegates to an underlying file object.
*
* @param jfo the underlying file object
*
* @return the proxy file object
*/
private JavaFileObject wrap(JavaFileObject jfo) {
return fileObjectHandlers.stream()
.filter(e -> e.filter().test(jfo))
.findFirst()
.map(e -> cache.computeIfAbsent(jfo, jfo_ -> createProxyFileObject(jfo_, e.handlers())))
.orElse(jfo);
}
/**
* Creates a proxy file object that either calls handler functions for specific methods
* or delegates to an underlying file object.
*
* @param jfo the underlying file object
* @param handlers the handlers
*
* @return the proxy file object
*/
private JavaFileObject createProxyFileObject(JavaFileObject jfo,
Map<Method, BiFunction<JavaFileObject, Object[], Throwable>> handlers) {
return (JavaFileObject) Proxy.newProxyInstance(getClass().getClassLoader(),
new Class<?>[] { JavaFileObject.class },
new JavaFileObject_InvocationHandler(jfo, handlers));
}
/**
* {@return an array of objects with any proxy file objects replaced by their underlying
* delegate value}
*
* If there are no proxy objects in the array, the original array is returned.
*
* @param args the array of values
*/
private Object[] unwrap(Object[] args) {
if (!containsProxyFileObject(args)) {
return args;
}
Object[] uArgs = new Object[args.length];
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
uArgs[i] = (Proxy.isProxyClass(arg.getClass())
&& Proxy.getInvocationHandler(arg) instanceof JavaFileObject_InvocationHandler ih)
? ih.jfo
: arg;
}
return uArgs;
}
/**
* {@return {@code true} if an array of objects contains any proxy file objects,
* and {@code false} otherwise}
*
* @param args the array of objects
*/
private boolean containsProxyFileObject(Object[] args) {
for (Object arg : args) {
if (arg != null && Proxy.isProxyClass(arg.getClass())
&& Proxy.getInvocationHandler(arg) instanceof JavaFileObject_InvocationHandler) {
return true;
}
}
return false;
}
private Object handleMethod(JavaFileManager fm, Method method, Object[] args) throws Throwable {
var handler = fileManagerHandlers.get(method);
if (handler != null) {
Throwable t = handler.apply(fm, args);
if (t != null) {
throw t;
}
}
return method.invoke(fm, args);
}
}
/**
* An invocation handler for "test file objects" which can be configured to call functions
* to handle the calls for individual methods.
* It is expected that a common use case is to throw an exception in circumstances that
* would otherwise be hard to create.
*/
private record JavaFileObject_InvocationHandler(JavaFileObject jfo,
Map<Method, BiFunction<JavaFileObject, Object[], Throwable>> handlers)
implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return handleMethod(jfo, method, args);
}
private Object handleMethod(JavaFileObject jfo, Method method, Object[] args) throws Throwable {
var handler = handlers.get(method);
if (handler != null) {
Throwable t = handler.apply(jfo, args);
if (t != null) {
throw t;
}
}
return method.invoke(jfo, args);
}
}
}

View File

@ -0,0 +1,242 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8276892
* @summary Provide a way to emulate exceptional situations in FileManager when using JavadocTester
* @library /tools/lib/ ../lib
* @modules jdk.javadoc/jdk.javadoc.internal.tool
* @build toolbox.ToolBox javadoc.tester.*
* @run main TestTFMBuilder
*/
import javax.tools.DocumentationTool;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javadoc.tester.JavadocTester;
import javadoc.tester.TestJavaFileManagerBuilder;
import toolbox.ToolBox;
/**
* Tests the {@link TestJavaFileManagerBuilder} class.
*
*/
// The use of the contraction TFMBuilder is deliberate, to avoid using
// the confusing but otherwise logical name of TestTestJavaFileManagerBuilder
public class TestTFMBuilder extends JavadocTester {
public static class TestException extends RuntimeException {
TestException(JavaFileObject jfo) {
this(jfo.getName());
}
TestException(String msg) {
super(msg);
}
}
public static void main(String... args) throws Exception {
TestTFMBuilder tester = new TestTFMBuilder();
tester.setup().runTests(m -> new Object[] { Path.of(m.getName()) });
}
private Path srcDir = Path.of("src");
private Class<?> thisClass = TestTFMBuilder.class;
private String thisClassName = thisClass.getName();
TestTFMBuilder setup() throws Exception {
ToolBox tb = new ToolBox();
tb.writeJavaFiles(srcDir, """
package p;
/** Dummy class, to be read by javadoc. {@snippet file="C.properties" } */
public class C {
private C() { }
}""");
tb.writeFile(srcDir.resolve("p").resolve("snippet-files").resolve("C.properties"), """
dummy content
""");
return this;
}
StandardJavaFileManager getFileManager() {
DocumentationTool dt = ToolProvider.getSystemDocumentationTool();
return dt.getStandardFileManager(null, null, null);
}
@Test
public void testSimpleDirectUse(Path base) throws Exception {
try (StandardJavaFileManager fm = getFileManager()) {
fm.setLocation(StandardLocation.SOURCE_PATH, List.of(Path.of(testSrc).toFile()));
// obtain a normal file object from the standard file manager
JavaFileObject someFileObject =
fm.getJavaFileForInput(StandardLocation.SOURCE_PATH, thisClassName, JavaFileObject.Kind.SOURCE);
// build a file manager that throws an exception when someFileObject is read
StandardJavaFileManager tfm = new TestJavaFileManagerBuilder(fm)
.handle(jfo -> jfo.equals(someFileObject),
Map.of(JavaFileObject.class.getMethod("getCharContent", boolean.class),
(fo, args) -> new TestException(fo.getName())))
.build();
// access the "same" file object via the test file manager
JavaFileObject someTestFileObject =
tfm.getJavaFileForInput(StandardLocation.SOURCE_PATH, thisClassName, JavaFileObject.Kind.SOURCE);
checking("non-trapped method");
try {
out.println("someTestFileObject.getName: " + someTestFileObject.getName());
passed("method returned normally, as expected");
} catch (Throwable t) {
failed("method threw unexpected exception: " + t);
}
checking ("trapped method");
try {
someTestFileObject.getCharContent(true);
failed("method returned normally, without throwing an exception");
} catch (TestException e) {
String expect = someFileObject.getName();
String found = e.getMessage();
if (found.equals(expect)) {
passed("method threw exception as expected");
} else {
failed("method throw exception with unexpected message:\n"
+ "expected: " + expect + "\n"
+ " found: " + found);
}
} catch (Throwable t) {
failed("method threw unexpected exception: " + t);
}
}
}
@Test
public void testFileManagerAccess(Path base) throws Exception {
try (StandardJavaFileManager fm = getFileManager()) {
// build a file manager that throws an exception when a specific source file is accessed
Method getFileForInput_method = JavaFileManager.class.getMethod("getFileForInput",
JavaFileManager.Location.class, String.class, String.class);
StandardJavaFileManager tfm = new TestJavaFileManagerBuilder(fm)
.handle(Map.of(getFileForInput_method,
(fm_, args) -> {
var relativeName = (String) args[2];
return (relativeName.endsWith("C.properties"))
? new TestException("getFileForInput: " + Arrays.asList(args))
: null;
}))
.build();
try {
setFileManager(tfm);
javadoc("-d", base.resolve("api").toString(),
"-sourcepath", srcDir.toString(),
"p");
checkExit((Exit.ERROR)); // Ideally, this should be ABNORMAL, but right now, the doclet has no way to indicate that
checkOutput(Output.OUT, true,
"""
error: An internal exception has occurred.
\t(##EXC##: getFileForInput: [SOURCE_PATH, p, snippet-files/C.properties])
1 error"""
.replace("##EXC##", TestException.class.getName()));
} finally {
setFileManager(null);
}
}
}
@Test
public void testFileObjectRead(Path base) throws Exception {
try (StandardJavaFileManager fm = getFileManager()) {
// build a file manager that throws an exception when any *.java is read
StandardJavaFileManager tfm = new TestJavaFileManagerBuilder(fm)
.handle(jfo -> jfo.getName().endsWith(".java"),
Map.of(JavaFileObject.class.getMethod("getCharContent", boolean.class),
(fo, args) -> new TestException(fo.getName())))
.build();
try {
setFileManager(tfm);
javadoc("-d", base.resolve("api").toString(),
"-sourcepath", srcDir.toString(),
"p");
checkExit((Exit.ABNORMAL));
checkOutput(Output.OUT, true,
"""
Loading source files for package p...
error: fatal error encountered: ##EXC##: ##FILE##
error: Please file a bug against the javadoc tool via the Java bug reporting page"""
.replace("##EXC##", TestException.class.getName())
.replace("##FILE##", srcDir.resolve("p").resolve("C.java").toString()));
} finally {
setFileManager(null);
}
}
}
@Test
public void testFileObjectWrite(Path base) throws Exception {
try (StandardJavaFileManager fm = getFileManager()) {
Path outDir = base.resolve("api");
// build a file manager that throws an exception when any file is generated
StandardJavaFileManager tfm = new TestJavaFileManagerBuilder(fm)
.handle(jfo -> fm.asPath(jfo).startsWith(outDir.toAbsolutePath())
&& jfo.getName().endsWith(".html"),
Map.of(JavaFileObject.class.getMethod("openOutputStream"),
(fo, args) -> new TestException(fo.getName())))
.build();
try {
setFileManager(tfm);
javadoc("-d", outDir.toString(),
"-sourcepath", srcDir.toString(),
"p");
checkExit((Exit.ERROR));
checkOutput(Output.OUT, true,
"""
Generating ##FILE##...
error: An internal exception has occurred.
\t(##EXC##: ##FILE##)
1 error"""
.replace("##EXC##", TestException.class.getName())
.replace("##FILE##", outDir.resolve("p").resolve("C.html").toString()));
} finally {
setFileManager(null);
}
}
}
}