View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugin.surefire.report;
20  
21  import java.io.BufferedOutputStream;
22  import java.io.File;
23  import java.io.FileOutputStream;
24  import java.io.FilterOutputStream;
25  import java.io.IOException;
26  import java.io.UncheckedIOException;
27  import java.nio.charset.Charset;
28  import java.util.concurrent.atomic.AtomicStampedReference;
29  
30  import org.apache.maven.plugin.surefire.booterclient.output.InPluginProcessDumpSingleton;
31  import org.apache.maven.surefire.api.report.ReportEntry;
32  import org.apache.maven.surefire.api.report.TestOutputReportEntry;
33  import org.apache.maven.surefire.api.report.TestSetReportEntry;
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 implements TestcycleConsoleOutputReceiver {
45      private static final int STREAM_BUFFER_SIZE = 64 * 1024;
46      private static final int OPEN = 0;
47      private static final int CLOSED_TO_REOPEN = 1;
48      private static final int CLOSED = 2;
49  
50      private final File reportsDirectory;
51      private final String reportNameSuffix;
52      private final boolean usePhrasedFileName;
53      private final Integer forkNumber;
54      private final String encoding;
55  
56      private final AtomicStampedReference<FilterOutputStream> fileOutputStream =
57              new AtomicStampedReference<>(null, OPEN);
58  
59      private volatile String reportEntryName;
60  
61      public ConsoleOutputFileReporter(
62              File reportsDirectory,
63              String reportNameSuffix,
64              boolean usePhrasedFileName,
65              Integer forkNumber,
66              String encoding) {
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          closeNullReportFile(reportEntry);
77      }
78  
79      @Override
80      public void testSetCompleted(TestSetReportEntry report) {}
81  
82      @Override
83      public synchronized void close() {
84          // The close() method is called in main Thread T2.
85          closeReportFile();
86      }
87  
88      @Override
89      public synchronized void writeTestOutput(TestOutputReportEntry reportEntry) {
90          try {
91              // This method is called in single thread T1 per fork JVM (see ThreadedStreamConsumer).
92              // The close() method is called in main Thread T2.
93              int[] status = new int[1];
94              FilterOutputStream os = fileOutputStream.get(status);
95              if (status[0] != CLOSED) {
96                  if (os == null) {
97                      if (!reportsDirectory.exists()) {
98                          //noinspection ResultOfMethodCallIgnored
99                          reportsDirectory.mkdirs();
100                     }
101                     File file = getReportFile(reportsDirectory, reportEntryName, reportNameSuffix, "-output.txt");
102                     os = new BufferedOutputStream(new FileOutputStream(file), STREAM_BUFFER_SIZE);
103                     fileOutputStream.set(os, OPEN);
104                 }
105                 String output = reportEntry.getLog();
106                 if (output == null) {
107                     output = "null";
108                 }
109                 Charset charset = Charset.forName(encoding);
110                 os.write(output.getBytes(charset));
111                 if (reportEntry.isNewLine()) {
112                     os.write(NL.getBytes(charset));
113                 }
114             }
115         } catch (IOException e) {
116             dumpException(e);
117             throw new UncheckedIOException(e);
118         }
119     }
120 
121     @SuppressWarnings("checkstyle:emptyblock")
122     private void closeNullReportFile(ReportEntry reportEntry) {
123         try {
124             // close null-output.txt report file
125             close(true);
126         } catch (IOException e) {
127             dumpException(e);
128         } finally {
129             // prepare <class>-output.txt report file
130             reportEntryName = usePhrasedFileName ? reportEntry.getSourceText() : reportEntry.getSourceName();
131         }
132     }
133 
134     @SuppressWarnings("checkstyle:emptyblock")
135     private void closeReportFile() {
136         try {
137             close(false);
138         } catch (IOException e) {
139             dumpException(e);
140         }
141     }
142 
143     private void close(boolean closeReattempt) throws IOException {
144         int[] status = new int[1];
145         FilterOutputStream os = fileOutputStream.get(status);
146         if (status[0] != CLOSED) {
147             fileOutputStream.set(null, closeReattempt ? CLOSED_TO_REOPEN : CLOSED);
148             if (os != null && status[0] == OPEN) {
149                 os.close();
150             }
151         }
152     }
153 
154     private void dumpException(IOException e) {
155         if (forkNumber == null) {
156             InPluginProcessDumpSingleton.getSingleton().dumpException(e, e.getLocalizedMessage(), reportsDirectory);
157         } else {
158             InPluginProcessDumpSingleton.getSingleton()
159                     .dumpException(e, e.getLocalizedMessage(), reportsDirectory, forkNumber);
160         }
161     }
162 }