|
| 1 | +/* |
| 2 | + * Copyright (c) 2021, 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 | +/* |
| 25 | + * @test |
| 26 | + * @bug 8268420 |
| 27 | + * @summary new Reporter method to report a diagnostic within a DocTree node |
| 28 | + * @library /tools/lib ../../lib |
| 29 | + * @modules jdk.javadoc/jdk.javadoc.internal.tool |
| 30 | + * @build javadoc.tester.* MyTaglet |
| 31 | + * @run main TestDocTreeDiags |
| 32 | + */ |
| 33 | + |
| 34 | +import java.io.IOException; |
| 35 | +import java.io.StringWriter; |
| 36 | +import java.nio.file.Files; |
| 37 | +import java.nio.file.Path; |
| 38 | +import java.util.Collections; |
| 39 | +import java.util.Iterator; |
| 40 | +import java.util.List; |
| 41 | +import java.util.regex.Matcher; |
| 42 | +import java.util.regex.Pattern; |
| 43 | +import javax.tools.Diagnostic; |
| 44 | +import javax.tools.DiagnosticListener; |
| 45 | +import javax.tools.DocumentationTool; |
| 46 | +import javax.tools.JavaFileObject; |
| 47 | +import javax.tools.StandardJavaFileManager; |
| 48 | +import javax.tools.StandardLocation; |
| 49 | +import javax.tools.ToolProvider; |
| 50 | + |
| 51 | +import javadoc.tester.JavadocTester; |
| 52 | +import toolbox.ToolBox; |
| 53 | + |
| 54 | +/** |
| 55 | + * Tests the ability to write diagnostics related to a (start,pos,end) range in those |
| 56 | + * DocTrees that wrap a String value. |
| 57 | + * |
| 58 | + * Ideally, this would be tested by using a custom doclet which scans all the doc comments, |
| 59 | + * generating diagnostics for eligible nodes. However, one of the cases that is tested is |
| 60 | + * a DocTypeTree, which only occurs in the context of an HTML file in a doc-files subdirectory, |
| 61 | + * which is very specific to the Standard Doclet. Therefore, we use the Standard Doclet |
| 62 | + * in conjunction with a non-standard use of a custom taglet, which is used to access and |
| 63 | + * scan the doc comments that enclose the tags that trigger the taglet. |
| 64 | + */ |
| 65 | +public class TestDocTreeDiags extends JavadocTester { |
| 66 | + |
| 67 | + public static void main(String... args) throws Exception { |
| 68 | + TestDocTreeDiags tester = new TestDocTreeDiags(); |
| 69 | + tester.runTests(m -> new Object[] { Path.of(m.getName())} ); |
| 70 | + } |
| 71 | + |
| 72 | + ToolBox tb = new ToolBox(); |
| 73 | + Path src; |
| 74 | + DocumentationTool tool; |
| 75 | + |
| 76 | + boolean showOutput = false; // set true for to set output written by javadoc |
| 77 | + |
| 78 | + TestDocTreeDiags() throws IOException { |
| 79 | + src = Path.of("src"); |
| 80 | + // Note: the following comments are somewhat stylized, and need to follow some |
| 81 | + // simple rules to avoid exceptions and false positives. |
| 82 | + // 1. Each fragment must be at least 7 (and preferably 9) characters long, |
| 83 | + // in order to contain the range that will be generated in the diagnostic. |
| 84 | + // 2. There must be no non-trivial duplication in the fragments, particularly |
| 85 | + // in the area where the range of characters will be generated for the |
| 86 | + // diagnostic. This is because we use String.indexOf to determine the |
| 87 | + // expected values of the range. |
| 88 | + tb.writeJavaFiles(src, |
| 89 | + """ |
| 90 | + package p; |
| 91 | + /** |
| 92 | + * First sentence. " Second sentence. |
| 93 | + * {@link java.lang.String first phrase; " second phrase } |
| 94 | + * And now ... <!-- this is a comment --> and so it was. |
| 95 | + * @scanMe |
| 96 | + */ |
| 97 | + public class C { |
| 98 | + /** |
| 99 | + * Sentence for method m(). More details for the method. |
| 100 | + * Embedded {@link java.lang.Object} link. |
| 101 | + * And another <!-- unusual comment --> strange comment. |
| 102 | + * @scanMe |
| 103 | + */ |
| 104 | + public void m() { } |
| 105 | + } |
| 106 | + """); |
| 107 | + tb.writeFile(src.resolve("p").resolve("doc-files").resolve("extra.html"), |
| 108 | + """ |
| 109 | + <!doctype doctype-description> |
| 110 | + <html> |
| 111 | + <head><title>Document Title</title></head> |
| 112 | + <body> |
| 113 | + Extra content. More content. |
| 114 | + @scanMe |
| 115 | + </body> |
| 116 | + </html> |
| 117 | + """ |
| 118 | + ); |
| 119 | + |
| 120 | + tool = ToolProvider.getSystemDocumentationTool(); |
| 121 | + } |
| 122 | + |
| 123 | + /** |
| 124 | + * Tests the diagnostics generated to the output stream when there is no |
| 125 | + * diagnostic listener in use. |
| 126 | + * |
| 127 | + * By default, in this context, the start and end of the range of characters are not |
| 128 | + * presented. The caret should point at the preferred position for the diagnostic. |
| 129 | + */ |
| 130 | + @Test |
| 131 | + public void testStdout(Path base) throws Exception { |
| 132 | + StringWriter outWriter = new StringWriter(); |
| 133 | + javadoc(outWriter, null, base.resolve("api")); |
| 134 | + |
| 135 | + // analyze and verify the generated diagnostics |
| 136 | + List<String> lines = outWriter.toString().lines().toList(); |
| 137 | + Iterator<String> iter = lines.iterator(); |
| 138 | + while (iter.hasNext()) { |
| 139 | + String l = iter.next(); |
| 140 | + if (l.startsWith("src")) { |
| 141 | + checkDiag(null, l, iter.next(), iter.next()); |
| 142 | + } |
| 143 | + } |
| 144 | + } |
| 145 | + |
| 146 | + /** |
| 147 | + * Tests the diagnostics received by a DiagnosticListener. |
| 148 | + * |
| 149 | + * In this context, various detailed coordinate information is available. |
| 150 | + */ |
| 151 | + @Test |
| 152 | + public void testDiagListener(Path base) throws Exception { |
| 153 | + StringWriter outWriter = new StringWriter(); |
| 154 | + DiagnosticListener dl = diagnostic -> { |
| 155 | + if (diagnostic.getPosition() != -1) { |
| 156 | + List<String> lines = List.of(diagnostic.toString().split("\\R")); |
| 157 | + assert lines.size() == 3; |
| 158 | + String msgLine = lines.get(0); |
| 159 | + String srcLine = lines.get(1); |
| 160 | + String caretLine = lines.get(2); |
| 161 | + checkDiag(diagnostic, msgLine, srcLine, caretLine); |
| 162 | + } |
| 163 | + }; |
| 164 | + javadoc(outWriter, dl, base.resolve("api")); |
| 165 | + } |
| 166 | + |
| 167 | + /** |
| 168 | + * Runs javadoc on package {@code p} in the {@code src} directory, |
| 169 | + * using the specified writer and optional diagnostic listener. |
| 170 | + * |
| 171 | + * @param writer the writer |
| 172 | + * @param dl the diagnostic listener, or {@code null} |
| 173 | + * @param outDir the output directory |
| 174 | + * |
| 175 | + * @throws IOException if an IO error occurs |
| 176 | + */ |
| 177 | + void javadoc(StringWriter writer, DiagnosticListener dl, Path outDir) throws IOException { |
| 178 | + Files.createDirectories(outDir); |
| 179 | + try (StandardJavaFileManager fm = tool.getStandardFileManager(null, null, null)) { |
| 180 | + fm.setLocationFromPaths(StandardLocation.SOURCE_PATH, List.of(src)); |
| 181 | + fm.setLocationFromPaths(DocumentationTool.Location.DOCUMENTATION_OUTPUT, List.of(outDir)); |
| 182 | + fm.setLocationFromPaths(DocumentationTool.Location.TAGLET_PATH, List.of(Path.of(System.getProperty("test.classes")))); |
| 183 | + Iterable<? extends JavaFileObject> files = Collections.emptyList(); |
| 184 | + Iterable<String> options = List.of("-taglet", MyTaglet.class.getName(), "-XDaccessInternalAPI", "p"); |
| 185 | + DocumentationTool.DocumentationTask t = tool.getTask(writer, fm, dl, null, options, files); |
| 186 | + |
| 187 | + checking("exit"); |
| 188 | + boolean ok = t.call(); |
| 189 | + |
| 190 | + if (showOutput) { |
| 191 | + out.println("OUT: >>>" + writer.toString().replace("\n", NL) + "<<<"); |
| 192 | + } |
| 193 | + |
| 194 | + if (ok) { |
| 195 | + passed("javadoc exited OK, as expected"); |
| 196 | + } else { |
| 197 | + failed("javadoc failed"); |
| 198 | + } |
| 199 | + } |
| 200 | + } |
| 201 | + |
| 202 | + /** |
| 203 | + * Checks the diagnostic output against information encoded in the diagnostics. |
| 204 | + * |
| 205 | + * The message in the message line contains a string that indicates where the |
| 206 | + * caret should be pointing in the source line. |
| 207 | + * |
| 208 | + * @param diag the diagnostic, or null |
| 209 | + * @param msgLine file:line: message >>>detail<<< |
| 210 | + * @param srcLine the source line |
| 211 | + * @param caretLine the line with the caret |
| 212 | + */ |
| 213 | + void checkDiag(Diagnostic diag, String msgLine, String srcLine, String caretLine) { |
| 214 | + if (diag != null) { |
| 215 | + out.printf("DIAG: %d:%d:%d %d:%d vvv%n%s%n^^^%n", |
| 216 | + diag.getStartPosition(), diag.getPosition(), diag.getEndPosition(), |
| 217 | + diag.getLineNumber(), diag.getColumnNumber(), |
| 218 | + diag.toString().replace("\\R", NL) ); |
| 219 | + } |
| 220 | + out.println(msgLine); |
| 221 | + out.println(srcLine); |
| 222 | + out.println(caretLine); |
| 223 | + |
| 224 | + String srcFileLine = msgLine.substring(0, msgLine.indexOf(": ")); |
| 225 | + int caretIndex = caretLine.indexOf('^'); |
| 226 | + Pattern p = Pattern.compile(">>>([^<]*)<<<"); |
| 227 | + Matcher m = p.matcher(msgLine); |
| 228 | + if (!m.find()) { |
| 229 | + throw new IllegalArgumentException("detail pattern not found: " + msgLine); |
| 230 | + } |
| 231 | + String rawDetail = m.group(1); |
| 232 | + String detail = rawDetail.replaceAll("[\\[\\]]", ""); |
| 233 | + |
| 234 | + if (diag != null) { |
| 235 | + checking("coords-column: " + srcFileLine); |
| 236 | + int col = (int) diag.getColumnNumber(); |
| 237 | + // line and column are 1-based, so col should be 1 more than caretIndex |
| 238 | + if (col - 1 == caretIndex) { |
| 239 | + passed("col: " + col + " caret: " + caretIndex); |
| 240 | + } else { |
| 241 | + failed("col: " + col + " caret: " + caretIndex); |
| 242 | + } |
| 243 | + |
| 244 | + checking("coords-start-end: " + srcFileLine); |
| 245 | + String fileStr = readFile(".", msgLine.substring(0, msgLine.indexOf(":"))); |
| 246 | + int start = (int) diag.getStartPosition(); |
| 247 | + int end = (int) diag.getEndPosition(); |
| 248 | + String fileRange = fileStr.substring(start, end); |
| 249 | + if (fileRange.equals(detail)) { |
| 250 | + passed("file: >>>" + fileRange + "<<< message: >>>" + detail + "<<<"); |
| 251 | + } else { |
| 252 | + failed("file: >>>" + fileRange + "<<< message: >>>" + detail + "<<<"); |
| 253 | + } |
| 254 | + } |
| 255 | + |
| 256 | + checking("message-caret: " + srcFileLine); |
| 257 | + int srcIndex = srcLine.indexOf(detail); |
| 258 | + int pad = (detail.length() - 1) / 2; |
| 259 | + int srcIndexPad = srcIndex + pad; |
| 260 | + if (srcIndexPad == caretIndex) { |
| 261 | + passed("src: " + srcIndexPad + " caret: " + caretIndex); |
| 262 | + } else { |
| 263 | + failed("src: " + srcIndexPad + " caret: " + caretIndex); |
| 264 | + } |
| 265 | + } |
| 266 | +} |
| 267 | + |
0 commit comments