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.maven.surefire.junitplatform;
20  
21  import java.io.IOException;
22  import java.io.StringReader;
23  import java.io.UncheckedIOException;
24  import java.util.ArrayList;
25  import java.util.HashMap;
26  import java.util.LinkedHashSet;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Optional;
30  import java.util.Properties;
31  import java.util.logging.Logger;
32  
33  import org.apache.maven.surefire.api.provider.AbstractProvider;
34  import org.apache.maven.surefire.api.provider.ProviderParameters;
35  import org.apache.maven.surefire.api.report.ReporterException;
36  import org.apache.maven.surefire.api.report.ReporterFactory;
37  import org.apache.maven.surefire.api.suite.RunResult;
38  import org.apache.maven.surefire.api.testset.TestSetFailedException;
39  import org.apache.maven.surefire.api.util.ScanResult;
40  import org.apache.maven.surefire.api.util.SurefireReflectionException;
41  import org.apache.maven.surefire.api.util.TestsToRun;
42  import org.apache.maven.surefire.shared.utils.StringUtils;
43  import org.junit.platform.engine.DiscoverySelector;
44  import org.junit.platform.engine.Filter;
45  import org.junit.platform.launcher.EngineFilter;
46  import org.junit.platform.launcher.Launcher;
47  import org.junit.platform.launcher.LauncherDiscoveryRequest;
48  import org.junit.platform.launcher.TagFilter;
49  import org.junit.platform.launcher.TestExecutionListener;
50  import org.junit.platform.launcher.TestIdentifier;
51  import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
52  
53  import static java.util.Arrays.stream;
54  import static java.util.Collections.emptyMap;
55  import static java.util.Optional.empty;
56  import static java.util.Optional.of;
57  import static java.util.logging.Level.WARNING;
58  import static java.util.stream.Collectors.toList;
59  import static org.apache.maven.surefire.api.booter.ProviderParameterNames.EXCLUDE_JUNIT5_ENGINES_PROP;
60  import static org.apache.maven.surefire.api.booter.ProviderParameterNames.INCLUDE_JUNIT5_ENGINES_PROP;
61  import static org.apache.maven.surefire.api.booter.ProviderParameterNames.TESTNG_EXCLUDEDGROUPS_PROP;
62  import static org.apache.maven.surefire.api.booter.ProviderParameterNames.TESTNG_GROUPS_PROP;
63  import static org.apache.maven.surefire.api.report.ConsoleOutputCapture.startCapture;
64  import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
65  import static org.apache.maven.surefire.api.report.RunMode.RERUN_TEST_AFTER_FAILURE;
66  import static org.apache.maven.surefire.api.testset.TestListResolver.optionallyWildcardFilter;
67  import static org.apache.maven.surefire.api.util.TestsToRun.fromClass;
68  import static org.apache.maven.surefire.shared.utils.StringUtils.isBlank;
69  import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
70  import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId;
71  import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request;
72  
73  /**
74   * JUnit 5 Platform Provider.
75   *
76   * @since 2.22.0
77   */
78  public class JUnitPlatformProvider extends AbstractProvider {
79      static final String CONFIGURATION_PARAMETERS = "configurationParameters";
80  
81      private final ProviderParameters parameters;
82  
83      private final Launcher launcher;
84  
85      private final Filter<?>[] filters;
86  
87      private final Map<String, String> configurationParameters;
88  
89      public JUnitPlatformProvider(ProviderParameters parameters) {
90          this(parameters, new LazyLauncher());
91      }
92  
93      JUnitPlatformProvider(ProviderParameters parameters, Launcher launcher) {
94          this.parameters = parameters;
95          this.launcher = launcher;
96          filters = newFilters();
97          configurationParameters = newConfigurationParameters();
98      }
99  
100     @Override
101     public Iterable<Class<?>> getSuites() {
102         try {
103             return scanClasspath();
104         } finally {
105             closeLauncher();
106         }
107     }
108 
109     @Override
110     public RunResult invoke(Object forkTestSet) throws TestSetFailedException, ReporterException {
111         ReporterFactory reporterFactory = parameters.getReporterFactory();
112         final RunResult runResult;
113         try {
114             RunListenerAdapter adapter = new RunListenerAdapter(reporterFactory.createTestReportListener());
115             adapter.setRunMode(NORMAL_RUN);
116 
117             startCapture(adapter);
118             setupJunitLogger();
119             if (forkTestSet instanceof TestsToRun) {
120                 invokeAllTests((TestsToRun) forkTestSet, adapter);
121             } else if (forkTestSet instanceof Class) {
122                 invokeAllTests(fromClass((Class<?>) forkTestSet), adapter);
123             } else if (forkTestSet == null) {
124                 invokeAllTests(scanClasspath(), adapter);
125             } else {
126                 throw new IllegalArgumentException("Unexpected value of forkTestSet: " + forkTestSet);
127             }
128         } finally {
129             runResult = reporterFactory.close();
130         }
131         return runResult;
132     }
133 
134     private static void setupJunitLogger() {
135         Logger logger = Logger.getLogger("org.junit");
136         if (logger.getLevel() == null) {
137             logger.setLevel(WARNING);
138         }
139     }
140 
141     private TestsToRun scanClasspath() {
142         TestPlanScannerFilter filter = new TestPlanScannerFilter(launcher, filters);
143         ScanResult scanResult = parameters.getScanResult();
144         TestsToRun scannedClasses = scanResult.applyFilter(filter, parameters.getTestClassLoader());
145         return parameters.getRunOrderCalculator().orderTestClasses(scannedClasses);
146     }
147 
148     private void invokeAllTests(TestsToRun testsToRun, RunListenerAdapter adapter) {
149         try {
150             execute(testsToRun, adapter);
151         } finally {
152             closeLauncher();
153         }
154         // Rerun failing tests if requested
155         int count = parameters.getTestRequest().getRerunFailingTestsCount();
156         if (count > 0 && adapter.hasFailingTests()) {
157             adapter.setRunMode(RERUN_TEST_AFTER_FAILURE);
158             for (int i = 0; i < count; i++) {
159                 try {
160                     // Replace the "discoveryRequest" so that it only specifies the failing tests
161                     LauncherDiscoveryRequest discoveryRequest = buildLauncherDiscoveryRequestForRerunFailures(adapter);
162                     // Reset adapter's recorded failures and invoke the failed tests again
163                     adapter.reset();
164                     launcher.execute(discoveryRequest, adapter);
165                     // If no tests fail in the rerun, we're done
166                     if (!adapter.hasFailingTests()) {
167                         break;
168                     }
169                 } finally {
170                     closeLauncher();
171                 }
172             }
173         }
174     }
175 
176     private void execute(TestsToRun testsToRun, RunListenerAdapter adapter) {
177         // parameters.getProviderProperties().get(CONFIGURATION_PARAMETERS)
178         // add this LegacyXmlReportGeneratingListener ?
179         //            adapter,
180         //            new LegacyXmlReportGeneratingListener(
181         //                    new File("target", "junit-platform").toPath(), new PrintWriter(System.out))
182         //        };
183 
184         TestExecutionListener[] testExecutionListeners = new TestExecutionListener[] {adapter};
185 
186         if (testsToRun.allowEagerReading()) {
187             List<DiscoverySelector> selectors = new ArrayList<>();
188             testsToRun.iterator().forEachRemaining(c -> selectors.add(selectClass(c.getName())));
189 
190             LauncherDiscoveryRequestBuilder builder = request()
191                     .filters(filters)
192                     .configurationParameters(configurationParameters)
193                     .selectors(selectors);
194             launcher.execute(builder.build(), testExecutionListeners);
195         } else {
196             testsToRun.iterator().forEachRemaining(c -> {
197                 LauncherDiscoveryRequestBuilder builder = request()
198                         .filters(filters)
199                         .configurationParameters(configurationParameters)
200                         .selectors(selectClass(c.getName()));
201                 launcher.execute(builder.build(), testExecutionListeners);
202             });
203         }
204     }
205 
206     private void closeLauncher() {
207         if (launcher instanceof AutoCloseable) {
208             try {
209                 ((AutoCloseable) launcher).close();
210             } catch (Exception e) {
211                 throw new SurefireReflectionException(e);
212             }
213         }
214     }
215 
216     private LauncherDiscoveryRequest buildLauncherDiscoveryRequestForRerunFailures(RunListenerAdapter adapter) {
217         LauncherDiscoveryRequestBuilder builder =
218                 request().filters(filters).configurationParameters(configurationParameters);
219         // Iterate over recorded failures
220         for (TestIdentifier identifier :
221                 new LinkedHashSet<>(adapter.getFailures().keySet())) {
222             builder.selectors(selectUniqueId(identifier.getUniqueId()));
223         }
224         return builder.build();
225     }
226 
227     private Filter<?>[] newFilters() {
228         List<Filter<?>> filters = new ArrayList<>();
229 
230         getPropertiesList(TESTNG_GROUPS_PROP).map(TagFilter::includeTags).ifPresent(filters::add);
231 
232         getPropertiesList(TESTNG_EXCLUDEDGROUPS_PROP)
233                 .map(TagFilter::excludeTags)
234                 .ifPresent(filters::add);
235 
236         of(optionallyWildcardFilter(parameters.getTestRequest().getTestListResolver()))
237                 .filter(f -> !f.isEmpty())
238                 .filter(f -> !f.isWildcard())
239                 .map(TestMethodFilter::new)
240                 .ifPresent(filters::add);
241 
242         getPropertiesList(INCLUDE_JUNIT5_ENGINES_PROP)
243                 .map(EngineFilter::includeEngines)
244                 .ifPresent(filters::add);
245 
246         getPropertiesList(EXCLUDE_JUNIT5_ENGINES_PROP)
247                 .map(EngineFilter::excludeEngines)
248                 .ifPresent(filters::add);
249 
250         return filters.toArray(new Filter<?>[0]);
251     }
252 
253     Filter<?>[] getFilters() {
254         return filters;
255     }
256 
257     private Map<String, String> newConfigurationParameters() {
258         String content = parameters.getProviderProperties().get(CONFIGURATION_PARAMETERS);
259         if (content == null) {
260             return emptyMap();
261         }
262         try (StringReader reader = new StringReader(content)) {
263             Map<String, String> result = new HashMap<>();
264             Properties props = new Properties();
265             props.load(reader);
266             props.stringPropertyNames().forEach(key -> result.put(key, props.getProperty(key)));
267             return result;
268         } catch (IOException e) {
269             throw new UncheckedIOException("Error reading " + CONFIGURATION_PARAMETERS, e);
270         }
271     }
272 
273     Map<String, String> getConfigurationParameters() {
274         return configurationParameters;
275     }
276 
277     private Optional<List<String>> getPropertiesList(String key) {
278         String property = parameters.getProviderProperties().get(key);
279         return isBlank(property)
280                 ? empty()
281                 : of(stream(property.split("[,]+"))
282                         .filter(StringUtils::isNotBlank)
283                         .map(String::trim)
284                         .collect(toList()));
285     }
286 }