View Javadoc
1   package org.apache.maven.plugin.surefire.report;
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.plugin.surefire.booterclient.output.InPluginProcessDumpSingleton;
23  import org.apache.maven.surefire.api.report.ReportEntry;
24  import org.apache.maven.surefire.api.report.TestSetReportEntry;
25  
26  import java.io.BufferedOutputStream;
27  import java.io.File;
28  import java.io.FileOutputStream;
29  import java.io.FilterOutputStream;
30  import java.io.IOException;
31  import java.nio.charset.Charset;
32  import java.util.concurrent.atomic.AtomicStampedReference;
33  import java.util.concurrent.locks.ReentrantLock;
34  
35  import static org.apache.maven.plugin.surefire.report.FileReporter.getReportFile;
36  import static org.apache.maven.surefire.api.util.internal.StringUtils.NL;
37  
38  /**
39   * Surefire output consumer proxy that writes test output to a {@link java.io.File} for each test suite.
40   *
41   * @author Kristian Rosenvold
42   * @author Carlos Sanchez
43   */
44  public class ConsoleOutputFileReporter
45      implements TestcycleConsoleOutputReceiver
46  {
47      private static final int STREAM_BUFFER_SIZE = 64 * 1024;
48      private static final int OPEN = 0;
49      private static final int CLOSED_TO_REOPEN = 1;
50      private static final int CLOSED = 2;
51  
52      private final File reportsDirectory;
53      private final String reportNameSuffix;
54      private final boolean usePhrasedFileName;
55      private final Integer forkNumber;
56      private final String encoding;
57  
58      private final AtomicStampedReference<FilterOutputStream> fileOutputStream =
59              new AtomicStampedReference<>( null, OPEN );
60  
61      private final ReentrantLock lock = new ReentrantLock();
62  
63      private volatile String reportEntryName;
64  
65      public ConsoleOutputFileReporter( File reportsDirectory, String reportNameSuffix, boolean usePhrasedFileName,
66                                        Integer forkNumber, String encoding )
67      {
68          this.reportsDirectory = reportsDirectory;
69          this.reportNameSuffix = reportNameSuffix;
70          this.usePhrasedFileName = usePhrasedFileName;
71          this.forkNumber = forkNumber;
72          this.encoding = encoding;
73      }
74  
75      @Override
76      public void testSetStarting( TestSetReportEntry reportEntry )
77      {
78          lock.lock();
79          try
80          {
81              closeNullReportFile( reportEntry );
82          }
83          finally
84          {
85              lock.unlock();
86          }
87      }
88  
89      @Override
90      public void testSetCompleted( TestSetReportEntry report )
91      {
92      }
93  
94      @Override
95      public void close()
96      {
97          // The close() method is called in main Thread T2.
98          lock.lock();
99          try
100         {
101             closeReportFile();
102         }
103         finally
104         {
105             lock.unlock();
106         }
107     }
108 
109     @Override
110     public void writeTestOutput( String output, boolean newLine, boolean stdout )
111     {
112         lock.lock();
113         try
114         {
115             // This method is called in single thread T1 per fork JVM (see ThreadedStreamConsumer).
116             // The close() method is called in main Thread T2.
117             int[] status = new int[1];
118             FilterOutputStream os = fileOutputStream.get( status );
119             if ( status[0] != CLOSED )
120             {
121                 if ( os == null )
122                 {
123                     if ( !reportsDirectory.exists() )
124                     {
125                         //noinspection ResultOfMethodCallIgnored
126                         reportsDirectory.mkdirs();
127                     }
128                     File file = getReportFile( reportsDirectory, reportEntryName, reportNameSuffix, "-output.txt" );
129                     os = new BufferedOutputStream( new FileOutputStream( file ), STREAM_BUFFER_SIZE );
130                     fileOutputStream.set( os, OPEN );
131                 }
132 
133                 if ( output == null )
134                 {
135                     output = "null";
136                 }
137                 Charset charset = Charset.forName( encoding );
138                 os.write( output.getBytes( charset ) );
139                 if ( newLine )
140                 {
141                     os.write( NL.getBytes( charset ) );
142                 }
143             }
144         }
145         catch ( IOException e )
146         {
147             dumpException( e );
148             // todo use UncheckedIOException in Java 8
149             throw new RuntimeException( e );
150         }
151         finally
152         {
153             lock.unlock();
154         }
155     }
156 
157     @SuppressWarnings( "checkstyle:emptyblock" )
158     private void closeNullReportFile( ReportEntry reportEntry )
159     {
160         try
161         {
162             // close null-output.txt report file
163             close( true );
164         }
165         catch ( IOException e )
166         {
167             dumpException( e );
168         }
169         finally
170         {
171             // prepare <class>-output.txt report file
172             reportEntryName = usePhrasedFileName ? reportEntry.getSourceText() : reportEntry.getSourceName();
173         }
174     }
175 
176     @SuppressWarnings( "checkstyle:emptyblock" )
177     private void closeReportFile()
178     {
179         try
180         {
181             close( false );
182         }
183         catch ( IOException e )
184         {
185             dumpException( e );
186         }
187     }
188 
189     private void close( boolean closeReattempt )
190             throws IOException
191     {
192         int[] status = new int[1];
193         FilterOutputStream os = fileOutputStream.get( status );
194         if ( status[0] != CLOSED )
195         {
196             fileOutputStream.set( null, closeReattempt ? CLOSED_TO_REOPEN : CLOSED );
197             if ( os != null && status[0] == OPEN )
198             {
199                 os.close();
200             }
201         }
202     }
203 
204     private void dumpException( IOException e )
205     {
206         if ( forkNumber == null )
207         {
208             InPluginProcessDumpSingleton.getSingleton()
209                     .dumpException( e, e.getLocalizedMessage(), reportsDirectory );
210         }
211         else
212         {
213             InPluginProcessDumpSingleton.getSingleton()
214                     .dumpException( e, e.getLocalizedMessage(), reportsDirectory, forkNumber );
215         }
216     }
217 }