Path: blob/master/test/langtools/jdk/javadoc/tool/8224613/OptionProcessingFailureTest.java
40971 views
/*1* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/*24* @test25* @bug 822461326* @library /tools/lib ../../lib27* @modules jdk.javadoc/jdk.javadoc.internal.tool28* @build javadoc.tester.* toolbox.ToolBox builder.ClassBuilder29* @run main/othervm OptionProcessingFailureTest30*/3132import builder.ClassBuilder;33import javadoc.tester.JavadocTester;34import jdk.javadoc.doclet.Doclet;35import jdk.javadoc.doclet.DocletEnvironment;36import jdk.javadoc.doclet.Reporter;37import toolbox.ToolBox;3839import javax.lang.model.SourceVersion;40import javax.tools.Diagnostic;41import java.io.IOException;42import java.nio.file.Path;43import java.nio.file.Paths;44import java.util.ArrayList;45import java.util.Collection;46import java.util.HashSet;47import java.util.List;48import java.util.Locale;49import java.util.Objects;50import java.util.Set;51import java.util.stream.Collectors;5253/*54* Tests that a particular error is raised only if a doclet has not reported any55* errors (to Reporter), and yet at least one of that Doclet's options returned56* `false` from the `Doclet.Option.process` method.57*58* This test explores scenarios generated by combining a few independent factors59* involved in failing a doclet run. These factors are:60*61* 1. Reporting errors in Doclet.init(...)62* 2. Reporting errors in Doclet.Option.process(...)63* 3. Returning `false` from Doclet.Option.process(...)64*65* A doclet that behaves according to a scenario is run by the javadoc tool. An66* output of that run is then examined for presence of a particular error. That67* error must be in the output if and only if one or more options returned68* `false` from Doclet.Option.process(...) and no other errors were reported.69*70* Configuration of the doclet is performed out-of-band, using System Properties71* (when running the javadoc tool from the command line this could be achieved72* using -J-Dsystem.property.name=value syntax). The "puppet" doclet is73* instructed on which options is has, how many errors it should report,74* and how each individual option should be processed.75*/76public class OptionProcessingFailureTest extends JavadocTester {7778private final ToolBox tb = new ToolBox();7980public static void main(String... args) throws Exception {81new OptionProcessingFailureTest().runTests(m -> new Object[]{Paths.get(m.getName())});82}8384@Test85public void test(Path base) throws IOException {86Path srcDir = base.resolve("src");8788// Since the minimal successful run of the javadoc tool with a custom89// doclet involves source files, a package with a class in it is generated:90new ClassBuilder(tb, "pkg.A")91.setModifiers("public", "class")92.write(srcDir);9394generateScenarios(base, this::testScenario);95}9697private static void generateScenarios(Path base, ScenarioConsumer consumer) {98for (int nInitErrors : List.of(0, 1)) { // the number of errors the Doclet.init method should report99for (int nOptions : List.of(0, 1, 2)) { // the number of options a doclet should have100generateOptionsCombinations(base, nInitErrors, nOptions, consumer);101}102}103}104105private static void generateOptionsCombinations(Path base,106int nInitErrors,107int nOptions,108ScenarioConsumer consumer) {109var emptyDescriptions = new PuppetOption.Description[nOptions];110generateOptionsCombinations(base, nInitErrors, 0, emptyDescriptions, consumer);111}112113114private static void generateOptionsCombinations(Path base,115int nInitErrors,116int descriptionsIndex,117PuppetOption.Description[] descriptions,118ScenarioConsumer consumer) {119if (descriptionsIndex >= descriptions.length) {120consumer.consume(base, nInitErrors, List.of(descriptions));121return;122}123for (boolean success : List.of(true, false)) { // return values of Doclet.Options.process124for (int nErrors : List.of(0, 1)) { // the number of errors Doclet.Options.process should report125String optionName = "--doclet-option-" + descriptionsIndex;126descriptions[descriptionsIndex] = new PuppetOption.Description(optionName, success, nErrors);127generateOptionsCombinations(base, nInitErrors, descriptionsIndex + 1, descriptions, consumer);128}129}130}131132private void testScenario(Path base,133int nInitErrors,134List<PuppetOption.Description> optionDescriptions) {135System.out.printf("nInitErrors=%s, optionDescriptions=%s%n", nInitErrors, optionDescriptions);136137List<String> args = new ArrayList<>(138List.of("-doclet", PuppetDoclet.class.getName(),139"-docletpath", System.getProperty("test.classes", "."),140"-sourcepath", base.resolve("src").toString(),141"pkg"));142143optionDescriptions.forEach(d -> args.add(d.name)); // options passed to the doclet144145/* The puppet doclet does not create any output directories, so there is146no need for any related checks; other checks are disabled to speed up147the processing and reduce the size of the output. */148149setOutputDirectoryCheck(DirectoryCheck.NONE);150setAutomaticCheckAccessibility(false);151setAutomaticCheckLinks(false);152153/* Ideally, the system properties should've been passed to the `javadoc`154method below. However, since there's no such option, the system155properties are manipulated in an external fashion. */156157String descriptions = System.getProperty("puppet.descriptions");158if (descriptions != null) {159throw new IllegalStateException(descriptions);160}161String errors = System.getProperty("puppet.errors");162if (errors != null) {163throw new IllegalStateException(errors);164}165try {166System.setProperty("puppet.descriptions", PuppetDoclet.descriptionsToString(optionDescriptions));167System.setProperty("puppet.errors", String.valueOf(nInitErrors));168javadoc(args.toArray(new String[0]));169} finally {170System.clearProperty("puppet.descriptions");171System.clearProperty("puppet.errors");172}173174long sumErrors = optionDescriptions.stream().mapToLong(d -> d.nProcessErrors).sum() + nInitErrors;175boolean success = optionDescriptions.stream().allMatch(d -> d.success);176177checkOutput(Output.OUT, sumErrors != 0 || !success, "error: ");178}179180/* Creating a specialized consumer is even more lightweight than creating a POJO */181@FunctionalInterface182public interface ScenarioConsumer {183void consume(Path src, int nInitErrors, List<PuppetOption.Description> optionDescriptions);184}185186public static final class PuppetDoclet implements Doclet {187188private final int nErrorsInInit;189private final Set<PuppetOption.Description> descriptions;190private Set<Option> options;191private Reporter reporter;192193public PuppetDoclet() {194this(nInitErrorsFromString(System.getProperty("puppet.errors")),195descriptionsFromString(System.getProperty("puppet.descriptions")));196}197198private static Collection<PuppetOption.Description> descriptionsFromString(String value) {199if (value.isEmpty())200return List.of(); // special case of description of zero-length201String[] split = value.split(",");202List<PuppetOption.Description> descriptions = new ArrayList<>();203for (int i = 0; i < split.length; i += 3) {204String name = split[i];205boolean success = Boolean.parseBoolean(split[i + 1]);206int nErrors = Integer.parseInt(split[i + 2]);207descriptions.add(new PuppetOption.Description(name, success, nErrors));208}209return descriptions;210}211212public static String descriptionsToString(Collection<? extends PuppetOption.Description> descriptions) {213return descriptions.stream()214.map(d -> d.name + "," + d.success + "," + d.nProcessErrors)215.collect(Collectors.joining(","));216}217218private static int nInitErrorsFromString(String value) {219return Integer.parseInt(Objects.requireNonNull(value));220}221222public PuppetDoclet(int nErrorsInInit, Collection<PuppetOption.Description> descriptions) {223if (nErrorsInInit < 0) {224throw new IllegalArgumentException();225}226this.nErrorsInInit = nErrorsInInit;227this.descriptions = Set.copyOf(descriptions);228}229230@Override231public void init(Locale locale, Reporter reporter) {232this.reporter = reporter;233for (int i = 0; i < nErrorsInInit; i++) {234reporter.print(Diagnostic.Kind.ERROR, "init error #%s".formatted(i));235}236}237238@Override239public String getName() {240return getClass().getSimpleName();241}242243@Override244public Set<? extends Option> getSupportedOptions() {245if (options == null) {246options = new HashSet<>();247for (PuppetOption.Description d : descriptions) {248options.add(new PuppetOption(d, reporter));249}250}251return options;252}253254@Override255public SourceVersion getSupportedSourceVersion() {256return SourceVersion.latestSupported();257}258259@Override260public boolean run(DocletEnvironment environment) {261return true;262}263}264265private static final class PuppetOption implements Doclet.Option {266267private final Reporter reporter;268private final Description description;269270public PuppetOption(Description description, Reporter reporter) {271this.description = description;272this.reporter = reporter;273}274275@Override276public int getArgumentCount() {277return 0;278}279280@Override281public String getDescription() {282return description.name283+ ": success=" + description.success284+ ", nProcessErrors=" + description.nProcessErrors;285}286287@Override288public Kind getKind() {289return Kind.STANDARD;290}291292@Override293public List<String> getNames() {294return List.of(description.name);295}296297@Override298public String getParameters() {299return "";300}301302@Override303public boolean process(String option, List<String> arguments) {304for (int i = 0; i < description.nProcessErrors; i++) {305reporter.print(Diagnostic.Kind.ERROR,306"option: '%s', process error #%s".formatted(description.name, i));307}308return description.success;309}310311public final static class Description {312313public final String name;314public final boolean success;315public final int nProcessErrors;316317Description(String name, boolean success, int nErrors) {318this.name = name;319this.success = success;320this.nProcessErrors = nErrors;321}322323@Override324public String toString() {325return "Description{" +326"name='" + name + '\'' +327", success=" + success +328", nProcessErrors=" + nProcessErrors +329'}';330}331}332}333}334335336337