Skip to content

Commit 858c53b

Browse files
committed
8274211: Test man page that options are documented
Backport-of: 734d1fbd33be0aa20b26e6e8c776709f478069de
1 parent 919964a commit 858c53b

File tree

1 file changed

+287
-0
lines changed

1 file changed

+287
-0
lines changed
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
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 8274211
27+
* @summary Test man page that options are documented
28+
* @modules jdk.javadoc/jdk.javadoc.internal.tool:+open
29+
* @run main CheckManPageOptions
30+
*/
31+
32+
import jdk.javadoc.doclet.Doclet;
33+
import jdk.javadoc.doclet.StandardDoclet;
34+
import jdk.javadoc.internal.tool.ToolOptions;
35+
36+
import java.io.IOException;
37+
import java.io.PrintStream;
38+
import java.lang.reflect.Constructor;
39+
import java.lang.reflect.Field;
40+
import java.lang.reflect.Method;
41+
import java.nio.file.Files;
42+
import java.nio.file.Path;
43+
import java.util.ArrayList;
44+
import java.util.Collection;
45+
import java.util.List;
46+
import java.util.Locale;
47+
import java.util.TreeSet;
48+
import java.util.regex.Matcher;
49+
import java.util.regex.Pattern;
50+
import java.util.stream.Collectors;
51+
52+
/**
53+
* Checks the set of options found by fuzzy-parsing the troff or Markdown versions
54+
* of the javadoc man page against the set of options declared in the source code.
55+
*/
56+
public class CheckManPageOptions {
57+
public static void main(String... args) throws Exception {
58+
new CheckManPageOptions().run(args);
59+
}
60+
61+
static final PrintStream out = System.err;
62+
63+
// FIXME: JDK-8274295, JDK-8266666
64+
List<String> MISSING_IN_MAN_PAGE = List.of(
65+
"--legal-notices",
66+
"--link-platform-properties",
67+
"--no-platform-links",
68+
"--since",
69+
"--since-label",
70+
"--snippet-path");
71+
72+
void run(String... args) throws Exception {
73+
var file = args.length == 0 ? findDefaultFile() : Path.of(args[0]);
74+
out.println("File: " + file);
75+
out.println();
76+
77+
var manPageOptions = getManPageOptions(file);
78+
out.println("Man page options: " + manPageOptions);
79+
out.println();
80+
81+
var toolOptions = getToolOptions();
82+
out.println("ToolOptions: " + toolOptions);
83+
out.println();
84+
85+
var docletOptions = getDocletOptions();
86+
out.println("DocletOptions: " + docletOptions);
87+
out.println();
88+
89+
var toolDocletOnly = new TreeSet<String>();
90+
toolDocletOnly.addAll(toolOptions);
91+
toolDocletOnly.addAll(docletOptions);
92+
toolDocletOnly.removeAll(manPageOptions);
93+
toolDocletOnly.removeAll(MISSING_IN_MAN_PAGE);
94+
if (!toolDocletOnly.isEmpty()) {
95+
error("The following options are defined by the tool or doclet, but not defined in the man page:\n"
96+
+ toSimpleList(toolDocletOnly));
97+
}
98+
99+
var manPageOnly = new TreeSet<String>();
100+
manPageOnly.addAll(manPageOptions);
101+
manPageOnly.removeAll(toolOptions);
102+
manPageOnly.removeAll(docletOptions);
103+
if (!manPageOnly.isEmpty()) {
104+
error("The following options are defined in the man page, but not defined by the tool or doclet:\n"
105+
+ toSimpleList(manPageOnly));
106+
}
107+
108+
if (!MISSING_IN_MAN_PAGE.isEmpty()) {
109+
var notMissing = new TreeSet<>(MISSING_IN_MAN_PAGE);
110+
notMissing.retainAll(manPageOptions);
111+
if (!notMissing.isEmpty()) {
112+
error("The following options were declared as missing, but were found on the man page:\n"
113+
+ toSimpleList(notMissing));
114+
}
115+
116+
out.println("NOTE: the following options are currently excluded and need to be documented in the man page:");
117+
out.println(toSimpleList(MISSING_IN_MAN_PAGE));
118+
}
119+
120+
if (errors > 0) {
121+
out.println(errors + " errors found");
122+
throw new Exception(errors + " errors found");
123+
}
124+
}
125+
126+
int errors = 0;
127+
void error(String message) {
128+
("Error: " + message).lines().forEach(out::println);
129+
errors++;
130+
}
131+
132+
String toSimpleList(Collection<String> items) {
133+
return items.stream().collect(Collectors.joining(", ", " ", ""));
134+
}
135+
136+
Path findDefaultFile() {
137+
return findRootDir().resolve("src/jdk.javadoc/share/man/javadoc.1");
138+
}
139+
140+
Path findRootDir() {
141+
Path dir = Path.of(System.getProperty("test.src", ".")).toAbsolutePath();
142+
while (dir != null) {
143+
if (Files.exists(dir.resolve("src"))) {
144+
return dir;
145+
} else {
146+
Path openDir = dir.resolve("open");
147+
if (Files.exists(openDir.resolve("src"))) {
148+
return openDir;
149+
}
150+
}
151+
dir = dir.getParent();
152+
}
153+
throw new IllegalStateException("cannot find root dir");
154+
}
155+
156+
List<String> getToolOptions() throws Error {
157+
try {
158+
Class<ToolOptions> toolOptionsClass = ToolOptions.class;
159+
160+
Constructor<ToolOptions> constr = toolOptionsClass.getDeclaredConstructor();
161+
constr.setAccessible(true);
162+
163+
Method getSupportedOptions = toolOptionsClass.getMethod("getSupportedOptions");
164+
Class<?> toolOptionClass = List.of(toolOptionsClass.getDeclaredClasses()).stream()
165+
.filter(c -> c.getSimpleName().equals("ToolOption"))
166+
.findFirst()
167+
.orElseThrow();
168+
169+
Field kindField = toolOptionClass.getDeclaredField("kind");
170+
kindField.setAccessible(true);
171+
Method getNames = toolOptionClass.getDeclaredMethod("getNames");
172+
getNames.setAccessible(true);
173+
174+
ToolOptions t = constr.newInstance();
175+
var list = new ArrayList<String>();
176+
var options = (List<?>) getSupportedOptions.invoke(t);
177+
for (var option : options) {
178+
Object kind = kindField.get(option);
179+
if (kind.toString().equals("HIDDEN")) {
180+
continue;
181+
}
182+
183+
@SuppressWarnings("unchecked")
184+
var oNames = (List<String>) getNames.invoke(option);
185+
oNames.stream()
186+
.filter(o -> !o.equals("@"))
187+
.forEach(list::add);
188+
}
189+
return list;
190+
} catch (ReflectiveOperationException e) {
191+
throw new Error(e);
192+
}
193+
}
194+
195+
List<String> getDocletOptions() {
196+
StandardDoclet d = new StandardDoclet();
197+
d.init(Locale.getDefault(), null);
198+
return getDocletOptions(d);
199+
}
200+
201+
List<String> getDocletOptions(Doclet d) {
202+
return d.getSupportedOptions().stream()
203+
.filter(o -> o.getKind() != Doclet.Option.Kind.OTHER)
204+
.flatMap(o -> o.getNames().stream())
205+
.map(n -> n.replaceAll(":$", ""))
206+
.toList();
207+
}
208+
209+
List<String> getManPageOptions(Path file) throws IOException {
210+
String page = Files.readString(file);
211+
String name = file.getFileName().toString();
212+
String extn = name.substring(name.lastIndexOf('.'));
213+
return switch (extn) {
214+
case ".1" -> parseNRoff(page);
215+
case ".md" -> parseMarkdown(page);
216+
default -> throw new IllegalArgumentException(file.toString());
217+
};
218+
}
219+
220+
List<String> parseNRoff(String page) {
221+
var list = new ArrayList<String>();
222+
223+
// In the troff man page, options are defined in one of two forms:
224+
// 1. options delegated to javac appear in pairs of lines of the form
225+
// .IP \[bu] 2
226+
// \f[CB]\-....
227+
// 2. options implemented by the tool or doclet appear in lines of the form
228+
// .B \f[CB]\-...
229+
230+
Pattern p1 = Pattern.compile("\\R" + Pattern.quote(".IP \\[bu] 2") + "\\R" + Pattern.quote("\\f[CB]\\-") + ".*");
231+
Pattern p2 = Pattern.compile("\\R" + Pattern.quote(".B \\f[CB]\\-") + ".*");
232+
Pattern outer = Pattern.compile("(" + p1.pattern() + "|" + p2.pattern() + ")");
233+
Matcher outerMatcher = outer.matcher(page);
234+
235+
// In the defining areas, option names are represented as follows:
236+
// \f[CB]OPTION\f[R] or \f[CB]OPTION:
237+
// where OPTION is the shortest string not containing whitespace or colon,
238+
// and in which all '-' characters are escaped with a single backslash.
239+
240+
Pattern inner = Pattern.compile("\\s\\\\f\\[CB](\\\\-[^ :]+?)(:|\\\\f\\[R])");
241+
242+
while (outerMatcher.find()) {
243+
String lines = outerMatcher.group();
244+
out.println("found:" + lines + "\n");
245+
246+
Matcher innerMatcher = inner.matcher(lines);
247+
while (innerMatcher.find()) {
248+
String option = innerMatcher.group(1).replace("\\-", "-");
249+
list.add(option);
250+
}
251+
}
252+
253+
return list;
254+
}
255+
256+
List<String> parseMarkdown(String page) {
257+
var list = new ArrayList<String>();
258+
// In the Markdown man page, options are defined in one of two forms:
259+
// 1. options delegated to javac appear in lines of the form
260+
// - `-...
261+
// 2. options implemented by the tool or doclet appear in lines of the form
262+
// `-...`
263+
264+
Pattern p1 = Pattern.compile("\\R- `-.*");
265+
Pattern p2 = Pattern.compile("\\R`-.*");
266+
Pattern outer = Pattern.compile("(" + p1.pattern() + "|" + p2.pattern() + ")");
267+
Matcher outerMatcher = outer.matcher(page);
268+
269+
// In the defining areas, option names are represented as follows:
270+
// `OPTION`
271+
// where OPTION is the shortest string not containing whitespace or colon
272+
Pattern inner = Pattern.compile("\\s`([^:`]+)");
273+
274+
while (outerMatcher.find()) {
275+
String lines = outerMatcher.group();
276+
out.println("found:" + lines + "\n");
277+
278+
Matcher innerMatcher = inner.matcher(lines);
279+
while (innerMatcher.find()) {
280+
String option = innerMatcher.group(1);
281+
list.add(option);
282+
}
283+
}
284+
285+
return list;
286+
}
287+
}

0 commit comments

Comments
 (0)