blob: 5336ac64b33bc4c75d42a511630f2807ebab04f5 [file] [log] [blame]
package org.apache.maven.plugin.surefire.booterclient;
/*
* 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.JdkAttributes;
import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
import org.apache.maven.surefire.booter.AbstractPathConfiguration;
import org.apache.maven.surefire.booter.Classpath;
import org.apache.maven.surefire.booter.StartupConfiguration;
import org.apache.maven.surefire.booter.SurefireBooterForkException;
import org.apache.maven.surefire.extensions.ForkNodeFactory;
import org.apache.maven.surefire.api.util.internal.ImmutableMap;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import static org.apache.maven.plugin.surefire.SurefireHelper.replaceForkThreadsInPath;
import static org.apache.maven.plugin.surefire.util.Relocator.relocate;
import static org.apache.maven.plugin.surefire.SurefireHelper.replaceThreadNumberPlaceholders;
import static org.apache.maven.surefire.booter.Classpath.join;
/**
* Basic framework which constructs CLI.
*
* @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
* @since 2.21.0.Jigsaw
*/
public abstract class DefaultForkConfiguration
extends ForkConfiguration
{
@Nonnull private final Classpath booterClasspath;
@Nonnull private final File tempDirectory;
@Nullable
private final String debugLine;
@Nonnull private final File workingDirectory;
@Nonnull private final Properties modelProperties;
@Nullable private final String argLine;
@Nonnull private final Map<String, String> environmentVariables;
@Nonnull private final String[] excludedEnvironmentVariables;
private final boolean debug;
private final int forkCount;
private final boolean reuseForks;
@Nonnull private final Platform pluginPlatform;
@Nonnull private final ConsoleLogger log;
@Nonnull private final ForkNodeFactory forkNodeFactory;
@SuppressWarnings( "checkstyle:parameternumber" )
protected DefaultForkConfiguration( @Nonnull Classpath booterClasspath,
@Nonnull File tempDirectory,
@Nullable String debugLine,
@Nonnull File workingDirectory,
@Nonnull Properties modelProperties,
@Nullable String argLine,
@Nonnull Map<String, String> environmentVariables,
@Nonnull String[] excludedEnvironmentVariables,
boolean debug,
int forkCount,
boolean reuseForks,
@Nonnull Platform pluginPlatform,
@Nonnull ConsoleLogger log,
@Nonnull ForkNodeFactory forkNodeFactory )
{
this.booterClasspath = booterClasspath;
this.tempDirectory = tempDirectory;
this.debugLine = debugLine;
this.workingDirectory = workingDirectory;
this.modelProperties = modelProperties;
this.argLine = argLine;
this.environmentVariables = toImmutable( environmentVariables );
this.excludedEnvironmentVariables = excludedEnvironmentVariables;
this.debug = debug;
this.forkCount = forkCount;
this.reuseForks = reuseForks;
this.pluginPlatform = pluginPlatform;
this.log = log;
this.forkNodeFactory = forkNodeFactory;
}
protected abstract void resolveClasspath( @Nonnull OutputStreamFlushableCommandline cli,
@Nonnull String booterThatHasMainMethod,
@Nonnull StartupConfiguration config,
@Nonnull File dumpLogDirectory )
throws SurefireBooterForkException;
@Nonnull
protected String extendJvmArgLine( @Nonnull String jvmArgLine )
{
return jvmArgLine;
}
@Nonnull
@Override
public final ForkNodeFactory getForkNodeFactory()
{
return forkNodeFactory;
}
/**
* @param config The startup configuration
* @param forkNumber index of forked JVM, to be the replacement in the argLine
* @param dumpLogDirectory directory for dump log file
* @return CommandLine able to flush entire command going to be sent to forked JVM
* @throws org.apache.maven.surefire.booter.SurefireBooterForkException when unable to perform the fork
*/
@Nonnull
@Override
public OutputStreamFlushableCommandline createCommandLine( @Nonnull StartupConfiguration config,
int forkNumber,
@Nonnull File dumpLogDirectory )
throws SurefireBooterForkException
{
OutputStreamFlushableCommandline cli =
new OutputStreamFlushableCommandline( getExcludedEnvironmentVariables() );
cli.setWorkingDirectory( getWorkingDirectory( forkNumber ).getAbsolutePath() );
for ( Entry<String, String> entry : getEnvironmentVariables().entrySet() )
{
String value = entry.getValue();
cli.addEnvironment( entry.getKey(), value == null ? "" : value );
}
cli.setExecutable( getJdkForTests().getJvmExecutable().getAbsolutePath() );
String jvmArgLine = newJvmArgLine( forkNumber );
if ( !jvmArgLine.isEmpty() )
{
cli.createArg()
.setLine( jvmArgLine );
}
if ( getDebugLine() != null && !getDebugLine().isEmpty() )
{
cli.createArg()
.setLine( getDebugLine() );
}
resolveClasspath( cli, findStartClass( config ), config, dumpLogDirectory );
return cli;
}
protected ConsoleLogger getLogger()
{
return log;
}
@Nonnull
protected List<String> toCompleteClasspath( StartupConfiguration conf ) throws SurefireBooterForkException
{
AbstractPathConfiguration pathConfig = conf.getClasspathConfiguration();
if ( pathConfig.isClassPathConfig() == pathConfig.isModularPathConfig() )
{
throw new SurefireBooterForkException( "Could not find class-path config nor modular class-path either." );
}
Classpath bootClasspath = getBooterClasspath();
Classpath testClasspath = pathConfig.getTestClasspath();
Classpath providerClasspath = pathConfig.getProviderClasspath();
Classpath completeClasspath = join( join( bootClasspath, testClasspath ), providerClasspath );
getLogger().debug( completeClasspath.getLogMessage( "boot classpath:" ) );
getLogger().debug( completeClasspath.getCompactLogMessage( "boot(compact) classpath:" ) );
return completeClasspath.getClassPath();
}
@Nonnull
private File getWorkingDirectory( int forkNumber )
throws SurefireBooterForkException
{
File cwd = replaceForkThreadsInPath( getWorkingDirectory(), forkNumber );
if ( !cwd.exists() && !cwd.mkdirs() )
{
throw new SurefireBooterForkException( "Cannot create workingDirectory " + cwd.getAbsolutePath() );
}
if ( !cwd.isDirectory() )
{
throw new SurefireBooterForkException(
"WorkingDirectory " + cwd.getAbsolutePath() + " exists and is not a directory" );
}
return cwd;
}
/**
* Replaces expressions <pre>@{property-name}</pre> with the corresponding properties
* from the model. This allows late evaluation of property values when the plugin is executed (as compared
* to evaluation when the pom is parsed as is done with <pre>${property-name}</pre> expressions).
*
* This allows other plugins to modify or set properties with the changes getting picked up by surefire.
*/
@Nonnull
private String interpolateArgLineWithPropertyExpressions()
{
if ( getArgLine() == null )
{
return "";
}
String resolvedArgLine = getArgLine().trim();
if ( resolvedArgLine.isEmpty() )
{
return "";
}
for ( final String key : getModelProperties().stringPropertyNames() )
{
String field = "@{" + key + "}";
if ( getArgLine().contains( field ) )
{
resolvedArgLine = resolvedArgLine.replace( field, getModelProperties().getProperty( key, "" ) );
}
}
return resolvedArgLine;
}
@Nonnull
private static String stripNewLines( @Nonnull String argLine )
{
return argLine.replace( "\n", " " ).replace( "\r", " " );
}
/**
* Immutable map.
*
* @param map immutable map copies elements from <code>map</code>
* @param <K> key type
* @param <V> value type
* @return never returns null
*/
@Nonnull
private static <K, V> Map<K, V> toImmutable( @Nullable Map<K, V> map )
{
return map == null ? Collections.<K, V>emptyMap() : new ImmutableMap<>( map );
}
@Override
@Nonnull
public File getTempDirectory()
{
return tempDirectory;
}
@Override
@Nullable
protected String getDebugLine()
{
return debugLine;
}
@Override
@Nonnull
protected File getWorkingDirectory()
{
return workingDirectory;
}
@Override
@Nonnull
protected Properties getModelProperties()
{
return modelProperties;
}
@Override
@Nullable
protected String getArgLine()
{
return argLine;
}
@Override
@Nonnull
protected Map<String, String> getEnvironmentVariables()
{
return environmentVariables;
}
@Nonnull
@Override
protected String[] getExcludedEnvironmentVariables()
{
return excludedEnvironmentVariables;
}
@Override
protected boolean isDebug()
{
return debug;
}
@Override
protected int getForkCount()
{
return forkCount;
}
@Override
protected boolean isReuseForks()
{
return reuseForks;
}
@Override
@Nonnull
protected Platform getPluginPlatform()
{
return pluginPlatform;
}
@Override
@Nonnull
protected JdkAttributes getJdkForTests()
{
return getPluginPlatform().getJdkExecAttributesForTests();
}
@Override
@Nonnull
protected Classpath getBooterClasspath()
{
return booterClasspath;
}
@Nonnull
private String newJvmArgLine( int forks )
{
String interpolatedArgs = stripNewLines( interpolateArgLineWithPropertyExpressions() );
String argsWithReplacedForkNumbers = replaceThreadNumberPlaceholders( interpolatedArgs, forks );
return extendJvmArgLine( argsWithReplacedForkNumbers );
}
@Nonnull
private static String findStartClass( StartupConfiguration config )
{
return config.isShadefire() ? relocate( DEFAULT_PROVIDER_CLASS ) : DEFAULT_PROVIDER_CLASS;
}
}