View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.rat.mp;
18  
19  import static java.lang.String.format;
20  import static java.nio.charset.StandardCharsets.UTF_8;
21  import static org.apache.rat.mp.RatTestHelpers.ensureRatReportIsCorrect;
22  import static org.assertj.core.api.Assertions.assertThat;
23  import static org.assertj.core.api.Fail.fail;
24  
25  import java.io.File;
26  
27  import java.io.IOException;
28  import java.nio.file.Files;
29  import java.nio.file.Path;
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.HashMap;
33  import java.util.List;
34  import java.util.Map;
35  import javax.xml.xpath.XPath;
36  import javax.xml.xpath.XPathFactory;
37  import org.apache.commons.io.FileUtils;
38  import org.apache.rat.ReportConfiguration;
39  import org.apache.rat.ReportConfigurationTest;
40  import org.apache.rat.ReporterTestUtils;
41  import org.apache.rat.api.Document;
42  import org.apache.rat.commandline.Arg;
43  import org.apache.rat.license.ILicenseFamily;
44  import org.apache.rat.license.LicenseSetFactory;
45  import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
46  import org.apache.rat.report.claim.ClaimStatistic;
47  import org.apache.rat.test.AbstractConfigurationOptionsProvider;
48  import org.apache.rat.test.utils.Resources;
49  import org.apache.rat.testhelpers.TextUtils;
50  import org.apache.rat.testhelpers.XmlUtils;
51  import org.junit.jupiter.api.AfterAll;
52  import org.junit.jupiter.api.Assertions;
53  import org.junit.jupiter.api.Test;
54  import org.junit.jupiter.api.condition.EnabledOnOs;
55  import org.junit.jupiter.api.condition.OS;
56  import org.junit.jupiter.api.io.TempDir;
57  import org.w3c.dom.NamedNodeMap;
58  import org.w3c.dom.NodeList;
59  
60  /**
61   * Test case for the {@link RatCheckMojo} and {@link RatReportMojo}.
62   */
63  public class RatCheckMojoTest {
64  
65      @TempDir
66      static Path tempDir;
67  
68      private final static XPath xPath = XPathFactory.newInstance().newXPath();
69  
70      @AfterAll
71      static void preserveData() {
72          AbstractConfigurationOptionsProvider.preserveData(tempDir.toFile(), "unit");
73      }
74  
75      @AfterAll
76      @EnabledOnOs(OS.WINDOWS)
77      static void cleanup() {
78          System.gc(); // hacky workaround for windows bug.
79      }
80  
81      private RatCheckMojo getMojo(File pomFile) throws IOException {
82          try {
83              final RatCheckMojo mojo = new OptionMojoTest.SimpleMojoTestcase() {
84              }.getMojo(pomFile);
85              Assertions.assertNotNull(mojo);
86              return mojo;
87          } catch (IOException e) {
88              throw e;
89          } catch (Exception e) {
90              throw new IOException(format("Unable to generate mojo for %s", pomFile), e);
91          }
92      }
93  
94      /**
95       * Creates a new instance of {@link AbstractRatMojo}.
96       *
97       * @param testDir The directory, where to look for a pom.xml file.
98       * copy location.
99       * @return The configured Mojo.
100      * @throws Exception An error occurred while creating the Mojo.
101      */
102     private RatCheckMojo newRatMojo(String testDir) throws Exception {
103         Arg.reset();
104         final File sourceDir = Resources.getResourceDirectory(format("unit/%s", testDir));
105         final File baseDir = tempDir.resolve(testDir).toFile();
106         FileUtils.copyDirectory(sourceDir, baseDir);
107         final File pomFile = new File(baseDir, "pom.xml");
108         RatCheckMojo mojo = getMojo(pomFile);
109         assertThat(mojo).isNotNull();
110         assertThat(mojo.getProject())
111                 .as("The mojo is missing its MavenProject, which will result in an NPE during RAT runs.")
112                 .isNotNull();
113 
114         File buildDirectory = new File(baseDir, "target");
115         assertThat(buildDirectory.mkdirs()).isTrue();
116         final File ratTxtFile = new File(buildDirectory, "rat.txt");
117         FileUtils.write(ratTxtFile, "", UTF_8); // Ensure the output file exists and is empty (rerunning the test will append)
118         mojo.setOutputFile(ratTxtFile.getAbsolutePath());
119         return mojo;
120     }
121 
122     /**
123      * Runs a check, which should expose no problems.
124      *
125      * @throws Exception The test failed.
126      */
127     @Test
128     void it1() throws Exception {
129         final RatCheckMojo mojo = newRatMojo("it1");
130         final File ratTxtFile = mojo.getRatTxtFile();
131 
132         ReportConfiguration config = mojo.getConfiguration();
133         ReportConfigurationTest.validateDefault(config);
134 
135         mojo.execute();
136         Map<ClaimStatistic.Counter, String> data = new HashMap<>();
137         data.put(ClaimStatistic.Counter.ARCHIVES, "0");
138         data.put(ClaimStatistic.Counter.APPROVED, "1");
139         data.put(ClaimStatistic.Counter.BINARIES, "0");
140         data.put(ClaimStatistic.Counter.DOCUMENT_TYPES, "2");
141         data.put(ClaimStatistic.Counter.IGNORED, "2");
142         data.put(ClaimStatistic.Counter.LICENSE_CATEGORIES, "1");
143         data.put(ClaimStatistic.Counter.LICENSE_NAMES, "1");
144         data.put(ClaimStatistic.Counter.NOTICES, "0");
145         data.put(ClaimStatistic.Counter.STANDARDS, "1");
146         data.put(ClaimStatistic.Counter.UNAPPROVED, "0");
147         data.put(ClaimStatistic.Counter.UNKNOWN, "0");
148 
149         org.w3c.dom.Document document = XmlUtils.toDom(Files.newInputStream(ratTxtFile.toPath()));
150         XPath xPath = XPathFactory.newInstance().newXPath();
151 
152         for (ClaimStatistic.Counter counter : ClaimStatistic.Counter.values()) {
153             String xpath = String.format("/rat-report/statistics/statistic[@name='%s']", counter.displayName());
154             Map<String, String> map = mapOf("approval", "true", "count", data.get(counter),
155                     "description", counter.getDescription());
156             XmlUtils.assertAttributes(document, xPath, xpath, map);
157         }
158 
159         XmlUtils.assertAttributes(document, xPath, "/rat-report/resource[@name='/.bzrignore']",
160                 mapOf("mediaType", "application/octet-stream", "type", "IGNORED"));
161 
162         XmlUtils.assertAttributes(document, xPath, "/rat-report/resource[@name='/pom.xml']",
163                 mapOf("mediaType", "application/xml", "type", "STANDARD", "encoding", "ISO-8859-1"));
164     }
165 
166     private static Map<String, String> mapOf(String... parts) {
167         Map<String, String> map = new HashMap<>();
168         for (int i = 0; i < parts.length; i += 2) {
169             map.put(parts[i], parts[i + 1]);
170         }
171         return map;
172     }
173 
174     /**
175      * Runs a check, which should detect a problem.
176      *
177      * @throws Exception The test failed.
178      */
179     @Test
180     void it2() throws Exception {
181         final RatCheckMojo mojo = newRatMojo("it2");
182         final File ratTxtFile = mojo.getRatTxtFile();
183         final String[] expected = {
184                 "^Files with unapproved licenses\\s+\\*+\\s+\\Q/src.txt\\E\\s+",
185                 ReporterTestUtils.counterText(ClaimStatistic.Counter.NOTICES, 0, false),
186                 ReporterTestUtils.counterText(ClaimStatistic.Counter.BINARIES, 0, false),
187                 ReporterTestUtils.counterText(ClaimStatistic.Counter.ARCHIVES, 0, false),
188                 ReporterTestUtils.counterText(ClaimStatistic.Counter.STANDARDS, 2, false),
189                 ReporterTestUtils.counterText(ClaimStatistic.Counter.IGNORED, 1, false),
190                 ReporterTestUtils.apacheLicenseVersion2(1),
191                 ReporterTestUtils.unknownLicense(1),
192                 ReporterTestUtils.documentOut(false, Document.Type.STANDARD, "/src.txt") +
193                         ReporterTestUtils.UNKNOWN_LICENSE,
194                 ReporterTestUtils.documentOut(true, Document.Type.STANDARD, "/pom.xml") +
195                         ReporterTestUtils.APACHE_LICENSE
196         };
197         try {
198             mojo.execute();
199             fail("Expected RatCheckException");
200         } catch (RatCheckException e) {
201             final String msg = e.getMessage();
202             assertThat(msg.contains(ratTxtFile.getName())).as(() -> format("report filename was not contained in '%s'", msg))
203                     .isTrue();
204             assertThat(msg.toUpperCase()).contains("UNAPPROVED EXCEEDED MINIMUM");
205 
206             ensureRatReportIsCorrect(ratTxtFile, expected, TextUtils.EMPTY);
207         }
208     }
209 
210     /**
211      * Tests adding license headers.
212      */
213     @Test
214     void it3() throws Exception {
215         final RatCheckMojo mojo = newRatMojo("it3");
216         final File ratTxtFile = mojo.getRatTxtFile();
217 
218         ReportConfiguration config = mojo.getConfiguration();
219         assertThat(config.isAddingLicenses()).as("should be adding licenses").isTrue();
220         mojo.execute();
221         org.w3c.dom.Document document = XmlUtils.toDom(Files.newInputStream(ratTxtFile.toPath()));
222 
223         XmlUtils.assertAttributes(document, xPath, "/rat-report/resource[@name='/pom.xml']", "type",
224                 "STANDARD");
225         XmlUtils.assertAttributes(document, xPath, "/rat-report/resource[@name='/src.apt']", "type",
226                 "STANDARD");
227         XmlUtils.assertIsPresent(document, xPath, "/rat-report/resource[@name='/src.apt']/license[@approval='false']");
228 
229         XmlUtils.assertAttributes(document, xPath, "/rat-report/resource[@name='/src.apt']", "type",
230                 "STANDARD");
231 
232         for (Document.Type type : Document.Type.values()) {
233             if (type == Document.Type.STANDARD) {
234                 XmlUtils.assertAttributes(document, xPath, "/rat-report/statistics/documentType[@name='STANDARD']", "count",
235                         "2");
236             } else if (type == Document.Type.IGNORED) {
237                 XmlUtils.assertAttributes(document, xPath, "/rat-report/statistics/documentType[@name='IGNORED']", "count",
238                         "1");
239             } else {
240                 XmlUtils.assertIsNotPresent(document, xPath, format("/rat-report/statistics/documentType[@name='%s']", type));
241             }
242         }
243     }
244 
245     /**
246      * Tests defining licenses in configuration
247      */
248     @Test
249     void it5() throws Exception {
250         final RatCheckMojo mojo = newRatMojo("it5");
251         final File ratTxtFile = mojo.getRatTxtFile();
252 
253         ReportConfiguration config = mojo.getConfiguration();
254         assertThat(config.isAddingLicenses()).as("Should not be adding licenses").isFalse();
255         assertThat(config.isAddingLicensesForced()).as("Should not be forcing licenses").isFalse();
256 
257         ReportConfigurationTest.validateDefaultApprovedLicenses(config, 1);
258         assertThat(config.getLicenseCategories(LicenseFilter.APPROVED)).doesNotContain(ILicenseFamily.makeCategory("YAL"))
259                 .contains(ILicenseFamily.makeCategory("CC"));
260         ReportConfigurationTest.validateDefaultLicenseFamilies(config, "YAL", "CC");
261         assertThat(LicenseSetFactory.familySearch("YAL", config.getLicenseFamilies(LicenseFilter.APPROVED))).isNull();
262         assertThat(LicenseSetFactory.familySearch("YAL", config.getLicenseFamilies(LicenseFilter.ALL))).isNotNull();
263         assertThat(LicenseSetFactory.familySearch("CC", config.getLicenseFamilies(LicenseFilter.APPROVED))).isNotNull();
264         assertThat(LicenseSetFactory.familySearch("CC", config.getLicenseFamilies(LicenseFilter.ALL))).isNotNull();
265 
266         ReportConfigurationTest.validateDefaultLicenses(config, "CC-BY-NC-ND", "YAL");
267         assertThat(LicenseSetFactory.search("YAL", "YAL", config.getLicenses(LicenseFilter.ALL))).isPresent();
268 
269         mojo.execute();
270 
271         Map<ClaimStatistic.Counter, String> data = new HashMap<>();
272         data.put(ClaimStatistic.Counter.APPROVED, "1");
273         data.put(ClaimStatistic.Counter.ARCHIVES, "0");
274         data.put(ClaimStatistic.Counter.BINARIES, "0");
275         data.put(ClaimStatistic.Counter.DOCUMENT_TYPES, "2");
276         data.put(ClaimStatistic.Counter.IGNORED, "3");
277         data.put(ClaimStatistic.Counter.LICENSE_CATEGORIES, "1");
278         data.put(ClaimStatistic.Counter.LICENSE_NAMES, "1");
279         data.put(ClaimStatistic.Counter.NOTICES, "0");
280         data.put(ClaimStatistic.Counter.STANDARDS, "1");
281         data.put(ClaimStatistic.Counter.UNAPPROVED, "0");
282         data.put(ClaimStatistic.Counter.UNKNOWN, "0");
283 
284         org.w3c.dom.Document document = XmlUtils.toDom(Files.newInputStream(ratTxtFile.toPath()));
285         XPath xPath = XPathFactory.newInstance().newXPath();
286 
287         for (ClaimStatistic.Counter counter : ClaimStatistic.Counter.values()) {
288             String xpath = String.format("/rat-report/statistics/statistic[@name='%s']", counter.displayName());
289             Map<String, String> map = mapOf("approval",
290                     "true",
291                     "count", data.get(counter),
292                     "description", counter.getDescription());
293             XmlUtils.assertAttributes(document, xPath, xpath, map);
294         }
295 
296         XmlUtils.assertAttributes(document, xPath, "/rat-report/resource[@name='/.rat']",
297                 "mediaType", "application/octet-stream", "type", "IGNORED", "isDirectory", "true");
298         XmlUtils.assertAttributes(document, xPath, "/rat-report/resource[@name='/pom.xml']",
299                 "mediaType", "application/xml", "type", "IGNORED", "isDirectory", "false");
300         XmlUtils.assertAttributes(document, xPath, "/rat-report/resource[@name='/src/main/java/nl/basjes/something/Something.java']",
301                 "mediaType", "text/x-java-source", "type", "STANDARD", "encoding", "ISO-8859-1");
302         XmlUtils.assertAttributes(document, xPath, "/rat-report/resource[@name='/src/main/java/nl/basjes/something/Something.java']/license",
303                 "approval", "true", "family", ILicenseFamily.makeCategory("CC"), "id", "CC-BY-NC-ND", "name",
304                 "Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International");
305     }
306 
307     /**
308      * Runs a check, which should expose no problems.
309      *
310      * @throws Exception The test failed.
311      */
312     @Test
313     void rat343() throws Exception {
314         final RatCheckMojo mojo = newRatMojo("RAT-343");
315         final File ratTxtFile = mojo.getRatTxtFile();
316         // POM reports AL, BSD and CC BYas BSD because it contains the BSD and CC BY strings
317         final String[] expected = {
318                 ReporterTestUtils.documentOut(false, Document.Type.STANDARD, "/pom.xml") +
319                         ReporterTestUtils.APACHE_LICENSE +
320                         ReporterTestUtils.licenseOut("BSD", "BSD") +
321                         ReporterTestUtils.licenseOut("CC BY", "Creative Commons Attribution (Unapproved)"),
322                 ReporterTestUtils.counterText(ClaimStatistic.Counter.NOTICES, 0, false),
323                 ReporterTestUtils.counterText(ClaimStatistic.Counter.BINARIES, 0, false),
324                 ReporterTestUtils.counterText(ClaimStatistic.Counter.ARCHIVES, 0, false),
325                 ReporterTestUtils.counterText(ClaimStatistic.Counter.STANDARDS, 1, false),
326                 ReporterTestUtils.counterText(ClaimStatistic.Counter.IGNORED, 1, false),
327                 ReporterTestUtils.apacheLicenseVersion2(1),
328                 "^BSD: 1 ",
329                 "^Creative Commons Attribution: 1 ",
330         };
331         final String[] notExpected = {
332                 "^Unknown License:"
333         };
334 
335         ReportConfiguration config = mojo.getConfiguration();
336         // validate configuration
337         assertThat(config.isAddingLicenses()).isFalse();
338         assertThat(config.isAddingLicensesForced()).isFalse();
339         assertThat(config.getCopyrightMessage()).isNull();
340         assertThat(config.getStyleSheet()).withFailMessage("Stylesheet should not be null").isNotNull();
341 
342         ReportConfigurationTest.validateDefaultApprovedLicenses(config, 1);
343         ReportConfigurationTest.validateDefaultLicenseFamilies(config, "BSD", "CC BY");
344         ReportConfigurationTest.validateDefaultLicenses(config, "BSD", "CC BY");
345 
346         mojo.execute();
347         ensureRatReportIsCorrect(ratTxtFile, expected, notExpected);
348     }
349 
350     /**
351      * Tests verifying gitignore parsing
352      */
353     @Test
354     void rat335() throws Exception {
355         final RatCheckMojo mojo = newRatMojo("RAT-335");
356         final File ratTxtFile = mojo.getRatTxtFile();
357         try {
358             mojo.execute();
359             fail("Expected RatCheckException");
360         } catch (RatCheckException e) {
361             final String msg = e.getMessage();
362             assertThat(msg).contains(ratTxtFile.getName());
363             assertThat(msg).contains("UNAPPROVED exceeded minimum");
364 
365             Map<ClaimStatistic.Counter, String> data = new HashMap<>();
366             data.put(ClaimStatistic.Counter.APPROVED, "1");
367             data.put(ClaimStatistic.Counter.ARCHIVES, "0");
368             data.put(ClaimStatistic.Counter.BINARIES, "0");
369             data.put(ClaimStatistic.Counter.DOCUMENT_TYPES, "3");
370             data.put(ClaimStatistic.Counter.IGNORED, "6");
371             data.put(ClaimStatistic.Counter.LICENSE_CATEGORIES, "2");
372             data.put(ClaimStatistic.Counter.LICENSE_NAMES, "2");
373             data.put(ClaimStatistic.Counter.NOTICES, "1");
374             data.put(ClaimStatistic.Counter.STANDARDS, "5");
375             data.put(ClaimStatistic.Counter.UNAPPROVED, "4");
376             data.put(ClaimStatistic.Counter.UNKNOWN, "4");
377 
378             org.w3c.dom.Document document = XmlUtils.toDom(Files.newInputStream(ratTxtFile.toPath()));
379 
380             for (ClaimStatistic.Counter counter : ClaimStatistic.Counter.values()) {
381                 String xpath = String.format("/rat-report/statistics/statistic[@name='%s']", counter.displayName());
382                 Map<String, String> map = mapOf("approval",
383                         counter == ClaimStatistic.Counter.UNAPPROVED ? "false" : "true",
384                         "count", data.get(counter),
385                         "description", counter.getDescription());
386                 XmlUtils.assertAttributes(document, xPath, xpath, map);
387             }
388 
389             // license categories
390             XmlUtils.assertAttributes(document, xPath, "/rat-report/statistics/licenseCategory[@name='?????']",
391                     mapOf("count", "4"));
392 
393             XmlUtils.assertAttributes(document, xPath, "/rat-report/statistics/licenseCategory[@name='AL   ']",
394                     mapOf("count", "1"));
395 
396             // license names
397             XmlUtils.assertAttributes(document, xPath, "/rat-report/statistics/licenseName[@name='Apache License 2.0']",
398                     mapOf("count", "1"));
399 
400             XmlUtils.assertAttributes(document, xPath, "/rat-report/statistics/licenseName[@name='Unknown license']",
401                     mapOf("count", "4"));
402 
403             // Document types
404             XmlUtils.assertAttributes(document, xPath, "/rat-report/statistics/documentType[@name='IGNORED']",
405                     mapOf("count", "6"));
406 
407             XmlUtils.assertAttributes(document, xPath, "/rat-report/statistics/documentType[@name='NOTICE']",
408                     mapOf("count", "1"));
409 
410             XmlUtils.assertAttributes(document, xPath, "/rat-report/statistics/documentType[@name='STANDARD']",
411                     mapOf("count", "5"));
412 
413             List<String> ignoredFiles = new ArrayList<>(Arrays.asList(
414                     "/dir1/dir1.txt",
415                     "/dir1/.gitignore",
416                     "/dir2/dir2.md",
417                     "/dir3/dir3.log",
418                     "/.gitignore",
419                     "/root.md"));
420 
421             NodeList nodeList = XmlUtils.getNodeList(document, xPath, "/rat-report/resource[@type='IGNORED']");
422             for (int i = 0; i < nodeList.getLength(); i++) {
423                 NamedNodeMap attr = nodeList.item(i).getAttributes();
424                 String s = attr.getNamedItem("name").getNodeValue();
425                 assertThat(ignoredFiles).contains(s);
426                 ignoredFiles.remove(s);
427             }
428             assertThat(ignoredFiles).isEmpty();
429         }
430     }
431 
432     /**
433      * Tests verifying gitignore parsing under a special edge case condition
434      * The problem occurs when '/foo.md' is to be ignored and a file with that name exists
435      * in a directory which is the project base directory twice concatenated.
436      * So for this test we must create such a file which is specific for the current
437      * working directory.
438      */
439     @Test
440     void rat362() throws Exception {
441         final RatCheckMojo mojo = newRatMojo("RAT-362");
442         final File ratTxtFile = mojo.getRatTxtFile();
443         try {
444             mojo.execute();
445             fail("Expected RatCheckException");
446         } catch (RatCheckException e) {
447             final String msg = e.getMessage();
448             assertThat(msg).contains(ratTxtFile.getName());
449             assertThat(msg).contains("UNAPPROVED exceeded minimum");
450 
451             org.w3c.dom.Document document = XmlUtils.toDom(Files.newInputStream(ratTxtFile.toPath()));
452             // Document types
453             XmlUtils.assertAttributes(document, xPath, "/rat-report/statistics/documentType[@name='IGNORED']",
454                     "count", "3");
455 
456             XmlUtils.assertAttributes(document, xPath, "/rat-report/resource[@name='/bar.md']",
457                     "type", "STANDARD");
458             XmlUtils.assertAttributes(document, xPath, "/rat-report/resource[@name='/foo.md']",
459                     "type", "IGNORED");
460         }
461     }
462 
463     /**
464      * Tests implicit excludes apply to submodules too
465      */
466     @Test
467     void rat107() throws Exception {
468         final RatCheckMojo mojo = newRatMojo("RAT-107");
469         final File ratTxtFile = mojo.getRatTxtFile();
470         final String[] expected = {};
471         final String[] notExpected = {};
472         //setVariableValueToObject(mojo, "excludeSubProjects", Boolean.FALSE);
473         mojo.setInputExcludeParsedScm("MAVEN");
474         mojo.setInputExcludeParsedScm("idea");
475         mojo.setInputExcludeParsedScm("eclipse");
476         mojo.execute();
477 
478         ensureRatReportIsCorrect(ratTxtFile, expected, notExpected);
479     }
480 }