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