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