jdk-24/test/langtools/jdk/javadoc/doclet/testDocTreeDiags/TestDocTreeDiags.java
Jonathan Gibbons 2294f225c0 8286311: remove boilerplate from use of runTests
Reviewed-by: hannesw
2022-12-22 21:20:43 +00:00

272 lines
11 KiB
Java

/*
* Copyright (c) 2021, 2022, 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 8268420
* @summary new Reporter method to report a diagnostic within a DocTree node
* @library /tools/lib ../../lib
* @modules jdk.javadoc/jdk.javadoc.internal.tool
* @build javadoc.tester.* MyTaglet
* @run main TestDocTreeDiags
*/
import java.io.IOException;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import javax.tools.DocumentationTool;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import javadoc.tester.JavadocTester;
import toolbox.ToolBox;
/**
* Tests the ability to write diagnostics related to a (start,pos,end) range in those
* DocTrees that wrap a String value.
*
* Ideally, this would be tested by using a custom doclet which scans all the doc comments,
* generating diagnostics for eligible nodes. However, one of the cases that is tested is
* a DocTypeTree, which only occurs in the context of an HTML file in a doc-files subdirectory,
* which is very specific to the Standard Doclet. Therefore, we use the Standard Doclet
* in conjunction with a non-standard use of a custom taglet, which is used to access and
* scan the doc comments that enclose the tags that trigger the taglet.
*/
public class TestDocTreeDiags extends JavadocTester {
public static void main(String... args) throws Exception {
var tester = new TestDocTreeDiags();
tester.runTests();
}
ToolBox tb = new ToolBox();
Path src;
DocumentationTool tool;
boolean showOutput = false; // set true for to set output written by javadoc
TestDocTreeDiags() throws IOException {
src = Path.of("src");
// Note: the following comments are somewhat stylized, and need to follow some
// simple rules to avoid exceptions and false positives.
// 1. Each fragment must be at least 7 (and preferably 9) characters long,
// in order to contain the range that will be generated in the diagnostic.
// 2. There must be no non-trivial duplication in the fragments, particularly
// in the area where the range of characters will be generated for the
// diagnostic. This is because we use String.indexOf to determine the
// expected values of the range.
tb.writeJavaFiles(src,
"""
package p;
/**
* First sentence. " Second sentence.
* {@link java.lang.String first phrase; " second phrase }
* And now ... <!-- this is a comment --> and so it was.
* @scanMe
*/
public class C {
/**
* Sentence for method m(). More details for the method.
* Embedded {@link java.lang.Object} link.
* And another <!-- unusual comment --> strange comment.
* @scanMe
*/
public void m() { }
}
""");
tb.writeFile(src.resolve("p").resolve("doc-files").resolve("extra.html"),
"""
<!doctype doctype-description>
<html>
<head><title>Document Title</title></head>
<body>
Extra content. More content.
@scanMe
</body>
</html>
"""
);
tool = ToolProvider.getSystemDocumentationTool();
}
/**
* Tests the diagnostics generated to the output stream when there is no
* diagnostic listener in use.
*
* By default, in this context, the start and end of the range of characters are not
* presented. The caret should point at the preferred position for the diagnostic.
*/
@Test
public void testStdout(Path base) throws Exception {
StringWriter outWriter = new StringWriter();
javadoc(outWriter, null, base.resolve("api"));
// analyze and verify the generated diagnostics
List<String> lines = outWriter.toString().lines().toList();
Iterator<String> iter = lines.iterator();
while (iter.hasNext()) {
String l = iter.next();
if (l.startsWith("src")) {
checkDiag(null, l, iter.next(), iter.next());
}
}
}
/**
* Tests the diagnostics received by a DiagnosticListener.
*
* In this context, various detailed coordinate information is available.
*/
@Test
public void testDiagListener(Path base) throws Exception {
StringWriter outWriter = new StringWriter();
DiagnosticListener dl = diagnostic -> {
if (diagnostic.getPosition() != -1) {
List<String> lines = List.of(diagnostic.toString().split("\\R"));
assert lines.size() == 3;
String msgLine = lines.get(0);
String srcLine = lines.get(1);
String caretLine = lines.get(2);
checkDiag(diagnostic, msgLine, srcLine, caretLine);
}
};
javadoc(outWriter, dl, base.resolve("api"));
}
/**
* Runs javadoc on package {@code p} in the {@code src} directory,
* using the specified writer and optional diagnostic listener.
*
* @param writer the writer
* @param dl the diagnostic listener, or {@code null}
* @param outDir the output directory
*
* @throws IOException if an IO error occurs
*/
void javadoc(StringWriter writer, DiagnosticListener dl, Path outDir) throws IOException {
Files.createDirectories(outDir);
try (StandardJavaFileManager fm = tool.getStandardFileManager(null, null, null)) {
fm.setLocationFromPaths(StandardLocation.SOURCE_PATH, List.of(src));
fm.setLocationFromPaths(DocumentationTool.Location.DOCUMENTATION_OUTPUT, List.of(outDir));
fm.setLocationFromPaths(DocumentationTool.Location.TAGLET_PATH, List.of(Path.of(System.getProperty("test.classes"))));
Iterable<? extends JavaFileObject> files = Collections.emptyList();
Iterable<String> options = List.of(
"-taglet", MyTaglet.class.getName(),
"-XDaccessInternalAPI",
"-Xdoclint:all,-missing",
"p");
DocumentationTool.DocumentationTask t = tool.getTask(writer, fm, dl, null, options, files);
checking("exit");
boolean ok = t.call();
if (showOutput) {
out.println("OUT: >>>" + writer.toString().replace("\n", NL) + "<<<");
}
if (ok) {
passed("javadoc exited OK, as expected");
} else {
failed("javadoc failed");
}
}
}
/**
* Checks the diagnostic output against information encoded in the diagnostics.
*
* The message in the message line contains a string that indicates where the
* caret should be pointing in the source line.
*
* @param diag the diagnostic, or null
* @param msgLine file:line: message >>>detail<<<
* @param srcLine the source line
* @param caretLine the line with the caret
*/
void checkDiag(Diagnostic diag, String msgLine, String srcLine, String caretLine) {
if (diag != null) {
out.printf("DIAG: %d:%d:%d %d:%d vvv%n%s%n^^^%n",
diag.getStartPosition(), diag.getPosition(), diag.getEndPosition(),
diag.getLineNumber(), diag.getColumnNumber(),
diag.toString().replace("\\R", NL) );
}
out.println(msgLine);
out.println(srcLine);
out.println(caretLine);
String srcFileLine = msgLine.substring(0, msgLine.indexOf(": "));
int caretIndex = caretLine.indexOf('^');
Pattern p = Pattern.compile(">>>([^<]*)<<<");
Matcher m = p.matcher(msgLine);
if (!m.find()) {
throw new IllegalArgumentException("detail pattern not found: " + msgLine);
}
String rawDetail = m.group(1);
String detail = rawDetail.replaceAll("[\\[\\]]", "");
if (diag != null) {
checking("coords-column: " + srcFileLine);
int col = (int) diag.getColumnNumber();
// line and column are 1-based, so col should be 1 more than caretIndex
if (col - 1 == caretIndex) {
passed("col: " + col + " caret: " + caretIndex);
} else {
failed("col: " + col + " caret: " + caretIndex);
}
checking("coords-start-end: " + srcFileLine);
String fileStr = readFile(".", msgLine.substring(0, msgLine.indexOf(":")));
int start = (int) diag.getStartPosition();
int end = (int) diag.getEndPosition();
String fileRange = fileStr.substring(start, end);
if (fileRange.equals(detail)) {
passed("file: >>>" + fileRange + "<<< message: >>>" + detail + "<<<");
} else {
failed("file: >>>" + fileRange + "<<< message: >>>" + detail + "<<<");
}
}
checking("message-caret: " + srcFileLine);
int srcIndex = srcLine.indexOf(detail);
int pad = (detail.length() - 1) / 2;
int srcIndexPad = srcIndex + pad;
if (srcIndexPad == caretIndex) {
passed("src: " + srcIndexPad + " caret: " + caretIndex);
} else {
failed("src: " + srcIndexPad + " caret: " + caretIndex);
}
}
}