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.junit;
20  
21  import java.lang.reflect.InvocationTargetException;
22  import java.lang.reflect.Method;
23  import java.lang.reflect.Modifier;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  
27  import org.apache.maven.surefire.api.report.LegacyPojoStackTraceWriter;
28  import org.apache.maven.surefire.api.report.SimpleReportEntry;
29  import org.apache.maven.surefire.api.report.StackTraceWriter;
30  import org.apache.maven.surefire.api.testset.TestSetFailedException;
31  import org.apache.maven.surefire.report.ClassMethodIndexer;
32  
33  import static java.util.Objects.requireNonNull;
34  import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
35  import static org.apache.maven.surefire.api.report.SimpleReportEntry.withException;
36  
37  /**
38   * Executes a JUnit3 test class
39   *
40   */
41  public class PojoTestSetExecutor implements SurefireTestSetExecutor {
42      private static final String TEST_METHOD_PREFIX = "test";
43  
44      private static final String SETUP_METHOD_NAME = "setUp";
45  
46      private static final String TEARDOWN_METHOD_NAME = "tearDown";
47  
48      private static final Object[] EMPTY_OBJECT_ARRAY = {};
49  
50      private final JUnit3Reporter reporter;
51  
52      public PojoTestSetExecutor(JUnit3Reporter reporter) {
53          this.reporter = requireNonNull(reporter, "reportManager is null");
54      }
55  
56      @Override
57      public void execute(Class<?> testClass, ClassLoader loader) throws TestSetFailedException {
58          DiscoveredTestMethods discoveredTestMethods = discoverTestMethods(requireNonNull(testClass));
59  
60          for (Method testMethod : discoveredTestMethods.testMethods) {
61              ClassMethodIndexer indexer = reporter.getClassMethodIndexer();
62              long testRunId = indexer.indexClassMethod(testClass.getName(), testMethod.getName());
63              boolean abort = executeTestMethod(testClass, testMethod, testRunId, discoveredTestMethods);
64              if (abort) {
65                  break;
66              }
67          }
68      }
69  
70      private boolean executeTestMethod(Class<?> testClass, Method method, long testRunId, DiscoveredTestMethods methods)
71              throws TestSetFailedException {
72          final Object testObject;
73  
74          try {
75              testObject = testClass.getDeclaredConstructor().newInstance();
76          } catch (ReflectiveOperationException e) {
77              throw new TestSetFailedException("Unable to instantiate POJO '" + testClass + "'.", e);
78          }
79  
80          final String testClassName = testClass.getName();
81          final String methodName = method.getName();
82          final String userFriendlyMethodName = methodName + "()";
83          final String testName = getTestName(testClassName, userFriendlyMethodName);
84  
85          reporter.testStarting(new SimpleReportEntry(NORMAL_RUN, testRunId, testClassName, null, testName, null));
86  
87          try {
88              setUpFixture(testObject, methods);
89          } catch (Throwable e) {
90              StackTraceWriter stackTraceWriter = new LegacyPojoStackTraceWriter(testClassName, methodName, e);
91              reporter.testFailed(
92                      withException(NORMAL_RUN, testRunId, testClassName, null, testName, null, stackTraceWriter));
93  
94              // A return value of true indicates to this class's executeTestMethods
95              // method that it should abort and not attempt to execute
96              // any other test methods. The other caller of this method,
97              // TestRerunner.rerun, ignores this return value, because it is
98              // only running one test.
99              return true;
100         }
101 
102         // Make sure that tearDownFixture
103         try {
104             method.invoke(testObject, EMPTY_OBJECT_ARRAY);
105             reporter.testSucceeded(new SimpleReportEntry(NORMAL_RUN, testRunId, testClassName, null, testName, null));
106         } catch (InvocationTargetException e) {
107             Throwable t = e.getTargetException();
108             StackTraceWriter stackTraceWriter = new LegacyPojoStackTraceWriter(testClassName, methodName, t);
109             reporter.testFailed(
110                     withException(NORMAL_RUN, testRunId, testClassName, null, testName, null, stackTraceWriter));
111             // Don't return  here, because tearDownFixture should be called even
112             // if the test method throws an exception.
113         } catch (Throwable t) {
114             StackTraceWriter stackTraceWriter = new LegacyPojoStackTraceWriter(testClassName, methodName, t);
115             reporter.testFailed(
116                     withException(NORMAL_RUN, testRunId, testClassName, null, testName, null, stackTraceWriter));
117             // Don't return  here, because tearDownFixture should be called even
118             // if the test method throws an exception.
119         }
120 
121         try {
122             tearDownFixture(testObject, methods);
123         } catch (Throwable t) {
124             StackTraceWriter stackTraceWriter = new LegacyPojoStackTraceWriter(testClassName, methodName, t);
125             // Treat any exception from tearDownFixture as a failure of the test.
126             reporter.testFailed(
127                     withException(NORMAL_RUN, testRunId, testClassName, null, testName, null, stackTraceWriter));
128 
129             // A return value of true indicates to this class's executeTestMethods
130             // method that it should abort and not attempt to execute
131             // any other test methods. The other caller of this method,
132             // TestRerunner.rerun, ignores this return value, because it is
133             // only running one test.
134             return true;
135         }
136 
137         // A return value of false indicates to this class's executeTestMethods
138         // method that it should keep plowing ahead and invoke more test methods.
139         // The other caller of this method,
140         // TestRerunner.rerun, ignores this return value, because it is
141         // only running one test.
142         return false;
143     }
144 
145     private String getTestName(String testClassName, String testMethodName) {
146         return testClassName + "." + requireNonNull(testMethodName, "testMethodName is null");
147     }
148 
149     private void setUpFixture(Object testObject, DiscoveredTestMethods methods) throws Throwable {
150         if (methods.setUpMethod != null) {
151             methods.setUpMethod.invoke(testObject);
152         }
153     }
154 
155     private void tearDownFixture(Object testObject, DiscoveredTestMethods methods) throws Throwable {
156         if (methods.tearDownMethod != null) {
157             methods.tearDownMethod.invoke(testObject);
158         }
159     }
160 
161     private DiscoveredTestMethods discoverTestMethods(Class<?> testClass) {
162         DiscoveredTestMethods methods = new DiscoveredTestMethods();
163         for (Method m : testClass.getMethods()) {
164             if (isNoArgsInstanceMethod(m)) {
165                 if (isValidTestMethod(m)) {
166                     methods.testMethods.add(m);
167                 } else if (SETUP_METHOD_NAME.equals(m.getName())) {
168                     methods.setUpMethod = m;
169                 } else if (TEARDOWN_METHOD_NAME.equals(m.getName())) {
170                     methods.tearDownMethod = m;
171                 }
172             }
173         }
174         return methods;
175     }
176 
177     private static boolean isNoArgsInstanceMethod(Method m) {
178         boolean isInstanceMethod = !Modifier.isStatic(m.getModifiers());
179         boolean returnsVoid = m.getReturnType().equals(void.class);
180         boolean hasNoParams = m.getParameterTypes().length == 0;
181         return isInstanceMethod && returnsVoid && hasNoParams;
182     }
183 
184     private static boolean isValidTestMethod(Method m) {
185         return m.getName().startsWith(TEST_METHOD_PREFIX);
186     }
187 
188     private static class DiscoveredTestMethods {
189         final Collection<Method> testMethods = new ArrayList<>();
190         Method setUpMethod;
191         Method tearDownMethod;
192     }
193 }