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.test;
20  
21  import java.io.FileWriter;
22  import java.nio.charset.StandardCharsets;
23  import java.nio.file.FileSystems;
24  import java.nio.file.Path;
25  import org.apache.commons.cli.Option;
26  import org.apache.commons.io.FileUtils;
27  import org.apache.commons.io.IOUtils;
28  import org.apache.commons.lang3.tuple.ImmutablePair;
29  import org.apache.commons.lang3.tuple.Pair;
30  import org.apache.commons.text.WordUtils;
31  import org.apache.rat.OptionCollectionTest;
32  import org.apache.rat.ReportConfiguration;
33  import org.apache.rat.ReporterTest;
34  import org.apache.rat.commandline.Arg;
35  import org.apache.rat.commandline.StyleSheets;
36  import org.apache.rat.config.exclusion.StandardCollection;
37  import org.apache.rat.document.DocumentNameMatcher;
38  import org.apache.rat.document.DocumentName;
39  import org.apache.rat.license.ILicense;
40  import org.apache.rat.license.ILicenseFamily;
41  import org.apache.rat.license.LicenseSetFactory;
42  import org.apache.rat.report.claim.ClaimStatistic;
43  import org.apache.rat.report.xml.writer.XmlWriter;
44  import org.apache.rat.test.utils.Resources;
45  import org.apache.rat.utils.DefaultLog;
46  import org.apache.rat.utils.Log.Level;
47  
48  import java.io.BufferedReader;
49  import java.io.ByteArrayOutputStream;
50  import java.io.File;
51  import java.io.IOException;
52  import java.io.InputStream;
53  import java.io.InputStreamReader;
54  import java.io.OutputStream;
55  import java.io.PrintStream;
56  import java.nio.file.Files;
57  import java.util.Arrays;
58  import java.util.Collection;
59  import java.util.Collections;
60  import java.util.Locale;
61  import java.util.SortedSet;
62  
63  import static org.apache.rat.commandline.Arg.HELP_LICENSES;
64  import static org.assertj.core.api.Assertions.assertThat;
65  import static org.assertj.core.api.Fail.fail;
66  import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
67  
68  /**
69   * A list of methods that an OptionsProvider in a test case must support.
70   * Use of this interface ensures consistent testing across the UIs. Each method
71   * tests an Option from OptionCollection that must be implemented in the UI.
72   * Each method in this interface tests an Option in {@link org.apache.rat.OptionCollection}.
73   */
74  public abstract class AbstractConfigurationOptionsProvider extends AbstractOptionsProvider {
75  
76      /**
77       * Copy the runtime data to the "target" directory.
78       * @param baseDir the base directory to copy to.
79       * @param targetDir the directory relative to the base directory to copy to.
80       */
81      public static void preserveData(File baseDir, String targetDir) {
82          final Path recordPath = FileSystems.getDefault().getPath("target", targetDir);
83          org.apache.rat.utils.FileUtils.mkDir(recordPath.toFile());
84          try {
85              FileUtils.copyDirectory(baseDir, recordPath.toFile());
86          } catch (IOException e) {
87              System.err.format("Unable to copy data from %s to %s%n", baseDir, recordPath);
88          }
89      }
90  
91      /**
92       * Copies the test data to the specified directory.
93       * @param baseDir the directory to copy the {@code /src/test/resources} to.
94       * @return the {@code baseDir} argument.
95       */
96      public static File setup(final File baseDir) {
97          try {
98              final File sourceDir = Resources.getResourceDirectory("OptionTools");
99              FileUtils.copyDirectory(sourceDir, new File(baseDir,"/src/test/resources/OptionTools"));
100         } catch (IOException e) {
101             DefaultLog.getInstance().error("Can not copy 'OptionTools' to " + baseDir, e);
102         }
103         return baseDir;
104     }
105 
106     /**
107      * Construct the tests.
108      * @param providerName the common name of the provider under test.
109      * @param unsupportedArgs the list of unsupported arguments.
110      * @param baseDir the base directory for the tests. Tests and test data will be copied to directories under this directory.
111      */
112     protected AbstractConfigurationOptionsProvider(final String providerName, final Collection<String> unsupportedArgs, final File baseDir) {
113         super(providerName, setup(baseDir));
114         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "addLicense", this::addLicenseTest));
115         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "config", this::configTest));
116         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "configuration-no-defaults", this::configurationNoDefaultsTest));
117         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "copyright", this::copyrightTest));
118         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "counter-min", this::counterMinTest));
119         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "counter-max", this::counterMaxTest));
120         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "dir", () -> DefaultLog.getInstance().info("--dir has no valid test")));
121         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "dry-run", this::dryRunTest));
122         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "edit-copyright", this::editCopyrightTest));
123         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "edit-license", this::editLicenseTest));
124         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "edit-overwrite", this::editOverwriteTest));
125         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "exclude", this::excludeTest));
126         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "exclude-file", this::excludeFileTest));
127         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "force", this::forceTest));
128         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "help-licenses", this::helpLicenses));
129         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "include", this::includeTest));
130         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "includes-file", this::includesFileTest));
131         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "input-exclude", this::inputExcludeTest));
132         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "input-exclude-file", this::inputExcludeFileTest));
133         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "input-exclude-parsed-scm", this::inputExcludeParsedScmTest));
134         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "input-exclude-std", this::inputExcludeStdTest));
135         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "input-exclude-size", this::inputExcludeSizeTest));
136         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "input-include", this::inputIncludeTest));
137         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "input-include-file", this::inputIncludeFileTest));
138         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "input-include-std", this::inputIncludeStdTest));
139         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "input-source", this::inputSourceTest));
140         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "license-families-approved", this::licenseFamiliesApprovedTest));
141         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "license-families-approved-file", this::licenseFamiliesApprovedFileTest));
142         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "license-families-denied", this::licenseFamiliesDeniedTest));
143         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "license-families-denied-file", this::licenseFamiliesDeniedFileTest));
144         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "licenses", this::licensesTest));
145         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "licenses-approved", this::licensesApprovedTest));
146         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "licenses-approved-file", this::licensesApprovedFileTest));
147         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "licenses-denied", this::licensesDeniedTest));
148         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "licenses-denied-file", this::licensesDeniedFileTest));
149         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "list-families", this::listFamiliesTest));
150         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "list-licenses", this::listLicensesTest));
151         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "log-level", this::logLevelTest));
152         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "no-default-licenses", this::noDefaultsTest));
153         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "out", this::outTest));
154         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "output-archive", this::outputArchiveTest));
155         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "output-families", this::outputFamiliesTest));
156         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "output-file", this::outputFileTest));
157         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "output-licenses", this::outputLicensesTest));
158         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "output-standard", this::outputStandardTest));
159         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "output-style", this::outputStyleTest));
160         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "scan-hidden-directories", this::scanHiddenDirectoriesTest));
161         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "stylesheet", this::styleSheetTest));
162         addTest(OptionCollectionTest.OptionTest.namedTest(providerName, "xml", this::xmlTest));
163         super.validate(unsupportedArgs);
164     }
165 
166     // exclude tests
167     private void execExcludeTest(final Option option, final String[] args) {
168         String[] notExcluded = {"notbaz", "well._afile"};
169         String[] excluded = {"some.foo", "B.bar", "justbaz"};
170 
171         assertDoesNotThrow(() -> {
172             ReportConfiguration config = generateConfig(ImmutablePair.of(option, args));
173             DocumentNameMatcher excluder = config.getDocumentExcluder(baseName());
174             for (String fname : notExcluded) {
175                 DocumentName docName = mkDocName(fname);
176                 assertThat(excluder.matches(docName)).as(() -> dump(option, fname, excluder, docName)).isTrue();
177             }
178             for (String fname : excluded) {
179                 DocumentName docName = mkDocName(fname);
180                 assertThat(excluder.matches(docName)).as(() -> dump(option, fname, excluder, docName)).isFalse();
181             }
182         });
183     }
184 
185     private void excludeFileTest(final Option option) {
186         File outputFile = writeFile("exclude.txt", Arrays.asList(EXCLUDE_ARGS));
187         execExcludeTest(option, new String[]{outputFile.getAbsolutePath()});
188     }
189 
190     protected void excludeFileTest() {
191         excludeFileTest(Arg.EXCLUDE_FILE.find("exclude-file"));
192     }
193 
194     protected void inputExcludeFileTest() {
195         excludeFileTest(Arg.EXCLUDE_FILE.find("input-exclude-file"));
196     }
197 
198     protected void excludeTest() {
199         execExcludeTest(Arg.EXCLUDE.find("exclude"), EXCLUDE_ARGS);
200     }
201 
202     protected void inputExcludeTest() {
203         execExcludeTest(Arg.EXCLUDE.find("input-exclude"), EXCLUDE_ARGS);
204     }
205 
206     protected void inputExcludeStdTest() {
207         Option option = Arg.EXCLUDE_STD.find("input-exclude-std");
208         String[] args = {StandardCollection.MISC.name()};
209         String[] excluded = {"afile~", ".#afile", "%afile%", "._afile"};
210         String[] notExcluded = {"afile~more", "what.#afile", "%afile%withMore", "well._afile"};
211 
212         assertDoesNotThrow(() -> {
213             ReportConfiguration config = generateConfig(ImmutablePair.of(option, args));
214             DocumentNameMatcher excluder = config.getDocumentExcluder(baseName());
215             for (String fname : excluded) {
216                 DocumentName docName = mkDocName(fname);
217                 assertThat(excluder.matches(docName)).as(() -> dump(option, fname, excluder, docName)).isFalse();
218             }
219             for (String fname : notExcluded) {
220                 DocumentName docName = mkDocName(fname);
221                 assertThat(excluder.matches(docName)).as(() -> dump(option, fname, excluder, docName)).isTrue();
222             }
223         });
224     }
225 
226     protected void inputExcludeParsedScmTest() {
227         Option option = Arg.EXCLUDE_PARSE_SCM.find("input-exclude-parsed-scm");
228         String[] args = {"GIT"};
229         String[] lines = {
230                 "# somethings",
231                 "!thingone", "thing*", System.lineSeparator(),
232                 "# some fish",
233                 "**/fish", "*_fish",
234                 "# some colorful directories",
235                 "red/", "blue/*/"};
236         String[] notExcluded = {"thingone", "dir/fish_two", "some/thingone", "blue/fish/dory" };
237         String[] excluded = {"thingtwo", "some/things", "dir/fish", "red/fish", "blue/fish", "some/fish", "another/red_fish"};
238 
239         writeFile(".gitignore", Arrays.asList(lines));
240         File dir = new File(baseDir, "red");
241         org.apache.rat.utils.FileUtils.mkDir(dir);
242         dir = new File(baseDir, "blue");
243         dir = new File(dir, "fish");
244         org.apache.rat.utils.FileUtils.mkDir(dir);
245 
246         assertDoesNotThrow(() -> {
247             ReportConfiguration config = generateConfig(ImmutablePair.of(option, args));
248             DocumentNameMatcher excluder = config.getDocumentExcluder(baseName());
249             for (String fname : excluded) {
250                 DocumentName docName = mkDocName(fname);
251                 assertThat(excluder.matches(docName)).as(() -> dump(option, fname, excluder, docName)).isFalse();
252             }
253             for (String fname : notExcluded) {
254                 DocumentName docName = mkDocName(fname);
255                 assertThat(excluder.matches(docName)).as(() -> dump(option, fname, excluder, docName)).isTrue();
256             }
257         });
258     }
259 
260     private void inputExcludeSizeTest() {
261         Option option = Arg.EXCLUDE_SIZE.option();
262         String[] args = {"5"};
263         writeFile("Hi.txt", Collections.singletonList("Hi"));
264         writeFile("Hello.txt", Collections.singletonList("Hello"));
265         writeFile("HelloWorld.txt", Collections.singletonList("HelloWorld"));
266 
267         String[] notExcluded = {"Hello.txt", "HelloWorld.txt"};
268         String[] excluded = {"Hi.txt"};
269 
270         assertDoesNotThrow(() -> {
271             ReportConfiguration config = generateConfig(ImmutablePair.of(option, args));
272             DocumentNameMatcher excluder = config.getDocumentExcluder(baseName());
273             for (String fname : excluded) {
274                 DocumentName docName = mkDocName(fname);
275                 assertThat(excluder.matches(docName)).as(() -> dump(option, fname, excluder, docName)).isFalse();
276             }
277             for (String fname : notExcluded) {
278                 DocumentName docName = mkDocName(fname);
279                 assertThat(excluder.matches(docName)).as(() -> dump(option, fname, excluder, docName)).isTrue();
280             }
281         });
282     }
283 
284     // include tests
285     private void execIncludeTest(final Option option, final String[] args) {
286         Option excludeOption = Arg.EXCLUDE.option();
287         String[] notExcluded = {"B.bar", "justbaz", "notbaz"};
288         String[] excluded = {"some.foo"};
289 
290         assertDoesNotThrow(() -> {
291             ReportConfiguration config = generateConfig(ImmutablePair.of(option, args),
292                     ImmutablePair.of(excludeOption, EXCLUDE_ARGS));
293             DocumentNameMatcher excluder = config.getDocumentExcluder(baseName());
294             for (String fname : excluded) {
295                 DocumentName docName = mkDocName(fname);
296                 assertThat(excluder.matches(docName)).as(() -> dump(option, fname, excluder, docName)).isFalse();
297             }
298             for (String fname : notExcluded) {
299                 DocumentName docName = mkDocName(fname);
300                 assertThat(excluder.matches(docName)).as(() -> dump(option, fname, excluder, docName)).isTrue();
301             }
302         });
303     }
304 
305     private void includeFileTest(final Option option) {
306         File outputFile = writeFile("include.txt", Arrays.asList(INCLUDE_ARGS));
307         execIncludeTest(option, new String[]{outputFile.getAbsolutePath()});
308     }
309 
310     protected void inputIncludeFileTest() {
311         includeFileTest(Arg.INCLUDE_FILE.find("input-include-file"));
312     }
313 
314     protected void includesFileTest() {
315         includeFileTest(Arg.INCLUDE_FILE.find("includes-file"));
316     }
317 
318     protected void includeTest() {
319         execIncludeTest(Arg.INCLUDE.find("include"), INCLUDE_ARGS);
320     }
321 
322     protected void inputIncludeTest() {
323         execIncludeTest(Arg.INCLUDE.find("input-include"), INCLUDE_ARGS);
324     }
325 
326     protected void inputIncludeStdTest() {
327         ImmutablePair<Option, String[]> excludes = ImmutablePair.of(Arg.EXCLUDE.find("input-exclude"),
328                 new String[]{"*~more", "*~"});
329         Option option = Arg.INCLUDE_STD.find("input-include-std");
330         String[] args = {StandardCollection.MISC.name()};
331         String[] excluded = {"afile~more"};
332         String[] notExcluded = {"afile~", ".#afile", "%afile%", "._afile", "what.#afile", "%afile%withMore", "well._afile"};
333 
334         assertDoesNotThrow(() -> {
335             ReportConfiguration config = generateConfig(excludes, ImmutablePair.of(option, args));
336             DocumentNameMatcher excluder = config.getDocumentExcluder(baseName());
337             for (String fname : excluded) {
338                 DocumentName docName = mkDocName(fname);
339                 assertThat(excluder.matches(docName)).as(() -> dump(option, fname, excluder, docName)).isFalse();
340             }
341             for (String fname : notExcluded) {
342                 DocumentName docName = mkDocName(fname);
343                 assertThat(excluder.matches(docName)).as(() -> dump(option, fname, excluder, docName)).isTrue();
344             }
345         });
346     }
347 
348     protected void inputSourceTest() {
349         Option option = Arg.SOURCE.find("input-source");
350         assertDoesNotThrow(() -> {
351             ReportConfiguration config = generateConfig(ImmutablePair.of(option, new String[]{baseDir.getAbsolutePath()}));
352             assertThat(config.hasSource()).isTrue();
353         });
354     }
355 
356     // LICENSE tests
357     protected void execLicensesApprovedTest(final Option option, String[] args) {
358         Pair<Option, String[]> arg1 = ImmutablePair.of(option, args);
359 
360         assertDoesNotThrow(() -> {
361             ReportConfiguration config = generateConfig(arg1);
362             SortedSet<String> result = config.getLicenseIds(LicenseSetFactory.LicenseFilter.APPROVED);
363             assertThat(result).contains("one", "two");
364         });
365 
366         Pair<Option, String[]> arg2 = ImmutablePair.of(
367                 Arg.CONFIGURATION_NO_DEFAULTS.find("configuration-no-defaults"),
368                 null
369         );
370 
371         assertDoesNotThrow(() -> {
372             ReportConfiguration config = generateConfig(arg1, arg2);
373             SortedSet<String> result = config.getLicenseIds(LicenseSetFactory.LicenseFilter.APPROVED);
374             assertThat(result).containsExactly("one", "two");
375         });
376     }
377 
378     protected void helpLicenses() {
379         ByteArrayOutputStream output = new ByteArrayOutputStream();
380         PrintStream origin = System.out;
381         try (PrintStream out = new PrintStream(output)) {
382             System.setOut(out);
383             generateConfig(ImmutablePair.of(HELP_LICENSES.option(), null));
384         } catch (IOException e) {
385             fail(e.getMessage());
386         } finally {
387             System.setOut(origin);
388         }
389         String txt = output.toString();
390         assertThat(txt).contains("====== Licenses ======", "====== Defined Matchers ======", "====== Defined Families ======");
391     }
392 
393     protected void licensesApprovedFileTest() {
394         File outputFile = writeFile("licensesApproved.txt", Arrays.asList("one", "two"));
395         execLicensesApprovedTest(Arg.LICENSES_APPROVED_FILE.find("licenses-approved-file"),
396                 new String[]{outputFile.getAbsolutePath()});
397     }
398 
399     protected void licensesApprovedTest() {
400         execLicensesApprovedTest(Arg.LICENSES_APPROVED.find("licenses-approved"),
401                 new String[]{"one, two"});
402     }
403 
404     private void execLicensesDeniedTest(final Option option, final String[] args) {
405         assertDoesNotThrow(() -> {
406             ReportConfiguration config = generateConfig(ImmutablePair.of(option, args));
407             assertThat(config.getLicenseIds(LicenseSetFactory.LicenseFilter.ALL)).contains("ILLUMOS");
408             SortedSet<String> result = config.getLicenseIds(LicenseSetFactory.LicenseFilter.APPROVED);
409             assertThat(result).doesNotContain("ILLUMOS");
410         });
411     }
412 
413     protected void licensesDeniedTest() {
414         execLicensesDeniedTest(Arg.LICENSES_DENIED.find("licenses-denied"), new String[]{"ILLUMOS"});
415     }
416 
417     protected void licensesDeniedFileTest() {
418         File outputFile = writeFile("licensesDenied.txt", Collections.singletonList("ILLUMOS"));
419         execLicensesDeniedTest(Arg.LICENSES_DENIED_FILE.find("licenses-denied-file"),
420                 new String[]{outputFile.getAbsolutePath()});
421     }
422 
423     private void execLicenseFamiliesApprovedTest(final Option option, final String[] args) {
424         String catz = ILicenseFamily.makeCategory("catz");
425         Pair<Option, String[]> arg1 = ImmutablePair.of(option, args);
426 
427         assertDoesNotThrow(() -> {
428             ReportConfiguration config = generateConfig(arg1);
429             SortedSet<String> result = config.getLicenseCategories(LicenseSetFactory.LicenseFilter.APPROVED);
430             assertThat(result).contains(catz);
431 
432             Pair<Option, String[]> arg2 = ImmutablePair.of(Arg.CONFIGURATION_NO_DEFAULTS.find("configuration-no-defaults"), null);
433             config = generateConfig(arg1, arg2);
434             result = config.getLicenseCategories(LicenseSetFactory.LicenseFilter.APPROVED);
435             assertThat(result).containsExactly(catz);
436         });
437     }
438 
439     protected void licenseFamiliesApprovedFileTest() {
440         File outputFile = writeFile("familiesApproved.txt", Collections.singletonList("catz"));
441         execLicenseFamiliesApprovedTest(Arg.FAMILIES_APPROVED_FILE.find("license-families-approved-file"),
442                 new String[]{outputFile.getAbsolutePath()});
443     }
444 
445     protected void licenseFamiliesApprovedTest() {
446         execLicenseFamiliesApprovedTest(Arg.FAMILIES_APPROVED.find("license-families-approved"),
447                 new String[]{"catz"});
448     }
449 
450     private void execLicenseFamiliesDeniedTest(final Option option, final String[] args) {
451         String gpl = ILicenseFamily.makeCategory("GPL");
452 
453         assertDoesNotThrow(() -> {
454             ReportConfiguration config = generateConfig(ImmutablePair.of(option, args));
455             assertThat(config.getLicenseCategories(LicenseSetFactory.LicenseFilter.ALL)).contains(gpl);
456             SortedSet<String> result = config.getLicenseCategories(LicenseSetFactory.LicenseFilter.APPROVED);
457             assertThat(result).doesNotContain(gpl);
458         });
459     }
460 
461     protected void licenseFamiliesDeniedFileTest() {
462         File outputFile = writeFile("familiesDenied.txt", Collections.singletonList("GPL"));
463         execLicenseFamiliesDeniedTest(Arg.FAMILIES_DENIED_FILE.find("license-families-denied-file"),
464                 new String[]{outputFile.getAbsolutePath()});
465     }
466 
467     protected void licenseFamiliesDeniedTest() {
468         execLicenseFamiliesDeniedTest(Arg.FAMILIES_DENIED.find("license-families-denied"),
469                 new String[]{"GPL"});
470     }
471 
472     protected void counterMaxTest() {
473         Option option = Arg.COUNTER_MAX.option();
474         String[] args = {null, null};
475 
476         assertDoesNotThrow(() -> {
477             ReportConfiguration config = generateConfig(ImmutablePair.nullPair());
478             assertThat(config.getClaimValidator().getMax(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0);
479             args[0] = "Unapproved:-1";
480             args[1] = "ignored:1";
481             config = generateConfig(ImmutablePair.of(option, args));
482             assertThat(config.getClaimValidator().getMax(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(Integer.MAX_VALUE);
483             assertThat(config.getClaimValidator().getMax(ClaimStatistic.Counter.IGNORED)).isEqualTo(1);
484             args[1] = "unapproved:5";
485             args[0] = "ignored:0";
486             config = generateConfig(ImmutablePair.of(option, args));
487             assertThat(config.getClaimValidator().getMax(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(5);
488             assertThat(config.getClaimValidator().getMax(ClaimStatistic.Counter.IGNORED)).isEqualTo(0);
489         });
490     }
491 
492     protected void counterMinTest() {
493         Option option = Arg.COUNTER_MIN.option();
494         String[] args = {null, null};
495 
496         assertDoesNotThrow(() -> {
497             ReportConfiguration config = generateConfig(ImmutablePair.nullPair());
498             assertThat(config.getClaimValidator().getMin(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0);
499             args[0] = "Unapproved:1";
500             args[1] = "ignored:1";
501             config = generateConfig(ImmutablePair.of(option, args));
502             assertThat(config.getClaimValidator().getMin(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
503             assertThat(config.getClaimValidator().getMin(ClaimStatistic.Counter.IGNORED)).isEqualTo(1);
504             args[1] = "unapproved:5";
505             args[0] = "ignored:0";
506             config = generateConfig(ImmutablePair.of(option, args));
507             assertThat(config.getClaimValidator().getMin(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(5);
508             assertThat(config.getClaimValidator().getMin(ClaimStatistic.Counter.IGNORED)).isEqualTo(0);
509         });
510     }
511 
512     private String writeConfigXML(String name) {
513         String licenseText = """
514                 Licensed to the Apache Software Foundation (ASF) under one or more
515                        contributor license agreements.  See the NOTICE file distributed with
516                        this work for additional information regarding copyright ownership.
517                                The ASF licenses this file to You under the Apache License, Version 2.0
518                        (the "License"); you may not use this file except in compliance with
519                        the License.  You may obtain a copy of the License at
520                 
521                        https://www.apache.org/licenses/LICENSE-2.0
522                 
523                        Unless required by applicable law or agreed to in writing, software
524                        distributed under the License is distributed on an "AS IS" BASIS,
525                                WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
526                                See the License for the specific language governing permissions and
527                        limitations under the License.""";
528         String capName = WordUtils.capitalize(name);
529         String upperName = name.toUpperCase(Locale.ROOT);
530 
531         Path xmlPath = this.baseDir.toPath().resolve(".rat/" + capName + ".xml");
532         File f = xmlPath.toFile();
533         try {
534             FileUtils.forceMkdir(f.getParentFile());
535             try (FileWriter writer = new FileWriter(f);
536                  XmlWriter xmlWriter = new XmlWriter(writer)) {
537 
538                 xmlWriter.startDocument()
539                         .comment(licenseText)
540                         .startElement("rat-config")
541                         .startElement("families")
542                         .startElement("family")
543                         .attribute("id", upperName)
544                         .attribute("name", "from " + capName + ".xml")
545                         .closeElement("families")
546                         .startElement("licenses")
547                         .startElement("license")
548                         .attribute("family", upperName)
549                         .startElement("text")
550                         .content(name)
551                         .closeElement("rat-config");
552             }
553         } catch (IOException e) {
554             throw new RuntimeException(e);
555         }
556         return xmlPath.toString();
557     }
558 
559     private void configTest(final Option option) {
560         String[] args = {writeConfigXML("one"), writeConfigXML("two")};
561         Pair<Option, String[]> arg1 = ImmutablePair.of(option, args);
562 
563         assertDoesNotThrow(() -> {
564             ReportConfiguration config = generateConfig(arg1);
565             SortedSet<ILicense> set = config.getLicenses(LicenseSetFactory.LicenseFilter.ALL);
566             assertThat(set).hasSizeGreaterThan(2);
567             assertThat(LicenseSetFactory.search("ONE", "ONE", set)).isPresent();
568             assertThat(LicenseSetFactory.search("TWO", "TWO", set)).isPresent();
569 
570             Pair<Option, String[]> arg2 = ImmutablePair.of(Arg.CONFIGURATION_NO_DEFAULTS.find("configuration-no-defaults"), null);
571 
572             config = generateConfig(arg1, arg2);
573             set = config.getLicenses(LicenseSetFactory.LicenseFilter.ALL);
574             assertThat(set).hasSize(2);
575             assertThat(LicenseSetFactory.search("ONE", "ONE", set)).isPresent();
576             assertThat(LicenseSetFactory.search("TWO", "TWO", set)).isPresent();
577         });
578     }
579 
580     protected void licensesTest() {
581         configTest(Arg.CONFIGURATION.find("licenses"));
582     }
583 
584     protected void configTest() {
585         configTest(Arg.CONFIGURATION.find("config"));
586     }
587 
588     private void noDefaultsTest(final Option arg) {
589         assertDoesNotThrow(() -> {
590             ReportConfiguration config = generateConfig(ImmutablePair.of(arg, null));
591             assertThat(config.getLicenses(LicenseSetFactory.LicenseFilter.ALL)).isEmpty();
592             config = generateConfig(ImmutablePair.nullPair());
593             assertThat(config.getLicenses(LicenseSetFactory.LicenseFilter.ALL)).isNotEmpty();
594         });
595     }
596 
597     protected void noDefaultsTest() {
598         noDefaultsTest(Arg.CONFIGURATION_NO_DEFAULTS.find("no-default-licenses"));
599     }
600 
601     protected void configurationNoDefaultsTest() {
602         noDefaultsTest(Arg.CONFIGURATION_NO_DEFAULTS.find("configuration-no-defaults"));
603     }
604 
605     protected void dryRunTest() {
606         assertDoesNotThrow(() -> {
607             ReportConfiguration config = generateConfig(ImmutablePair.of(Arg.DRY_RUN.find("dry-run"), null));
608             assertThat(config.isDryRun()).isTrue();
609             config = generateConfig(ImmutablePair.nullPair());
610             assertThat(config.isDryRun()).isFalse();
611         });
612     }
613 
614     private void editCopyrightTest(final Option option) {
615         assertDoesNotThrow(() -> {
616             Pair<Option, String[]> arg1 = ImmutablePair.of(option, new String[]{"MyCopyright"});
617             ReportConfiguration config;
618             try {
619                 config = generateConfig(arg1);
620                 assertThat(config.getCopyrightMessage()).as("Copyright without --edit-license should not work").isNull();
621             } catch (IOException e) {
622                 if (e.getCause() != null) {
623                     fail(e.getMessage() + ": " + e.getCause().getMessage());
624                 }
625                 throw e;
626             }
627 
628             Pair<Option, String[]> arg2 = ImmutablePair.of(Arg.EDIT_ADD.find("edit-license"), null);
629             try {
630                 config = generateConfig(arg1, arg2);
631                 assertThat(config.getCopyrightMessage()).isEqualTo("MyCopyright");
632             } catch (IOException e) {
633                 if (e.getCause() != null) {
634                     fail(e.getMessage() + ": " + e.getCause().getMessage());
635                 }
636                 throw e;
637             }
638         });
639     }
640 
641     protected void copyrightTest() {
642         editCopyrightTest(Arg.EDIT_COPYRIGHT.find("copyright"));
643     }
644 
645     protected void editCopyrightTest() {
646         editCopyrightTest(Arg.EDIT_COPYRIGHT.find("edit-copyright"));
647     }
648 
649     private void editLicenseTest(final Option option) {
650         assertDoesNotThrow(() -> {
651             ReportConfiguration config = generateConfig(ImmutablePair.of(option, null));
652             assertThat(config.isAddingLicenses()).isTrue();
653             config = generateConfig(ImmutablePair.nullPair());
654             assertThat(config.isAddingLicenses()).isFalse();
655         });
656     }
657 
658     protected void addLicenseTest() {
659         editLicenseTest(Arg.EDIT_ADD.find("addLicense"));
660     }
661 
662     protected void editLicenseTest() {
663         editLicenseTest(Arg.EDIT_ADD.find("edit-license"));
664     }
665 
666     private void overwriteTest(final Option option) {
667         Pair<Option, String[]> arg1 = ImmutablePair.of(option, null);
668 
669         assertDoesNotThrow(() -> {
670             ReportConfiguration config = generateConfig(arg1);
671             assertThat(config.isAddingLicensesForced()).isFalse();
672             Pair<Option, String[]> arg2 = ImmutablePair.of(Arg.EDIT_ADD.find("edit-license"), null);
673 
674             config = generateConfig(arg1, arg2);
675             assertThat(config.isAddingLicensesForced()).isTrue();
676         });
677     }
678 
679     protected void forceTest() {
680         overwriteTest(Arg.EDIT_OVERWRITE.find("force"));
681     }
682 
683     protected void editOverwriteTest() {
684         overwriteTest(Arg.EDIT_OVERWRITE.find("edit-overwrite"));
685     }
686 
687     protected void logLevelTest() {
688         Option option = Arg.LOG_LEVEL.find("log-level");
689         String[] args = {null};
690         Level logLevel = DefaultLog.getInstance().getLevel();
691         try {
692             for (Level level : Level.values()) {
693                 try {
694                     args[0] = level.name();
695                     generateConfig(ImmutablePair.of(option, args));
696                     assertThat(DefaultLog.getInstance().getLevel()).isEqualTo(level);
697                 } catch (IOException e) {
698                     fail(e.getMessage());
699                 }
700             }
701         } finally {
702             DefaultLog.getInstance().setLevel(logLevel);
703         }
704     }
705 
706     private void archiveTest(final Option option) {
707         String[] args = {null};
708 
709         assertDoesNotThrow(() -> {
710             for (ReportConfiguration.Processing proc : ReportConfiguration.Processing.values()) {
711                 args[0] = proc.name();
712                 ReportConfiguration config = generateConfig(ImmutablePair.of(option, args));
713                 assertThat(config.getArchiveProcessing()).isEqualTo(proc);
714             }
715         });
716     }
717 
718     protected void outputArchiveTest() {
719         archiveTest(Arg.OUTPUT_ARCHIVE.find("output-archive"));
720     }
721 
722     private void listFamilies(final Option option) {
723         String[] args = {null};
724 
725         assertDoesNotThrow(() -> {
726             for (LicenseSetFactory.LicenseFilter filter : LicenseSetFactory.LicenseFilter.values()) {
727                 args[0] = filter.name();
728                 ReportConfiguration config = generateConfig(ImmutablePair.of(option, args));
729                 assertThat(config.listFamilies()).isEqualTo(filter);
730             }
731         });
732     }
733 
734     protected void listFamiliesTest() {
735         listFamilies(Arg.OUTPUT_FAMILIES.find("list-families"));
736     }
737 
738     protected void outputFamiliesTest() {
739         listFamilies(Arg.OUTPUT_FAMILIES.find("output-families"));
740     }
741 
742     private void outTest(final Option option) {
743         File outFile = new File(baseDir, "outexample-" + option.getLongOpt());
744         String[] args = new String[]{outFile.getAbsolutePath()};
745 
746         assertDoesNotThrow(() -> {
747             ReportConfiguration config = generateConfig(ImmutablePair.of(option, args));
748             try (OutputStream os = config.getOutput().get()) {
749                 os.write("Hello world".getBytes());
750             } catch (IOException e) {
751                 throw new RuntimeException(e);
752             }
753             try (BufferedReader reader = new BufferedReader(new InputStreamReader(Files.newInputStream(outFile.toPath())))) {
754                 assertThat(reader.readLine()).isEqualTo("Hello world");
755             } catch (IOException e) {
756                 throw new RuntimeException(e);
757             }
758         });
759     }
760 
761     protected void outTest() {
762         outTest(Arg.OUTPUT_FILE.find("out"));
763     }
764 
765     protected void outputFileTest() {
766         outTest(Arg.OUTPUT_FILE.find("output-file"));
767     }
768 
769     private void listLicenses(final Option option) {
770         String[] args = {null};
771 
772         assertDoesNotThrow(() -> {
773             for (LicenseSetFactory.LicenseFilter filter : LicenseSetFactory.LicenseFilter.values()) {
774                 args[0] = filter.name();
775                 ReportConfiguration config = generateConfig(ImmutablePair.of(option, args));
776                 assertThat(config.listLicenses()).isEqualTo(filter);
777             }
778         });
779     }
780 
781     protected void listLicensesTest() {
782         listLicenses(Arg.OUTPUT_LICENSES.find("list-licenses"));
783     }
784 
785     protected void outputLicensesTest() {
786         listLicenses(Arg.OUTPUT_LICENSES.find("output-licenses"));
787     }
788 
789     private void standardTest(final Option option) {
790         String[] args = {null};
791         assertDoesNotThrow(() -> {
792             for (ReportConfiguration.Processing proc : ReportConfiguration.Processing.values()) {
793                 args[0] = proc.name();
794                 ReportConfiguration config = generateConfig(ImmutablePair.of(option, args));
795                 assertThat(config.getStandardProcessing()).isEqualTo(proc);
796             }
797         });
798     }
799 
800     protected void outputStandardTest() {
801         standardTest(Arg.OUTPUT_STANDARD.find("output-standard"));
802     }
803 
804     private void styleSheetTest(final Option option) {
805         // copy the dummy stylesheet so that we have a local file for users of the testing jar.
806         File file = new File(baseDir, "stylesheet-" + option.getLongOpt());
807         DocumentName xsltFile = DocumentName.builder(file).build();
808         try (
809                 InputStream in = ReporterTest.class.getResourceAsStream("MatcherContainerResource.txt");
810                 OutputStream out = Files.newOutputStream(file.toPath())) {
811             if (in == null) {
812                 fail("Could not copy MatcherContainerResource.txt: resource not found");
813             } else {
814                 IOUtils.copy(in, out);
815             }
816         } catch (IOException e) {
817             fail("Could not copy MatcherContainerResource.txt: " + e.getMessage());
818         }
819 
820         // run the test
821         String[] args = {null};
822         assertDoesNotThrow(() -> {
823             for (String sheet : new String[]{"plain-rat", "missing-headers", "unapproved-licenses", xsltFile.getName()}) {
824                 args[0] = sheet;
825                 ReportConfiguration config = generateConfig(ImmutablePair.of(option, args));
826                 try (InputStream expected = StyleSheets.getStyleSheet(sheet, xsltFile.getBaseDocumentName()).ioSupplier().get();
827                      InputStream actual = config.getStyleSheet().get()) {
828                     String expectedStr =  IOUtils.toString(expected, StandardCharsets.UTF_8);
829                     String actualStr =  IOUtils.toString(actual, StandardCharsets.UTF_8);
830                     assertThat(actualStr).as(
831                                 () -> String.format("'%s' is not correct: %s != %s",
832                                 config.getStyleSheetDescriptor().name(),
833                                 actualStr, expectedStr))
834                             .isEqualTo(expectedStr);
835                 }
836             }
837         });
838     }
839 
840     protected void styleSheetTest() {
841         styleSheetTest(Arg.OUTPUT_STYLE.find("stylesheet"));
842     }
843 
844     protected void outputStyleTest() {
845         styleSheetTest(Arg.OUTPUT_STYLE.find("output-style"));
846     }
847 
848     protected void scanHiddenDirectoriesTest() {
849         assertDoesNotThrow(() -> {
850             ReportConfiguration config = generateConfig(ImmutablePair.of(Arg.INCLUDE_STD.find("scan-hidden-directories"), null));
851             DocumentNameMatcher excluder = config.getDocumentExcluder(baseName());
852             assertThat(excluder.matches(mkDocName(".file"))).as(".file").isTrue();
853         });
854     }
855 
856     protected void xmlTest() {
857         assertDoesNotThrow(() -> {
858             ReportConfiguration config = generateConfig(ImmutablePair.of(Arg.OUTPUT_STYLE.find("xml"), null));
859             try (InputStream expected = StyleSheets.getStyleSheet("xml", null).ioSupplier().get();
860                  InputStream actual = config.getStyleSheet().get()) {
861                 assertThat(IOUtils.contentEquals(expected, actual)).as("'xml' does not match").isTrue();
862             }
863         });
864     }
865 }