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