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