blob: 46e48f649079458c11bd240222f6315ae1b4ba03 [file] [log] [blame]
/*
* Copyright (c) 2019, 2020, 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 8218998 8219946 8219060 8241190 8242056 8254627
* @summary Add metadata to generated API documentation files
* @library /tools/lib ../../lib
* @modules jdk.javadoc/jdk.javadoc.internal.tool
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* jdk.javadoc/jdk.javadoc.internal.api
* jdk.javadoc/jdk.javadoc.internal.tool
* @build toolbox.ToolBox toolbox.JavacTask javadoc.tester.*
* @run main TestMetadata
*/
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import toolbox.ModuleBuilder;
import toolbox.ToolBox;
import javadoc.tester.JavadocTester;
public class TestMetadata extends JavadocTester {
public static void main(String... args) throws Exception {
TestMetadata tester = new TestMetadata();
tester.runTests();
}
enum Index { SINGLE, SPLIT };
enum Source { PACKAGES, MODULES };
final ToolBox tb = new ToolBox();
final Set<String> allBodyClassesFound = new HashSet<>();
final Set<String> allGeneratorsFound = new HashSet<>();
public void runTests() throws Exception {
for (Source s : Source.values()) {
Path src = genSource(s);
for (Index i : Index.values()) {
List<String> args = new ArrayList<>();
args.add("-d");
args.add(String.format("out-%s-%s", s, i));
args.add("-use");
args.add("-linksource");
if (i == Index.SPLIT) {
args.add("-splitIndex");
}
if (s == Source.PACKAGES) {
args.add("-sourcepath");
args.add(src.toString());
args.add("pA");
args.add("pB");
} else {
args.add("--module-source-path");
args.add(src.toString());
args.add("--module");
args.add("mA,mB");
}
javadoc(args.toArray(new String[args.size()]));
checkExit(Exit.OK);
checkBodyClasses();
checkMetadata();
// spot check the descriptions for declarations
switch (s) {
case PACKAGES:
checkOutput("pA/package-summary.html", true,
"""
<meta name="description" content="declaration: package: pA">""");
checkOutput("pA/CA.html", true,
"""
<meta name="description" content="declaration: package: pA, class: CA">""");
break;
case MODULES:
checkOutput("mA/module-summary.html", true,
"""
<meta name="description" content="declaration: module: mA">""");
checkOutput("mA/pA/package-summary.html", true,
"""
<meta name="description" content="declaration: module: mA, package: pA">""");
checkOutput("mA/pA/CA.html", true,
"""
<meta name="description" content="declaration: module: mA, package: pA, class: CA">""");
break;
}
}
}
checking ("all generators");
if (allGeneratorsFound.equals(allGenerators)) {
passed("all generators found");
} else {
Set<String> notFound = new TreeSet<>(allGenerators);
notFound.removeAll(allGeneratorsFound);
failed("not found: " + notFound);
}
checking ("all body classes");
if (allBodyClassesFound.equals(allBodyClasses)) {
passed("all body classes found");
} else {
Set<String> notFound = new TreeSet<>(allBodyClasses);
notFound.removeAll(allBodyClassesFound);
failed("not found: " + notFound);
}
printSummary();
}
final Pattern nl = Pattern.compile("[\\r\\n]+");
final Pattern bodyPattern = Pattern.compile("<body [^>]*class=\"([^\"]+)\"");
final Set<String> allBodyClasses = Set.of(
"all-classes-index-page",
"all-packages-index-page",
"class-declaration-page",
"class-use-page",
"constants-summary-page",
"deprecated-list-page",
"doc-file-page",
"help-page",
"index-page",
"index-redirect-page",
"module-declaration-page",
"module-index-page",
"package-declaration-page",
"package-index-page",
"package-tree-page",
"package-use-page",
"serialized-form-page",
"source-page",
"system-properties-page",
"tree-page"
);
void checkBodyClasses() throws IOException {
Path outputDirPath = outputDir.toPath();
for (Path p : tb.findFiles(".html", outputDirPath)) {
checkBodyClass(outputDirPath.relativize(p));
}
}
void checkBodyClass(Path p) {
checking("Check body: " + p);
List<String> bodyLines = nl.splitAsStream(readOutputFile(p.toString()))
.filter(s -> s.contains("<body class="))
.collect(Collectors.toList());
String bodyLine;
switch (bodyLines.size()) {
case 0:
failed("Not found: <body class=");
return;
case 1:
bodyLine = bodyLines.get(0);
break;
default:
failed("Multiple found: <body class=");
return;
}
Matcher m = bodyPattern.matcher(bodyLine);
if (m.find()) {
String bodyClass = m.group(1);
if (allBodyClasses.contains(bodyClass)) {
passed("found: " + bodyClass);
allBodyClassesFound.add(bodyClass);
} else {
failed("Unrecognized body class: " + bodyClass);
}
} else {
failed("Unrecognized line:\n" + bodyLine);
}
}
final Pattern contentPattern = Pattern.compile("content=\"([^\"]+)\">");
final Pattern generatorPattern = Pattern.compile("content=\"javadoc/([^\"]+)\">");
final Set<String> allGenerators = Set.of(
"AllClassesIndexWriter",
"AllPackagesIndexWriter",
"ClassUseWriter",
"ClassWriterImpl",
"ConstantsSummaryWriterImpl",
"DeprecatedListWriter",
"DocFileWriter",
"HelpWriter",
"IndexRedirectWriter",
"IndexWriter",
"ModuleIndexWriter",
"ModuleWriterImpl",
"PackageIndexWriter",
"PackageTreeWriter",
"PackageUseWriter",
"PackageWriterImpl",
"SerializedFormWriterImpl",
"SourceToHTMLConverter",
"SystemPropertiesWriter",
"TreeWriter"
);
void checkMetadata() throws IOException {
Path outputDirPath = outputDir.toPath();
for (Path p : tb.findFiles(".html", outputDirPath)) {
checkMetadata(outputDirPath.relativize(p));
}
}
void checkMetadata(Path p) {
checking("Check generator: " + p);
List<String> generators = nl.splitAsStream(readOutputFile(p.toString()))
.filter(s -> s.contains("<meta name=\"generator\""))
.collect(Collectors.toList());
String generator;
switch (generators.size()) {
case 0:
failed("""
Not found: <meta name="generator\"""");
return;
case 1:
generator = generators.get(0);
break;
default:
failed("""
Multiple found: <meta name="generator\"""");
return;
}
Matcher m = generatorPattern.matcher(generator);
if (m.find()) {
String content = m.group(1);
if (allGenerators.contains(content)) {
passed("found: " + content);
allGeneratorsFound.add(content);
checkDescription(p, content);
} else {
failed("Unrecognized content: " + content);
}
} else {
failed("Unrecognized line:\n" + generator);
}
}
void checkDescription(Path p, String generator) {
checking("Check description: " + p);
List<String> descriptions = nl.splitAsStream(readOutputFile(p.toString()))
.filter(s -> s.contains("<meta name=\"description\""))
.collect(Collectors.toList());
String description;
switch (descriptions.size()) {
case 0:
if (generator.equals("DocFileWriter")) {
passed("Not found, as expected");
} else {
failed("""
Not found: <meta name="description\"""");
}
return;
case 1:
description = descriptions.get(0);
break;
default:
failed("""
Multiple found: <meta name="description\"""");
return;
}
String content;
Matcher m = contentPattern.matcher(description);
if (m.find()) {
content = m.group(1);
} else {
failed("Unrecognized line:\n" + description);
return;
}
switch (generator) {
case "AllClassesIndexWriter":
case "AllPackagesIndexWriter":
case "ModuleIndexWriter":
case "PackageIndexWriter":
check(generator, content, content.contains("index"));
break;
case "AnnotationTypeWriterImpl":
case "ClassWriterImpl":
case "ModuleWriterImpl":
case "PackageWriterImpl":
check(generator, content, content.startsWith("declaration: "));
break;
case "ClassUseWriter":
case "PackageUseWriter":
check(generator, content, content.startsWith("use: "));
break;
case "ConstantsSummaryWriterImpl":
check(generator, content, content.contains("constants"));
break;
case "DeprecatedListWriter":
check(generator, content, content.contains("deprecated"));
break;
case "DocFileWriter":
passed("no constraint for user-provided doc-files");
break;
case "HelpWriter":
check(generator, content, content.contains("help"));
break;
case "IndexRedirectWriter":
check(generator, content, content.contains("redirect"));
break;
case "IndexWriter":
check(generator, content, content.startsWith("index"));
break;
case "PackageTreeWriter":
case "TreeWriter":
check(generator, content, content.contains("tree"));
break;
case "SerializedFormWriterImpl":
check(generator, content, content.contains("serialized"));
break;
case "SourceToHTMLConverter":
check(generator, content, content.startsWith("source:"));
break;
case "SystemPropertiesWriter":
check(generator, content, content.contains("system properties"));
break;
default:
failed("unexpected generator: " + generator);
break;
}
}
void check(String generator, String content, boolean ok) {
if (ok) {
passed("OK: " + generator + " " + content);
} else {
failed("unexpected value for " + generator + ": " + content);
}
}
Path genSource(Source s) throws IOException {
Path src = Path.of("src-" + s);
switch (s) {
case PACKAGES:
tb.writeJavaFiles(src,
"/** Package pA. {@systemProperty exampleProperty} */ package pA;",
"/** Class pA.CA. */ package pA; public class CA { @Deprecated public static final int ZERO = 0; }",
"/** Anno pA.Anno, */ package pA; public @interface Anno { }",
"/** Serializable pA.Ser, */ package pA; public class Ser implements java.io.Serializable { }",
"/** Package pB. */ package pB;",
"/** Class pB.CB. */ package pB; public class CB { }");
tb.writeFile(src.resolve("pA").resolve("doc-files").resolve("extra.html"),
"""
<!doctype html>
<html><head></head><body>Extra</body></html>""");
break;
case MODULES:
new ModuleBuilder(tb, "mA")
.exports("pA")
.classes("/** Package mA/pA. */ package pA;")
.classes("/** Class mA/pA.CA. */ package pA; public class CA { @Deprecated public static int ZERO = 0; }")
.write(src);
new ModuleBuilder(tb, "mB")
.exports("pB")
.classes("/** Package mB/pB. */ package pB;")
.classes("/** Class mB/pB.CB. */ package pB; public class CB { }")
.write(src);
break;
}
return src;
}
}