View Javadoc
1   package org.apache.maven.surefire.booter;
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 org.junit.After;
23  import org.junit.Before;
24  import org.junit.Test;
25  import org.junit.internal.runners.model.ReflectiveCallable;
26  import org.junit.internal.runners.statements.ExpectException;
27  import org.junit.internal.runners.statements.Fail;
28  import org.junit.internal.runners.statements.RunAfters;
29  import org.junit.internal.runners.statements.RunBefores;
30  import org.junit.runner.notification.RunNotifier;
31  import org.junit.runners.BlockJUnit4ClassRunner;
32  import org.junit.runners.model.FrameworkMethod;
33  import org.junit.runners.model.InitializationError;
34  import org.junit.runners.model.Statement;
35  import org.junit.runners.model.TestClass;
36  
37  import java.io.File;
38  import java.io.IOException;
39  import java.lang.annotation.Annotation;
40  import java.net.MalformedURLException;
41  import java.net.URL;
42  import java.net.URLClassLoader;
43  import java.util.Collection;
44  import java.util.HashSet;
45  import java.util.List;
46  
47  import static java.io.File.pathSeparator;
48  import static org.apache.commons.io.FileUtils.readFileToString;
49  
50  /**
51   * JUnit runner testing methods in a separate class loader.
52   *
53   * @author Tibor Digana (tibor17)
54   * @since 2.19
55   */
56  public class NewClassLoaderRunner
57      extends BlockJUnit4ClassRunner
58  {
59      private Class<?> cls;
60  
61      public NewClassLoaderRunner( Class<?> clazz )
62          throws InitializationError
63      {
64          super( clazz );
65      }
66  
67      @Override
68      protected void runChild( FrameworkMethod method, RunNotifier notifier )
69      {
70          ClassLoader backup = Thread.currentThread().getContextClassLoader();
71          try
72          {
73              TestClassLoader loader = new TestClassLoader();
74              Thread.currentThread().setContextClassLoader( loader );
75              cls = getFromTestClassLoader( getTestClass().getName(), loader );
76              method = new FrameworkMethod( cls.getMethod( method.getName() ) );
77              super.runChild( method, notifier );
78          }
79          catch ( NoSuchMethodException e )
80          {
81              throw new IllegalStateException( e );
82          }
83          finally
84          {
85              Thread.currentThread().setContextClassLoader( backup );
86          }
87      }
88  
89      @Override
90      protected Statement methodBlock( FrameworkMethod method )
91      {
92          try
93          {
94              Object test = new ReflectiveCallable()
95              {
96                  @Override
97                  protected Object runReflectiveCall()
98                      throws Throwable
99                  {
100                     return createTest();
101                 }
102             }.run();
103 
104             Statement statement = methodInvoker( method, test );
105             statement = possiblyExpectingExceptions( method, test, statement );
106             statement = withBefores( method, test, statement );
107             statement = withAfters( method, test, statement );
108             return statement;
109         }
110         catch ( Throwable e )
111         {
112             return new Fail( e );
113         }
114     }
115 
116     @Override
117     @SuppressWarnings( "unchecked" )
118     protected Statement possiblyExpectingExceptions( FrameworkMethod method, Object test, Statement next )
119     {
120         try
121         {
122             Class<? extends Annotation> t =
123                 (Class<? extends Annotation>) Thread.currentThread().getContextClassLoader().loadClass(
124                     Test.class.getName() );
125             Annotation annotation = method.getAnnotation( t );
126             Class<? extends Throwable> exp =
127                 (Class<? extends Throwable>) t.getMethod( "expected" ).invoke( annotation );
128             boolean isException = exp != null && !Test.None.class.getName().equals( exp.getName() );
129             return isException ? new ExpectException( next, exp ) : next;
130         }
131         catch ( Exception e )
132         {
133             throw new IllegalStateException( e );
134         }
135     }
136 
137     @Override
138     @SuppressWarnings( "unchecked" )
139     protected Statement withBefores( FrameworkMethod method, Object target, Statement statement )
140     {
141         try
142         {
143             Class<? extends Annotation> before =
144                 (Class<? extends Annotation>) Thread.currentThread().getContextClassLoader().loadClass(
145                     Before.class.getName() );
146             List<FrameworkMethod> befores = new TestClass( target.getClass() ).getAnnotatedMethods( before );
147             return befores.isEmpty() ? statement : new RunBefores( statement, befores, target );
148         }
149         catch ( ClassNotFoundException e )
150         {
151             throw new IllegalStateException( e );
152         }
153     }
154 
155     @Override
156     @SuppressWarnings( "unchecked" )
157     protected Statement withAfters( FrameworkMethod method, Object target, Statement statement )
158     {
159         try
160         {
161             Class<? extends Annotation> after =
162                 (Class<? extends Annotation>) Thread.currentThread().getContextClassLoader().loadClass(
163                     After.class.getName() );
164             List<FrameworkMethod> afters = new TestClass( target.getClass() ).getAnnotatedMethods( after );
165             return afters.isEmpty() ? statement : new RunAfters( statement, afters, target );
166         }
167         catch ( ClassNotFoundException e )
168         {
169             throw new IllegalStateException( e );
170         }
171     }
172 
173     @Override
174     protected Object createTest()
175         throws Exception
176     {
177         return cls == null ? super.createTest() : cls.getConstructor().newInstance();
178     }
179 
180     private static Class<?> getFromTestClassLoader( String clazz, TestClassLoader loader )
181     {
182         try
183         {
184             return Class.forName( clazz, true, loader );
185         }
186         catch ( ClassNotFoundException e )
187         {
188             throw new IllegalStateException( e );
189         }
190     }
191 
192     public static class TestClassLoader
193         extends URLClassLoader
194     {
195         public TestClassLoader()
196         {
197             super( toClassPath(), null );
198         }
199 
200         /**
201          * Compliant with Java 9 or prior version of JRE.
202          *
203          * @return classpath
204          */
205         private static URL[] toClassPath()
206         {
207             try
208             {
209                 Collection<URL> cp = toPathList(); // if Maven run
210                 if ( cp.isEmpty() )
211                 {
212                     // if IDE
213                     cp = toPathList( System.getProperty( "java.class.path" ) );
214                 }
215                 return cp.toArray( new URL[cp.size()] );
216             }
217             catch ( IOException e )
218             {
219                 return new URL[0];
220             }
221         }
222 
223         private static Collection<URL> toPathList( String path ) throws MalformedURLException
224         {
225             Collection<URL> classPath = new HashSet<URL>();
226             for ( String file : path.split( pathSeparator ) )
227             {
228                 classPath.add( new File( file ).toURL() );
229             }
230             return classPath;
231         }
232 
233         private static Collection<URL> toPathList()
234         {
235             Collection<URL> classPath = new HashSet<URL>();
236             try
237             {
238                 String[] files = readFileToString( new File( "target/test-classpath/cp.txt" ) ).split( pathSeparator );
239                 for ( String file : files )
240                 {
241                     File f = new File( file );
242                     File dir = f.getParentFile();
243                     classPath.add( ( dir.getName().equals( "target" ) ? new File( dir, "classes" ) : f ).toURL() );
244                 }
245                 classPath.add( new File( "target/classes" ).toURL() );
246                 classPath.add( new File( "target/test-classes" ).toURL() );
247             }
248             catch ( IOException e )
249             {
250                 // turn to java.class.path
251                 classPath.clear();
252             }
253             return classPath;
254         }
255     }
256 }