View Javadoc
1   package org.apache.maven.surefire.junitplatform;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import static java.util.Arrays.stream;
23  import static java.util.Collections.emptyMap;
24  import static java.util.Optional.empty;
25  import static java.util.Optional.of;
26  import static java.util.logging.Level.WARNING;
27  import static java.util.stream.Collectors.toList;
28  import static org.apache.maven.surefire.api.booter.ProviderParameterNames.TESTNG_EXCLUDEDGROUPS_PROP;
29  import static org.apache.maven.surefire.api.booter.ProviderParameterNames.TESTNG_GROUPS_PROP;
30  import static org.apache.maven.surefire.api.report.ConsoleOutputCapture.startCapture;
31  import static org.apache.maven.surefire.api.util.TestsToRun.fromClass;
32  import static org.apache.maven.surefire.shared.utils.StringUtils.isBlank;
33  import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
34  import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId;
35  import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request;
36  
37  import java.io.IOException;
38  import java.io.StringReader;
39  import java.io.UncheckedIOException;
40  import java.util.ArrayList;
41  import java.util.HashMap;
42  import java.util.LinkedHashSet;
43  import java.util.List;
44  import java.util.Map;
45  import java.util.Optional;
46  import java.util.Properties;
47  import java.util.logging.Logger;
48  
49  import org.apache.maven.surefire.api.provider.AbstractProvider;
50  import org.apache.maven.surefire.api.provider.ProviderParameters;
51  import org.apache.maven.surefire.api.report.ConsoleOutputReceiver;
52  import org.apache.maven.surefire.api.report.ReporterException;
53  import org.apache.maven.surefire.api.report.ReporterFactory;
54  import org.apache.maven.surefire.api.report.RunListener;
55  import org.apache.maven.surefire.api.suite.RunResult;
56  import org.apache.maven.surefire.api.testset.TestListResolver;
57  import org.apache.maven.surefire.api.testset.TestSetFailedException;
58  import org.apache.maven.surefire.api.util.ScanResult;
59  import org.apache.maven.surefire.api.util.TestsToRun;
60  import org.apache.maven.surefire.shared.utils.StringUtils;
61  import org.junit.platform.engine.DiscoverySelector;
62  import org.junit.platform.engine.Filter;
63  import org.junit.platform.launcher.Launcher;
64  import org.junit.platform.launcher.LauncherDiscoveryRequest;
65  import org.junit.platform.launcher.TagFilter;
66  import org.junit.platform.launcher.TestIdentifier;
67  import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
68  import org.junit.platform.launcher.core.LauncherFactory;
69  
70  /**
71   * JUnit 5 Platform Provider.
72   *
73   * @since 2.22.0
74   */
75  public class JUnitPlatformProvider
76      extends AbstractProvider
77  {
78      static final String CONFIGURATION_PARAMETERS = "configurationParameters";
79  
80      private final ProviderParameters parameters;
81  
82      private final Launcher launcher;
83  
84      private final Filter<?>[] filters;
85  
86      private final Map<String, String> configurationParameters;
87  
88      public JUnitPlatformProvider( ProviderParameters parameters )
89      {
90          this( parameters, LauncherFactory.create() );
91      }
92  
93      JUnitPlatformProvider( ProviderParameters parameters, Launcher launcher )
94      {
95          this.parameters = parameters;
96          this.launcher = launcher;
97          filters = newFilters();
98          configurationParameters = newConfigurationParameters();
99          Logger.getLogger( "org.junit" ).setLevel( WARNING );
100     }
101 
102     @Override
103     public Iterable<Class<?>> getSuites()
104     {
105         return scanClasspath();
106     }
107 
108     @Override
109     public RunResult invoke( Object forkTestSet )
110                     throws TestSetFailedException, ReporterException
111     {
112         ReporterFactory reporterFactory = parameters.getReporterFactory();
113         final RunResult runResult;
114         try
115         {
116             RunListener runListener = reporterFactory.createReporter();
117             startCapture( ( ConsoleOutputReceiver ) runListener );
118             if ( forkTestSet instanceof TestsToRun )
119             {
120                 invokeAllTests( (TestsToRun) forkTestSet, runListener );
121             }
122             else if ( forkTestSet instanceof Class )
123             {
124                 invokeAllTests( fromClass( ( Class<?> ) forkTestSet ), runListener );
125             }
126             else if ( forkTestSet == null )
127             {
128                 invokeAllTests( scanClasspath(), runListener );
129             }
130             else
131             {
132                 throw new IllegalArgumentException(
133                         "Unexpected value of forkTestSet: " + forkTestSet );
134             }
135         }
136         finally
137         {
138             runResult = reporterFactory.close();
139         }
140         return runResult;
141     }
142 
143     private TestsToRun scanClasspath()
144     {
145         TestPlanScannerFilter filter = new TestPlanScannerFilter( launcher, filters );
146         ScanResult scanResult = parameters.getScanResult();
147         TestsToRun scannedClasses = scanResult.applyFilter( filter, parameters.getTestClassLoader() );
148         return parameters.getRunOrderCalculator().orderTestClasses( scannedClasses );
149     }
150 
151     private void invokeAllTests( TestsToRun testsToRun, RunListener runListener )
152     {
153         RunListenerAdapter adapter = new RunListenerAdapter( runListener );
154         execute( testsToRun, adapter );
155         // Rerun failing tests if requested
156         int count = parameters.getTestRequest().getRerunFailingTestsCount();
157         if ( count > 0 && adapter.hasFailingTests() )
158         {
159             for ( int i = 0; i < count; i++ )
160             {
161                 // Replace the "discoveryRequest" so that it only specifies the failing tests
162                 LauncherDiscoveryRequest discoveryRequest = buildLauncherDiscoveryRequestForRerunFailures( adapter );
163                 // Reset adapter's recorded failures and invoke the failed tests again
164                 adapter.reset();
165                 launcher.execute( discoveryRequest, adapter );
166                 // If no tests fail in the rerun, we're done
167                 if ( !adapter.hasFailingTests() )
168                 {
169                     break;
170                 }
171             }
172         }
173     }
174 
175     private void execute( TestsToRun testsToRun, RunListenerAdapter adapter )
176     {
177         if ( testsToRun.allowEagerReading() )
178         {
179             List<DiscoverySelector> selectors = new ArrayList<>();
180             testsToRun.iterator()
181                 .forEachRemaining( c -> selectors.add( selectClass( c.getName() )  ) );
182 
183             LauncherDiscoveryRequestBuilder builder = request()
184                 .filters( filters )
185                 .configurationParameters( configurationParameters )
186                 .selectors( selectors );
187 
188             launcher.execute( builder.build(), adapter );
189         }
190         else
191         {
192             testsToRun.iterator()
193                 .forEachRemaining( c ->
194                 {
195                     LauncherDiscoveryRequestBuilder builder = request()
196                         .filters( filters )
197                         .configurationParameters( configurationParameters )
198                         .selectors( selectClass( c.getName() ) );
199                     launcher.execute( builder.build(), adapter );
200                 } );
201         }
202     }
203 
204     private LauncherDiscoveryRequest buildLauncherDiscoveryRequestForRerunFailures( RunListenerAdapter adapter )
205     {
206         LauncherDiscoveryRequestBuilder builder = request().filters( filters ).configurationParameters(
207                 configurationParameters );
208         // Iterate over recorded failures
209         for ( TestIdentifier identifier : new LinkedHashSet<>( adapter.getFailures().keySet() ) )
210         {
211             builder.selectors( selectUniqueId( identifier.getUniqueId() ) );
212         }
213         return builder.build();
214     }
215 
216     private Filter<?>[] newFilters()
217     {
218         List<Filter<?>> filters = new ArrayList<>();
219 
220         getPropertiesList( TESTNG_GROUPS_PROP )
221                 .map( TagFilter::includeTags )
222                 .ifPresent( filters::add );
223 
224         getPropertiesList( TESTNG_EXCLUDEDGROUPS_PROP )
225                 .map( TagFilter::excludeTags )
226                 .ifPresent( filters::add );
227 
228         TestListResolver testListResolver = parameters.getTestRequest().getTestListResolver();
229         if ( !testListResolver.isEmpty() )
230         {
231             filters.add( new TestMethodFilter( testListResolver ) );
232         }
233 
234         return filters.toArray( new Filter<?>[ filters.size() ] );
235     }
236 
237     Filter<?>[] getFilters()
238     {
239         return filters;
240     }
241 
242     private Map<String, String> newConfigurationParameters()
243     {
244         String content = parameters.getProviderProperties().get( CONFIGURATION_PARAMETERS );
245         if ( content == null )
246         {
247             return emptyMap();
248         }
249         try ( StringReader reader = new StringReader( content ) )
250         {
251             Map<String, String> result = new HashMap<>();
252             Properties props = new Properties();
253             props.load( reader );
254             props.stringPropertyNames()
255                     .forEach( key -> result.put( key, props.getProperty( key ) ) );
256             return result;
257         }
258         catch ( IOException e )
259         {
260             throw new UncheckedIOException( "Error reading " + CONFIGURATION_PARAMETERS, e );
261         }
262     }
263 
264     Map<String, String> getConfigurationParameters()
265     {
266         return configurationParameters;
267     }
268 
269     private Optional<List<String>> getPropertiesList( String key )
270     {
271         String property = parameters.getProviderProperties().get( key );
272         return isBlank( property ) ? empty()
273                         : of( stream( property.split( "[,]+" ) )
274                                               .filter( StringUtils::isNotBlank )
275                                               .map( String::trim )
276                                               .collect( toList() ) );
277     }
278 }