blob: c5b8f22096b995f57dce014e3204435ff951f30a [file] [log] [blame]
package org.apache.maven.plugin.surefire.report;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.apache.maven.plugin.surefire.booterclient.output.InPluginProcessDumpSingleton;
import org.apache.maven.surefire.api.report.ReportEntry;
import org.apache.maven.surefire.api.report.TestSetReportEntry;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.ReentrantLock;
import static org.apache.maven.plugin.surefire.report.FileReporter.getReportFile;
import static org.apache.maven.surefire.api.util.internal.StringUtils.NL;
/**
* Surefire output consumer proxy that writes test output to a {@link java.io.File} for each test suite.
*
* @author Kristian Rosenvold
* @author Carlos Sanchez
*/
public class ConsoleOutputFileReporter
implements TestcycleConsoleOutputReceiver
{
private static final int STREAM_BUFFER_SIZE = 64 * 1024;
private static final int OPEN = 0;
private static final int CLOSED_TO_REOPEN = 1;
private static final int CLOSED = 2;
private final File reportsDirectory;
private final String reportNameSuffix;
private final boolean usePhrasedFileName;
private final Integer forkNumber;
private final String encoding;
private final AtomicStampedReference<FilterOutputStream> fileOutputStream =
new AtomicStampedReference<>( null, OPEN );
private final ReentrantLock lock = new ReentrantLock();
private volatile String reportEntryName;
public ConsoleOutputFileReporter( File reportsDirectory, String reportNameSuffix, boolean usePhrasedFileName,
Integer forkNumber, String encoding )
{
this.reportsDirectory = reportsDirectory;
this.reportNameSuffix = reportNameSuffix;
this.usePhrasedFileName = usePhrasedFileName;
this.forkNumber = forkNumber;
this.encoding = encoding;
}
@Override
public void testSetStarting( TestSetReportEntry reportEntry )
{
lock.lock();
try
{
closeNullReportFile( reportEntry );
}
finally
{
lock.unlock();
}
}
@Override
public void testSetCompleted( TestSetReportEntry report )
{
}
@Override
public void close()
{
// The close() method is called in main Thread T2.
lock.lock();
try
{
closeReportFile();
}
finally
{
lock.unlock();
}
}
@Override
public void writeTestOutput( String output, boolean newLine, boolean stdout )
{
lock.lock();
try
{
// This method is called in single thread T1 per fork JVM (see ThreadedStreamConsumer).
// The close() method is called in main Thread T2.
int[] status = new int[1];
FilterOutputStream os = fileOutputStream.get( status );
if ( status[0] != CLOSED )
{
if ( os == null )
{
if ( !reportsDirectory.exists() )
{
//noinspection ResultOfMethodCallIgnored
reportsDirectory.mkdirs();
}
File file = getReportFile( reportsDirectory, reportEntryName, reportNameSuffix, "-output.txt" );
os = new BufferedOutputStream( new FileOutputStream( file ), STREAM_BUFFER_SIZE );
fileOutputStream.set( os, OPEN );
}
if ( output == null )
{
output = "null";
}
Charset charset = Charset.forName( encoding );
os.write( output.getBytes( charset ) );
if ( newLine )
{
os.write( NL.getBytes( charset ) );
}
}
}
catch ( IOException e )
{
dumpException( e );
// todo use UncheckedIOException in Java 8
throw new RuntimeException( e );
}
finally
{
lock.unlock();
}
}
@SuppressWarnings( "checkstyle:emptyblock" )
private void closeNullReportFile( ReportEntry reportEntry )
{
try
{
// close null-output.txt report file
close( true );
}
catch ( IOException e )
{
dumpException( e );
}
finally
{
// prepare <class>-output.txt report file
reportEntryName = usePhrasedFileName ? reportEntry.getSourceText() : reportEntry.getSourceName();
}
}
@SuppressWarnings( "checkstyle:emptyblock" )
private void closeReportFile()
{
try
{
close( false );
}
catch ( IOException e )
{
dumpException( e );
}
}
private void close( boolean closeReattempt )
throws IOException
{
int[] status = new int[1];
FilterOutputStream os = fileOutputStream.get( status );
if ( status[0] != CLOSED )
{
fileOutputStream.set( null, closeReattempt ? CLOSED_TO_REOPEN : CLOSED );
if ( os != null && status[0] == OPEN )
{
os.close();
}
}
}
private void dumpException( IOException e )
{
if ( forkNumber == null )
{
InPluginProcessDumpSingleton.getSingleton()
.dumpException( e, e.getLocalizedMessage(), reportsDirectory );
}
else
{
InPluginProcessDumpSingleton.getSingleton()
.dumpException( e, e.getLocalizedMessage(), reportsDirectory, forkNumber );
}
}
}