1 /*
2  * Copyright (c) 2016, 2017, 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 import java.io.IOException;
25 import java.io.PrintWriter;
26 import java.io.StringWriter;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.util.Arrays;
30 import java.util.HashSet;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Set;
34 import java.util.TreeSet;
35 import java.util.stream.Collectors;
36 
37 import javax.lang.model.SourceVersion;
38 import javax.lang.model.element.Element;
39 import javax.lang.model.element.ElementKind;
40 import javax.lang.model.element.ModuleElement;
41 import javax.lang.model.element.PackageElement;
42 import javax.lang.model.element.TypeElement;
43 import javax.lang.model.util.ElementFilter;
44 import javax.lang.model.util.SimpleElementVisitor9;
45 
46 import jdk.javadoc.doclet.Doclet;
47 import jdk.javadoc.doclet.DocletEnvironment;
48 import jdk.javadoc.doclet.Reporter;
49 
50 import toolbox.JavadocTask;
51 import toolbox.Task;
52 import toolbox.Task.Expect;
53 import toolbox.TestRunner;
54 import toolbox.ToolBox;
55 
56 import static toolbox.Task.OutputKind.*;
57 
58 /**
59  * Base class for module tests.
60  */
61 public class ModuleTestBase extends TestRunner {
62 
63     // Field Separator
64     private static final String FS = " ";
65 
66     protected ToolBox tb;
67     private final Class<?> docletClass;
68 
69     private Task.Result currentTask = null;
70 
ModuleTestBase()71     ModuleTestBase() {
72         super(System.err);
73         tb = new ToolBox();
74         ClassLoader cl = ModuleTestBase.class.getClassLoader();
75         try {
76             docletClass = cl.loadClass("ModuleTestBase$ModulesTesterDoclet");
77         } catch (ClassNotFoundException cfe) {
78             throw new Error(cfe);
79         }
80     }
81 
82     /**
83      * Execute methods annotated with @Test, and throw an exception if any
84      * errors are reported..
85      *
86      * @throws Exception if any errors occurred
87      */
runTests()88     protected void runTests() throws Exception {
89         runTests(m -> new Object[] { Paths.get(m.getName()) });
90     }
91 
execTask(String... args)92     Task.Result execTask(String... args) {
93         return execTask0(false, args);
94     }
95 
execNegativeTask(String... args)96     Task.Result execNegativeTask(String... args) {
97         return execTask0(true, args);
98     }
99 
execTask0(boolean isNegative, String... args)100     private Task.Result execTask0(boolean isNegative, String... args) {
101         JavadocTask et = new JavadocTask(tb, Task.Mode.API);
102         et.docletClass(docletClass);
103         //Arrays.asList(args).forEach((a -> System.err.println("arg: " + a)));
104         System.err.println(Arrays.asList(args));
105         currentTask = isNegative
106                 ? et.options(args).run(Expect.FAIL)
107                 : et.options(args).run();
108         return currentTask;
109     }
110 
findHtmlFiles(Path... paths)111     Path[] findHtmlFiles(Path... paths) throws IOException {
112         return tb.findFiles(".html", paths);
113     }
114 
grep(String regex, Path file)115     boolean grep(String regex, Path file) throws Exception {
116         List<String> lines = tb.readAllLines(file);
117         List<String> foundList = tb.grep(regex, lines);
118         return !foundList.isEmpty();
119     }
120 
normalize(String in)121     String normalize(String in) {
122         return in.replace('\\', '/');
123     }
124 
checkModulesSpecified(String... args)125     void checkModulesSpecified(String... args) throws Exception {
126         for (String arg : args) {
127             checkDocletOutputPresent("Specified", ElementKind.MODULE, arg);
128         }
129     }
130 
checkPackagesSpecified(String... args)131     void checkPackagesSpecified(String... args) throws Exception {
132         for (String arg : args) {
133             checkDocletOutputPresent("Specified", ElementKind.PACKAGE, arg);
134         }
135     }
136 
checkTypesSpecified(String... args)137     void checkTypesSpecified(String... args) throws Exception {
138         for (String arg : args) {
139             checkDocletOutputPresent("Specified", ElementKind.CLASS, arg);
140         }
141     }
142 
checkModulesIncluded(String... args)143     void checkModulesIncluded(String... args) throws Exception {
144         for (String arg : args) {
145             checkDocletOutputPresent("Included", ElementKind.MODULE, arg);
146         }
147     }
148 
checkPackagesIncluded(String... args)149     void checkPackagesIncluded(String... args) throws Exception {
150         for (String arg : args) {
151             checkDocletOutputPresent("Included", ElementKind.PACKAGE, arg);
152         }
153     }
154 
checkTypesIncluded(String... args)155     void checkTypesIncluded(String... args) throws Exception {
156         for (String arg : args) {
157             checkDocletOutputPresent("Included", ElementKind.CLASS, arg);
158         }
159     }
160 
checkTypesSelected(String... args)161     void checkTypesSelected(String... args) throws Exception {
162         for (String arg : args) {
163             checkDocletOutputPresent("Selected", ElementKind.CLASS, arg);
164         }
165     }
166 
checkMembersSelected(String... args)167     void checkMembersSelected(String... args) throws Exception {
168         for (String arg : args) {
169             checkDocletOutputPresent("Selected", ElementKind.METHOD, arg);
170         }
171     }
172 
checkModuleMode(String mode)173     void checkModuleMode(String mode) throws Exception {
174         assertPresent("^ModuleMode" + FS + mode);
175     }
176 
checkStringPresent(String regex)177     void checkStringPresent(String regex) throws Exception {
178         assertPresent(regex);
179     }
180 
checkDocletOutputPresent(String category, ElementKind kind, String regex)181     void checkDocletOutputPresent(String category, ElementKind kind, String regex) throws Exception {
182         assertPresent("^" + category + " " + kind.toString() + " " + regex);
183     }
184 
assertPresent(String regex)185     void assertPresent(String regex) throws Exception {
186         assertPresent(regex, STDOUT);
187     }
188 
assertMessagePresent(String regex)189     void assertMessagePresent(String regex) throws Exception {
190         assertPresent(regex, Task.OutputKind.DIRECT);
191     }
192 
assertMessageNotPresent(String regex)193     void assertMessageNotPresent(String regex) throws Exception {
194         assertNotPresent(regex, Task.OutputKind.DIRECT);
195     }
196 
assertPresent(String regex, Task.OutputKind kind)197     void assertPresent(String regex, Task.OutputKind kind) throws Exception {
198         List<String> foundList = tb.grep(regex, currentTask.getOutputLines(kind));
199         if (foundList.isEmpty()) {
200             dumpDocletDiagnostics();
201             throw new Exception(regex + " not found in: " + kind);
202         }
203     }
204 
assertNotPresent(String regex, Task.OutputKind kind)205     void assertNotPresent(String regex, Task.OutputKind kind) throws Exception {
206         List<String> foundList = tb.grep(regex, currentTask.getOutputLines(kind));
207         if (!foundList.isEmpty()) {
208             dumpDocletDiagnostics();
209             throw new Exception(regex + " found in: " + kind);
210         }
211     }
212 
dumpDocletDiagnostics()213     void dumpDocletDiagnostics() {
214         for (Task.OutputKind kind : Task.OutputKind.values()) {
215             String output = currentTask.getOutput(kind);
216             if (output != null && !output.isEmpty()) {
217                 System.err.println("<" + kind + ">");
218                 System.err.println(output);
219             }
220         }
221     }
222 
checkModulesNotSpecified(String... args)223     void checkModulesNotSpecified(String... args) throws Exception {
224         for (String arg : args) {
225             checkDocletOutputAbsent("Specified", ElementKind.MODULE, arg);
226         }
227     }
228 
checkPackagesNotSpecified(String... args)229     void checkPackagesNotSpecified(String... args) throws Exception {
230         for (String arg : args) {
231             checkDocletOutputAbsent("Specified", ElementKind.PACKAGE, arg);
232         }
233     }
234 
checkTypesNotSpecified(String... args)235     void checkTypesNotSpecified(String... args) throws Exception {
236         for (String arg : args) {
237             checkDocletOutputAbsent("Specified", ElementKind.CLASS, arg);
238         }
239     }
240 
checkModulesNotIncluded(String... args)241     void checkModulesNotIncluded(String... args) throws Exception {
242         for (String arg : args) {
243             checkDocletOutputAbsent("Included", ElementKind.MODULE, arg);
244         }
245     }
246 
checkPackagesNotIncluded(String... args)247     void checkPackagesNotIncluded(String... args) throws Exception {
248         for (String arg : args) {
249             checkDocletOutputAbsent("Included", ElementKind.PACKAGE, arg);
250         }
251     }
252 
checkTypesNotIncluded(String... args)253     void checkTypesNotIncluded(String... args) throws Exception {
254         for (String arg : args) {
255             checkDocletOutputAbsent("Included", ElementKind.CLASS, arg);
256         }
257     }
258 
checkMembersNotSelected(String... args)259     void checkMembersNotSelected(String... args) throws Exception {
260         for (String arg : args) {
261             checkDocletOutputAbsent("Selected", ElementKind.METHOD, arg);
262         }
263     }
264 
checkStringAbsent(String regex)265     void checkStringAbsent(String regex) throws Exception {
266         assertAbsent(regex);
267     }
268 
checkDocletOutputAbsent(String category, ElementKind kind, String regex)269     void checkDocletOutputAbsent(String category, ElementKind kind, String regex) throws Exception {
270         assertAbsent("^" + category + FS + kind.toString() + FS + regex);
271     }
272 
assertAbsent(String regex)273     void assertAbsent(String regex) throws Exception {
274         assertAbsent(regex, STDOUT);
275     }
276 
assertAbsent(String regex, Task.OutputKind kind)277     void assertAbsent(String regex, Task.OutputKind kind) throws Exception {
278         List<String> foundList = tb.grep(regex, currentTask.getOutputLines(kind));
279         if (!foundList.isEmpty()) {
280             dumpDocletDiagnostics();
281             throw new Exception(regex + " found in: " + kind);
282         }
283     }
284 
285     public static class ModulesTesterDoclet implements Doclet {
286         StringWriter sw = new StringWriter();
287         PrintWriter ps = new PrintWriter(sw);
288 
289         DocletEnvironment docEnv = null;
290 
291         boolean hasDocComments = false;
292 
hasDocComments(Element e)293         String hasDocComments(Element e) {
294             String comment = docEnv.getElementUtils().getDocComment(e);
295             return comment != null && !comment.isEmpty()
296                     ? "hasDocComments"
297                     : "noDocComments";
298         }
299 
300         // csv style output, for simple regex verification
printDataSet(String header, Set<? extends Element> set)301         void printDataSet(String header, Set<? extends Element> set) {
302             for (Element e : set) {
303                 ps.print(header);
304                 new SimpleElementVisitor9<Void, Void>() {
305                     @Override
306                     public Void visitModule(ModuleElement e, Void p) {
307                         ps.print(FS);
308                         ps.print(e.getKind());
309                         ps.print(FS);
310                         ps.print(e.getQualifiedName());
311                         if (hasDocComments) {
312                             ps.print(FS);
313                             ps.print(hasDocComments(e));
314                         }
315                         ps.println();
316                         return null;
317                     }
318 
319                     @Override
320                     public Void visitPackage(PackageElement e, Void p) {
321                         ps.print(FS);
322                         ps.print(e.getKind());
323                         ps.print(FS);
324                         ps.print(e.getQualifiedName());
325                         if (hasDocComments) {
326                             ps.print(FS);
327                             ps.print(hasDocComments(e));
328                         }
329                         ps.println();
330                         return null;
331                     }
332 
333                     @Override
334                     public Void visitType(TypeElement e, Void p) {
335                         ps.print(FS);
336                         ps.print(ElementKind.CLASS);
337                         ps.print(FS);
338                         ps.print(e.getQualifiedName());
339                         if (hasDocComments) {
340                             ps.print(FS);
341                             ps.print(hasDocComments(e));
342                         }
343                         ps.println();
344                         return null;
345                     }
346 
347                     @Override
348                     protected Void defaultAction(Element e, Void p) {
349                         Element encl = e.getEnclosingElement();
350                         CharSequence fqn = new SimpleElementVisitor9<CharSequence, Void>() {
351                             @Override
352                             public CharSequence visitModule(ModuleElement e, Void p) {
353                                 return e.getQualifiedName();
354                             }
355 
356                             @Override
357                             public CharSequence visitType(TypeElement e, Void p) {
358                                 return e.getQualifiedName();
359                             }
360 
361                             @Override
362                             public CharSequence visitPackage(PackageElement e, Void p) {
363                                 return e.getQualifiedName();
364                             }
365 
366                         }.visit(encl);
367 
368                         ps.print(FS);
369                         ps.print(ElementKind.METHOD); // always METHOD
370                         ps.print(FS);
371                         ps.print(fqn);
372                         ps.print(".");
373                         ps.print(e.getSimpleName());
374                         if (hasDocComments) {
375                             ps.print(FS);
376                             ps.print(hasDocComments(e));
377                         }
378                         ps.println();
379                         return null;
380                     }
381                 }.visit(e);
382             }
383         }
384 
385         @Override
run(DocletEnvironment docenv)386         public boolean run(DocletEnvironment docenv) {
387             this.docEnv = docenv;
388             ps.println("ModuleMode" + FS + docenv.getModuleMode());
389             printDataSet("Specified", docenv.getSpecifiedElements());
390             printDataSet("Included", docenv.getIncludedElements());
391             printDataSet("Selected", getAllSelectedElements(docenv));
392             System.out.println(sw);
393             return true;
394         }
395 
getAllSelectedElements(DocletEnvironment docenv)396         Set<Element> getAllSelectedElements(DocletEnvironment docenv) {
397             Set<Element> result = new TreeSet<Element>((Element e1, Element e2) -> {
398                 // some grouping by kind preferred
399                 int rc = e1.getKind().compareTo(e2.getKind());
400                 if (rc != 0) return rc;
401                 rc = e1.toString().compareTo(e2.toString());
402                 if (rc != 0) return rc;
403                 return Integer.compare(e1.hashCode(), e2.hashCode());
404             });
405             Set<? extends Element> elements = docenv.getIncludedElements();
406             for (ModuleElement me : ElementFilter.modulesIn(elements)) {
407                 addEnclosedElements(docenv, result, me);
408             }
409             for (PackageElement pe : ElementFilter.packagesIn(elements)) {
410                 ModuleElement mdle = docenv.getElementUtils().getModuleOf(pe);
411                 if (mdle != null)
412                     addEnclosedElements(docenv, result, mdle);
413                 addEnclosedElements(docenv, result, pe);
414             }
415             for (TypeElement te : ElementFilter.typesIn(elements)) {
416                 addEnclosedElements(docenv, result, te);
417             }
418             return result;
419         }
420 
addEnclosedElements(DocletEnvironment docenv, Set<Element> result, Element e)421         void addEnclosedElements(DocletEnvironment docenv, Set<Element> result, Element e) {
422             List<Element> elems = e.getEnclosedElements().stream()
423                     .filter(el -> docenv.isIncluded(el))
424                     .collect(Collectors.toList());
425             result.addAll(elems);
426             for (TypeElement t : ElementFilter.typesIn(elems)) {
427                 addEnclosedElements(docenv, result, t);
428             }
429         }
430 
431         @Override
getSupportedOptions()432         public Set<Doclet.Option> getSupportedOptions() {
433             Option[] options = {
434                 new Option() {
435                     private final List<String> someOption = Arrays.asList(
436                             "-hasDocComments"
437                     );
438 
439                     @Override
440                     public int getArgumentCount() {
441                         return 0;
442                     }
443 
444                     @Override
445                     public String getDescription() {
446                         return "print disposition of doc comments on an element";
447                     }
448 
449                     @Override
450                     public Option.Kind getKind() {
451                         return Option.Kind.STANDARD;
452                     }
453 
454                     @Override
455                     public List<String> getNames() {
456                         return someOption;
457                     }
458 
459                     @Override
460                     public String getParameters() {
461                         return "flag";
462                     }
463 
464                     @Override
465                     public boolean process(String opt, List<String> arguments) {
466                         hasDocComments = true;
467                         return true;
468                     }
469                 }
470             };
471             return new HashSet<>(Arrays.asList(options));
472         }
473 
474         @Override
init(Locale locale, Reporter reporter)475         public void init(Locale locale, Reporter reporter) {}
476 
477         @Override
getName()478         public String getName() {
479             return "ModulesTesterDoclet";
480         }
481 
482         @Override
getSupportedSourceVersion()483         public SourceVersion getSupportedSourceVersion() {
484             return SourceVersion.latest();
485         }
486     }
487 }
488