| 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; |
| } |
| } |