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