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.InvocationHandler;
22  import java.lang.reflect.Method;
23  import java.util.HashSet;
24  import java.util.Set;
25  
26  import org.apache.maven.surefire.api.report.LegacyPojoStackTraceWriter;
27  import org.apache.maven.surefire.api.report.ReportEntry;
28  import org.apache.maven.surefire.api.report.RunListener;
29  import org.apache.maven.surefire.api.report.SimpleReportEntry;
30  import org.apache.maven.surefire.api.report.StackTraceWriter;
31  
32  import static java.util.Objects.requireNonNull;
33  import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
34  import static org.apache.maven.surefire.api.report.SimpleReportEntry.withException;
35  import static org.apache.maven.surefire.api.util.internal.TestClassMethodNameUtils.extractClassName;
36  import static org.apache.maven.surefire.api.util.internal.TestClassMethodNameUtils.extractMethodName;
37  
38  /**
39   * Invocation Handler for TestListener proxies to delegate to our {@link RunListener}
40   *
41   */
42  public class TestListenerInvocationHandler implements InvocationHandler {
43      // The String names of the four methods in interface junit.framework.TestListener
44      private static final String START_TEST = "startTest";
45  
46      private static final String ADD_FAILURE = "addFailure";
47  
48      private static final String ADD_ERROR = "addError";
49  
50      private static final String END_TEST = "endTest";
51  
52      private final Set<FailedTest> failedTestsSet = new HashSet<>();
53  
54      private final JUnit3Reporter reporter;
55  
56      private static final Class<?>[] EMPTY_CLASS_ARRAY = {};
57  
58      private static final Object[] EMPTY_STRING_ARRAY = {};
59  
60      private static class FailedTest {
61          private final Object testThatFailed;
62          private final Thread threadOnWhichTestFailed;
63  
64          FailedTest(Object testThatFailed, Thread threadOnWhichTestFailed) {
65              this.testThatFailed = requireNonNull(testThatFailed, "testThatFailed is null");
66  
67              this.threadOnWhichTestFailed = requireNonNull(threadOnWhichTestFailed, "threadOnWhichTestFailed is null");
68          }
69  
70          @Override
71          public boolean equals(Object obj) {
72              boolean retVal = true;
73  
74              if (obj == null || getClass() != obj.getClass()) {
75                  retVal = false;
76              } else {
77                  FailedTest ft = (FailedTest) obj;
78  
79                  if (ft.testThatFailed != testThatFailed) {
80                      retVal = false;
81                  } else if (!ft.threadOnWhichTestFailed.equals(threadOnWhichTestFailed)) {
82                      retVal = false;
83                  }
84              }
85  
86              return retVal;
87          }
88  
89          @Override
90          public int hashCode() {
91              return threadOnWhichTestFailed.hashCode();
92          }
93      }
94  
95      public TestListenerInvocationHandler(JUnit3Reporter reporter) {
96          this.reporter = requireNonNull(reporter, "reporter is null");
97      }
98  
99      @Override
100     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
101         String methodName = method.getName();
102 
103         switch (methodName) {
104             case START_TEST:
105                 handleStartTest(args);
106                 break;
107             case ADD_ERROR:
108                 handleAddError(args);
109                 break;
110             case ADD_FAILURE:
111                 handleAddFailure(args);
112                 break;
113             case END_TEST:
114                 handleEndTest(args);
115                 break;
116             default:
117                 break;
118         }
119 
120         return null;
121     }
122 
123     // Handler for TestListener.startTest(Test)
124     private void handleStartTest(Object[] args) {
125         ReportEntry report = createStartEndReportEntry(args);
126 
127         reporter.testStarting(report);
128     }
129 
130     // Handler for TestListener.addFailure(Test, Throwable)
131     private void handleAddError(Object[] args) throws ReflectiveOperationException {
132         ReportEntry report = toReportEntryWithException(args);
133 
134         reporter.testError(report);
135 
136         failedTestsSet.add(new FailedTest(args[0], Thread.currentThread()));
137     }
138 
139     private static LegacyPojoStackTraceWriter toStackTraceWriter(Object[] args) throws ReflectiveOperationException {
140         String testName;
141 
142         try {
143             Method m = args[0].getClass().getMethod("getName", EMPTY_CLASS_ARRAY);
144             testName = (String) m.invoke(args[0], EMPTY_STRING_ARRAY);
145         } catch (NoSuchMethodException e) {
146             testName = "UNKNOWN";
147         }
148 
149         return new LegacyPojoStackTraceWriter(args[0].getClass().getName(), testName, (Throwable) args[1]);
150     }
151 
152     private void handleAddFailure(Object[] args) throws ReflectiveOperationException {
153         ReportEntry report = toReportEntryWithException(args);
154 
155         reporter.testFailed(report);
156 
157         failedTestsSet.add(new FailedTest(args[0], Thread.currentThread()));
158     }
159 
160     private void handleEndTest(Object[] args) {
161         boolean testHadFailed = failedTestsSet.remove(new FailedTest(args[0], Thread.currentThread()));
162 
163         if (!testHadFailed) {
164             ReportEntry report = createStartEndReportEntry(args);
165 
166             reporter.testSucceeded(report);
167         }
168     }
169 
170     private ReportEntry toReportEntryWithException(Object[] args) throws ReflectiveOperationException {
171         String description = args[0].toString();
172         String className = extractClassName(description);
173         String methodName = extractMethodName(description);
174         StackTraceWriter stackTraceWriter = toStackTraceWriter(args);
175         long testRunId = reporter.getClassMethodIndexer().indexClassMethod(className, methodName);
176         return withException(NORMAL_RUN, testRunId, className, null, methodName, null, stackTraceWriter);
177     }
178 
179     private SimpleReportEntry createStartEndReportEntry(Object[] args) {
180         String description = args[0].toString();
181         String className = extractClassName(description);
182         String methodName = extractMethodName(description);
183         long testRunId = reporter.getClassMethodIndexer().indexClassMethod(className, methodName);
184         return new SimpleReportEntry(NORMAL_RUN, testRunId, className, null, methodName, null);
185     }
186 }