View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one   *
3    * or more contributor license agreements.  See the NOTICE file *
4    * distributed with this work for additional information        *
5    * regarding copyright ownership.  The ASF licenses this file   *
6    * to you under the Apache License, Version 2.0 (the            *
7    * "License"); you may not use this file except in compliance   *
8    * with the License.  You may obtain a copy of the License at   *
9    *                                                              *
10   *   http://www.apache.org/licenses/LICENSE-2.0                 *
11   *                                                              *
12   * Unless required by applicable law or agreed to in writing,   *
13   * software distributed under the License is distributed on an  *
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15   * KIND, either express or implied.  See the License for the    *
16   * specific language governing permissions and limitations      *
17   * under the License.                                           *
18   */
19  package org.apache.rat;
20  
21  import java.io.ByteArrayOutputStream;
22  import java.io.File;
23  import java.io.FileReader;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.OutputStream;
27  import java.io.PrintStream;
28  import java.nio.charset.StandardCharsets;
29  import java.nio.file.Files;
30  import java.util.Arrays;
31  import java.util.Collections;
32  import java.util.List;
33  import java.util.concurrent.atomic.AtomicBoolean;
34  import java.util.regex.Pattern;
35  import javax.xml.xpath.XPath;
36  import org.apache.commons.cli.Option;
37  import org.apache.commons.cli.Options;
38  import org.apache.commons.io.IOUtils;
39  import org.apache.commons.lang3.tuple.ImmutablePair;
40  import org.apache.commons.lang3.tuple.Pair;
41  import org.apache.commons.xml.secure.SecureXPathFactory;
42  import org.apache.rat.api.RatException;
43  import org.apache.rat.commandline.Arg;
44  import org.apache.rat.commandline.StyleSheets;
45  import org.apache.rat.config.exclusion.StandardCollection;
46  import org.apache.rat.config.results.ClaimValidator;
47  import org.apache.rat.configuration.builders.SpdxBuilder;
48  import org.apache.rat.help.Help;
49  import org.apache.rat.license.ILicense;
50  import org.apache.rat.license.ILicenseFamily;
51  import org.apache.rat.license.LicenseSetFactory;
52  import org.apache.rat.report.claim.ClaimStatistic;
53  import org.apache.rat.test.AbstractOptionsProvider;
54  import org.apache.rat.test.utils.OptionFormatter;
55  import org.apache.rat.test.utils.Resources;
56  import org.apache.rat.utils.FileUtils;
57  import org.apache.rat.testhelpers.TestingLog;
58  import org.apache.rat.testhelpers.TextUtils;
59  import org.apache.rat.testhelpers.XmlUtils;
60  import org.apache.rat.utils.DefaultLog;
61  import org.apache.rat.utils.Log;
62  import org.junit.jupiter.params.provider.ArgumentsProvider;
63  import org.w3c.dom.Document;
64  
65  import static org.apache.rat.commandline.Arg.HELP_LICENSES;
66  import static org.assertj.core.api.Assertions.assertThat;
67  import static org.assertj.core.api.Assertions.assertThatThrownBy;
68  import static org.assertj.core.api.Fail.fail;
69  import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
70  
71  /**
72   * A class to provide the Options and tests to the testOptionsUpdateConfig.
73   */
74  class ReporterOptionsProvider extends AbstractOptionsProvider implements ArgumentsProvider {
75      static File sourceDir;
76  
77      /**
78       * A flag to determine if help was called.
79       */
80      final AtomicBoolean helpCalled = new AtomicBoolean(false);
81  
82      public ReporterOptionsProvider() {
83          super("ReporterOptionsProvider", ReporterOptionsTest.testPath.toFile());
84          processTestFunctionAnnotations();
85          testMap.put("addLicense", this::addLicenseTest);
86          testMap.remove("add-license");
87          testMap.put("dir", () -> DefaultLog.getInstance().info("--dir has no valid test"));
88          super.validate(Collections.emptyList());
89      }
90  
91      /**
92       * Generate a ReportConfiguration from a set of arguments.
93       * Forces the {@code helpCalled} flag to be reset.
94       *
95       * @param args the arguments.
96       * @return a ReportConfiguration
97       * @throws IOException on critical error.
98       */
99      @Override
100     protected final ReportConfiguration generateConfig(List<Pair<Option, String[]>> args) throws IOException {
101         return generateConfig(args, false);
102     }
103 
104     protected final ReportConfiguration generateConfig(List<Pair<Option, String[]>> args, boolean helpExpected) throws IOException {
105         if (sourceDir == null) {
106             throw new IOException("sourceDir not set");
107         }
108         helpCalled.set(false);
109         ReportConfiguration config = OptionCollection.parseCommands(sourceDir, extractArgs(args), o -> helpCalled.set(true), true);
110         assertThat(helpCalled.get()).as("Help was called").isEqualTo(helpExpected);
111         if (config != null && !config.hasSource()) {
112             config.addSource(OptionCollection.getReportable(sourceDir, config));
113         }
114         return config;
115     }
116 
117     /**
118      * Creates the option/.rat directory
119      * @param option the name for the sourceDirectory.
120      */
121     private File configureRatDir(Option option) {
122         configureSourceDir(option);
123         File ratDir = new File(sourceDir, ".rat");
124         FileUtils.mkDir(ratDir);
125         return ratDir;
126     }
127 
128     /**
129      * Creates the srcDir.
130      * @param option the name for the srcDir.
131      */
132     private void configureSourceDir(Option option) {
133         sourceDir = new File(baseDir, OptionFormatter.getName(option));
134         FileUtils.mkDir(sourceDir);
135     }
136 
137     /**
138      * Verify that without args the report is ok.
139      */
140     private void validateNoArgSetup() throws IOException, RatException {
141         TestingLog log = new TestingLog();
142         DefaultLog.setInstance(log);
143         try {
144             ReportConfiguration config = generateConfig(Collections.emptyList());
145             Reporter reporter = new Reporter(config);
146             Reporter.Output output = reporter.execute();
147             ClaimValidator validator = config.getClaimValidator();
148             assertThat(validator.listIssues(output.getStatistic())).isEmpty();
149         } finally {
150             DefaultLog.setInstance(null);
151         }
152     }
153 
154     @OptionCollectionTest.TestFunction
155     protected void addLicenseTest() {
156         editLicenseTest(Arg.EDIT_ADD.find("addLicense"));
157     }
158 
159     @OptionCollectionTest.TestFunction
160     protected void editLicenseTest() {
161         editLicenseTest(Arg.EDIT_ADD.find("edit-license"));
162     }
163 
164     private void editLicenseTest(final Option option) {
165         assertDoesNotThrow(() -> {
166             configureSourceDir(option);
167             ReportConfiguration config = generateConfig(ImmutablePair.of(option, null));
168             File testFile = writeFile("NoLicense.java", "class NoLicense {}");
169             File resultFile = new File(sourceDir, "NoLicense.java.new");
170             FileUtils.delete(resultFile);
171 
172             Reporter reporter = new Reporter(config);
173             Reporter.Output output = reporter.execute();
174             assertThat(output.getStatistic()).isNotNull();
175             String contents = String.join("\n", IOUtils.readLines(new FileReader(testFile)));
176             assertThat(contents).isEqualTo("class NoLicense {}");
177             assertThat(resultFile).exists();
178             contents = String.join("\n", IOUtils.readLines(new FileReader(resultFile)));
179             assertThat(contents).isEqualTo("""
180                     /*
181                      * Licensed to the Apache Software Foundation (ASF) under one
182                      * or more contributor license agreements.  See the NOTICE file
183                      * distributed with this work for additional information
184                      * regarding copyright ownership.  The ASF licenses this file
185                      * to you under the Apache License, Version 2.0 (the
186                      * "License"); you may not use this file except in compliance
187                      * with the License.  You may obtain a copy of the License at
188                      *\s
189                      *   http://www.apache.org/licenses/LICENSE-2.0
190                      *\s
191                      * Unless required by applicable law or agreed to in writing,
192                      * software distributed under the License is distributed on an
193                      * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
194                      * KIND, either express or implied.  See the License for the
195                      * specific language governing permissions and limitations
196                      * under the License.
197                      */
198                     
199                     class NoLicense {}""");
200         });
201     }
202 
203     protected File writeFile(String name) {
204         return FileUtils.writeFile(sourceDir, name, Collections.singletonList(name));
205     }
206 
207     protected File writeFile(String name, String content) {
208         return FileUtils.writeFile(sourceDir, name, Collections.singletonList(content));
209     }
210 
211     protected File writeFile(String name, Iterable<String> content) {
212         return FileUtils.writeFile(sourceDir, name, content);
213     }
214 
215     @OptionCollectionTest.TestFunction
216     private void execLicensesDeniedTest(final Option option, final String[] args) {
217         assertDoesNotThrow(() -> {
218             configureSourceDir(option);
219             File illumosFile = writeFile("illumousFile.java", "The contents of this file are " +
220                     "subject to the terms of the Common Development and Distribution License (the \"License\") You " +
221                     "may not use this file except in compliance with the License.");
222 
223             validateNoArgSetup();
224 
225             ReportConfiguration config = generateConfig(ImmutablePair.of(option, args));
226             Reporter reporter = new Reporter(config);
227             Reporter.Output output = reporter.execute();
228             ClaimValidator validator = config.getClaimValidator();
229             assertThat(validator.listIssues(output.getStatistic())).containsExactly("UNAPPROVED");
230         });
231     }
232 
233     @OptionCollectionTest.TestFunction
234     protected void licensesDeniedTest() {
235         execLicensesDeniedTest(Arg.LICENSES_DENIED.find("licenses-denied"), new String[]{"ILLUMOS"});
236     }
237 
238     @OptionCollectionTest.TestFunction
239     protected void licensesDeniedFileTest() {
240         Option option = Arg.LICENSES_DENIED_FILE.find("licenses-denied-file");
241         File outputFile = FileUtils.writeFile(configureRatDir(option), "licensesDenied.txt", Collections.singletonList("ILLUMOS"));
242         execLicensesDeniedTest(option, new String[]{outputFile.getAbsolutePath()});
243     }
244 
245     private void noDefaultsTest(final Option option) {
246         assertDoesNotThrow(() -> {
247             configureSourceDir(option);
248             File testFile = writeFile("Test.java", Arrays.asList("/*\n", "SPDX-License-Identifier: Apache-2.0\n",
249                     "*/\n\n", "class Test {}\n"));
250 
251             validateNoArgSetup();
252             ReportConfiguration config = generateConfig(ImmutablePair.of(option, null));
253             Reporter reporter = new Reporter(config);
254             assertThatThrownBy(reporter::execute)
255                     .isInstanceOf(RatException.class);
256         });
257     }
258 
259     @OptionCollectionTest.TestFunction
260     protected void noDefaultLicensesTest() {
261         noDefaultsTest(Arg.CONFIGURATION_NO_DEFAULTS.find("no-default-licenses"));
262     }
263 
264     @OptionCollectionTest.TestFunction
265     protected void configurationNoDefaultsTest() {
266         noDefaultsTest(Arg.CONFIGURATION_NO_DEFAULTS.find("configuration-no-defaults"));
267     }
268 
269     @OptionCollectionTest.TestFunction
270     protected void counterMaxTest() {
271         Option option = Arg.COUNTER_MAX.option();
272         String[] arg = {null};
273 
274         assertDoesNotThrow(() -> {
275             configureSourceDir(option);
276             File testFile = writeFile("Test.java", Arrays.asList("/*\n", "SPDX-License-Identifier: Unapproved\n",
277                     "*/\n\n", "class Test {}\n"));
278 
279             ReportConfiguration config = generateConfig(Collections.emptyList());
280             Reporter reporter = new Reporter(config);
281             Reporter.Output output = reporter.execute();
282             ClaimValidator validator = config.getClaimValidator();
283             assertThat(validator.listIssues(output.getStatistic())).containsExactly("UNAPPROVED");
284 
285             arg[0] = "Unapproved:1";
286             config = generateConfig(ImmutablePair.of(option, arg));
287             reporter = new Reporter(config);
288             output = reporter.execute();
289             validator = config.getClaimValidator();
290             assertThat(validator.listIssues(output.getStatistic())).isEmpty();
291         });
292     }
293 
294     @OptionCollectionTest.TestFunction
295     protected void counterMinTest() {
296         Option option = Arg.COUNTER_MIN.option();
297         String[] arg = {null};
298 
299         assertDoesNotThrow(() -> {
300             configureSourceDir(option);
301             File testFile = writeFile("Test.java", Arrays.asList("/*\n", "SPDX-License-Identifier: Unapproved\n",
302                     "*/\n\n", "class Test {}\n"));
303 
304             ReportConfiguration config = generateConfig(Collections.emptyList());
305             Reporter reporter = new Reporter(config);
306             Reporter.Output output = reporter.execute();
307             ClaimValidator validator = config.getClaimValidator();
308             assertThat(validator.listIssues(output.getStatistic())).containsExactly("UNAPPROVED");
309 
310             arg[0] = "Unapproved:1";
311             config = generateConfig(ImmutablePair.of(option, arg));
312             reporter = new Reporter(config);
313             output = reporter.execute();
314             validator = config.getClaimValidator();
315             assertThat(validator.listIssues(output.getStatistic())).isEmpty();
316         });
317     }
318 
319     /**
320      * Runs the exclude tests.
321      * @param option the exclude option to run.
322      * @param args the arguments for the command line.
323      * @param includesRatDir @{code true} if the .rat directory was created.
324      */
325     private void execExcludeTest(final Option option, final String[] args, final boolean includesRatDir) {
326         String[] notExcluded = {"notbaz", "well._afile"};
327         String[] excluded = {"some.foo", "B.bar", "justbaz"};
328 
329         assertDoesNotThrow(() -> {
330             configureSourceDir(option);
331             writeFile("notbaz");
332             writeFile("well._afile");
333             writeFile("some.foo");
334             writeFile("B.bar");
335             writeFile("justbaz");
336 
337             ReportConfiguration config = generateConfig(Collections.emptyList());
338             Reporter reporter = new Reporter(config);
339             Reporter.Output output = reporter.execute();
340             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(5);
341             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(includesRatDir ? 1 : 0);
342 
343             // filter out source
344             config = generateConfig(ImmutablePair.of(option, args));
345             reporter = new Reporter(config);
346             output = reporter.execute();
347             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
348             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(includesRatDir ? 4 : 3);
349         });
350     }
351 
352     private void excludeFileTest(final Option option) {
353         File outputFile = FileUtils.writeFile(configureRatDir(option), "exclude.txt", Arrays.asList(EXCLUDE_ARGS));
354         execExcludeTest(option, new String[]{outputFile.getAbsolutePath()}, true);
355     }
356 
357     @OptionCollectionTest.TestFunction
358     protected void excludeFileTest() {
359         excludeFileTest(Arg.EXCLUDE_FILE.find("exclude-file"));
360     }
361 
362     @OptionCollectionTest.TestFunction
363     protected void inputExcludeFileTest() {
364         excludeFileTest(Arg.EXCLUDE_FILE.find("input-exclude-file"));
365     }
366 
367     @OptionCollectionTest.TestFunction
368     protected void excludeTest() {
369         execExcludeTest(Arg.EXCLUDE.find("exclude"), EXCLUDE_ARGS, false);
370     }
371 
372     @OptionCollectionTest.TestFunction
373     protected void inputExcludeTest() {
374         execExcludeTest(Arg.EXCLUDE.find("input-exclude"), EXCLUDE_ARGS, false);
375     }
376 
377     @OptionCollectionTest.TestFunction
378     protected void inputExcludeSizeTest() {
379         Option option = Arg.EXCLUDE_SIZE.option();
380         String[] args = {"5"};
381 
382         assertDoesNotThrow(() -> {
383             configureSourceDir(option);
384             writeFile("Hi.txt", "Hi");
385             writeFile("Hello.txt", "Hello");
386             writeFile("HelloWorld.txt", "HelloWorld");
387 
388             ReportConfiguration config = generateConfig(Collections.emptyList());
389             Reporter reporter = new Reporter(config);
390             Reporter.Output output = reporter.execute();
391             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3);
392             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isZero();
393 
394             // filter out source
395             config = generateConfig(ImmutablePair.of(option, args));
396             reporter = new Reporter(config);
397             output = reporter.execute();
398             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
399             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(1);
400         });
401     }
402 
403     @OptionCollectionTest.TestFunction
404     protected void inputExcludeStdTest() {
405         Option option = Arg.EXCLUDE_STD.find("input-exclude-std");
406         String[] args = {StandardCollection.MAVEN.name()};
407         // these files are excluded by default "afile~", ".#afile", "%afile%", "._afile"
408         // these files are not excluded by default "afile~more", "what.#afile", "%afile%withMore", "well._afile", "build.log"
409         // build.log is excluded by MAVEN.
410         assertDoesNotThrow(() -> {
411             configureSourceDir(option);
412             writeFile("afile~");
413             writeFile(".#afile");
414             writeFile("%afile%");
415             writeFile("._afile");
416             writeFile("afile~more");
417             writeFile("what.#afile");
418             writeFile("%afile%withMore");
419             writeFile("well._afile");
420             writeFile("build.log");
421 
422             ReportConfiguration config = generateConfig(Collections.emptyList());
423             Reporter reporter = new Reporter(config);
424             Reporter.Output output = reporter.execute();
425             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(5);
426             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(4);
427 
428             config = generateConfig(ImmutablePair.of(option, args));
429             reporter = new Reporter(config);
430             output = reporter.execute();
431             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(4);
432             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(5);
433         });
434     }
435 
436     @OptionCollectionTest.TestFunction
437     protected void inputExcludeParsedScmTest() {
438         Option option = Arg.EXCLUDE_PARSE_SCM.find("input-exclude-parsed-scm");
439         String[] args = {"GIT"};
440         String[] lines = {
441                 "# somethings",
442                 "!thingone", "thing*", System.lineSeparator(),
443                 "# some fish",
444                 "**/fish", "*_fish",
445                 "# some colorful directories",
446                 "red/", "blue/*/"};
447 
448         assertDoesNotThrow(() -> {
449             configureSourceDir(option);
450 
451             writeFile(".gitignore", Arrays.asList(lines));
452             writeFile("thingone");
453             writeFile("thingtwo");
454 
455             File dir = new File(sourceDir, "dir");
456             FileUtils.mkDir(dir);
457             FileUtils.writeFile(dir, "fish_two");
458             FileUtils.writeFile(dir, "fish");
459 
460             dir = new File(sourceDir, "red");
461             FileUtils.mkDir(dir);
462             FileUtils.writeFile(dir, "fish");
463 
464             dir = new File(sourceDir, "blue/fish");
465             FileUtils.mkDir(dir);
466             FileUtils.writeFile(dir, "dory");
467 
468             dir = new File(sourceDir, "some");
469             FileUtils.mkDir(dir);
470             FileUtils.writeFile(dir, "fish");
471             FileUtils.writeFile(dir, "things");
472             FileUtils.writeFile(dir, "thingone");
473 
474             dir = new File(sourceDir, "another");
475             FileUtils.mkDir(dir);
476             FileUtils.writeFile(dir, "red_fish");
477 
478             ReportConfiguration config = generateConfig(Collections.emptyList());
479             Reporter reporter = new Reporter(config);
480             Reporter.Output output = reporter.execute();
481             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(11);
482             // .gitignore is ignored by default as it is hidden but not counted
483             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isZero();
484 
485             config = generateConfig(ImmutablePair.of(option, args));
486             reporter = new Reporter(config);
487             output = reporter.execute();
488             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3);
489             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(8);
490         });
491     }
492 
493     /**
494      * Runs the include tests.
495      * @param option the include option to run.
496      * @param args the arguments for the command line.
497      * @param includesRatDir @{code true} if the .rat directory was created.
498      */
499     private void execIncludeTest(final Option option, final String[] args, final boolean includesRatDir) {
500         Option excludeOption = Arg.EXCLUDE.option();
501         String[] notExcluded = {"B.bar", "justbaz", "notbaz"};
502         String[] excluded = {"some.foo"};
503 
504         assertDoesNotThrow(() -> {
505             configureSourceDir(option);
506             writeFile("notbaz");
507             writeFile("some.foo");
508             writeFile("B.bar");
509             writeFile("justbaz");
510 
511             ReportConfiguration config = generateConfig(Collections.emptyList());
512             Reporter reporter = new Reporter(config);
513             Reporter.Output output = reporter.execute();
514             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(4);
515             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(includesRatDir ? 1 : 0);
516 
517             // verify exclude removes most files
518             config = generateConfig(ImmutablePair.of(excludeOption, EXCLUDE_ARGS));
519             reporter = new Reporter(config);
520             output = reporter.execute();
521             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
522             // .gitignore is ignored by default as it is hidden but not counted
523             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(includesRatDir ? 4 : 3);
524 
525             // verify include put them back
526             config = generateConfig(ImmutablePair.of(option, args), ImmutablePair.of(excludeOption, EXCLUDE_ARGS));
527             reporter = new Reporter(config);
528             output = reporter.execute();
529             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3);
530             // .gitignore is ignored by default as it is hidden but not counted
531             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(includesRatDir ? 2 : 1 );
532         });
533     }
534 
535     private void includeFileTest(final Option option) {
536         File outputFile = FileUtils.writeFile(configureRatDir(option), "include.txt", Arrays.asList(INCLUDE_ARGS));
537         execIncludeTest(option, new String[]{outputFile.getAbsolutePath()}, true);
538     }
539 
540     @OptionCollectionTest.TestFunction
541     protected void inputIncludeFileTest() {
542         includeFileTest(Arg.INCLUDE_FILE.find("input-include-file"));
543     }
544 
545     @OptionCollectionTest.TestFunction
546     protected void includesFileTest() {
547         includeFileTest(Arg.INCLUDE_FILE.find("includes-file"));
548     }
549 
550     @OptionCollectionTest.TestFunction
551     protected void includeTest() {
552         execIncludeTest(Arg.INCLUDE.find("include"), INCLUDE_ARGS, false);
553     }
554 
555     @OptionCollectionTest.TestFunction
556     protected void inputIncludeTest() {
557         execIncludeTest(Arg.INCLUDE.find("input-include"), INCLUDE_ARGS, false);
558     }
559 
560     @OptionCollectionTest.TestFunction
561     protected void inputIncludeStdTest() {
562         Option option = Arg.INCLUDE_STD.find("input-include-std");
563         String[] args = {StandardCollection.MISC.name()};
564 
565         assertDoesNotThrow(() -> {
566             configureSourceDir(option);
567 
568             writeFile("afile~more");
569             writeFile("afile~");
570             writeFile(".#afile");
571             writeFile("%afile%");
572             writeFile("._afile");
573             writeFile("what.#afile");
574             writeFile("%afile%withMore");
575             writeFile("well._afile");
576 
577             ImmutablePair<Option, String[]> excludes = ImmutablePair.of(Arg.EXCLUDE.find("input-exclude"),
578                     new String[]{"*~more", "*~"});
579 
580             ReportConfiguration config = generateConfig(Collections.singletonList(excludes));
581             Reporter reporter = new Reporter(config);
582             Reporter.Output output = reporter.execute();
583             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3);
584             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(5);
585 
586             config = generateConfig(excludes, ImmutablePair.of(option, args));
587             reporter = new Reporter(config);
588             output = reporter.execute();
589             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(7);
590             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(1);
591         });
592     }
593 
594     @OptionCollectionTest.TestFunction
595     protected void inputSourceTest() {
596         Option option = Arg.SOURCE.find("input-source");
597 
598         assertDoesNotThrow(() -> {
599             configureSourceDir(option);
600 
601             writeFile("codefile");
602             File inputFile = writeFile("intput.txt", "codefile");
603             writeFile("notcodFile");
604 
605             ReportConfiguration config = generateConfig();
606             Reporter reporter = new Reporter(config);
607             Reporter.Output output = reporter.execute();
608             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3);
609             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isZero();
610 
611             config = generateConfig(ImmutablePair.of(option, new String[]{inputFile.getAbsolutePath()}));
612             reporter = new Reporter(config);
613             output = reporter.execute();
614             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
615             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isZero();
616         });
617     }
618 
619     private ReportConfiguration addCatzLicense(ReportConfiguration config) {
620         String catz = ILicenseFamily.makeCategory("catz");
621         config.addFamily(ILicenseFamily.builder().setLicenseFamilyCategory(catz).setLicenseFamilyName("catz").build());
622         config.addLicense(ILicense.builder().setFamily(catz)
623                 .setMatcher(new SpdxBuilder().setName("catz")));
624         return config;
625     }
626 
627     private void execLicenseFamiliesApprovedTest(final Option option, final String[] args) {
628         Pair<Option, String[]> arg1 = ImmutablePair.of(option, args);
629 
630         assertDoesNotThrow(() -> {
631             configureSourceDir(option);
632             // write the catz licensed text file
633             writeFile("catz.txt", "SPDX-License-Identifier: catz");
634 
635             ReportConfiguration config = addCatzLicense(generateConfig());
636             Reporter reporter = new Reporter(config);
637             Reporter.Output output = reporter.execute();
638 
639             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
640             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isZero();
641             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
642 
643             config = addCatzLicense(generateConfig(arg1));
644             reporter = new Reporter(config);
645             output = reporter.execute();
646             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
647             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
648             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isZero();
649         });
650     }
651 
652     @OptionCollectionTest.TestFunction
653     protected void licenseFamiliesApprovedFileTest() {
654         Option option = Arg.FAMILIES_APPROVED_FILE.find("license-families-approved-file");
655         File outputFile = FileUtils.writeFile(configureRatDir(option), "familiesApproved.txt", Collections.singletonList("catz"));
656         execLicenseFamiliesApprovedTest(option, new String[]{outputFile.getAbsolutePath()});
657     }
658 
659     @OptionCollectionTest.TestFunction
660     protected void licenseFamiliesApprovedTest() {
661         execLicenseFamiliesApprovedTest(Arg.FAMILIES_APPROVED.find("license-families-approved"),
662                 new String[]{"catz"});
663     }
664 
665     private void execLicenseFamiliesDeniedTest(final Option option, final String[] args) {
666         Pair<Option, String[]> arg1 = ImmutablePair.of(option, args);
667         String bsd = ILicenseFamily.makeCategory("BSD-3");
668 
669         assertDoesNotThrow(() -> {
670             configureSourceDir(option);
671             writeFile("bsd.txt", "SPDX-License-Identifier: BSD-3-Clause");
672 
673             ReportConfiguration config = generateConfig();
674             Reporter reporter = new Reporter(config);
675             Reporter.Output output = reporter.execute();
676             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
677             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
678             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isZero();
679 
680             config = generateConfig(arg1);
681             reporter = new Reporter(config);
682             output = reporter.execute();
683             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
684             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isZero();
685             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
686         });
687     }
688 
689     @OptionCollectionTest.TestFunction
690     protected void licenseFamiliesDeniedFileTest() {
691         Option option = Arg.FAMILIES_DENIED_FILE.find("license-families-denied-file");
692         File outputFile = FileUtils.writeFile(configureRatDir(option), "familiesDenied.txt", Collections.singletonList("BSD-3"));
693         execLicenseFamiliesDeniedTest(option, new String[]{outputFile.getAbsolutePath()});
694     }
695 
696     @OptionCollectionTest.TestFunction
697     protected void licenseFamiliesDeniedTest() {
698         execLicenseFamiliesDeniedTest(Arg.FAMILIES_DENIED.find("license-families-denied"),
699                 new String[]{"BSD-3"});
700     }
701 
702     private void configTest(final Option option) {
703         assertDoesNotThrow(() -> {
704             configureSourceDir(option);
705             String[] args = {
706                     Resources.getResourceFile("OptionTools/One.xml").getAbsolutePath(),
707                     Resources.getResourceFile("OptionTools/Two.xml").getAbsolutePath()};
708             Pair<Option, String[]> arg1 = ImmutablePair.of(option, args);
709 
710             writeFile("bsd.txt", "SPDX-License-Identifier: BSD-3-Clause");
711             writeFile("one.txt", "one is the loneliest number");
712 
713             ReportConfiguration config = generateConfig();
714             Reporter reporter = new Reporter(config);
715             Reporter.Output output = reporter.execute();
716             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
717             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
718             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
719 
720             config = generateConfig(arg1);
721             reporter = new Reporter(config);
722             output = reporter.execute();
723             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
724             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(2);
725             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isZero();
726 
727             Pair<Option, String[]> arg2 = ImmutablePair.of(Arg.CONFIGURATION_NO_DEFAULTS.find("configuration-no-defaults"), null);
728 
729             config = generateConfig(arg1, arg2);
730             reporter = new Reporter(config);
731             output = reporter.execute();
732             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
733             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
734             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
735         });
736     }
737 
738     @OptionCollectionTest.TestFunction
739     protected void licensesTest() {
740         configTest(Arg.CONFIGURATION.find("licenses"));
741     }
742 
743     @OptionCollectionTest.TestFunction
744     protected void configTest() {
745         configTest(Arg.CONFIGURATION.find("config"));
746     }
747 
748     @OptionCollectionTest.TestFunction
749     protected void execLicensesApprovedTest(final Option option, String[] args) {
750         Pair<Option, String[]> arg1 = ImmutablePair.of(option, args);
751 
752         assertDoesNotThrow(() -> {
753             configureSourceDir(option);
754 
755             writeFile("gpl.txt", "SPDX-License-Identifier: GPL-1.0-only");
756             writeFile("apl.txt", "SPDX-License-Identifier: Apache-2.0");
757 
758             ReportConfiguration config = generateConfig();
759             Reporter reporter = new Reporter(config);
760             Reporter.Output output = reporter.execute();
761             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
762             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
763             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
764 
765             config = generateConfig(arg1);
766             reporter = new Reporter(config);
767             output = reporter.execute();
768             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
769             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(2);
770             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isZero();
771         });
772     }
773 
774     @OptionCollectionTest.TestFunction
775     protected void licensesApprovedFileTest() {
776         Option option = Arg.LICENSES_APPROVED_FILE.find("licenses-approved-file");
777         File outputFile = FileUtils.writeFile(configureRatDir(option), "licensesApproved.txt", Collections.singletonList("GPL1"));
778         execLicensesApprovedTest(option, new String[]{outputFile.getAbsolutePath()});
779     }
780 
781     @OptionCollectionTest.TestFunction
782     protected void licensesApprovedTest() {
783         execLicensesApprovedTest(Arg.LICENSES_APPROVED.find("licenses-approved"),
784                 new String[]{"GPL1"});
785     }
786 
787     @OptionCollectionTest.TestFunction
788     protected void scanHiddenDirectoriesTest() {
789         Option option = Arg.INCLUDE_STD.find("scan-hidden-directories");
790         Pair<Option, String[]> arg1 = ImmutablePair.of(option, null);
791 
792         assertDoesNotThrow(() -> {
793             configureSourceDir(option);
794 
795             writeFile("apl.txt", "SPDX-License-Identifier: Apache-2.0");
796 
797             File hiddenDir = new File(sourceDir, ".hiddendir");
798             FileUtils.mkDir(hiddenDir);
799             FileUtils.writeFile(hiddenDir, "gpl.txt", Collections.singletonList("SPDX-License-Identifier: GPL-1.0-only"));
800 
801             ReportConfiguration config = generateConfig();
802             Reporter reporter = new Reporter(config);
803             Reporter.Output output = reporter.execute();
804             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
805             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
806             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isZero();
807 
808             config = generateConfig(arg1);
809             reporter = new Reporter(config);
810             output = reporter.execute();
811             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
812             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
813             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
814         });
815     }
816 
817     private void outTest(final Option option) {
818         assertDoesNotThrow(() -> {
819             configureSourceDir(option);
820             writeFile("apl.txt", "SPDX-License-Identifier: Apache-2.0");
821             File outFile = new File(sourceDir, "outexample");
822             FileUtils.delete(outFile);
823             String[] args = new String[]{outFile.getAbsolutePath()};
824 
825             ReportConfiguration config = generateConfig(ImmutablePair.of(option, args));
826             Reporter reporter = new Reporter(config);
827             Reporter.Output output = reporter.execute();
828             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
829             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
830             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isZero();
831             output.format(config);
832             String actualText = TextUtils.readFile(outFile);
833             assertThat(actualText).containsOnlyOnce("Apache License 2.0: 1 ")
834                             .containsOnlyOnce("STANDARD: 1 ");
835         });
836     }
837 
838     @OptionCollectionTest.TestFunction
839     protected void outTest() {
840         outTest(Arg.OUTPUT_FILE.find("out"));
841     }
842 
843     @OptionCollectionTest.TestFunction
844     protected void outputFileTest() {
845         outTest(Arg.OUTPUT_FILE.find("output-file"));
846     }
847 
848     private void styleSheetTest(final Option option) {
849         PrintStream origin = System.out;
850         ByteArrayOutputStream baos = new ByteArrayOutputStream();
851         try (PrintStream out = new PrintStream(baos)) {
852             System.setOut(out);
853             configureSourceDir(option);
854             // create a dummy stylesheet so that we have a local file for users of the testing jar
855             File file = writeFile("stylesheet", """
856                     <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
857                         <xsl:template match="@*|node()">
858                             Hello world
859                         </xsl:template>
860                     </xsl:stylesheet>""");
861 
862             String[] args = {null};
863             for (StyleSheets sheet : StyleSheets.values()) {
864                 args[0] = sheet.arg();
865                 ReportConfiguration config = generateConfig(ImmutablePair.of(option, args));
866                 Reporter reporter = new Reporter(config);
867                 Reporter.Output output = reporter.execute();
868                 assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
869                 assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isZero();
870                 assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
871                 output.format(config);
872                 String actualText = baos.toString(StandardCharsets.UTF_8);
873                 switch (sheet) {
874                     case MISSING_HEADERS:
875                         assertThat(actualText).containsOnlyOnce("Files with missing headers:" + System.lineSeparator() +
876                                 "  /stylesheet");
877                         break;
878                     case PLAIN:
879                         assertThat(actualText).containsOnlyOnce("Unknown license: 1 ");
880                         assertThat(actualText).containsOnlyOnce("?????: 1 ");
881                         break;
882                     case XML:
883                         assertThat(actualText).containsOnlyOnce("<resource encoding=\"windows-1252\" mediaType=\"text/plain\" name=\"/stylesheet\" type=\"STANDARD\">");
884                         break;
885                     case UNAPPROVED_LICENSES:
886                         assertThat(actualText).containsOnlyOnce("Files with unapproved licenses:" + System.lineSeparator() + "  /stylesheet");
887                         break;
888                     case XHTML5:
889                         assertThat(actualText).containsPattern("<td>Approved</td>\\s+<td>\\d+</td>\\s+<td>A count of approved licenses.</td>");
890                         break;
891                     default:
892                         fail("No test for stylesheet " + sheet);
893                         break;
894                 }
895                 baos.reset();
896             }
897             args[0] = file.getAbsolutePath();
898             ReportConfiguration config = generateConfig(ImmutablePair.of(option, args));
899             Reporter reporter = new Reporter(config);
900             Reporter.Output output = reporter.execute();
901             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
902             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isZero();
903             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
904             output.format(config);
905             String actualText = baos.toString(StandardCharsets.UTF_8);
906             assertThat(actualText).containsOnlyOnce("Hello world");
907         } catch (IOException | RatException e) {
908             fail(e.getMessage(), e);
909         } finally {
910             System.setOut(origin);
911         }
912     }
913 
914     @OptionCollectionTest.TestFunction
915     protected void stylesheetTest() {
916         styleSheetTest(Arg.OUTPUT_STYLE.find("stylesheet"));
917     }
918 
919     @OptionCollectionTest.TestFunction
920     protected void outputStyleTest() {
921         styleSheetTest(Arg.OUTPUT_STYLE.find("output-style"));
922     }
923 
924     @OptionCollectionTest.TestFunction
925     protected void xmlTest() {
926         PrintStream origin = System.out;
927         Option option = Arg.OUTPUT_STYLE.find("xml");
928         ByteArrayOutputStream baos = new ByteArrayOutputStream();
929         try (PrintStream out = new PrintStream(baos)) {
930             System.setOut(out);
931             configureSourceDir(option);
932             // create a dummy stylesheet so that we match the stylesheet tests.
933             File file = writeFile("stylesheet", """
934                     <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
935                         <xsl:template match="@*|node()">
936                             Hello world
937                         </xsl:template>
938                     </xsl:stylesheet>""");
939 
940             ReportConfiguration config = generateConfig(ImmutablePair.of(option, null));
941             Reporter reporter = new Reporter(config);
942             Reporter.Output output = reporter.execute();
943             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
944             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isZero();
945             assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
946             output.format(config);
947             String actualText = baos.toString(StandardCharsets.UTF_8);
948             assertThat(actualText)
949                     .containsOnlyOnce("<resource encoding=\"windows-1252\" mediaType=\"text/plain\" name=\"/stylesheet\" type=\"STANDARD\">");
950 
951             try (InputStream expected = StyleSheets.getStyleSheet("xml", null).ioSupplier().get();
952                  InputStream actual = config.getStyleSheet().get()) {
953                 assertThat(IOUtils.contentEquals(expected, actual)).as("'xml' does not match").isTrue();
954             }
955         } catch (IOException | RatException e) {
956             fail(e.getMessage(), e);
957         } finally {
958             System.setOut(origin);
959         }
960     }
961 
962     @OptionCollectionTest.TestFunction
963     protected void logLevelTest() {
964         PrintStream origin = System.out;
965         Option option = Arg.LOG_LEVEL.find("log-level");
966         ByteArrayOutputStream baos = new ByteArrayOutputStream();
967         Log.Level oldLevel = DefaultLog.getInstance().getLevel();
968         try (PrintStream out = new PrintStream(baos)) {
969             System.setOut(out);
970             configureSourceDir(option);
971 
972             ReportConfiguration config = generateConfig();
973             Reporter reporter = new Reporter(config);
974             reporter.execute();
975             assertThat(baos.toString(StandardCharsets.UTF_8)).doesNotContain("DEBUG");
976 
977             config = generateConfig(ImmutablePair.of(option, new String[]{"debug"}));
978             reporter = new Reporter(config);
979             reporter.execute();
980             assertThat(baos.toString(StandardCharsets.UTF_8)).contains("DEBUG");
981         } catch (IOException | RatException e) {
982             fail(e.getMessage(), e);
983         } finally {
984             System.setOut(origin);
985             DefaultLog.getInstance().setLevel(oldLevel);
986         }
987     }
988 
989     private void listLicenses(final Option option) {
990         XPath xPath = SecureXPathFactory.newInstance().newXPath();
991         String[] args = {null};
992 
993         assertDoesNotThrow(() -> {
994             configureSourceDir(option);
995             for (LicenseSetFactory.LicenseFilter filter : LicenseSetFactory.LicenseFilter.values()) {
996                 args[0] = filter.name();
997                 ReportConfiguration config = generateConfig(ImmutablePair.of(option, args));
998                 Reporter reporter = new Reporter(config);
999                 Reporter.Output output = reporter.execute();
1000                 Document document = output.getDocument();
1001                 switch (filter) {
1002                     case ALL:
1003                         XmlUtils.assertIsPresent(filter.name(), document, xPath, "/rat-report/rat-config/licenses/license[@id='AL2.0']");
1004                         XmlUtils.assertIsPresent(filter.name(), document, xPath, "/rat-report/rat-config/licenses/license[@id='GPL1']");
1005                         break;
1006                     case APPROVED:
1007                         XmlUtils.assertIsPresent(filter.name(), document, xPath, "/rat-report/rat-config/licenses/license[@id='AL2.0']");
1008                         XmlUtils.assertIsNotPresent(filter.name(), document, xPath, "/rat-report/rat-config/licenses/license[@id='GPL1']");
1009                         break;
1010                     case NONE:
1011                         XmlUtils.assertIsNotPresent(filter.name(), document, xPath, "/rat-report/rat-config/licenses/license[@id='AL2.0']");
1012                         XmlUtils.assertIsNotPresent(filter.name(), document, xPath, "/rat-report/rat-config/licenses/license[@id='GPL1']");
1013                         break;
1014                     default:
1015                         throw new IllegalArgumentException("Unexpected filter: " + filter);
1016                 }
1017             }
1018         });
1019     }
1020 
1021     @OptionCollectionTest.TestFunction
1022     protected void listLicensesTest() {
1023         listLicenses(Arg.OUTPUT_LICENSES.find("list-licenses"));
1024     }
1025 
1026     @OptionCollectionTest.TestFunction
1027     protected void outputLicensesTest() {
1028         listLicenses(Arg.OUTPUT_LICENSES.find("output-licenses"));
1029     }
1030 
1031     private void listFamilies(final Option option) {
1032         XPath xPath = SecureXPathFactory.newInstance().newXPath();
1033         String[] args = {null};
1034 
1035         assertDoesNotThrow(() -> {
1036             configureSourceDir(option);
1037             for (LicenseSetFactory.LicenseFilter filter : LicenseSetFactory.LicenseFilter.values()) {
1038                 args[0] = filter.name();
1039                 ReportConfiguration config = generateConfig(ImmutablePair.of(option, args));
1040                 Reporter reporter = new Reporter(config);
1041                 Reporter.Output output = reporter.execute();
1042                 Document document = output.getDocument();
1043                 switch (filter) {
1044                     case ALL:
1045                         XmlUtils.assertIsPresent(filter.name(), document, xPath, "/rat-report/rat-config/families/family[@id='AL']");
1046                         XmlUtils.assertIsPresent(filter.name(), document, xPath, "/rat-report/rat-config/families/family[@id='GPL']");
1047                         break;
1048                     case APPROVED:
1049                         XmlUtils.assertIsPresent(filter.name(), document, xPath, "/rat-report/rat-config/families/family[@id='AL']");
1050                         XmlUtils.assertIsNotPresent(filter.name(), document, xPath, "/rat-report/rat-config/families/family[@id='GPL']");
1051                         break;
1052                     case NONE:
1053                         XmlUtils.assertIsNotPresent(filter.name(), document, xPath, "/rat-report/rat-config/families/family[@id='AL']");
1054                         XmlUtils.assertIsNotPresent(filter.name(), document, xPath, "/rat-report/rat-config/families/family[@id='GPL']");
1055                         break;
1056                     default:
1057                         throw new IllegalArgumentException("Unexpected filter: " + filter);
1058                 }
1059             }
1060         });
1061     }
1062 
1063     @OptionCollectionTest.TestFunction
1064     protected void listFamiliesTest() {
1065         listFamilies(Arg.OUTPUT_FAMILIES.find("list-families"));
1066     }
1067 
1068     @OptionCollectionTest.TestFunction
1069     protected void outputFamiliesTest() {
1070         listFamilies(Arg.OUTPUT_FAMILIES.find("output-families"));
1071     }
1072 
1073     private void archiveTest(final Option option) {
1074         XPath xPath = SecureXPathFactory.newInstance().newXPath();
1075         String[] args = {null};
1076 
1077         assertDoesNotThrow(() -> {
1078             configureSourceDir(option);
1079             File outFile = new File(sourceDir, "out.xml");
1080             FileUtils.delete(outFile);
1081             ImmutablePair<Option, String[]> outputFile = ImmutablePair.of(Arg.OUTPUT_FILE.option(), new String[]{outFile.getAbsolutePath()});
1082             ImmutablePair<Option, String[]> stylesheet = ImmutablePair.of(Arg.OUTPUT_STYLE.option(), new String[]{StyleSheets.XML.arg()});
1083             File archive = Resources.getResourceFile("tikaFiles/archive/dummy.jar");
1084             File localArchive = new File(sourceDir, "dummy.jar");
1085             try (InputStream in = Files.newInputStream(archive.toPath());
1086                  OutputStream out = Files.newOutputStream(localArchive.toPath())) {
1087                 IOUtils.copy(in, out);
1088             }
1089 
1090             for (ReportConfiguration.Processing proc : ReportConfiguration.Processing.values()) {
1091                 args[0] = proc.name();
1092                 ReportConfiguration config = generateConfig(outputFile, stylesheet, ImmutablePair.of(option, args));
1093                 Reporter reporter = new Reporter(config);
1094                 reporter.execute().format(config);
1095 
1096                 Document document = reporter.execute().getDocument();
1097                 XmlUtils.assertIsPresent(proc.name(), document, xPath, "/rat-report/resource[@name='/dummy.jar']");
1098                 switch (proc) {
1099                     case ABSENCE:
1100                         XmlUtils.assertIsPresent(proc.name(), document, xPath, "/rat-report/resource[@name='/dummy.jar']/license[@family='AL   ']");
1101                         XmlUtils.assertIsPresent(proc.name(), document, xPath, "/rat-report/resource[@name='/dummy.jar']/license[@family='?????']");
1102                         break;
1103                     case PRESENCE:
1104                         XmlUtils.assertIsPresent(proc.name(), document, xPath, "/rat-report/resource[@name='/dummy.jar']/license[@family='AL   ']");
1105                         XmlUtils.assertIsNotPresent(proc.name(), document, xPath, "/rat-report/resource[@name='/dummy.jar']/license[@family='?????']");
1106                         break;
1107                     case NOTIFICATION:
1108                         XmlUtils.assertIsNotPresent(proc.name(), document, xPath, "/rat-report/resource[@name='/dummy.jar']/license[@family='AL   ']");
1109                         XmlUtils.assertIsNotPresent(proc.name(), document, xPath, "/rat-report/resource[@name='/dummy.jar']/license[@family='?????']");
1110                         break;
1111                     default:
1112                         throw new IllegalArgumentException("Unexpected processing " + proc);
1113                 }
1114             }
1115         });
1116     }
1117 
1118     @OptionCollectionTest.TestFunction
1119     protected void outputArchiveTest() {
1120         archiveTest(Arg.OUTPUT_ARCHIVE.find("output-archive"));
1121     }
1122 
1123     private void standardTest(final Option option) {
1124         XPath xPath = SecureXPathFactory.newInstance().newXPath();
1125         String[] args = {null};
1126 
1127         assertDoesNotThrow(() -> {
1128             configureSourceDir(option);
1129             File outFile = new File(sourceDir, "out.xml");
1130             ImmutablePair<Option, String[]> outputFile = ImmutablePair.of(Arg.OUTPUT_FILE.option(), new String[]{outFile.getAbsolutePath()});
1131             ImmutablePair<Option, String[]> stylesheet = ImmutablePair.of(Arg.OUTPUT_STYLE.option(), new String[]{StyleSheets.XML.arg()});
1132 
1133             writeFile("Test.java", Arrays.asList("/*\n", "SPDX-License-Identifier: Apache-2.0\n",
1134                     "*/\n\n", "class Test {}\n"));
1135             writeFile("Missing.java", Arrays.asList("/* no license */\n\n", "class Test {}\n"));
1136 
1137             String testDoc = "/rat-report/resource[@name='/Test.java']";
1138             String missingDoc = "/rat-report/resource[@name='/Missing.java']";
1139 
1140             for (ReportConfiguration.Processing proc : ReportConfiguration.Processing.values()) {
1141                 args[0] = proc.name();
1142                 ReportConfiguration config = generateConfig(outputFile, stylesheet, ImmutablePair.of(option, args));
1143                 Reporter reporter = new Reporter(config);
1144                 Reporter.Output output = reporter.execute();
1145 
1146                 Document document = output.getDocument();
1147                 XmlUtils.assertIsPresent(proc.name(), document, xPath, testDoc);
1148                 XmlUtils.assertIsPresent(proc.name(), document, xPath, missingDoc);
1149 
1150                 switch (proc) {
1151                     case ABSENCE:
1152                         XmlUtils.assertIsPresent(proc.name(), document, xPath, testDoc + "/license[@family='AL   ']");
1153                         XmlUtils.assertIsPresent(proc.name(), document, xPath, missingDoc + "/license[@family='?????']");
1154                         break;
1155                     case PRESENCE:
1156                         XmlUtils.assertIsPresent(proc.name(), document, xPath, testDoc + "/license[@family='AL   ']");
1157                         XmlUtils.assertIsNotPresent(proc.name(), document, xPath, missingDoc + "/license[@family='?????']");
1158                         break;
1159                     case NOTIFICATION:
1160                         XmlUtils.assertIsNotPresent(proc.name(), document, xPath, testDoc + "/license[@family='AL   ']");
1161                         XmlUtils.assertIsNotPresent(proc.name(), document, xPath, missingDoc + "/license[@family='?????']");
1162                         break;
1163                     default:
1164                         throw new IllegalArgumentException("Unexpected processing " + proc);
1165                 }
1166             }
1167         });
1168     }
1169 
1170     @OptionCollectionTest.TestFunction
1171     protected void outputStandardTest() {
1172         standardTest(Arg.OUTPUT_STANDARD.find("output-standard"));
1173     }
1174 
1175     private void editCopyrightTest(final Option option, final Option extraOption) {
1176         final String myCopyright = "MyCopyright";
1177         Pair<Option, String[]> arg1 = ImmutablePair.of(option, new String[]{myCopyright});
1178         final boolean forced = Arg.EDIT_OVERWRITE.option().equals(extraOption);
1179         final boolean dryRun = Arg.DRY_RUN.option().equals(extraOption);
1180 
1181         assertDoesNotThrow(() -> {
1182             Pair<Option, String[]> extraArg = null;
1183             if (forced || dryRun) {
1184                 extraArg = ImmutablePair.of(extraOption, null);
1185             }
1186 
1187             configureSourceDir(option);
1188             File javaFile = writeFile("Missing.java", Arrays.asList("/* no license */\n\n", "class Test {}\n"));
1189             File newJavaFile = new File(sourceDir, "Missing.java.new");
1190             FileUtils.delete(newJavaFile);
1191 
1192             ReportConfiguration config = extraArg != null ? generateConfig(arg1, extraArg) : generateConfig(arg1);
1193             Reporter reporter = new Reporter(config);
1194             reporter.execute();
1195 
1196             String actualText = TextUtils.readFile(javaFile);
1197             assertThat(actualText).doesNotContain(myCopyright);
1198 
1199             Pair<Option, String[]> arg2 = ImmutablePair.of(Arg.EDIT_ADD.find("edit-license"), null);
1200             config = extraArg != null ? generateConfig(arg1, arg2, extraArg) : generateConfig(arg1, arg2);
1201             reporter = new Reporter(config);
1202             reporter.execute();
1203 
1204             actualText = TextUtils.readFile(javaFile);
1205             if (forced) {
1206                 assertThat(actualText).contains(myCopyright);
1207                 assertThat(newJavaFile).doesNotExist();
1208             } else if (dryRun) {
1209                 assertThat(actualText).doesNotContain(myCopyright);
1210                 assertThat(newJavaFile).doesNotExist();
1211             } else {
1212                 assertThat(actualText).doesNotContain(myCopyright);
1213                 assertThat(newJavaFile).exists();
1214             }
1215         });
1216     }
1217 
1218     @OptionCollectionTest.TestFunction
1219     protected void copyrightTest() {
1220         editCopyrightTest(Arg.EDIT_COPYRIGHT.find("copyright"), null);
1221     }
1222 
1223     @OptionCollectionTest.TestFunction
1224     protected void editCopyrightTest() {
1225         editCopyrightTest(Arg.EDIT_COPYRIGHT.find("edit-copyright"), null);
1226     }
1227 
1228     @OptionCollectionTest.TestFunction
1229     protected void forceTest() {
1230         editCopyrightTest(Arg.EDIT_COPYRIGHT.find("edit-copyright"), Arg.EDIT_OVERWRITE.find("force"));
1231     }
1232 
1233     @OptionCollectionTest.TestFunction
1234     protected void editOverwriteTest() {
1235         editCopyrightTest(Arg.EDIT_COPYRIGHT.find("edit-copyright"), Arg.EDIT_OVERWRITE.find("edit-overwrite"));
1236     }
1237 
1238     @OptionCollectionTest.TestFunction
1239     public void helpTest() {
1240         PrintStream origin = System.out;
1241         Options options = OptionCollection.buildOptions();
1242         Pair<Option, String[]> arg1 = ImmutablePair.of(OptionCollection.HELP, null);
1243         ByteArrayOutputStream baos = new ByteArrayOutputStream();
1244         String actualText = null;
1245         try (PrintStream out = new PrintStream(baos)) {
1246             System.setOut(out);
1247             configureSourceDir(OptionCollection.HELP);
1248 
1249             generateConfig(List.of(arg1), true);
1250             assertThat(helpCalled.get()).as("Help was not called").isTrue();
1251             new Help(System.out).printUsage(options);
1252             actualText = baos.toString(StandardCharsets.UTF_8);
1253         } catch (IOException e) {
1254             fail(e.getMessage(), e);
1255         } finally {
1256             System.setOut(origin);
1257         }
1258 
1259         // verify all the options
1260         assertThat(actualText).contains("====== Available Options ======");
1261         for (Option option : options.getOptions()) {
1262             StringBuilder regex = new StringBuilder();
1263             if (option.getOpt() != null) {
1264                 regex.append("-").append(option.getOpt());
1265                 if (option.hasLongOpt()) {
1266                     regex.append(",");
1267                 }
1268             }
1269             if (option.hasLongOpt()) {
1270                 regex.append("--").append(option.getLongOpt());
1271             }
1272             if (option.hasArg()) {
1273                 String name = option.getArgName() == null ? "arg" : option.getArgName();
1274                 regex.append(".+\\<").append(name).append("\\>");
1275             }
1276             if (option.isDeprecated()) {
1277                 regex.append(".+\\[Deprecated ");
1278             }
1279             assertThat(Pattern.compile(regex.toString()).matcher(actualText).find()).as("missing '" + regex + "'").isTrue();
1280         }
1281 
1282         assertThat(actualText).contains("====== Argument Types ======");
1283         assertThat(actualText).contains("====== Standard Collections ======");
1284         for (StandardCollection collection : StandardCollection.values()) {
1285             assertThat(actualText).contains("<" + collection.name() + ">");
1286         }
1287     }
1288 
1289     @OptionCollectionTest.TestFunction
1290     protected void helpLicensesTest() {
1291         PrintStream origin = System.out;
1292         Option option = HELP_LICENSES.option();
1293         Pair<Option, String[]> arg1 = ImmutablePair.of(option, null);
1294         ByteArrayOutputStream baos = new ByteArrayOutputStream();
1295         String actualText = null;
1296         try (PrintStream out = new PrintStream(baos)) {
1297             System.setOut(out);
1298             configureSourceDir(option);
1299             generateConfig(arg1);
1300             actualText = baos.toString(StandardCharsets.UTF_8);
1301         } catch (IOException e) {
1302             fail(e.getMessage(), e);
1303         } finally {
1304             System.setOut(origin);
1305         }
1306 
1307         assertThat(actualText).isNotNull()
1308                         .contains("====== Licenses ======", "====== Defined Matchers ======", "====== Defined Families ======");
1309     }
1310 
1311     @OptionCollectionTest.TestFunction
1312     protected void dryRunTest() {
1313         editCopyrightTest(Arg.EDIT_COPYRIGHT.find("edit-copyright"), Arg.DRY_RUN.find("dry-run"));
1314     }
1315 }