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