View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.rat;
20  
21  import groovy.lang.GroovyShell;
22  import java.io.File;
23  import java.io.FileReader;
24  import java.io.IOException;
25  import java.io.PrintStream;
26  import java.net.URISyntaxException;
27  import java.net.URL;
28  import java.nio.file.Paths;
29  import java.util.ArrayList;
30  import java.util.List;
31  import java.util.stream.Stream;
32  import org.apache.commons.cli.CommandLine;
33  import org.apache.commons.cli.DefaultParser;
34  import org.apache.commons.io.IOCase;
35  import org.apache.commons.io.IOUtils;
36  import org.apache.commons.io.filefilter.AbstractFileFilter;
37  import org.apache.commons.io.filefilter.DirectoryFileFilter;
38  import org.apache.commons.io.filefilter.NameFileFilter;
39  import org.apache.commons.io.filefilter.OrFileFilter;
40  import org.apache.rat.api.Document;
41  import org.apache.rat.api.RatException;
42  import org.apache.rat.commandline.Arg;
43  import org.apache.rat.document.DocumentName;
44  import org.apache.rat.document.DocumentNameMatcher;
45  import org.apache.rat.document.FileDocument;
46  import org.apache.rat.document.RatDocumentAnalysisException;
47  import org.apache.rat.report.RatReport;
48  import org.apache.rat.utils.DefaultLog;
49  import org.apache.rat.utils.Log;
50  import org.apache.rat.walker.DirectoryWalker;
51  import org.codehaus.groovy.control.CompilerConfiguration;
52  import org.junit.jupiter.params.ParameterizedTest;
53  import org.junit.jupiter.params.provider.Arguments;
54  import org.junit.jupiter.params.provider.MethodSource;
55  
56  import static org.junit.jupiter.api.Assertions.assertThrows;
57  import static org.junit.jupiter.api.Assertions.fail;
58  
59  /**
60   * Integration tests for the {@link Report} class.
61   * Integration tests must have a specific structure.
62   * <ul>
63   *     <li>They are in directories under {@code src/it/resources/ReportText/X} where {@code X} is the name of the test. Usually
64   *     a reference to a reported bug.</li>
65   *     <li>Within the directory is a file named {@code commandLine.txt}. Each line in the file is a single command line
66   *     token. For example "--output-style XML" would be 2 lines in the file "--output-style" and "XML"</li>
67   *     <li>Within the directory there is a subdirectory named {@code src}. This is the root of the file system for RAT to scan.</li>
68   *     <li>There may be a {@code notes.md} describing the test</li>
69   *     <li>There is a {@code verify.groovy} script. When executed:
70   *     <ul>
71   *         <li>The first parameter will be then name of the file that captured the output.</li>
72   *         <li>The second parameter will be the name of the file that captured the log.</li>
73   *         <li>Any assert that fails within the Groovy script will fail the test.</li>
74   *         <li>Any value returned from the script execution will fail the test and the returned value will be
75   *         used as the failure message.</li>
76   *      </ul>
77   *      </li>
78   *      <li>If an exception is expected when Report is run with the command line a file named {@code expected-message.txt}
79   *      must be present in the directory. It must contain text that is expected to be found within the message
80   *      associated with the exception.</li>
81   * </ul>
82   */
83  public class ReportTest {
84  
85      private String[] asArgs(final List<String> argsList) {
86          return argsList.toArray(new String[0]);
87      }
88  
89      @ParameterizedTest(name = "{index} {0}")
90      @MethodSource("args")
91      public void integrationTest(String testName, Document commandLineDoc) throws Exception {
92          DefaultLog.getInstance().log(Log.Level.INFO, "Running test for " + testName);
93          File baseDir = new File(commandLineDoc.getName().getName()).getParentFile();
94  
95          // get the arguments
96          List<String> argsList = IOUtils.readLines(commandLineDoc.reader());
97  
98          CommandLine commandLine = DefaultParser.builder().setDeprecatedHandler(DeprecationReporter.getLogReporter())
99                  .setAllowPartialMatching(true).build().parse(Arg.getOptions(), asArgs(argsList));
100 
101         File outputFile = new File(baseDir,"output.txt");
102         if (!commandLine.hasOption(Arg.OUTPUT_FILE.option())) {
103             argsList.add(0, "--" + Arg.OUTPUT_FILE.option().getLongOpt());
104             argsList.add(1, outputFile.getAbsolutePath());
105         } else {
106             outputFile = new File(baseDir, commandLine.getOptionValue(Arg.OUTPUT_FILE.option()));
107         }
108 
109         File logFile = new File(baseDir,"log.txt");
110         FileLog fileLog = new FileLog(logFile);
111         Log oldLog = null;
112         try {
113             oldLog = DefaultLog.setInstance(fileLog);
114 
115             File src = new File(baseDir, "src");
116             if (src.isDirectory()) {
117                 argsList.add(src.getAbsolutePath());
118             }
119 
120             File expectedMsg = new File(baseDir, "expected-message.txt");
121             if (expectedMsg.exists()) {
122                 String msg = IOUtils.readLines(new FileReader(expectedMsg)).get(0).trim();
123                 assertThrows(RatDocumentAnalysisException.class, () -> Report.main(asArgs(argsList)),
124                         msg);
125             } else {
126                 Report.main(asArgs(argsList));
127             }
128         } finally {
129             DefaultLog.setInstance(oldLog);
130             fileLog.close();
131         }
132 
133         File groovyScript = new File(baseDir, "verify.groovy");
134         if (groovyScript.exists()) {
135             // call groovy expressions from Java code
136             CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
137 
138             GroovyShell shell = new GroovyShell(compilerConfiguration);
139             for (String classPath : System.getProperty("java.class.path").split(File.pathSeparator)) {
140                 shell.getClassLoader().addClasspath(classPath);
141             }
142             try {
143                 Object value = shell.run(groovyScript, new String[]{outputFile.getAbsolutePath(), logFile.getAbsolutePath()});
144                 if (value != null) {
145                     fail(String.format("%s", value));
146                 }
147             } catch (AssertionError e) {
148                 throw new AssertionError(String.format("%s: %s", testName, e.getMessage()), e);
149             }
150         }
151     }
152 
153     static Stream<Arguments> args() throws RatException {
154         List<Arguments> results = new ArrayList<>();
155         URL url = ReportTest.class.getResource("/ReportTest");
156 
157         String urlAsFile;
158         try {
159             urlAsFile = Paths.get(url.toURI()).toString();
160         } catch (URISyntaxException e) {
161             throw new RatException("Unable to find root directory for " + url, e);
162         }
163 
164         File baseDir = new File(urlAsFile);
165         DocumentName docName = DocumentName.builder(baseDir).build();
166         AbstractFileFilter fileFilter = new NameFileFilter("commandLine.txt", docName.isCaseSensitive() ? IOCase.SENSITIVE : IOCase.INSENSITIVE);
167         fileFilter = new OrFileFilter(fileFilter, DirectoryFileFilter.INSTANCE);
168 
169         Document document = new FileDocument(docName, baseDir, new DocumentNameMatcher(fileFilter));
170         DirectoryWalker walker = new DirectoryWalker(document);
171         RatReport report = new RatReport() {
172             @Override
173             public void report(Document document)  {
174             if (!document.isIgnored()) {
175                 String[] tokens = DocumentName.FSInfo.getDefault().tokenize(document.getName().localized());
176                 results.add(Arguments.of(tokens[1], document));
177             }
178             }
179         };
180         walker.run(report);
181         return results.stream();
182     }
183 
184     /**
185      * Log that captures output for later review.
186      */
187     public static class FileLog implements Log {
188 
189         private final PrintStream logFile;
190 
191         /**
192          * The level at which we will write messages
193          */
194         private Level level;
195 
196         FileLog(File logFile) throws IOException {
197             this.logFile = new PrintStream(logFile);
198             level = Level.INFO;
199         }
200 
201         /**
202          * Sets the level.Log messages below the specified level will
203          * not be written to the log.
204          *
205          * @param level the level to use when writing messages.
206          */
207         public void setLevel(final Level level) {
208             this.level = level;
209         }
210 
211         @Override
212         public Level getLevel() {
213             return level;
214         }
215 
216         @Override
217         public void log(Level level, String msg) {
218             if (isEnabled(level)) {
219                 logFile.printf("%s: %s%n", level, msg);
220             }
221         }
222 
223         public void close() {
224             logFile.close();
225         }
226     }
227 }