View Javadoc

1   package org.apache.maven.surefire.common.junit4;
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 org.apache.maven.surefire.report.ReportEntry;
23  import org.apache.maven.surefire.report.RunListener;
24  import org.apache.maven.surefire.report.SimpleReportEntry;
25  import org.apache.maven.surefire.report.StackTraceWriter;
26  import org.apache.maven.surefire.testset.TestSetFailedException;
27  import org.junit.runner.Description;
28  import org.junit.runner.Result;
29  import org.junit.runner.notification.Failure;
30  
31  import java.util.regex.Matcher;
32  import java.util.regex.Pattern;
33  
34  public class JUnit4RunListener
35      extends org.junit.runner.notification.RunListener
36  {
37      private static final Pattern PARENS = Pattern.compile( "^" + ".+" //any character
38                                                                 + "\\(("
39                                                                 // then an open-paren (start matching a group)
40                                                                 + "[^\\\\(\\\\)]+" //non-parens
41                                                                 + ")\\)" + "$" );
42  
43      protected final RunListener reporter;
44  
45      /**
46       * This flag is set after a failure has occurred so that a <code>testSucceeded</code> event is not fired.
47       * This is necessary because JUnit4 always fires a <code>testRunFinished</code> event-- even if there was a failure.
48       */
49      private final ThreadLocal<Boolean> failureFlag = new InheritableThreadLocal<Boolean>();
50  
51      private final JUnit4Reflector jUnit4Reflector = new JUnit4Reflector();
52  
53      /**
54       * Constructor.
55       *
56       * @param reporter the reporter to log testing events to
57       */
58      public JUnit4RunListener( RunListener reporter )
59      {
60          this.reporter = reporter;
61      }
62  
63      // Testrun methods are not invoked when using the runner
64  
65      /**
66       * Called when a specific test has been skipped (for whatever reason).
67       *
68       * @see org.junit.runner.notification.RunListener#testIgnored(org.junit.runner.Description)
69       */
70      public void testIgnored( Description description )
71          throws Exception
72      {
73          final String reason = jUnit4Reflector.getAnnotatedIgnoreValue( description );
74          final SimpleReportEntry report =
75              SimpleReportEntry.ignored( getClassName( description ), description.getDisplayName(), reason );
76          reporter.testSkipped( report );
77      }
78  
79      /**
80       * Called when a specific test has started.
81       *
82       * @see org.junit.runner.notification.RunListener#testStarted(org.junit.runner.Description)
83       */
84      public void testStarted( Description description )
85          throws Exception
86      {
87          reporter.testStarting( createReportEntry( description ) );
88          failureFlag.remove();
89      }
90  
91      /**
92       * Called when a specific test has failed.
93       *
94       * @see org.junit.runner.notification.RunListener#testFailure(org.junit.runner.notification.Failure)
95       */
96      @SuppressWarnings( { "ThrowableResultOfMethodCallIgnored" } )
97      public void testFailure( Failure failure )
98          throws Exception
99      {
100         String testHeader = failure.getTestHeader();
101         if ( isInsaneJunitNullString( testHeader ) )
102         {
103             testHeader = "Failure when constructing test";
104         }
105         ReportEntry report = SimpleReportEntry.withException( getClassName( failure.getDescription() ), testHeader,
106                                                               createStackTraceWriter( failure ) );
107 
108         if ( failure.getException() instanceof AssertionError )
109         {
110             this.reporter.testFailed( report );
111         }
112         else
113         {
114             this.reporter.testError( report );
115         }
116         failureFlag.set( Boolean.TRUE );
117     }
118 
119     protected StackTraceWriter createStackTraceWriter( Failure failure )
120     {
121         return new JUnit4StackTraceWriter( failure );
122     }
123 
124     @SuppressWarnings( { "UnusedDeclaration" } )
125     public void testAssumptionFailure( Failure failure )
126     {
127         this.reporter.testAssumptionFailure( createReportEntry( failure.getDescription() ) );
128         failureFlag.set( Boolean.TRUE );
129     }
130 
131 
132     /**
133      * Called after a specific test has finished.
134      *
135      * @see org.junit.runner.notification.RunListener#testFinished(org.junit.runner.Description)
136      */
137     public void testFinished( Description description )
138         throws Exception
139     {
140         Boolean failure = failureFlag.get();
141         if ( failure == null )
142         {
143             reporter.testSucceeded( createReportEntry( description ) );
144         }
145     }
146 
147     protected SimpleReportEntry createReportEntry( Description description )
148     {
149         return new SimpleReportEntry( getClassName( description ), description.getDisplayName() );
150     }
151 
152     public String getClassName( Description description )
153     {
154         String name = extractClassName( description );
155         if ( name == null || isInsaneJunitNullString( name ) )
156         {
157             // This can happen upon early failures (class instantiation error etc)
158             Description subDescription = description.getChildren().get( 0 );
159             if ( subDescription != null )
160             {
161                 name = extractClassName( subDescription );
162             }
163             if ( name == null )
164             {
165                 name = "Test Instantiation Error";
166             }
167         }
168         return name;
169     }
170 
171     private boolean isInsaneJunitNullString( String value )
172     {
173         return "null".equals( value );
174     }
175 
176     public static String extractClassName( Description description )
177     {
178         String displayName = description.getDisplayName();
179         Matcher m = PARENS.matcher( displayName );
180         if ( !m.find() )
181         {
182             return displayName;
183         }
184         return m.group( 1 );
185     }
186 
187     public static String extractMethodName( Description description )
188     {
189         String displayName = description.getDisplayName();
190         int i = displayName.indexOf( "(" );
191         if (i >= 0 ) return displayName.substring( 0, i );
192         return displayName;
193     }
194 
195 
196     public static void rethrowAnyTestMechanismFailures( Result run )
197         throws TestSetFailedException
198     {
199         if ( run.getFailureCount() > 0 )
200         {
201             for ( Failure failure : run.getFailures() )
202             {
203                 if ( isFailureInsideJUnitItself( failure ) )
204                 {
205                     final Throwable exception = failure.getException();
206                     throw new TestSetFailedException( exception );
207                 }
208             }
209         }
210     }
211 
212     private static boolean isFailureInsideJUnitItself( Failure failure )
213     {
214         return failure.getDescription().getDisplayName().equals( "Test mechanism" );
215     }
216 }