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