diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..c0bcafe98 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip diff --git a/mvnw b/mvnw new file mode 100755 index 000000000..bd8896bf2 --- /dev/null +++ b/mvnw @@ -0,0 +1,295 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.4 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 000000000..92450f932 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,189 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/DefaultMutationGrouperFactory.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/DefaultMutationGrouperFactory.java index 123c23ad8..dd507d9e3 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/DefaultMutationGrouperFactory.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/DefaultMutationGrouperFactory.java @@ -3,6 +3,7 @@ import java.util.Properties; import org.pitest.classpath.CodeSource; +import org.pitest.plugin.Feature; public class DefaultMutationGrouperFactory implements MutationGrouperFactory { @@ -17,4 +18,11 @@ public MutationGrouper makeFactory(final Properties props, return new DefaultGrouper(unitSize); } + @Override + public Feature provides() { + return Feature.named("defaultgrouper") + .asInternalFeature() + .withOnByDefault(true) + .withDescription(description()); + } } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/MutationGrouperFactory.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/MutationGrouperFactory.java index 506917ef1..09e5d8532 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/MutationGrouperFactory.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/MutationGrouperFactory.java @@ -3,9 +3,15 @@ import java.util.Properties; import org.pitest.classpath.CodeSource; +import org.pitest.plugin.ProvidesFeature; import org.pitest.plugin.ToolClasspathPlugin; -public interface MutationGrouperFactory extends ToolClasspathPlugin { +/** + * Groups mutations within units. Additional precautions must be taken to ensure + * JVMS do not become poisoned if mutations from different classes are grouped within + * the same unit. Do not implement unless you understand this and know what you are doing. + */ +public interface MutationGrouperFactory extends ToolClasspathPlugin, ProvidesFeature { MutationGrouper makeFactory(Properties props, CodeSource codeSource, int numberOfThreads, int unitSize); diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/WorkerFactory.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/WorkerFactory.java index 9ed175e13..9c651ef88 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/WorkerFactory.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/WorkerFactory.java @@ -30,15 +30,17 @@ public class WorkerFactory { private final boolean fullMutationMatrix; private final MutationConfig config; private final EngineArguments args; + private final Collection featureStrings; - public WorkerFactory(final File baseDir, - final TestPluginArguments pitConfig, - final MutationConfig mutationConfig, - final EngineArguments args, - final TimeoutLengthStrategy timeoutStrategy, - final Verbosity verbosity, - final boolean fullMutationMatrix, - final String classPath) { + public WorkerFactory(File baseDir, + TestPluginArguments pitConfig, + MutationConfig mutationConfig, + EngineArguments args, + TimeoutLengthStrategy timeoutStrategy, + Verbosity verbosity, + boolean fullMutationMatrix, + String classPath, + Collection featureStrings) { this.pitConfig = pitConfig; this.timeoutStrategy = timeoutStrategy; this.verbosity = verbosity; @@ -47,6 +49,7 @@ public WorkerFactory(final File baseDir, this.baseDir = baseDir; this.config = mutationConfig; this.args = args; + this.featureStrings = featureStrings; } public MutationTestProcess createWorker( @@ -54,7 +57,7 @@ public MutationTestProcess createWorker( final Collection testClasses) { final MinionArguments fileArgs = new MinionArguments(remainingMutations, testClasses, this.config.getEngine().getName(), this.args, this.timeoutStrategy, - Log.verbosity(), this.fullMutationMatrix, this.pitConfig); + Log.verbosity(), this.fullMutationMatrix, this.pitConfig, this.featureStrings); final ProcessArgs args = ProcessArgs.withClassPath(this.classPath) .andLaunchOptions(this.config.getLaunchOptions()) diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/config/PluginServices.java b/pitest-entry/src/main/java/org/pitest/mutationtest/config/PluginServices.java index a5257ba69..37666e258 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/config/PluginServices.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/config/PluginServices.java @@ -97,7 +97,7 @@ Collection findTestFrameworkPlugins() { return load(TestPluginFactory.class); } - Collection findGroupers() { + Collection findGroupers() { return load(MutationGrouperFactory.class); } diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/config/SettingsFactory.java b/pitest-entry/src/main/java/org/pitest/mutationtest/config/SettingsFactory.java index f993562bd..c9e290cdb 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/config/SettingsFactory.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/config/SettingsFactory.java @@ -134,11 +134,24 @@ public JavaExecutableLocator getJavaExecutable() { } public MutationGrouperFactory getMutationGrouper() { - // Grouping behaviour is important. We cannot have more than 1 class mutated within - // a JVM or else the last mutation will poison the next. This restriction can only - // be removed if the hotswap functionality is reworked. - // Grouping behaviour is therefore hard coded for now. - return new DefaultMutationGrouperFactory(); + // Grouping behaviour is important. Without additional work to ensure a JVM is + // not poisoned, grouping mutations from different classes together will result + // in incorrect results. Implement with care. + final FeatureParser parser = new FeatureParser(); + Collection available = this.plugins.findGroupers(); + FeatureSelector features = new FeatureSelector<>(parser.parseFeatures(this.options.getFeatures()), available); + List enabled = features.getActiveFeatures(); + + if (enabled.isEmpty()) { + return new DefaultMutationGrouperFactory(); + } + + if (enabled.size() > 1) { + throw new RuntimeException("Only one grouper may be enabled"); + } + + return enabled.get(0); + } public CodeSource createCodeSource(ProjectClassPaths classPath) { @@ -217,7 +230,7 @@ public TestPrioritiserFactory getTestPrioritiser() { public CoverageOptions createCoverageOptions() { return new CoverageOptions( this.options.getTargetClasses(), this.options.getExcludedClasses(), - this.options.createMinionSettings(), this.options.getVerbosity()); + this.options.createMinionSettings(), this.options.getVerbosity(), this.options.getFeatures()); } public CompoundInterceptorFactory getInterceptor() { diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/MutationCoverage.java b/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/MutationCoverage.java index c84ddae35..e8cac6277 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/MutationCoverage.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/MutationCoverage.java @@ -359,7 +359,7 @@ private List buildMutationTests(CoverageDatabase coverageD .getConfiguration(), mutationConfig, args, new PercentAndConstantTimeoutStrategy(this.data.getTimeoutFactor(), this.data.getTimeoutConstant()), this.data.getVerbosity(), this.data.isFullMutationMatrix(), - this.data.getClassPath().getLocalClassPath()); + this.data.getClassPath().getLocalClassPath(), this.data.getFeatures()); final MutationGrouper grouper = this.settings.getMutationGrouper().makeFactory( this.data.getFreeFormProperties(), this.code, diff --git a/pitest-entry/src/test/java/org/pitest/coverage/execute/CoverageProcessSystemTest.java b/pitest-entry/src/test/java/org/pitest/coverage/execute/CoverageProcessSystemTest.java index 513fe1ffe..f828cfcad 100644 --- a/pitest-entry/src/test/java/org/pitest/coverage/execute/CoverageProcessSystemTest.java +++ b/pitest-entry/src/test/java/org/pitest/coverage/execute/CoverageProcessSystemTest.java @@ -38,6 +38,7 @@ import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; @@ -277,9 +278,13 @@ private List runCoverageForTest(final Class test) private void runCoverageProcess(final Class test, final List coveredClasses) throws IOException, InterruptedException { - final Consumer handler = a -> coveredClasses.add(a); + final Consumer handler = coveredClasses::add; - final CoverageOptions sa = new CoverageOptions(coverOnlyTestees(), excludeTests(), TestPluginArguments.defaults(), VERBOSE); + final CoverageOptions sa = new CoverageOptions(coverOnlyTestees(), + excludeTests(), + TestPluginArguments.defaults(), + VERBOSE, + Collections.emptyList()); final JarCreatingJarFinder agent = new JarCreatingJarFinder(); try { diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/ReportTestBase.java b/pitest-entry/src/test/java/org/pitest/mutationtest/ReportTestBase.java index ee6f08378..67ab90dfa 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/ReportTestBase.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/ReportTestBase.java @@ -141,7 +141,7 @@ code, this.data, new SettingsFactory(this.data, this.plugins), private CoverageOptions createCoverageOptions(TestPluginArguments configuration) { return new CoverageOptions(this.data.getTargetClasses(),this.data.getExcludedClasses(), - configuration, this.data.getVerbosity()); + configuration, this.data.getVerbosity(), data.getFeatures()); } protected void setMutators(final String... mutator) { diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/TestMutationTesting.java b/pitest-entry/src/test/java/org/pitest/mutationtest/TestMutationTesting.java index 4d4e64520..5bb3d1356 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/TestMutationTesting.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/TestMutationTesting.java @@ -289,7 +289,7 @@ null, coverageOptions, launchOptions, code, new NullCoverageExporter(), coverageOptions.getPitConfig(), mutationConfig, arguments, new PercentAndConstantTimeoutStrategy(data.getTimeoutFactor(), data.getTimeoutConstant()), data.getVerbosity(), false, data.getClassPath() - .getLocalClassPath()); + .getLocalClassPath(), data.getFeatures()); @@ -305,7 +305,7 @@ null, coverageOptions, launchOptions, code, new NullCoverageExporter(), private CoverageOptions createCoverageOptions(ReportOptions data) { return new CoverageOptions(data.getTargetClasses(),data.getExcludedClasses(), this.config, - data.getVerbosity()); + data.getVerbosity(), data.getFeatures()); } protected void verifyResults(final DetectionStatus... detectionStatus) { diff --git a/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationTestUnitTest.java b/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationTestUnitTest.java index d950a9aee..f7f9cd4e6 100644 --- a/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationTestUnitTest.java +++ b/pitest-entry/src/test/java/org/pitest/mutationtest/build/MutationTestUnitTest.java @@ -54,7 +54,7 @@ public void setUp() { this.tests = new ArrayList<>(); this.testee = new MutationTestUnit(this.mutations, new WorkerFactory(null, TestPluginArguments.defaults(), this.mutationConfig, EngineArguments.arguments(), this.timeout, - Verbosity.DEFAULT, false, null)); + Verbosity.DEFAULT, false, null, Collections.emptyList())); } diff --git a/pitest/src/main/java/org/pitest/coverage/execute/CoverageMinion.java b/pitest/src/main/java/org/pitest/coverage/execute/CoverageMinion.java index 70b5f3ce2..f7f26b07a 100644 --- a/pitest/src/main/java/org/pitest/coverage/execute/CoverageMinion.java +++ b/pitest/src/main/java/org/pitest/coverage/execute/CoverageMinion.java @@ -37,6 +37,7 @@ import java.lang.instrument.ClassFileTransformer; import java.net.Socket; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.function.Predicate; @@ -52,8 +53,6 @@ public class CoverageMinion { public static void main(final String[] args) { - enableTransformations(); - ExitCode exitCode = ExitCode.OK; Socket s = null; CoveragePipe invokeQueue = null; @@ -68,6 +67,7 @@ public static void main(final String[] args) { s.getInputStream()); final CoverageOptions paramsFromParent = dis.read(CoverageOptions.class); + enableTransformations(paramsFromParent.features()); configureVerbosity(paramsFromParent); @@ -121,9 +121,9 @@ public static void main(final String[] args) { } - private static void enableTransformations() { + private static void enableTransformations(Collection features) { ClientPluginServices plugins = ClientPluginServices.makeForContextLoader(); - for (TransformationPlugin each : plugins.findTransformations()) { + for (TransformationPlugin each : plugins.findTransformations(features)) { ClassFileTransformer transformer = each.makeCoverageTransformer(); if (transformer != null) { HotSwapAgent.addTransformer(transformer); diff --git a/pitest/src/main/java/org/pitest/coverage/execute/CoverageOptions.java b/pitest/src/main/java/org/pitest/coverage/execute/CoverageOptions.java index 03e1bb1dd..bd341f626 100644 --- a/pitest/src/main/java/org/pitest/coverage/execute/CoverageOptions.java +++ b/pitest/src/main/java/org/pitest/coverage/execute/CoverageOptions.java @@ -34,11 +34,13 @@ public class CoverageOptions implements Serializable { private final Collection exclude; private final Verbosity verbosity; private final TestPluginArguments pitConfig; + private final Collection features; public CoverageOptions(final Collection include, final Collection exclude, - final TestPluginArguments pitConfig, final Verbosity verbose) { - Objects.requireNonNull(pitConfig); + final TestPluginArguments pitConfig, final Verbosity verbose, Collection features) { + this.features = features; + Objects.requireNonNull(pitConfig); this.include = include; this.exclude = exclude; this.verbosity = verbose; @@ -59,6 +61,10 @@ public TestPluginArguments getPitConfig() { return this.pitConfig; } + public Collection features() { + return features; + } + private static Predicate commonClasses() { return Prelude.or( glob("org.pitest.*"), diff --git a/pitest/src/main/java/org/pitest/mutationtest/config/ClientPluginServices.java b/pitest/src/main/java/org/pitest/mutationtest/config/ClientPluginServices.java index 4d1bca470..e1eae9b0f 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/config/ClientPluginServices.java +++ b/pitest/src/main/java/org/pitest/mutationtest/config/ClientPluginServices.java @@ -5,6 +5,8 @@ import org.pitest.mutationtest.environment.EnvironmentResetPlugin; import org.pitest.mutationtest.MutationEngineFactory; import org.pitest.mutationtest.environment.TransformationPlugin; +import org.pitest.plugin.FeatureParser; +import org.pitest.plugin.FeatureSelector; import org.pitest.testapi.TestPluginFactory; import org.pitest.util.IsolationUtils; import org.pitest.util.ServiceLoader; @@ -28,12 +30,21 @@ public Collection findMutationEngines() { return ServiceLoader.load(MutationEngineFactory.class, this.loader); } - public Collection findResets() { - return ServiceLoader.load(EnvironmentResetPlugin.class, this.loader); + public Collection findResets(Collection featureStrings) { + Collection available = ServiceLoader.load(EnvironmentResetPlugin.class, this.loader); + final FeatureParser parser = new FeatureParser(); + + FeatureSelector features = new FeatureSelector<>(parser.parseFeatures(featureStrings), available); + return features.getActiveFeatures(); + } - public Collection findTransformations() { - return ServiceLoader.load(TransformationPlugin.class, this.loader); + public Collection findTransformations(Collection featureStrings) { + Collection available = ServiceLoader.load(TransformationPlugin.class, this.loader); + + final FeatureParser parser = new FeatureParser(); + FeatureSelector features = new FeatureSelector<>(parser.parseFeatures(featureStrings), available); + return features.getActiveFeatures(); } } diff --git a/pitest/src/main/java/org/pitest/mutationtest/config/MinionSettings.java b/pitest/src/main/java/org/pitest/mutationtest/config/MinionSettings.java index dac4dd3c4..baac8387f 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/config/MinionSettings.java +++ b/pitest/src/main/java/org/pitest/mutationtest/config/MinionSettings.java @@ -4,12 +4,13 @@ import org.pitest.help.PitHelpError; import org.pitest.junit.NullConfiguration; import org.pitest.mutationtest.environment.CompositeReset; -import org.pitest.mutationtest.environment.EnvironmentResetPlugin; import org.pitest.mutationtest.MutationEngineFactory; +import org.pitest.mutationtest.environment.ResetArguments; import org.pitest.mutationtest.environment.ResetEnvironment; import org.pitest.testapi.Configuration; import org.pitest.util.PitError; +import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @@ -23,9 +24,11 @@ public MinionSettings(final ClientPluginServices plugins) { this.plugins = plugins; } - public ResetEnvironment createReset() { - List resets = this.plugins.findResets().stream() - .map(EnvironmentResetPlugin::make).collect(Collectors.toList()); + public ResetEnvironment createReset(Collection featureStrings, ResetArguments args) { + List resets = this.plugins.findResets(featureStrings).stream() + .map(p -> p.make(args)) + .collect(Collectors.toList()); + return new CompositeReset(resets); } diff --git a/pitest/src/main/java/org/pitest/mutationtest/environment/CompositeReset.java b/pitest/src/main/java/org/pitest/mutationtest/environment/CompositeReset.java index fc1e24e86..8e0b50310 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/environment/CompositeReset.java +++ b/pitest/src/main/java/org/pitest/mutationtest/environment/CompositeReset.java @@ -2,20 +2,30 @@ import org.pitest.mutationtest.engine.Mutant; -import java.util.Collections; +import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; public class CompositeReset implements ResetEnvironment { private final List children; public CompositeReset(List children) { - this.children = Collections.unmodifiableList(children); + this.children = children.stream() + .sorted(Comparator.comparingInt(ResetEnvironment::priority)) + .collect(Collectors.toList()); } @Override - public void resetFor(Mutant mutatedClass) { + public void resetFor(Mutant mutatedClass, ClassLoader loader) { for (ResetEnvironment each : children) { - each.resetFor(mutatedClass); + each.resetFor(mutatedClass, loader); } } + + @Override + public void finishFor(Mutant mutatedClass, ClassLoader loader) { + for (ResetEnvironment each : children) { + each.finishFor(mutatedClass, loader); + } + } } diff --git a/pitest/src/main/java/org/pitest/mutationtest/environment/EnvironmentResetPlugin.java b/pitest/src/main/java/org/pitest/mutationtest/environment/EnvironmentResetPlugin.java index 29868986e..8b47073a8 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/environment/EnvironmentResetPlugin.java +++ b/pitest/src/main/java/org/pitest/mutationtest/environment/EnvironmentResetPlugin.java @@ -1,9 +1,18 @@ package org.pitest.mutationtest.environment; import org.pitest.plugin.ClientClasspathPlugin; +import org.pitest.plugin.Feature; +import org.pitest.plugin.ProvidesFeature; -public interface EnvironmentResetPlugin extends ClientClasspathPlugin { +public interface EnvironmentResetPlugin extends ClientClasspathPlugin, ProvidesFeature { - ResetEnvironment make(); + ResetEnvironment make(ResetArguments args); + @Override + // provide default feature for backwards compatibility + default Feature provides() { + return Feature.named("_internal") + .withOnByDefault(true) + .asInternalFeature(); + } } diff --git a/pitest/src/main/java/org/pitest/mutationtest/environment/ResetArguments.java b/pitest/src/main/java/org/pitest/mutationtest/environment/ResetArguments.java new file mode 100644 index 000000000..dde765a69 --- /dev/null +++ b/pitest/src/main/java/org/pitest/mutationtest/environment/ResetArguments.java @@ -0,0 +1,16 @@ +package org.pitest.mutationtest.environment; + +import org.pitest.classinfo.ClassByteArraySource; + +public class ResetArguments { + + private final ClassByteArraySource source; + + public ResetArguments(ClassByteArraySource source) { + this.source = source; + } + + public ClassByteArraySource source() { + return this.source; + } +} diff --git a/pitest/src/main/java/org/pitest/mutationtest/environment/ResetEnvironment.java b/pitest/src/main/java/org/pitest/mutationtest/environment/ResetEnvironment.java index b8b3c9914..83c14d9c5 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/environment/ResetEnvironment.java +++ b/pitest/src/main/java/org/pitest/mutationtest/environment/ResetEnvironment.java @@ -3,5 +3,12 @@ import org.pitest.mutationtest.engine.Mutant; public interface ResetEnvironment { - void resetFor(Mutant mutatedClass); -} + + void resetFor(Mutant mutatedClass, ClassLoader loader); + + void finishFor(Mutant mutatedClass, ClassLoader loader); + + default int priority() { + return 10; + } + } diff --git a/pitest/src/main/java/org/pitest/mutationtest/environment/TransformationPlugin.java b/pitest/src/main/java/org/pitest/mutationtest/environment/TransformationPlugin.java index 9de0c0a1c..768a31b23 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/environment/TransformationPlugin.java +++ b/pitest/src/main/java/org/pitest/mutationtest/environment/TransformationPlugin.java @@ -1,10 +1,12 @@ package org.pitest.mutationtest.environment; import org.pitest.plugin.ClientClasspathPlugin; +import org.pitest.plugin.Feature; +import org.pitest.plugin.ProvidesFeature; import java.lang.instrument.ClassFileTransformer; -public interface TransformationPlugin extends ClientClasspathPlugin { +public interface TransformationPlugin extends ClientClasspathPlugin, ProvidesFeature { @Deprecated default ClassFileTransformer makeTransformer() { @@ -19,4 +21,12 @@ default ClassFileTransformer makeMutationTransformer() { return null; } + @Override + // provide default feature for backwards compatibility + default Feature provides() { + return Feature.named("_internal") + .withOnByDefault(true) + .asInternalFeature(); + } + } diff --git a/pitest/src/main/java/org/pitest/mutationtest/execute/CatchNewClassLoadersTransformer.java b/pitest/src/main/java/org/pitest/mutationtest/environment/isolation/CatchNewClassLoadersTransformer.java similarity index 85% rename from pitest/src/main/java/org/pitest/mutationtest/execute/CatchNewClassLoadersTransformer.java rename to pitest/src/main/java/org/pitest/mutationtest/environment/isolation/CatchNewClassLoadersTransformer.java index 377ae49e7..32620d155 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/execute/CatchNewClassLoadersTransformer.java +++ b/pitest/src/main/java/org/pitest/mutationtest/environment/isolation/CatchNewClassLoadersTransformer.java @@ -1,7 +1,8 @@ -package org.pitest.mutationtest.execute; +package org.pitest.mutationtest.environment.isolation; import org.pitest.boot.HotSwapAgent; import org.pitest.classinfo.ClassName; +import org.pitest.mutationtest.engine.Mutant; import org.pitest.util.Log; import java.lang.instrument.ClassFileTransformer; @@ -33,7 +34,7 @@ public class CatchNewClassLoadersTransformer implements ClassFileTransformer { private static final Logger LOG = Log.getLogger(); - private static String targetClass; + private static ClassName targetClass; private static byte[] currentMutant; // @@ -46,16 +47,21 @@ public class CatchNewClassLoadersTransformer implements ClassFileTransformer { // we'll abuse a WeakHashMap and live with the synchronization. static final Map CLASS_LOADERS = Collections.synchronizedMap(new WeakHashMap<>()); - public static synchronized void setMutant(String className, byte[] mutant) { - targetClass = className; - currentMutant = mutant; + public static synchronized void reset() { + targetClass = null; + currentMutant = null; + } + + public static synchronized void setMutant(Mutant mutant) { + targetClass = mutant.getDetails().getClassName(); + currentMutant = mutant.getBytes(); logClassloaders(); for (ClassLoader each : CLASS_LOADERS.keySet()) { - final Class clazz = checkClassForLoader(each, className); + final Class clazz = checkClassForLoader(each, targetClass); if (clazz != null) { - HotSwapAgent.hotSwap(clazz, mutant); + HotSwapAgent.hotSwap(clazz, mutant.getBytes()); } } } @@ -65,7 +71,7 @@ public byte[] transform(final ClassLoader loader, final String className, final Class classBeingRedefined, final ProtectionDomain protectionDomain, final byte[] classfileBuffer) { - if (className.equals(targetClass) && shouldTransform(loader)) { + if (className.equals(targetClass.asInternalName()) && shouldTransform(loader)) { if (shouldStore(loader)) { CLASS_LOADERS.put(loader, null); } @@ -76,9 +82,9 @@ public byte[] transform(final ClassLoader loader, final String className, return null; } - private static Class checkClassForLoader(ClassLoader loader, String className) { + private static Class checkClassForLoader(ClassLoader loader, ClassName className) { try { - Class clazz = Class.forName(ClassName.fromString(className).asJavaName(), false, loader); + Class clazz = Class.forName(className.asJavaName(), false, loader); // loaded by parent if (clazz.getClassLoader() != loader) { return null; diff --git a/pitest/src/main/java/org/pitest/mutationtest/environment/isolation/ClassloaderIsolationResetPlugin.java b/pitest/src/main/java/org/pitest/mutationtest/environment/isolation/ClassloaderIsolationResetPlugin.java new file mode 100644 index 000000000..0814b8c30 --- /dev/null +++ b/pitest/src/main/java/org/pitest/mutationtest/environment/isolation/ClassloaderIsolationResetPlugin.java @@ -0,0 +1,38 @@ +package org.pitest.mutationtest.environment.isolation; + +import org.pitest.mutationtest.engine.Mutant; +import org.pitest.mutationtest.environment.EnvironmentResetPlugin; +import org.pitest.mutationtest.environment.ResetArguments; +import org.pitest.mutationtest.environment.ResetEnvironment; +import org.pitest.plugin.Feature; + +public class ClassloaderIsolationResetPlugin implements EnvironmentResetPlugin { + @Override + public ResetEnvironment make(ResetArguments unused) { + return new CatchNewClassLoadersReset(); + } + + @Override + public String description() { + return "Restore classes to unmutated version in other classloaders"; + } + + @Override + public Feature provides() { + return Feature.named("isolate_classloaders") + .withOnByDefault(true) + .withDescription(description()); + } +} + +class CatchNewClassLoadersReset implements ResetEnvironment { + @Override + public void resetFor(Mutant mutatedClass, ClassLoader unused) { + CatchNewClassLoadersTransformer.setMutant(mutatedClass); + } + + @Override + public void finishFor(Mutant mutatedClass, ClassLoader loader) { + CatchNewClassLoadersTransformer.reset(); + } +} diff --git a/pitest/src/main/java/org/pitest/mutationtest/environment/isolation/ClassloaderIsolationTransformationPlugin.java b/pitest/src/main/java/org/pitest/mutationtest/environment/isolation/ClassloaderIsolationTransformationPlugin.java new file mode 100644 index 000000000..285b594a7 --- /dev/null +++ b/pitest/src/main/java/org/pitest/mutationtest/environment/isolation/ClassloaderIsolationTransformationPlugin.java @@ -0,0 +1,25 @@ +package org.pitest.mutationtest.environment.isolation; + +import org.pitest.mutationtest.environment.TransformationPlugin; +import org.pitest.plugin.Feature; + +import java.lang.instrument.ClassFileTransformer; + +public class ClassloaderIsolationTransformationPlugin implements TransformationPlugin { + @Override + public String description() { + return "Restore classes to unmutated version in other classloaders"; + } + + @Override + public ClassFileTransformer makeMutationTransformer() { + return new CatchNewClassLoadersTransformer(); + } + + @Override + public Feature provides() { + return Feature.named("isolate_classloaders") + .withOnByDefault(true) + .withDescription(description()); + } +} diff --git a/pitest/src/main/java/org/pitest/mutationtest/execute/HotSwap.java b/pitest/src/main/java/org/pitest/mutationtest/execute/HotSwap.java index 0833fe372..85b2673f7 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/execute/HotSwap.java +++ b/pitest/src/main/java/org/pitest/mutationtest/execute/HotSwap.java @@ -13,10 +13,6 @@ class HotSwap { public Boolean insertClass(final ClassName clazzName, ClassLoader loader, final byte[] mutantBytes) { try { - // Some frameworks (eg quarkus) run tests in non delegating - // classloaders. Need to make sure these are transformed too - CatchNewClassLoadersTransformer.setMutant(clazzName.asInternalName(), mutantBytes); - // trigger loading for the current loader Class clazz = Class.forName(clazzName.asJavaName(), false, loader); diff --git a/pitest/src/main/java/org/pitest/mutationtest/execute/MinionArguments.java b/pitest/src/main/java/org/pitest/mutationtest/execute/MinionArguments.java index 1fd5854f1..421fa72ab 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/execute/MinionArguments.java +++ b/pitest/src/main/java/org/pitest/mutationtest/execute/MinionArguments.java @@ -36,11 +36,17 @@ public class MinionArguments implements Serializable { final Verbosity verbosity; final boolean fullMutationMatrix; final TestPluginArguments pitConfig; + final Collection featureStrings; - public MinionArguments(final Collection mutations, - final Collection tests, final String engine, final EngineArguments engineArgs, - final TimeoutLengthStrategy timeoutStrategy, final Verbosity verbosity, final boolean fullMutationMatrix, - final TestPluginArguments pitConfig) { + public MinionArguments(Collection mutations, + Collection tests, + String engine, + EngineArguments engineArgs, + TimeoutLengthStrategy timeoutStrategy, + Verbosity verbosity, + boolean fullMutationMatrix, + TestPluginArguments pitConfig, + Collection featureStrings) { this.mutations = mutations; this.testClasses = tests; this.engine = engine; @@ -49,6 +55,7 @@ public MinionArguments(final Collection mutations, this.verbosity = verbosity; this.fullMutationMatrix = fullMutationMatrix; this.pitConfig = pitConfig; + this.featureStrings = featureStrings; } public Verbosity verbosity() { diff --git a/pitest/src/main/java/org/pitest/mutationtest/execute/MutationTestMinion.java b/pitest/src/main/java/org/pitest/mutationtest/execute/MutationTestMinion.java index 17908aa34..c9dea912e 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/execute/MutationTestMinion.java +++ b/pitest/src/main/java/org/pitest/mutationtest/execute/MutationTestMinion.java @@ -20,6 +20,7 @@ import org.pitest.classinfo.ClassName; import org.pitest.classpath.ClassloaderByteArraySource; import org.pitest.mutationtest.EngineArguments; +import org.pitest.mutationtest.environment.ResetArguments; import org.pitest.mutationtest.environment.ResetEnvironment; import org.pitest.mutationtest.config.ClientPluginServices; import org.pitest.mutationtest.config.MinionSettings; @@ -71,6 +72,8 @@ public void run() { final MinionArguments paramsFromParent = this.dis .read(MinionArguments.class); + enableTransformations(paramsFromParent.featureStrings); + configureVerbosity(paramsFromParent); final ClassLoader loader = IsolationUtils.getContextClassLoader(); @@ -82,7 +85,7 @@ public void run() { final MutationEngine engine = createEngine(paramsFromParent.engine, paramsFromParent.engineArgs); - final ResetEnvironment reset = this.plugins.createReset(); + final ResetEnvironment reset = this.plugins.createReset(paramsFromParent.featureStrings, new ResetArguments(byteSource)); final MutationTestWorker worker = new MutationTestWorker(hotswap, engine.createMutator(byteSource), loader, reset, paramsFromParent.fullMutationMatrix); @@ -126,9 +129,6 @@ private Configuration createTestPlugin(TestPluginArguments pitConfig) { public static void main(final String[] args) { LOG.fine(() -> "minion started"); - enableTransformations(); - HotSwapAgent.addTransformer(new CatchNewClassLoadersTransformer()); - final int port = Integer.parseInt(args[0]); Socket s = null; @@ -167,9 +167,9 @@ private static List findTestsForTestClasses( return finder.findTestUnitsForAllSuppliedClasses(tcs); } - private static void enableTransformations() { + private static void enableTransformations(Collection featureString) { ClientPluginServices plugins = ClientPluginServices.makeForContextLoader(); - for (TransformationPlugin each : plugins.findTransformations()) { + for (TransformationPlugin each : plugins.findTransformations(featureString)) { ClassFileTransformer transformer = each.makeMutationTransformer(); if (transformer != null) { HotSwapAgent.addTransformer(transformer); diff --git a/pitest/src/main/java/org/pitest/mutationtest/execute/MutationTestWorker.java b/pitest/src/main/java/org/pitest/mutationtest/execute/MutationTestWorker.java index 6c8de32a5..4810b6bed 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/execute/MutationTestWorker.java +++ b/pitest/src/main/java/org/pitest/mutationtest/execute/MutationTestWorker.java @@ -98,7 +98,7 @@ private void processMutation(Reporter r, final MutationIdentifier mutationId = mutationDetails.getId(); final Mutant mutatedClass = this.mutater.getMutation(mutationId); - reset.resetFor(mutatedClass); + reset.resetFor(mutatedClass, this.loader); if (DEBUG) { LOG.fine("mutating method " + mutatedClass.getDetails().getMethod()); @@ -111,6 +111,8 @@ private void processMutation(Reporter r, final MutationStatusTestPair mutationDetected = handleMutation( mutationDetails, mutatedClass, relevantTests); + reset.finishFor(mutatedClass, this.loader); + r.report(mutationId, mutationDetected); if (DEBUG) { LOG.fine("Mutation " + mutationId + " detected = " + mutationDetected); diff --git a/pitest/src/main/java/org/pitest/mutationtest/mocksupport/JavassistInterceptor.java b/pitest/src/main/java/org/pitest/mutationtest/mocksupport/JavassistInterceptor.java index b11f2a216..b14f6814d 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/mocksupport/JavassistInterceptor.java +++ b/pitest/src/main/java/org/pitest/mutationtest/mocksupport/JavassistInterceptor.java @@ -61,4 +61,8 @@ private static boolean isMutatedClass(final String name) { static void setMutant(final Mutant newMutant) { mutant = newMutant; } + + static void reset() { + mutant = null; + } } diff --git a/pitest/src/main/java/org/pitest/mutationtest/mocksupport/JavassistTransformation.java b/pitest/src/main/java/org/pitest/mutationtest/mocksupport/JavassistTransformation.java index 9d6220754..dd848b3a0 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/mocksupport/JavassistTransformation.java +++ b/pitest/src/main/java/org/pitest/mutationtest/mocksupport/JavassistTransformation.java @@ -2,6 +2,7 @@ import org.pitest.functional.prelude.Prelude; import org.pitest.mutationtest.environment.TransformationPlugin; +import org.pitest.plugin.Feature; import org.pitest.util.Glob; import java.lang.instrument.ClassFileTransformer; @@ -22,6 +23,13 @@ public ClassFileTransformer makeMutationTransformer() { JavassistInputStreamInterceptorAdapter.inputStreamAdapterSupplier(JavassistInterceptor.class)); } + @Override + public Feature provides() { + return Feature.named("javassist") + .withOnByDefault(true) + .withDescription(description()); + } + @Override public String description() { return "Support for mocking frameworks using javassist"; diff --git a/pitest/src/main/java/org/pitest/mutationtest/mocksupport/ResetJavassistEnvironment.java b/pitest/src/main/java/org/pitest/mutationtest/mocksupport/ResetJavassistEnvironment.java index 90f015068..c5111dbbf 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/mocksupport/ResetJavassistEnvironment.java +++ b/pitest/src/main/java/org/pitest/mutationtest/mocksupport/ResetJavassistEnvironment.java @@ -1,12 +1,22 @@ package org.pitest.mutationtest.mocksupport; +import org.pitest.mutationtest.engine.Mutant; import org.pitest.mutationtest.environment.EnvironmentResetPlugin; +import org.pitest.mutationtest.environment.ResetArguments; import org.pitest.mutationtest.environment.ResetEnvironment; +import org.pitest.plugin.Feature; public class ResetJavassistEnvironment implements EnvironmentResetPlugin { @Override - public ResetEnvironment make() { - return JavassistInterceptor::setMutant; + public ResetEnvironment make(ResetArguments unused) { + return new JavassistReset(); + } + + @Override + public Feature provides() { + return Feature.named("javassist") + .withOnByDefault(true) + .withDescription(description()); } @Override @@ -14,3 +24,15 @@ public String description() { return "Reset environment for javassist"; } } + +class JavassistReset implements ResetEnvironment { + @Override + public void resetFor(Mutant mutatedClass, ClassLoader unused) { + JavassistInterceptor.setMutant(mutatedClass); + } + + @Override + public void finishFor(Mutant mutatedClass, ClassLoader loader) { + JavassistInterceptor.reset(); + } +} diff --git a/pitest/src/main/resources/META-INF/services/org.pitest.mutationtest.environment.EnvironmentResetPlugin b/pitest/src/main/resources/META-INF/services/org.pitest.mutationtest.environment.EnvironmentResetPlugin index a665fe494..2dddb5efa 100644 --- a/pitest/src/main/resources/META-INF/services/org.pitest.mutationtest.environment.EnvironmentResetPlugin +++ b/pitest/src/main/resources/META-INF/services/org.pitest.mutationtest.environment.EnvironmentResetPlugin @@ -1 +1,2 @@ -org.pitest.mutationtest.mocksupport.ResetJavassistEnvironment \ No newline at end of file +org.pitest.mutationtest.mocksupport.ResetJavassistEnvironment +org.pitest.mutationtest.environment.isolation.ClassloaderIsolationResetPlugin \ No newline at end of file diff --git a/pitest/src/main/resources/META-INF/services/org.pitest.mutationtest.environment.TransformationPlugin b/pitest/src/main/resources/META-INF/services/org.pitest.mutationtest.environment.TransformationPlugin index 4b62911ed..9a605c2a3 100644 --- a/pitest/src/main/resources/META-INF/services/org.pitest.mutationtest.environment.TransformationPlugin +++ b/pitest/src/main/resources/META-INF/services/org.pitest.mutationtest.environment.TransformationPlugin @@ -1 +1,2 @@ -org.pitest.mutationtest.mocksupport.JavassistTransformation \ No newline at end of file +org.pitest.mutationtest.mocksupport.JavassistTransformation +org.pitest.mutationtest.environment.isolation.ClassloaderIsolationTransformationPlugin \ No newline at end of file diff --git a/pitest/src/test/java/org/pitest/coverage/execute/CoverageOptionsTest.java b/pitest/src/test/java/org/pitest/coverage/execute/CoverageOptionsTest.java index 82ad2c611..7dff7e081 100644 --- a/pitest/src/test/java/org/pitest/coverage/execute/CoverageOptionsTest.java +++ b/pitest/src/test/java/org/pitest/coverage/execute/CoverageOptionsTest.java @@ -1,5 +1,6 @@ package org.pitest.coverage.execute; +import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.pitest.util.Verbosity.DEFAULT; @@ -12,18 +13,18 @@ public class CoverageOptionsTest { TestPluginArguments pitConfig = TestPluginArguments.defaults(); - CoverageOptions testee = new CoverageOptions(Collections.singletonList("*"), Collections.emptyList(), this.pitConfig, DEFAULT); + CoverageOptions testee = new CoverageOptions(Collections.singletonList("*"), emptyList(), this.pitConfig, DEFAULT, emptyList()); @Test public void shouldIncludeTargettedClasses() { - this.testee = new CoverageOptions(Collections.singletonList("com.example.*"), Collections.emptyList(), this.pitConfig, DEFAULT); + this.testee = new CoverageOptions(Collections.singletonList("com.example.*"), emptyList(), this.pitConfig, DEFAULT, emptyList()); assertThat(this.testee.getFilter().test("com.example.Foo")).isTrue(); } @Test public void shouldExcludeExcludedClasses() { - this.testee = new CoverageOptions(Collections.singletonList("com.example.*"), Collections.singletonList("com.example.NotMe"), this.pitConfig, DEFAULT); + this.testee = new CoverageOptions(Collections.singletonList("com.example.*"), Collections.singletonList("com.example.NotMe"), this.pitConfig, DEFAULT, emptyList()); assertThat(this.testee.getFilter().test("com.example.Foo")).isTrue(); assertThat(this.testee.getFilter().test("com.example.NotMe")).isFalse(); diff --git a/pitest/src/test/java/org/pitest/mutationtest/environment/CompositeResetTest.java b/pitest/src/test/java/org/pitest/mutationtest/environment/CompositeResetTest.java new file mode 100644 index 000000000..b5e395148 --- /dev/null +++ b/pitest/src/test/java/org/pitest/mutationtest/environment/CompositeResetTest.java @@ -0,0 +1,112 @@ +package org.pitest.mutationtest.environment; + +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.pitest.mutationtest.engine.Mutant; + +import java.util.Arrays; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class CompositeResetTest { + + private CompositeReset testee; + + private ResetEnvironment child1 = Mockito.mock(ResetEnvironment.class); + + private ResetEnvironment child2 = Mockito.mock(ResetEnvironment.class); + + private Mutant mutant; + + @Test + public void callsAllChildrenForReset() { + testee = new CompositeReset(Arrays.asList(child1, child2)); + testee.resetFor(mutant, aLoader()); + verify(child1).resetFor(mutant, aLoader()); + verify(child2).resetFor(mutant, aLoader()); + } + + @Test + public void ordersChildrenByPriorityForReset() { + when(child1.priority()).thenReturn(10); + when(child2.priority()).thenReturn(5); + + testee = new CompositeReset(Arrays.asList(child1, child2)); + testee.resetFor(mutant, aLoader()); + + InOrder inOrder = Mockito.inOrder(child1, child2); + inOrder.verify(child2).resetFor(mutant, aLoader()); + inOrder.verify(child1).resetFor(mutant, aLoader()); + } + + @Test + public void ordersChildrenByPriorityInverseForReset() { + when(child1.priority()).thenReturn(5); + when(child2.priority()).thenReturn(10); + + testee = new CompositeReset(Arrays.asList(child1, child2)); + testee.resetFor(mutant, aLoader()); + + InOrder inOrder = Mockito.inOrder(child1, child2); + inOrder.verify(child1).resetFor(mutant, aLoader()); + inOrder.verify(child2).resetFor(mutant, aLoader()); + } + + @Test + public void handlesEmptyListOfChildrenForReset() { + testee = new CompositeReset(Collections.emptyList()); + assertThatCode(() ->testee.resetFor(mutant, aLoader())) + .doesNotThrowAnyException(); + } + + @Test + public void callsAllChildrenForFinish() { + testee = new CompositeReset(Arrays.asList(child1, child2)); + testee.finishFor(mutant, aLoader()); + verify(child1).finishFor(mutant, aLoader()); + verify(child2).finishFor(mutant, aLoader()); + } + + @Test + public void ordersChildrenByPriorityForFinish() { + when(child1.priority()).thenReturn(10); + when(child2.priority()).thenReturn(5); + + testee = new CompositeReset(Arrays.asList(child1, child2)); + testee.finishFor(mutant, aLoader()); + + InOrder inOrder = Mockito.inOrder(child1, child2); + inOrder.verify(child2).finishFor(mutant, aLoader()); + inOrder.verify(child1).finishFor(mutant, aLoader()); + } + + @Test + public void ordersChildrenByPriorityInverseForFinish() { + when(child1.priority()).thenReturn(5); + when(child2.priority()).thenReturn(10); + + testee = new CompositeReset(Arrays.asList(child1, child2)); + testee.finishFor(mutant, aLoader()); + + InOrder inOrder = Mockito.inOrder(child1, child2); + inOrder.verify(child1).finishFor(mutant, aLoader()); + inOrder.verify(child2).finishFor(mutant, aLoader()); + } + + @Test + public void handlesEmptyListOfChildrenForFinish() { + testee = new CompositeReset(Collections.emptyList()); + assertThatCode(() ->testee.finishFor(mutant, aLoader())) + .doesNotThrowAnyException(); + } + + private ClassLoader aLoader() { + // good enough + return null; + } + +} \ No newline at end of file