diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..5f537e68 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,42 @@ +version: 2 + +updates: + - package-ecosystem: "gradle" + directory: "/CompSuiteHarness" + schedule: + interval: "daily" + + - package-ecosystem: "gradle" + directory: "/ConfGen" + schedule: + interval: "daily" + + - package-ecosystem: "gradle" + directory: "/Instrumentation" + schedule: + interval: "daily" + + - package-ecosystem: "gradle" + directory: "/Maestro" + schedule: + interval: "daily" + + - package-ecosystem: "gradle" + directory: "/Samples/Gradle/sampleclient" + schedule: + interval: "daily" + + - package-ecosystem: "gradle" + directory: "/Samples/Gradle/samplelibrary" + schedule: + interval: "daily" + + - package-ecosystem: "gradle" + directory: "/TestGenerator" + schedule: + interval: "daily" + + - package-ecosystem: "gradle" + directory: "/TraceDiff" + schedule: + interval: "daily" diff --git a/.gitignore b/.gitignore index 0569916f..3cf0268c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +Backup/ + # Compiled class file *.class @@ -148,4 +150,7 @@ fabric.properties # End of https://www.toptal.com/developers/gitignore/api/jetbrains *.db -.vscode/ \ No newline at end of file +.vscode/ +Results/ +/logs/ +/.idea/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000..ba3af781 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gilesi.iml b/.idea/gilesi.iml new file mode 100644 index 00000000..b107a2dd --- /dev/null +++ b/.idea/gilesi.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 00000000..d1fda739 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..0b11c48e --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..475aaf10 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Maestro__Sample_Project_.xml b/.idea/runConfigurations/Maestro__Sample_Project_.xml new file mode 100644 index 00000000..86ca14fc --- /dev/null +++ b/.idea/runConfigurations/Maestro__Sample_Project_.xml @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Samples/.gitignore b/CompSuiteHarness/.gitignore similarity index 100% rename from Samples/.gitignore rename to CompSuiteHarness/.gitignore diff --git a/CompSuiteHarness/build.gradle b/CompSuiteHarness/build.gradle new file mode 100644 index 00000000..3ddc7e84 --- /dev/null +++ b/CompSuiteHarness/build.gradle @@ -0,0 +1,81 @@ +plugins { + id 'application' + id 'com.github.johnrengelman.shadow' version '8.1.1' +} + +group 'com.github.gilesi.harness.compsuite' +version '1.0-SNAPSHOT' + + +compileJava { + options.encoding = 'UTF-8' +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} + +repositories { + mavenCentral() + mavenLocal() + maven { + url 'https://packages.jetbrains.team/maven/p/ij/intellij-dependencies' + } +} + +dependencies { + implementation 'com.fasterxml.jackson.core:jackson-core:2.18.1' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.18.1' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.1' + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.18.1' + implementation 'com.fasterxml.jackson:jackson-base:2.18.1' + implementation 'org.apache.maven:maven-core:3.9.9' + implementation 'org.apache.maven.shared:maven-invoker:3.3.0' + testImplementation platform('org.junit:junit-bom:5.11.3') + testImplementation 'org.junit.jupiter:junit-jupiter' + implementation 'org.apache.logging.log4j:log4j-core:2.24.1' + implementation 'org.apache.logging.log4j:log4j-api:2.24.3' +} + +jar { + manifest { + attributes 'Main-Class': 'com.github.gilesi.harness.compsuite.Main', + 'Multi-Release': 'true' + } +} + +application { + mainClass = 'com.github.gilesi.harness.compsuite.Main' +} + +description = 'Main distribution.' + +shadowJar { + archiveBaseName.set('com.github.gilesi.harness.compsuite') + archiveClassifier.set('') + archiveVersion.set('') + mergeServiceFiles() +} + +distributions { + shadow { + distributionBaseName = 'com.github.gilesi.harness.compsuite' + } +} + +apply plugin: 'java' +apply plugin: 'idea' + +idea { + module { + downloadJavadoc = true + downloadSources = true + } +} + +run { + jvmArgs = [ + "-XX:InitialHeapSize=2G", + "-XX:MaxHeapSize=2G" + ] +} \ No newline at end of file diff --git a/CompSuiteHarness/gradle/wrapper/gradle-wrapper.jar b/CompSuiteHarness/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..249e5832 Binary files /dev/null and b/CompSuiteHarness/gradle/wrapper/gradle-wrapper.jar differ diff --git a/CompSuiteHarness/gradle/wrapper/gradle-wrapper.properties b/CompSuiteHarness/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..5c07b32a --- /dev/null +++ b/CompSuiteHarness/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon May 27 14:15:06 CEST 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/CompSuiteHarness/gradlew b/CompSuiteHarness/gradlew new file mode 100755 index 00000000..1b6c7873 --- /dev/null +++ b/CompSuiteHarness/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +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 + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/CompSuiteHarness/gradlew.bat b/CompSuiteHarness/gradlew.bat new file mode 100644 index 00000000..ac1b06f9 --- /dev/null +++ b/CompSuiteHarness/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/CompSuiteHarness/settings.gradle b/CompSuiteHarness/settings.gradle new file mode 100644 index 00000000..5b4528a5 --- /dev/null +++ b/CompSuiteHarness/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'CompSuiteHarness' + diff --git a/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/CompSuite.java b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/CompSuite.java new file mode 100644 index 00000000..664ef91d --- /dev/null +++ b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/CompSuite.java @@ -0,0 +1,54 @@ +package com.github.gilesi.harness.compsuite; + +import com.github.gilesi.harness.compsuite.runners.maven.TestAssertedLogger; +import org.apache.maven.shared.invoker.MavenInvocationException; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +public class CompSuite { + public static Map runRegressionTestsOnDataset( + String dataset, + String datasetOutput + ) throws IOException, MavenInvocationException { + + CompsuiteReproduction compsuiteReproduction = new CompsuiteReproduction(dataset); + CompSuiteIncompatibility[] incompatibilities = compsuiteReproduction.getIncompatibilities(); + Map mapList = new HashMap<>(); + Path datasetOutputPath = Path.of(datasetOutput); + + for (CompSuiteIncompatibility incompatibility : incompatibilities) { + CompsuiteReproduction.printIncompatibility(incompatibility, Main.logger); + + TestAssertedLogger oldTestAssertedLogger = CompsuiteReproduction.runTestOnClientUsingOldVersionOfLibrary( + datasetOutputPath, + incompatibility, + null, + Main.logger + ); + + TestAssertedLogger newTestAssertedLogger = CompsuiteReproduction.runTestOnClientUsingNewVersionOfLibrary( + datasetOutputPath, + incompatibility, + null, + Main.logger + ); + + Main.logger.info("Project ID: %s - OLD TEST FAILED: %s - NEW TEST FAILED: %s".formatted(incompatibility.id, + oldTestAssertedLogger.HasTestFailed(), + newTestAssertedLogger.HasTestFailed())); + + mapList.put( + incompatibility, + new CompSuiteRegressionResults( + !oldTestAssertedLogger.HasTestFailed(), + !newTestAssertedLogger.HasTestFailed() + ) + ); + } + + return mapList; + } +} diff --git a/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/CompSuiteIncompatibility.java b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/CompSuiteIncompatibility.java new file mode 100644 index 00000000..990c0848 --- /dev/null +++ b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/CompSuiteIncompatibility.java @@ -0,0 +1,17 @@ +package com.github.gilesi.harness.compsuite; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class CompSuiteIncompatibility { + public String id; + public String client; + public String url; + public String sha; + public String lib; + public String old; + @JsonProperty("new") + public String _new; + public String test; + public String submodule; + public String test_cmd; +} diff --git a/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/CompSuiteRegressionResults.java b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/CompSuiteRegressionResults.java new file mode 100644 index 00000000..39fc5409 --- /dev/null +++ b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/CompSuiteRegressionResults.java @@ -0,0 +1,23 @@ +package com.github.gilesi.harness.compsuite; + +public class CompSuiteRegressionResults { + public boolean oldPassed; + public boolean newPassed; + + public CompSuiteRegressionResults(boolean oldPassed, boolean newPassed) { + this.oldPassed = oldPassed; + this.newPassed = newPassed; + } + + public boolean isNewPassed() { + return newPassed; + } + + public boolean isOldPassed() { + return oldPassed; + } + + public boolean isRegressionReproduced() { + return oldPassed && !newPassed; + } +} diff --git a/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/CompsuiteReproduction.java b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/CompsuiteReproduction.java new file mode 100644 index 00000000..b2799caa --- /dev/null +++ b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/CompsuiteReproduction.java @@ -0,0 +1,267 @@ +package com.github.gilesi.harness.compsuite; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; + +import org.apache.maven.shared.invoker.MavenInvocationException; + +import com.fasterxml.jackson.core.exc.StreamReadException; +import com.fasterxml.jackson.databind.DatabindException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.gilesi.harness.compsuite.runners.maven.Log4JLogger; +import com.github.gilesi.harness.compsuite.runners.maven.ProjectRunner; +import com.github.gilesi.harness.compsuite.runners.maven.TestAssertedLogger; + +public class CompsuiteReproduction { + + private static String LIBRARY_OLD_VERSION_FOLDER_NAME = "old"; + private static String LIBRARY_NEW_VERSION_FOLDER_NAME = "new"; + + private CompSuiteIncompatibility[] incompatibilities; + + public CompsuiteReproduction(String dataset) throws StreamReadException, DatabindException, IOException { + incompatibilities = new ObjectMapper().readValue(new File(dataset), CompSuiteIncompatibility[].class); + } + + public CompSuiteIncompatibility[] getIncompatibilities() { + return incompatibilities; + } + + public static void printIncompatibility(CompSuiteIncompatibility incompatibility, org.apache.logging.log4j.Logger logger) { + logger.info("-----------------------------------"); + logger.info("id: %s".formatted(incompatibility.id)); + logger.info("client: %s".formatted(incompatibility.client)); + logger.info("url: %s".formatted(incompatibility.url)); + logger.info("sha: %s".formatted(incompatibility.sha)); + logger.info("lib: %s".formatted(incompatibility.lib)); + logger.info("old: %s".formatted(incompatibility.old)); + logger.info("new: %s".formatted(incompatibility._new)); + logger.info("test: %s".formatted(incompatibility.test)); + logger.info("submodule: %s".formatted(incompatibility.submodule)); + logger.info("test_cmd: %s".formatted(incompatibility.test_cmd)); + logger.info("-----------------------------------"); + } + + public void cloneDataset(String datasetOutput) throws IOException, InterruptedException { + FileUtils.deleteDirectoryIfExists(datasetOutput); + Path datasetOutputPath = Path.of(datasetOutput); + Files.createDirectories(datasetOutputPath); + + for (CompSuiteIncompatibility incompatibility : incompatibilities) { + printIncompatibility(incompatibility, Main.logger); + + Path oldDestinationProjectPath = datasetOutputPath.resolve(incompatibility.id).resolve(LIBRARY_OLD_VERSION_FOLDER_NAME); + FileUtils.deleteDirectoryIfExists(oldDestinationProjectPath.toString()); + Files.createDirectories(oldDestinationProjectPath); + + Main.logger.info("Cloning old at: %s".formatted(oldDestinationProjectPath)); + FetchGit.cloneProject(incompatibility.url, incompatibility.sha, oldDestinationProjectPath); + + Path newDestinationProjectPath = datasetOutputPath.resolve(incompatibility.id).resolve(LIBRARY_NEW_VERSION_FOLDER_NAME); + FileUtils.deleteDirectoryIfExists(newDestinationProjectPath.toString()); + Files.createDirectories(newDestinationProjectPath); + + Main.logger.info("Cloning new at: %s".formatted(newDestinationProjectPath)); + FetchGit.cloneProject( + incompatibility.url, + "tags/%s-%s" + .formatted( + incompatibility.lib + .replace(":", "--"), + incompatibility._new + ), + newDestinationProjectPath + ); + } + } + + public static String getIncompatibilityTestCommand(CompSuiteIncompatibility incompatibility) { + String testCmd = "test -fn -Drat.ignoreErrors=true -DtrimStackTrace=false -DfailIfNoTests=false -Dtest=%s" + .formatted(incompatibility.test); + + if (!incompatibility.test_cmd.equals("N/A")) { + testCmd = incompatibility.test_cmd; + + if (testCmd.startsWith("mvn ")) { + testCmd = testCmd.substring(4); + } + } + + return testCmd; + } + + private static Path getIncompatibilityLibraryProjectPath( + CompSuiteIncompatibility incompatibility, + Path datasetOutputPath, + String projectFolder + ) { + Path destinationProjectPath = datasetOutputPath.resolve(incompatibility.id).resolve(projectFolder); + + if (!incompatibility.submodule.equals("N/A")) { + destinationProjectPath = destinationProjectPath.resolve(incompatibility.submodule); + } + + return destinationProjectPath; + } + + public static Path getIncompatibilityOldLibraryProjectPath( + CompSuiteIncompatibility incompatibility, + Path datasetOutputPath + ) { + return getIncompatibilityLibraryProjectPath(incompatibility, datasetOutputPath, LIBRARY_OLD_VERSION_FOLDER_NAME); + } + + public static Path getIncompatibilityNewLibraryProjectPath( + CompSuiteIncompatibility incompatibility, + Path datasetOutputPath + ) { + return getIncompatibilityLibraryProjectPath(incompatibility, datasetOutputPath, LIBRARY_NEW_VERSION_FOLDER_NAME); + } + + private static TestAssertedLogger runTestOnClient( + Path datasetOutputPath, + String projectFolder, + CompSuiteIncompatibility incompatibility, + String additionalJavaToolParameters, + org.apache.logging.log4j.Logger logger + ) throws MavenInvocationException { + + Path destinationProjectPath = getIncompatibilityLibraryProjectPath(incompatibility, datasetOutputPath, projectFolder); + String testCmd = getIncompatibilityTestCommand(incompatibility); + + TestAssertedLogger testAssertedLogger = new TestAssertedLogger( + new Log4JLogger( + logger, + org.apache.maven.shared.invoker.InvokerLogger.INFO, + "%s-%s".formatted(projectFolder, incompatibility.id) + ) + ); + + ProjectRunner.runMavenGoalOnRepository(destinationProjectPath.toString(), + testCmd, + null, + "1.8", + testAssertedLogger, + additionalJavaToolParameters); + + return testAssertedLogger; + } + + public static TestAssertedLogger runTestOnClientUsingOldVersionOfLibrary( + Path datasetOutputPath, + CompSuiteIncompatibility incompatibility, + String additionalJavaToolParameters, + org.apache.logging.log4j.Logger logger + ) throws MavenInvocationException { + return runTestOnClient(datasetOutputPath, LIBRARY_OLD_VERSION_FOLDER_NAME, incompatibility, additionalJavaToolParameters, logger); + } + + public static TestAssertedLogger runTestOnClientUsingNewVersionOfLibrary( + Path datasetOutputPath, + CompSuiteIncompatibility incompatibility, + String additionalJavaToolParameters, + org.apache.logging.log4j.Logger logger + ) throws MavenInvocationException { + return runTestOnClient(datasetOutputPath, LIBRARY_NEW_VERSION_FOLDER_NAME, incompatibility, additionalJavaToolParameters, logger); + } + + public void copySourcesForDataset(Path datasetOutputPath) throws IOException, MavenInvocationException { + copyOldSourcesForDataset(datasetOutputPath, Path.of(Constants.M2_REPOSITORY)); + copyNewSourcesForDataset(datasetOutputPath, Path.of(Constants.M2_REPOSITORY)); + } + + private void copyOldSourcesForDataset(Path datasetOutputPath, Path m2RepositoryPath) throws IOException, MavenInvocationException { + for (CompSuiteIncompatibility incompatibility : incompatibilities) { + String[] pathComponents = incompatibility.lib.split(":")[0].split("\\."); + String secondaryPathComponent = incompatibility.lib.split(":")[1]; + String tiertiaryPathComponent = incompatibility.old; + + Path fileSystemRepoPath = m2RepositoryPath; + + for (String pathComponent : pathComponents) { + fileSystemRepoPath = fileSystemRepoPath.resolve(pathComponent); + } + + fileSystemRepoPath = fileSystemRepoPath.resolve(secondaryPathComponent).resolve(tiertiaryPathComponent); + + printIncompatibility(incompatibility, Main.logger); + + ArrayList srcCandidates = FileUtils.enumerateFiles(fileSystemRepoPath.toString(), ".*sources\\.jar", false); + + if (srcCandidates.size() == 0) { + Main.logger.info("%s Missing sources for lib, aborting!".formatted(incompatibility.id)); + continue; + } + + Path oldDestinationProjectPath = datasetOutputPath.resolve(incompatibility.id).resolve("lib-old"); + FileUtils.deleteDirectoryIfExists(oldDestinationProjectPath.toString()); + Files.createDirectories(oldDestinationProjectPath); + + for (String cand : srcCandidates) { + System.out.println("Extracting " + cand); + extractJar(cand, oldDestinationProjectPath.toString()); + } + } + } + + private void copyNewSourcesForDataset(Path datasetOutputPath, Path m2RepositoryPath) throws IOException, MavenInvocationException { + for (CompSuiteIncompatibility incompatibility : incompatibilities) { + String[] pathComponents = incompatibility.lib.split(":")[0].split("\\."); + String secondaryPathComponent = incompatibility.lib.split(":")[1]; + String tiertiaryPathComponent = incompatibility._new; + + Path fileSystemRepoPath = m2RepositoryPath; + + for (String pathComponent : pathComponents) { + fileSystemRepoPath = fileSystemRepoPath.resolve(pathComponent); + } + + fileSystemRepoPath = fileSystemRepoPath.resolve(secondaryPathComponent).resolve(tiertiaryPathComponent); + + printIncompatibility(incompatibility, Main.logger); + + ArrayList srcCandidates = FileUtils.enumerateFiles(fileSystemRepoPath.toString(), ".*sources\\.jar", false); + + if (srcCandidates.size() == 0) { + Main.logger.info("%s Missing sources for lib, aborting!".formatted(incompatibility.id)); + continue; + } + + Path newDestinationProjectPath = datasetOutputPath.resolve(incompatibility.id).resolve("lib-new"); + FileUtils.deleteDirectoryIfExists(newDestinationProjectPath.toString()); + Files.createDirectories(newDestinationProjectPath); + + for (String cand : srcCandidates) { + System.out.println("Extracting " + cand); + extractJar(cand, newDestinationProjectPath.toString()); + } + } + } + + private static void extractJar(String jar, String destdir) throws java.io.IOException { + java.util.jar.JarFile jarfile = new java.util.jar.JarFile(new java.io.File(jar)); + java.util.Enumeration enu = jarfile.entries(); + while (enu.hasMoreElements()) { + java.util.jar.JarEntry je = enu.nextElement(); + + java.io.File fl = new java.io.File(destdir, je.getName()); + if (!fl.exists()) { + fl.getParentFile().mkdirs(); + fl = new java.io.File(destdir, je.getName()); + } + if (je.isDirectory()) { + continue; + } + java.io.InputStream is = jarfile.getInputStream(je); + java.io.FileOutputStream fo = new java.io.FileOutputStream(fl); + while (is.available() > 0) { + fo.write(is.read()); + } + fo.close(); + is.close(); + } + } +} diff --git a/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/Constants.java b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/Constants.java new file mode 100644 index 00000000..1ae5fdbd --- /dev/null +++ b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/Constants.java @@ -0,0 +1,22 @@ +package com.github.gilesi.harness.compsuite; + +import java.util.Hashtable; +import java.util.Map; + +public class Constants { + public static final String JAVA_21_BINARY_LOCATION = "/usr/lib/jvm/java-21-openjdk-amd64"; + + public static final Map JAVA_VERSIONS = new Hashtable<>() { + { + put("1.5", "/usr/lib/jvm/java-8-openjdk-amd64"); + put("1.6", "/usr/lib/jvm/java-8-openjdk-amd64"); + put("1.7", "/usr/lib/jvm/java-8-openjdk-amd64"); + put("1.8", "/usr/lib/jvm/java-8-openjdk-amd64"); + put("11", "/usr/lib/jvm/java-11-openjdk-amd64"); + put("17", "/usr/lib/jvm/java-17-openjdk-amd64"); + put("21", JAVA_21_BINARY_LOCATION); + } + }; + + public static final String M2_REPOSITORY = "/home/gus/.m2/repository"; +} diff --git a/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/FetchGit.java b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/FetchGit.java new file mode 100644 index 00000000..27061338 --- /dev/null +++ b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/FetchGit.java @@ -0,0 +1,67 @@ +package com.github.gilesi.harness.compsuite; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; + +public class FetchGit { + private static String[] getGitCloneCommands(String repoUrl, String hash) { + return new String[]{ + "git init", + "git remote add origin " + repoUrl, + "git fetch --depth 1 origin " + hash, + "git reset --hard FETCH_HEAD", + "git submodule init", + "git submodule update" + }; + } + + // Needed for some compsuite projects + // TODO: say which ones right here... + private static String[] getGitFullCloneCommands(String repoUrl, String hash) { + return new String[]{ + "git clone %s .".formatted(repoUrl), + "git checkout %s".formatted(hash), + "git submodule init", + "git submodule update" + }; + } + + private static boolean deleteDirectory(File fi) { + boolean globalResult = true; + + for (File file : Objects.requireNonNull(fi.listFiles())) { + if (file.isDirectory()) { + deleteDirectory(file); + } + if (!file.delete()) { + globalResult = false; + } + } + if (!fi.delete()) { + globalResult = false; + } + + return globalResult; + } + + public static void cloneProject(String cloneUrl, String commit, Path repositoryDestination) throws IOException, InterruptedException { + String[] commands = getGitFullCloneCommands(cloneUrl, commit); + + if (Files.exists(repositoryDestination)) { + if (deleteDirectory(new File(repositoryDestination.toString()))) { + Files.createDirectory(repositoryDestination); + } + } else { + Files.createDirectory(repositoryDestination); + } + + for (String cmd : commands) { + File fi = new File(repositoryDestination.toString()); + Process p1 = Runtime.getRuntime().exec(cmd.split(" "), null, fi); + p1.waitFor(); + } + } +} diff --git a/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/FileUtils.java b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/FileUtils.java new file mode 100644 index 00000000..08ad78b7 --- /dev/null +++ b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/FileUtils.java @@ -0,0 +1,130 @@ +package com.github.gilesi.harness.compsuite; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Objects; +import java.util.regex.Pattern; + +public class FileUtils { + public static void deleteDirectory(File fi) { + for (File file : Objects.requireNonNull(fi.listFiles())) { + if (file.isDirectory()) { + deleteDirectory(file); + } + file.delete(); + } + fi.delete(); + } + + public static void deleteDirectoryIfExists(String path) { + Path pa = Path.of(path); + if (Files.exists(pa)) { + deleteDirectory(new File(path)); + } + } + + public static ArrayList enumerateDirectory(File fi, String wildcard, boolean recursive) { + ArrayList files = new ArrayList<>(); + Pattern pattern = Pattern.compile(wildcard); + for (File file : Objects.requireNonNull(fi.listFiles())) { + if (file.isDirectory() && recursive) { + files.addAll(enumerateDirectory(file, wildcard, true)); + } else { + if (pattern.matcher(file.getName()).matches()) { + files.add(file.getPath()); + } + } + } + return files; + } + + public static ArrayList enumerateFiles(String path, String wildcard, boolean recursive) { + Path pa = Path.of(path); + if (Files.exists(pa)) { + return enumerateDirectory(new File(path), wildcard, recursive); + } + return new ArrayList<>(); + } + + public static void ensureDirectoryExistsAndIsEmpty(String path) throws IOException { + Path pa = Path.of(path); + deleteDirectoryIfExists(path); + Files.createDirectory(pa); + } + + public static void backupFile(String fileLocation) throws IOException { + Path filePath = Path.of(fileLocation); + Path backupFilePath = Path.of("%s.orig".formatted(fileLocation)); + + if (!Files.exists(filePath)) { + return; + } + + if (Files.exists(backupFilePath)) { + Files.delete(backupFilePath); + } + + Files.copy(filePath, backupFilePath); + } + + public static void restoreFile(String fileLocation) throws IOException { + Path filePath = Path.of(fileLocation); + Path backupFilePath = Path.of("%s.orig".formatted(fileLocation)); + + if (!Files.exists(backupFilePath)) { + return; + } + + if (Files.exists(filePath)) { + Files.delete(filePath); + } + + Files.move(backupFilePath, filePath); + } + + public static void copyFolder(File source, File destination) { + if (source.isDirectory()) { + if (!destination.exists()) { + destination.mkdirs(); + } + + String[] files = source.list(); + + for (String file : files) { + File srcFile = new File(source, file); + File destFile = new File(destination, file); + + copyFolder(srcFile, destFile); + } + } else { + InputStream in = null; + OutputStream out = null; + + try { + in = new FileInputStream(source); + out = new FileOutputStream(destination); + + byte[] buffer = new byte[1024]; + + int length; + while ((length = in.read(buffer)) > 0) { + out.write(buffer, 0, length); + } + } catch (Exception e) { + try { + in.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + + try { + out.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } + } +} diff --git a/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/Main.java b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/Main.java new file mode 100644 index 00000000..d3f2a762 --- /dev/null +++ b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/Main.java @@ -0,0 +1,45 @@ +package com.github.gilesi.harness.compsuite; + +import org.apache.maven.shared.invoker.MavenInvocationException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +public class Main { + public static final Logger logger = LogManager.getLogger(); + + public static void main(String[] args) throws MavenInvocationException, IOException, InterruptedException { + if (System.getenv("MAVEN_HOME") == null || !Files.exists(Path.of(System.getenv("MAVEN_HOME"))) || Files.isRegularFile(Path.of(System.getenv("MAVEN_HOME")))) { + logger.info("You must specify the MAVEN_HOME environment variable pointing to a valid Maven directory"); + return; + } + + if (args.length < 2) { + logger.info("Usage: "); + return; + } + + String dataset = args[0]; + String datasetOutput = args[1]; + + //logger.info("Cloning CompSuite DataSet"); + //new CompsuiteReproduction(dataset).cloneDataset(datasetOutput); + + logger.info("Running CompSuite Incompatibilities"); + Map result = CompSuite.runRegressionTestsOnDataset(dataset, datasetOutput); + + for (Map.Entry entry : result.entrySet()) { + logger.info("Project: %s Old: %s New: %s RegressionReproduced: %s" + .formatted( + entry.getKey().id, + entry.getValue().isOldPassed(), + entry.getValue().isNewPassed(), + entry.getValue().isRegressionReproduced()) + ); + } + } +} \ No newline at end of file diff --git a/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/buildsystem/configuration/maven/PomHelper.java b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/buildsystem/configuration/maven/PomHelper.java new file mode 100644 index 00000000..aaaa3405 --- /dev/null +++ b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/buildsystem/configuration/maven/PomHelper.java @@ -0,0 +1,21 @@ +package com.github.gilesi.harness.compsuite.buildsystem.configuration.maven; + +import org.apache.maven.model.Model; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; + +public class PomHelper { + public static Model readPomFile(String pomFilePath) throws IOException, XmlPullParserException { + MavenXpp3Reader pomReader = new MavenXpp3Reader(); + Path pomPath = Path.of(pomFilePath); + InputStream pomStream = Files.newInputStream(pomPath); + Model pomModel = pomReader.read(pomStream); + pomModel.setPomFile(new File(pomFilePath)); + pomStream.close(); + return pomModel; + } +} diff --git a/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/runners/maven/Log4JLogger.java b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/runners/maven/Log4JLogger.java new file mode 100644 index 00000000..8d569682 --- /dev/null +++ b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/runners/maven/Log4JLogger.java @@ -0,0 +1,271 @@ +package com.github.gilesi.harness.compsuite.runners.maven; + +/* + * 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.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.maven.shared.invoker.InvocationOutputHandler; +import org.apache.maven.shared.invoker.InvokerLogger; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * Offers a logger that writes to a print stream like {@link java.lang.System#out}. + * + * @since 2.0.9 + */ +public class Log4JLogger implements InvokerLogger, InvocationOutputHandler { + + /** + * The print stream to write to, never null. + */ + private final Logger out; + + /** + * The threshold used to filter messages. + */ + private int threshold; + + private String prefix; + + /** + * Creates a new logger that writes to {@link java.lang.System#out} and has a threshold of {@link #INFO}. + */ + public Log4JLogger() { + this(LogManager.getLogger(), INFO, ""); + } + + public Log4JLogger(String prefix) { + this(LogManager.getLogger(), INFO, prefix); + } + + /** + * Creates a new logger that writes to the specified print stream. + * + * @param out The print stream to write to, must not be null. + * @param threshold The threshold for the logger. + */ + public Log4JLogger(Logger out, int threshold, String prefix) { + if (out == null) { + throw new NullPointerException("missing output stream"); + } + this.prefix = prefix; + this.out = out; + setThreshold(threshold); + } + + /** + * Writes the specified message and exception to the print stream. + * + * @param level The priority level of the message. + * @param message The message to log, may be null. + * @param error The exception to log, may be null. + */ + private void log(int level, String message, Throwable error) { + if (level > threshold) { + // don't log when it doesn't match your threshold. + return; + } + + if (message == null && error == null) { + // don't log when there's nothing to log. + return; + } + + StringBuilder buffer = new StringBuilder(); + Level logLevel = Level.INFO; + + switch (level) { + case (DEBUG) -> logLevel = Level.DEBUG; + case (INFO) -> logLevel = Level.INFO; + case (WARN) -> logLevel = Level.WARN; + case (ERROR) -> logLevel = Level.ERROR; + case (FATAL) -> logLevel = Level.FATAL; + default -> { + } + } + + if (prefix != null && !prefix.isEmpty()) { + buffer.append("[%s-Maven] ".formatted(prefix)); + } else { + buffer.append("[Maven] "); + } + + if (message != null) { + buffer.append(message); + } + + if (error != null) { + StringWriter writer = new StringWriter(); + PrintWriter pWriter = new PrintWriter(writer); + + error.printStackTrace(pWriter); + + if (message != null) { + buffer.append('\n'); + } + + buffer.append("Error:\n"); + buffer.append(writer); + } + + out.log(logLevel, buffer.toString()); + } + + /** + * {@inheritDoc} + */ + public void debug(String message) { + log(DEBUG, message, null); + } + + /** + * {@inheritDoc} + */ + public void debug(String message, Throwable throwable) { + log(DEBUG, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void info(String message) { + log(INFO, message, null); + } + + /** + * {@inheritDoc} + */ + public void info(String message, Throwable throwable) { + log(INFO, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void warn(String message) { + log(WARN, message, null); + } + + /** + * {@inheritDoc} + */ + public void warn(String message, Throwable throwable) { + log(WARN, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void error(String message) { + log(ERROR, message, null); + } + + /** + * {@inheritDoc} + */ + public void error(String message, Throwable throwable) { + log(ERROR, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void fatalError(String message) { + log(FATAL, message, null); + } + + /** + * {@inheritDoc} + */ + public void fatalError(String message, Throwable throwable) { + log(FATAL, message, throwable); + } + + /** + *

isDebugEnabled.

+ * + * @return a boolean. + */ + public boolean isDebugEnabled() { + return threshold >= DEBUG; + } + + /** + *

isErrorEnabled.

+ * + * @return a boolean. + */ + public boolean isErrorEnabled() { + return threshold >= ERROR; + } + + /** + *

isFatalErrorEnabled.

+ * + * @return a boolean. + */ + public boolean isFatalErrorEnabled() { + return threshold >= FATAL; + } + + /** + *

isInfoEnabled.

+ * + * @return a boolean. + */ + public boolean isInfoEnabled() { + return threshold >= INFO; + } + + /** + *

isWarnEnabled.

+ * + * @return a boolean. + */ + public boolean isWarnEnabled() { + return threshold >= WARN; + } + + /** + *

Getter for the field threshold.

+ * + * @return an int. + */ + public int getThreshold() { + return threshold; + } + + /** + * {@inheritDoc} + */ + public void setThreshold(int threshold) { + this.threshold = threshold; + } + + /** + * {@inheritDoc} + */ + public void consumeLine(String line) { + log(INFO, line, null); + } +} \ No newline at end of file diff --git a/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/runners/maven/ProjectRunner.java b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/runners/maven/ProjectRunner.java new file mode 100644 index 00000000..ffc882d3 --- /dev/null +++ b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/runners/maven/ProjectRunner.java @@ -0,0 +1,181 @@ +package com.github.gilesi.harness.compsuite.runners.maven; + +import com.github.gilesi.harness.compsuite.Constants; +import com.github.gilesi.harness.compsuite.Main; +import org.apache.maven.model.*; +import org.apache.maven.shared.invoker.*; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import static com.github.gilesi.harness.compsuite.buildsystem.configuration.maven.PomHelper.readPomFile; + +public class ProjectRunner { + + public static int runPomGoals(String pomFilePath, List pomGoals, List pomProperties, + boolean batchMode, String userSettingsFilePath, Object logger, String javaToolOptions) + throws MavenInvocationException { + + String javaVersion = "8"; + + try { + Model pomModel = readPomFile(pomFilePath); + Properties props = pomModel.getProperties(); + if (props.stringPropertyNames().contains("java.compiler.version")) { + javaVersion = props.getProperty("java.compiler.version"); + Main.logger.info("Debug: Using java.compiler.version=" + javaVersion); + } else if (props.stringPropertyNames().contains("maven.compiler.source")) { + javaVersion = props.getProperty("maven.compiler.source"); + Main.logger.info("Debug: Using maven.compiler.source=" + javaVersion); + } else if (props.stringPropertyNames().contains("maven.compiler.target")) { + javaVersion = props.getProperty("maven.compiler.target"); + Main.logger.info("Debug: Using maven.compiler.target=" + javaVersion); + } else if (props.stringPropertyNames().contains("maven.compile.source")) { + javaVersion = props.getProperty("maven.compile.source"); + Main.logger.info("Debug: Using maven.compile.source=" + javaVersion); + } else if (props.stringPropertyNames().contains("maven.compile.target")) { + javaVersion = props.getProperty("maven.compile.target"); + Main.logger.info("Debug: Using maven.compile.target=" + javaVersion); + } + } catch (Exception ignored) { + Main.logger.info("Debug: Getting java specific version unfortunately failed."); + } + + return runPomGoals(pomFilePath, pomGoals, pomProperties, batchMode, userSettingsFilePath, javaVersion, logger, + javaToolOptions); + } + + public static int runPomGoals(String pomFilePath, List pomGoals, List pomProperties, + boolean batchMode, String userSettingsFilePath, String javaVersion, Object logger, String javaToolOptions) + throws MavenInvocationException { + String javaHome = Constants.JAVA_VERSIONS.getOrDefault(javaVersion, System.getenv("JAVA_HOME")); + return runPomGoalsInternal(pomFilePath, pomGoals, pomProperties, batchMode, userSettingsFilePath, javaHome, + javaVersion, logger, javaToolOptions); + } + + public static int runPomGoalsInternal(String pomFilePath, List pomGoals, List pomProperties, + boolean batchMode, String userSettingsFilePath, String JavaHome, String JavaVersion, Object logger, + String javaToolOptions) throws MavenInvocationException { + File pomFile = new File(pomFilePath); + + InvokerLogger invokerLogger = null; + InvocationOutputHandler invocationOutputHandler = null; + + if (logger instanceof InvokerLogger tmp) { + invokerLogger = tmp; + } + + if (logger instanceof InvocationOutputHandler tmp) { + invocationOutputHandler = tmp; + } + + File projectDirectory = new File(Path.of(pomFilePath).getParent().toString()); + + Properties properties = new Properties(); + pomProperties.forEach(p -> properties.setProperty(p, "true")); + + properties.setProperty("maven.compiler.source", JavaVersion); + properties.setProperty("maven.compiler.target", JavaVersion); + properties.setProperty("maven.compile.source", JavaVersion); + properties.setProperty("maven.compile.target", JavaVersion); + + properties.setProperty("maven.source.skip", "true"); + properties.setProperty("assembly.skipAssembly", "true"); + properties.setProperty("shade.skip", "true"); + properties.setProperty("maven.war.skip", "true"); + properties.setProperty("maven.rar.skip", "true"); + properties.setProperty("changelog.skip", "true"); + properties.setProperty("checkstyle.skip", "true"); + properties.setProperty("maven.doap.skip", "true"); + properties.setProperty("maven.javadoc.skip", "true"); + properties.setProperty("maven.jxr.skip", "true"); + properties.setProperty("linkcheck.skip", "true"); + properties.setProperty("pmd.skip", "true"); + properties.setProperty("mpir.skip", "true"); + properties.setProperty("gpg.skip", "true"); + properties.setProperty("jdepend.skip", "true"); + + String javacLocation = "%s%sbin%sjavac".formatted(JavaHome, File.separator, File.separator); + pomGoals.add("-Dmaven.compiler.fork=true"); + if (Files.exists(Path.of(javacLocation))) { + pomGoals.add("-Dmaven.compiler.executable=%s".formatted(javacLocation)); + } + + InvocationRequest request = new DefaultInvocationRequest(); + + Integer timeout = 30 * 60; // 30 minutes in seconds + + request.setShellEnvironmentInherited(false); + request.setPomFile(pomFile); + request.setGoals(pomGoals); + request.setProperties(properties); + request.setAlsoMake(true); + request.setBatchMode(true); + request.setTimeoutInSeconds(timeout); + + request.setJavaHome(new File(JavaHome)); + request.setBaseDirectory(projectDirectory); + request.setInputStream(InputStream.nullInputStream()); + if (invocationOutputHandler != null) { + request.setOutputHandler(invocationOutputHandler); + request.setErrorHandler(invocationOutputHandler); + } + + if (userSettingsFilePath != null && !userSettingsFilePath.isEmpty() && !userSettingsFilePath.isBlank()) { + Path pa = Path.of(userSettingsFilePath); + if (Files.exists(pa) && Files.isRegularFile(pa)) { + request.setUserSettingsFile(new File(userSettingsFilePath)); + } + } + + if (javaToolOptions != null && !javaToolOptions.isEmpty()) { + request.addShellEnvironment("JAVA_TOOL_OPTIONS", javaToolOptions); + } + + Main.logger.info("Building with pom=%s goals=%s properties=%s%n" + .formatted(pomFilePath, + pomGoals, + properties)); + + try { + Invoker invoker = new DefaultInvoker(); + if (invokerLogger != null) { + invoker.setLogger(invokerLogger); + } + + invoker.setMavenHome(new File(System.getenv("MAVEN_HOME"))); + + InvocationResult result = invoker.execute(request); + + Main.logger.info("Building with pom={} goals={} properties={} returned {}", + pomFile, + pomGoals, + properties, + result.getExitCode()); + + return result.getExitCode(); + } catch (MavenInvocationException e) { + throw e; + } + } + + public static boolean runMavenGoalOnRepository(String repositoryDirectory, String goal, String userSettingsFilePath, + String javaVersion, Object logger, String javaToolOptions) throws MavenInvocationException { + Main.logger.info("ExecuteMavenGoalOnDuetsRepository Entry"); + String pomFilePath = "%s%spom.xml".formatted(repositoryDirectory, File.separator); + ArrayList goals = new ArrayList<>(); + goals.add(goal); + + if (javaVersion != null && !javaVersion.isBlank()) { + return runPomGoals(pomFilePath, goals, new ArrayList<>(), false, userSettingsFilePath, javaVersion, logger, + javaToolOptions) == 0; + } else { + return runPomGoals(pomFilePath, goals, new ArrayList<>(), false, userSettingsFilePath, logger, + javaToolOptions) == 0; + } + } +} \ No newline at end of file diff --git a/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/runners/maven/TestAssertedLogger.java b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/runners/maven/TestAssertedLogger.java new file mode 100644 index 00000000..866fd8f3 --- /dev/null +++ b/CompSuiteHarness/src/main/java/com/github/gilesi/harness/compsuite/runners/maven/TestAssertedLogger.java @@ -0,0 +1,192 @@ +package com.github.gilesi.harness.compsuite.runners.maven; + +import org.apache.maven.shared.invoker.InvocationOutputHandler; +import org.apache.maven.shared.invoker.InvokerLogger; + +import java.io.IOException; + +public class TestAssertedLogger implements InvokerLogger, InvocationOutputHandler { + + private InvokerLogger invokerLogger = null; + private InvocationOutputHandler invocationOutputHandler = null; + private boolean testFailed = false; + + public TestAssertedLogger(Object logger) { + if (logger instanceof InvokerLogger tmp) { + invokerLogger = tmp; + } + + if (logger instanceof InvocationOutputHandler tmp) { + invocationOutputHandler = tmp; + } + } + + + public void debug(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.debug(message); + } + } + + public void debug(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.debug(message, throwable); + } + } + + public boolean isDebugEnabled() { + if (invokerLogger != null) { + return invokerLogger.isDebugEnabled(); + } + + return false; + } + + public void info(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.info(message); + } + } + + public void info(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.info(message, throwable); + } + } + + public boolean isInfoEnabled() { + if (invokerLogger != null) { + return invokerLogger.isInfoEnabled(); + } + + return false; + } + + public void warn(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.warn(message); + } + } + + public void warn(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.warn(message, throwable); + } + } + + public boolean isWarnEnabled() { + if (invokerLogger != null) { + return invokerLogger.isWarnEnabled(); + } + + return false; + } + + public void error(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.error(message); + } + } + + public void error(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.error(message, throwable); + } + } + + public boolean isErrorEnabled() { + if (invokerLogger != null) { + return invokerLogger.isErrorEnabled(); + } + + return false; + } + + public void fatalError(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.fatalError(message); + } + } + + public void fatalError(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.fatalError(message, throwable); + } + } + + public boolean isFatalErrorEnabled() { + if (invokerLogger != null) { + return invokerLogger.isFatalErrorEnabled(); + } + + return false; + } + + public int getThreshold() { + if (invokerLogger != null) { + return invokerLogger.getThreshold(); + } + + return 0; + } + + public void setThreshold(int threshold) { + if (invokerLogger != null) { + invokerLogger.setThreshold(threshold); + } + } + + public void consumeLine(String line) throws IOException { + if (line.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invocationOutputHandler != null) { + invocationOutputHandler.consumeLine(line); + } + } + + public boolean HasTestFailed() { + return testFailed; + } +} diff --git a/CompSuiteHarness/src/main/resources/log4j2.xml b/CompSuiteHarness/src/main/resources/log4j2.xml new file mode 100644 index 00000000..e49fab87 --- /dev/null +++ b/CompSuiteHarness/src/main/resources/log4j2.xml @@ -0,0 +1,22 @@ + + + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ConfGen/build.gradle b/ConfGen/build.gradle index 8a4c17bd..7804bbaa 100644 --- a/ConfGen/build.gradle +++ b/ConfGen/build.gradle @@ -1,9 +1,9 @@ plugins { id 'application' - id 'com.github.johnrengelman.shadow' version '7.0.0' + id 'com.github.johnrengelman.shadow' version '8.1.1' } -group 'com.github.maracas.gilesi.confgen' +group 'com.github.gilesi.confgen' version '1.0-SNAPSHOT' @@ -11,41 +11,47 @@ compileJava { options.encoding = 'UTF-8' } -tasks.withType(JavaCompile) { +tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' } repositories { mavenCentral() - mavenLocal() + //mavenLocal() maven { + name = "IntelliJ" url 'https://packages.jetbrains.team/maven/p/ij/intellij-dependencies' } + maven { + name = "Roseau" + url = "https://maven.pkg.github.com/alien-tools/roseau" + credentials { + username = project.findProperty("gpr.user") + password = project.findProperty("gpr.key") + } + } } dependencies { - implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.1' - implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.15.1' - implementation 'com.fasterxml.jackson.module:jackson-module-paranamer:2.15.1' - implementation 'com.google.guava:guava:32.1.2-jre' - implementation 'fr.inria.gforge.spoon:spoon-core:10.4.2' + implementation 'com.github.maracas:roseau:0.0.2-SNAPSHOT' + implementation 'com.thoughtworks.xstream:xstream:1.4.20' } jar { manifest { - attributes 'Main-Class': 'com.github.maracas.gilesi.confgen.Main', + attributes 'Main-Class': 'com.github.gilesi.confgen.Main', 'Multi-Release': 'true' } } application { - mainClass = 'com.github.maracas.gilesi.confgen.Main' + mainClass = 'com.github.gilesi.confgen.Main' } description = 'Main distribution.' shadowJar { - archiveBaseName.set('com.github.maracas.gilesi.confgen') + archiveBaseName.set('com.github.gilesi.confgen') archiveClassifier.set('') archiveVersion.set('') mergeServiceFiles() @@ -53,7 +59,7 @@ shadowJar { distributions { shadow { - distributionBaseName = 'com.github.maracas.gilesi.confgen' + distributionBaseName = 'com.github.gilesi.confgen' } } @@ -72,4 +78,4 @@ run { "-XX:InitialHeapSize=2G", "-XX:MaxHeapSize=2G" ] -} \ No newline at end of file +} diff --git a/ConfGen/src/main/java/com/github/maracas/gilesi/confgen/CodeType.java b/ConfGen/src/main/java/com/github/gilesi/confgen/CodeType.java similarity index 79% rename from ConfGen/src/main/java/com/github/maracas/gilesi/confgen/CodeType.java rename to ConfGen/src/main/java/com/github/gilesi/confgen/CodeType.java index 5e25f986..19a08cc4 100644 --- a/ConfGen/src/main/java/com/github/maracas/gilesi/confgen/CodeType.java +++ b/ConfGen/src/main/java/com/github/gilesi/confgen/CodeType.java @@ -1,4 +1,4 @@ -package com.github.maracas.gilesi.confgen; +package com.github.gilesi.confgen; import java.util.EnumSet; diff --git a/ConfGen/src/main/java/com/github/gilesi/confgen/Main.java b/ConfGen/src/main/java/com/github/gilesi/confgen/Main.java new file mode 100644 index 00000000..a4a3d2eb --- /dev/null +++ b/ConfGen/src/main/java/com/github/gilesi/confgen/Main.java @@ -0,0 +1,182 @@ +package com.github.gilesi.confgen; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.github.gilesi.confgen.exceptions.UnsupportedJavaPrimitiveTypeException; +import com.github.gilesi.confgen.spoon.SpoonLauncherUtilities; +import com.github.gilesi.confgen.models.Class; +import com.github.gilesi.confgen.models.InstrumentationParameters; +import com.github.gilesi.confgen.models.Method; +import com.github.maracas.roseau.api.SpoonAPIExtractor; +import com.github.maracas.roseau.api.model.*; +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.DomDriver; +import spoon.Launcher; +import spoon.reflect.CtModel; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.*; + +public class Main { + private static final ObjectMapper objectMapper = new ObjectMapper() + .enable(SerializationFeature.INDENT_OUTPUT) + //.enable(SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL) + .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) + .disable(SerializationFeature.FAIL_ON_SELF_REFERENCES) + .disable(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS); + + private static String getClassNameFromFQN(String fullyQualifiedName) { + String parameterLessQualifiedName = fullyQualifiedName; + + if (fullyQualifiedName.contains("(")) { + parameterLessQualifiedName = fullyQualifiedName.split("\\(")[0]; + } + + String[] nameElements = parameterLessQualifiedName.split("\\."); + + return String.join(".", Arrays.stream(nameElements).limit(nameElements.length - 1).toArray(String[]::new)); + } + + private static String getMethodNameFromFQN(String fullyQualifiedName) { + String parameterLessQualifiedName = fullyQualifiedName; + + if (fullyQualifiedName.contains("(")) { + parameterLessQualifiedName = fullyQualifiedName.split("\\(")[0]; + } + + String[] nameElements = parameterLessQualifiedName.split("\\."); + + return nameElements[nameElements.length - 1]; + } + + private static void addToInstrumentationObject(InstrumentationParameters instrumentationParameters, String className, String methodName, String methodDescriptor) { + for (Class classToInstrument : instrumentationParameters.InstrumentedAPIClasses) { + if (classToInstrument.ClassName.equals(className)) { + for (Method methodParameter : classToInstrument.ClassMethods) { + if (methodParameter.MethodName.equals(methodName)) { + for (String descriptorToInstrument : methodParameter.MethodDescriptors) { + if (descriptorToInstrument.equals(methodDescriptor)) { + // Element is already in the config, return now + return; + } + } + + // Element doesn't have a descriptor in the config, add now + methodParameter.MethodDescriptors.add(methodDescriptor); + return; + } + } + + // Element doesn't have a method in the config, add now + Method methodParameter = new Method(); + methodParameter.MethodName = methodName; + methodParameter.MethodDescriptors = new ArrayList<>(); + methodParameter.MethodDescriptors.add(methodDescriptor); + + classToInstrument.ClassMethods.add(methodParameter); + return; + } + } + + // Element doesn't have a class in the config, add now + Class classParameter = new Class(); + classParameter.ClassName = className; + classParameter.ClassMethods = new ArrayList<>(); + classParameter.ClassDescriptors = new ArrayList<>(); + Method methodParameter = new Method(); + methodParameter.MethodName = methodName; + methodParameter.MethodDescriptors = new ArrayList<>(); + methodParameter.MethodDescriptors.add(methodDescriptor); + classParameter.ClassMethods.add(methodParameter); + + instrumentationParameters.InstrumentedAPIClasses.add(classParameter); + } + + private static void addToInstrumentationObject(InstrumentationParameters instrumentationParameters, String className, String constructorDescriptor) { + for (Class classToInstrument : instrumentationParameters.InstrumentedAPIClasses) { + if (classToInstrument.ClassName.equals(className)) { + for (String descriptorToInstrument : classToInstrument.ClassDescriptors) { + if (descriptorToInstrument.equals(constructorDescriptor)) { + // Element is already in the config, return now + return; + } + + // Element doesn't have a descriptor in the config, add now + classToInstrument.ClassDescriptors.add(constructorDescriptor); + return; + } + } + } + + // Element doesn't have a class in the config, add now + Class classParameter = new Class(); + classParameter.ClassName = className; + classParameter.ClassMethods = new ArrayList<>(); + classParameter.ClassDescriptors = new ArrayList<>(); + classParameter.ClassDescriptors.add(constructorDescriptor); + + instrumentationParameters.InstrumentedAPIClasses.add(classParameter); + } + + public static void main(String[] args) throws UnsupportedJavaPrimitiveTypeException, IOException { + String apiReportOutputLocation = args[0]; + String traceOutputLocation = args[1]; + String libraryProjectLocation = args[2]; + + Path apiReportOutputPath = Path.of(apiReportOutputLocation); + + System.out.println("Processing Library Project..."); + + Launcher libraryLauncher = SpoonLauncherUtilities.getCommonLauncherInstance(); + SpoonLauncherUtilities.applyProjectToLauncher(libraryLauncher, Path.of(libraryProjectLocation), EnumSet.of(CodeType.MAIN)); + CtModel libraryModel = libraryLauncher.buildModel(); + + // API model for libraries + API libraryApiModel = new SpoonAPIExtractor().extractAPI(libraryModel); + + InstrumentationParameters instrumentationParameters = new InstrumentationParameters(); + instrumentationParameters.InstrumentedAPIClasses = new ArrayList<>(); + instrumentationParameters.LibraryTypes = new ArrayList<>(); + instrumentationParameters.traceOutputLocation = traceOutputLocation; + + for (ClassDecl classDecl : libraryApiModel.getExportedTypes() + .filter(ClassDecl.class::isInstance) + .map(ClassDecl.class::cast).toList()) { + + for (ConstructorDecl constructor : classDecl.getConstructors()) { + String className = getClassNameFromFQN(constructor.getQualifiedName()); + String methodDescriptor = RoseauDescriptor.getDescriptor(constructor, false); + + addToInstrumentationObject(instrumentationParameters, className, methodDescriptor); + } + + for (MethodDecl method : classDecl.getAllMethods().toList()) { + String FQN = method.getQualifiedName(); + String className = getClassNameFromFQN(FQN); + String methodName = getMethodNameFromFQN(FQN); + String methodDescriptor = RoseauDescriptor.getDescriptor(method, false); + + addToInstrumentationObject(instrumentationParameters, className, methodName, methodDescriptor); + } + } + + for (TypeDecl typeDecl : libraryApiModel.getExportedTypes().toList()) { + instrumentationParameters.LibraryTypes.add(typeDecl.getQualifiedName()); + } + + BufferedWriter bufferedWriter = Files.newBufferedWriter(apiReportOutputPath, StandardOpenOption.CREATE); + + try { + String serializedJsonString = objectMapper.writeValueAsString(instrumentationParameters); + bufferedWriter.write(serializedJsonString); + } catch (Exception e) { + System.out.println(String.format("ERROR: Cannot serialize specific argument: %s", e)); + } + + bufferedWriter.close(); + } +} \ No newline at end of file diff --git a/ConfGen/src/main/java/com/github/gilesi/confgen/RoseauDescriptor.java b/ConfGen/src/main/java/com/github/gilesi/confgen/RoseauDescriptor.java new file mode 100644 index 00000000..4a193630 --- /dev/null +++ b/ConfGen/src/main/java/com/github/gilesi/confgen/RoseauDescriptor.java @@ -0,0 +1,88 @@ +package com.github.gilesi.confgen; + +import com.github.gilesi.confgen.exceptions.UnsupportedJavaPrimitiveTypeException; +import com.github.maracas.roseau.api.model.ConstructorDecl; +import com.github.maracas.roseau.api.model.MethodDecl; +import com.github.maracas.roseau.api.model.ParameterDecl; +import com.github.maracas.roseau.api.model.reference.*; + +import java.util.List; + +public class RoseauDescriptor { + public static String getDescriptor(ConstructorDecl constructorDecl, boolean handleTypeArgs) throws UnsupportedJavaPrimitiveTypeException { + StringBuilder descriptor = new StringBuilder().append('('); + for (ParameterDecl parameterType : constructorDecl.getParameters()) { + descriptor.append(getDescriptor(parameterType, handleTypeArgs)); + } + return descriptor.append(")V").toString(); + } + + public static String getDescriptor(MethodDecl methodDecl, boolean handleTypeArgs) throws UnsupportedJavaPrimitiveTypeException { + StringBuilder descriptor = new StringBuilder().append('('); + for (ParameterDecl parameterType : methodDecl.getParameters()) { + descriptor.append(getDescriptor(parameterType, handleTypeArgs)); + } + return descriptor.append(')').append(getDescriptor(methodDecl.getType(), handleTypeArgs)).toString(); + } + + public static String getDescriptor(ParameterDecl parameterDecl, boolean handleTypeArgs) throws UnsupportedJavaPrimitiveTypeException { + return getDescriptor(parameterDecl.type(), handleTypeArgs); + } + + public static String getDescriptor(ITypeReference iTypeReference, boolean handleTypeArgs) throws UnsupportedJavaPrimitiveTypeException { + switch (iTypeReference) { + case ArrayTypeReference arrayTypeReference -> { + String rest = getDescriptor(arrayTypeReference.componentType(), handleTypeArgs); + for (int i = 0; i < arrayTypeReference.dimension(); i++) { + rest = "[" + rest; + } + return rest; + } + case PrimitiveTypeReference primitiveTypeReference -> { + String name = primitiveTypeReference.getQualifiedName(); + return switch (name) { + case "int" -> "I"; + case "void" -> "V"; + case "boolean" -> "Z"; + case "byte" -> "B"; + case "char" -> "C"; + case "short" -> "S"; + case "double" -> "D"; + case "float" -> "F"; + case "long" -> "J"; + default -> throw new UnsupportedJavaPrimitiveTypeException(name); + }; + } + case TypeParameterReference ignored -> { + //String name = typeParameterReference.getQualifiedName(); + //return "L" + name.replace('.', '/') + ";"; + // That's going to be U getSmething() in the JVM, so return object. + return "Ljava/lang/Object;"; + } + case WildcardTypeReference ignored -> { + //String name = typeDecl.getQualifiedName(); + //return name; + // That's going to be U getSmething() in the JVM, so return object. + return "Ljava/lang/Object;"; + } + case TypeReference typeReference -> { + String typeArgumentString = ""; + if (handleTypeArgs) { + List typeArgs = typeReference.getTypeArguments(); + if (!typeArgs.isEmpty()) { + // Check join string here to be very sure against spec and test it + typeArgumentString = "<%s>".formatted(String.join("", typeArgs.stream().map(t -> { + try { + return getDescriptor(t, true); + } catch (UnsupportedJavaPrimitiveTypeException e) { + throw new RuntimeException(e); + } + }).toList())); + } + } + String name = typeReference.getQualifiedName(); + return "L%s%s;".formatted(name.replace('.', '/'), typeArgumentString); + } + } + } +} diff --git a/ConfGen/src/main/java/com/github/gilesi/confgen/exceptions/UnsupportedJavaPrimitiveTypeException.java b/ConfGen/src/main/java/com/github/gilesi/confgen/exceptions/UnsupportedJavaPrimitiveTypeException.java new file mode 100644 index 00000000..20908204 --- /dev/null +++ b/ConfGen/src/main/java/com/github/gilesi/confgen/exceptions/UnsupportedJavaPrimitiveTypeException.java @@ -0,0 +1,7 @@ +package com.github.gilesi.confgen.exceptions; + +public class UnsupportedJavaPrimitiveTypeException extends Exception { + public UnsupportedJavaPrimitiveTypeException(String primitive) { + super("Unsupported primitive type: %s".formatted(primitive)); + } +} diff --git a/ConfGen/src/main/java/com/github/gilesi/confgen/models/Class.java b/ConfGen/src/main/java/com/github/gilesi/confgen/models/Class.java new file mode 100644 index 00000000..939d7380 --- /dev/null +++ b/ConfGen/src/main/java/com/github/gilesi/confgen/models/Class.java @@ -0,0 +1,9 @@ +package com.github.gilesi.confgen.models; + +import java.util.List; + +public class Class { + public String ClassName; + public List ClassMethods; + public List ClassDescriptors; +} diff --git a/ConfGen/src/main/java/com/github/gilesi/confgen/models/InstrumentationParameters.java b/ConfGen/src/main/java/com/github/gilesi/confgen/models/InstrumentationParameters.java new file mode 100644 index 00000000..351419b2 --- /dev/null +++ b/ConfGen/src/main/java/com/github/gilesi/confgen/models/InstrumentationParameters.java @@ -0,0 +1,9 @@ +package com.github.gilesi.confgen.models; + +import java.util.List; + +public class InstrumentationParameters { + public List InstrumentedAPIClasses; + public List LibraryTypes; + public String traceOutputLocation; +} diff --git a/ConfGen/src/main/java/com/github/gilesi/confgen/models/Method.java b/ConfGen/src/main/java/com/github/gilesi/confgen/models/Method.java new file mode 100644 index 00000000..52202bb9 --- /dev/null +++ b/ConfGen/src/main/java/com/github/gilesi/confgen/models/Method.java @@ -0,0 +1,8 @@ +package com.github.gilesi.confgen.models; + +import java.util.List; + +public class Method { + public String MethodName; + public List MethodDescriptors; +} \ No newline at end of file diff --git a/ConfGen/src/main/java/com/github/maracas/gilesi/confgen/spoon/SpoonLauncherUtilities.java b/ConfGen/src/main/java/com/github/gilesi/confgen/spoon/SpoonLauncherUtilities.java similarity index 82% rename from ConfGen/src/main/java/com/github/maracas/gilesi/confgen/spoon/SpoonLauncherUtilities.java rename to ConfGen/src/main/java/com/github/gilesi/confgen/spoon/SpoonLauncherUtilities.java index 56d59b22..41cb0d0b 100644 --- a/ConfGen/src/main/java/com/github/maracas/gilesi/confgen/spoon/SpoonLauncherUtilities.java +++ b/ConfGen/src/main/java/com/github/gilesi/confgen/spoon/SpoonLauncherUtilities.java @@ -1,6 +1,6 @@ -package com.github.maracas.gilesi.confgen.spoon; +package com.github.gilesi.confgen.spoon; -import com.github.maracas.gilesi.confgen.CodeType; +import com.github.gilesi.confgen.CodeType; import spoon.Launcher; import spoon.MavenLauncher; import spoon.SpoonException; @@ -11,11 +11,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.security.InvalidParameterException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.List; +import java.util.*; import java.util.regex.Pattern; public class SpoonLauncherUtilities { @@ -28,6 +24,11 @@ public static Launcher getCommonLauncherInstance() { launcher.getEnvironment().setIgnoreDuplicateDeclarations(true); // Ignore files with syntax/JLS violations and proceed launcher.getEnvironment().setIgnoreSyntaxErrors(true); + // Ignore comments + launcher.getEnvironment().setCommentEnabled(false); + // Set Java version + // Note: even when using the MavenLauncher, it's sometimes not properly inferred, better be safe + launcher.getEnvironment().setComplianceLevel(17); return launcher; } @@ -38,7 +39,7 @@ private static Collection getPomProjectPaths(String mavenProject, spoon.Ma File mavenProjectFile = new File(mavenProject); if (!mavenProjectFile.exists()) { - throw new SpoonException(mavenProject + " does not exist."); + throw new SpoonException("%s does not exist.".formatted(mavenProject)); } Pattern profileFilter = Pattern.compile("^$"); @@ -53,7 +54,7 @@ private static Collection getPomProjectPaths(String mavenProject, spoon.Ma if (spoon.MavenLauncher.SOURCE_TYPE.APP_SOURCE == sourceType || spoon.MavenLauncher.SOURCE_TYPE.ALL_SOURCE == sourceType) { List sourceDirectories = model.getSourceDirectories(); for (File sourceDirectory : sourceDirectories) { - System.out.println("Detected Project MAIN Source Directory at: " + sourceDirectory); + System.out.printf("Detected Project MAIN Source Directory at: %s%n", sourceDirectory); paths.add(sourceDirectory.toPath()); } } @@ -62,7 +63,7 @@ private static Collection getPomProjectPaths(String mavenProject, spoon.Ma if (spoon.MavenLauncher.SOURCE_TYPE.TEST_SOURCE == sourceType || spoon.MavenLauncher.SOURCE_TYPE.ALL_SOURCE == sourceType) { List testSourceDirectories = model.getTestDirectories(); for (File sourceDirectory : testSourceDirectories) { - System.out.println("Detected Project TEST Source Directory at: " + sourceDirectory); + System.out.printf("Detected Project TEST Source Directory at: %s%n", sourceDirectory); paths.add(sourceDirectory.toPath()); } } @@ -75,7 +76,7 @@ private static int getPomProjectSourceComplianceLevel(String mavenProject) throw File mavenProjectFile = new File(mavenProject); if (!mavenProjectFile.exists()) { - throw new SpoonException(mavenProject + " does not exist."); + throw new SpoonException("%s does not exist.".formatted(mavenProject)); } Pattern profileFilter = Pattern.compile("^$"); @@ -93,7 +94,7 @@ public static int getProjectSourceComplianceLevel(Path location) { try { return getPomProjectSourceComplianceLevel(location.toAbsolutePath().toString()); } catch (Exception ignore) { - + System.out.println("Unable to get project source compliance level. %s".formatted(ignore)); } return 11; @@ -161,12 +162,6 @@ private static MavenLauncher.SOURCE_TYPE getSourceTypeValue(EnumSet co } } - public static Launcher getLauncherForProject(Path projectLocation, EnumSet codeTypes) { - Launcher launcher = getCommonLauncherInstance(); - applyProjectToLauncher(launcher, projectLocation, codeTypes); - return launcher; - } - public static void applyProjectToLauncher(Launcher launcher, Path projectLocation, EnumSet codeTypes) { launcher.getEnvironment().setComplianceLevel(getProjectSourceComplianceLevel(projectLocation)); @@ -179,18 +174,19 @@ public static void applyProjectToLauncher(Launcher launcher, Path projectLocatio public static Collection getProjectPaths(Path projectLocation, EnumSet codeTypes) { Collection paths = new HashSet<>(); - System.out.println("Trying to detect source directories for project: " + projectLocation + " with types: " + codeTypes); + System.out.printf("Trying to detect source directories for project: %s with types: %s%n", projectLocation, codeTypes); if (codeTypes.contains(CodeType.MAIN) || codeTypes.contains(CodeType.TEST)) { try { MavenLauncher.SOURCE_TYPE sourceType = getSourceTypeValue(codeTypes); paths.addAll(getPomProjectPaths(projectLocation.toString(), sourceType)); } catch (Exception ignored) { + System.out.println("Unable to get project code %s".formatted(ignored)); System.out.println("WARNING: Falling back to manual detection of project source paths because no maven pom could be parsed for the passed project"); if (codeTypes.contains(CodeType.MAIN)) { Path mainPath = getPossibleMainPath(projectLocation); if (mainPath != null) { - System.out.println("Detected Project MAIN Source Directory at: " + mainPath); + System.out.printf("Detected Project MAIN Source Directory at: %s%n", mainPath); paths.add(mainPath); } } @@ -198,7 +194,7 @@ public static Collection getProjectPaths(Path projectLocation, EnumSet getProjectPaths(Path projectLocation, EnumSet getProjectPaths(Path projectLocation, EnumSet ClassMethodsOrConstructors; -} diff --git a/ConfGen/src/main/java/com/github/maracas/gilesi/confgen/InstrumentationParameters.java b/ConfGen/src/main/java/com/github/maracas/gilesi/confgen/InstrumentationParameters.java deleted file mode 100644 index 7b849ad5..00000000 --- a/ConfGen/src/main/java/com/github/maracas/gilesi/confgen/InstrumentationParameters.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.maracas.gilesi.confgen; - -import java.util.List; - -public class InstrumentationParameters { - public List ClassesToInstrument; -} diff --git a/ConfGen/src/main/java/com/github/maracas/gilesi/confgen/Main.java b/ConfGen/src/main/java/com/github/maracas/gilesi/confgen/Main.java deleted file mode 100644 index f59ed04a..00000000 --- a/ConfGen/src/main/java/com/github/maracas/gilesi/confgen/Main.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.github.maracas.gilesi.confgen; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.github.maracas.roseau.api.SpoonAPIExtractor; -import com.github.maracas.roseau.api.model.API; -import com.github.maracas.roseau.api.model.ClassDecl; -import com.github.maracas.roseau.api.model.ConstructorDecl; -import com.github.maracas.roseau.api.model.MethodDecl; -import com.github.maracas.gilesi.confgen.spoon.SpoonLauncherUtilities; -import spoon.Launcher; -import spoon.reflect.CtModel; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EnumSet; -import java.util.List; - -public class Main { - private static String getClassNameFromConstructorFQN(String constructorFQN) { - return constructorFQN.split("\\(")[0]; - } - - private static String getClassNameFromFQN(String constructorFQN) { - String[] constructClassName = constructorFQN.split("\\(")[0].split("\\."); - return String.join(".", Arrays.stream(constructClassName).limit(constructClassName.length - 1).toArray(String[]::new)); - } - - private static String getMethodNameFromFQN(String constructorFQN) { - String[] constructClassName = constructorFQN.split("\\(")[0].split("\\."); - return constructClassName[constructClassName.length - 1]; - } - - private static void addToInstrumentationObject(InstrumentationParameters instrumentationParameters, String className, String methodName) { - // If we already have a class here, don't add a new one - if (instrumentationParameters.ClassesToInstrument.stream().anyMatch(t -> t.ClassName.equals(className))) { - // Find the class - for (InstrumentationParameter classToInstrument : instrumentationParameters.ClassesToInstrument) { - if (classToInstrument.ClassName.equals(className)) { - // Only add if missing - if (classToInstrument.ClassMethodsOrConstructors.stream().noneMatch(t -> t.equals(methodName))) { - classToInstrument.ClassMethodsOrConstructors.add(methodName); - } - - // We found what we wanted so break now. - break; - } - } - } else { - // Otherwise, add a brand new one now. - InstrumentationParameter instrumentationParameter = new InstrumentationParameter(); - instrumentationParameter.ClassName = className; - instrumentationParameter.ClassMethodsOrConstructors = new ArrayList<>(); - instrumentationParameter.ClassMethodsOrConstructors.add(methodName); - - instrumentationParameters.ClassesToInstrument.add(instrumentationParameter); - } - } - - public static void main(String[] args) throws Exception { - String apiReportOutputLocation = args[0]; - Path apiReportOutputPath = Path.of(apiReportOutputLocation); - - System.out.println("Processing Main Project..."); - - Launcher mainLauncher = SpoonLauncherUtilities.getCommonLauncherInstance(); - SpoonLauncherUtilities.applyProjectToLauncher(mainLauncher, Path.of(args[1]), EnumSet.of(CodeType.MAIN)); - CtModel mainModel = mainLauncher.buildModel(); - - // API model for libraries - API mainProjectApiModel = new SpoonAPIExtractor(mainModel).extractAPI(); - - InstrumentationParameters instrumentationParameters = new InstrumentationParameters(); - instrumentationParameters.ClassesToInstrument = new ArrayList<>(); - - for (ClassDecl classDecl : mainProjectApiModel.getExportedTypes().stream() - .filter(ClassDecl.class::isInstance) - .map(ClassDecl.class::cast).toList()) { - - for (ConstructorDecl constructor : classDecl.getConstructors()) { - String className = getClassNameFromConstructorFQN(constructor.getQualifiedName()); - String methodName = ""; - - addToInstrumentationObject(instrumentationParameters, className, methodName); - } - - for (MethodDecl method : classDecl.getMethods()) { - String FQN = method.getQualifiedName(); - String className = getClassNameFromFQN(FQN); - String methodName = getMethodNameFromFQN(FQN); - - addToInstrumentationObject(instrumentationParameters, className, methodName); - } - } - - System.out.println("Processing Test Project..."); - - List testMethods = new ArrayList<>(); - - Launcher launcher = SpoonLauncherUtilities.getCommonLauncherInstance(); - SpoonLauncherUtilities.applyProjectToLauncher(launcher, Path.of(args[2]), EnumSet.of(CodeType.TEST)); - CtModel model = launcher.buildModel(); - - model.getAllPackages().forEach(pkg -> pkg.getTypes().forEach(type -> type.getMethods() - .forEach(method -> method.getAnnotations().stream() - .filter(annotation -> annotation.getName().contains("Test")) - .map(annotation -> type.getQualifiedName() + "." + method.getSignature()) - .forEach(testMethods::add)))); - - for (String testMethod : testMethods) { - String className = getClassNameFromFQN(testMethod); - String methodName = getMethodNameFromFQN(testMethod); - - addToInstrumentationObject(instrumentationParameters, className, methodName); - } - - ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); - String apiJson = objectMapper.writeValueAsString(instrumentationParameters); - Files.writeString(apiReportOutputPath, apiJson, StandardOpenOption.CREATE); - } -} \ No newline at end of file diff --git a/ConfGen/src/main/java/com/github/maracas/gilesi/confgen/models/UsagePosition.java b/ConfGen/src/main/java/com/github/maracas/gilesi/confgen/models/UsagePosition.java deleted file mode 100644 index 6874114b..00000000 --- a/ConfGen/src/main/java/com/github/maracas/gilesi/confgen/models/UsagePosition.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.github.maracas.gilesi.confgen.models; - -import spoon.reflect.cu.SourcePosition; - -import java.nio.file.Path; - -public record UsagePosition(String path, int line, int column, int endLine, int endColumn) { - public static UsagePosition getFromSourcePosition(SourcePosition position) { - String path = ""; - int line = -1; - int column = -1; - int endLine = -1; - int endColumn = -1; - - if (position != null && position.isValidPosition()) { - if (position.getFile() != null) { - path = position.getFile().getAbsolutePath(); - } - - line = position.getLine(); - column = position.getColumn(); - endLine = position.getEndLine(); - endColumn = position.getEndColumn(); - } - - return new UsagePosition(path, line, column, endLine, endColumn); - } - - public String getPositionAsString(Path projectLocation) { - return path().toLowerCase().replace(projectLocation.toAbsolutePath().toString().toLowerCase(), "").replace('\\', '/') + "(" + line() + ":" + column() + ")"; - } - - public String getEndPositionAsString(Path projectLocation) { - return path().toLowerCase().replace(projectLocation.toAbsolutePath().toString().toLowerCase(), "").replace('\\', '/') + "(" + endLine() + ":" + endColumn() + ")"; - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/gilesi/confgen/spoon/visitors/SpoonFullyQualifiedNameExtractor.java b/ConfGen/src/main/java/com/github/maracas/gilesi/confgen/spoon/visitors/SpoonFullyQualifiedNameExtractor.java deleted file mode 100644 index 95be976e..00000000 --- a/ConfGen/src/main/java/com/github/maracas/gilesi/confgen/spoon/visitors/SpoonFullyQualifiedNameExtractor.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.github.maracas.gilesi.confgen.spoon.visitors; - -import spoon.reflect.code.CtConstructorCall; -import spoon.reflect.code.CtFieldRead; -import spoon.reflect.code.CtFieldWrite; -import spoon.reflect.code.CtInvocation; -import spoon.reflect.declaration.*; -import spoon.reflect.reference.CtExecutableReference; -import spoon.reflect.reference.CtFieldReference; -import spoon.reflect.reference.CtTypeReference; - -public class SpoonFullyQualifiedNameExtractor { - public static String getFullyQualifiedName(CtField ctField) { - return getFullyQualifiedName(ctField.getDeclaringType()) + "." + ctField.getSimpleName(); - } - - public static String getFullyQualifiedName(CtInvocation ctInvocation) { - return getFullyQualifiedName(ctInvocation.getExecutable()); - } - - public static String getFullyQualifiedName(CtConstructorCall ctConstructorCall) { - return getFullyQualifiedName(ctConstructorCall.getExecutable()); - } - - public static String getFullyQualifiedName(CtExecutableReference ctExecutableReference) { - if (ctExecutableReference.isConstructor()) { - return ctExecutableReference.getSignature(); - } else { - CtTypeReference typeReference = ctExecutableReference.getDeclaringType(); - return typeReference.getQualifiedName() + "." + ctExecutableReference.getSignature(); - } - } - - public static String getFullyQualifiedName(CtFieldRead ctFieldRead) { - return getFullyQualifiedName(ctFieldRead.getVariable()); - } - - public static String getFullyQualifiedName(CtFieldWrite ctFieldWrite) { - return getFullyQualifiedName(ctFieldWrite.getVariable()); - } - - public static String getFullyQualifiedName(CtFieldReference ctFieldReference) { - return ctFieldReference.getQualifiedName(); - } - - public static String getFullyQualifiedName(CtTypeReference ctTypeReference) { - return ctTypeReference.getQualifiedName(); - } - - public static String getFullyQualifiedName(CtMethod ctMethod) { - CtType declaringType = ctMethod.getDeclaringType(); - return declaringType.getQualifiedName() + "." + ctMethod.getSignature(); - } - - public static String getFullyQualifiedName(CtConstructor ctConstructor) { - return ctConstructor.getSignature(); - } - - public static String getFullyQualifiedName(CtType ctType) { - return ctType.getQualifiedName(); - } -} \ No newline at end of file diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/Roseau.java b/ConfGen/src/main/java/com/github/maracas/roseau/Roseau.java deleted file mode 100644 index d9cbd7e7..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/Roseau.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.github.maracas.roseau; - -import com.github.maracas.roseau.api.APIExtractor; -import com.github.maracas.roseau.api.SpoonAPIExtractor; -import com.github.maracas.roseau.api.model.API; -import com.github.maracas.roseau.diff.APIDiff; -import com.github.maracas.roseau.diff.changes.BreakingChange; -import com.google.common.base.Stopwatch; -import spoon.reflect.CtModel; - -import java.io.FileWriter; -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; -import java.util.stream.Collectors; - -/** - * The `roseau` class is the main entry point of the project. - */ -final class Roseau { - private static final int SPOON_TIMEOUT = 60; - - private Roseau() { - - } - - public static void main(String[] args) { - try (FileWriter writer = new FileWriter("durations_report.csv")) { - writer.write("Task,Duration\n"); - - Stopwatch sw = Stopwatch.createStarted(); - - // Spoon parsing - CtModel m1 = SpoonAPIExtractor.buildModel(Path.of(args[0]), SPOON_TIMEOUT) - .orElseThrow(() -> new RuntimeException("Couldn't build in < 60s")); - CtModel m2 = SpoonAPIExtractor.buildModel(Path.of(args[1]), SPOON_TIMEOUT) - .orElseThrow(() -> new RuntimeException("Couldn't build in < 60s")); - - writer.write("Spoon model building," + sw.elapsed().toMillis() + "\n"); - System.out.println("Spoon model building: " + sw.elapsed().toSeconds()); - sw.reset(); - sw.start(); - - // API extraction - APIExtractor extractor1 = new SpoonAPIExtractor(m1); - APIExtractor extractor2 = new SpoonAPIExtractor(m2); - API apiV1 = extractor1.extractAPI(); - API apiV2 = extractor2.extractAPI(); - - writer.write("API extraction," + sw.elapsed().toMillis() + "\n"); - System.out.println("API extraction: " + sw.elapsed().toSeconds()); - sw.reset(); - sw.start(); - - // Type resolution - apiV1.resolve(); - apiV2.resolve(); - System.out.println("Type resolution: " + sw.elapsed().toSeconds()); - sw.reset(); - sw.start(); - - // API serialization - apiV1.writeJson(Path.of("api-v1.json")); - apiV2.writeJson(Path.of("api-v2.json")); - - System.out.println("API serialization: " + sw.elapsed().toSeconds()); - sw.reset(); - sw.start(); - - // API diff - APIDiff diff = new APIDiff(apiV1, apiV2); - List bcs = diff.diff(); - - writer.write("DELTA model," + sw.elapsed().toMillis() + "\n"); - System.out.println("API diff: " + sw.elapsed().toSeconds()); - - diff.breakingChangesReport(); - System.out.println(bcs.stream().map(Object::toString).collect(Collectors.joining("\n"))); - } catch (IOException e) { - e.printStackTrace(); - System.exit(1); - } - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/APIExtractor.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/APIExtractor.java deleted file mode 100644 index 14c23416..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/APIExtractor.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.maracas.roseau.api; - -import com.github.maracas.roseau.api.model.API; - -public interface APIExtractor { - API extractAPI(); -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/JarAPIExtractor.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/JarAPIExtractor.java deleted file mode 100644 index a5c2b08a..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/JarAPIExtractor.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.github.maracas.roseau.api; - -import com.github.maracas.roseau.api.model.API; - -public class JarAPIExtractor implements APIExtractor { - @Override - public API extractAPI() { - throw new UnsupportedOperationException("Not yet implemented"); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/JavaParserAPIExtractor.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/JavaParserAPIExtractor.java deleted file mode 100644 index a3f319b6..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/JavaParserAPIExtractor.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.github.maracas.roseau.api; - -import com.github.maracas.roseau.api.model.API; - -/** - * Not fully implemented yet; half of the info isn't extracted - */ -public class JavaParserAPIExtractor implements APIExtractor { - public API extractAPI() { - throw new UnsupportedOperationException("Not yet implemented"); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/SpoonAPIExtractor.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/SpoonAPIExtractor.java deleted file mode 100644 index 732cf555..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/SpoonAPIExtractor.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.github.maracas.roseau.api; - -import com.github.maracas.roseau.api.model.API; -import com.github.maracas.roseau.api.model.SpoonAPIFactory; -import com.github.maracas.roseau.api.model.TypeDecl; -import spoon.Launcher; -import spoon.MavenLauncher; -import spoon.SpoonException; -import spoon.reflect.CtModel; -import spoon.reflect.declaration.CtPackage; -import spoon.reflect.declaration.CtType; -import spoon.reflect.factory.TypeFactory; -import spoon.support.compiler.SpoonProgress; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.stream.Stream; - -/** - * This class represents roseau's API extraction tool. - *
- * Types are resolved within the universe of API types (exported or not). - * We don't know anything about the outside world. - */ -public class SpoonAPIExtractor implements APIExtractor { - private static final int JAVA_VERSION = 17; - private final CtModel model; - - /** - * Constructs an APIExtractor instance with the provided CtModel to extract its information. - */ - public SpoonAPIExtractor(CtModel model) { - this.model = Objects.requireNonNull(model); - } - - public static Optional buildModel(Path location, int timeoutSeconds) { - CompletableFuture> future = CompletableFuture.supplyAsync(() -> { - Launcher launcher = launcherFor(location); - return Optional.of(launcher.buildModel()); - }); - - try { - return future.get(timeoutSeconds, TimeUnit.SECONDS); - } catch (TimeoutException | ExecutionException e) { - return Optional.empty(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return Optional.empty(); - } - } - - private static Launcher launcherFor(Path location) { - Launcher launcher; - - if (Files.exists(location.resolve("pom.xml"))) - launcher = new MavenLauncher(location.toString(), MavenLauncher.SOURCE_TYPE.APP_SOURCE); - else { - launcher = new Launcher(); - launcher.addInputResource(location.toString()); - } - - // Ignore missing types/classpath related errors - launcher.getEnvironment().setNoClasspath(true); - // Proceed even if we find the same type twice; affects the precision of the result - launcher.getEnvironment().setIgnoreDuplicateDeclarations(true); - // Ignore files with syntax/JLS violations and proceed - launcher.getEnvironment().setIgnoreSyntaxErrors(true); - // Ignore comments - launcher.getEnvironment().setCommentEnabled(false); - // Set Java version - // Note: even when using the MavenLauncher, it's sometimes not properly inferred, better be safe - launcher.getEnvironment().setComplianceLevel(JAVA_VERSION); - - // Interruptible launcher: this is dirty. - // Spoon's compiler does two lengthy things: compile units with JDTs, - // turn these units into Spoon's model. In both cases it iterates - // over many CUs and reports progress. - // A simple dirty way to make the process interruptible is to look for - // interruptions when Spoon reports progress and throw an unchecked - // exception. The method is called very often, so we're likely to - // react quickly to external interruptions. - launcher.getEnvironment().setSpoonProgress(new SpoonProgress() { - @Override - public void step(Process process, String task, int taskId, int nbTask) { - if (Thread.interrupted()) { - throw new SpoonException("Process interrupted"); - } - } - }); - - return launcher; - } - - /** - * Extracts the library's (model's) structured API. - * - * @return Library's (model's) API. - */ - public API extractAPI() { - TypeFactory typeFactory = model.getRootPackage().getFactory().Type(); - SpoonAPIFactory factory = new SpoonAPIFactory(typeFactory); - - List allTypes = - model.getAllPackages().stream() - .flatMap(p -> getAllTypes(p).stream().map(factory::convertCtType)) - .toList(); - - return new API(allTypes, factory); - } - - // Returns all types within a package - private List> getAllTypes(CtPackage pkg) { - return pkg.getTypes().stream() - .flatMap(type -> Stream.concat( - Stream.of(type), - getNestedTypes(type).stream() - )) - .toList(); - } - - // Returns (recursively) nested types within a type - private List> getNestedTypes(CtType type) { - return type.getNestedTypes().stream() - .flatMap(nestedType -> Stream.concat( - Stream.of(nestedType), - getNestedTypes(nestedType).stream() - )) - .toList(); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/TypeResolver.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/TypeResolver.java deleted file mode 100644 index a90ff7ee..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/TypeResolver.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.github.maracas.roseau.api; - -import com.github.maracas.roseau.api.model.API; -import com.github.maracas.roseau.api.model.SpoonAPIFactory; -import com.github.maracas.roseau.api.model.TypeDecl; -import com.github.maracas.roseau.api.model.reference.TypeReference; -import com.github.maracas.roseau.visit.AbstractAPIVisitor; -import com.github.maracas.roseau.visit.Visit; -import spoon.reflect.factory.TypeFactory; - -public class TypeResolver extends AbstractAPIVisitor { - private final API api; - private final SpoonAPIFactory factory; - - public TypeResolver(API api, SpoonAPIFactory factory) { - this.api = api; - this.factory = factory; - } - - @Override - public Visit typeReference(TypeReference it) { - return () -> { - it.setFactory(factory); - api.findType(it.getQualifiedName()).ifPresent(t -> it.setResolvedApiType((U) t)); - }; - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/API.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/API.java deleted file mode 100644 index 5e4466c1..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/API.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.github.maracas.roseau.api.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.fasterxml.jackson.module.paranamer.ParanamerModule; -import com.github.maracas.roseau.api.TypeResolver; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Function; -import java.util.stream.Collectors; - -/** - * Represents the API of a library containing all the types, each of which may have methods, fields, constructors, and - * more information about the type. This class encapsulates a list of {@link TypeDecl} instances, each representing - * distinct types identified by their respective qualified names. - */ -public final class API { - private final Map types; - @JsonIgnore - private final SpoonAPIFactory factory; - - @JsonCreator - public API(@JsonProperty("allTypes") List types, SpoonAPIFactory factory) { - this.types = types.stream() - .collect(Collectors.toMap( - Symbol::getQualifiedName, - Function.identity() - )); - this.factory = factory; - } - - public void resolve() { - // Within-library type resolution - new TypeResolver(this, factory).$(this).visit(); - } - - public List getAllTypes() { - return types.values().stream().toList(); - } - - public SpoonAPIFactory getFactory() { - return factory; - } - - @JsonIgnore - public List getAllClasses() { - return types.values().stream() - .filter(ClassDecl.class::isInstance) - .map(ClassDecl.class::cast) - .toList(); - } - - @JsonIgnore - public List getExportedTypes() { - return getAllTypes().stream() - .filter(Symbol::isExported) - .toList(); - } - - public Optional getExportedType(String qualifiedName) { - return getExportedTypes().stream() - .filter(t -> Objects.equals(qualifiedName, t.getQualifiedName())) - .findFirst(); - } - - public Optional findType(String qualifiedName) { - return Optional.ofNullable(types.get(qualifiedName)); - } - - public Optional findClass(String qualifiedName) { - return getAllClasses().stream() - .filter(cls -> cls.getQualifiedName().equals(qualifiedName)) - .findFirst(); - } - - public void writeJson(Path jsonFile) throws IOException { - ObjectMapper mapper = new ObjectMapper(); - mapper.registerModule(new Jdk8Module()); - mapper.writerWithDefaultPrettyPrinter().writeValue(jsonFile.toFile(), this); - } - - public static API fromJson(Path jsonFile) throws IOException { - ObjectMapper mapper = new ObjectMapper(); - mapper.registerModule(new Jdk8Module()); // For Optional<> - mapper.registerModule(new ParanamerModule()); // For @JsonCreator - return mapper.readValue(jsonFile.toFile(), API.class); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - - for (TypeDecl typeDeclaration : types.values()) { - builder.append(typeDeclaration).append("\n"); - builder.append(" =========================\n\n"); - } - - return builder.toString(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - API api = (API) o; - return Objects.equals(types, api.types); - } - - @Override - public int hashCode() { - return Objects.hash(types); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/AccessModifier.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/AccessModifier.java deleted file mode 100644 index 13359d15..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/AccessModifier.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.github.maracas.roseau.api.model; - -/** - * Enumerates the possible access modifiers in Java. - */ -public enum AccessModifier { - /** - * Private access modifier - */ - PRIVATE, - - /** - * Protected access modifier - */ - PROTECTED, - - /** - * Public access modifier - */ - PUBLIC, - - /** - * Package-private access modifier - */ - PACKAGE_PRIVATE; - - @Override - public String toString() { - return name().toLowerCase(); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/AnnotationDecl.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/AnnotationDecl.java deleted file mode 100644 index 66bcea9b..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/AnnotationDecl.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.github.maracas.roseau.api.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.github.maracas.roseau.api.model.reference.TypeReference; - -import java.util.Collections; -import java.util.List; - -public final class AnnotationDecl extends TypeDecl { - @JsonCreator - public AnnotationDecl(String qualifiedName, AccessModifier visibility, List modifiers, - SourceLocation location, List fields, List methods, - TypeReference enclosingType) { - super(qualifiedName, visibility, modifiers, location, Collections.emptyList(), - Collections.emptyList(), fields, methods, enclosingType); - } - - @Override - public boolean isAnnotation() { - return true; - } - - @Override - public String toString() { - return """ - annotation %s [%s] - %s - %s - """.formatted(qualifiedName, visibility, fields, methods); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/ClassDecl.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/ClassDecl.java deleted file mode 100644 index 6057df3f..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/ClassDecl.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.github.maracas.roseau.api.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.github.maracas.roseau.api.model.reference.TypeReference; - -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Stream; - -public sealed class ClassDecl extends TypeDecl permits RecordDecl, EnumDecl { - /** - * The superclass as a type reference (null if there isn't any). - */ - protected final TypeReference superClass; - - /** - * List of constructors declared within the class. - */ - protected final List constructors; - - @JsonCreator - public ClassDecl(String qualifiedName, AccessModifier visibility, List modifiers, SourceLocation location, - List> implementedInterfaces, - List formalTypeParameters, List fields, List methods, - TypeReference enclosingType, - TypeReference superClass, List constructors) { - super(qualifiedName, visibility, modifiers, location, - implementedInterfaces, formalTypeParameters, fields, methods, enclosingType); - this.superClass = superClass; - this.constructors = constructors; - } - - @Override - public boolean isClass() { - return true; - } - - @Override - protected List getSuperMethods() { - return Stream.concat( - superClass != null - ? superClass.getResolvedApiType().map(cls -> cls.getAllMethods().stream()).orElse(Stream.empty()) - : Stream.empty(), - super.getSuperMethods().stream() - ).toList(); - } - - @Override - public List getAllFields() { - return Stream.concat( - superClass != null - ? superClass.getResolvedApiType().map(cls -> cls.getAllFields().stream()).orElse(Stream.empty()) - : Stream.empty(), - super.getAllFields().stream() - ).toList(); - } - - @Override - public boolean isCheckedException() { - return getAllSuperClasses().stream().anyMatch(cls -> "java.lang.Exception".equals(cls.getQualifiedName())) - && getAllSuperClasses().stream().noneMatch(cls -> "java.lang.RuntimeException".equals(cls.getQualifiedName())); - } - - @Override - public boolean isEffectivelyFinal() { - // A class without a subclass-accessible constructor cannot be extended - // If the class had a default constructor, it would be there - return super.isEffectivelyFinal() || constructors.isEmpty(); - } - - @Override - public List> getAllSuperTypes() { - return Stream.concat( - super.getAllSuperTypes().stream(), - getAllSuperClasses().stream() - ).toList(); - } - - public Optional> getSuperClass() { - return Optional.ofNullable(superClass); - } - - @JsonIgnore - public List> getAllSuperClasses() { - return superClass != null - ? Stream.concat( - Stream.of(superClass), - superClass.getResolvedApiType().map(c -> c.getAllSuperClasses().stream()).orElse(Stream.empty()) - ).toList() - : Collections.emptyList(); - } - - @Override - public List> getAllImplementedInterfaces() { - return Stream.concat( - super.getAllImplementedInterfaces().stream(), - superClass != null - ? superClass.getResolvedApiType().map(cls -> cls.getAllImplementedInterfaces().stream()).orElse(Stream.empty()) - : Stream.empty() - ).distinct().toList(); - } - - public List getConstructors() { - return constructors; - } - - @Override - public String toString() { - return """ - class %s [%s] (%s) - %s - %s - """.formatted(qualifiedName, visibility, enclosingType, fields, methods); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - ClassDecl classDecl = (ClassDecl) o; - return Objects.equals(superClass, classDecl.superClass) && Objects.equals(constructors, classDecl.constructors); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), superClass, constructors); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/ConstructorDecl.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/ConstructorDecl.java deleted file mode 100644 index 328017da..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/ConstructorDecl.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.github.maracas.roseau.api.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.github.maracas.roseau.api.model.reference.ITypeReference; -import com.github.maracas.roseau.api.model.reference.TypeReference; - -import java.util.List; - -/** - * Represents a constructor declaration within a Java type. This class extends the {@link ExecutableDecl} class and - * contains information about the constructor's parameters, return type, class, and more. - */ -public final class ConstructorDecl extends ExecutableDecl { - @JsonCreator - public ConstructorDecl(String qualifiedName, AccessModifier visibility, List modifiers, - SourceLocation location, TypeReference containingType, ITypeReference type, - List parameters, List formalTypeParameters, - List> thrownExceptions) { - super(qualifiedName, visibility, modifiers, location, containingType, type, - parameters, formalTypeParameters, thrownExceptions); - } - - /** - * Generates a string representation of the ConstructorDeclaration. - * - * @return A formatted string containing the constructor's qualifiedName, type, return type, parameter types, - * visibility, modifiers, exceptions, and position. - */ - @Override - public String toString() { - return "constructor %s [%s] (%s)".formatted(qualifiedName, visibility, location); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/EnumDecl.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/EnumDecl.java deleted file mode 100644 index 9c645a13..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/EnumDecl.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.github.maracas.roseau.api.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.github.maracas.roseau.api.model.reference.TypeReference; - -import java.util.Collections; -import java.util.List; - -public final class EnumDecl extends ClassDecl { - @JsonCreator - public EnumDecl(String qualifiedName, AccessModifier visibility, List modifiers, SourceLocation location, - List> implementedInterfaces, List fields, - List methods, TypeReference enclosingType, List constructors) { - super(qualifiedName, visibility, modifiers, location, implementedInterfaces, Collections.emptyList(), - fields, methods, enclosingType, null, constructors); - } - - @Override - public boolean isEnum() { - return true; - } - - @Override - public String toString() { - return """ - enum %s [%s] - %s - %s - """.formatted(qualifiedName, visibility, fields, methods); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/ExecutableDecl.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/ExecutableDecl.java deleted file mode 100644 index 755b644a..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/ExecutableDecl.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.github.maracas.roseau.api.model; - -import com.github.maracas.roseau.api.model.reference.ITypeReference; -import com.github.maracas.roseau.api.model.reference.TypeReference; - -import java.util.List; -import java.util.Objects; - -public abstract sealed class ExecutableDecl extends TypeMemberDecl permits MethodDecl, ConstructorDecl { - /** - * List of the executable's parameter types. - */ - protected final List parameters; - - /** - * List of the executable's formal type parameters. - */ - protected final List formalTypeParameters; - - /** - * List of exceptions thrown by the executable. - */ - protected final List> thrownExceptions; - - protected ExecutableDecl(String qualifiedName, AccessModifier visibility, List modifiers, - SourceLocation location, TypeReference containingType, ITypeReference type, - List parameters, List formalTypeParameters, - List> thrownExceptions) { - super(qualifiedName, visibility, modifiers, location, containingType, type); - this.parameters = parameters; - this.formalTypeParameters = formalTypeParameters; - this.thrownExceptions = thrownExceptions; - } - - public boolean hasSameSignature(ExecutableDecl other) { - return hasSignature(other.getSimpleName(), - other.getParameters().stream().map(ParameterDecl::type).toList(), - !other.getParameters().isEmpty() && other.getParameters().getLast().isVarargs()); - } - - boolean hasSignature(String simpleName, List parameterTypes, boolean varargs) { - if (!getSimpleName().equals(simpleName)) - return false; - - if (parameters.size() != parameterTypes.size()) - return false; - - if (varargs && (parameters.isEmpty() || !parameters.getLast().isVarargs())) - return false; - - for (int i = 0; i < parameterTypes.size(); i++) { - ITypeReference otherParameter = parameterTypes.get(i); - ITypeReference thisParameter = parameters.get(i).type(); - - if (!otherParameter.equals(thisParameter)) - return false; - } - - return true; - } - - /** - * We assume that input source code compiles, so we won't have two methods - * with same name and parameters but different return types. - * Executable do not overload themselves. - */ - public boolean isOverloading(ExecutableDecl other) { - return getSimpleName().equals(other.getSimpleName()) - && !hasSameSignature(other) - && containingType.isSameHierarchy(other.getContainingType()); - } - - /** - * Executables override themselves - */ - public boolean isOverriding(ExecutableDecl other) { - return hasSameSignature(other) - && containingType.isSubtypeOf(other.getContainingType()); - } - - /** - * Retrieves the list of parameters - * - * @return List of parameters - */ - public List getParameters() { - return parameters; - } - - /** - * Retrieves the executable's formal type parameters. - * - * @return List of formal type parameters - */ - public List getFormalTypeParameters() { - return formalTypeParameters; - } - - @Override - public String getSimpleName() { - return getQualifiedName().substring(getQualifiedName().lastIndexOf('.') + 1); - } - - /** - * Retrieves the list of exceptions thrown by the executable. - * - * @return List of exceptions thrown by the executable - */ - public List> getThrownExceptions() { - return thrownExceptions; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - ExecutableDecl other = (ExecutableDecl) o; - return Objects.equals(parameters, other.parameters) - && Objects.equals(formalTypeParameters, other.formalTypeParameters) - && Objects.equals(thrownExceptions, other.thrownExceptions); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), parameters, formalTypeParameters, thrownExceptions); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/FieldDecl.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/FieldDecl.java deleted file mode 100644 index be54d9ac..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/FieldDecl.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.github.maracas.roseau.api.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.github.maracas.roseau.api.model.reference.ITypeReference; -import com.github.maracas.roseau.api.model.reference.TypeReference; - -import java.util.List; - -/** - * Represents a field declaration in a Java type. This class extends the {@link Symbol} class and contains information - * about the field's data type and the {@link TypeDecl} to which it belongs. - */ -public final class FieldDecl extends TypeMemberDecl { - @JsonCreator - public FieldDecl(String qualifiedName, AccessModifier visibility, List modifiers, SourceLocation location, - TypeReference containingType, ITypeReference type) { - super(qualifiedName, visibility, modifiers, location, containingType, type); - } - - @Override - public String getSimpleName() { - return qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1); - } - - /** - * Generates a string representation of the FieldDeclaration. - * - * @return A formatted string containing the field's qualifiedName, data type, type, visibility, - * modifiers, and position. - */ - @Override - public String toString() { - return "field %s [%s] [%s]".formatted(qualifiedName, visibility, type); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/FormalTypeParameter.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/FormalTypeParameter.java deleted file mode 100644 index 1576db69..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/FormalTypeParameter.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.github.maracas.roseau.api.model; - -import com.github.maracas.roseau.api.model.reference.ITypeReference; - -import java.util.List; - -public record FormalTypeParameter( - String name, - List bounds -) { - -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/InterfaceDecl.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/InterfaceDecl.java deleted file mode 100644 index e324c0db..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/InterfaceDecl.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.github.maracas.roseau.api.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.github.maracas.roseau.api.model.reference.TypeReference; - -import java.util.List; - -public final class InterfaceDecl extends TypeDecl { - @JsonCreator - public InterfaceDecl(String qualifiedName, AccessModifier visibility, List modifiers, - SourceLocation location, List> implementedInterfaces, - List formalTypeParameters, List fields, - List methods, TypeReference enclosingType) { - super(qualifiedName, visibility, modifiers, location, implementedInterfaces, formalTypeParameters, - fields, methods, enclosingType); - } - - @Override - public boolean isInterface() { - return true; - } - - @Override - public String toString() { - return """ - interface %s [%s] - %s - %s - """.formatted(qualifiedName, visibility, fields, methods); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/MethodDecl.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/MethodDecl.java deleted file mode 100644 index 87d9120a..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/MethodDecl.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.github.maracas.roseau.api.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.github.maracas.roseau.api.model.reference.ITypeReference; -import com.github.maracas.roseau.api.model.reference.TypeReference; - -import java.util.List; -import java.util.stream.Collectors; - -/** - * Represents a method declaration within a Java type. - * This class extends the {@link ExecutableDecl} class and complements it with method-specific information - */ -public final class MethodDecl extends ExecutableDecl { - @JsonCreator - public MethodDecl(String qualifiedName, AccessModifier visibility, List modifiers, SourceLocation location, - TypeReference containingType, ITypeReference type, List parameters, - List formalTypeParameters, List> thrownExceptions) { - super(qualifiedName, visibility, modifiers, location, containingType, type, parameters, - formalTypeParameters, thrownExceptions); - } - - /** - * Checks if the method is a default method. - * - * @return True if the method is a default method, false otherwise - */ - @JsonIgnore - public boolean isDefault() { - return modifiers.contains(Modifier.DEFAULT); - } - - @JsonIgnore - public boolean isAbstract() { - return modifiers.contains(Modifier.ABSTRACT); - } - - @JsonIgnore - public boolean isNative() { - return modifiers.contains(Modifier.NATIVE); - } - - @JsonIgnore - public boolean isStrictFp() { - return modifiers.contains(Modifier.STRICTFP); - } - - /** - * Generates a string representation of the MethodDeclaration. - * - * @return A formatted string containing the method's qualifiedName, return type, parameter types, - * visibility, modifiers, type, exceptions, and position. - */ - @Override - public String toString() { - return "%s %s %s %s(%s)".formatted( - modifiers.stream().map(Object::toString).collect(Collectors.joining(", ")), visibility, type, getSimpleName(), - parameters.stream().map(Object::toString).collect(Collectors.joining(", "))); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/Modifier.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/Modifier.java deleted file mode 100644 index 4da78976..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/Modifier.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.github.maracas.roseau.api.model; - -/** - * Enumerates the possible non-access modifiers. - */ -public enum Modifier { - /** - * Represents the "static" modifier - */ - STATIC, - - /** - * Represents the "final" modifier - */ - FINAL, - - /** - * Represents the "abstract" modifier - */ - ABSTRACT, - - /** - * Represents the "synchronized" modifier - */ - SYNCHRONIZED, - - /** - * Represents the "volatile" modifier - */ - VOLATILE, - - /** - * Represents the "transient" modifier - */ - TRANSIENT, - - /** - * Represents the "native" modifier - */ - NATIVE, - - /** - * Represents the "strictfp" modifier - */ - STRICTFP, - - /** - * Represents the "sealed" modifier - */ - SEALED, - - /** - * Represents the "non-sealed" modifier - */ - NON_SEALED, - - /** - * Represents the "default" modifier - */ - DEFAULT; - - @Override - public String toString() { - return name().toLowerCase(); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/ParameterDecl.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/ParameterDecl.java deleted file mode 100644 index d081d03a..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/ParameterDecl.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.maracas.roseau.api.model; - -import com.github.maracas.roseau.api.model.reference.ITypeReference; - -public record ParameterDecl( - String name, - ITypeReference type, - boolean isVarargs -) { - @Override - public String toString() { - return "%s%s %s".formatted(type, isVarargs ? "..." : "", name); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/RecordDecl.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/RecordDecl.java deleted file mode 100644 index 0657ce9d..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/RecordDecl.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.github.maracas.roseau.api.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.github.maracas.roseau.api.model.reference.TypeReference; - -import java.util.List; - -public final class RecordDecl extends ClassDecl { - @JsonCreator - public RecordDecl(String qualifiedName, AccessModifier visibility, List modifiers, SourceLocation location, - List> implementedInterfaces, - List formalTypeParameters, List fields, List methods, - TypeReference enclosingType, List constructors) { - super(qualifiedName, visibility, modifiers, location, implementedInterfaces, formalTypeParameters, - fields, methods, enclosingType, null, constructors); - } - - @Override - public boolean isRecord() { - return true; - } - - @Override - public String toString() { - return """ - record %s [%s] - %s - %s - """.formatted(qualifiedName, visibility, fields, methods); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/SourceLocation.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/SourceLocation.java deleted file mode 100644 index 1d5c9036..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/SourceLocation.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.github.maracas.roseau.api.model; - -import java.nio.file.Path; - -public record SourceLocation( - Path file, - int line -) { - public static final SourceLocation NO_LOCATION = new SourceLocation(null, -1); -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/SpoonAPIFactory.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/SpoonAPIFactory.java deleted file mode 100644 index 3fbd9317..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/SpoonAPIFactory.java +++ /dev/null @@ -1,349 +0,0 @@ -package com.github.maracas.roseau.api.model; - -import com.github.maracas.roseau.api.model.reference.ITypeReference; -import com.github.maracas.roseau.api.model.reference.SpoonTypeReferenceFactory; -import com.github.maracas.roseau.api.model.reference.TypeReference; -import com.github.maracas.roseau.api.model.reference.TypeReferenceFactory; -import spoon.reflect.cu.SourcePosition; -import spoon.reflect.declaration.CtAnnotationType; -import spoon.reflect.declaration.CtClass; -import spoon.reflect.declaration.CtConstructor; -import spoon.reflect.declaration.CtEnum; -import spoon.reflect.declaration.CtExecutable; -import spoon.reflect.declaration.CtField; -import spoon.reflect.declaration.CtFormalTypeDeclarer; -import spoon.reflect.declaration.CtInterface; -import spoon.reflect.declaration.CtMethod; -import spoon.reflect.declaration.CtParameter; -import spoon.reflect.declaration.CtRecord; -import spoon.reflect.declaration.CtType; -import spoon.reflect.declaration.CtTypeMember; -import spoon.reflect.declaration.CtTypeParameter; -import spoon.reflect.declaration.ModifierKind; -import spoon.reflect.factory.TypeFactory; -import spoon.reflect.reference.CtArrayTypeReference; -import spoon.reflect.reference.CtIntersectionTypeReference; -import spoon.reflect.reference.CtTypeParameterReference; -import spoon.reflect.reference.CtTypeReference; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; - -public class SpoonAPIFactory { - private final TypeFactory typeFactory; - private final TypeReferenceFactory typeReferenceFactory; - - public SpoonAPIFactory(TypeFactory typeFactory) { - this.typeFactory = typeFactory; - this.typeReferenceFactory = new SpoonTypeReferenceFactory(this); - } - - public TypeReferenceFactory getTypeReferenceFactory() { - return typeReferenceFactory; - } - - private ITypeReference createITypeReference(CtTypeReference typeRef) { - if (typeRef == null) - return null; - - return switch (typeRef) { - case CtArrayTypeReference arrayRef -> typeReferenceFactory.createArrayTypeReference(createITypeReference(arrayRef.getComponentType())); - case CtTypeParameterReference tpRef -> { - if (tpRef.getBoundingType() instanceof CtIntersectionTypeReference intersection) - yield typeReferenceFactory.createTypeParameterReference(tpRef.getQualifiedName(), createITypeReferences(intersection.getBounds())); - else - yield typeReferenceFactory.createTypeParameterReference(tpRef.getQualifiedName(), List.of(createITypeReference(tpRef.getBoundingType()))); - } - case CtTypeReference ref when ref.isPrimitive() -> typeReferenceFactory.createPrimitiveTypeReference(ref.getQualifiedName()); - default -> createTypeReference(typeRef); - }; - } - - private TypeReference createTypeReference(CtTypeReference typeRef) { - return typeRef != null ? typeReferenceFactory.createTypeReference(typeRef.getQualifiedName()) : null; - } - - private TypeReference createTypeReference(CtType type) { - return type != null ? createTypeReference(type.getReference()) : null; - } - - private List createITypeReferences(Collection> typeRefs) { - return typeRefs.stream() - .map(this::createITypeReference) - .filter(Objects::nonNull) - .toList(); - } - - private List> createTypeReferences(Collection> typeRefs) { - return typeRefs.stream() - .map(this::createTypeReference) - .filter(Objects::nonNull) - .toList(); - } - - public TypeDecl convertCtType(CtType type) { - return switch (type) { - case CtAnnotationType a -> convertCtAnnotationType(a); - case CtInterface i -> convertCtInterface(i); - case CtRecord r -> convertCtRecord(r); - case CtEnum e -> convertCtEnum(e); - case CtClass c -> convertCtClass(c); - default -> throw new IllegalArgumentException("Unknown type kind: " + type); - }; - } - - public TypeDecl convertCtType(String qualifiedName) { - CtTypeReference ref = typeFactory.createReference(qualifiedName); - - return ref.getTypeDeclaration() != null - ? convertCtType(ref.getTypeDeclaration()) - : null; - } - - private ClassDecl convertCtClass(CtClass cls) { - return new ClassDecl( - cls.getQualifiedName(), - convertSpoonVisibility(cls.getVisibility()), - convertSpoonNonAccessModifiers(cls.getModifiers()), - convertSpoonPosition(cls.getPosition()), - createTypeReferences(cls.getSuperInterfaces()), - convertCtFormalTypeParameters(cls), - convertCtFields(cls), - convertCtMethods(cls), - createTypeReference(cls.getDeclaringType()), - createTypeReference(cls.getSuperclass()), - convertCtConstructors(cls) - ); - } - - private InterfaceDecl convertCtInterface(CtInterface intf) { - return new InterfaceDecl( - intf.getQualifiedName(), - convertSpoonVisibility(intf.getVisibility()), - convertSpoonNonAccessModifiers(intf.getModifiers()), - convertSpoonPosition(intf.getPosition()), - createTypeReferences(intf.getSuperInterfaces()), - convertCtFormalTypeParameters(intf), - convertCtFields(intf), - convertCtMethods(intf), - createTypeReference(intf.getDeclaringType()) - ); - } - - private AnnotationDecl convertCtAnnotationType(CtAnnotationType annotation) { - return new AnnotationDecl( - annotation.getQualifiedName(), - convertSpoonVisibility(annotation.getVisibility()), - convertSpoonNonAccessModifiers(annotation.getModifiers()), - convertSpoonPosition(annotation.getPosition()), - convertCtFields(annotation), - convertCtMethods(annotation), - createTypeReference(annotation.getDeclaringType()) - ); - } - - private EnumDecl convertCtEnum(CtEnum enm) { - return new EnumDecl( - enm.getQualifiedName(), - convertSpoonVisibility(enm.getVisibility()), - convertSpoonNonAccessModifiers(enm.getModifiers()), - convertSpoonPosition(enm.getPosition()), - createTypeReferences(enm.getSuperInterfaces()), - convertCtFields(enm), - convertCtMethods(enm), - createTypeReference(enm.getDeclaringType()), - convertCtConstructors(enm) - ); - } - - private RecordDecl convertCtRecord(CtRecord record) { - return new RecordDecl( - record.getQualifiedName(), - convertSpoonVisibility(record.getVisibility()), - convertSpoonNonAccessModifiers(record.getModifiers()), - convertSpoonPosition(record.getPosition()), - createTypeReferences(record.getSuperInterfaces()), - convertCtFormalTypeParameters(record), - convertCtFields(record), - convertCtMethods(record), - createTypeReference(record.getDeclaringType()), - convertCtConstructors(record) - ); - } - - private FieldDecl convertCtField(CtField field) { - return new FieldDecl( - makeQualifiedName(field), - convertSpoonVisibility(field.getVisibility()), - convertSpoonNonAccessModifiers(field.getModifiers()), - convertSpoonPosition(field.getPosition()), - createTypeReference(field.getDeclaringType()), - createITypeReference(field.getType()) - ); - } - - private MethodDecl convertCtMethod(CtMethod method) { - // Spoon does not store 'default' information as modifier, but we do - List modifiers = Stream.concat( - convertSpoonNonAccessModifiers(method.getModifiers()).stream(), - method.isDefaultMethod() ? Stream.of(Modifier.DEFAULT) : Stream.empty() - ).toList(); - - return new MethodDecl( - makeQualifiedName(method), - convertSpoonVisibility(method.getVisibility()), - modifiers, - convertSpoonPosition(method.getPosition()), - createTypeReference(method.getDeclaringType()), - createITypeReference(method.getType()), - convertCtParameters(method), - convertCtFormalTypeParameters(method), - createTypeReferences(new ArrayList<>(method.getThrownTypes())) - ); - } - - private ConstructorDecl convertCtConstructor(CtConstructor cons) { - return new ConstructorDecl( - makeQualifiedName(cons), - convertSpoonVisibility(cons.getVisibility()), - convertSpoonNonAccessModifiers(cons.getModifiers()), - convertSpoonPosition(cons.getPosition()), - createTypeReference(cons.getDeclaringType()), - createITypeReference(cons.getType()), - convertCtParameters(cons), - convertCtFormalTypeParameters(cons), - createTypeReferences(new ArrayList<>(cons.getThrownTypes())) - ); - } - - private List convertCtFields(CtType type) { - return type.getFields().stream() - .filter(this::isExported) - .map(this::convertCtField) - .toList(); - } - - private List convertCtMethods(CtType type) { - return type.getMethods().stream() - .filter(this::isExported) - .map(this::convertCtMethod) - .toList(); - } - - private List convertCtConstructors(CtClass cls) { - // We need to keep track of default constructors in the API model. - // In such case, Spoon indeed returns an (implicit) constructor, but its visibility is null, - // so we need to handle it separately. - return cls.getConstructors().stream() - .filter(cons -> isExported(cons) || cons.isImplicit()) - .map(this::convertCtConstructor) - .toList(); - } - - private List convertCtFormalTypeParameters(CtFormalTypeDeclarer declarer) { - return declarer.getFormalCtTypeParameters().stream() - .map(this::convertCtTypeParameter) - .toList(); - } - - private FormalTypeParameter convertCtTypeParameter(CtTypeParameter parameter) { - return new FormalTypeParameter( - parameter.getSimpleName(), - convertCtTypeParameterBounds(parameter.getSuperclass()) - ); - } - - private List convertCtTypeParameterBounds(CtTypeReference ref) { - return switch (ref) { - case CtIntersectionTypeReference intersection -> intersection.getBounds().stream().map(this::createITypeReference).toList(); - case CtTypeReference reference -> List.of(createTypeReference(reference)); - case null -> Collections.emptyList(); - }; - } - - private List convertCtParameters(CtExecutable executable) { - return executable.getParameters().stream() - .map(this::convertCtParameter) - .toList(); - } - - private ParameterDecl convertCtParameter(CtParameter parameter) { - return new ParameterDecl(parameter.getSimpleName(), createITypeReference(parameter.getType()), parameter.isVarArgs()); - } - - private AccessModifier convertSpoonVisibility(ModifierKind visibility) { - return switch (visibility) { - case PUBLIC -> AccessModifier.PUBLIC; - case PRIVATE -> AccessModifier.PRIVATE; - case PROTECTED -> AccessModifier.PROTECTED; - case null -> AccessModifier.PACKAGE_PRIVATE; - default -> throw new IllegalArgumentException("Unknown visibility " + visibility); - }; - } - - private Modifier convertSpoonModifier(ModifierKind modifier) { - return switch (modifier) { - case STATIC -> Modifier.STATIC; - case FINAL -> Modifier.FINAL; - case ABSTRACT -> Modifier.ABSTRACT; - case SYNCHRONIZED -> Modifier.SYNCHRONIZED; - case VOLATILE -> Modifier.VOLATILE; - case TRANSIENT -> Modifier.TRANSIENT; - case SEALED -> Modifier.SEALED; - case NON_SEALED -> Modifier.NON_SEALED; - case NATIVE -> Modifier.NATIVE; - case STRICTFP -> Modifier.STRICTFP; - default -> throw new IllegalArgumentException("Unknown modifier " + modifier); - }; - } - - private List convertSpoonNonAccessModifiers(Collection modifiers) { - return modifiers.stream() - .filter(mod -> - ModifierKind.PUBLIC != mod - && ModifierKind.PROTECTED != mod - && ModifierKind.PRIVATE != mod) - .map(this::convertSpoonModifier) - .toList(); - } - - private SourceLocation convertSpoonPosition(SourcePosition position) { - return position.isValidPosition() - ? new SourceLocation( - position.getFile() != null ? position.getFile().toPath() : null, - position.getLine()) - : SourceLocation.NO_LOCATION; - } - - private boolean isExported(CtType type) { - return - (type.isPublic() || (type.isProtected() && !isEffectivelyFinal(type))) - && isParentExported(type); - } - - private boolean isExported(CtTypeMember member) { - return (member.isPublic() || (member.isProtected() && !isEffectivelyFinal(member.getDeclaringType()))) - && isParentExported(member); - } - - private boolean isParentExported(CtTypeMember member) { - return member.getDeclaringType() == null || isExported(member.getDeclaringType()); - } - - private boolean isEffectivelyFinal(CtType type) { - if (type instanceof CtClass cls) - if (!cls.getConstructors().isEmpty() - && cls.getConstructors().stream().noneMatch(cons -> cons.isPublic() || cons.isProtected())) - return true; - - return type.isFinal() || type.hasModifier(ModifierKind.SEALED); - } - - private String makeQualifiedName(CtTypeMember member) { - return String.format("%s.%s", member.getDeclaringType().getQualifiedName(), member.getSimpleName()); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/Symbol.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/Symbol.java deleted file mode 100644 index 48e54e63..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/Symbol.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.github.maracas.roseau.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnore; - -import java.util.List; -import java.util.Objects; - -/** - * This abstract class represents a symbol in the library, which can be a type, - * a method, a constructor, or a field. - *

- * It provides information about the symbol's qualified qualifiedName, visibility, modifiers, - * and position within the source code. - */ -public abstract sealed class Symbol permits TypeDecl, TypeMemberDecl { - /** - * The qualifiedName of the symbol. - */ - protected final String qualifiedName; - - /** - * The visibility of the symbol. - */ - protected final AccessModifier visibility; - - /** - * List of non-access modifiers applied to the symbol. - */ - protected final List modifiers; - - /** - * The exact location of the symbol - */ - protected final SourceLocation location; - - protected Symbol(String qualifiedName, AccessModifier visibility, List modifiers, SourceLocation location) { - this.qualifiedName = qualifiedName; - this.visibility = visibility; - this.modifiers = modifiers; - this.location = location; - } - - /** - * Retrieves the qualifiedName of the symbol. - * - * @return The symbol's qualifiedName - */ - public String getQualifiedName() { - return qualifiedName; - } - - /** - * Retrieves the visibility of the symbol. - * - * @return The symbol's visibility - */ - public AccessModifier getVisibility() { - return visibility; - } - - /** - * Checks whether the symbol is accessible/exported - * - * @return exported or not - */ - @JsonIgnore - public abstract boolean isExported(); - - /** - * Retrieves the list of non-access modifiers applied to the symbol. - * - * @return The symbol's non-access modifiers - */ - public List getModifiers() { - return modifiers; - } - - /** - * Retrieves the position of the symbol. - * - * @return The symbol's position. - */ - public SourceLocation getLocation() { - return location; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Symbol symbol = (Symbol) o; - return Objects.equals(qualifiedName, symbol.qualifiedName) - && visibility == symbol.visibility - && Objects.equals(modifiers, symbol.modifiers) - && Objects.equals(location, symbol.location); - } - - @Override - public int hashCode() { - return Objects.hash(qualifiedName, visibility, modifiers, location); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/TypeDecl.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/TypeDecl.java deleted file mode 100644 index 7a830be2..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/TypeDecl.java +++ /dev/null @@ -1,271 +0,0 @@ -package com.github.maracas.roseau.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.github.maracas.roseau.api.model.reference.ITypeReference; -import com.github.maracas.roseau.api.model.reference.TypeReference; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Stream; - - -/** - * Represents a type declaration in the library. - * This class extends the {@link Symbol} class and contains information about the type's kind, fields, methods, constructors, and more. - */ -@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "typeKind") -public abstract sealed class TypeDecl extends Symbol permits ClassDecl, InterfaceDecl, AnnotationDecl { - protected final List> implementedInterfaces; - - /** - * List of formal type parameters for generic types. - */ - protected final List formalTypeParameters; - - /** - * List of fields declared within the type. - */ - protected final List fields; - - /** - * List of methods declared within the type. - */ - protected final List methods; - - protected final TypeReference enclosingType; - - protected TypeDecl(String qualifiedName, - AccessModifier visibility, - List modifiers, - SourceLocation location, - List> implementedInterfaces, - List formalTypeParameters, - List fields, - List methods, - TypeReference enclosingType) { - super(qualifiedName, visibility, modifiers, location); - this.implementedInterfaces = implementedInterfaces; - this.formalTypeParameters = formalTypeParameters; - this.fields = fields; - this.methods = methods; - this.enclosingType = enclosingType; - } - - @JsonIgnore - @Override - public boolean isExported() { - return (isPublic() || (isProtected() && !isEffectivelyFinal())) - && (enclosingType == null || enclosingType.getResolvedApiType().map(TypeDecl::isExported).orElse(true)); - } - - @JsonIgnore - public boolean isNested() { - return enclosingType != null; - } - - @JsonIgnore - public boolean isClass() { - return false; - } - - @JsonIgnore - public boolean isInterface() { - return false; - } - - @JsonIgnore - public boolean isEnum() { - return false; - } - - @JsonIgnore - public boolean isRecord() { - return false; - } - - @JsonIgnore - public boolean isAnnotation() { - return false; - } - - @JsonIgnore - public boolean isCheckedException() { - return false; - } - - @JsonIgnore - public boolean isStatic() { - return modifiers.contains(Modifier.STATIC); - } - - @JsonIgnore - public boolean isFinal() { - return modifiers.contains(Modifier.FINAL); - } - - @JsonIgnore - public boolean isSealed() { - return modifiers.contains(Modifier.SEALED); - } - - @JsonIgnore - public boolean isEffectivelyFinal() { - // FIXME: in fact, a sealed class may not be final if one of its permitted subclass - // is explicitly marked as non-sealed - return !modifiers.contains(Modifier.NON_SEALED) && (isFinal() || isSealed()); - } - - @JsonIgnore - public boolean isPublic() { - return AccessModifier.PUBLIC == visibility; - } - - @JsonIgnore - public boolean isProtected() { - return AccessModifier.PROTECTED == visibility; - } - - @JsonIgnore - public boolean isPrivate() { - return AccessModifier.PRIVATE == visibility; - } - - @JsonIgnore - public boolean isPackagePrivate() { - return AccessModifier.PACKAGE_PRIVATE == visibility; - } - - @JsonIgnore - public boolean isAbstract() { - return modifiers.contains(Modifier.ABSTRACT); - } - - @JsonIgnore - public List getAllMethods() { - List allMethods = Stream.concat( - methods.stream(), - getSuperMethods().stream() - ).toList(); - - return allMethods.stream() - .filter(m -> allMethods.stream().noneMatch(m2 -> !m2.equals(m) && m2.isOverriding(m))) - .toList(); - } - - @JsonIgnore - public List> getAllSuperTypes() { - return new ArrayList<>(getAllImplementedInterfaces()); - } - - protected List getSuperMethods() { - return implementedInterfaces.stream() - .map(TypeReference::getResolvedApiType) - .flatMap(Optional::stream) - .map(InterfaceDecl::getAllMethods) - .flatMap(Collection::stream) - .toList(); - } - - @JsonIgnore - public List getAllFields() { - return Stream.concat( - fields.stream(), - implementedInterfaces.stream() - .map(TypeReference::getResolvedApiType) - .flatMap(Optional::stream) - .map(InterfaceDecl::getAllFields) - .flatMap(Collection::stream) - ).toList(); - } - - /** - * Retrieves the superinterfaces of the type as typeDeclarations. - * - * @return Type's superinterfaces as typeDeclarations - */ - public List> getImplementedInterfaces() { - return implementedInterfaces; - } - - /** - * Retrieves the list of formal type parameters for generic types. - * - * @return List of formal type parameters - */ - public List getFormalTypeParameters() { - return formalTypeParameters; - } - - /** - * Retrieves the list of fields declared within the type. - * - * @return List of fields declared within the type - */ - public List getFields() { - return fields; - } - - public List getMethods() { - return methods; - } - - public Optional> getEnclosingType() { - return Optional.ofNullable(enclosingType); - } - - public Optional findField(String name) { - return fields.stream() - .filter(f -> f.getSimpleName().equals(name)) - .findFirst(); - } - - public Optional findMethod(String name, List parameterTypes, boolean varargs) { - return methods.stream() - .filter(m -> m.hasSignature(name, parameterTypes, varargs)) - .findFirst(); - } - - public Optional findMethod(String name, List parameterTypes) { - return findMethod(name, parameterTypes, false); - } - - public Optional findMethod(String name) { - return methods.stream() - .filter(m -> m.getSimpleName().equals(name)) - .findFirst(); - } - - @JsonIgnore - public List> getAllImplementedInterfaces() { - return Stream.concat( - implementedInterfaces.stream(), - implementedInterfaces.stream() - .map(TypeReference::getResolvedApiType) - .flatMap(Optional::stream) - .map(InterfaceDecl::getAllImplementedInterfaces) - .flatMap(Collection::stream) - ).distinct().toList(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - TypeDecl typeDecl = (TypeDecl) o; - return Objects.equals(implementedInterfaces, typeDecl.implementedInterfaces) - && Objects.equals(formalTypeParameters, typeDecl.formalTypeParameters) - && Objects.equals(fields, typeDecl.fields) - && Objects.equals(methods, typeDecl.methods); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), implementedInterfaces, formalTypeParameters, fields, methods); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/TypeMember.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/TypeMember.java deleted file mode 100644 index 8f00d0cc..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/TypeMember.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.github.maracas.roseau.api.model; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.github.maracas.roseau.api.model.reference.ITypeReference; -import com.github.maracas.roseau.api.model.reference.TypeReference; - -public interface TypeMember { - TypeReference getContainingType(); - ITypeReference getType(); - @JsonIgnore - String getSimpleName(); - @JsonIgnore - boolean isStatic(); - @JsonIgnore - boolean isFinal(); - @JsonIgnore - boolean isPublic(); - @JsonIgnore - boolean isProtected(); -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/TypeMemberDecl.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/TypeMemberDecl.java deleted file mode 100644 index 0d219541..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/TypeMemberDecl.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.github.maracas.roseau.api.model; - -import com.github.maracas.roseau.api.model.reference.ITypeReference; -import com.github.maracas.roseau.api.model.reference.TypeReference; - -import java.util.List; -import java.util.Objects; - -public abstract sealed class TypeMemberDecl extends Symbol implements TypeMember permits FieldDecl, ExecutableDecl { - protected final TypeReference containingType; - protected final ITypeReference type; - - protected TypeMemberDecl(String qualifiedName, AccessModifier visibility, List modifiers, - SourceLocation location, TypeReference containingType, ITypeReference type) { - super(qualifiedName, visibility, modifiers, location); - this.containingType = containingType; - this.type = type; - } - - @Override - public TypeReference getContainingType() { - return containingType; - } - - @Override - public ITypeReference getType() { - return type; - } - - @Override - public boolean isExported() { - return (isPublic() - || (isProtected() && !containingType.getResolvedApiType().map(TypeDecl::isEffectivelyFinal).orElse(true))) - && containingType.getResolvedApiType().map(TypeDecl::isExported).orElse(true); - } - - @Override - public boolean isStatic() { - return modifiers.contains(Modifier.STATIC); - } - - @Override - public boolean isFinal() { - return modifiers.contains(Modifier.FINAL); - } - - @Override - public boolean isPublic() { - return AccessModifier.PUBLIC == visibility; - } - - @Override - public boolean isProtected() { - return AccessModifier.PROTECTED == visibility; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - TypeMemberDecl other = (TypeMemberDecl) o; - return Objects.equals(type, other.type); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), type); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/package-info.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/package-info.java deleted file mode 100644 index 7ba99615..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * This package contains the classes and types needed for API extraction. - */ -package com.github.maracas.roseau.api.model; diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/ArrayTypeReference.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/ArrayTypeReference.java deleted file mode 100644 index 32c777d1..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/ArrayTypeReference.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.github.maracas.roseau.api.model.reference; - -import com.fasterxml.jackson.annotation.JsonIgnore; - -public record ArrayTypeReference(ITypeReference componentType) implements ITypeReference { - @JsonIgnore - @Override - public String getQualifiedName() { - return componentType().getQualifiedName() + "[]"; - } - - @Override - public String toString() { - return getQualifiedName(); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/ITypeReference.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/ITypeReference.java deleted file mode 100644 index b0b70daf..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/ITypeReference.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.github.maracas.roseau.api.model.reference; - -import com.fasterxml.jackson.annotation.JsonTypeInfo; - -@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "refKind") -public sealed interface ITypeReference - permits TypeReference, ArrayTypeReference, PrimitiveTypeReference, TypeParameterReference { - String getQualifiedName(); -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/PrimitiveTypeReference.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/PrimitiveTypeReference.java deleted file mode 100644 index 4a3914e1..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/PrimitiveTypeReference.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.github.maracas.roseau.api.model.reference; - -public record PrimitiveTypeReference(String qualifiedName) implements ITypeReference { - @Override - public String getQualifiedName() { - return qualifiedName; - } - - @Override - public String toString() { - return qualifiedName; - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/SpoonTypeReferenceFactory.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/SpoonTypeReferenceFactory.java deleted file mode 100644 index 5cf9be0b..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/SpoonTypeReferenceFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.github.maracas.roseau.api.model.reference; - -import com.github.maracas.roseau.api.model.SpoonAPIFactory; -import com.github.maracas.roseau.api.model.TypeDecl; - -import java.util.List; - -public class SpoonTypeReferenceFactory implements TypeReferenceFactory { - private final SpoonAPIFactory apiFactory; - - public SpoonTypeReferenceFactory(SpoonAPIFactory apiFactory) { - this.apiFactory = apiFactory; - } - - @Override - public TypeReference createTypeReference(String qualifiedName) { - return new TypeReference<>(qualifiedName, apiFactory); - } - - @Override - public PrimitiveTypeReference createPrimitiveTypeReference(String name) { - return new PrimitiveTypeReference(name); - } - - @Override - public ArrayTypeReference createArrayTypeReference(ITypeReference componentType) { - return componentType != null ? new ArrayTypeReference(componentType) : null; - } - - @Override - public TypeParameterReference createTypeParameterReference(String qualifiedName, List bounds) { - return new TypeParameterReference(qualifiedName, bounds); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/TypeParameterReference.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/TypeParameterReference.java deleted file mode 100644 index 216d0034..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/TypeParameterReference.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.github.maracas.roseau.api.model.reference; - -import java.util.List; -import java.util.stream.Collectors; - -public record TypeParameterReference(String qualifiedName, List bounds) implements ITypeReference { - @Override - public String getQualifiedName() { - return qualifiedName; - } - - @Override - public String toString() { - return "%s<%s>".formatted(qualifiedName, - bounds.stream().map(Object::toString).collect(Collectors.joining(", "))); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/TypeReference.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/TypeReference.java deleted file mode 100644 index cf39a949..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/TypeReference.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.github.maracas.roseau.api.model.reference; - -import com.fasterxml.jackson.annotation.JsonValue; -import com.github.maracas.roseau.api.model.SpoonAPIFactory; -import com.github.maracas.roseau.api.model.TypeDecl; -import com.google.common.base.Objects; - -import java.util.Optional; - -public final class TypeReference implements ITypeReference { - private final String qualifiedName; - private SpoonAPIFactory factory; - private T resolvedApiType; - - public TypeReference(String qualifiedName) { - this.qualifiedName = qualifiedName; - } - - public TypeReference(String qualifiedName, SpoonAPIFactory factory) { - this.qualifiedName = qualifiedName; - this.factory = factory; - } - - @JsonValue - @Override - public String getQualifiedName() { - return qualifiedName; - } - - public void setFactory(SpoonAPIFactory factory) { - this.factory = factory; - } - - public Optional getResolvedApiType() { - if (resolvedApiType == null && factory != null) - resolvedApiType = (T) factory.convertCtType(qualifiedName); - - return Optional.ofNullable(resolvedApiType); - } - - public void setResolvedApiType(T type) { - resolvedApiType = type; - } - - public boolean isSubtypeOf(TypeReference other) { - return equals(other) || getResolvedApiType().map(t -> t.getAllSuperTypes().contains(other)).orElse(false); - } - - public boolean isSameHierarchy(TypeReference other) { - return isSubtypeOf(other) || other.isSubtypeOf(this); - } - - @Override - public String toString() { - return qualifiedName; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - TypeReference other = (TypeReference) o; - return Objects.equal(qualifiedName, other.qualifiedName); - } - - @Override - public int hashCode() { - return Objects.hashCode(qualifiedName); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/TypeReferenceFactory.java b/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/TypeReferenceFactory.java deleted file mode 100644 index 4b5632ef..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/api/model/reference/TypeReferenceFactory.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.github.maracas.roseau.api.model.reference; - -import com.github.maracas.roseau.api.model.TypeDecl; - -import java.util.List; - -public interface TypeReferenceFactory { - TypeReference createTypeReference(String qualifiedName); - PrimitiveTypeReference createPrimitiveTypeReference(String name); - ArrayTypeReference createArrayTypeReference(ITypeReference componentType); - TypeParameterReference createTypeParameterReference(String qualifiedName, List bounds); -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/diff/APIDiff.java b/ConfGen/src/main/java/com/github/maracas/roseau/diff/APIDiff.java deleted file mode 100644 index 80cf6015..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/diff/APIDiff.java +++ /dev/null @@ -1,348 +0,0 @@ -package com.github.maracas.roseau.diff; - -import com.github.maracas.roseau.api.model.API; -import com.github.maracas.roseau.api.model.ClassDecl; -import com.github.maracas.roseau.api.model.ConstructorDecl; -import com.github.maracas.roseau.api.model.ExecutableDecl; -import com.github.maracas.roseau.api.model.FieldDecl; -import com.github.maracas.roseau.api.model.FormalTypeParameter; -import com.github.maracas.roseau.api.model.MethodDecl; -import com.github.maracas.roseau.api.model.SourceLocation; -import com.github.maracas.roseau.api.model.Symbol; -import com.github.maracas.roseau.api.model.TypeDecl; -import com.github.maracas.roseau.api.model.reference.ITypeReference; -import com.github.maracas.roseau.api.model.reference.TypeReference; -import com.github.maracas.roseau.diff.changes.BreakingChange; -import com.github.maracas.roseau.diff.changes.BreakingChangeKind; - -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -/** - * This class represents Roseau's comparison tool for detecting breaking changes between two API versions. - */ -public class APIDiff { - /** - * The first version of the API to be compared. - */ - private final API v1; - - /** - * The second version of the API to be compared. - */ - private final API v2; - - /** - * List of all the breaking changes identified in the comparison. - */ - private final List breakingChanges; - - /** - * Constructs an APIDiff instance to compare two API versions for breaking changes detection. - * - * @param v1 The first version of the API to compare. - * @param v2 The second version of the API to compare. - */ - public APIDiff(API v1, API v2) { - this.v1 = Objects.requireNonNull(v1); - this.v2 = Objects.requireNonNull(v2); - breakingChanges = new ArrayList<>(); - } - - public List diff() { - v1.getExportedTypes().forEach(t1 -> { - Optional findT2 = v2.getExportedType(t1.getQualifiedName()); - - findT2.ifPresentOrElse( - // There is a matching type - t2 -> { - diffType(t1, t2); - diffFields(t1, t2); - diffMethods(t1, t2); - diffAddedMethods(t1, t2); - - if (t1 instanceof ClassDecl c1 && t2 instanceof ClassDecl c2) - diffConstructors(c1, c2); - }, - // Type has been removed - () -> bc(BreakingChangeKind.TYPE_REMOVED, t1) - ); - }); - - return breakingChanges; - } - - private void diffFields(TypeDecl t1, TypeDecl t2) { - t1.getFields().forEach(f1 -> { - Optional findF2 = t2.findField(f1.getSimpleName()); - - findF2.ifPresentOrElse( - // There is a matching field - f2 -> diffField(f1, f2), - // The field has been removed - () -> bc(BreakingChangeKind.FIELD_REMOVED, f1) - ); - }); - } - - private void diffMethods(TypeDecl t1, TypeDecl t2) { - t1.getMethods().forEach(m1 -> { - Optional matchM2 = t2.getMethods().stream() - .filter(m -> m.hasSameSignature(m1)) - .findFirst(); - - matchM2.ifPresentOrElse( - // There is a matching method - m2 -> diffMethod(t1, m1, m2), - // The method has been removed - () -> bc(BreakingChangeKind.METHOD_REMOVED, m1) - ); - }); - } - - private void diffConstructors(ClassDecl c1, ClassDecl c2) { - c1.getConstructors().forEach(cons1 -> { - Optional matchCons2 = c2.getConstructors().stream() - .filter(cons -> cons.hasSameSignature(cons1)) - .findFirst(); - - matchCons2.ifPresentOrElse( - // There is a matching constructor - cons2 -> diffConstructor(cons1, cons2), - // The constructor has been removed - () -> bc(BreakingChangeKind.CONSTRUCTOR_REMOVED, cons1) - ); - }); - } - - private void diffAddedMethods(TypeDecl t1, TypeDecl t2) { - t2.getMethods().stream() - .filter(m2 -> t1.getMethods().stream().noneMatch(m1 -> m1.hasSameSignature(m2))) - .forEach(m2 -> { - if (t2.isInterface() && !m2.isDefault()) - bc(BreakingChangeKind.METHOD_ADDED_TO_INTERFACE, t1); - - if (t2.isClass() && m2.isAbstract()) - bc(BreakingChangeKind.METHOD_ABSTRACT_ADDED_TO_CLASS, t1); - }); - } - - private void diffType(TypeDecl t1, TypeDecl t2) { - if (t1.isClass()) { - if (!t1.isFinal() && t2.isFinal()) - bc(BreakingChangeKind.CLASS_NOW_FINAL, t1); - - if (!t1.isSealed() && t2.isSealed()) - bc(BreakingChangeKind.CLASS_NOW_FINAL, t1); - - if (!t1.isAbstract() && t2.isAbstract()) - bc(BreakingChangeKind.CLASS_NOW_ABSTRACT, t1); - - if (!t1.isStatic() && t2.isStatic() && t1.isNested() && t2.isNested()) - bc(BreakingChangeKind.NESTED_CLASS_NOW_STATIC, t1); - - if (t1.isStatic() && !t2.isStatic() && t1.isNested() && t2.isNested()) - bc(BreakingChangeKind.NESTED_CLASS_NO_LONGER_STATIC, t1); - - if (!t1.isCheckedException() && t2.isCheckedException()) - bc(BreakingChangeKind.CLASS_NOW_CHECKED_EXCEPTION, t1); - } - - if (t1.isPublic() && t2.isProtected()) - bc(BreakingChangeKind.TYPE_NOW_PROTECTED, t1); - - if (t1 instanceof ClassDecl cls1 && t2 instanceof ClassDecl cls2) { - if (cls1.getSuperClass().isPresent() && cls2.getSuperClass().isEmpty()) - bc(BreakingChangeKind.SUPERCLASS_MODIFIED_INCOMPATIBLE, t1); - } - - // Deleted super-interfaces - if (t1.getImplementedInterfaces().stream() - .anyMatch(intf1 -> t2.getImplementedInterfaces().stream() - .noneMatch(intf2 -> intf1.getQualifiedName().equals(intf2.getQualifiedName())))) - bc(BreakingChangeKind.SUPERCLASS_MODIFIED_INCOMPATIBLE, t1); - - if (!t1.getClass().equals(t2.getClass())) - bc(BreakingChangeKind.CLASS_TYPE_CHANGED, t1); - - int formalParametersCount1 = t1.getFormalTypeParameters().size(); - int formalParametersCount2 = t2.getFormalTypeParameters().size(); - if (formalParametersCount1 == formalParametersCount2) { - for (int i = 0; i < formalParametersCount1; i++) { - FormalTypeParameter p1 = t1.getFormalTypeParameters().get(i); - FormalTypeParameter p2 = t2.getFormalTypeParameters().get(i); - - List bounds1 = p1.bounds().stream() - .map(ITypeReference::getQualifiedName) - .toList(); - List bounds2 = p2.bounds().stream() - .map(ITypeReference::getQualifiedName) - .toList(); - - if (bounds1.size() != bounds2.size() - || !(new HashSet<>(bounds1)).equals(new HashSet<>(bounds2))) { - bc(BreakingChangeKind.TYPE_FORMAL_TYPE_PARAMETERS_CHANGED, t1); - } - } - } else if (formalParametersCount1 < formalParametersCount2) { - bc(BreakingChangeKind.TYPE_FORMAL_TYPE_PARAMETERS_REMOVED, t1); - } else { - bc(BreakingChangeKind.TYPE_FORMAL_TYPE_PARAMETERS_ADDED, t1); - } - } - - private void diffField(FieldDecl f1, FieldDecl f2) { - if (!f1.isFinal() && f2.isFinal()) - bc(BreakingChangeKind.FIELD_NOW_FINAL, f1); - - if (!f1.isStatic() && f2.isStatic()) - bc(BreakingChangeKind.FIELD_NOW_STATIC, f1); - - if (f1.isStatic() && !f2.isStatic()) - bc(BreakingChangeKind.FIELD_NO_LONGER_STATIC, f1); - - if (!f1.getType().equals(f2.getType())) - bc(BreakingChangeKind.FIELD_TYPE_CHANGED, f1); - - if (f1.isPublic() && f2.isProtected()) - bc(BreakingChangeKind.FIELD_LESS_ACCESSIBLE, f1); - } - - private void diffMethod(TypeDecl t1, MethodDecl m1, MethodDecl m2) { - if (!m1.isFinal() && m2.isFinal()) - bc(BreakingChangeKind.METHOD_NOW_FINAL, m1); - - if (!m1.isStatic() && m2.isStatic()) - bc(BreakingChangeKind.METHOD_NOW_STATIC, m1); - - if (!m1.isNative() && m2.isNative()) - bc(BreakingChangeKind.METHOD_NOW_NATIVE, m1); - - if (m1.isStatic() && !m2.isStatic()) - bc(BreakingChangeKind.METHOD_NO_LONGER_STATIC, m1); - - if (m1.isStrictFp() && !m2.isStrictFp()) - bc(BreakingChangeKind.METHOD_NO_LONGER_STRICTFP, m1); - - if (!m1.isAbstract() && m2.isAbstract()) - bc(BreakingChangeKind.METHOD_NOW_ABSTRACT, m1); - - if (m1.isAbstract() && m2.isDefault()) // Careful - bc(BreakingChangeKind.METHOD_ABSTRACT_NOW_DEFAULT, m1); - - if (m1.isPublic() && m2.isProtected()) - bc(BreakingChangeKind.METHOD_LESS_ACCESSIBLE, m1); - - if (!m1.getType().equals(m2.getType())) - bc(BreakingChangeKind.METHOD_RETURN_TYPE_CHANGED, m1); - - List> additionalExceptions1 = m1.getThrownExceptions().stream() - .filter(e -> !m2.getThrownExceptions().contains(e)) - .toList(); - - List> additionalExceptions2 = m2.getThrownExceptions().stream() - .filter(e -> !m1.getThrownExceptions().contains(e)) - .toList(); - - if (!additionalExceptions1.isEmpty()) - bc(BreakingChangeKind.METHOD_NO_LONGER_THROWS_CHECKED_EXCEPTION, m1); - - if (!additionalExceptions2.isEmpty()) - bc(BreakingChangeKind.METHOD_NOW_THROWS_CHECKED_EXCEPTION, m1); - - // JLS says only one vararg per method, in last position - if (!m1.getParameters().isEmpty() && m1.getParameters().getLast().isVarargs() - && (m2.getParameters().isEmpty() || !m2.getParameters().getLast().isVarargs())) - bc(BreakingChangeKind.METHOD_NO_LONGER_VARARGS, m1); - - if (!m2.getParameters().isEmpty() && m2.getParameters().getLast().isVarargs() - && (m1.getParameters().isEmpty() || !m1.getParameters().getLast().isVarargs())) - bc(BreakingChangeKind.METHOD_NOW_VARARGS, m1); - - // FIXME: no checks for parameters??? - - diffFormalTypeParameters(m1, m2); - } - - private void diffConstructor(ConstructorDecl cons1, ConstructorDecl cons2) { - if (cons1.isPublic() && cons2.isProtected()) - bc(BreakingChangeKind.CONSTRUCTOR_LESS_ACCESSIBLE, cons1); - - diffFormalTypeParameters(cons1, cons2); - } - - private void diffFormalTypeParameters(ExecutableDecl e1, ExecutableDecl e2) { - if (e1.getFormalTypeParameters().size() > e2.getFormalTypeParameters().size()) - bc(BreakingChangeKind.METHOD_FORMAL_TYPE_PARAMETERS_REMOVED, e1); - - if (e1.getFormalTypeParameters().size() < e2.getFormalTypeParameters().size()) - bc(BreakingChangeKind.METHOD_FORMAL_TYPE_PARAMETERS_ADDED, e1); - - for (int i = 0; i < e1.getFormalTypeParameters().size(); i++) { - List bounds1 = e1.getFormalTypeParameters().get(i).bounds(); - - if (i < e2.getFormalTypeParameters().size()) { - List bounds2 = e2.getFormalTypeParameters().get(i).bounds(); - - if (bounds1.size() != bounds2.size() - || !new HashSet<>(bounds1).equals(new HashSet<>(bounds2))) - bc(BreakingChangeKind.METHOD_FORMAL_TYPE_PARAMETERS_CHANGED, e1); - } - } - } - - private void bc(BreakingChangeKind kind, Symbol impactedSymbol) { - breakingChanges.add(new BreakingChange(kind, impactedSymbol)); - } - - /** - * Retrieves the list of all the breaking changes detected between the two API versions. - * - * @return List of all the breaking changes - */ - public List getBreakingChanges() { - return breakingChanges; - } - - /** - * Generates a csv report for the detected breaking changes. This report includes the kind, type qualifiedName, - *

- * position, associated element, and nature of each detected BC. - */ - public void breakingChangesReport() throws IOException { - try (FileWriter writer = new FileWriter("breaking_changes_report.csv")) { - writer.write("Kind,Element,Nature,Position\n"); - - for (BreakingChange breakingChange : breakingChanges) { - String kind = breakingChange.kind().toString(); - String element = breakingChange.impactedSymbol().getQualifiedName(); - String nature = breakingChange.kind().getNature().toString(); - SourceLocation location = breakingChange.impactedSymbol().getLocation(); - - writer.write(kind + "," + element + "," + nature + "," + location + "\n"); - } - } - } - - /** - * Generates a string representation of the breaking changes list. - * - * @return A formatted string containing all the breaking changes and their info. - */ - @Override - public String toString() { - StringBuilder result = new StringBuilder(); - for (BreakingChange breakingChange : breakingChanges) { - result.append(breakingChange.toString()).append("\n"); - result.append(" =========================\n\n"); - } - - return result.toString(); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/BreakingChange.java b/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/BreakingChange.java deleted file mode 100644 index 7929f2ef..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/BreakingChange.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.github.maracas.roseau.diff.changes; - -import com.github.maracas.roseau.api.model.Symbol; - -/** - * Represents a breaking change identified during the comparison of APIs between the two library versions. - * This class encapsulates information about the breaking change's kind and impacted symbol. - * - * @param kind The kind of the breaking change. - * @param impactedSymbol The element associated with the breaking change. - */ -public record BreakingChange( - BreakingChangeKind kind, - Symbol impactedSymbol -) { - @Override - public String toString() { - return "BC[kind=%s, symbol=%s]".formatted(kind, impactedSymbol.getQualifiedName()); - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/BreakingChangeKind.java b/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/BreakingChangeKind.java deleted file mode 100644 index 3d9362cb..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/BreakingChangeKind.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.github.maracas.roseau.diff.changes; - -import static com.github.maracas.roseau.diff.changes.BreakingChangeNature.ADDITION; -import static com.github.maracas.roseau.diff.changes.BreakingChangeNature.DELETION; -import static com.github.maracas.roseau.diff.changes.BreakingChangeNature.MUTATION; - -/** - * Enumerates the source and binary breaking changes taken into account. - */ -public enum BreakingChangeKind { - TYPE_REMOVED(DELETION), - CLASS_NOW_ABSTRACT(MUTATION), - CLASS_NOW_FINAL(MUTATION), - NESTED_CLASS_NOW_STATIC(MUTATION), - NESTED_CLASS_NO_LONGER_STATIC(MUTATION), - CLASS_TYPE_CHANGED(MUTATION), - CLASS_NOW_CHECKED_EXCEPTION(MUTATION), - TYPE_NOW_PROTECTED(MUTATION), - SUPERCLASS_MODIFIED_INCOMPATIBLE(MUTATION), - TYPE_FORMAL_TYPE_PARAMETERS_ADDED(MUTATION), - TYPE_FORMAL_TYPE_PARAMETERS_REMOVED(MUTATION), - TYPE_FORMAL_TYPE_PARAMETERS_CHANGED(MUTATION), - - METHOD_REMOVED(DELETION), - METHOD_LESS_ACCESSIBLE(MUTATION), - METHOD_RETURN_TYPE_CHANGED(MUTATION), - METHOD_NOW_ABSTRACT(MUTATION), - METHOD_NOW_FINAL(MUTATION), - METHOD_NOW_STATIC(MUTATION), - METHOD_NOW_NATIVE(MUTATION), - METHOD_NOW_VARARGS(MUTATION), - METHOD_NO_LONGER_VARARGS(MUTATION), - METHOD_NO_LONGER_STATIC(MUTATION), - METHOD_NO_LONGER_STRICTFP(MUTATION), - METHOD_ADDED_TO_INTERFACE(ADDITION), - METHOD_NOW_THROWS_CHECKED_EXCEPTION(MUTATION), - METHOD_NO_LONGER_THROWS_CHECKED_EXCEPTION(MUTATION), - METHOD_ABSTRACT_ADDED_TO_CLASS(ADDITION), - METHOD_ABSTRACT_NOW_DEFAULT(MUTATION), - METHOD_FORMAL_TYPE_PARAMETERS_ADDED(MUTATION), - METHOD_FORMAL_TYPE_PARAMETERS_REMOVED(MUTATION), - METHOD_FORMAL_TYPE_PARAMETERS_CHANGED(MUTATION), - - FIELD_NOW_FINAL(MUTATION), - FIELD_NOW_STATIC(MUTATION), - FIELD_NO_LONGER_STATIC(MUTATION), - FIELD_TYPE_CHANGED(MUTATION), - FIELD_REMOVED(DELETION), - FIELD_LESS_ACCESSIBLE(MUTATION), - - CONSTRUCTOR_REMOVED(DELETION), - CONSTRUCTOR_LESS_ACCESSIBLE(MUTATION); - - // Do not make sense or unsupported or not implemented yet - /*ANNOTATION_DEPRECATED_ADDED, - - TYPE_GENERICS_CHANGED, - - METHOD_LESS_ACCESSIBLE_THAN_IN_SUPERCLASS, - METHOD_IS_STATIC_AND_OVERRIDES_NOT_STATIC, - METHOD_IS_NOT_STATIC_AND_OVERRIDES_STATIC, - METHOD_RETURN_TYPE_GENERICS_CHANGED, - METHOD_PARAMETER_GENERICS_CHANGED, - METHOD_NEW_DEFAULT, - METHOD_MOVED_TO_SUPERCLASS, - - FIELD_STATIC_AND_OVERRIDES_NON_STATIC, - FIELD_NON_STATIC_AND_OVERRIDES_STATIC, - FIELD_LESS_ACCESSIBLE_THAN_IN_SUPERCLASS, - FIELD_GENERICS_CHANGED, - - CONSTRUCTOR_PARAMS_GENERICS_CHANGED, - CONSTRUCTOR_GENERICS_CHANGED, - CONSTRUCTOR_FORMAL_TYPE_PARAMETERS_CHANGED, - CONSTRUCTOR_FORMAL_TYPE_PARAMETERS_ADDED, - CONSTRUCTOR_FORMAL_TYPE_PARAMETERS_REMOVED;*/ - - private final BreakingChangeNature nature; - - BreakingChangeKind(BreakingChangeNature nature) { - this.nature = nature; - } - - public BreakingChangeNature getNature() { - return nature; - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/BreakingChangeNature.java b/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/BreakingChangeNature.java deleted file mode 100644 index 7da6ce52..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/BreakingChangeNature.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.github.maracas.roseau.diff.changes; - -/** - * Enumerates the three possible natures of a breaking change: ADDITION, MUTATION, and DELETION. - */ -public enum BreakingChangeNature { - /** - * Indicates that the breaking change is a result of an addition to the API. - */ - ADDITION, - - /** - * Indicates that the breaking change results from an alteration of existing elements within the API. - */ - MUTATION, - - /** - * Indicates that the breaking change is a result of a deletion from the API. - */ - DELETION -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/package-info.java b/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/package-info.java deleted file mode 100644 index a775b2ef..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/diff/changes/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * This package contains the classes and types needed for API comparisons and breaking changes detection. - */ -package com.github.maracas.roseau.diff.changes; diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/package-info.java b/ConfGen/src/main/java/com/github/maracas/roseau/package-info.java deleted file mode 100644 index d5a13a68..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * This package contains the main API extraction and comparison tools. - */ - -package com.github.maracas.roseau; diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/visit/APIAlgebra.java b/ConfGen/src/main/java/com/github/maracas/roseau/visit/APIAlgebra.java deleted file mode 100644 index b330c781..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/visit/APIAlgebra.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.github.maracas.roseau.visit; - -import com.github.maracas.roseau.api.model.API; -import com.github.maracas.roseau.api.model.AnnotationDecl; -import com.github.maracas.roseau.api.model.ClassDecl; -import com.github.maracas.roseau.api.model.ConstructorDecl; -import com.github.maracas.roseau.api.model.EnumDecl; -import com.github.maracas.roseau.api.model.FieldDecl; -import com.github.maracas.roseau.api.model.InterfaceDecl; -import com.github.maracas.roseau.api.model.MethodDecl; -import com.github.maracas.roseau.api.model.ParameterDecl; -import com.github.maracas.roseau.api.model.RecordDecl; -import com.github.maracas.roseau.api.model.Symbol; -import com.github.maracas.roseau.api.model.TypeDecl; -import com.github.maracas.roseau.api.model.reference.ArrayTypeReference; -import com.github.maracas.roseau.api.model.reference.ITypeReference; -import com.github.maracas.roseau.api.model.reference.PrimitiveTypeReference; -import com.github.maracas.roseau.api.model.reference.TypeParameterReference; -import com.github.maracas.roseau.api.model.reference.TypeReference; - -public interface APIAlgebra { - T api(API it); - T classDecl(ClassDecl it); - T interfaceDecl(InterfaceDecl it); - T enumDecl(EnumDecl it); - T annotationDecl(AnnotationDecl it); - T recordDecl(RecordDecl it); - T methodDecl(MethodDecl it); - T constructorDecl(ConstructorDecl it); - T fieldDecl(FieldDecl it); - T parameterDecl(ParameterDecl it); - T typeReference(TypeReference it); - T primitiveTypeReference(PrimitiveTypeReference it); - T arrayTypeReference(ArrayTypeReference it); - T typeParameterReference(TypeParameterReference it); - - default T $(API it) { - return api(it); - } - - default T $(Symbol it) { - return switch (it) { - case RecordDecl r -> recordDecl(r); - case EnumDecl e -> enumDecl(e); - case ClassDecl c -> classDecl(c); - case InterfaceDecl i -> interfaceDecl(i); - case AnnotationDecl a -> annotationDecl(a); - case MethodDecl m -> methodDecl(m); - case ConstructorDecl c -> constructorDecl(c); - case FieldDecl f -> fieldDecl(f); - }; - } - - default T $(ParameterDecl it) { - return parameterDecl(it); - } - - default T $(ITypeReference it) { - return switch (it) { - case TypeReference typeRef -> typeReference(typeRef); - case PrimitiveTypeReference primitiveRef -> primitiveTypeReference(primitiveRef); - case ArrayTypeReference arrayRef -> arrayTypeReference(arrayRef); - case TypeParameterReference tpRef -> typeParameterReference(tpRef); - }; - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/visit/AbstractAPIVisitor.java b/ConfGen/src/main/java/com/github/maracas/roseau/visit/AbstractAPIVisitor.java deleted file mode 100644 index 0d8d741d..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/visit/AbstractAPIVisitor.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.github.maracas.roseau.visit; - -import com.github.maracas.roseau.api.model.API; -import com.github.maracas.roseau.api.model.AnnotationDecl; -import com.github.maracas.roseau.api.model.ClassDecl; -import com.github.maracas.roseau.api.model.ConstructorDecl; -import com.github.maracas.roseau.api.model.EnumDecl; -import com.github.maracas.roseau.api.model.ExecutableDecl; -import com.github.maracas.roseau.api.model.FieldDecl; -import com.github.maracas.roseau.api.model.InterfaceDecl; -import com.github.maracas.roseau.api.model.MethodDecl; -import com.github.maracas.roseau.api.model.ParameterDecl; -import com.github.maracas.roseau.api.model.RecordDecl; -import com.github.maracas.roseau.api.model.Symbol; -import com.github.maracas.roseau.api.model.TypeDecl; -import com.github.maracas.roseau.api.model.reference.ArrayTypeReference; -import com.github.maracas.roseau.api.model.reference.PrimitiveTypeReference; -import com.github.maracas.roseau.api.model.reference.TypeParameterReference; -import com.github.maracas.roseau.api.model.reference.TypeReference; - -public class AbstractAPIVisitor implements APIAlgebra { - public Visit api(API it) { - return () -> it.getAllTypes().forEach(t -> $(t).visit()); - } - - @Override - public Visit classDecl(ClassDecl it) { - return () -> { - typeDecl(it).visit(); - it.getSuperClass().ifPresent(sup -> $(sup).visit()); - it.getConstructors().forEach(cons -> $(cons).visit()); - }; - } - - @Override - public Visit interfaceDecl(InterfaceDecl it) { - return typeDecl(it); - } - - @Override - public Visit enumDecl(EnumDecl it) { - return classDecl(it); - } - - @Override - public Visit annotationDecl(AnnotationDecl it) { - return typeDecl(it); - } - - @Override - public Visit recordDecl(RecordDecl it) { - return classDecl(it); - } - - @Override - public Visit methodDecl(MethodDecl it) { - return executableDecl(it); - } - - @Override - public Visit constructorDecl(ConstructorDecl it) { - return executableDecl(it); - } - - @Override - public Visit fieldDecl(FieldDecl it) { - return () -> { - symbol(it).visit(); - if (it.getType() != null) - $(it.getType()).visit(); - }; - } - - @Override - public Visit parameterDecl(ParameterDecl it) { - return () -> { - if (it.type() != null) - $(it.type()).visit(); - }; - } - - @Override - public Visit typeReference(TypeReference it) { - return () -> {}; - } - - @Override - public Visit primitiveTypeReference(PrimitiveTypeReference it) { - return () -> {}; - } - - @Override - public Visit arrayTypeReference(ArrayTypeReference it) { - return () -> {}; - } - - @Override - public Visit typeParameterReference(TypeParameterReference it) { - return () -> {}; - } - - public Visit symbol(Symbol it) { - return () -> {}; - } - - public Visit typeDecl(TypeDecl it) { - return () -> { - symbol(it).visit(); - it.getImplementedInterfaces().forEach(intf -> $(intf).visit()); - it.getFields().forEach(field -> $(field).visit()); - it.getMethods().forEach(meth -> $(meth).visit()); - }; - } - - public Visit executableDecl(ExecutableDecl it) { - return () -> { - symbol(it).visit(); - if (it.getType() != null) - $(it.getType()).visit(); - it.getParameters().forEach(p -> $(p).visit()); - it.getThrownExceptions().forEach(e -> $(e).visit()); - }; - } -} diff --git a/ConfGen/src/main/java/com/github/maracas/roseau/visit/Visit.java b/ConfGen/src/main/java/com/github/maracas/roseau/visit/Visit.java deleted file mode 100644 index 317b4f89..00000000 --- a/ConfGen/src/main/java/com/github/maracas/roseau/visit/Visit.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.github.maracas.roseau.visit; - -@FunctionalInterface -public interface Visit { - void visit(); -} diff --git a/Samples/sampleclient/.gitignore b/ConfGenCoverage/.gitignore similarity index 100% rename from Samples/sampleclient/.gitignore rename to ConfGenCoverage/.gitignore diff --git a/ConfGenCoverage/build.gradle b/ConfGenCoverage/build.gradle new file mode 100644 index 00000000..cd077321 --- /dev/null +++ b/ConfGenCoverage/build.gradle @@ -0,0 +1,81 @@ +plugins { + id 'application' + id 'com.github.johnrengelman.shadow' version '8.1.1' +} + +group 'com.github.gilesi.confgencoverage' +version '1.0-SNAPSHOT' + + +compileJava { + options.encoding = 'UTF-8' +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} + +repositories { + mavenCentral() + //mavenLocal() + maven { + name = "IntelliJ" + url 'https://packages.jetbrains.team/maven/p/ij/intellij-dependencies' + } + maven { + name = "Roseau" + url = "https://maven.pkg.github.com/alien-tools/roseau" + credentials { + username = project.findProperty("gpr.user") + password = project.findProperty("gpr.key") + } + } +} + +dependencies { + implementation 'com.github.maracas:roseau:0.0.2-SNAPSHOT' + implementation 'com.thoughtworks.xstream:xstream:1.4.20' +} + +jar { + manifest { + attributes 'Main-Class': 'com.github.gilesi.confgencoverage.Main', + 'Multi-Release': 'true' + } +} + +application { + mainClass = 'com.github.gilesi.confgencoverage.Main' +} + +description = 'Main distribution.' + +shadowJar { + archiveBaseName.set('com.github.gilesi.confgencoverage') + archiveClassifier.set('') + archiveVersion.set('') + mergeServiceFiles() +} + +distributions { + shadow { + distributionBaseName = 'com.github.gilesi.confgencoverage' + } +} + +apply plugin: 'java' +apply plugin: 'idea' + +idea { + module { + downloadJavadoc = true + downloadSources = true + } +} + +run { + jvmArgs = [ + "-XX:InitialHeapSize=2G", + "-XX:MaxHeapSize=2G" + ] +} diff --git a/Samples/gradle/wrapper/gradle-wrapper.jar b/ConfGenCoverage/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from Samples/gradle/wrapper/gradle-wrapper.jar rename to ConfGenCoverage/gradle/wrapper/gradle-wrapper.jar diff --git a/Samples/gradle/wrapper/gradle-wrapper.properties b/ConfGenCoverage/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from Samples/gradle/wrapper/gradle-wrapper.properties rename to ConfGenCoverage/gradle/wrapper/gradle-wrapper.properties diff --git a/Samples/gradlew b/ConfGenCoverage/gradlew old mode 100644 new mode 100755 similarity index 100% rename from Samples/gradlew rename to ConfGenCoverage/gradlew diff --git a/Samples/gradlew.bat b/ConfGenCoverage/gradlew.bat similarity index 100% rename from Samples/gradlew.bat rename to ConfGenCoverage/gradlew.bat diff --git a/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/CodeType.java b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/CodeType.java new file mode 100644 index 00000000..f4f0594e --- /dev/null +++ b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/CodeType.java @@ -0,0 +1,11 @@ +package com.github.gilesi.confgencoverage; + +import java.util.EnumSet; + +public enum CodeType { + MAIN, + TEST, + SAMPLE; + + public static final EnumSet ALL = EnumSet.allOf(CodeType.class); +} diff --git a/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/Main.java b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/Main.java new file mode 100644 index 00000000..c32b23a9 --- /dev/null +++ b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/Main.java @@ -0,0 +1,177 @@ +package com.github.gilesi.confgencoverage; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.github.gilesi.confgencoverage.exceptions.UnsupportedJavaPrimitiveTypeException; +import com.github.gilesi.confgencoverage.models.Class; +import com.github.gilesi.confgencoverage.models.InstrumentationParameters; +import com.github.gilesi.confgencoverage.models.Method; +import com.github.gilesi.confgencoverage.spoon.SpoonLauncherUtilities; +import com.github.maracas.roseau.api.SpoonAPIExtractor; +import com.github.maracas.roseau.api.model.*; +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.DomDriver; +import spoon.Launcher; +import spoon.reflect.CtModel; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.*; + +public class Main { + private static final ObjectMapper objectMapper = new ObjectMapper() + .enable(SerializationFeature.INDENT_OUTPUT) + //.enable(SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL) + .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) + .disable(SerializationFeature.FAIL_ON_SELF_REFERENCES) + .disable(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS); + + private static String getClassNameFromFQN(String fullyQualifiedName) { + String parameterLessQualifiedName = fullyQualifiedName; + + if (fullyQualifiedName.contains("(")) { + parameterLessQualifiedName = fullyQualifiedName.split("\\(")[0]; + } + + String[] nameElements = parameterLessQualifiedName.split("\\."); + + return String.join(".", Arrays.stream(nameElements).limit(nameElements.length - 1).toArray(String[]::new)); + } + + private static String getMethodNameFromFQN(String fullyQualifiedName) { + String parameterLessQualifiedName = fullyQualifiedName; + + if (fullyQualifiedName.contains("(")) { + parameterLessQualifiedName = fullyQualifiedName.split("\\(")[0]; + } + + String[] nameElements = parameterLessQualifiedName.split("\\."); + + return nameElements[nameElements.length - 1]; + } + + private static void addToInstrumentationObject(InstrumentationParameters instrumentationParameters, String className, String methodName, String methodDescriptor) { + for (Class classToInstrument : instrumentationParameters.InstrumentedAPIClasses) { + if (classToInstrument.ClassName.equals(className)) { + for (Method methodParameter : classToInstrument.ClassMethods) { + if (methodParameter.MethodName.equals(methodName)) { + for (String descriptorToInstrument : methodParameter.MethodDescriptors) { + if (descriptorToInstrument.equals(methodDescriptor)) { + // Element is already in the config, return now + return; + } + } + + // Element doesn't have a descriptor in the config, add now + methodParameter.MethodDescriptors.add(methodDescriptor); + return; + } + } + + // Element doesn't have a method in the config, add now + Method methodParameter = new Method(); + methodParameter.MethodName = methodName; + methodParameter.MethodDescriptors = new ArrayList<>(); + methodParameter.MethodDescriptors.add(methodDescriptor); + + classToInstrument.ClassMethods.add(methodParameter); + return; + } + } + + // Element doesn't have a class in the config, add now + Class classParameter = new Class(); + classParameter.ClassName = className; + classParameter.ClassMethods = new ArrayList<>(); + classParameter.ClassDescriptors = new ArrayList<>(); + Method methodParameter = new Method(); + methodParameter.MethodName = methodName; + methodParameter.MethodDescriptors = new ArrayList<>(); + methodParameter.MethodDescriptors.add(methodDescriptor); + classParameter.ClassMethods.add(methodParameter); + + instrumentationParameters.InstrumentedAPIClasses.add(classParameter); + } + + private static void addToInstrumentationObject(InstrumentationParameters instrumentationParameters, String className, String constructorDescriptor) { + for (Class classToInstrument : instrumentationParameters.InstrumentedAPIClasses) { + if (classToInstrument.ClassName.equals(className)) { + for (String descriptorToInstrument : classToInstrument.ClassDescriptors) { + if (descriptorToInstrument.equals(constructorDescriptor)) { + // Element is already in the config, return now + return; + } + + // Element doesn't have a descriptor in the config, add now + classToInstrument.ClassDescriptors.add(constructorDescriptor); + return; + } + } + } + + // Element doesn't have a class in the config, add now + Class classParameter = new Class(); + classParameter.ClassName = className; + classParameter.ClassMethods = new ArrayList<>(); + classParameter.ClassDescriptors = new ArrayList<>(); + classParameter.ClassDescriptors.add(constructorDescriptor); + + instrumentationParameters.InstrumentedAPIClasses.add(classParameter); + } + + public static void main(String[] args) throws UnsupportedJavaPrimitiveTypeException, IOException { + String apiReportOutputLocation = args[0]; + String traceOutputLocation = args[1]; + String libraryProjectLocation = args[2]; + + Path apiReportOutputPath = Path.of(apiReportOutputLocation); + + System.out.println("Processing Library Project..."); + + Launcher libraryLauncher = SpoonLauncherUtilities.getCommonLauncherInstance(); + SpoonLauncherUtilities.applyProjectToLauncher(libraryLauncher, Path.of(libraryProjectLocation), EnumSet.of(CodeType.MAIN)); + CtModel libraryModel = libraryLauncher.buildModel(); + + // API model for libraries + API libraryApiModel = new SpoonAPIExtractor().extractAPI(libraryModel); + + InstrumentationParameters instrumentationParameters = new InstrumentationParameters(); + instrumentationParameters.InstrumentedAPIClasses = new ArrayList<>(); + instrumentationParameters.traceOutputLocation = traceOutputLocation; + + for (ClassDecl classDecl : libraryApiModel.getExportedTypes() + .filter(ClassDecl.class::isInstance) + .map(ClassDecl.class::cast).toList()) { + + for (ConstructorDecl constructor : classDecl.getConstructors()) { + String className = getClassNameFromFQN(constructor.getQualifiedName()); + String methodDescriptor = RoseauDescriptor.getDescriptor(constructor, false); + + addToInstrumentationObject(instrumentationParameters, className, methodDescriptor); + } + + for (MethodDecl method : classDecl.getAllMethods().toList()) { + String FQN = method.getQualifiedName(); + String className = getClassNameFromFQN(FQN); + String methodName = getMethodNameFromFQN(FQN); + String methodDescriptor = RoseauDescriptor.getDescriptor(method, false); + + addToInstrumentationObject(instrumentationParameters, className, methodName, methodDescriptor); + } + } + + BufferedWriter bufferedWriter = Files.newBufferedWriter(apiReportOutputPath, StandardOpenOption.CREATE); + + try { + String serializedJsonString = objectMapper.writeValueAsString(instrumentationParameters); + bufferedWriter.write(serializedJsonString); + } catch (Exception e) { + System.out.println(String.format("ERROR: Cannot serialize specific argument: %s", e)); + } + + bufferedWriter.close(); + } +} \ No newline at end of file diff --git a/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/RoseauDescriptor.java b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/RoseauDescriptor.java new file mode 100644 index 00000000..19be6166 --- /dev/null +++ b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/RoseauDescriptor.java @@ -0,0 +1,88 @@ +package com.github.gilesi.confgencoverage; + +import com.github.gilesi.confgencoverage.exceptions.UnsupportedJavaPrimitiveTypeException; +import com.github.maracas.roseau.api.model.ConstructorDecl; +import com.github.maracas.roseau.api.model.MethodDecl; +import com.github.maracas.roseau.api.model.ParameterDecl; +import com.github.maracas.roseau.api.model.reference.*; + +import java.util.List; + +public class RoseauDescriptor { + public static String getDescriptor(ConstructorDecl constructorDecl, boolean handleTypeArgs) throws UnsupportedJavaPrimitiveTypeException { + StringBuilder descriptor = new StringBuilder().append('('); + for (ParameterDecl parameterType : constructorDecl.getParameters()) { + descriptor.append(getDescriptor(parameterType, handleTypeArgs)); + } + return descriptor.append(")V").toString(); + } + + public static String getDescriptor(MethodDecl methodDecl, boolean handleTypeArgs) throws UnsupportedJavaPrimitiveTypeException { + StringBuilder descriptor = new StringBuilder().append('('); + for (ParameterDecl parameterType : methodDecl.getParameters()) { + descriptor.append(getDescriptor(parameterType, handleTypeArgs)); + } + return descriptor.append(')').append(getDescriptor(methodDecl.getType(), handleTypeArgs)).toString(); + } + + public static String getDescriptor(ParameterDecl parameterDecl, boolean handleTypeArgs) throws UnsupportedJavaPrimitiveTypeException { + return getDescriptor(parameterDecl.type(), handleTypeArgs); + } + + public static String getDescriptor(ITypeReference iTypeReference, boolean handleTypeArgs) throws UnsupportedJavaPrimitiveTypeException { + switch (iTypeReference) { + case ArrayTypeReference arrayTypeReference -> { + String rest = getDescriptor(arrayTypeReference.componentType(), handleTypeArgs); + for (int i = 0; i < arrayTypeReference.dimension(); i++) { + rest = "[" + rest; + } + return rest; + } + case PrimitiveTypeReference primitiveTypeReference -> { + String name = primitiveTypeReference.getQualifiedName(); + return switch (name) { + case "int" -> "I"; + case "void" -> "V"; + case "boolean" -> "Z"; + case "byte" -> "B"; + case "char" -> "C"; + case "short" -> "S"; + case "double" -> "D"; + case "float" -> "F"; + case "long" -> "J"; + default -> throw new UnsupportedJavaPrimitiveTypeException(name); + }; + } + case TypeParameterReference ignored -> { + //String name = typeParameterReference.getQualifiedName(); + //return "L" + name.replace('.', '/') + ";"; + // That's going to be U getSmething() in the JVM, so return object. + return "Ljava/lang/Object;"; + } + case WildcardTypeReference ignored -> { + //String name = typeDecl.getQualifiedName(); + //return name; + // That's going to be U getSmething() in the JVM, so return object. + return "Ljava/lang/Object;"; + } + case TypeReference typeReference -> { + String typeArgumentString = ""; + if (handleTypeArgs) { + List typeArgs = typeReference.getTypeArguments(); + if (!typeArgs.isEmpty()) { + // Check join string here to be very sure against spec and test it + typeArgumentString = "<%s>".formatted(String.join("", typeArgs.stream().map(t -> { + try { + return getDescriptor(t, true); + } catch (UnsupportedJavaPrimitiveTypeException e) { + throw new RuntimeException(e); + } + }).toList())); + } + } + String name = typeReference.getQualifiedName(); + return "L%s%s;".formatted(name.replace('.', '/'), typeArgumentString); + } + } + } +} diff --git a/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/exceptions/UnsupportedJavaPrimitiveTypeException.java b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/exceptions/UnsupportedJavaPrimitiveTypeException.java new file mode 100644 index 00000000..f324c951 --- /dev/null +++ b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/exceptions/UnsupportedJavaPrimitiveTypeException.java @@ -0,0 +1,7 @@ +package com.github.gilesi.confgencoverage.exceptions; + +public class UnsupportedJavaPrimitiveTypeException extends Exception { + public UnsupportedJavaPrimitiveTypeException(String primitive) { + super("Unsupported primitive type: %s".formatted(primitive)); + } +} diff --git a/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/models/Class.java b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/models/Class.java new file mode 100644 index 00000000..934695c9 --- /dev/null +++ b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/models/Class.java @@ -0,0 +1,9 @@ +package com.github.gilesi.confgencoverage.models; + +import java.util.List; + +public class Class { + public String ClassName; + public List ClassMethods; + public List ClassDescriptors; +} diff --git a/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/models/InstrumentationParameters.java b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/models/InstrumentationParameters.java new file mode 100644 index 00000000..ddac58e6 --- /dev/null +++ b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/models/InstrumentationParameters.java @@ -0,0 +1,8 @@ +package com.github.gilesi.confgencoverage.models; + +import java.util.List; + +public class InstrumentationParameters { + public List InstrumentedAPIClasses; + public String traceOutputLocation; +} diff --git a/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/models/Method.java b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/models/Method.java new file mode 100644 index 00000000..1c7ddc75 --- /dev/null +++ b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/models/Method.java @@ -0,0 +1,8 @@ +package com.github.gilesi.confgencoverage.models; + +import java.util.List; + +public class Method { + public String MethodName; + public List MethodDescriptors; +} \ No newline at end of file diff --git a/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/spoon/SpoonLauncherUtilities.java b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/spoon/SpoonLauncherUtilities.java new file mode 100644 index 00000000..ab7ef5ab --- /dev/null +++ b/ConfGenCoverage/src/main/java/com/github/gilesi/confgencoverage/spoon/SpoonLauncherUtilities.java @@ -0,0 +1,222 @@ +package com.github.gilesi.confgencoverage.spoon; + +import spoon.Launcher; +import spoon.MavenLauncher; +import spoon.SpoonException; +import spoon.support.StandardEnvironment; +import spoon.support.compiler.SpoonPom; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.InvalidParameterException; +import java.util.*; +import java.util.regex.Pattern; + +import com.github.gilesi.confgencoverage.CodeType; + +public class SpoonLauncherUtilities { + public static Launcher getCommonLauncherInstance() { + Launcher launcher = new Launcher(); + + // Ignore missing types/classpath related errors + launcher.getEnvironment().setNoClasspath(true); + // Proceed even if we find the same type twice; affects the precision of the result + launcher.getEnvironment().setIgnoreDuplicateDeclarations(true); + // Ignore files with syntax/JLS violations and proceed + launcher.getEnvironment().setIgnoreSyntaxErrors(true); + // Ignore comments + launcher.getEnvironment().setCommentEnabled(false); + // Set Java version + // Note: even when using the MavenLauncher, it's sometimes not properly inferred, better be safe + launcher.getEnvironment().setComplianceLevel(17); + + return launcher; + } + + private static Collection getPomProjectPaths(String mavenProject, spoon.MavenLauncher.SOURCE_TYPE sourceType) throws SpoonException { + SpoonPom model; + ArrayList paths = new ArrayList<>(); + + File mavenProjectFile = new File(mavenProject); + if (!mavenProjectFile.exists()) { + throw new SpoonException("%s does not exist.".formatted(mavenProject)); + } + + Pattern profileFilter = Pattern.compile("^$"); + + try { + model = new SpoonPom(mavenProject, sourceType, new StandardEnvironment(), profileFilter); + } catch (Exception e) { + throw new SpoonException("Unable to read the pom", e); + } + + // app source + if (spoon.MavenLauncher.SOURCE_TYPE.APP_SOURCE == sourceType || spoon.MavenLauncher.SOURCE_TYPE.ALL_SOURCE == sourceType) { + List sourceDirectories = model.getSourceDirectories(); + for (File sourceDirectory : sourceDirectories) { + System.out.printf("Detected Project MAIN Source Directory at: %s%n", sourceDirectory); + paths.add(sourceDirectory.toPath()); + } + } + + // test source + if (spoon.MavenLauncher.SOURCE_TYPE.TEST_SOURCE == sourceType || spoon.MavenLauncher.SOURCE_TYPE.ALL_SOURCE == sourceType) { + List testSourceDirectories = model.getTestDirectories(); + for (File sourceDirectory : testSourceDirectories) { + System.out.printf("Detected Project TEST Source Directory at: %s%n", sourceDirectory); + paths.add(sourceDirectory.toPath()); + } + } + + return paths; + } + + private static int getPomProjectSourceComplianceLevel(String mavenProject) throws SpoonException { + SpoonPom model; + + File mavenProjectFile = new File(mavenProject); + if (!mavenProjectFile.exists()) { + throw new SpoonException("%s does not exist.".formatted(mavenProject)); + } + + Pattern profileFilter = Pattern.compile("^$"); + + try { + model = new SpoonPom(mavenProject, MavenLauncher.SOURCE_TYPE.ALL_SOURCE, new StandardEnvironment(), profileFilter); + } catch (Exception e) { + throw new SpoonException("Unable to read the pom", e); + } + + return model.getSourceVersion(); + } + + public static int getProjectSourceComplianceLevel(Path location) { + try { + return getPomProjectSourceComplianceLevel(location.toAbsolutePath().toString()); + } catch (Exception ignore) { + System.out.println("Unable to get project source compliance level. %s".formatted(ignore)); + } + + return 11; + } + + private static Path getPossibleSamplePath(Path location) { + Path examplesPath = location.resolve("examples"); + Path samplesPath = location.resolve("samples"); + Path srcPath = location.resolve("src"); + + if (Files.exists(examplesPath)) { + return examplesPath; + } + + if (Files.exists(samplesPath)) { + return samplesPath; + } + + if (Files.exists(srcPath)) { + return getPossibleSamplePath(srcPath); + } + + return null; + } + + private static Path getPossibleMainPath(Path location) { + Path mainPath = location.resolve("main"); + Path srcPath = location.resolve("src"); + + if (Files.exists(mainPath)) { + return mainPath; + } + + if (Files.exists(srcPath)) { + return getPossibleMainPath(srcPath); + } + + return null; + } + + private static Path getPossibleTestPath(Path location) { + Path testPath = location.resolve("test"); + Path srcPath = location.resolve("src"); + + if (Files.exists(testPath)) { + return testPath; + } + + if (Files.exists(srcPath)) { + return getPossibleTestPath(srcPath); + } + + return null; + } + + private static MavenLauncher.SOURCE_TYPE getSourceTypeValue(EnumSet codeTypes) throws InvalidParameterException { + if (codeTypes.contains(CodeType.MAIN) && codeTypes.contains(CodeType.TEST)) { + return MavenLauncher.SOURCE_TYPE.ALL_SOURCE; + } else if (codeTypes.contains(CodeType.MAIN)) { + return MavenLauncher.SOURCE_TYPE.APP_SOURCE; + } else if (codeTypes.contains(CodeType.TEST)) { + return MavenLauncher.SOURCE_TYPE.TEST_SOURCE; + } else { + throw new InvalidParameterException(); + } + } + + public static void applyProjectToLauncher(Launcher launcher, Path projectLocation, EnumSet codeTypes) { + launcher.getEnvironment().setComplianceLevel(getProjectSourceComplianceLevel(projectLocation)); + + Collection paths = getProjectPaths(projectLocation, codeTypes); + for (Path path : paths) { + launcher.addInputResource(path.toAbsolutePath().toString()); + } + } + + public static Collection getProjectPaths(Path projectLocation, EnumSet codeTypes) { + Collection paths = new HashSet<>(); + + System.out.printf("Trying to detect source directories for project: %s with types: %s%n", projectLocation, codeTypes); + + if (codeTypes.contains(CodeType.MAIN) || codeTypes.contains(CodeType.TEST)) { + try { + MavenLauncher.SOURCE_TYPE sourceType = getSourceTypeValue(codeTypes); + paths.addAll(getPomProjectPaths(projectLocation.toString(), sourceType)); + } catch (Exception ignored) { + System.out.println("Unable to get project code %s".formatted(ignored)); + System.out.println("WARNING: Falling back to manual detection of project source paths because no maven pom could be parsed for the passed project"); + if (codeTypes.contains(CodeType.MAIN)) { + Path mainPath = getPossibleMainPath(projectLocation); + if (mainPath != null) { + System.out.printf("Detected Project MAIN Source Directory at: %s%n", mainPath); + paths.add(mainPath); + } + } + + if (codeTypes.contains(CodeType.TEST)) { + Path testPath = getPossibleTestPath(projectLocation); + if (testPath != null) { + System.out.printf("Detected Project TEST Source Directory at: %s%n", testPath); + paths.add(testPath); + } + } + } + } + + if (codeTypes.contains(CodeType.SAMPLE)) { + Path samplePath = getPossibleSamplePath(projectLocation); + if (samplePath != null) { + System.out.printf("Detected Project SAMPLE Source Directory at: %s%n", samplePath); + paths.add(samplePath); + } + } + + if (paths.isEmpty()) { + System.out.println("WARNING: Adding the entire directory because no project got detected and CodeTypes = ALL!"); + paths.add(projectLocation); + } + + System.out.printf("Finished detecting source directories for project: %s%n", projectLocation); + + return paths; + } +} diff --git a/Samples/samplelibrary/.gitignore b/Coverage/.gitignore similarity index 100% rename from Samples/samplelibrary/.gitignore rename to Coverage/.gitignore diff --git a/Coverage/build.gradle b/Coverage/build.gradle new file mode 100644 index 00000000..130749be --- /dev/null +++ b/Coverage/build.gradle @@ -0,0 +1,78 @@ +plugins { + id 'application' + id 'com.github.johnrengelman.shadow' version '8.1.1' +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +group = 'com.github.gilesi.coverage' +version = '1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + implementation 'net.bytebuddy:byte-buddy:1.15.10' + implementation 'net.bytebuddy:byte-buddy-agent:1.15.8' + implementation 'com.thoughtworks.xstream:xstream:1.4.20' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.2' +} + +jar { + manifest { + attributes 'Premain-Class': 'com.github.gilesi.coverage.Agent', + 'Agent-Class': 'com.github.gilesi.coverage.Agent', + 'Launcher-Agent-Class': 'com.github.gilesi.coverage.Agent', + 'Can-Retransform-Classes': 'true', + 'Can-Redefine-Classes': 'true', + 'Main-Class': 'com.github.gilesi.coverage.Agent', + 'Multi-Release': 'true' + } +} + +application { + mainClass = 'com.github.gilesi.coverage.Agent' +} + +description = 'Agent distribution.' + +shadowJar { + archiveBaseName.set('com.github.gilesi.coverage') + archiveClassifier.set('') + archiveVersion.set('') + mergeServiceFiles() + + // Filtering shadow jar contents by file pattern. + exclude('LICENSE.txt') + exclude('META-INF/LICENSE') + exclude('META-INF/LICENSE.txt') + exclude('META-INF/NOTICE') + exclude('META-INF/NOTICE.txt') + + // Relocating dependencies. + relocate('com.fasterxml', 'gilesi.com.fasterxml') + relocate('com.thoughtworks', 'gilesi.com.thoughtworks') + relocate('net', 'gilesi.net') + relocate('io', 'gilesi.io') + //relocate('org', 'gilesi.org') // causes issues with xtream if done +} + +distributions { + shadow { + distributionBaseName = 'com.github.gilesi.coverage' + } +} + +apply plugin: 'java' +apply plugin: 'idea' + +idea { + module { + downloadJavadoc = true + downloadSources = true + } +} \ No newline at end of file diff --git a/TraceView/gradle/wrapper/gradle-wrapper.jar b/Coverage/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from TraceView/gradle/wrapper/gradle-wrapper.jar rename to Coverage/gradle/wrapper/gradle-wrapper.jar diff --git a/TraceView/gradle/wrapper/gradle-wrapper.properties b/Coverage/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from TraceView/gradle/wrapper/gradle-wrapper.properties rename to Coverage/gradle/wrapper/gradle-wrapper.properties diff --git a/TraceView/gradlew b/Coverage/gradlew old mode 100644 new mode 100755 similarity index 100% rename from TraceView/gradlew rename to Coverage/gradlew diff --git a/TraceView/gradlew.bat b/Coverage/gradlew.bat similarity index 100% rename from TraceView/gradlew.bat rename to Coverage/gradlew.bat diff --git a/Coverage/settings.gradle b/Coverage/settings.gradle new file mode 100644 index 00000000..43813bd7 --- /dev/null +++ b/Coverage/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'gilesi' + diff --git a/Coverage/src/main/java/com/github/gilesi/confgencoverage/models/Class.java b/Coverage/src/main/java/com/github/gilesi/confgencoverage/models/Class.java new file mode 100644 index 00000000..10b23867 --- /dev/null +++ b/Coverage/src/main/java/com/github/gilesi/confgencoverage/models/Class.java @@ -0,0 +1,7 @@ +package com.github.gilesi.confgencoverage.models; + +public class Class { + public String ClassName; + public Method[] ClassMethods; + public String[] ClassDescriptors; +} diff --git a/Coverage/src/main/java/com/github/gilesi/confgencoverage/models/CoverageParameters.java b/Coverage/src/main/java/com/github/gilesi/confgencoverage/models/CoverageParameters.java new file mode 100644 index 00000000..5a43b75b --- /dev/null +++ b/Coverage/src/main/java/com/github/gilesi/confgencoverage/models/CoverageParameters.java @@ -0,0 +1,6 @@ +package com.github.gilesi.confgencoverage.models; + +public class CoverageParameters { + public Class[] InstrumentedAPIClasses; + public String traceOutputLocation; +} diff --git a/Coverage/src/main/java/com/github/gilesi/confgencoverage/models/Method.java b/Coverage/src/main/java/com/github/gilesi/confgencoverage/models/Method.java new file mode 100644 index 00000000..1e88a5f9 --- /dev/null +++ b/Coverage/src/main/java/com/github/gilesi/confgencoverage/models/Method.java @@ -0,0 +1,6 @@ +package com.github.gilesi.confgencoverage.models; + +public class Method { + public String MethodName; + public String[] MethodDescriptors; +} \ No newline at end of file diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/Agent.java b/Coverage/src/main/java/com/github/gilesi/coverage/Agent.java new file mode 100644 index 00000000..1603c373 --- /dev/null +++ b/Coverage/src/main/java/com/github/gilesi/coverage/Agent.java @@ -0,0 +1,149 @@ +package com.github.gilesi.coverage; + +import com.github.gilesi.confgencoverage.models.Class; +import com.github.gilesi.confgencoverage.models.CoverageParameters; +import com.github.gilesi.coverage.visitors.MethodInstrumentor; +import net.bytebuddy.agent.ByteBuddyAgent; + +import java.io.File; +import java.io.IOException; +import java.lang.instrument.Instrumentation; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/* + Our main agent class + */ +public class Agent { + public static Path generatedLogsFolderPath; + public static Path generatedMarkersFolderPath; + public static Path generatedReachedListFolderPath; + + /* + Our primary method to install the agent required to trace methods during execution + We take in as parameter an XML file with the list of methods to instrument and their class + */ + private static void installAgent(String arg, Instrumentation inst) { + if (!RunMilestoneService.shouldRun()) { + return; + } + + // Get the parameters + File configurationFile = new File(arg); + if (!configurationFile.exists()) { + System.err.printf("ERROR: the passed in configuration file (%s) does not exist or could not be found!%n", arg); + return; + } + + CoverageParameters coverageParameters; + + try { + coverageParameters = ConfigurationReader.parseCoverageparameters(configurationFile); + } catch (IOException ioException) { + System.err.printf("ERROR: Could not parse coverage configuration file (%s)!%n", arg); + System.err.println(ioException.getMessage()); + ioException.printStackTrace(); + return; + } + + String generatedFolderLocation = coverageParameters.traceOutputLocation; + Path generatedFolderPath = Paths.get(generatedFolderLocation); + generatedLogsFolderPath = generatedFolderPath.resolve("logs"); + generatedMarkersFolderPath = generatedFolderPath.resolve("markers"); + generatedReachedListFolderPath = generatedFolderPath.resolve("reached"); + + if (!Files.isDirectory(generatedFolderPath)) { + try { + Files.createDirectory(generatedFolderPath); + } catch (IOException ioException) { + System.err.printf("ERROR: Could not create output directory (%s)!%n", arg); + System.err.println(ioException.getMessage()); + ioException.printStackTrace(); + return; + } + } + + if (!Files.isDirectory(generatedLogsFolderPath)) { + try { + Files.createDirectory(generatedLogsFolderPath); + } catch (IOException ioException) { + System.err.printf("ERROR: Could not create logs output directory (%s)!%n", arg); + System.err.println(ioException.getMessage()); + ioException.printStackTrace(); + return; + } + } + + if (!Files.isDirectory(generatedMarkersFolderPath)) { + try { + Files.createDirectory(generatedMarkersFolderPath); + } catch (IOException ioException) { + System.err.printf("ERROR: Could not create markers output directory (%s)!%n", arg); + System.err.println(ioException.getMessage()); + ioException.printStackTrace(); + return; + } + } + + if (!Files.isDirectory(generatedReachedListFolderPath)) { + try { + Files.createDirectory(generatedReachedListFolderPath); + } catch (IOException ioException) { + System.err.printf("ERROR: Could not create reached list output directory (%s)!%n", arg); + System.err.println(ioException.getMessage()); + ioException.printStackTrace(); + return; + } + } + + RunMilestoneService.writeMarker(); + + // Add a shutdown hook to save all logs at exit + Runtime.getRuntime().addShutdownHook(new Thread(Logger::SaveLogResults)); + Runtime.getRuntime().addShutdownHook(new Thread(ReachedCollector::SaveReacedResults)); + + setupCoverage(inst, coverageParameters.InstrumentedAPIClasses); + } + + private static void setupCoverage(Instrumentation inst, Class[] InstrumentedAPIClasses) { + // First, install the ByteBuddy Agent required to redefine classes during execution + // Sometimes this can fail, so try to catch it + try { + ByteBuddyAgent.install(); + } catch (Throwable e) { + System.err.println("ERROR: Could not install byte buddy agent!"); + System.err.println(e.getMessage()); + e.printStackTrace(); + return; + } + + // Instrument every class + for (Class coverageParameter : InstrumentedAPIClasses) { + MethodInstrumentor.instrumentClassForTracingAgent(inst, coverageParameter); + } + } + + /* + premain is invoked when an agent is started before the application. + Agents invoked using premain are specified with the -javaagent switch. + (https://stackoverflow.com/a/19789168) + */ + public static void premain(String arg, Instrumentation inst) { + installAgent(arg, inst); + } + + /* + agentmain is invoked when an agent is started after the application is already running. + Agents started with agentmain can be attached programmatically using the Sun tools API (for Sun/Oracle JVMs only + -- the method for introducing dynamic agents is implementation-dependent). + (https://stackoverflow.com/a/19789168) + */ + public static void agentmain(String arg, Instrumentation inst) { + installAgent(arg, inst); + } + + public static void main(String[] args) { + System.err.println("This program cannot be executed standalone."); + } +} diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/ConfigurationReader.java b/Coverage/src/main/java/com/github/gilesi/coverage/ConfigurationReader.java new file mode 100644 index 00000000..29c280a6 --- /dev/null +++ b/Coverage/src/main/java/com/github/gilesi/coverage/ConfigurationReader.java @@ -0,0 +1,21 @@ +package com.github.gilesi.coverage; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.github.gilesi.confgencoverage.models.CoverageParameters; + +import java.io.File; +import java.io.IOException; + +public class ConfigurationReader { + public static final ObjectMapper objectMapper = new ObjectMapper() + .enable(SerializationFeature.INDENT_OUTPUT) + //.enable(SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL) + .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) + .disable(SerializationFeature.FAIL_ON_SELF_REFERENCES) + .disable(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS); + + public static CoverageParameters parseCoverageparameters(File file) throws IOException { + return objectMapper.readValue(file, CoverageParameters.class); + } +} diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/Logger.java b/Coverage/src/main/java/com/github/gilesi/coverage/Logger.java new file mode 100644 index 00000000..7fe41f0e --- /dev/null +++ b/Coverage/src/main/java/com/github/gilesi/coverage/Logger.java @@ -0,0 +1,58 @@ +package com.github.gilesi.coverage; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class Logger { + private static final Map> LOGGER = new HashMap<>(); + private static int LOGGERINSTANCE = 0; + + public static void SaveLogResults() { + int mainPadding = 1; + String logFileName = String.format("%s%sgilesi.coverage_%d.log", Agent.generatedLogsFolderPath, File.separatorChar, mainPadding); + + while (Files.exists(Paths.get(logFileName))) { + logFileName = String.format("%s%sgilesi.coverage_%d.log", Agent.generatedLogsFolderPath, File.separatorChar, ++mainPadding); + } + + ArrayList logLines = new ArrayList<>(); + for (int loggerInstance = 0; loggerInstance < LOGGERINSTANCE; loggerInstance++) { + ArrayList logs = LOGGER.get(loggerInstance); + if (logs != null) { + for (String log : logs) { + logLines.add(String.format("%d: %s", loggerInstance, log)); + } + } + } + + if (logLines.isEmpty()) { + return; + } + + try { + Files.write(new File(logFileName).toPath(), logLines); + } catch (Throwable e) { + System.err.println("ERROR while saving logs"); + e.printStackTrace(); + } + } + + public static int GetInstance() { + return LOGGERINSTANCE++; + } + + public static void Log(int loggerInstance, String log) { + if (LOGGER.containsKey(loggerInstance)) { + ArrayList logs = LOGGER.get(loggerInstance); + logs.add(log); + } else { + ArrayList logs = new ArrayList<>(); + logs.add(log); + LOGGER.put(loggerInstance, logs); + } + } +} diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/ReachedCollector.java b/Coverage/src/main/java/com/github/gilesi/coverage/ReachedCollector.java new file mode 100644 index 00000000..94cd1819 --- /dev/null +++ b/Coverage/src/main/java/com/github/gilesi/coverage/ReachedCollector.java @@ -0,0 +1,39 @@ +package com.github.gilesi.coverage; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; + +public class ReachedCollector { + private static final ArrayList LOGGER = new ArrayList<>(); + + public static void SaveReacedResults() { + int mainPadding = 1; + String logFileName = String.format("%s%sgilesi.coverage_list_%d.log", Agent.generatedReachedListFolderPath, File.separatorChar, mainPadding); + + while (Files.exists(Paths.get(logFileName))) { + logFileName = String.format("%s%sgilesi.coverage_list_%d.log", Agent.generatedReachedListFolderPath, File.separatorChar, ++mainPadding); + } + + ArrayList logLines = new ArrayList<>(); + for (String log : LOGGER) { + logLines.add(log); + } + + if (logLines.isEmpty()) { + return; + } + + try { + Files.write(new File(logFileName).toPath(), logLines); + } catch (Throwable e) { + System.err.println("ERROR while saving reached results"); + e.printStackTrace(); + } + } + + public static void Log(String log) { + LOGGER.add(log); + } +} diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/RunMilestoneService.java b/Coverage/src/main/java/com/github/gilesi/coverage/RunMilestoneService.java new file mode 100644 index 00000000..bafac676 --- /dev/null +++ b/Coverage/src/main/java/com/github/gilesi/coverage/RunMilestoneService.java @@ -0,0 +1,46 @@ +package com.github.gilesi.coverage; + +import java.io.File; +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; + +public class RunMilestoneService { + public static void writeMarker() { + RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean(); + List arguments = runtimeMxBean.getInputArguments(); + + int mainPadding = 1; + String tracesFileName = String.format("%s%sgilesi.coverage_loaded_%d.marker", Agent.generatedMarkersFolderPath, File.separatorChar, mainPadding); + + while (Files.exists(Paths.get(tracesFileName))) { + tracesFileName = String.format("%s%sgilesi.coverage_loaded_%d.marker", Agent.generatedMarkersFolderPath, File.separatorChar, ++mainPadding); + } + + try { + Files.write(new File(tracesFileName).toPath(), arguments); + } catch (Throwable e) { + System.err.println("ERROR while saving marker"); + e.printStackTrace(); + } + } + + public static boolean shouldRun() { + RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean(); + List arguments = runtimeMxBean.getInputArguments(); + + for (String argument : arguments) { + if (argument.startsWith("-Dmaven.home=")) { + return false; + } else if (argument.startsWith("-Dorg.gradle.appname=")) { + return false; + } else if (argument.startsWith("-Dnet.bytebuddy.agent.attacher.dump=")) { + return false; + } + } + + return true; + } +} diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/visitors/CommonAdvisor.java b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/CommonAdvisor.java new file mode 100644 index 00000000..f09254ba --- /dev/null +++ b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/CommonAdvisor.java @@ -0,0 +1,150 @@ +package com.github.gilesi.coverage.visitors; + +import com.github.gilesi.coverage.Logger; +import com.github.gilesi.coverage.ReachedCollector; + +import java.util.ArrayList; +import java.util.Arrays; + +public class CommonAdvisor { + private static final String JUNIT_REFLECTION_UTILS_INVOKE_METHOD_FQN = "org.junit.platform.commons.util.ReflectionUtils.invokeMethod"; + private static final String JUNIT_FRAMEWORK_METHOD_RUN_REFLECTIVE_CALL = "org.junit.runners.model.FrameworkMethod$1.runReflectiveCall"; + private static final String UNKNOWN_TEST_METHOD_NAME = "UnknownTestPackage.UnknownTestClass.Unknown"; + private static final ArrayList StackTraces = new ArrayList<>(); + + public static Object commonEnter() { + StackTraceElement[] stackTraceElements = new Throwable().getStackTrace(); + String[] stackTrace = Arrays.stream(stackTraceElements).skip(2).map(t -> String.format("%s.%s", t.getClassName(), t.getMethodName())).toArray(String[]::new); + + if (isAcceptedTrace(stackTrace)) { + // Print reached api method here. + + ReachedCollector.Log(StackTraces.get(StackTraces.size() - 1)[0]); + + return "valid"; + } else { + return null; + } + } + + public static void commonExit(Object entryContext) { + if (entryContext == null) { + return; + } + + StackTraceElement[] stackTraceElements = new Throwable().getStackTrace(); + String[] currentStackTrace = Arrays.stream(stackTraceElements).skip(2).map(t -> String.format("%s.%s", t.getClassName(), t.getMethodName())).toArray(String[]::new); + + synchronized (StackTraces) { + // Remove existing stacktrace + for (int StoredPreviousStackTraceIndex = 0; StoredPreviousStackTraceIndex < StackTraces.size(); StoredPreviousStackTraceIndex++) { + String[] StoredPreviousStackTrace = StackTraces.get(StoredPreviousStackTraceIndex); + + if (StoredPreviousStackTrace.length != currentStackTrace.length) { + continue; + } + + boolean match = true; + + for (int i = 0; i < StoredPreviousStackTrace.length; i++) { + String methodName = StoredPreviousStackTrace[i]; + if (!methodName.equals(currentStackTrace[i])) { + match = false; + break; + } + } + + if (match) { + StackTraces.remove(StoredPreviousStackTraceIndex); + break; + } + } + } + } + + private static String getTestMethodName(String[] stackTrace) { + for (int i = 3; i < stackTrace.length; i++) { + String methodName = stackTrace[i]; + if (methodName.equals(JUNIT_REFLECTION_UTILS_INVOKE_METHOD_FQN)) { + return stackTrace[i - 3]; + } + } + + for (int i = 5; i < stackTrace.length; i++) { + String methodName = stackTrace[i]; + if (methodName.equals(JUNIT_FRAMEWORK_METHOD_RUN_REFLECTIVE_CALL)) { + return stackTrace[i - 5]; + } + } + + return UNKNOWN_TEST_METHOD_NAME; + } + + private static String[] getCutOffStackTrace(String[] stackTrace) { + String testName = getTestMethodName(stackTrace); + ArrayList cutOffStackTrace = new ArrayList<>(); + for (String stackTraceMethodName : stackTrace) { + if (stackTraceMethodName.startsWith("org.junit.") || + stackTraceMethodName.startsWith("sun.") || + stackTraceMethodName.startsWith("jdk.") || + stackTraceMethodName.startsWith("java.")) { + continue; + } + + cutOffStackTrace.add(stackTraceMethodName); + + if (stackTraceMethodName.equals(testName)) { + break; + } + } + + return cutOffStackTrace.stream().toArray(String[]::new); + } + + private static boolean isAcceptedTrace(String[] stackTrace) { + synchronized (StackTraces) { + int loggerInstance = Logger.GetInstance(); + + stackTrace = getCutOffStackTrace(stackTrace); + + Logger.Log(loggerInstance, "++CommonAdvisor::isAcceptedTrace"); + + Logger.Log(loggerInstance, " Incoming StackTrace:"); + for (String methodName : stackTrace) { + Logger.Log(loggerInstance, String.format(" %s", methodName)); + } + + for (String[] previousStackTrace : StackTraces) { + previousStackTrace = getCutOffStackTrace(previousStackTrace); + + // An existing stacktrace can only be smaller than us if they overlap + if (previousStackTrace.length >= stackTrace.length) { + continue; + } + + boolean matchesExistingStackTrace = true; + for (int i = 0; i < previousStackTrace.length; i++) { + String oldMethod = previousStackTrace[previousStackTrace.length - 1 - i]; + String newMethod = stackTrace[stackTrace.length - 1 -i]; + + if (!oldMethod.equals(newMethod)) { + matchesExistingStackTrace = false; + break; + } + } + + if (matchesExistingStackTrace) { + Logger.Log(loggerInstance, " Rejected Trace"); + Logger.Log(loggerInstance, "--CommonAdvisor::isAcceptedTrace"); + + return false; + } + } + + Logger.Log(loggerInstance, " Accepted Trace"); + Logger.Log(loggerInstance, "--CommonAdvisor::isAcceptedTrace"); + StackTraces.add(stackTrace); + return true; + } + } +} \ No newline at end of file diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/visitors/ConstructorAdvisor.java b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/ConstructorAdvisor.java new file mode 100644 index 00000000..5c2be157 --- /dev/null +++ b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/ConstructorAdvisor.java @@ -0,0 +1,26 @@ +package com.github.gilesi.coverage.visitors; + +import net.bytebuddy.asm.Advice; +import net.bytebuddy.implementation.bytecode.assign.Assigner; + +import java.lang.reflect.Executable; + +public class ConstructorAdvisor { + @Advice.OnMethodEnter(inline = false) + public static Object enter( + @Advice.Origin Executable origin, + @Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] arguments, + @Advice.This(typing = Assigner.Typing.DYNAMIC, optional = true) Object instance) { + return CommonAdvisor.commonEnter(); + } + + @Advice.OnMethodExit(inline = false) + public static void exit( + @Advice.Origin Executable origin, + @Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] arguments, + @Advice.Return(typing = Assigner.Typing.DYNAMIC) Object returned, + @Advice.This(typing = Assigner.Typing.DYNAMIC, optional = true) Object instance, + @Advice.Enter(typing = Assigner.Typing.DYNAMIC) String entryTrace) { + CommonAdvisor.commonExit(entryTrace); + } +} \ No newline at end of file diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/visitors/LoggingListener.java b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/LoggingListener.java new file mode 100644 index 00000000..778272bb --- /dev/null +++ b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/LoggingListener.java @@ -0,0 +1,50 @@ +package com.github.gilesi.coverage.visitors; + +import com.github.gilesi.coverage.Logger; +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.utility.JavaModule; + +import java.io.PrintWriter; +import java.io.StringWriter; + +public class LoggingListener implements AgentBuilder.Listener { + private int loggerInstance = Logger.GetInstance(); + + @Override + public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, + boolean loaded) { + } + + @Override + public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, + JavaModule module, boolean loaded, DynamicType dynamicType) { + + } + + @Override + public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, + JavaModule module, boolean loaded) { + } + + @Override + public void onError(String typeName, ClassLoader classLoader, JavaModule module, + boolean loaded, Throwable throwable) { + Logger.Log(loggerInstance, String.format("BYTEBUDDY INSTRUMENTATION ERROR:")); + Logger.Log(loggerInstance, String.format("Type Name: %s", typeName)); + Logger.Log(loggerInstance, String.format("Loaded: %s", loaded)); + Logger.Log(loggerInstance, String.format("Exception: %s", throwable)); + + StringWriter writer = new StringWriter(); + throwable.printStackTrace(new PrintWriter(writer)); + String stackTrace = writer.toString(); + + Logger.Log(loggerInstance, String.format("Stack Trace: %s", stackTrace)); + } + + @Override + public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, + boolean loaded) { + } +} \ No newline at end of file diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/visitors/MethodAdvisor.java b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/MethodAdvisor.java new file mode 100644 index 00000000..5bcc1dd2 --- /dev/null +++ b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/MethodAdvisor.java @@ -0,0 +1,27 @@ +package com.github.gilesi.coverage.visitors; + +import net.bytebuddy.asm.Advice; +import net.bytebuddy.implementation.bytecode.assign.Assigner; + +import java.lang.reflect.Executable; + +public class MethodAdvisor { + @Advice.OnMethodEnter(inline = false) + public static Object enter( + @Advice.Origin Executable origin, + @Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] arguments, + @Advice.This(typing = Assigner.Typing.DYNAMIC, optional = true) Object instance) { + return CommonAdvisor.commonEnter(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, inline = false) + public static void exit( + @Advice.Origin Executable origin, + @Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] arguments, + @Advice.Return(typing = Assigner.Typing.DYNAMIC) Object returned, + @Advice.This(typing = Assigner.Typing.DYNAMIC, optional = true) Object instance, + @Advice.Thrown Throwable thrown, + @Advice.Enter(typing = Assigner.Typing.DYNAMIC) String entryTrace) { + CommonAdvisor.commonExit(entryTrace); + } +} \ No newline at end of file diff --git a/Coverage/src/main/java/com/github/gilesi/coverage/visitors/MethodInstrumentor.java b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/MethodInstrumentor.java new file mode 100644 index 00000000..f501c9dd --- /dev/null +++ b/Coverage/src/main/java/com/github/gilesi/coverage/visitors/MethodInstrumentor.java @@ -0,0 +1,82 @@ +package com.github.gilesi.coverage.visitors; + +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; + +import java.lang.instrument.Instrumentation; + +import com.github.gilesi.confgencoverage.models.Class; +import com.github.gilesi.confgencoverage.models.Method; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +public class MethodInstrumentor { + // commented out due to hooking issues + // yet to investigate + /*private static ElementMatcher.Junction getConstructorElementMatcherFromClassParameter(Class classParameter) { + ElementMatcher.Junction descriptorMatcher = null; + for (String descriptorParameter : classParameter.ClassDescriptors) { + if (descriptorMatcher == null) { + descriptorMatcher = hasDescriptor(descriptorParameter); + } else { + descriptorMatcher = descriptorMatcher.or(hasDescriptor(descriptorParameter)); + } + } + return descriptorMatcher; + }*/ + + private static ElementMatcher.Junction getMethodElementMatcherFromClassParameter(Class classParameter) { + ElementMatcher.Junction classMatcher = null; + for (Method methodParameter : classParameter.ClassMethods) { + ElementMatcher.Junction methodMatcher = named(methodParameter.MethodName); + // commented out due to hooking issues + // yet to investigate + /*ElementMatcher.Junction descriptorMatcher = null; + for (String descriptorParameter : methodParameter.MethodDescriptors) { + if (descriptorMatcher == null) { + descriptorMatcher = hasDescriptor(descriptorParameter); + } else { + descriptorMatcher = descriptorMatcher.or(hasDescriptor(descriptorParameter)); + } + } + + if (descriptorMatcher != null) { + methodMatcher = methodMatcher.and(descriptorMatcher); + }*/ + + if (classMatcher == null) { + classMatcher = methodMatcher; + } else { + classMatcher = classMatcher.or(methodMatcher); + } + } + return classMatcher; + } + + public static void instrumentClassForTracingAgent(Instrumentation inst, Class classParameter) { + ElementMatcher.Junction methodMatcher = getMethodElementMatcherFromClassParameter(classParameter); + //ElementMatcher.Junction constructorMatcher = getConstructorElementMatcherFromClassParameter(classParameter); + + new AgentBuilder.Default() + .with(new LoggingListener()) + .type(named(classParameter.ClassName)) + .transform((builder, + typeDescription, + classLoader, + javaModule, + protectionDomain) -> + builder + .visit( + Advice.to(MethodAdvisor.class).on(ElementMatchers.not(ElementMatchers.isTypeInitializer().or(ElementMatchers.isConstructor())).and(methodMatcher)) + ) + .visit( + Advice.to(ConstructorAdvisor.class).on(ElementMatchers.not(ElementMatchers.isTypeInitializer()).and(ElementMatchers.isConstructor())/*.and(constructorMatcher)*/) + ) + ) + .with(AgentBuilder.RedefinitionStrategy.REDEFINITION) + .with(AgentBuilder.TypeStrategy.Default.REDEFINE).installOn(inst); + } +} \ No newline at end of file diff --git a/TraceView/.gitignore b/Illico/.gitignore similarity index 100% rename from TraceView/.gitignore rename to Illico/.gitignore diff --git a/Illico/build.gradle b/Illico/build.gradle new file mode 100644 index 00000000..c9155048 --- /dev/null +++ b/Illico/build.gradle @@ -0,0 +1,81 @@ +plugins { + id 'application' + id 'com.github.johnrengelman.shadow' version '8.1.1' +} + +group 'com.github.gilesi.illico' +version '1.0-SNAPSHOT' + + +compileJava { + options.encoding = 'UTF-8' +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} + +repositories { + mavenCentral() + mavenLocal() + maven { + url 'https://packages.jetbrains.team/maven/p/ij/intellij-dependencies' + } +} + +dependencies { + implementation 'com.fasterxml.jackson.core:jackson-core:2.17.2' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.2' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.2' + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.17.2' + implementation 'com.fasterxml.jackson:jackson-base:2.17.2' + implementation 'org.apache.maven:maven-core:3.9.9' + implementation 'org.apache.maven.shared:maven-invoker:3.3.0' + testImplementation platform('org.junit:junit-bom:5.11.0') + testImplementation 'org.junit.jupiter:junit-jupiter' + implementation 'org.apache.logging.log4j:log4j-core:2.24.0' + implementation 'org.apache.logging.log4j:log4j-api:2.24.0' +} + +jar { + manifest { + attributes 'Main-Class': 'com.github.gilesi.illico.Main', + 'Multi-Release': 'true' + } +} + +application { + mainClass = 'com.github.gilesi.illico.Main' +} + +description = 'Main distribution.' + +shadowJar { + archiveBaseName.set('com.github.gilesi.illico') + archiveClassifier.set('') + archiveVersion.set('') + mergeServiceFiles() +} + +distributions { + shadow { + distributionBaseName = 'com.github.gilesi.illico' + } +} + +apply plugin: 'java' +apply plugin: 'idea' + +idea { + module { + downloadJavadoc = true + downloadSources = true + } +} + +run { + jvmArgs = [ + "-XX:InitialHeapSize=2G", + "-XX:MaxHeapSize=32G" + ] +} \ No newline at end of file diff --git a/Illico/gradle/wrapper/gradle-wrapper.jar b/Illico/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..249e5832 Binary files /dev/null and b/Illico/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Illico/gradle/wrapper/gradle-wrapper.properties b/Illico/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..5c07b32a --- /dev/null +++ b/Illico/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon May 27 14:15:06 CEST 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/Illico/gradlew b/Illico/gradlew new file mode 100755 index 00000000..1b6c7873 --- /dev/null +++ b/Illico/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +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 + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/Illico/gradlew.bat b/Illico/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /dev/null +++ b/Illico/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Illico/settings.gradle b/Illico/settings.gradle new file mode 100644 index 00000000..19c50608 --- /dev/null +++ b/Illico/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'Illico' + diff --git a/Illico/src/main/java/com/github/gilesi/illico/Constants.java b/Illico/src/main/java/com/github/gilesi/illico/Constants.java new file mode 100644 index 00000000..8e770a81 --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/Constants.java @@ -0,0 +1,41 @@ +package com.github.gilesi.illico; + +import java.util.Hashtable; +import java.util.Map; + +public class Constants { + // These paths should already exist on the end user machine for now and never change + public static final String CONF_GEN_LOCATION = "ConfGen/build/libs/com.github.gilesi.confgen.jar"; + public static final String TEST_GEN_LOCATION = "TestGenerator/build/libs/com.github.gilesi.testgenerator.jar"; + public static final String TRACE_DIFF_LOCATION = "TraceDiff/build/libs/com.github.gilesi.tracediff.jar"; + public static final String INSTRUMENTATION_LOCATION = "Instrumentation/build/libs/com.github.gilesi.instrumentation.jar"; + + public static final String JAVA_21_BINARY_LOCATION = "/usr/lib/jvm/java-21-openjdk-amd64"; + + // These paths are dynamically created by the tool but must stay consistent within methods + public static final String targetAgentName = "com.github.gilesi.instrumentation.jar"; + public static final String targetConfigurationName = "TestWorkflowConfiguration.json"; + + public static final Map JAVA_VERSIONS = new Hashtable<>() { + { + put("1.5", "/usr/lib/jvm/java-8-openjdk-amd64"); + put("1.6", "/usr/lib/jvm/java-8-openjdk-amd64"); + put("1.7", "/usr/lib/jvm/java-8-openjdk-amd64"); + put("1.8", "/usr/lib/jvm/java-8-openjdk-amd64"); + put("11", "/usr/lib/jvm/java-11-openjdk-amd64"); + put("17", "/usr/lib/jvm/java-17-openjdk-amd64"); + put("21", JAVA_21_BINARY_LOCATION); + } + }; + + public static final String GREEN_TEST = "Green"; + public static final String RED_TEST = "Red"; + + public static final String SUREFIRE_VERSION = "2.8"; + public static final String SUREFIRE_GROUPID = "org.apache.maven.plugins"; + public static final String SUREFIRE_ARTIFACTID = "maven-surefire-plugin"; + + public static boolean SKIP_INSTRUMENTATION_RAN_ALREADY_FOR_DEBUGGING = false; + + public static final String M2_REPOSITORY = "/home/gus/.m2/repository"; +} diff --git a/Illico/src/main/java/com/github/gilesi/illico/FileUtils.java b/Illico/src/main/java/com/github/gilesi/illico/FileUtils.java new file mode 100644 index 00000000..dab7ee8e --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/FileUtils.java @@ -0,0 +1,130 @@ +package com.github.gilesi.illico; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Objects; +import java.util.regex.Pattern; + +public class FileUtils { + public static void deleteDirectory(File fi) { + for (File file : Objects.requireNonNull(fi.listFiles())) { + if (file.isDirectory()) { + deleteDirectory(file); + } + file.delete(); + } + fi.delete(); + } + + public static void deleteDirectoryIfExists(String path) { + Path pa = Path.of(path); + if (Files.exists(pa)) { + deleteDirectory(new File(path)); + } + } + + public static ArrayList enumerateDirectory(File fi, String wildcard, boolean recursive) { + ArrayList files = new ArrayList<>(); + Pattern pattern = Pattern.compile(wildcard); + for (File file : Objects.requireNonNull(fi.listFiles())) { + if (file.isDirectory() && recursive) { + files.addAll(enumerateDirectory(file, wildcard, true)); + } else { + if (pattern.matcher(file.getName()).matches()) { + files.add(file.getPath()); + } + } + } + return files; + } + + public static ArrayList enumerateFiles(String path, String wildcard, boolean recursive) { + Path pa = Path.of(path); + if (Files.exists(pa)) { + return enumerateDirectory(new File(path), wildcard, recursive); + } + return new ArrayList<>(); + } + + public static void ensureDirectoryExistsAndIsEmpty(String path) throws IOException { + Path pa = Path.of(path); + deleteDirectoryIfExists(path); + Files.createDirectory(pa); + } + + public static void backupFile(String fileLocation) throws IOException { + Path filePath = Path.of(fileLocation); + Path backupFilePath = Path.of("%s.orig".formatted(fileLocation)); + + if (!Files.exists(filePath)) { + return; + } + + if (Files.exists(backupFilePath)) { + Files.delete(backupFilePath); + } + + Files.copy(filePath, backupFilePath); + } + + public static void restoreFile(String fileLocation) throws IOException { + Path filePath = Path.of(fileLocation); + Path backupFilePath = Path.of("%s.orig".formatted(fileLocation)); + + if (!Files.exists(backupFilePath)) { + return; + } + + if (Files.exists(filePath)) { + Files.delete(filePath); + } + + Files.move(backupFilePath, filePath); + } + + public static void copyFolder(File source, File destination) { + if (source.isDirectory()) { + if (!destination.exists()) { + destination.mkdirs(); + } + + String[] files = source.list(); + + for (String file : files) { + File srcFile = new File(source, file); + File destFile = new File(destination, file); + + copyFolder(srcFile, destFile); + } + } else { + InputStream in = null; + OutputStream out = null; + + try { + in = new FileInputStream(source); + out = new FileOutputStream(destination); + + byte[] buffer = new byte[1024]; + + int length; + while ((length = in.read(buffer)) > 0) { + out.write(buffer, 0, length); + } + } catch (Exception e) { + try { + in.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + + try { + out.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } + } +} diff --git a/Illico/src/main/java/com/github/gilesi/illico/Main.java b/Illico/src/main/java/com/github/gilesi/illico/Main.java new file mode 100644 index 00000000..bad0bd8a --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/Main.java @@ -0,0 +1,194 @@ +package com.github.gilesi.illico; + +import org.apache.maven.shared.invoker.MavenInvocationException; + +import com.github.gilesi.illico.runners.maven.Log4JLogger; +import com.github.gilesi.illico.runners.maven.ProjectRunner; +import com.github.gilesi.illico.runners.maven.TestAssertedLogger; +import com.github.gilesi.illico.tools.ConfGen; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; + +public class Main { + public static final Logger logger = LogManager.getLogger("illico"); + public static final Logger loggerMaven = LogManager.getLogger("maven"); + public static final Logger loggerConfGen = LogManager.getLogger("confgen"); + public static final Logger loggerTraceDiff = LogManager.getLogger("tracediff"); + public static final Logger loggerTraceGen = LogManager.getLogger("tracegen"); + public static final Logger loggerAgent = LogManager.getLogger("agent"); + + public static void main(String[] args) throws MavenInvocationException, IOException, InterruptedException { + if (System.getenv("MAVEN_HOME") == null || !Files.exists(Path.of(System.getenv("MAVEN_HOME"))) || Files.isRegularFile(Path.of(System.getenv("MAVEN_HOME")))) { + logger.info("You must specify the MAVEN_HOME environment variable pointing to a valid Maven directory"); + return; + } + + if (args.length < 4) { + logger.info("Usage: "); + return; + } + + String clientLocation = args[0]; + String libraryIdentifier = args[1]; + String outputFolder = args[2]; + String GilesiRepositoryLocation = args[3]; + String optionalCustomTestCommand = null; + + if (args.length > 4) { + optionalCustomTestCommand = args[4]; + } + + ProjectRunner.runMavenGoalOnRepository( + clientLocation, + "dependency:sources", + null, + "1.8", + new Log4JLogger(), + null + ); + + Path outputPath = Path.of(outputFolder).toAbsolutePath(); + + Path libraryPath = outputPath.resolve("library-sources"); + + copyLibrarySourcesForDataset(libraryIdentifier, libraryPath, Path.of(Constants.M2_REPOSITORY)); + + String additionalJavaToolParameters = installInstrumentationAgentOntoProject(Path.of(GilesiRepositoryLocation), libraryPath, Path.of(clientLocation), outputPath.resolve("instrumentation")); + + String testCmd = "test -fn -Drat.ignoreErrors=true -DtrimStackTrace=false -DfailIfNoTests=false"; + + if (optionalCustomTestCommand != null && + !optionalCustomTestCommand.isEmpty() && + !optionalCustomTestCommand.equals("N/A")) { + testCmd = optionalCustomTestCommand; + } + + if (testCmd.startsWith("mvn ")) { + testCmd = testCmd.substring(4); + } + + TestAssertedLogger testAssertedLogger = new TestAssertedLogger( + new Log4JLogger( + logger, + org.apache.maven.shared.invoker.InvokerLogger.INFO, + "%s-%s".formatted(clientLocation, clientLocation) + ) + ); + + ProjectRunner.runMavenGoalOnRepository(clientLocation, + testCmd, + null, + "1.8", + testAssertedLogger, + additionalJavaToolParameters); + + Main.logger.info("Client: %s - TEST(S) FAILED: %s".formatted(clientLocation, + testAssertedLogger.HasTestFailed())); + } + + + private static String installInstrumentationAgentOntoProject( + Path GilesiRepositoryLocationPath, + Path libraryLocationPath, + Path clientLocationPath, + Path generatedFolderPath + ) throws IOException, InterruptedException { + + FileUtils.deleteDirectoryIfExists(generatedFolderPath.toString()); + + Path libraryConfig = libraryLocationPath.resolve(Constants.targetConfigurationName); + + // Run ConfGen on Library first + // TODO: Check how meta projects get handled here exactly... + ConfGen.runConfGen(GilesiRepositoryLocationPath.toString(), + libraryConfig.toString(), + generatedFolderPath.toString(), + libraryLocationPath.toString()); + + Path InstrumentationAgentEffectiveLocation = GilesiRepositoryLocationPath.resolve(Constants.INSTRUMENTATION_LOCATION); + + // Modify Client Build System to use the instrumentation agent + ArrayList clientProjectPomFiles = FileUtils.enumerateFiles(clientLocationPath.toString(), + "pom.xml", + true); + ArrayList clientProjectGradleBuildFiles = FileUtils.enumerateFiles(clientLocationPath.toString(), + "build.gradle", + true); + + ArrayList allProjectFiles = new ArrayList<>(); + allProjectFiles.addAll(clientProjectPomFiles); + allProjectFiles.addAll(clientProjectGradleBuildFiles); + + Orchestrator.copyInstrumentationToolAndConfiguration(allProjectFiles, + InstrumentationAgentEffectiveLocation, + libraryConfig); + + String javaToolOptions = "-javaagent:%s=%s".formatted(InstrumentationAgentEffectiveLocation, + libraryConfig.toString()); + + Orchestrator.orchestrateGradleProjects(clientProjectGradleBuildFiles); + + return javaToolOptions; + } + + private static void copyLibrarySourcesForDataset(String libraryIdentifier, Path librarySourceOutputPath, Path m2RepositoryPath) throws IOException, MavenInvocationException { + String[] groupId = libraryIdentifier.split(":")[0].split("\\."); + String artifactId = libraryIdentifier.split(":")[1]; + String libraryVersion = libraryIdentifier.split(":")[2]; + + Path fileSystemRepoPath = m2RepositoryPath; + + for (String pathComponent : groupId) { + fileSystemRepoPath = fileSystemRepoPath.resolve(pathComponent); + } + + fileSystemRepoPath = fileSystemRepoPath.resolve(artifactId).resolve(libraryVersion); + + ArrayList srcCandidates = FileUtils.enumerateFiles(fileSystemRepoPath.toString(), ".*sources\\.jar", false); + + if (srcCandidates.size() == 0) { + Main.logger.info("%s Missing sources for lib, aborting!".formatted(libraryIdentifier)); + return; + } + + FileUtils.deleteDirectoryIfExists(librarySourceOutputPath.toString()); + Files.createDirectories(librarySourceOutputPath); + + for (String cand : srcCandidates) { + System.out.println("Extracting " + cand); + System.out.println("Extracting " + librarySourceOutputPath); + extractJar(cand, librarySourceOutputPath.toString()); + } + } + + private static void extractJar(String jar, String destdir) throws java.io.IOException { + java.util.jar.JarFile jarFile = new java.util.jar.JarFile(new java.io.File(jar)); + java.util.Enumeration enu = jarFile.entries(); + while (enu.hasMoreElements()) { + java.util.jar.JarEntry je = enu.nextElement(); + + java.io.File fl = new java.io.File(destdir, je.getName()); + if (!fl.exists()) { + fl.getParentFile().mkdirs(); + fl = new java.io.File(destdir, je.getName()); + } + if (je.isDirectory()) { + continue; + } + java.io.InputStream is = jarFile.getInputStream(je); + java.io.FileOutputStream fo = new java.io.FileOutputStream(fl); + while (is.available() > 0) { + fo.write(is.read()); + } + fo.close(); + is.close(); + } + jarFile.close(); + } +} \ No newline at end of file diff --git a/Illico/src/main/java/com/github/gilesi/illico/Orchestrator.java b/Illico/src/main/java/com/github/gilesi/illico/Orchestrator.java new file mode 100644 index 00000000..671f82d2 --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/Orchestrator.java @@ -0,0 +1,267 @@ +package com.github.gilesi.illico; + +import com.github.gilesi.illico.buildsystem.configuration.maven.PomHelper; +import com.github.gilesi.illico.runners.maven.Log4JLogger; +import com.github.gilesi.illico.runners.maven.ProjectRunner; +import com.github.gilesi.illico.tools.ConfGen; +import com.github.gilesi.illico.tools.TestGen; +import org.apache.maven.shared.invoker.MavenInvocationException; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; + +public class Orchestrator { + // TODO: More robust logging here using JUnit perhaps... + // TODO: Simplify required arg to run here + // TODO: Ideally we would want to deduce the version from the provided source but not always doable + // TODO: Could we also fetch source jars ourselves somehow or source loc? + // TODO: Maybe the dependency name is also doable to extract here, need to see how... + // TODO: Fix cross plat! + // TODO: picocli? + + public static void handleProject(String clientLocation, String libraryLocation, String GilesiRepositoryLocation, String dependencyName, String dependencyVersion, String outputTestProject) throws XmlPullParserException, IOException, MavenInvocationException, InterruptedException { + FileUtils.deleteDirectoryIfExists(outputTestProject); + Path outputTestProjectPath = Path.of(outputTestProject); + + Path libraryLocationPath = Path.of(libraryLocation); + Path libraryConfig = libraryLocationPath.resolve(Constants.targetConfigurationName); + + // Create the results directory + Files.createDirectories(outputTestProjectPath); + + // Run ConfGen on Library first + // TODO: Check how meta projects get handled here exactly... + ConfGen.runConfGen(GilesiRepositoryLocation, libraryConfig.toString(), outputTestProject, libraryLocation); + + Path InstrumentationAgentEffectiveLocation = Path.of("%s%s".formatted(GilesiRepositoryLocation, Constants.INSTRUMENTATION_LOCATION)); + + // Modify Client Build System to use the instrumentation agent + ArrayList clientProjectPomFiles = FileUtils.enumerateFiles(clientLocation, "pom.xml", true); + ArrayList clientProjectGradleBuildFiles = FileUtils.enumerateFiles(clientLocation, "build.gradle", true); + + ArrayList allProjectFiles = new ArrayList<>(); + allProjectFiles.addAll(clientProjectPomFiles); + allProjectFiles.addAll(clientProjectGradleBuildFiles); + + copyInstrumentationToolAndConfiguration(allProjectFiles, InstrumentationAgentEffectiveLocation, libraryConfig); + + orchestrateMavenProjects(clientProjectPomFiles); + orchestrateGradleProjects(clientProjectGradleBuildFiles); + + // Run the client tests + executeMavenProjectTests(clientProjectPomFiles, clientLocation); + executeGradleProjectTests(clientProjectGradleBuildFiles, clientLocation); + + TestGen.runTestGen(GilesiRepositoryLocation, outputTestProject, outputTestProjectPath.resolve("src").resolve("test").resolve("java").toString(), libraryConfig.toString()); + + lastMinuteCleanup(allProjectFiles); + + // Generate Gradle test project here in dir outputTestProject, with dep dependencyName of version dependencyVersion + + String buildGradleProjectFile = """ + plugins { + id 'java' + } + + group = 'testProject' + version = '1.0-SNAPSHOT' + + repositories { + mavenCentral() + mavenLocal() + } + + dependencies { + testImplementation '%s:%s' + testImplementation 'com.thoughtworks.xstream:xstream:1.4.20' + testImplementation platform('org.junit:junit-bom:5.10.0') + testImplementation 'org.junit.jupiter:junit-jupiter' + } + + test { + useJUnitPlatform() + }""".formatted(dependencyName, dependencyVersion); + + Files.writeString(outputTestProjectPath.resolve("build.gradle"), buildGradleProjectFile); + Files.copy(Path.of(GilesiRepositoryLocation).resolve("Illico").resolve("gradlew"), outputTestProjectPath.resolve("gradlew")); + Files.copy(Path.of(GilesiRepositoryLocation).resolve("Illico").resolve("gradlew.bat"), outputTestProjectPath.resolve("gradlew.bat")); + + // Test test project (3 times to be sure) + + // Update test project dependency version at version dependencyNewVersion + + // Test test project with dependency at version dependencyNewVersion (3 times to be sure) + + // Collect results of the execution + + // Write report + } + + public static void copyInstrumentationToolAndConfiguration(ArrayList allProjectFiles, Path InstrumentationAgentEffectiveLocation, Path libraryConfig) throws IOException { + for (String clientBuildFile : allProjectFiles) { + Path clientBuildFilePath = Path.of(clientBuildFile); + Path clientProjectLocationPath = clientBuildFilePath.getParent().toAbsolutePath(); + Path agentDest = clientProjectLocationPath.resolve(Constants.targetAgentName); + Path workflowDest = clientProjectLocationPath.resolve(Constants.targetConfigurationName); + + Path clientBuildFileBackup = Path.of("%s.gilesi.bak".formatted(clientBuildFile)); + if (Files.exists(clientBuildFileBackup)) { + Files.deleteIfExists(clientBuildFilePath); + Files.move(clientBuildFileBackup, clientBuildFilePath); + } + + Files.deleteIfExists(agentDest); + Files.deleteIfExists(workflowDest); + + //Files.copy(InstrumentationAgentEffectiveLocation, agentDest); + //Files.copy(libraryConfig, workflowDest); + } + } + + public static void orchestrateMavenProjects(ArrayList clientProjectPomFiles) throws IOException, XmlPullParserException { + for (String clientBuildFile : clientProjectPomFiles) { + Path clientBuildFilePath = Path.of(clientBuildFile); + Files.copy(clientBuildFilePath, Path.of("%s.gilesi.bak".formatted(clientBuildFile))); + + String clientSurefireArgLine = "-Dnet.bytebuddy.experimental=true -javaagent:%s=%s".formatted(Constants.targetAgentName, Constants.targetConfigurationName); + PomHelper.addSureFire(clientBuildFile, clientSurefireArgLine); + } + } + + public static void orchestrateGradleProjects(ArrayList clientProjectGradleBuildFiles) throws IOException { + for (String clientBuildFile : clientProjectGradleBuildFiles) { + String clientTestingBlock = """ + testing { + \tsuites { + \t\ttest { + \t\t\tuseJUnitJupiter() + \t\t\ttargets { + \t\t\t\tall { + \t\t\t\t\ttestTask.configure { + \t\t\t\t\t\tsystemProperty 'net.bytebuddy.experimental', 'true' + \t\t\t\t\t\tjvmArgs = ['-XX:+EnableDynamicAgentLoading', '-javaagent:%s=%s'] + \t\t\t\t\t} + \t\t\t\t} + \t\t\t} + \t\t} + \t} + }""".formatted(Constants.targetAgentName, Constants.targetConfigurationName); + + Path clientBuildFilePath = Path.of(clientBuildFile); + + String buildFileContent = Files.readString(clientBuildFilePath); + if (!buildFileContent.contains(clientTestingBlock)) { + buildFileContent += "\n" + clientTestingBlock; + + Files.move(clientBuildFilePath, Path.of("%s.gilesi.bak".formatted(clientBuildFile))); + Files.writeString(clientBuildFilePath, buildFileContent); + } + } + } + + public static void executeMavenProjectTests(ArrayList clientProjectPomFiles, String clientLocation) throws MavenInvocationException { + // This is the maven path + if (!clientProjectPomFiles.isEmpty()) { + if (clientProjectPomFiles.stream().anyMatch(t -> t.replace("%s%c".formatted(clientLocation, File.separatorChar), "").replace(clientLocation, "").equals("pom.xml"))) { + ProjectRunner.runMavenGoalOnRepository(clientLocation, "-fn test -Danimal.sniffer.skip=true", null, "21", new Log4JLogger(), null); + } else { + for (String clientPomFile : clientProjectPomFiles) { + Path clientPomFilePath = Path.of(clientPomFile); + Path clientProjectLocationPath = clientPomFilePath.getParent().toAbsolutePath(); + + ProjectRunner.runMavenGoalOnRepository(clientProjectLocationPath.toString(), "-fn test -Danimal.sniffer.skip=true", null, "21", new Log4JLogger(), null); + } + } + } + } + + public static void executeGradleProjectTests(ArrayList clientProjectGradleBuildFiles, String clientLocation) throws IOException, InterruptedException { + // This is the gradle path + if (!clientProjectGradleBuildFiles.isEmpty()) { + if (clientProjectGradleBuildFiles.stream().anyMatch + ( + t -> + t + .replace( + "%s%c" + .formatted( + clientLocation, + File.separatorChar + ), + "" + ) + .replace( + clientLocation, + "" + ) + .equals("build.gradle") + )) { + ProcessUtils.runExternalCommand(new String[]{ + "/usr/bin/env", + "bash", + Path.of(clientLocation).resolve("gradlew").toString(), + "test", + "--warning-mode", + "all" + }, new File(clientLocation), Main.logger); + + // TODO: Check for underlying platform + /*ProcessUtils.runExternalCommand(new String[]{ + "cmd.exe", + "-C", + Path.of(clientLocation).resolve("gradlew").toString(), + "test", + "--warning-mode", + "all" + }, new File(clientLocation), Main.logger);*/ + } else { + for (String clientGradleBuildFile : clientProjectGradleBuildFiles) { + Path clientGradleBuildFilePath = Path.of(clientGradleBuildFile); + Path clientProjectLocationPath = clientGradleBuildFilePath.getParent().toAbsolutePath(); + + ProcessUtils.runExternalCommand(new String[]{ + "/usr/bin/env", + "bash", + clientProjectLocationPath.resolve("gradlew").toString(), + "test", + "--warning-mode", + "all" + }, new File(clientProjectLocationPath.toString()), Main.logger); + + // TODO: Check for underlying platform + /*ProcessUtils.runExternalCommand(new String[]{ + "cmd.exe", + "-C", + clientProjectLocationPath.resolve("gradlew.bat").toString(), + "test", + "--warning-mode", + "all" + }, new File(clientProjectLocationPath.toString()), Main.logger);*/ + } + } + } + } + + public static void lastMinuteCleanup(ArrayList allProjectFiles) throws IOException { + // Last minute cleanup + for (String clientBuildFile : allProjectFiles) { + Path clientBuildFilePath = Path.of(clientBuildFile); + Path clientProjectLocationPath = clientBuildFilePath.getParent().toAbsolutePath(); + Path agentDest = clientProjectLocationPath.resolve(Constants.targetAgentName); + Path workflowDest = clientProjectLocationPath.resolve(Constants.targetConfigurationName); + + Path clientBuildFileBackup = Path.of("%s.gilesi.bak".formatted(clientBuildFile)); + if (Files.exists(clientBuildFileBackup)) { + Files.deleteIfExists(clientBuildFilePath); + Files.move(clientBuildFileBackup, clientBuildFilePath); + } + + Files.deleteIfExists(agentDest); + Files.deleteIfExists(workflowDest); + } + } +} diff --git a/Illico/src/main/java/com/github/gilesi/illico/ProcessUtils.java b/Illico/src/main/java/com/github/gilesi/illico/ProcessUtils.java new file mode 100644 index 00000000..2b55ed7c --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/ProcessUtils.java @@ -0,0 +1,26 @@ +package com.github.gilesi.illico; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; + +public class ProcessUtils { + public static int runExternalCommand(String[] command, File directory, org.apache.logging.log4j.Logger logger) throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder(command); + if (directory != null) { + pb.directory(directory); + } + pb.redirectErrorStream(true); + Process p = pb.start(); + BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); + + String line; + + while ((line = reader.readLine()) != null) { + logger.info(line); + } + + return p.waitFor(); + } +} diff --git a/Illico/src/main/java/com/github/gilesi/illico/RunResult.java b/Illico/src/main/java/com/github/gilesi/illico/RunResult.java new file mode 100644 index 00000000..2e153c2e --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/RunResult.java @@ -0,0 +1,22 @@ +package com.github.gilesi.illico; + +import com.github.gilesi.illico.testing.ReportItem; + +import java.util.ArrayList; + +public class RunResult { + public String ProjectId = ""; + public boolean ProjectIsMissingLibrarySourceCode = false; + public boolean ProjectCannotBeReproduced = false; // TODO + public boolean ProjectCannotBeInstrumented = false; + public boolean ProjectFailedToLoadAgent = false; + public boolean ProjectIsMissingTraceFiles = false; + public boolean ProjectIsMissingSpecificTraces = false; + public boolean ProjectFailedToGenerateAnyTraceTestSrc = false; + public boolean ProjectFailedToCompileGeneratedTests = false; + public boolean ProjectFailedToExecuteGeneratedTests = false; + public boolean ProjectFailedToCompileGeneratedTestsWithNewVersion = false; + public boolean ProjectFailedToExecuteGeneratedTestsWithNewVersion = false; + public ArrayList TestExecutionReportWithOldVersion; + public ArrayList TestExecutionReportWithNewVersion; +} diff --git a/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/ConfigurationUtils.java b/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/ConfigurationUtils.java new file mode 100644 index 00000000..3023c5da --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/ConfigurationUtils.java @@ -0,0 +1,31 @@ +package com.github.gilesi.illico.buildsystem.configuration.maven; + +import org.codehaus.plexus.util.xml.Xpp3Dom; + +public class ConfigurationUtils { + public static Xpp3Dom getDomValueObject(String name, String value) { + Xpp3Dom domValue = new Xpp3Dom(name); + domValue.setValue(value); + return domValue; + } + + public static Xpp3Dom getDomArrayObject(String arrayName, String name, String[] values) { + Xpp3Dom valuesDom = new Xpp3Dom(arrayName); + for (String val : values) { + addStringValueIfNotEmpty(valuesDom, name, val); + } + return valuesDom; + } + + public static void addStringValueIfNotEmpty(Xpp3Dom config, String name, String value) { + if (value != null && !value.isEmpty()) { + config.addChild(getDomValueObject(name, value)); + } + } + + public static void addStringArrayValueIfNotEmpty(Xpp3Dom config, String arrayName, String name, String[] values) { + if (values != null && values.length > 0) { + config.addChild(getDomArrayObject(arrayName, name, values)); + } + } +} diff --git a/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/IPluginConfiguration.java b/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/IPluginConfiguration.java new file mode 100644 index 00000000..2e567e78 --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/IPluginConfiguration.java @@ -0,0 +1,7 @@ +package com.github.gilesi.illico.buildsystem.configuration.maven; + +import org.codehaus.plexus.util.xml.Xpp3Dom; + +public interface IPluginConfiguration { + Xpp3Dom serialize(); +} diff --git a/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/PomHelper.java b/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/PomHelper.java new file mode 100644 index 00000000..f2e53748 --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/PomHelper.java @@ -0,0 +1,154 @@ +package com.github.gilesi.illico.buildsystem.configuration.maven; + +import com.github.gilesi.illico.Constants; +import org.apache.maven.model.Build; +import org.apache.maven.model.Dependency; +import org.apache.maven.model.Model; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.apache.maven.model.io.xpp3.MavenXpp3Writer; +import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; + +public class PomHelper { + + public static Model readPomFile(String pomFilePath) throws IOException, XmlPullParserException { + MavenXpp3Reader pomReader = new MavenXpp3Reader(); + Path pomPath = Path.of(pomFilePath); + InputStream pomStream = Files.newInputStream(pomPath); + Model pomModel = pomReader.read(pomStream); + pomModel.setPomFile(new File(pomFilePath)); + pomStream.close(); + return pomModel; + } + + private static void writePomFile(Model pomModel, String pomFilePath) throws IOException { + MavenXpp3Writer pomWriter = new MavenXpp3Writer(); + FileWriter pomWriteStream = new FileWriter(pomFilePath); + pomWriter.write(pomWriteStream, pomModel); + pomWriteStream.close(); + } + + private static boolean doesBuildContainPluginGroupId(Build bld, String groupId) { + for (Plugin plg : bld.getPlugins()) { + if (plg.getGroupId().equalsIgnoreCase(groupId)) { + return true; + } + } + return false; + } + + private static boolean doesModelContainDependencyGroupId(Model pomModel, String groupId) { + for (Dependency plg : pomModel.getDependencies()) { + if (plg.getGroupId().equalsIgnoreCase(groupId)) { + return true; + } + } + return false; + } + + private static Plugin getPluginByGroupId(Build bld, String groupId) throws FileNotFoundException { + for (Plugin plg : bld.getPlugins()) { + if (plg.getGroupId().equalsIgnoreCase(groupId)) { + return plg; + } + } + throw new FileNotFoundException(); + } + + private static Xpp3Dom buildSureFireConfiguration() { + SureFirePluginConfiguration sureFireConfiguration = new SureFirePluginConfiguration(); + return sureFireConfiguration.serialize(); + } + + private static Xpp3Dom buildSureFireConfiguration(String argLine) { + SureFirePluginConfiguration sureFireConfiguration = new SureFirePluginConfiguration(); + sureFireConfiguration.setArgLine(argLine); + return sureFireConfiguration.serialize(); + } + + private static Plugin getSureFirePlugin() { + return buildPlugin(Constants.SUREFIRE_GROUPID, Constants.SUREFIRE_ARTIFACTID, Constants.SUREFIRE_VERSION); + } + + private static Dependency buildDependency(String groupId, String artifactId, String version) { + Dependency dependency = new Dependency(); + dependency.setGroupId(groupId); + dependency.setArtifactId(artifactId); + dependency.setVersion(version); + return dependency; + } + + private static Plugin buildPlugin(String groupId, String artifactId, String version) { + Plugin plugin = new Plugin(); + plugin.setGroupId(groupId); + plugin.setArtifactId(artifactId); + plugin.setVersion(version); + return plugin; + } + + private static Plugin buildSureFirePlugin() { + Plugin surePlugin = getSureFirePlugin(); + Xpp3Dom config = buildSureFireConfiguration(); + surePlugin.setConfiguration(config); + return surePlugin; + } + + private static Plugin buildSureFirePlugin(String argLine) { + Plugin surePlugin = getSureFirePlugin(); + Xpp3Dom config = buildSureFireConfiguration(argLine); + surePlugin.setConfiguration(config); + return surePlugin; + } + + private static void removeExistingPlugin(Build bld, String groupId) throws FileNotFoundException { + // Get rid of all previous instances of the plugin in case one already exists + while (doesBuildContainPluginGroupId(bld, groupId)) { + bld.removePlugin(getPluginByGroupId(bld, groupId)); + } + } + + private static void removeExistingSureFirePlugins(Build bld) throws FileNotFoundException { + removeExistingPlugin(bld, Constants.SUREFIRE_GROUPID); + } + + public static void addSureFire(String pomFilePath) throws XmlPullParserException, IOException { + Model pomModel = readPomFile(pomFilePath); + Build bld = pomModel.getBuild(); + + if (bld == null) { + bld = new Build(); + } + + removeExistingSureFirePlugins(bld); + + Plugin surePlugin = buildSureFirePlugin(); + + bld.addPlugin(surePlugin); + + pomModel.setBuild(bld); + writePomFile(pomModel, pomFilePath); + } + + public static void addSureFire(String pomFilePath, String argLine) throws XmlPullParserException, IOException { + Model pomModel = readPomFile(pomFilePath); + Build bld = pomModel.getBuild(); + + if (bld == null) { + bld = new Build(); + } + + removeExistingSureFirePlugins(bld); + + Plugin surePlugin = buildSureFirePlugin(argLine); + + bld.addPlugin(surePlugin); + + pomModel.setBuild(bld); + writePomFile(pomModel, pomFilePath); + } +} diff --git a/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/SureFirePluginConfiguration.java b/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/SureFirePluginConfiguration.java new file mode 100644 index 00000000..7498ff38 --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/buildsystem/configuration/maven/SureFirePluginConfiguration.java @@ -0,0 +1,103 @@ +package com.github.gilesi.illico.buildsystem.configuration.maven; + +import org.codehaus.plexus.util.xml.Xpp3Dom; + +public class SureFirePluginConfiguration implements IPluginConfiguration { + private int forkCount = 1; + private boolean reuseForks = false; + private String argLine = "-Xmx2G -XX:MaxMetaspaceSize=2G"; + private String parallel = "methods"; + private int threadCount = 1; + private String forkedProcessTimeoutInSeconds = "40"; + private String forkedProcessExitTimeoutInSeconds = "40"; + private String parallelTestsTimeoutInSeconds = "30"; + private String parallelTestsTimeoutForcedInSeconds = "30"; + + public int getForkCount() { + return forkCount; + } + + public void setForkCount(int forkCount) { + this.forkCount = forkCount; + } + + public int getThreadCount() { + return threadCount; + } + + public void setThreadCount(int threadCount) { + this.threadCount = threadCount; + } + + public String getArgLine() { + return argLine; + } + + public void setArgLine(String argLine) { + this.argLine = argLine; + } + + public String getForkedProcessExitTimeoutInSeconds() { + return forkedProcessExitTimeoutInSeconds; + } + + public void setForkedProcessExitTimeoutInSeconds(String forkedProcessExitTimeoutInSeconds) { + this.forkedProcessExitTimeoutInSeconds = forkedProcessExitTimeoutInSeconds; + } + + public String getForkedProcessTimeoutInSeconds() { + return forkedProcessTimeoutInSeconds; + } + + public void setForkedProcessTimeoutInSeconds(String forkedProcessTimeoutInSeconds) { + this.forkedProcessTimeoutInSeconds = forkedProcessTimeoutInSeconds; + } + + public String getParallelTestsTimeoutForcedInSeconds() { + return parallelTestsTimeoutForcedInSeconds; + } + + public void setParallelTestsTimeoutForcedInSeconds(String parallelTestsTimeoutForcedInSeconds) { + this.parallelTestsTimeoutForcedInSeconds = parallelTestsTimeoutForcedInSeconds; + } + + public String getParallelTestsTimeoutInSeconds() { + return parallelTestsTimeoutInSeconds; + } + + public void setParallelTestsTimeoutInSeconds(String parallelTestsTimeoutInSeconds) { + this.parallelTestsTimeoutInSeconds = parallelTestsTimeoutInSeconds; + } + + public String getParallel() { + return parallel; + } + + public void setParallel(String parallel) { + this.parallel = parallel; + } + + public boolean getReuseForks() { + return reuseForks; + } + + public void setReuseForks(boolean reuseForks) { + this.reuseForks = reuseForks; + } + + public Xpp3Dom serialize() { + Xpp3Dom config = new Xpp3Dom("configuration"); + + ConfigurationUtils.addStringValueIfNotEmpty(config, "forkCount", Integer.toString(forkCount)); + ConfigurationUtils.addStringValueIfNotEmpty(config, "reuseForks", Boolean.toString(reuseForks)); + ConfigurationUtils.addStringValueIfNotEmpty(config, "argLine", argLine); + ConfigurationUtils.addStringValueIfNotEmpty(config, "parallel", parallel); + ConfigurationUtils.addStringValueIfNotEmpty(config, "threadCount", Integer.toString(threadCount)); + ConfigurationUtils.addStringValueIfNotEmpty(config, "forkedProcessTimeoutInSeconds", forkedProcessTimeoutInSeconds); + ConfigurationUtils.addStringValueIfNotEmpty(config, "forkedProcessExitTimeoutInSeconds", forkedProcessExitTimeoutInSeconds); + ConfigurationUtils.addStringValueIfNotEmpty(config, "parallelTestsTimeoutInSeconds", parallelTestsTimeoutInSeconds); + ConfigurationUtils.addStringValueIfNotEmpty(config, "parallelTestsTimeoutForcedInSeconds", parallelTestsTimeoutForcedInSeconds); + + return config; + } +} diff --git a/Illico/src/main/java/com/github/gilesi/illico/runners/maven/ConsoleLogger.java b/Illico/src/main/java/com/github/gilesi/illico/runners/maven/ConsoleLogger.java new file mode 100644 index 00000000..18e4c5d4 --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/runners/maven/ConsoleLogger.java @@ -0,0 +1,248 @@ +package com.github.gilesi.illico.runners.maven; + +/* + * 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.shared.invoker.InvocationOutputHandler; +import org.apache.maven.shared.invoker.InvokerLogger; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Date; + +/** + * Offers a logger that writes to a print stream like {@link java.lang.System#out}. + * + * @since 2.0.9 + */ +public class ConsoleLogger implements InvokerLogger, InvocationOutputHandler { + + /** + * The threshold used to filter messages. + */ + private int threshold; + + /** + * Creates a new logger that writes to {@link java.lang.System#out} and has a threshold of {@link #INFO}. + */ + public ConsoleLogger() { + this(INFO); + } + + /** + * Creates a new logger that writes to the specified print stream. + * + * @param threshold The threshold for the logger. + */ + public ConsoleLogger(int threshold) { + setThreshold(threshold); + } + + /** + * Writes the specified message and exception to the print stream. + * + * @param level The priority level of the message. + * @param message The message to log, may be null. + * @param error The exception to log, may be null. + */ + private void log(int level, String message, Throwable error) { + if (level > threshold) { + // don't log when it doesn't match your threshold. + return; + } + + if (message == null && error == null) { + // don't log when there's nothing to log. + return; + } + + StringBuilder buffer = new StringBuilder(); + String logLevel = "INFO"; + + switch (level) { + case (DEBUG) -> logLevel = "DEBUG"; + case (INFO) -> logLevel = "INFO"; + case (WARN) -> logLevel = "WARN"; + case (ERROR) -> logLevel = "ERROR"; + case (FATAL) -> logLevel = "FATAL"; + default -> { + } + } + + buffer.append("[Maven] "); + + if (message != null) { + buffer.append(message); + } + + if (error != null) { + StringWriter writer = new StringWriter(); + PrintWriter pWriter = new PrintWriter(writer); + + error.printStackTrace(pWriter); + + if (message != null) { + buffer.append('\n'); + } + + buffer.append("Error:\n"); + buffer.append(writer); + } + + System.out.printf("[%s] [%s] %s%n", new Date(), logLevel, buffer); + } + + /** + * {@inheritDoc} + */ + public void debug(String message) { + log(DEBUG, message, null); + } + + /** + * {@inheritDoc} + */ + public void debug(String message, Throwable throwable) { + log(DEBUG, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void info(String message) { + log(INFO, message, null); + } + + /** + * {@inheritDoc} + */ + public void info(String message, Throwable throwable) { + log(INFO, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void warn(String message) { + log(WARN, message, null); + } + + /** + * {@inheritDoc} + */ + public void warn(String message, Throwable throwable) { + log(WARN, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void error(String message) { + log(ERROR, message, null); + } + + /** + * {@inheritDoc} + */ + public void error(String message, Throwable throwable) { + log(ERROR, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void fatalError(String message) { + log(FATAL, message, null); + } + + /** + * {@inheritDoc} + */ + public void fatalError(String message, Throwable throwable) { + log(FATAL, message, throwable); + } + + /** + *

isDebugEnabled.

+ * + * @return a boolean. + */ + public boolean isDebugEnabled() { + return threshold >= DEBUG; + } + + /** + *

isErrorEnabled.

+ * + * @return a boolean. + */ + public boolean isErrorEnabled() { + return threshold >= ERROR; + } + + /** + *

isFatalErrorEnabled.

+ * + * @return a boolean. + */ + public boolean isFatalErrorEnabled() { + return threshold >= FATAL; + } + + /** + *

isInfoEnabled.

+ * + * @return a boolean. + */ + public boolean isInfoEnabled() { + return threshold >= INFO; + } + + /** + *

isWarnEnabled.

+ * + * @return a boolean. + */ + public boolean isWarnEnabled() { + return threshold >= WARN; + } + + /** + *

Getter for the field threshold.

+ * + * @return an int. + */ + public int getThreshold() { + return threshold; + } + + /** + * {@inheritDoc} + */ + public void setThreshold(int threshold) { + this.threshold = threshold; + } + + /** + * {@inheritDoc} + */ + public void consumeLine(String line) { + log(INFO, line, null); + } +} \ No newline at end of file diff --git a/Illico/src/main/java/com/github/gilesi/illico/runners/maven/Log4JLogger.java b/Illico/src/main/java/com/github/gilesi/illico/runners/maven/Log4JLogger.java new file mode 100644 index 00000000..d7fb41a7 --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/runners/maven/Log4JLogger.java @@ -0,0 +1,271 @@ +package com.github.gilesi.illico.runners.maven; + +/* + * 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.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.maven.shared.invoker.InvocationOutputHandler; +import org.apache.maven.shared.invoker.InvokerLogger; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * Offers a logger that writes to a print stream like {@link java.lang.System#out}. + * + * @since 2.0.9 + */ +public class Log4JLogger implements InvokerLogger, InvocationOutputHandler { + + /** + * The print stream to write to, never null. + */ + private final Logger out; + + /** + * The threshold used to filter messages. + */ + private int threshold; + + private String prefix; + + /** + * Creates a new logger that writes to {@link java.lang.System#out} and has a threshold of {@link #INFO}. + */ + public Log4JLogger() { + this(LogManager.getLogger(), INFO, ""); + } + + public Log4JLogger(String prefix) { + this(LogManager.getLogger(), INFO, prefix); + } + + /** + * Creates a new logger that writes to the specified print stream. + * + * @param out The print stream to write to, must not be null. + * @param threshold The threshold for the logger. + */ + public Log4JLogger(Logger out, int threshold, String prefix) { + if (out == null) { + throw new NullPointerException("missing output stream"); + } + this.prefix = prefix; + this.out = out; + setThreshold(threshold); + } + + /** + * Writes the specified message and exception to the print stream. + * + * @param level The priority level of the message. + * @param message The message to log, may be null. + * @param error The exception to log, may be null. + */ + private void log(int level, String message, Throwable error) { + if (level > threshold) { + // don't log when it doesn't match your threshold. + return; + } + + if (message == null && error == null) { + // don't log when there's nothing to log. + return; + } + + StringBuilder buffer = new StringBuilder(); + Level logLevel = Level.INFO; + + switch (level) { + case (DEBUG) -> logLevel = Level.DEBUG; + case (INFO) -> logLevel = Level.INFO; + case (WARN) -> logLevel = Level.WARN; + case (ERROR) -> logLevel = Level.ERROR; + case (FATAL) -> logLevel = Level.FATAL; + default -> { + } + } + + if (prefix != null && !prefix.isEmpty()) { + buffer.append("[%s-Maven] ".formatted(prefix)); + } else { + buffer.append("[Maven] "); + } + + if (message != null) { + buffer.append(message); + } + + if (error != null) { + StringWriter writer = new StringWriter(); + PrintWriter pWriter = new PrintWriter(writer); + + error.printStackTrace(pWriter); + + if (message != null) { + buffer.append('\n'); + } + + buffer.append("Error:\n"); + buffer.append(writer); + } + + out.log(logLevel, buffer.toString()); + } + + /** + * {@inheritDoc} + */ + public void debug(String message) { + log(DEBUG, message, null); + } + + /** + * {@inheritDoc} + */ + public void debug(String message, Throwable throwable) { + log(DEBUG, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void info(String message) { + log(INFO, message, null); + } + + /** + * {@inheritDoc} + */ + public void info(String message, Throwable throwable) { + log(INFO, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void warn(String message) { + log(WARN, message, null); + } + + /** + * {@inheritDoc} + */ + public void warn(String message, Throwable throwable) { + log(WARN, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void error(String message) { + log(ERROR, message, null); + } + + /** + * {@inheritDoc} + */ + public void error(String message, Throwable throwable) { + log(ERROR, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void fatalError(String message) { + log(FATAL, message, null); + } + + /** + * {@inheritDoc} + */ + public void fatalError(String message, Throwable throwable) { + log(FATAL, message, throwable); + } + + /** + *

isDebugEnabled.

+ * + * @return a boolean. + */ + public boolean isDebugEnabled() { + return threshold >= DEBUG; + } + + /** + *

isErrorEnabled.

+ * + * @return a boolean. + */ + public boolean isErrorEnabled() { + return threshold >= ERROR; + } + + /** + *

isFatalErrorEnabled.

+ * + * @return a boolean. + */ + public boolean isFatalErrorEnabled() { + return threshold >= FATAL; + } + + /** + *

isInfoEnabled.

+ * + * @return a boolean. + */ + public boolean isInfoEnabled() { + return threshold >= INFO; + } + + /** + *

isWarnEnabled.

+ * + * @return a boolean. + */ + public boolean isWarnEnabled() { + return threshold >= WARN; + } + + /** + *

Getter for the field threshold.

+ * + * @return an int. + */ + public int getThreshold() { + return threshold; + } + + /** + * {@inheritDoc} + */ + public void setThreshold(int threshold) { + this.threshold = threshold; + } + + /** + * {@inheritDoc} + */ + public void consumeLine(String line) { + log(INFO, line, null); + } +} \ No newline at end of file diff --git a/Illico/src/main/java/com/github/gilesi/illico/runners/maven/ProjectRunner.java b/Illico/src/main/java/com/github/gilesi/illico/runners/maven/ProjectRunner.java new file mode 100644 index 00000000..1edcb010 --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/runners/maven/ProjectRunner.java @@ -0,0 +1,181 @@ +package com.github.gilesi.illico.runners.maven; + +import com.github.gilesi.illico.Constants; +import com.github.gilesi.illico.Main; +import org.apache.maven.model.*; +import org.apache.maven.shared.invoker.*; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import static com.github.gilesi.illico.buildsystem.configuration.maven.PomHelper.readPomFile; + +public class ProjectRunner { + + public static int runPomGoals(String pomFilePath, List pomGoals, List pomProperties, + boolean batchMode, String userSettingsFilePath, Object logger, String javaToolOptions) + throws MavenInvocationException { + + String javaVersion = "8"; + + try { + Model pomModel = readPomFile(pomFilePath); + Properties props = pomModel.getProperties(); + if (props.stringPropertyNames().contains("java.compiler.version")) { + javaVersion = props.getProperty("java.compiler.version"); + Main.logger.info("Debug: Using java.compiler.version=" + javaVersion); + } else if (props.stringPropertyNames().contains("maven.compiler.source")) { + javaVersion = props.getProperty("maven.compiler.source"); + Main.logger.info("Debug: Using maven.compiler.source=" + javaVersion); + } else if (props.stringPropertyNames().contains("maven.compiler.target")) { + javaVersion = props.getProperty("maven.compiler.target"); + Main.logger.info("Debug: Using maven.compiler.target=" + javaVersion); + } else if (props.stringPropertyNames().contains("maven.compile.source")) { + javaVersion = props.getProperty("maven.compile.source"); + Main.logger.info("Debug: Using maven.compile.source=" + javaVersion); + } else if (props.stringPropertyNames().contains("maven.compile.target")) { + javaVersion = props.getProperty("maven.compile.target"); + Main.logger.info("Debug: Using maven.compile.target=" + javaVersion); + } + } catch (Exception ignored) { + Main.logger.info("Debug: Getting java specific version unfortunately failed."); + } + + return runPomGoals(pomFilePath, pomGoals, pomProperties, batchMode, userSettingsFilePath, javaVersion, logger, + javaToolOptions); + } + + public static int runPomGoals(String pomFilePath, List pomGoals, List pomProperties, + boolean batchMode, String userSettingsFilePath, String javaVersion, Object logger, String javaToolOptions) + throws MavenInvocationException { + String javaHome = Constants.JAVA_VERSIONS.getOrDefault(javaVersion, System.getenv("JAVA_HOME")); + return runPomGoalsInternal(pomFilePath, pomGoals, pomProperties, batchMode, userSettingsFilePath, javaHome, + javaVersion, logger, javaToolOptions); + } + + public static int runPomGoalsInternal(String pomFilePath, List pomGoals, List pomProperties, + boolean batchMode, String userSettingsFilePath, String JavaHome, String JavaVersion, Object logger, + String javaToolOptions) throws MavenInvocationException { + File pomFile = new File(pomFilePath); + + InvokerLogger invokerLogger = null; + InvocationOutputHandler invocationOutputHandler = null; + + if (logger instanceof InvokerLogger tmp) { + invokerLogger = tmp; + } + + if (logger instanceof InvocationOutputHandler tmp) { + invocationOutputHandler = tmp; + } + + File projectDirectory = new File(Path.of(pomFilePath).getParent().toString()); + + Properties properties = new Properties(); + pomProperties.forEach(p -> properties.setProperty(p, "true")); + + properties.setProperty("maven.compiler.source", JavaVersion); + properties.setProperty("maven.compiler.target", JavaVersion); + properties.setProperty("maven.compile.source", JavaVersion); + properties.setProperty("maven.compile.target", JavaVersion); + + properties.setProperty("maven.source.skip", "true"); + properties.setProperty("assembly.skipAssembly", "true"); + properties.setProperty("shade.skip", "true"); + properties.setProperty("maven.war.skip", "true"); + properties.setProperty("maven.rar.skip", "true"); + properties.setProperty("changelog.skip", "true"); + properties.setProperty("checkstyle.skip", "true"); + properties.setProperty("maven.doap.skip", "true"); + properties.setProperty("maven.javadoc.skip", "true"); + properties.setProperty("maven.jxr.skip", "true"); + properties.setProperty("linkcheck.skip", "true"); + properties.setProperty("pmd.skip", "true"); + properties.setProperty("mpir.skip", "true"); + properties.setProperty("gpg.skip", "true"); + properties.setProperty("jdepend.skip", "true"); + + String javacLocation = "%s%sbin%sjavac".formatted(JavaHome, File.separator, File.separator); + pomGoals.add("-Dmaven.compiler.fork=true"); + if (Files.exists(Path.of(javacLocation))) { + pomGoals.add("-Dmaven.compiler.executable=%s".formatted(javacLocation)); + } + + InvocationRequest request = new DefaultInvocationRequest(); + + Integer timeout = 30 * 60; // 30 minutes in seconds + + request.setShellEnvironmentInherited(false); + request.setPomFile(pomFile); + request.setGoals(pomGoals); + request.setProperties(properties); + request.setAlsoMake(true); + request.setBatchMode(true); + request.setTimeoutInSeconds(timeout); + + request.setJavaHome(new File(JavaHome)); + request.setBaseDirectory(projectDirectory); + request.setInputStream(InputStream.nullInputStream()); + if (invocationOutputHandler != null) { + request.setOutputHandler(invocationOutputHandler); + request.setErrorHandler(invocationOutputHandler); + } + + if (userSettingsFilePath != null && !userSettingsFilePath.isEmpty() && !userSettingsFilePath.isBlank()) { + Path pa = Path.of(userSettingsFilePath); + if (Files.exists(pa) && Files.isRegularFile(pa)) { + request.setUserSettingsFile(new File(userSettingsFilePath)); + } + } + + if (javaToolOptions != null && !javaToolOptions.isEmpty()) { + request.addShellEnvironment("JAVA_TOOL_OPTIONS", javaToolOptions); + } + + Main.logger.info("Building with pom=%s goals=%s properties=%s%n" + .formatted(pomFilePath, + pomGoals, + properties)); + + try { + Invoker invoker = new DefaultInvoker(); + if (invokerLogger != null) { + invoker.setLogger(invokerLogger); + } + + invoker.setMavenHome(new File(System.getenv("MAVEN_HOME"))); + + InvocationResult result = invoker.execute(request); + + Main.logger.info("Building with pom={} goals={} properties={} returned {}", + pomFile, + pomGoals, + properties, + result.getExitCode()); + + return result.getExitCode(); + } catch (MavenInvocationException e) { + throw e; + } + } + + public static boolean runMavenGoalOnRepository(String repositoryDirectory, String goal, String userSettingsFilePath, + String javaVersion, Object logger, String javaToolOptions) throws MavenInvocationException { + Main.logger.info("ExecuteMavenGoalOnDuetsRepository Entry"); + String pomFilePath = "%s%spom.xml".formatted(repositoryDirectory, File.separator); + ArrayList goals = new ArrayList<>(); + goals.add(goal); + + if (javaVersion != null && !javaVersion.isBlank()) { + return runPomGoals(pomFilePath, goals, new ArrayList<>(), false, userSettingsFilePath, javaVersion, logger, + javaToolOptions) == 0; + } else { + return runPomGoals(pomFilePath, goals, new ArrayList<>(), false, userSettingsFilePath, logger, + javaToolOptions) == 0; + } + } +} \ No newline at end of file diff --git a/Illico/src/main/java/com/github/gilesi/illico/runners/maven/TestAssertedLogger.java b/Illico/src/main/java/com/github/gilesi/illico/runners/maven/TestAssertedLogger.java new file mode 100644 index 00000000..ced5965f --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/runners/maven/TestAssertedLogger.java @@ -0,0 +1,192 @@ +package com.github.gilesi.illico.runners.maven; + +import org.apache.maven.shared.invoker.InvocationOutputHandler; +import org.apache.maven.shared.invoker.InvokerLogger; + +import java.io.IOException; + +public class TestAssertedLogger implements InvokerLogger, InvocationOutputHandler { + + private InvokerLogger invokerLogger = null; + private InvocationOutputHandler invocationOutputHandler = null; + private boolean testFailed = false; + + public TestAssertedLogger(Object logger) { + if (logger instanceof InvokerLogger tmp) { + invokerLogger = tmp; + } + + if (logger instanceof InvocationOutputHandler tmp) { + invocationOutputHandler = tmp; + } + } + + + public void debug(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.debug(message); + } + } + + public void debug(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.debug(message, throwable); + } + } + + public boolean isDebugEnabled() { + if (invokerLogger != null) { + return invokerLogger.isDebugEnabled(); + } + + return false; + } + + public void info(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.info(message); + } + } + + public void info(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.info(message, throwable); + } + } + + public boolean isInfoEnabled() { + if (invokerLogger != null) { + return invokerLogger.isInfoEnabled(); + } + + return false; + } + + public void warn(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.warn(message); + } + } + + public void warn(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.warn(message, throwable); + } + } + + public boolean isWarnEnabled() { + if (invokerLogger != null) { + return invokerLogger.isWarnEnabled(); + } + + return false; + } + + public void error(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.error(message); + } + } + + public void error(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.error(message, throwable); + } + } + + public boolean isErrorEnabled() { + if (invokerLogger != null) { + return invokerLogger.isErrorEnabled(); + } + + return false; + } + + public void fatalError(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.fatalError(message); + } + } + + public void fatalError(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.fatalError(message, throwable); + } + } + + public boolean isFatalErrorEnabled() { + if (invokerLogger != null) { + return invokerLogger.isFatalErrorEnabled(); + } + + return false; + } + + public int getThreshold() { + if (invokerLogger != null) { + return invokerLogger.getThreshold(); + } + + return 0; + } + + public void setThreshold(int threshold) { + if (invokerLogger != null) { + invokerLogger.setThreshold(threshold); + } + } + + public void consumeLine(String line) throws IOException { + if (line.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invocationOutputHandler != null) { + invocationOutputHandler.consumeLine(line); + } + } + + public boolean HasTestFailed() { + return testFailed; + } +} diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/ReportItem.java b/Illico/src/main/java/com/github/gilesi/illico/testing/ReportItem.java new file mode 100644 index 00000000..459c6778 --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/testing/ReportItem.java @@ -0,0 +1,65 @@ +package com.github.gilesi.illico.testing; + +import com.github.gilesi.illico.Constants; +import com.github.gilesi.illico.testing.models.Testcase; + +public class ReportItem { + public String Name; + public String Version; + private String TestName; + private String TestResult; + private String ErrorDetails; + + public ReportItem(String projectName, String projectCommit, Testcase testcase) { + this.TestName = "%s.%s".formatted(testcase.ClassName, testcase.Name); + this.TestResult = ((testcase.Error != null) || (testcase.Failure != null)) ? Constants.RED_TEST : Constants.GREEN_TEST; + this.ErrorDetails = (testcase.Error != null) ? testcase.Error.Message : ""; + this.Name = projectName; + this.Version = projectCommit; + } + + public String getName() { + return Name; + } + + public void setName(String name) { + Name = name; + } + + public String getVersion() { + return Version; + } + + public void setVersion(String version) { + Version = version; + } + + public String getTestName() { + return TestName; + } + + public void setTestName(String testName) { + TestName = testName; + } + + public String getTestResult() { + return TestResult; + } + + public void setTestResult(String testResult) { + TestResult = testResult; + } + + public String getErrorDetails() { + return ErrorDetails; + } + + public void setErrorDetails(String errorDetails) { + ErrorDetails = errorDetails; + } + + @Override + public String toString() { + return "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"".formatted(Name, Version, TestName, TestResult, ErrorDetails); + } +} diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/XmlParser.java b/Illico/src/main/java/com/github/gilesi/illico/testing/XmlParser.java new file mode 100644 index 00000000..4929b28b --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/testing/XmlParser.java @@ -0,0 +1,33 @@ +package com.github.gilesi.illico.testing; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.github.gilesi.illico.testing.models.Testsuite; +import com.github.gilesi.illico.testing.models.Testsuites; + +import java.io.File; +import java.io.IOException; + +public class XmlParser { + + private static final XmlMapper mapper = XmlMapper + .builder() + .defaultUseWrapper(false) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .build(); + + public static Testsuite deserializeTestsuite(String XmlPath) throws IOException { + File fi = new File(XmlPath); + return mapper.readValue(fi, Testsuite.class); + } + + public static Testsuites deserializeTestsuites(String XmlPath) throws IOException { + File fi = new File(XmlPath); + return mapper.readValue(fi, Testsuites.class); + } + + public static void serialize(String XmlPath, Object obj) throws IOException { + File fi = new File(XmlPath); + mapper.writeValue(fi, obj); + } +} diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/gradle/GradleHarness.java b/Illico/src/main/java/com/github/gilesi/illico/testing/gradle/GradleHarness.java new file mode 100644 index 00000000..53b5b90f --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/testing/gradle/GradleHarness.java @@ -0,0 +1,52 @@ +package com.github.gilesi.illico.testing.gradle; + +import com.github.gilesi.illico.FileUtils; +import com.github.gilesi.illico.Main; +import com.github.gilesi.illico.testing.ReportItem; +import com.github.gilesi.illico.testing.XmlParser; +import com.github.gilesi.illico.testing.models.Testcase; +import com.github.gilesi.illico.testing.models.Testsuite; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; + +public class GradleHarness { + private static void buildReportXml(String projectName, String projectCommit, String targetLocation, + ArrayList reportItems) throws IOException { + String sureFireReportsLocation = "%s%stest-results%stest".formatted(targetLocation, File.separator, + File.separator); + ArrayList reportXmls = FileUtils.enumerateFiles(sureFireReportsLocation, ".*\\.xml", false); + + for (String xml : reportXmls) { + Testsuite testsuite = XmlParser.deserializeTestsuite(xml); + Testcase[] list = testsuite.getTestcase(); + if (list == null) { + continue; + } + for (Testcase testcase : list) { + reportItems.add(new ReportItem(projectName, projectCommit, testcase)); + } + } + } + + public static ArrayList getProjectTestReports(String projectPathFriendlyName, String commit, + String buildGradleFileLocation) { + ArrayList reportItems = new ArrayList<>(); + + Path buildGradleFilePath = Path.of(buildGradleFileLocation); + String targetLocation = "%s%sbuild".formatted(buildGradleFilePath.getParent().toAbsolutePath(), File.separator); + + if (Files.isDirectory(Path.of(targetLocation))) { + try { + buildReportXml(projectPathFriendlyName, commit, targetLocation, reportItems); + } catch (Exception e) { + Main.logger.info(e); + } + } + + return reportItems; + } +} diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/models/Error.java b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Error.java new file mode 100644 index 00000000..c0b06c6c --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Error.java @@ -0,0 +1,17 @@ +package com.github.gilesi.illico.testing.models; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlCData; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText; + +public class Error { + @JacksonXmlProperty(isAttribute = true, localName = "message") + public String Message; + + @JacksonXmlProperty(isAttribute = true, localName = "type") + public String Type; + + @JacksonXmlText + @JacksonXmlCData + private String Data; +} \ No newline at end of file diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/models/Properties.java b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Properties.java new file mode 100644 index 00000000..5828312b --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Properties.java @@ -0,0 +1,13 @@ +package com.github.gilesi.illico.testing.models; + +public class Properties { + private Property[] property; + + public Property[] getProperty() { + return property; + } + + public void setProperty(Property[] value) { + this.property = value; + } +} diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/models/Property.java b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Property.java new file mode 100644 index 00000000..307faddc --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Property.java @@ -0,0 +1,22 @@ +package com.github.gilesi.illico.testing.models; + +public class Property { + private String name; + private String value; + + public String getName() { + return name; + } + + public void setName(String value) { + this.name = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} \ No newline at end of file diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/models/Testcase.java b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Testcase.java new file mode 100644 index 00000000..c19eb2ea --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Testcase.java @@ -0,0 +1,23 @@ +package com.github.gilesi.illico.testing.models; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; + +public class Testcase { + @JacksonXmlElementWrapper(localName = "error") + public com.github.gilesi.illico.testing.models.Error Error; + + @JacksonXmlElementWrapper(localName = "failure") + public Error Failure; + + @JacksonXmlElementWrapper(localName = "name") + public String Name; + + @JacksonXmlElementWrapper(localName = "classname") + public String ClassName; + + @JacksonXmlElementWrapper(localName = "time") + public String Time; + + @JacksonXmlElementWrapper(localName = "system-out") + public String SystemOut; +} diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/models/Testsuite.java b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Testsuite.java new file mode 100644 index 00000000..3a956c05 --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Testsuite.java @@ -0,0 +1,103 @@ +package com.github.gilesi.illico.testing.models; + +public class Testsuite { + private Properties properties; + private Testcase[] testcase; + private String xmlnsXsi; + private String xsiNoNamespaceSchemaLocation; + private String version; + private String name; + private String time; + private String tests; + private String errors; + private String skipped; + private String failures; + + public Properties getProperties() { + return properties; + } + + public void setProperties(Properties value) { + this.properties = value; + } + + public Testcase[] getTestcase() { + return testcase; + } + + public void setTestcase(Testcase[] value) { + this.testcase = value; + } + + public String getXmlnsXsi() { + return xmlnsXsi; + } + + public void setXmlnsXsi(String value) { + this.xmlnsXsi = value; + } + + public String getXsiNoNamespaceSchemaLocation() { + return xsiNoNamespaceSchemaLocation; + } + + public void setXsiNoNamespaceSchemaLocation(String value) { + this.xsiNoNamespaceSchemaLocation = value; + } + + public String getVersion() { + return version; + } + + public void setVersion(String value) { + this.version = value; + } + + public String getName() { + return name; + } + + public void setName(String value) { + this.name = value; + } + + public String getTime() { + return time; + } + + public void setTime(String value) { + this.time = value; + } + + public String getTests() { + return tests; + } + + public void setTests(String value) { + this.tests = value; + } + + public String getErrors() { + return errors; + } + + public void setErrors(String value) { + this.errors = value; + } + + public String getSkipped() { + return skipped; + } + + public void setSkipped(String value) { + this.skipped = value; + } + + public String getFailures() { + return failures; + } + + public void setFailures(String value) { + this.failures = value; + } +} \ No newline at end of file diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/models/Testsuites.java b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Testsuites.java new file mode 100644 index 00000000..1114e392 --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/testing/models/Testsuites.java @@ -0,0 +1,13 @@ +package com.github.gilesi.illico.testing.models; + +public class Testsuites { + private Testsuite[] testsuites; + + public Testsuite[] getTestsuites() { + return testsuites; + } + + public void setTestsuites(Testsuite[] value) { + this.testsuites = value; + } +} diff --git a/Illico/src/main/java/com/github/gilesi/illico/testing/surefire/SurefireHarness.java b/Illico/src/main/java/com/github/gilesi/illico/testing/surefire/SurefireHarness.java new file mode 100644 index 00000000..f077354c --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/testing/surefire/SurefireHarness.java @@ -0,0 +1,49 @@ +package com.github.gilesi.illico.testing.surefire; + +import com.github.gilesi.illico.FileUtils; +import com.github.gilesi.illico.Main; +import com.github.gilesi.illico.testing.ReportItem; +import com.github.gilesi.illico.testing.models.Testcase; +import com.github.gilesi.illico.testing.models.Testsuite; +import com.github.gilesi.illico.testing.XmlParser; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; + +public class SurefireHarness { + private static void buildReportXml(String projectName, String projectCommit, String targetLocation, ArrayList reportItems) throws IOException { + String sureFireReportsLocation = "%s%ssurefire-reports".formatted(targetLocation, File.separator); + ArrayList reportXmls = FileUtils.enumerateFiles(sureFireReportsLocation, ".*\\.xml", false); + + for (String xml : reportXmls) { + Testsuite testsuite = XmlParser.deserializeTestsuite(xml); + Testcase[] list = testsuite.getTestcase(); + if (list == null) { + continue; + } + for (Testcase testcase : list) { + reportItems.add(new ReportItem(projectName, projectCommit, testcase)); + } + } + } + + public static ArrayList getProjectTestReports(String projectPathFriendlyName, String commit, String pomFileLocation) { + ArrayList reportItems = new ArrayList<>(); + + Path pomFilePath = Path.of(pomFileLocation); + String targetLocation = "%s%starget".formatted(pomFilePath.getParent().toAbsolutePath(), File.separator); + + if (Files.isDirectory(Path.of(targetLocation))) { + try { + buildReportXml(projectPathFriendlyName, commit, targetLocation, reportItems); + } catch (Exception e) { + Main.logger.info(e); + } + } + + return reportItems; + } +} diff --git a/Illico/src/main/java/com/github/gilesi/illico/tools/ConfGen.java b/Illico/src/main/java/com/github/gilesi/illico/tools/ConfGen.java new file mode 100644 index 00000000..489cc0af --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/tools/ConfGen.java @@ -0,0 +1,23 @@ +package com.github.gilesi.illico.tools; + +import com.github.gilesi.illico.Constants; +import com.github.gilesi.illico.Main; +import com.github.gilesi.illico.ProcessUtils; + +import java.io.IOException; +import java.nio.file.Path; + +public class ConfGen { + public static void runConfGen(String GilesiRepositoryLocation, String libraryConfig, String outputTestProject, String libraryLocation) throws IOException, InterruptedException { + ProcessUtils.runExternalCommand(new String[]{ + Path.of(Constants.JAVA_21_BINARY_LOCATION, "bin", "java").toString(), + "-Xmx8g", + "-Xms8g", + "-jar", + Path.of(GilesiRepositoryLocation).resolve(Constants.CONF_GEN_LOCATION).toString(), + libraryConfig, + outputTestProject, + libraryLocation + }, null, Main.loggerConfGen); + } +} diff --git a/Illico/src/main/java/com/github/gilesi/illico/tools/TestGen.java b/Illico/src/main/java/com/github/gilesi/illico/tools/TestGen.java new file mode 100644 index 00000000..289ee991 --- /dev/null +++ b/Illico/src/main/java/com/github/gilesi/illico/tools/TestGen.java @@ -0,0 +1,44 @@ +package com.github.gilesi.illico.tools; + +import com.github.gilesi.illico.Constants; +import com.github.gilesi.illico.Main; +import com.github.gilesi.illico.ProcessUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.nio.file.Path; + +public class TestGen { + public static final Logger logger = LogManager.getLogger(); + + public static void runTestGen(String GilesiRepositoryLocation, String traceFile, String outputCodeDirectory, String libraryConfig) throws IOException, InterruptedException { + ProcessUtils.runExternalCommand(new String[]{ + Path.of(Constants.JAVA_21_BINARY_LOCATION, "bin", "java").toString(), + "-Xmx8g", + "-Xms8g", + "-jar", + Path.of(GilesiRepositoryLocation).resolve(Constants.TEST_GEN_LOCATION).toString(), + traceFile, + outputCodeDirectory, + libraryConfig + }, null, Main.loggerTraceGen); + } + + public static void runTraceDiff(String GilesiRepositoryLocation, String traceFile, String outputCodeDirectory, String libraryConfig) throws IOException, InterruptedException { + ProcessUtils.runExternalCommand(new String[]{ + Path.of(Constants.JAVA_21_BINARY_LOCATION, "bin", "java").toString(), + "-Xmx32768m", + "-Xms16384m", + "-Xss1024m", + "-XX:ParallelGCThreads=8", + "-XX:InitiatingHeapOccupancyPercent=70", + "-XX:+UnlockDiagnosticVMOptions", + "-jar", + Path.of(GilesiRepositoryLocation).resolve(Constants.TRACE_DIFF_LOCATION).toString(), + traceFile, + outputCodeDirectory, + libraryConfig + }, null, Main.loggerTraceDiff); + } +} \ No newline at end of file diff --git a/Illico/src/main/resources/log4j2.xml b/Illico/src/main/resources/log4j2.xml new file mode 100644 index 00000000..6ef0deaf --- /dev/null +++ b/Illico/src/main/resources/log4j2.xml @@ -0,0 +1,82 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/IllicoCoverage/.gitignore b/IllicoCoverage/.gitignore new file mode 100644 index 00000000..d4c6a6aa --- /dev/null +++ b/IllicoCoverage/.gitignore @@ -0,0 +1,39 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/* +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/IllicoCoverage/build.gradle b/IllicoCoverage/build.gradle new file mode 100644 index 00000000..1f4ed4fd --- /dev/null +++ b/IllicoCoverage/build.gradle @@ -0,0 +1,81 @@ +plugins { + id 'application' + id 'com.github.johnrengelman.shadow' version '8.1.1' +} + +group 'com.github.gilesi.illicocoverage' +version '1.0-SNAPSHOT' + + +compileJava { + options.encoding = 'UTF-8' +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} + +repositories { + mavenCentral() + mavenLocal() + maven { + url 'https://packages.jetbrains.team/maven/p/ij/intellij-dependencies' + } +} + +dependencies { + implementation 'com.fasterxml.jackson.core:jackson-core:2.17.2' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.2' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.2' + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.17.2' + implementation 'com.fasterxml.jackson:jackson-base:2.17.2' + implementation 'org.apache.maven:maven-core:3.9.9' + implementation 'org.apache.maven.shared:maven-invoker:3.3.0' + testImplementation platform('org.junit:junit-bom:5.11.0') + testImplementation 'org.junit.jupiter:junit-jupiter' + implementation 'org.apache.logging.log4j:log4j-core:2.24.0' + implementation 'org.apache.logging.log4j:log4j-api:2.24.0' +} + +jar { + manifest { + attributes 'Main-Class': 'com.github.gilesi.illicocoverage.Main', + 'Multi-Release': 'true' + } +} + +application { + mainClass = 'com.github.gilesi.illicocoverage.Main' +} + +description = 'Main distribution.' + +shadowJar { + archiveBaseName.set('com.github.gilesi.illicocoverage') + archiveClassifier.set('') + archiveVersion.set('') + mergeServiceFiles() +} + +distributions { + shadow { + distributionBaseName = 'com.github.gilesi.illicocoverage' + } +} + +apply plugin: 'java' +apply plugin: 'idea' + +idea { + module { + downloadJavadoc = true + downloadSources = true + } +} + +run { + jvmArgs = [ + "-XX:InitialHeapSize=2G", + "-XX:MaxHeapSize=32G" + ] +} \ No newline at end of file diff --git a/IllicoCoverage/gradle/wrapper/gradle-wrapper.jar b/IllicoCoverage/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..249e5832 Binary files /dev/null and b/IllicoCoverage/gradle/wrapper/gradle-wrapper.jar differ diff --git a/IllicoCoverage/gradle/wrapper/gradle-wrapper.properties b/IllicoCoverage/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..5c07b32a --- /dev/null +++ b/IllicoCoverage/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon May 27 14:15:06 CEST 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/IllicoCoverage/gradlew b/IllicoCoverage/gradlew new file mode 100755 index 00000000..1b6c7873 --- /dev/null +++ b/IllicoCoverage/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +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 + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/IllicoCoverage/gradlew.bat b/IllicoCoverage/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /dev/null +++ b/IllicoCoverage/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/IllicoCoverage/settings.gradle b/IllicoCoverage/settings.gradle new file mode 100644 index 00000000..19c50608 --- /dev/null +++ b/IllicoCoverage/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'Illico' + diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/Constants.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/Constants.java new file mode 100644 index 00000000..b6fb3846 --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/Constants.java @@ -0,0 +1,41 @@ +package com.github.gilesi.illicocoverage; + +import java.util.Hashtable; +import java.util.Map; + +public class Constants { + // These paths should already exist on the end user machine for now and never change + public static final String CONF_GEN_COVERAGE_LOCATION = "ConfGenCoverage/build/libs/com.github.gilesi.confgencoverage.jar"; + public static final String TEST_GEN_LOCATION = "TestGenerator/build/libs/com.github.gilesi.testgenerator.jar"; + public static final String TRACE_DIFF_LOCATION = "TraceDiff/build/libs/com.github.gilesi.tracediff.jar"; + public static final String COVERAGE_LOCATION = "Coverage/build/libs/com.github.gilesi.coverage.jar"; + + public static final String JAVA_21_BINARY_LOCATION = "/usr/lib/jvm/java-21-openjdk-amd64"; + + // These paths are dynamically created by the tool but must stay consistent within methods + public static final String targetAgentName = "com.github.gilesi.coverage.jar"; + public static final String targetConfigurationName = "TestWorkflowConfigurationCoverage.json"; + + public static final Map JAVA_VERSIONS = new Hashtable<>() { + { + put("1.5", "/usr/lib/jvm/java-8-openjdk-amd64"); + put("1.6", "/usr/lib/jvm/java-8-openjdk-amd64"); + put("1.7", "/usr/lib/jvm/java-8-openjdk-amd64"); + put("1.8", "/usr/lib/jvm/java-8-openjdk-amd64"); + put("11", "/usr/lib/jvm/java-11-openjdk-amd64"); + put("17", "/usr/lib/jvm/java-17-openjdk-amd64"); + put("21", JAVA_21_BINARY_LOCATION); + } + }; + + public static final String GREEN_TEST = "Green"; + public static final String RED_TEST = "Red"; + + public static final String SUREFIRE_VERSION = "2.8"; + public static final String SUREFIRE_GROUPID = "org.apache.maven.plugins"; + public static final String SUREFIRE_ARTIFACTID = "maven-surefire-plugin"; + + public static boolean SKIP_INSTRUMENTATION_RAN_ALREADY_FOR_DEBUGGING = false; + + public static final String M2_REPOSITORY = "/home/gus/.m2/repository"; +} diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/FileUtils.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/FileUtils.java new file mode 100644 index 00000000..162bc6b0 --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/FileUtils.java @@ -0,0 +1,130 @@ +package com.github.gilesi.illicocoverage; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Objects; +import java.util.regex.Pattern; + +public class FileUtils { + public static void deleteDirectory(File fi) { + for (File file : Objects.requireNonNull(fi.listFiles())) { + if (file.isDirectory()) { + deleteDirectory(file); + } + file.delete(); + } + fi.delete(); + } + + public static void deleteDirectoryIfExists(String path) { + Path pa = Path.of(path); + if (Files.exists(pa)) { + deleteDirectory(new File(path)); + } + } + + public static ArrayList enumerateDirectory(File fi, String wildcard, boolean recursive) { + ArrayList files = new ArrayList<>(); + Pattern pattern = Pattern.compile(wildcard); + for (File file : Objects.requireNonNull(fi.listFiles())) { + if (file.isDirectory() && recursive) { + files.addAll(enumerateDirectory(file, wildcard, true)); + } else { + if (pattern.matcher(file.getName()).matches()) { + files.add(file.getPath()); + } + } + } + return files; + } + + public static ArrayList enumerateFiles(String path, String wildcard, boolean recursive) { + Path pa = Path.of(path); + if (Files.exists(pa)) { + return enumerateDirectory(new File(path), wildcard, recursive); + } + return new ArrayList<>(); + } + + public static void ensureDirectoryExistsAndIsEmpty(String path) throws IOException { + Path pa = Path.of(path); + deleteDirectoryIfExists(path); + Files.createDirectory(pa); + } + + public static void backupFile(String fileLocation) throws IOException { + Path filePath = Path.of(fileLocation); + Path backupFilePath = Path.of("%s.orig".formatted(fileLocation)); + + if (!Files.exists(filePath)) { + return; + } + + if (Files.exists(backupFilePath)) { + Files.delete(backupFilePath); + } + + Files.copy(filePath, backupFilePath); + } + + public static void restoreFile(String fileLocation) throws IOException { + Path filePath = Path.of(fileLocation); + Path backupFilePath = Path.of("%s.orig".formatted(fileLocation)); + + if (!Files.exists(backupFilePath)) { + return; + } + + if (Files.exists(filePath)) { + Files.delete(filePath); + } + + Files.move(backupFilePath, filePath); + } + + public static void copyFolder(File source, File destination) { + if (source.isDirectory()) { + if (!destination.exists()) { + destination.mkdirs(); + } + + String[] files = source.list(); + + for (String file : files) { + File srcFile = new File(source, file); + File destFile = new File(destination, file); + + copyFolder(srcFile, destFile); + } + } else { + InputStream in = null; + OutputStream out = null; + + try { + in = new FileInputStream(source); + out = new FileOutputStream(destination); + + byte[] buffer = new byte[1024]; + + int length; + while ((length = in.read(buffer)) > 0) { + out.write(buffer, 0, length); + } + } catch (Exception e) { + try { + in.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + + try { + out.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } + } +} diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/Main.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/Main.java new file mode 100644 index 00000000..15a68699 --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/Main.java @@ -0,0 +1,198 @@ +package com.github.gilesi.illicocoverage; + +import org.apache.maven.shared.invoker.MavenInvocationException; + +import com.github.gilesi.illicocoverage.runners.maven.Log4JLogger; +import com.github.gilesi.illicocoverage.runners.maven.ProjectRunner; +import com.github.gilesi.illicocoverage.runners.maven.TestAssertedLogger; +import com.github.gilesi.illicocoverage.tools.ConfGenCoverage; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; + +public class Main { + public static final Logger logger = LogManager.getLogger("illicocoverage"); + public static final Logger loggerMaven = LogManager.getLogger("maven"); + public static final Logger loggerConfGenCoverage = LogManager.getLogger("confgencoverage"); + public static final Logger loggerTraceDiff = LogManager.getLogger("tracediff"); + public static final Logger loggerTraceGen = LogManager.getLogger("tracegen"); + public static final Logger loggerAgent = LogManager.getLogger("agent"); + + public static void main(String[] args) throws MavenInvocationException, IOException, InterruptedException { + if (System.getenv("MAVEN_HOME") == null || !Files.exists(Path.of(System.getenv("MAVEN_HOME"))) || Files.isRegularFile(Path.of(System.getenv("MAVEN_HOME")))) { + logger.info("You must specify the MAVEN_HOME environment variable pointing to a valid Maven directory"); + return; + } + + if (args.length < 4) { + logger.info("Usage: "); + return; + } + + String clientLocation = args[0]; + String libraryIdentifier = args[1]; + String outputFolder = args[2]; + String GilesiRepositoryLocation = args[3]; + String LibraryRepositoryLocation = args[4]; + String optionalCustomTestCommand = null; + + if (args.length > 5) { + optionalCustomTestCommand = args[5]; + } + + /*ProjectRunner.runMavenGoalOnRepository( + clientLocation, + "dependency:sources", + null, + "1.8", + new Log4JLogger(), + null + );*/ + + Path outputPath = Path.of(outputFolder).toAbsolutePath(); + + Path libraryPath = Path.of(LibraryRepositoryLocation);//outputPath.resolve("library-sources"); + + //copyLibrarySourcesForDataset(libraryIdentifier, libraryPath, Path.of(Constants.M2_REPOSITORY)); + + String additionalJavaToolParameters = installInstrumentationAgentOntoProject(Path.of(GilesiRepositoryLocation), libraryPath, Path.of(clientLocation), outputPath.resolve("coverage")); + + String testCmd = "test -fn -Drat.ignoreErrors=true -DtrimStackTrace=false -DfailIfNoTests=false"; + + if (optionalCustomTestCommand != null && + !optionalCustomTestCommand.isEmpty() && + !optionalCustomTestCommand.equals("N/A")) { + testCmd = optionalCustomTestCommand; + } + + if (testCmd.startsWith("mvn ")) { + testCmd = testCmd.substring(4); + } + + TestAssertedLogger testAssertedLogger = new TestAssertedLogger( + new Log4JLogger( + logger, + org.apache.maven.shared.invoker.InvokerLogger.INFO, + "%s-%s".formatted(clientLocation, clientLocation) + ) + ); + + ProjectRunner.runMavenGoalOnRepository(clientLocation, + testCmd, + null, + "1.8", + testAssertedLogger, + additionalJavaToolParameters); + + Main.logger.info("Client: %s - TEST(S) FAILED: %s".formatted(clientLocation, + testAssertedLogger.HasTestFailed())); + + System.out.println("The end."); + System.exit(0); + } + + + private static String installInstrumentationAgentOntoProject( + Path GilesiRepositoryLocationPath, + Path libraryLocationPath, + Path clientLocationPath, + Path generatedFolderPath + ) throws IOException, InterruptedException { + + FileUtils.deleteDirectoryIfExists(generatedFolderPath.toString()); + + Path libraryConfig = libraryLocationPath.resolve(Constants.targetConfigurationName); + + // Run ConfGen on Library first + // TODO: Check how meta projects get handled here exactly... + ConfGenCoverage.runConfGenCoverage(GilesiRepositoryLocationPath.toString(), + libraryConfig.toString(), + generatedFolderPath.toString(), + libraryLocationPath.toString()); + + Path InstrumentationAgentEffectiveLocation = GilesiRepositoryLocationPath.resolve(Constants.COVERAGE_LOCATION); + + // Modify Client Build System to use the instrumentation agent + ArrayList clientProjectPomFiles = FileUtils.enumerateFiles(clientLocationPath.toString(), + "pom.xml", + true); + ArrayList clientProjectGradleBuildFiles = FileUtils.enumerateFiles(clientLocationPath.toString(), + "build.gradle", + true); + + ArrayList allProjectFiles = new ArrayList<>(); + allProjectFiles.addAll(clientProjectPomFiles); + allProjectFiles.addAll(clientProjectGradleBuildFiles); + + Orchestrator.copyInstrumentationToolAndConfiguration(allProjectFiles, + InstrumentationAgentEffectiveLocation, + libraryConfig); + + String javaToolOptions = "-javaagent:%s=%s".formatted(InstrumentationAgentEffectiveLocation, + libraryConfig.toString()); + + Orchestrator.orchestrateGradleProjects(clientProjectGradleBuildFiles); + + return javaToolOptions; + } + + private static void copyLibrarySourcesForDataset(String libraryIdentifier, Path librarySourceOutputPath, Path m2RepositoryPath) throws IOException, MavenInvocationException { + String[] groupId = libraryIdentifier.split(":")[0].split("\\."); + String artifactId = libraryIdentifier.split(":")[1]; + String libraryVersion = libraryIdentifier.split(":")[2]; + + Path fileSystemRepoPath = m2RepositoryPath; + + for (String pathComponent : groupId) { + fileSystemRepoPath = fileSystemRepoPath.resolve(pathComponent); + } + + fileSystemRepoPath = fileSystemRepoPath.resolve(artifactId).resolve(libraryVersion); + + ArrayList srcCandidates = FileUtils.enumerateFiles(fileSystemRepoPath.toString(), ".*sources\\.jar", false); + + if (srcCandidates.size() == 0) { + Main.logger.info("%s Missing sources for lib, aborting!".formatted(libraryIdentifier)); + return; + } + + FileUtils.deleteDirectoryIfExists(librarySourceOutputPath.toString()); + Files.createDirectories(librarySourceOutputPath); + + for (String cand : srcCandidates) { + System.out.println("Extracting " + cand); + System.out.println("Extracting " + librarySourceOutputPath); + extractJar(cand, librarySourceOutputPath.toString()); + } + } + + private static void extractJar(String jar, String destdir) throws java.io.IOException { + java.util.jar.JarFile jarFile = new java.util.jar.JarFile(new java.io.File(jar)); + java.util.Enumeration enu = jarFile.entries(); + while (enu.hasMoreElements()) { + java.util.jar.JarEntry je = enu.nextElement(); + + java.io.File fl = new java.io.File(destdir, je.getName()); + if (!fl.exists()) { + fl.getParentFile().mkdirs(); + fl = new java.io.File(destdir, je.getName()); + } + if (je.isDirectory()) { + continue; + } + java.io.InputStream is = jarFile.getInputStream(je); + java.io.FileOutputStream fo = new java.io.FileOutputStream(fl); + while (is.available() > 0) { + fo.write(is.read()); + } + fo.close(); + is.close(); + } + jarFile.close(); + } +} \ No newline at end of file diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/Orchestrator.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/Orchestrator.java new file mode 100644 index 00000000..7b7b7d84 --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/Orchestrator.java @@ -0,0 +1,267 @@ +package com.github.gilesi.illicocoverage; + +import com.github.gilesi.illicocoverage.buildsystem.configuration.maven.PomHelper; +import com.github.gilesi.illicocoverage.runners.maven.Log4JLogger; +import com.github.gilesi.illicocoverage.runners.maven.ProjectRunner; +import com.github.gilesi.illicocoverage.tools.ConfGenCoverage; +import com.github.gilesi.illicocoverage.tools.TestGen; +import org.apache.maven.shared.invoker.MavenInvocationException; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; + +public class Orchestrator { + // TODO: More robust logging here using JUnit perhaps... + // TODO: Simplify required arg to run here + // TODO: Ideally we would want to deduce the version from the provided source but not always doable + // TODO: Could we also fetch source jars ourselves somehow or source loc? + // TODO: Maybe the dependency name is also doable to extract here, need to see how... + // TODO: Fix cross plat! + // TODO: picocli? + + public static void handleProject(String clientLocation, String libraryLocation, String GilesiRepositoryLocation, String dependencyName, String dependencyVersion, String outputTestProject) throws XmlPullParserException, IOException, MavenInvocationException, InterruptedException { + FileUtils.deleteDirectoryIfExists(outputTestProject); + Path outputTestProjectPath = Path.of(outputTestProject); + + Path libraryLocationPath = Path.of(libraryLocation); + Path libraryConfig = libraryLocationPath.resolve(Constants.targetConfigurationName); + + // Create the results directory + Files.createDirectories(outputTestProjectPath); + + // Run ConfGen on Library first + // TODO: Check how meta projects get handled here exactly... + ConfGenCoverage.runConfGenCoverage(GilesiRepositoryLocation, libraryConfig.toString(), outputTestProject, libraryLocation); + + Path InstrumentationAgentEffectiveLocation = Path.of("%s%s".formatted(GilesiRepositoryLocation, Constants.COVERAGE_LOCATION)); + + // Modify Client Build System to use the instrumentation agent + ArrayList clientProjectPomFiles = FileUtils.enumerateFiles(clientLocation, "pom.xml", true); + ArrayList clientProjectGradleBuildFiles = FileUtils.enumerateFiles(clientLocation, "build.gradle", true); + + ArrayList allProjectFiles = new ArrayList<>(); + allProjectFiles.addAll(clientProjectPomFiles); + allProjectFiles.addAll(clientProjectGradleBuildFiles); + + copyInstrumentationToolAndConfiguration(allProjectFiles, InstrumentationAgentEffectiveLocation, libraryConfig); + + orchestrateMavenProjects(clientProjectPomFiles); + orchestrateGradleProjects(clientProjectGradleBuildFiles); + + // Run the client tests + executeMavenProjectTests(clientProjectPomFiles, clientLocation); + executeGradleProjectTests(clientProjectGradleBuildFiles, clientLocation); + + TestGen.runTestGen(GilesiRepositoryLocation, outputTestProject, outputTestProjectPath.resolve("src").resolve("test").resolve("java").toString(), libraryConfig.toString()); + + lastMinuteCleanup(allProjectFiles); + + // Generate Gradle test project here in dir outputTestProject, with dep dependencyName of version dependencyVersion + + String buildGradleProjectFile = """ + plugins { + id 'java' + } + + group = 'testProject' + version = '1.0-SNAPSHOT' + + repositories { + mavenCentral() + mavenLocal() + } + + dependencies { + testImplementation '%s:%s' + testImplementation 'com.thoughtworks.xstream:xstream:1.4.20' + testImplementation platform('org.junit:junit-bom:5.10.0') + testImplementation 'org.junit.jupiter:junit-jupiter' + } + + test { + useJUnitPlatform() + }""".formatted(dependencyName, dependencyVersion); + + Files.writeString(outputTestProjectPath.resolve("build.gradle"), buildGradleProjectFile); + Files.copy(Path.of(GilesiRepositoryLocation).resolve("IllicoCoverage").resolve("gradlew"), outputTestProjectPath.resolve("gradlew")); + Files.copy(Path.of(GilesiRepositoryLocation).resolve("IllicoCoverage").resolve("gradlew.bat"), outputTestProjectPath.resolve("gradlew.bat")); + + // Test test project (3 times to be sure) + + // Update test project dependency version at version dependencyNewVersion + + // Test test project with dependency at version dependencyNewVersion (3 times to be sure) + + // Collect results of the execution + + // Write report + } + + public static void copyInstrumentationToolAndConfiguration(ArrayList allProjectFiles, Path InstrumentationAgentEffectiveLocation, Path libraryConfig) throws IOException { + for (String clientBuildFile : allProjectFiles) { + Path clientBuildFilePath = Path.of(clientBuildFile); + Path clientProjectLocationPath = clientBuildFilePath.getParent().toAbsolutePath(); + Path agentDest = clientProjectLocationPath.resolve(Constants.targetAgentName); + Path workflowDest = clientProjectLocationPath.resolve(Constants.targetConfigurationName); + + Path clientBuildFileBackup = Path.of("%s.gilesi.bak".formatted(clientBuildFile)); + if (Files.exists(clientBuildFileBackup)) { + Files.deleteIfExists(clientBuildFilePath); + Files.move(clientBuildFileBackup, clientBuildFilePath); + } + + Files.deleteIfExists(agentDest); + Files.deleteIfExists(workflowDest); + + //Files.copy(InstrumentationAgentEffectiveLocation, agentDest); + //Files.copy(libraryConfig, workflowDest); + } + } + + public static void orchestrateMavenProjects(ArrayList clientProjectPomFiles) throws IOException, XmlPullParserException { + for (String clientBuildFile : clientProjectPomFiles) { + Path clientBuildFilePath = Path.of(clientBuildFile); + Files.copy(clientBuildFilePath, Path.of("%s.gilesi.bak".formatted(clientBuildFile))); + + String clientSurefireArgLine = "-Dnet.bytebuddy.experimental=true -javaagent:%s=%s".formatted(Constants.targetAgentName, Constants.targetConfigurationName); + PomHelper.addSureFire(clientBuildFile, clientSurefireArgLine); + } + } + + public static void orchestrateGradleProjects(ArrayList clientProjectGradleBuildFiles) throws IOException { + for (String clientBuildFile : clientProjectGradleBuildFiles) { + String clientTestingBlock = """ + testing { + \tsuites { + \t\ttest { + \t\t\tuseJUnitJupiter() + \t\t\ttargets { + \t\t\t\tall { + \t\t\t\t\ttestTask.configure { + \t\t\t\t\t\tsystemProperty 'net.bytebuddy.experimental', 'true' + \t\t\t\t\t\tjvmArgs = ['-XX:+EnableDynamicAgentLoading', '-javaagent:%s=%s'] + \t\t\t\t\t} + \t\t\t\t} + \t\t\t} + \t\t} + \t} + }""".formatted(Constants.targetAgentName, Constants.targetConfigurationName); + + Path clientBuildFilePath = Path.of(clientBuildFile); + + String buildFileContent = Files.readString(clientBuildFilePath); + if (!buildFileContent.contains(clientTestingBlock)) { + buildFileContent += "\n" + clientTestingBlock; + + Files.move(clientBuildFilePath, Path.of("%s.gilesi.bak".formatted(clientBuildFile))); + Files.writeString(clientBuildFilePath, buildFileContent); + } + } + } + + public static void executeMavenProjectTests(ArrayList clientProjectPomFiles, String clientLocation) throws MavenInvocationException { + // This is the maven path + if (!clientProjectPomFiles.isEmpty()) { + if (clientProjectPomFiles.stream().anyMatch(t -> t.replace("%s%c".formatted(clientLocation, File.separatorChar), "").replace(clientLocation, "").equals("pom.xml"))) { + ProjectRunner.runMavenGoalOnRepository(clientLocation, "-fn test -Danimal.sniffer.skip=true", null, "21", new Log4JLogger(), null); + } else { + for (String clientPomFile : clientProjectPomFiles) { + Path clientPomFilePath = Path.of(clientPomFile); + Path clientProjectLocationPath = clientPomFilePath.getParent().toAbsolutePath(); + + ProjectRunner.runMavenGoalOnRepository(clientProjectLocationPath.toString(), "-fn test -Danimal.sniffer.skip=true", null, "21", new Log4JLogger(), null); + } + } + } + } + + public static void executeGradleProjectTests(ArrayList clientProjectGradleBuildFiles, String clientLocation) throws IOException, InterruptedException { + // This is the gradle path + if (!clientProjectGradleBuildFiles.isEmpty()) { + if (clientProjectGradleBuildFiles.stream().anyMatch + ( + t -> + t + .replace( + "%s%c" + .formatted( + clientLocation, + File.separatorChar + ), + "" + ) + .replace( + clientLocation, + "" + ) + .equals("build.gradle") + )) { + ProcessUtils.runExternalCommand(new String[]{ + "/usr/bin/env", + "bash", + Path.of(clientLocation).resolve("gradlew").toString(), + "test", + "--warning-mode", + "all" + }, new File(clientLocation), Main.logger); + + // TODO: Check for underlying platform + /*ProcessUtils.runExternalCommand(new String[]{ + "cmd.exe", + "-C", + Path.of(clientLocation).resolve("gradlew").toString(), + "test", + "--warning-mode", + "all" + }, new File(clientLocation), Main.logger);*/ + } else { + for (String clientGradleBuildFile : clientProjectGradleBuildFiles) { + Path clientGradleBuildFilePath = Path.of(clientGradleBuildFile); + Path clientProjectLocationPath = clientGradleBuildFilePath.getParent().toAbsolutePath(); + + ProcessUtils.runExternalCommand(new String[]{ + "/usr/bin/env", + "bash", + clientProjectLocationPath.resolve("gradlew").toString(), + "test", + "--warning-mode", + "all" + }, new File(clientProjectLocationPath.toString()), Main.logger); + + // TODO: Check for underlying platform + /*ProcessUtils.runExternalCommand(new String[]{ + "cmd.exe", + "-C", + clientProjectLocationPath.resolve("gradlew.bat").toString(), + "test", + "--warning-mode", + "all" + }, new File(clientProjectLocationPath.toString()), Main.logger);*/ + } + } + } + } + + public static void lastMinuteCleanup(ArrayList allProjectFiles) throws IOException { + // Last minute cleanup + for (String clientBuildFile : allProjectFiles) { + Path clientBuildFilePath = Path.of(clientBuildFile); + Path clientProjectLocationPath = clientBuildFilePath.getParent().toAbsolutePath(); + Path agentDest = clientProjectLocationPath.resolve(Constants.targetAgentName); + Path workflowDest = clientProjectLocationPath.resolve(Constants.targetConfigurationName); + + Path clientBuildFileBackup = Path.of("%s.gilesi.bak".formatted(clientBuildFile)); + if (Files.exists(clientBuildFileBackup)) { + Files.deleteIfExists(clientBuildFilePath); + Files.move(clientBuildFileBackup, clientBuildFilePath); + } + + Files.deleteIfExists(agentDest); + Files.deleteIfExists(workflowDest); + } + } +} diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/ProcessUtils.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/ProcessUtils.java new file mode 100644 index 00000000..d83f9c72 --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/ProcessUtils.java @@ -0,0 +1,26 @@ +package com.github.gilesi.illicocoverage; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; + +public class ProcessUtils { + public static int runExternalCommand(String[] command, File directory, org.apache.logging.log4j.Logger logger) throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder(command); + if (directory != null) { + pb.directory(directory); + } + pb.redirectErrorStream(true); + Process p = pb.start(); + BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); + + String line; + + while ((line = reader.readLine()) != null) { + logger.info(line); + } + + return p.waitFor(); + } +} diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/RunResult.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/RunResult.java new file mode 100644 index 00000000..dd3629fd --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/RunResult.java @@ -0,0 +1,22 @@ +package com.github.gilesi.illicocoverage; + +import com.github.gilesi.illicocoverage.testing.ReportItem; + +import java.util.ArrayList; + +public class RunResult { + public String ProjectId = ""; + public boolean ProjectIsMissingLibrarySourceCode = false; + public boolean ProjectCannotBeReproduced = false; // TODO + public boolean ProjectCannotBeInstrumented = false; + public boolean ProjectFailedToLoadAgent = false; + public boolean ProjectIsMissingTraceFiles = false; + public boolean ProjectIsMissingSpecificTraces = false; + public boolean ProjectFailedToGenerateAnyTraceTestSrc = false; + public boolean ProjectFailedToCompileGeneratedTests = false; + public boolean ProjectFailedToExecuteGeneratedTests = false; + public boolean ProjectFailedToCompileGeneratedTestsWithNewVersion = false; + public boolean ProjectFailedToExecuteGeneratedTestsWithNewVersion = false; + public ArrayList TestExecutionReportWithOldVersion; + public ArrayList TestExecutionReportWithNewVersion; +} diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/buildsystem/configuration/maven/ConfigurationUtils.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/buildsystem/configuration/maven/ConfigurationUtils.java new file mode 100644 index 00000000..d6b50802 --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/buildsystem/configuration/maven/ConfigurationUtils.java @@ -0,0 +1,31 @@ +package com.github.gilesi.illicocoverage.buildsystem.configuration.maven; + +import org.codehaus.plexus.util.xml.Xpp3Dom; + +public class ConfigurationUtils { + public static Xpp3Dom getDomValueObject(String name, String value) { + Xpp3Dom domValue = new Xpp3Dom(name); + domValue.setValue(value); + return domValue; + } + + public static Xpp3Dom getDomArrayObject(String arrayName, String name, String[] values) { + Xpp3Dom valuesDom = new Xpp3Dom(arrayName); + for (String val : values) { + addStringValueIfNotEmpty(valuesDom, name, val); + } + return valuesDom; + } + + public static void addStringValueIfNotEmpty(Xpp3Dom config, String name, String value) { + if (value != null && !value.isEmpty()) { + config.addChild(getDomValueObject(name, value)); + } + } + + public static void addStringArrayValueIfNotEmpty(Xpp3Dom config, String arrayName, String name, String[] values) { + if (values != null && values.length > 0) { + config.addChild(getDomArrayObject(arrayName, name, values)); + } + } +} diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/buildsystem/configuration/maven/IPluginConfiguration.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/buildsystem/configuration/maven/IPluginConfiguration.java new file mode 100644 index 00000000..708afa64 --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/buildsystem/configuration/maven/IPluginConfiguration.java @@ -0,0 +1,7 @@ +package com.github.gilesi.illicocoverage.buildsystem.configuration.maven; + +import org.codehaus.plexus.util.xml.Xpp3Dom; + +public interface IPluginConfiguration { + Xpp3Dom serialize(); +} diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/buildsystem/configuration/maven/PomHelper.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/buildsystem/configuration/maven/PomHelper.java new file mode 100644 index 00000000..67f852fc --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/buildsystem/configuration/maven/PomHelper.java @@ -0,0 +1,154 @@ +package com.github.gilesi.illicocoverage.buildsystem.configuration.maven; + +import com.github.gilesi.illicocoverage.Constants; +import org.apache.maven.model.Build; +import org.apache.maven.model.Dependency; +import org.apache.maven.model.Model; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.apache.maven.model.io.xpp3.MavenXpp3Writer; +import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; + +public class PomHelper { + + public static Model readPomFile(String pomFilePath) throws IOException, XmlPullParserException { + MavenXpp3Reader pomReader = new MavenXpp3Reader(); + Path pomPath = Path.of(pomFilePath); + InputStream pomStream = Files.newInputStream(pomPath); + Model pomModel = pomReader.read(pomStream); + pomModel.setPomFile(new File(pomFilePath)); + pomStream.close(); + return pomModel; + } + + private static void writePomFile(Model pomModel, String pomFilePath) throws IOException { + MavenXpp3Writer pomWriter = new MavenXpp3Writer(); + FileWriter pomWriteStream = new FileWriter(pomFilePath); + pomWriter.write(pomWriteStream, pomModel); + pomWriteStream.close(); + } + + private static boolean doesBuildContainPluginGroupId(Build bld, String groupId) { + for (Plugin plg : bld.getPlugins()) { + if (plg.getGroupId().equalsIgnoreCase(groupId)) { + return true; + } + } + return false; + } + + private static boolean doesModelContainDependencyGroupId(Model pomModel, String groupId) { + for (Dependency plg : pomModel.getDependencies()) { + if (plg.getGroupId().equalsIgnoreCase(groupId)) { + return true; + } + } + return false; + } + + private static Plugin getPluginByGroupId(Build bld, String groupId) throws FileNotFoundException { + for (Plugin plg : bld.getPlugins()) { + if (plg.getGroupId().equalsIgnoreCase(groupId)) { + return plg; + } + } + throw new FileNotFoundException(); + } + + private static Xpp3Dom buildSureFireConfiguration() { + SureFirePluginConfiguration sureFireConfiguration = new SureFirePluginConfiguration(); + return sureFireConfiguration.serialize(); + } + + private static Xpp3Dom buildSureFireConfiguration(String argLine) { + SureFirePluginConfiguration sureFireConfiguration = new SureFirePluginConfiguration(); + sureFireConfiguration.setArgLine(argLine); + return sureFireConfiguration.serialize(); + } + + private static Plugin getSureFirePlugin() { + return buildPlugin(Constants.SUREFIRE_GROUPID, Constants.SUREFIRE_ARTIFACTID, Constants.SUREFIRE_VERSION); + } + + private static Dependency buildDependency(String groupId, String artifactId, String version) { + Dependency dependency = new Dependency(); + dependency.setGroupId(groupId); + dependency.setArtifactId(artifactId); + dependency.setVersion(version); + return dependency; + } + + private static Plugin buildPlugin(String groupId, String artifactId, String version) { + Plugin plugin = new Plugin(); + plugin.setGroupId(groupId); + plugin.setArtifactId(artifactId); + plugin.setVersion(version); + return plugin; + } + + private static Plugin buildSureFirePlugin() { + Plugin surePlugin = getSureFirePlugin(); + Xpp3Dom config = buildSureFireConfiguration(); + surePlugin.setConfiguration(config); + return surePlugin; + } + + private static Plugin buildSureFirePlugin(String argLine) { + Plugin surePlugin = getSureFirePlugin(); + Xpp3Dom config = buildSureFireConfiguration(argLine); + surePlugin.setConfiguration(config); + return surePlugin; + } + + private static void removeExistingPlugin(Build bld, String groupId) throws FileNotFoundException { + // Get rid of all previous instances of the plugin in case one already exists + while (doesBuildContainPluginGroupId(bld, groupId)) { + bld.removePlugin(getPluginByGroupId(bld, groupId)); + } + } + + private static void removeExistingSureFirePlugins(Build bld) throws FileNotFoundException { + removeExistingPlugin(bld, Constants.SUREFIRE_GROUPID); + } + + public static void addSureFire(String pomFilePath) throws XmlPullParserException, IOException { + Model pomModel = readPomFile(pomFilePath); + Build bld = pomModel.getBuild(); + + if (bld == null) { + bld = new Build(); + } + + removeExistingSureFirePlugins(bld); + + Plugin surePlugin = buildSureFirePlugin(); + + bld.addPlugin(surePlugin); + + pomModel.setBuild(bld); + writePomFile(pomModel, pomFilePath); + } + + public static void addSureFire(String pomFilePath, String argLine) throws XmlPullParserException, IOException { + Model pomModel = readPomFile(pomFilePath); + Build bld = pomModel.getBuild(); + + if (bld == null) { + bld = new Build(); + } + + removeExistingSureFirePlugins(bld); + + Plugin surePlugin = buildSureFirePlugin(argLine); + + bld.addPlugin(surePlugin); + + pomModel.setBuild(bld); + writePomFile(pomModel, pomFilePath); + } +} diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/buildsystem/configuration/maven/SureFirePluginConfiguration.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/buildsystem/configuration/maven/SureFirePluginConfiguration.java new file mode 100644 index 00000000..339b33b4 --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/buildsystem/configuration/maven/SureFirePluginConfiguration.java @@ -0,0 +1,103 @@ +package com.github.gilesi.illicocoverage.buildsystem.configuration.maven; + +import org.codehaus.plexus.util.xml.Xpp3Dom; + +public class SureFirePluginConfiguration implements IPluginConfiguration { + private int forkCount = 1; + private boolean reuseForks = false; + private String argLine = "-Xmx2G -XX:MaxMetaspaceSize=2G"; + private String parallel = "methods"; + private int threadCount = 1; + private String forkedProcessTimeoutInSeconds = "40"; + private String forkedProcessExitTimeoutInSeconds = "40"; + private String parallelTestsTimeoutInSeconds = "30"; + private String parallelTestsTimeoutForcedInSeconds = "30"; + + public int getForkCount() { + return forkCount; + } + + public void setForkCount(int forkCount) { + this.forkCount = forkCount; + } + + public int getThreadCount() { + return threadCount; + } + + public void setThreadCount(int threadCount) { + this.threadCount = threadCount; + } + + public String getArgLine() { + return argLine; + } + + public void setArgLine(String argLine) { + this.argLine = argLine; + } + + public String getForkedProcessExitTimeoutInSeconds() { + return forkedProcessExitTimeoutInSeconds; + } + + public void setForkedProcessExitTimeoutInSeconds(String forkedProcessExitTimeoutInSeconds) { + this.forkedProcessExitTimeoutInSeconds = forkedProcessExitTimeoutInSeconds; + } + + public String getForkedProcessTimeoutInSeconds() { + return forkedProcessTimeoutInSeconds; + } + + public void setForkedProcessTimeoutInSeconds(String forkedProcessTimeoutInSeconds) { + this.forkedProcessTimeoutInSeconds = forkedProcessTimeoutInSeconds; + } + + public String getParallelTestsTimeoutForcedInSeconds() { + return parallelTestsTimeoutForcedInSeconds; + } + + public void setParallelTestsTimeoutForcedInSeconds(String parallelTestsTimeoutForcedInSeconds) { + this.parallelTestsTimeoutForcedInSeconds = parallelTestsTimeoutForcedInSeconds; + } + + public String getParallelTestsTimeoutInSeconds() { + return parallelTestsTimeoutInSeconds; + } + + public void setParallelTestsTimeoutInSeconds(String parallelTestsTimeoutInSeconds) { + this.parallelTestsTimeoutInSeconds = parallelTestsTimeoutInSeconds; + } + + public String getParallel() { + return parallel; + } + + public void setParallel(String parallel) { + this.parallel = parallel; + } + + public boolean getReuseForks() { + return reuseForks; + } + + public void setReuseForks(boolean reuseForks) { + this.reuseForks = reuseForks; + } + + public Xpp3Dom serialize() { + Xpp3Dom config = new Xpp3Dom("configuration"); + + ConfigurationUtils.addStringValueIfNotEmpty(config, "forkCount", Integer.toString(forkCount)); + ConfigurationUtils.addStringValueIfNotEmpty(config, "reuseForks", Boolean.toString(reuseForks)); + ConfigurationUtils.addStringValueIfNotEmpty(config, "argLine", argLine); + ConfigurationUtils.addStringValueIfNotEmpty(config, "parallel", parallel); + ConfigurationUtils.addStringValueIfNotEmpty(config, "threadCount", Integer.toString(threadCount)); + ConfigurationUtils.addStringValueIfNotEmpty(config, "forkedProcessTimeoutInSeconds", forkedProcessTimeoutInSeconds); + ConfigurationUtils.addStringValueIfNotEmpty(config, "forkedProcessExitTimeoutInSeconds", forkedProcessExitTimeoutInSeconds); + ConfigurationUtils.addStringValueIfNotEmpty(config, "parallelTestsTimeoutInSeconds", parallelTestsTimeoutInSeconds); + ConfigurationUtils.addStringValueIfNotEmpty(config, "parallelTestsTimeoutForcedInSeconds", parallelTestsTimeoutForcedInSeconds); + + return config; + } +} diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/runners/maven/ConsoleLogger.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/runners/maven/ConsoleLogger.java new file mode 100644 index 00000000..0b4775df --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/runners/maven/ConsoleLogger.java @@ -0,0 +1,248 @@ +package com.github.gilesi.illicocoverage.runners.maven; + +/* + * 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.shared.invoker.InvocationOutputHandler; +import org.apache.maven.shared.invoker.InvokerLogger; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Date; + +/** + * Offers a logger that writes to a print stream like {@link java.lang.System#out}. + * + * @since 2.0.9 + */ +public class ConsoleLogger implements InvokerLogger, InvocationOutputHandler { + + /** + * The threshold used to filter messages. + */ + private int threshold; + + /** + * Creates a new logger that writes to {@link java.lang.System#out} and has a threshold of {@link #INFO}. + */ + public ConsoleLogger() { + this(INFO); + } + + /** + * Creates a new logger that writes to the specified print stream. + * + * @param threshold The threshold for the logger. + */ + public ConsoleLogger(int threshold) { + setThreshold(threshold); + } + + /** + * Writes the specified message and exception to the print stream. + * + * @param level The priority level of the message. + * @param message The message to log, may be null. + * @param error The exception to log, may be null. + */ + private void log(int level, String message, Throwable error) { + if (level > threshold) { + // don't log when it doesn't match your threshold. + return; + } + + if (message == null && error == null) { + // don't log when there's nothing to log. + return; + } + + StringBuilder buffer = new StringBuilder(); + String logLevel = "INFO"; + + switch (level) { + case (DEBUG) -> logLevel = "DEBUG"; + case (INFO) -> logLevel = "INFO"; + case (WARN) -> logLevel = "WARN"; + case (ERROR) -> logLevel = "ERROR"; + case (FATAL) -> logLevel = "FATAL"; + default -> { + } + } + + buffer.append("[Maven] "); + + if (message != null) { + buffer.append(message); + } + + if (error != null) { + StringWriter writer = new StringWriter(); + PrintWriter pWriter = new PrintWriter(writer); + + error.printStackTrace(pWriter); + + if (message != null) { + buffer.append('\n'); + } + + buffer.append("Error:\n"); + buffer.append(writer); + } + + System.out.printf("[%s] [%s] %s%n", new Date(), logLevel, buffer); + } + + /** + * {@inheritDoc} + */ + public void debug(String message) { + log(DEBUG, message, null); + } + + /** + * {@inheritDoc} + */ + public void debug(String message, Throwable throwable) { + log(DEBUG, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void info(String message) { + log(INFO, message, null); + } + + /** + * {@inheritDoc} + */ + public void info(String message, Throwable throwable) { + log(INFO, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void warn(String message) { + log(WARN, message, null); + } + + /** + * {@inheritDoc} + */ + public void warn(String message, Throwable throwable) { + log(WARN, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void error(String message) { + log(ERROR, message, null); + } + + /** + * {@inheritDoc} + */ + public void error(String message, Throwable throwable) { + log(ERROR, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void fatalError(String message) { + log(FATAL, message, null); + } + + /** + * {@inheritDoc} + */ + public void fatalError(String message, Throwable throwable) { + log(FATAL, message, throwable); + } + + /** + *

isDebugEnabled.

+ * + * @return a boolean. + */ + public boolean isDebugEnabled() { + return threshold >= DEBUG; + } + + /** + *

isErrorEnabled.

+ * + * @return a boolean. + */ + public boolean isErrorEnabled() { + return threshold >= ERROR; + } + + /** + *

isFatalErrorEnabled.

+ * + * @return a boolean. + */ + public boolean isFatalErrorEnabled() { + return threshold >= FATAL; + } + + /** + *

isInfoEnabled.

+ * + * @return a boolean. + */ + public boolean isInfoEnabled() { + return threshold >= INFO; + } + + /** + *

isWarnEnabled.

+ * + * @return a boolean. + */ + public boolean isWarnEnabled() { + return threshold >= WARN; + } + + /** + *

Getter for the field threshold.

+ * + * @return an int. + */ + public int getThreshold() { + return threshold; + } + + /** + * {@inheritDoc} + */ + public void setThreshold(int threshold) { + this.threshold = threshold; + } + + /** + * {@inheritDoc} + */ + public void consumeLine(String line) { + log(INFO, line, null); + } +} \ No newline at end of file diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/runners/maven/Log4JLogger.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/runners/maven/Log4JLogger.java new file mode 100644 index 00000000..72c2fb13 --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/runners/maven/Log4JLogger.java @@ -0,0 +1,271 @@ +package com.github.gilesi.illicocoverage.runners.maven; + +/* + * 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.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.maven.shared.invoker.InvocationOutputHandler; +import org.apache.maven.shared.invoker.InvokerLogger; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * Offers a logger that writes to a print stream like {@link java.lang.System#out}. + * + * @since 2.0.9 + */ +public class Log4JLogger implements InvokerLogger, InvocationOutputHandler { + + /** + * The print stream to write to, never null. + */ + private final Logger out; + + /** + * The threshold used to filter messages. + */ + private int threshold; + + private String prefix; + + /** + * Creates a new logger that writes to {@link java.lang.System#out} and has a threshold of {@link #INFO}. + */ + public Log4JLogger() { + this(LogManager.getLogger(), INFO, ""); + } + + public Log4JLogger(String prefix) { + this(LogManager.getLogger(), INFO, prefix); + } + + /** + * Creates a new logger that writes to the specified print stream. + * + * @param out The print stream to write to, must not be null. + * @param threshold The threshold for the logger. + */ + public Log4JLogger(Logger out, int threshold, String prefix) { + if (out == null) { + throw new NullPointerException("missing output stream"); + } + this.prefix = prefix; + this.out = out; + setThreshold(threshold); + } + + /** + * Writes the specified message and exception to the print stream. + * + * @param level The priority level of the message. + * @param message The message to log, may be null. + * @param error The exception to log, may be null. + */ + private void log(int level, String message, Throwable error) { + if (level > threshold) { + // don't log when it doesn't match your threshold. + return; + } + + if (message == null && error == null) { + // don't log when there's nothing to log. + return; + } + + StringBuilder buffer = new StringBuilder(); + Level logLevel = Level.INFO; + + switch (level) { + case (DEBUG) -> logLevel = Level.DEBUG; + case (INFO) -> logLevel = Level.INFO; + case (WARN) -> logLevel = Level.WARN; + case (ERROR) -> logLevel = Level.ERROR; + case (FATAL) -> logLevel = Level.FATAL; + default -> { + } + } + + if (prefix != null && !prefix.isEmpty()) { + buffer.append("[%s-Maven] ".formatted(prefix)); + } else { + buffer.append("[Maven] "); + } + + if (message != null) { + buffer.append(message); + } + + if (error != null) { + StringWriter writer = new StringWriter(); + PrintWriter pWriter = new PrintWriter(writer); + + error.printStackTrace(pWriter); + + if (message != null) { + buffer.append('\n'); + } + + buffer.append("Error:\n"); + buffer.append(writer); + } + + out.log(logLevel, buffer.toString()); + } + + /** + * {@inheritDoc} + */ + public void debug(String message) { + log(DEBUG, message, null); + } + + /** + * {@inheritDoc} + */ + public void debug(String message, Throwable throwable) { + log(DEBUG, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void info(String message) { + log(INFO, message, null); + } + + /** + * {@inheritDoc} + */ + public void info(String message, Throwable throwable) { + log(INFO, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void warn(String message) { + log(WARN, message, null); + } + + /** + * {@inheritDoc} + */ + public void warn(String message, Throwable throwable) { + log(WARN, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void error(String message) { + log(ERROR, message, null); + } + + /** + * {@inheritDoc} + */ + public void error(String message, Throwable throwable) { + log(ERROR, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void fatalError(String message) { + log(FATAL, message, null); + } + + /** + * {@inheritDoc} + */ + public void fatalError(String message, Throwable throwable) { + log(FATAL, message, throwable); + } + + /** + *

isDebugEnabled.

+ * + * @return a boolean. + */ + public boolean isDebugEnabled() { + return threshold >= DEBUG; + } + + /** + *

isErrorEnabled.

+ * + * @return a boolean. + */ + public boolean isErrorEnabled() { + return threshold >= ERROR; + } + + /** + *

isFatalErrorEnabled.

+ * + * @return a boolean. + */ + public boolean isFatalErrorEnabled() { + return threshold >= FATAL; + } + + /** + *

isInfoEnabled.

+ * + * @return a boolean. + */ + public boolean isInfoEnabled() { + return threshold >= INFO; + } + + /** + *

isWarnEnabled.

+ * + * @return a boolean. + */ + public boolean isWarnEnabled() { + return threshold >= WARN; + } + + /** + *

Getter for the field threshold.

+ * + * @return an int. + */ + public int getThreshold() { + return threshold; + } + + /** + * {@inheritDoc} + */ + public void setThreshold(int threshold) { + this.threshold = threshold; + } + + /** + * {@inheritDoc} + */ + public void consumeLine(String line) { + log(INFO, line, null); + } +} \ No newline at end of file diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/runners/maven/ProjectRunner.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/runners/maven/ProjectRunner.java new file mode 100644 index 00000000..7a6eacbd --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/runners/maven/ProjectRunner.java @@ -0,0 +1,181 @@ +package com.github.gilesi.illicocoverage.runners.maven; + +import com.github.gilesi.illicocoverage.Constants; +import com.github.gilesi.illicocoverage.Main; +import org.apache.maven.model.*; +import org.apache.maven.shared.invoker.*; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import static com.github.gilesi.illicocoverage.buildsystem.configuration.maven.PomHelper.readPomFile; + +public class ProjectRunner { + + public static int runPomGoals(String pomFilePath, List pomGoals, List pomProperties, + boolean batchMode, String userSettingsFilePath, Object logger, String javaToolOptions) + throws MavenInvocationException { + + String javaVersion = "8"; + + try { + Model pomModel = readPomFile(pomFilePath); + Properties props = pomModel.getProperties(); + if (props.stringPropertyNames().contains("java.compiler.version")) { + javaVersion = props.getProperty("java.compiler.version"); + Main.logger.info("Debug: Using java.compiler.version=" + javaVersion); + } else if (props.stringPropertyNames().contains("maven.compiler.source")) { + javaVersion = props.getProperty("maven.compiler.source"); + Main.logger.info("Debug: Using maven.compiler.source=" + javaVersion); + } else if (props.stringPropertyNames().contains("maven.compiler.target")) { + javaVersion = props.getProperty("maven.compiler.target"); + Main.logger.info("Debug: Using maven.compiler.target=" + javaVersion); + } else if (props.stringPropertyNames().contains("maven.compile.source")) { + javaVersion = props.getProperty("maven.compile.source"); + Main.logger.info("Debug: Using maven.compile.source=" + javaVersion); + } else if (props.stringPropertyNames().contains("maven.compile.target")) { + javaVersion = props.getProperty("maven.compile.target"); + Main.logger.info("Debug: Using maven.compile.target=" + javaVersion); + } + } catch (Exception ignored) { + Main.logger.info("Debug: Getting java specific version unfortunately failed."); + } + + return runPomGoals(pomFilePath, pomGoals, pomProperties, batchMode, userSettingsFilePath, javaVersion, logger, + javaToolOptions); + } + + public static int runPomGoals(String pomFilePath, List pomGoals, List pomProperties, + boolean batchMode, String userSettingsFilePath, String javaVersion, Object logger, String javaToolOptions) + throws MavenInvocationException { + String javaHome = Constants.JAVA_VERSIONS.getOrDefault(javaVersion, System.getenv("JAVA_HOME")); + return runPomGoalsInternal(pomFilePath, pomGoals, pomProperties, batchMode, userSettingsFilePath, javaHome, + javaVersion, logger, javaToolOptions); + } + + public static int runPomGoalsInternal(String pomFilePath, List pomGoals, List pomProperties, + boolean batchMode, String userSettingsFilePath, String JavaHome, String JavaVersion, Object logger, + String javaToolOptions) throws MavenInvocationException { + File pomFile = new File(pomFilePath); + + InvokerLogger invokerLogger = null; + InvocationOutputHandler invocationOutputHandler = null; + + if (logger instanceof InvokerLogger tmp) { + invokerLogger = tmp; + } + + if (logger instanceof InvocationOutputHandler tmp) { + invocationOutputHandler = tmp; + } + + File projectDirectory = new File(Path.of(pomFilePath).getParent().toString()); + + Properties properties = new Properties(); + pomProperties.forEach(p -> properties.setProperty(p, "true")); + + properties.setProperty("maven.compiler.source", JavaVersion); + properties.setProperty("maven.compiler.target", JavaVersion); + properties.setProperty("maven.compile.source", JavaVersion); + properties.setProperty("maven.compile.target", JavaVersion); + + properties.setProperty("maven.source.skip", "true"); + properties.setProperty("assembly.skipAssembly", "true"); + properties.setProperty("shade.skip", "true"); + properties.setProperty("maven.war.skip", "true"); + properties.setProperty("maven.rar.skip", "true"); + properties.setProperty("changelog.skip", "true"); + properties.setProperty("checkstyle.skip", "true"); + properties.setProperty("maven.doap.skip", "true"); + properties.setProperty("maven.javadoc.skip", "true"); + properties.setProperty("maven.jxr.skip", "true"); + properties.setProperty("linkcheck.skip", "true"); + properties.setProperty("pmd.skip", "true"); + properties.setProperty("mpir.skip", "true"); + properties.setProperty("gpg.skip", "true"); + properties.setProperty("jdepend.skip", "true"); + + String javacLocation = "%s%sbin%sjavac".formatted(JavaHome, File.separator, File.separator); + pomGoals.add("-Dmaven.compiler.fork=true"); + if (Files.exists(Path.of(javacLocation))) { + pomGoals.add("-Dmaven.compiler.executable=%s".formatted(javacLocation)); + } + + InvocationRequest request = new DefaultInvocationRequest(); + + Integer timeout = 30 * 60; // 30 minutes in seconds + + request.setShellEnvironmentInherited(false); + request.setPomFile(pomFile); + request.setGoals(pomGoals); + request.setProperties(properties); + request.setAlsoMake(true); + request.setBatchMode(true); + request.setTimeoutInSeconds(timeout); + + request.setJavaHome(new File(JavaHome)); + request.setBaseDirectory(projectDirectory); + request.setInputStream(InputStream.nullInputStream()); + if (invocationOutputHandler != null) { + request.setOutputHandler(invocationOutputHandler); + request.setErrorHandler(invocationOutputHandler); + } + + if (userSettingsFilePath != null && !userSettingsFilePath.isEmpty() && !userSettingsFilePath.isBlank()) { + Path pa = Path.of(userSettingsFilePath); + if (Files.exists(pa) && Files.isRegularFile(pa)) { + request.setUserSettingsFile(new File(userSettingsFilePath)); + } + } + + if (javaToolOptions != null && !javaToolOptions.isEmpty()) { + request.addShellEnvironment("JAVA_TOOL_OPTIONS", javaToolOptions); + } + + Main.logger.info("Building with pom=%s goals=%s properties=%s%n" + .formatted(pomFilePath, + pomGoals, + properties)); + + try { + Invoker invoker = new DefaultInvoker(); + if (invokerLogger != null) { + invoker.setLogger(invokerLogger); + } + + invoker.setMavenHome(new File(System.getenv("MAVEN_HOME"))); + + InvocationResult result = invoker.execute(request); + + Main.logger.info("Building with pom={} goals={} properties={} returned {}", + pomFile, + pomGoals, + properties, + result.getExitCode()); + + return result.getExitCode(); + } catch (MavenInvocationException e) { + throw e; + } + } + + public static boolean runMavenGoalOnRepository(String repositoryDirectory, String goal, String userSettingsFilePath, + String javaVersion, Object logger, String javaToolOptions) throws MavenInvocationException { + Main.logger.info("ExecuteMavenGoalOnDuetsRepository Entry"); + String pomFilePath = "%s%spom.xml".formatted(repositoryDirectory, File.separator); + ArrayList goals = new ArrayList<>(); + goals.add(goal); + + if (javaVersion != null && !javaVersion.isBlank()) { + return runPomGoals(pomFilePath, goals, new ArrayList<>(), false, userSettingsFilePath, javaVersion, logger, + javaToolOptions) == 0; + } else { + return runPomGoals(pomFilePath, goals, new ArrayList<>(), false, userSettingsFilePath, logger, + javaToolOptions) == 0; + } + } +} \ No newline at end of file diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/runners/maven/TestAssertedLogger.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/runners/maven/TestAssertedLogger.java new file mode 100644 index 00000000..fc5921a6 --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/runners/maven/TestAssertedLogger.java @@ -0,0 +1,192 @@ +package com.github.gilesi.illicocoverage.runners.maven; + +import org.apache.maven.shared.invoker.InvocationOutputHandler; +import org.apache.maven.shared.invoker.InvokerLogger; + +import java.io.IOException; + +public class TestAssertedLogger implements InvokerLogger, InvocationOutputHandler { + + private InvokerLogger invokerLogger = null; + private InvocationOutputHandler invocationOutputHandler = null; + private boolean testFailed = false; + + public TestAssertedLogger(Object logger) { + if (logger instanceof InvokerLogger tmp) { + invokerLogger = tmp; + } + + if (logger instanceof InvocationOutputHandler tmp) { + invocationOutputHandler = tmp; + } + } + + + public void debug(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.debug(message); + } + } + + public void debug(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.debug(message, throwable); + } + } + + public boolean isDebugEnabled() { + if (invokerLogger != null) { + return invokerLogger.isDebugEnabled(); + } + + return false; + } + + public void info(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.info(message); + } + } + + public void info(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.info(message, throwable); + } + } + + public boolean isInfoEnabled() { + if (invokerLogger != null) { + return invokerLogger.isInfoEnabled(); + } + + return false; + } + + public void warn(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.warn(message); + } + } + + public void warn(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.warn(message, throwable); + } + } + + public boolean isWarnEnabled() { + if (invokerLogger != null) { + return invokerLogger.isWarnEnabled(); + } + + return false; + } + + public void error(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.error(message); + } + } + + public void error(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.error(message, throwable); + } + } + + public boolean isErrorEnabled() { + if (invokerLogger != null) { + return invokerLogger.isErrorEnabled(); + } + + return false; + } + + public void fatalError(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.fatalError(message); + } + } + + public void fatalError(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.fatalError(message, throwable); + } + } + + public boolean isFatalErrorEnabled() { + if (invokerLogger != null) { + return invokerLogger.isFatalErrorEnabled(); + } + + return false; + } + + public int getThreshold() { + if (invokerLogger != null) { + return invokerLogger.getThreshold(); + } + + return 0; + } + + public void setThreshold(int threshold) { + if (invokerLogger != null) { + invokerLogger.setThreshold(threshold); + } + } + + public void consumeLine(String line) throws IOException { + if (line.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invocationOutputHandler != null) { + invocationOutputHandler.consumeLine(line); + } + } + + public boolean HasTestFailed() { + return testFailed; + } +} diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/ReportItem.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/ReportItem.java new file mode 100644 index 00000000..4479ddaf --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/ReportItem.java @@ -0,0 +1,65 @@ +package com.github.gilesi.illicocoverage.testing; + +import com.github.gilesi.illicocoverage.Constants; +import com.github.gilesi.illicocoverage.testing.models.Testcase; + +public class ReportItem { + public String Name; + public String Version; + private String TestName; + private String TestResult; + private String ErrorDetails; + + public ReportItem(String projectName, String projectCommit, Testcase testcase) { + this.TestName = "%s.%s".formatted(testcase.ClassName, testcase.Name); + this.TestResult = ((testcase.Error != null) || (testcase.Failure != null)) ? Constants.RED_TEST : Constants.GREEN_TEST; + this.ErrorDetails = (testcase.Error != null) ? testcase.Error.Message : ""; + this.Name = projectName; + this.Version = projectCommit; + } + + public String getName() { + return Name; + } + + public void setName(String name) { + Name = name; + } + + public String getVersion() { + return Version; + } + + public void setVersion(String version) { + Version = version; + } + + public String getTestName() { + return TestName; + } + + public void setTestName(String testName) { + TestName = testName; + } + + public String getTestResult() { + return TestResult; + } + + public void setTestResult(String testResult) { + TestResult = testResult; + } + + public String getErrorDetails() { + return ErrorDetails; + } + + public void setErrorDetails(String errorDetails) { + ErrorDetails = errorDetails; + } + + @Override + public String toString() { + return "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"".formatted(Name, Version, TestName, TestResult, ErrorDetails); + } +} diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/XmlParser.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/XmlParser.java new file mode 100644 index 00000000..c1bbc838 --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/XmlParser.java @@ -0,0 +1,33 @@ +package com.github.gilesi.illicocoverage.testing; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.github.gilesi.illicocoverage.testing.models.Testsuite; +import com.github.gilesi.illicocoverage.testing.models.Testsuites; + +import java.io.File; +import java.io.IOException; + +public class XmlParser { + + private static final XmlMapper mapper = XmlMapper + .builder() + .defaultUseWrapper(false) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .build(); + + public static Testsuite deserializeTestsuite(String XmlPath) throws IOException { + File fi = new File(XmlPath); + return mapper.readValue(fi, Testsuite.class); + } + + public static Testsuites deserializeTestsuites(String XmlPath) throws IOException { + File fi = new File(XmlPath); + return mapper.readValue(fi, Testsuites.class); + } + + public static void serialize(String XmlPath, Object obj) throws IOException { + File fi = new File(XmlPath); + mapper.writeValue(fi, obj); + } +} diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/gradle/GradleHarness.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/gradle/GradleHarness.java new file mode 100644 index 00000000..ef1f16cb --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/gradle/GradleHarness.java @@ -0,0 +1,52 @@ +package com.github.gilesi.illicocoverage.testing.gradle; + +import com.github.gilesi.illicocoverage.FileUtils; +import com.github.gilesi.illicocoverage.Main; +import com.github.gilesi.illicocoverage.testing.ReportItem; +import com.github.gilesi.illicocoverage.testing.XmlParser; +import com.github.gilesi.illicocoverage.testing.models.Testcase; +import com.github.gilesi.illicocoverage.testing.models.Testsuite; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; + +public class GradleHarness { + private static void buildReportXml(String projectName, String projectCommit, String targetLocation, + ArrayList reportItems) throws IOException { + String sureFireReportsLocation = "%s%stest-results%stest".formatted(targetLocation, File.separator, + File.separator); + ArrayList reportXmls = FileUtils.enumerateFiles(sureFireReportsLocation, ".*\\.xml", false); + + for (String xml : reportXmls) { + Testsuite testsuite = XmlParser.deserializeTestsuite(xml); + Testcase[] list = testsuite.getTestcase(); + if (list == null) { + continue; + } + for (Testcase testcase : list) { + reportItems.add(new ReportItem(projectName, projectCommit, testcase)); + } + } + } + + public static ArrayList getProjectTestReports(String projectPathFriendlyName, String commit, + String buildGradleFileLocation) { + ArrayList reportItems = new ArrayList<>(); + + Path buildGradleFilePath = Path.of(buildGradleFileLocation); + String targetLocation = "%s%sbuild".formatted(buildGradleFilePath.getParent().toAbsolutePath(), File.separator); + + if (Files.isDirectory(Path.of(targetLocation))) { + try { + buildReportXml(projectPathFriendlyName, commit, targetLocation, reportItems); + } catch (Exception e) { + Main.logger.info(e); + } + } + + return reportItems; + } +} diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/models/Error.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/models/Error.java new file mode 100644 index 00000000..acb8674f --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/models/Error.java @@ -0,0 +1,17 @@ +package com.github.gilesi.illicocoverage.testing.models; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlCData; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText; + +public class Error { + @JacksonXmlProperty(isAttribute = true, localName = "message") + public String Message; + + @JacksonXmlProperty(isAttribute = true, localName = "type") + public String Type; + + @JacksonXmlText + @JacksonXmlCData + private String Data; +} \ No newline at end of file diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/models/Properties.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/models/Properties.java new file mode 100644 index 00000000..e4aba116 --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/models/Properties.java @@ -0,0 +1,13 @@ +package com.github.gilesi.illicocoverage.testing.models; + +public class Properties { + private Property[] property; + + public Property[] getProperty() { + return property; + } + + public void setProperty(Property[] value) { + this.property = value; + } +} diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/models/Property.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/models/Property.java new file mode 100644 index 00000000..ea9e1d4d --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/models/Property.java @@ -0,0 +1,22 @@ +package com.github.gilesi.illicocoverage.testing.models; + +public class Property { + private String name; + private String value; + + public String getName() { + return name; + } + + public void setName(String value) { + this.name = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} \ No newline at end of file diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/models/Testcase.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/models/Testcase.java new file mode 100644 index 00000000..f2128522 --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/models/Testcase.java @@ -0,0 +1,23 @@ +package com.github.gilesi.illicocoverage.testing.models; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; + +public class Testcase { + @JacksonXmlElementWrapper(localName = "error") + public com.github.gilesi.illicocoverage.testing.models.Error Error; + + @JacksonXmlElementWrapper(localName = "failure") + public Error Failure; + + @JacksonXmlElementWrapper(localName = "name") + public String Name; + + @JacksonXmlElementWrapper(localName = "classname") + public String ClassName; + + @JacksonXmlElementWrapper(localName = "time") + public String Time; + + @JacksonXmlElementWrapper(localName = "system-out") + public String SystemOut; +} diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/models/Testsuite.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/models/Testsuite.java new file mode 100644 index 00000000..d0c827de --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/models/Testsuite.java @@ -0,0 +1,103 @@ +package com.github.gilesi.illicocoverage.testing.models; + +public class Testsuite { + private Properties properties; + private Testcase[] testcase; + private String xmlnsXsi; + private String xsiNoNamespaceSchemaLocation; + private String version; + private String name; + private String time; + private String tests; + private String errors; + private String skipped; + private String failures; + + public Properties getProperties() { + return properties; + } + + public void setProperties(Properties value) { + this.properties = value; + } + + public Testcase[] getTestcase() { + return testcase; + } + + public void setTestcase(Testcase[] value) { + this.testcase = value; + } + + public String getXmlnsXsi() { + return xmlnsXsi; + } + + public void setXmlnsXsi(String value) { + this.xmlnsXsi = value; + } + + public String getXsiNoNamespaceSchemaLocation() { + return xsiNoNamespaceSchemaLocation; + } + + public void setXsiNoNamespaceSchemaLocation(String value) { + this.xsiNoNamespaceSchemaLocation = value; + } + + public String getVersion() { + return version; + } + + public void setVersion(String value) { + this.version = value; + } + + public String getName() { + return name; + } + + public void setName(String value) { + this.name = value; + } + + public String getTime() { + return time; + } + + public void setTime(String value) { + this.time = value; + } + + public String getTests() { + return tests; + } + + public void setTests(String value) { + this.tests = value; + } + + public String getErrors() { + return errors; + } + + public void setErrors(String value) { + this.errors = value; + } + + public String getSkipped() { + return skipped; + } + + public void setSkipped(String value) { + this.skipped = value; + } + + public String getFailures() { + return failures; + } + + public void setFailures(String value) { + this.failures = value; + } +} \ No newline at end of file diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/models/Testsuites.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/models/Testsuites.java new file mode 100644 index 00000000..5ea98a69 --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/models/Testsuites.java @@ -0,0 +1,13 @@ +package com.github.gilesi.illicocoverage.testing.models; + +public class Testsuites { + private Testsuite[] testsuites; + + public Testsuite[] getTestsuites() { + return testsuites; + } + + public void setTestsuites(Testsuite[] value) { + this.testsuites = value; + } +} diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/surefire/SurefireHarness.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/surefire/SurefireHarness.java new file mode 100644 index 00000000..75572f86 --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/testing/surefire/SurefireHarness.java @@ -0,0 +1,49 @@ +package com.github.gilesi.illicocoverage.testing.surefire; + +import com.github.gilesi.illicocoverage.FileUtils; +import com.github.gilesi.illicocoverage.Main; +import com.github.gilesi.illicocoverage.testing.ReportItem; +import com.github.gilesi.illicocoverage.testing.models.Testcase; +import com.github.gilesi.illicocoverage.testing.models.Testsuite; +import com.github.gilesi.illicocoverage.testing.XmlParser; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; + +public class SurefireHarness { + private static void buildReportXml(String projectName, String projectCommit, String targetLocation, ArrayList reportItems) throws IOException { + String sureFireReportsLocation = "%s%ssurefire-reports".formatted(targetLocation, File.separator); + ArrayList reportXmls = FileUtils.enumerateFiles(sureFireReportsLocation, ".*\\.xml", false); + + for (String xml : reportXmls) { + Testsuite testsuite = XmlParser.deserializeTestsuite(xml); + Testcase[] list = testsuite.getTestcase(); + if (list == null) { + continue; + } + for (Testcase testcase : list) { + reportItems.add(new ReportItem(projectName, projectCommit, testcase)); + } + } + } + + public static ArrayList getProjectTestReports(String projectPathFriendlyName, String commit, String pomFileLocation) { + ArrayList reportItems = new ArrayList<>(); + + Path pomFilePath = Path.of(pomFileLocation); + String targetLocation = "%s%starget".formatted(pomFilePath.getParent().toAbsolutePath(), File.separator); + + if (Files.isDirectory(Path.of(targetLocation))) { + try { + buildReportXml(projectPathFriendlyName, commit, targetLocation, reportItems); + } catch (Exception e) { + Main.logger.info(e); + } + } + + return reportItems; + } +} diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/tools/ConfGenCoverage.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/tools/ConfGenCoverage.java new file mode 100644 index 00000000..3d5172ef --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/tools/ConfGenCoverage.java @@ -0,0 +1,23 @@ +package com.github.gilesi.illicocoverage.tools; + +import com.github.gilesi.illicocoverage.Constants; +import com.github.gilesi.illicocoverage.Main; +import com.github.gilesi.illicocoverage.ProcessUtils; + +import java.io.IOException; +import java.nio.file.Path; + +public class ConfGenCoverage { + public static void runConfGenCoverage(String GilesiRepositoryLocation, String libraryConfig, String outputTestProject, String libraryLocation) throws IOException, InterruptedException { + ProcessUtils.runExternalCommand(new String[]{ + Path.of(Constants.JAVA_21_BINARY_LOCATION, "bin", "java").toString(), + "-Xmx8g", + "-Xms8g", + "-jar", + Path.of(GilesiRepositoryLocation).resolve(Constants.CONF_GEN_COVERAGE_LOCATION).toString(), + libraryConfig, + outputTestProject, + libraryLocation + }, null, Main.loggerConfGenCoverage); + } +} diff --git a/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/tools/TestGen.java b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/tools/TestGen.java new file mode 100644 index 00000000..4a544ecd --- /dev/null +++ b/IllicoCoverage/src/main/java/com/github/gilesi/illicocoverage/tools/TestGen.java @@ -0,0 +1,44 @@ +package com.github.gilesi.illicocoverage.tools; + +import com.github.gilesi.illicocoverage.Constants; +import com.github.gilesi.illicocoverage.Main; +import com.github.gilesi.illicocoverage.ProcessUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.nio.file.Path; + +public class TestGen { + public static final Logger logger = LogManager.getLogger(); + + public static void runTestGen(String GilesiRepositoryLocation, String traceFile, String outputCodeDirectory, String libraryConfig) throws IOException, InterruptedException { + ProcessUtils.runExternalCommand(new String[]{ + Path.of(Constants.JAVA_21_BINARY_LOCATION, "bin", "java").toString(), + "-Xmx8g", + "-Xms8g", + "-jar", + Path.of(GilesiRepositoryLocation).resolve(Constants.TEST_GEN_LOCATION).toString(), + traceFile, + outputCodeDirectory, + libraryConfig + }, null, Main.loggerTraceGen); + } + + public static void runTraceDiff(String GilesiRepositoryLocation, String traceFile, String outputCodeDirectory, String libraryConfig) throws IOException, InterruptedException { + ProcessUtils.runExternalCommand(new String[]{ + Path.of(Constants.JAVA_21_BINARY_LOCATION, "bin", "java").toString(), + "-Xmx32768m", + "-Xms16384m", + "-Xss1024m", + "-XX:ParallelGCThreads=8", + "-XX:InitiatingHeapOccupancyPercent=70", + "-XX:+UnlockDiagnosticVMOptions", + "-jar", + Path.of(GilesiRepositoryLocation).resolve(Constants.TRACE_DIFF_LOCATION).toString(), + traceFile, + outputCodeDirectory, + libraryConfig + }, null, Main.loggerTraceDiff); + } +} \ No newline at end of file diff --git a/IllicoCoverage/src/main/resources/log4j2.xml b/IllicoCoverage/src/main/resources/log4j2.xml new file mode 100644 index 00000000..6ef0deaf --- /dev/null +++ b/IllicoCoverage/src/main/resources/log4j2.xml @@ -0,0 +1,82 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/IllicoCoverageRun.sh b/IllicoCoverageRun.sh new file mode 100755 index 00000000..89f5a5ff --- /dev/null +++ b/IllicoCoverageRun.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# illicocoverage requires the use of other subprojects that must be precompiled before running, and pointed to it via the gilesi repo arg. +# first compile everything +sh ./compile.sh + +export MAVEN_HOME="/snap/intellij-idea-ultimate/current/plugins/maven/lib/maven3" + +# then run illicocoverage for sample usage +# this commands starts the experience on the compsuite dataset copied at "/home/gus/Datasets/compsuite3", and limits it to just the project id "i-3" +# note that the compsuite dataset folder layout is our own format, and omitting "i-3" will run the entire experience on every project. +java -Xms8g -Xmx8g -jar ./IllicoCoverage/build/libs/com.github.gilesi.illicocoverage.jar "/home/gus/Datasets/compsuite3/i-7/old" "org.slf4j:slf4j-api:1.7.9" "./illicocoverage-sample-results" "$PWD" "test -fn -Drat.ignoreErrors=true -DtrimStackTrace=false -DfailIfNoTests=false -Dtest=Slf4jLogFilterTest#test_slf4j" + +# list of configurable hardocded options scattered throughout the entire project: +# illicocoverage/src/main/java/com/github/gilesi/illicocoverage/Constants.java +# TestGenerator/src/main/java/com/github/gilesi/testgenerator/StaticConfiguration.java diff --git a/IllicoRun.sh b/IllicoRun.sh new file mode 100755 index 00000000..3fb75a7d --- /dev/null +++ b/IllicoRun.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# illico requires the use of other subprojects that must be precompiled before running, and pointed to it via the gilesi repo arg. +# first compile everything +sh ./compile.sh + +export MAVEN_HOME="/snap/intellij-idea-ultimate/current/plugins/maven/lib/maven3" + +# then run illico for sample usage +# this commands starts the experience on the compsuite dataset copied at "/home/gus/Datasets/compsuite3", and limits it to just the project id "i-3" +# note that the compsuite dataset folder layout is our own format, and omitting "i-3" will run the entire experience on every project. +java -Xms8g -Xmx8g -jar ./Illico/build/libs/com.github.gilesi.illico.jar "/home/gus/Datasets/compsuite3/i-7/old" "org.slf4j:slf4j-api:1.7.9" "./illico-sample-results" "$PWD" "test -fn -Drat.ignoreErrors=true -DtrimStackTrace=false -DfailIfNoTests=false -Dtest=Slf4jLogFilterTest#test_slf4j" + +# list of configurable hardocded options scattered throughout the entire project: +# illico/src/main/java/com/github/gilesi/illico/Constants.java +# TestGenerator/src/main/java/com/github/gilesi/testgenerator/StaticConfiguration.java diff --git a/Instrumentation/build.gradle b/Instrumentation/build.gradle index 86a9f00d..77d6c6f6 100644 --- a/Instrumentation/build.gradle +++ b/Instrumentation/build.gradle @@ -1,6 +1,6 @@ plugins { id 'application' - id 'com.github.johnrengelman.shadow' version '7.0.0' + id 'com.github.johnrengelman.shadow' version '8.1.1' } java { @@ -8,7 +8,7 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } -group = 'com.github.maracas.gilesi.instrumentation' +group = 'com.github.gilesi.instrumentation' version = '1.0-SNAPSHOT' repositories { @@ -16,41 +16,54 @@ repositories { } dependencies { - implementation 'net.bytebuddy:byte-buddy:1.14.2' - implementation 'net.bytebuddy:byte-buddy-agent:1.14.2' + implementation 'net.bytebuddy:byte-buddy:1.15.10' + implementation 'net.bytebuddy:byte-buddy-agent:1.15.8' + implementation 'com.thoughtworks.xstream:xstream:1.4.20' implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.2' - testImplementation platform('org.junit:junit-bom:5.9.2') - testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' } jar { manifest { - attributes 'Premain-Class': 'com.github.maracas.gilesi.instrumentation.Agent', - 'Agent-Class': 'com.github.maracas.gilesi.instrumentation.Agentn', - 'Launcher-Agent-Class': 'com.github.maracas.gilesi.instrumentation.Agent', + attributes 'Premain-Class': 'com.github.gilesi.instrumentation.Agent', + 'Agent-Class': 'com.github.gilesi.instrumentation.Agent', + 'Launcher-Agent-Class': 'com.github.gilesi.instrumentation.Agent', 'Can-Retransform-Classes': 'true', 'Can-Redefine-Classes': 'true', - 'Main-Class': 'com.github.maracas.gilesi.instrumentation.Main', + 'Main-Class': 'com.github.gilesi.instrumentation.Agent', 'Multi-Release': 'true' } } application { - mainClass = 'com.github.maracas.gilesi.instrumentation.Main' + mainClass = 'com.github.gilesi.instrumentation.Agent' } description = 'Agent distribution.' shadowJar { - archiveBaseName.set('com.github.maracas.gilesi.instrumentation') + archiveBaseName.set('com.github.gilesi.instrumentation') archiveClassifier.set('') archiveVersion.set('') mergeServiceFiles() + + // Filtering shadow jar contents by file pattern. + exclude('LICENSE.txt') + exclude('META-INF/LICENSE') + exclude('META-INF/LICENSE.txt') + exclude('META-INF/NOTICE') + exclude('META-INF/NOTICE.txt') + + // Relocating dependencies. + relocate('com.fasterxml', 'gilesi.com.fasterxml') + relocate('com.thoughtworks', 'gilesi.com.thoughtworks') + relocate('net', 'gilesi.net') + relocate('io', 'gilesi.io') + //relocate('org', 'gilesi.org') // causes issues with xtream if done } distributions { shadow { - distributionBaseName = 'com.github.maracas.gilesi.instrumentation' + distributionBaseName = 'com.github.gilesi.instrumentation' } } @@ -62,8 +75,4 @@ idea { downloadJavadoc = true downloadSources = true } -} - -test { - useJUnitPlatform() } \ No newline at end of file diff --git a/Instrumentation/src/main/java/com/github/gilesi/confgen/models/Class.java b/Instrumentation/src/main/java/com/github/gilesi/confgen/models/Class.java new file mode 100644 index 00000000..d8e63389 --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/confgen/models/Class.java @@ -0,0 +1,7 @@ +package com.github.gilesi.confgen.models; + +public class Class { + public String ClassName; + public Method[] ClassMethods; + public String[] ClassDescriptors; +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/confgen/models/InstrumentationParameters.java b/Instrumentation/src/main/java/com/github/gilesi/confgen/models/InstrumentationParameters.java new file mode 100644 index 00000000..2fa9ceac --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/confgen/models/InstrumentationParameters.java @@ -0,0 +1,7 @@ +package com.github.gilesi.confgen.models; + +public class InstrumentationParameters { + public Class[] InstrumentedAPIClasses; + public String[] LibraryTypes; + public String traceOutputLocation; +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/confgen/models/Method.java b/Instrumentation/src/main/java/com/github/gilesi/confgen/models/Method.java new file mode 100644 index 00000000..8ef6c1e1 --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/confgen/models/Method.java @@ -0,0 +1,6 @@ +package com.github.gilesi.confgen.models; + +public class Method { + public String MethodName; + public String[] MethodDescriptors; +} \ No newline at end of file diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/Agent.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/Agent.java new file mode 100644 index 00000000..a659427c --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/Agent.java @@ -0,0 +1,155 @@ +package com.github.gilesi.instrumentation; + +import com.github.gilesi.confgen.models.Class; +import com.github.gilesi.confgen.models.InstrumentationParameters; +import com.github.gilesi.instrumentation.visitors.MethodInstrumentor; +import net.bytebuddy.agent.ByteBuddyAgent; + +import java.io.File; +import java.io.IOException; +import java.lang.instrument.Instrumentation; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/* + Our main agent class + */ +public class Agent { + public static Path generatedTracesFolderPath; + public static Path generatedLogsFolderPath; + public static Path generatedMarkersFolderPath; + + public static Class[] InstrumentedAPIClasses; + public static String[] LibraryTypes; + + /* + Our primary method to install the agent required to trace methods during execution + We take in as parameter an XML file with the list of methods to instrument and their class + */ + private static void installAgent(String arg, Instrumentation inst) { + if (!RunMilestoneService.shouldRun()) { + return; + } + + // Get the parameters + File configurationFile = new File(arg); + if (!configurationFile.exists()) { + System.err.printf("ERROR: the passed in configuration file (%s) does not exist or could not be found!%n", arg); + return; + } + + InstrumentationParameters instrumentationParameters; + + try { + instrumentationParameters = ConfigurationReader.parseInstrumentationparameters(configurationFile); + } catch (IOException ioException) { + System.err.printf("ERROR: Could not parse instrumentation configuration file (%s)!%n", arg); + System.err.println(ioException.getMessage()); + ioException.printStackTrace(); + return; + } + + LibraryTypes = instrumentationParameters.LibraryTypes; + + String generatedFolderLocation = instrumentationParameters.traceOutputLocation; + Path generatedFolderPath = Paths.get(generatedFolderLocation); + generatedTracesFolderPath = generatedFolderPath.resolve("traces"); + generatedLogsFolderPath = generatedFolderPath.resolve("logs"); + generatedMarkersFolderPath = generatedFolderPath.resolve("markers"); + + if (!Files.isDirectory(generatedFolderPath)) { + try { + Files.createDirectory(generatedFolderPath); + } catch (IOException ioException) { + System.err.printf("ERROR: Could not create output directory (%s)!%n", arg); + System.err.println(ioException.getMessage()); + ioException.printStackTrace(); + return; + } + } + + if (!Files.isDirectory(generatedTracesFolderPath)) { + try { + Files.createDirectory(generatedTracesFolderPath); + } catch (IOException ioException) { + System.err.printf("ERROR: Could not create trace output directory (%s)!%n", arg); + System.err.println(ioException.getMessage()); + ioException.printStackTrace(); + return; + } + } + + if (!Files.isDirectory(generatedLogsFolderPath)) { + try { + Files.createDirectory(generatedLogsFolderPath); + } catch (IOException ioException) { + System.err.printf("ERROR: Could not create logs output directory (%s)!%n", arg); + System.err.println(ioException.getMessage()); + ioException.printStackTrace(); + return; + } + } + + if (!Files.isDirectory(generatedMarkersFolderPath)) { + try { + Files.createDirectory(generatedMarkersFolderPath); + } catch (IOException ioException) { + System.err.printf("ERROR: Could not create markers output directory (%s)!%n", arg); + System.err.println(ioException.getMessage()); + ioException.printStackTrace(); + return; + } + } + + RunMilestoneService.writeMarker(); + + // Add a shutdown hook to save all logs at exit + Runtime.getRuntime().addShutdownHook(new Thread(Logger::SaveLogResults)); + + setupInstrumentation(inst, instrumentationParameters.InstrumentedAPIClasses); + } + + private static void setupInstrumentation(Instrumentation inst, Class[] InstrumentedAPIClasses) { + // First, install the ByteBuddy Agent required to redefine classes during execution + // Sometimes this can fail, so try to catch it + try { + ByteBuddyAgent.install(); + } catch (Throwable e) { + System.err.println("ERROR: Could not install byte buddy agent!"); + System.err.println(e.getMessage()); + e.printStackTrace(); + return; + } + + Agent.InstrumentedAPIClasses = InstrumentedAPIClasses; + + // Instrument every class + for (Class instrumentationParameter : InstrumentedAPIClasses) { + MethodInstrumentor.instrumentClassForTracingAgent(inst, instrumentationParameter); + } + } + + /* + premain is invoked when an agent is started before the application. + Agents invoked using premain are specified with the -javaagent switch. + (https://stackoverflow.com/a/19789168) + */ + public static void premain(String arg, Instrumentation inst) { + installAgent(arg, inst); + } + + /* + agentmain is invoked when an agent is started after the application is already running. + Agents started with agentmain can be attached programmatically using the Sun tools API (for Sun/Oracle JVMs only + -- the method for introducing dynamic agents is implementation-dependent). + (https://stackoverflow.com/a/19789168) + */ + public static void agentmain(String arg, Instrumentation inst) { + installAgent(arg, inst); + } + + public static void main(String[] args) { + System.err.println("This program cannot be executed standalone."); + } +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/ConfigurationReader.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/ConfigurationReader.java new file mode 100644 index 00000000..27f4c5ae --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/ConfigurationReader.java @@ -0,0 +1,12 @@ +package com.github.gilesi.instrumentation; + +import com.github.gilesi.confgen.models.InstrumentationParameters; + +import java.io.File; +import java.io.IOException; + +public class ConfigurationReader { + public static InstrumentationParameters parseInstrumentationparameters(File file) throws IOException { + return XmlUtils.objectMapper.readValue(file, InstrumentationParameters.class); + } +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/Logger.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/Logger.java new file mode 100644 index 00000000..d442cd61 --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/Logger.java @@ -0,0 +1,58 @@ +package com.github.gilesi.instrumentation; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class Logger { + private static final Map> LOGGER = new HashMap<>(); + private static int LOGGERINSTANCE = 0; + + public static void SaveLogResults() { + int mainPadding = 1; + String logFileName = String.format("%s%sgilesi.instrumentation_%d.log", Agent.generatedLogsFolderPath, File.separatorChar, mainPadding); + + while (Files.exists(Paths.get(logFileName))) { + logFileName = String.format("%s%sgilesi.instrumentation_%d.log", Agent.generatedLogsFolderPath, File.separatorChar, ++mainPadding); + } + + ArrayList logLines = new ArrayList<>(); + for (int loggerInstance = 0; loggerInstance < LOGGERINSTANCE; loggerInstance++) { + ArrayList logs = LOGGER.get(loggerInstance); + if (logs != null) { + for (String log : logs) { + logLines.add(String.format("%d: %s", loggerInstance, log)); + } + } + } + + if (logLines.isEmpty()) { + return; + } + + try { + Files.write(new File(logFileName).toPath(), logLines); + } catch (Throwable e) { + System.err.println("ERROR while saving logs"); + e.printStackTrace(); + } + } + + public static int GetInstance() { + return LOGGERINSTANCE++; + } + + public static void Log(int loggerInstance, String log) { + if (LOGGER.containsKey(loggerInstance)) { + ArrayList logs = LOGGER.get(loggerInstance); + logs.add(log); + } else { + ArrayList logs = new ArrayList<>(); + logs.add(log); + LOGGER.put(loggerInstance, logs); + } + } +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/RunMilestoneService.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/RunMilestoneService.java new file mode 100644 index 00000000..4888f220 --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/RunMilestoneService.java @@ -0,0 +1,46 @@ +package com.github.gilesi.instrumentation; + +import java.io.File; +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; + +public class RunMilestoneService { + public static void writeMarker() { + RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean(); + List arguments = runtimeMxBean.getInputArguments(); + + int mainPadding = 1; + String tracesFileName = String.format("%s%sgilesi.instrumentation_loaded_%d.marker", Agent.generatedMarkersFolderPath, File.separatorChar, mainPadding); + + while (Files.exists(Paths.get(tracesFileName))) { + tracesFileName = String.format("%s%sgilesi.instrumentation_loaded_%d.marker", Agent.generatedMarkersFolderPath, File.separatorChar, ++mainPadding); + } + + try { + Files.write(new File(tracesFileName).toPath(), arguments); + } catch (Throwable e) { + System.err.println("ERROR while saving marker"); + e.printStackTrace(); + } + } + + public static boolean shouldRun() { + RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean(); + List arguments = runtimeMxBean.getInputArguments(); + + for (String argument : arguments) { + if (argument.startsWith("-Dmaven.home=")) { + return false; + } else if (argument.startsWith("-Dorg.gradle.appname=")) { + return false; + } else if (argument.startsWith("-Dnet.bytebuddy.agent.attacher.dump=")) { + return false; + } + } + + return true; + } +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/TraceCollector.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/TraceCollector.java new file mode 100644 index 00000000..735b78ef --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/TraceCollector.java @@ -0,0 +1,14 @@ +package com.github.gilesi.instrumentation; + +import com.github.gilesi.instrumentation.models.TestTraceResults; +import com.github.gilesi.instrumentation.models.Trace; + +public class TraceCollector { + public static void addMethodTrace(String currentTestMethod, Trace trace) { + TestTraceResults testTraceResults = new TestTraceResults(); + testTraceResults.Test = currentTestMethod; + testTraceResults.Trace = trace; + + XmlUtils.saveToUniquePath(Agent.generatedTracesFolderPath.toString(), "MethodTrace", "json", testTraceResults); + } +} \ No newline at end of file diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/TraceFactory.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/TraceFactory.java new file mode 100644 index 00000000..f3d8a7b3 --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/TraceFactory.java @@ -0,0 +1,176 @@ +package com.github.gilesi.instrumentation; + +import com.github.gilesi.instrumentation.models.InstanceReference; +import com.github.gilesi.instrumentation.models.MethodReference; +import com.github.gilesi.instrumentation.models.PartialTrace; +import com.github.gilesi.instrumentation.models.Trace; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class TraceFactory { + public static final long ProcessEntryTimeStamp = System.nanoTime(); + public static int NullCounter = 0; + + /** + * Gets the origin name from an Exectuable object with the signature if possible. + *

+ * E.g. for a constructor: public com.github.gilesi.tests.legacy.Foo(java.lang.String) + * for a method: public static java.lang.String com.github.gilesi.tests.legacy.Foo.HelloEveryone(java.lang.String[]) + * + * @param origin The executable to get an origin name string from + * @return the origin name string matching the passed in executable + */ + private static String getOriginName(Executable origin) { + String originName; + + if (origin instanceof Method) { + Method method = (Method) origin; + originName = method.toGenericString(); + } else if (origin instanceof Constructor) { + Constructor constructor = (Constructor) origin; + originName = constructor.toGenericString(); + } else { + int loggerInstance = Logger.GetInstance(); + Logger.Log(loggerInstance, "BUGBUGBUG: Unknown Originating Executable Type!"); + System.err.println(origin.getClass().getName()); + originName = origin.toString(); + } + return originName; + } + + private static List getArgumentsAsSerializableData(Object[] arguments) { + List argumentStrings = new ArrayList<>(); + + if (arguments != null) { + argumentStrings = Arrays.stream(arguments).map(t -> new InstanceReference(t)).collect(Collectors.toList()); + } + + return argumentStrings; + } + + /** + * Collects information about a method execution call at entry, + * for further collection later on exit + * This method generates an incomplete partial method trace with on entry information, + * and caches it temporarily for use on exit. + * Callers must call later addMethodTrace on exit to complete the trace and make it available. + *

+ * This method is public, so it can be accessed as part of the modified instrumented classes + * without security problems at the JVM level + * + * @param origin The originating trace Executable object + * @param arguments The originating trace argument list on entry + */ + public static PartialTrace getPartialMethodTrace(Executable origin, Object[] arguments, Object instance) { + InstanceReference serializableInstance = null; + if (instance != null) { + serializableInstance = new InstanceReference(instance); + } + List preCallArguments = getArgumentsAsSerializableData(arguments); + + return new PartialTrace( + preCallArguments, + //0, + serializableInstance, + new MethodReference(origin)); + } + + /** + * Collects information about a method execution call at exit, + * with further data completion with an earlier partial trace if available from entry. + * This method generates an incomplete partial method trace with on exit information, + * and completes it with entry information if available, to cache a complete trace. + * Callers must call earlier addNotYetCompletedMethodTrace to provide entry information to + * have complete trace data. + *

+ * Callers must call later getMethodTraces to retrieve the trace information. + *

+ * This method is public, so it can be accessed as part of the modified instrumented classes + * without security problems at the JVM level + * + * @param origin The originating trace Executable object + * @param arguments The originating trace argument list on entry + * @param returned The data returned by the originating trace + */ + public static Trace getMethodTrace(Executable origin, Object[] arguments, Object returned, Object instance, Throwable thrown, PartialTrace originatingTrace, String[] stackTrace, boolean isConstructor) { + InstanceReference serializableInstance = null; + if (instance != null) { + serializableInstance = new InstanceReference(instance); + } + String methodSignature = getOriginName(origin); + List preCallArguments = originatingTrace.getPreCallArguments(); + List postCallArguments = getArgumentsAsSerializableData(arguments); + + // Every argument of a method should have the same instance id, in the case of null params, they will be different, so fix that + for (int i = 0; i < preCallArguments.size(); i++) { + if (postCallArguments.get(i).getInstanceId() < 0) { // Null ids are intentionally negative + postCallArguments.get(i).setInstanceId(preCallArguments.get(i).getInstanceId()); + } + } + + InstanceReference returnedValue = null; // TODO: Detect if the method returns nothing or not here! + if (returned != null) { + returnedValue = new InstanceReference(returned); + } + + //long timeStampEntry = originatingTrace.getTimeStampEntry(); + + InstanceReference exceptionThrown = null; + if (thrown != null) { + exceptionThrown = new InstanceReference(thrown); + } + + String methodName = methodSignature.split("\\(")[0]; + methodName = methodName.split(" ")[methodName.split(" ").length - 1].replace("$", "."); + + String confName = ""; + String[] confDescriptors = new String[] {}; + + for (com.github.gilesi.confgen.models.Class el : Agent.InstrumentedAPIClasses) { + if (isConstructor) { + String targetMethod = el.ClassName.replace("$", "."); + if (methodName.equals(targetMethod)) { + // found! + confName = el.ClassName; + confDescriptors = el.ClassDescriptors; + break; + } + } else { + boolean isFound = false; + for (com.github.gilesi.confgen.models.Method el2 : el.ClassMethods) { + String targetMethod = String.format("%s.%s", el.ClassName, el2.MethodName).replace("$", "."); + if (methodName.equals(targetMethod)) { + // found! + isFound = true; + confName = String.format("%s.%s", el.ClassName, el2.MethodName); + confDescriptors = el2.MethodDescriptors; + break; + } + } + + if (isFound) { + break; + } + } + } + + return new Trace( + preCallArguments, + postCallArguments, + returnedValue, + //timeStampEntry, + //0, + exceptionThrown, + //stackTrace, + confName, + confDescriptors, + serializableInstance, + new MethodReference(origin)); + } +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/XmlUtils.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/XmlUtils.java new file mode 100644 index 00000000..8ec08528 --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/XmlUtils.java @@ -0,0 +1,60 @@ +package com.github.gilesi.instrumentation; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.DomDriver; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import com.github.gilesi.instrumentation.converters.*; + +public class XmlUtils { + private static DomDriver domDriver = new DomDriver(); + private static XStream xStream = new XStream(domDriver); + private static Object syncObj = new Object(); + public static final ObjectMapper objectMapper = new ObjectMapper() + .enable(SerializationFeature.INDENT_OUTPUT) + //.enable(SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL) + .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) + .disable(SerializationFeature.FAIL_ON_SELF_REFERENCES) + .disable(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS); + + static { + xStream.registerConverter(new ClassLoaderConverter()); + xStream.registerConverter(new FileCleanableConverter()); + xStream.registerConverter(new InflaterConverter()); + xStream.registerConverter(new CleanerImplConverter()); + xStream.registerConverter(new ThreadConverter()); + xStream.registerConverter(new ThreadGroupConverter()); + /*xStream.registerConverter(new LibraryObjectConverter());*/ + xStream.registerConverter(new NonJavaObjectConverter()); + } + + public static String getToXml(Object obj) { + return xStream.toXML(obj); + } + + public static String getToJson(Object obj) throws JsonProcessingException { + return objectMapper.writeValueAsString(obj); + } + + public static void saveToUniquePath(String basePath, String baseName, String baseExtension, Object obj) { + synchronized (syncObj) { + int padding = 1; + String finalFileName = String.format("%s%s%s_%d.%s", basePath, File.separatorChar, baseName, padding, baseExtension); + + while (Files.exists(Paths.get(finalFileName))) { + finalFileName = String.format("%s%s%s_%d.%s", basePath, File.separatorChar, baseName, ++padding, baseExtension); + } + + try { + objectMapper.writeValue(new File(finalFileName), obj); + } catch (Throwable e) { + System.err.println("ERROR"); + e.printStackTrace(); + } + } + } +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/ClassLoaderConverter.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/ClassLoaderConverter.java new file mode 100644 index 00000000..3d5daa82 --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/ClassLoaderConverter.java @@ -0,0 +1,23 @@ +package com.github.gilesi.instrumentation.converters; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class ClassLoaderConverter implements Converter { + @Override + public void marshal(Object o, HierarchicalStreamWriter hierarchicalStreamWriter, MarshallingContext marshallingContext) { + } + + @Override + public Object unmarshal(HierarchicalStreamReader hierarchicalStreamReader, UnmarshallingContext unmarshallingContext) { + return null; + } + + @Override + public boolean canConvert(Class aClass) { + return aClass.getCanonicalName().contains("ClassLoader"); + } +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/CleanerImplConverter.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/CleanerImplConverter.java new file mode 100644 index 00000000..bca11a30 --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/CleanerImplConverter.java @@ -0,0 +1,30 @@ +package com.github.gilesi.instrumentation.converters; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +import java.lang.ref.Cleaner; + +public class CleanerImplConverter implements Converter { + @Override + public void marshal(Object o, HierarchicalStreamWriter hierarchicalStreamWriter, MarshallingContext marshallingContext) { + } + + @Override + public Object unmarshal(HierarchicalStreamReader hierarchicalStreamReader, UnmarshallingContext unmarshallingContext) { + return null; + } + + @Override + public boolean canConvert(Class aClass) { + return aClass.getCanonicalName().equals("java.lang.ref.Cleaner") || + aClass.getCanonicalName().equals("java.lang.ref.Cleaner.Cleanable") || + aClass.getCanonicalName().equals("jdk.internal.ref.CleanerImpl") || + aClass.getCanonicalName().equals("jdk.internal.ref.CleanerImpl.PhantomCleanableRef") || + Cleaner.class.isAssignableFrom(aClass) || + Cleaner.Cleanable.class.isAssignableFrom(aClass); + } +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/FileCleanableConverter.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/FileCleanableConverter.java new file mode 100644 index 00000000..7e38c321 --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/FileCleanableConverter.java @@ -0,0 +1,23 @@ +package com.github.gilesi.instrumentation.converters; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class FileCleanableConverter implements Converter { + @Override + public void marshal(Object o, HierarchicalStreamWriter hierarchicalStreamWriter, MarshallingContext marshallingContext) { + } + + @Override + public Object unmarshal(HierarchicalStreamReader hierarchicalStreamReader, UnmarshallingContext unmarshallingContext) { + return null; + } + + @Override + public boolean canConvert(Class aClass) { + return aClass.getCanonicalName().equals("java.io.FileCleanable"); + } +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/InflaterConverter.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/InflaterConverter.java new file mode 100644 index 00000000..4b6b0571 --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/InflaterConverter.java @@ -0,0 +1,23 @@ +package com.github.gilesi.instrumentation.converters; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class InflaterConverter implements Converter { + @Override + public void marshal(Object o, HierarchicalStreamWriter hierarchicalStreamWriter, MarshallingContext marshallingContext) { + } + + @Override + public Object unmarshal(HierarchicalStreamReader hierarchicalStreamReader, UnmarshallingContext unmarshallingContext) { + return null; + } + + @Override + public boolean canConvert(Class aClass) { + return aClass.getCanonicalName().equals("java.util.zip.Inflater"); + } +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/LibraryObjectConverter.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/LibraryObjectConverter.java new file mode 100644 index 00000000..1f4318dd --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/LibraryObjectConverter.java @@ -0,0 +1,26 @@ +package com.github.gilesi.instrumentation.converters; + +import com.github.gilesi.instrumentation.Agent; +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +import java.util.Arrays; + +public class LibraryObjectConverter implements Converter { + @Override + public void marshal(Object o, HierarchicalStreamWriter hierarchicalStreamWriter, MarshallingContext marshallingContext) { + } + + @Override + public Object unmarshal(HierarchicalStreamReader hierarchicalStreamReader, UnmarshallingContext unmarshallingContext) { + return null; + } + + @Override + public boolean canConvert(Class aClass) { + return Arrays.stream(Agent.LibraryTypes).anyMatch(t -> t.equals(aClass.getCanonicalName())); + } +} \ No newline at end of file diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/NonJavaObjectConverter.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/NonJavaObjectConverter.java new file mode 100644 index 00000000..011b74e6 --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/NonJavaObjectConverter.java @@ -0,0 +1,23 @@ +package com.github.gilesi.instrumentation.converters; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class NonJavaObjectConverter implements Converter { + @Override + public void marshal(Object o, HierarchicalStreamWriter hierarchicalStreamWriter, MarshallingContext marshallingContext) { + } + + @Override + public Object unmarshal(HierarchicalStreamReader hierarchicalStreamReader, UnmarshallingContext unmarshallingContext) { + return null; + } + + @Override + public boolean canConvert(Class aClass) { + return !aClass.getCanonicalName().startsWith("java."); + } +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/ThreadConverter.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/ThreadConverter.java new file mode 100644 index 00000000..85c6e070 --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/ThreadConverter.java @@ -0,0 +1,23 @@ +package com.github.gilesi.instrumentation.converters; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class ThreadConverter implements Converter { + @Override + public void marshal(Object o, HierarchicalStreamWriter hierarchicalStreamWriter, MarshallingContext marshallingContext) { + } + + @Override + public Object unmarshal(HierarchicalStreamReader hierarchicalStreamReader, UnmarshallingContext unmarshallingContext) { + return null; + } + + @Override + public boolean canConvert(Class aClass) { + return aClass.getCanonicalName().equals("java.lang.Thread"); + } +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/ThreadGroupConverter.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/ThreadGroupConverter.java new file mode 100644 index 00000000..4e3146da --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/converters/ThreadGroupConverter.java @@ -0,0 +1,23 @@ +package com.github.gilesi.instrumentation.converters; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; + +public class ThreadGroupConverter implements Converter { + @Override + public void marshal(Object o, HierarchicalStreamWriter hierarchicalStreamWriter, MarshallingContext marshallingContext) { + } + + @Override + public Object unmarshal(HierarchicalStreamReader hierarchicalStreamReader, UnmarshallingContext unmarshallingContext) { + return null; + } + + @Override + public boolean canConvert(Class aClass) { + return aClass.getCanonicalName().equals("java.lang.ThreadGroup"); + } +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/ClassReference.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/ClassReference.java new file mode 100644 index 00000000..f7fd092e --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/ClassReference.java @@ -0,0 +1,75 @@ +package com.github.gilesi.instrumentation.models; + +import java.util.ArrayList; +import java.util.Arrays; + +public class ClassReference { + private String fullyQualifiedTypeName = null; + private ClassReference[] interfaces = null; + private ClassReference superClass = null; + private GenericReference[] genericParameters = null; + + public ClassReference(Class clazz) { + fullyQualifiedTypeName = clazz.getTypeName(); + + interfaces = getInterfaces(clazz); + superClass = getSuperclass(clazz); + + genericParameters = Arrays.asList(clazz.getTypeParameters()).stream().map(t -> new GenericReference(t)).toArray(GenericReference[]::new); + } + + public ClassReference() { + + } + + private static ClassReference[] getInterfaces(Class clazz) { + ArrayList interfaces = new ArrayList<>(); + + for (Class interfaze : clazz.getInterfaces()) { + interfaces.add(new ClassReference(interfaze)); + } + + return interfaces.toArray(new ClassReference[0]); + } + + private static ClassReference getSuperclass(Class clazz) { + Class superClass = clazz.getSuperclass(); + if (superClass != null) { + return new ClassReference(superClass); + } + + return null; + } + + public String getFullyQualifiedTypeName() { + return fullyQualifiedTypeName; + } + + public ClassReference[] getInterfaces() { + return interfaces; + } + + public ClassReference getSuperClass() { + return superClass; + } + + public GenericReference[] getGenericParameters() { + return genericParameters; + } + + public void setFullyQualifiedTypeName(String fullyQualifiedTypeName) { + this.fullyQualifiedTypeName = fullyQualifiedTypeName; + } + + public void setInterfaces(ClassReference[] interfaces) { + this.interfaces = interfaces; + } + + public void setSuperClass(ClassReference superClass) { + this.superClass = superClass; + } + + public void setGenericParameters(GenericReference[] genericParameters) { + this.genericParameters = genericParameters; + } +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/GenericReference.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/GenericReference.java new file mode 100644 index 00000000..fc061465 --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/GenericReference.java @@ -0,0 +1,34 @@ +package com.github.gilesi.instrumentation.models; + +import java.lang.reflect.TypeVariable; +import java.util.Arrays; + +public class GenericReference { + private String name = null; + private ClassReference[] classBounds = null; + + public GenericReference(TypeVariable typeVariable) { + name = typeVariable.getName(); + classBounds = Arrays.asList(typeVariable.getBounds()).stream().filter(t -> t instanceof Class).map(t -> new ClassReference((Class)t)).toArray(ClassReference[]::new); + } + + public GenericReference() { + + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public ClassReference[] getClassBounds() { + return classBounds; + } + + public void getClassBounds(ClassReference[] classBounds) { + this.classBounds = classBounds; + } +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/InstanceReference.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/InstanceReference.java new file mode 100644 index 00000000..11c6049b --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/InstanceReference.java @@ -0,0 +1,92 @@ +package com.github.gilesi.instrumentation.models; + +//import java.util.ArrayList; + +import com.github.gilesi.instrumentation.Logger; +import com.github.gilesi.instrumentation.TraceFactory; +import com.github.gilesi.instrumentation.XmlUtils; + +public class InstanceReference { + private int instanceId = -1; + private String valueAsXmlString = null; + private String valueAsJsonString = null; + private boolean isNull = true; + private ClassReference classReference = null; + + //private static ArrayList instanceMap = new ArrayList<>(); + + public InstanceReference(Object object) { + if (object == null) { + instanceId = ++TraceFactory.NullCounter * -1; + valueAsXmlString = "null"; + valueAsJsonString = "null"; + isNull = true; + classReference = new ClassReference(Object.class); + return; + } + + /*int hashCode = System.identityHashCode(object); + if (instanceMap.contains(hashCode)) { + instanceId = instanceMap.indexOf(hashCode) + 1; + } else { + instanceMap.add(hashCode); + instanceId = instanceMap.size(); + }*/ + instanceId = System.identityHashCode(object); + + try { + valueAsXmlString = XmlUtils.getToXml(object); + } catch (Throwable e) { + int loggerInstance = Logger.GetInstance(); + Logger.Log(loggerInstance, String.format("ERROR: Cannot serialize specific argument: %s", e)); + } + + isNull = false; + + classReference = new ClassReference(object.getClass()); + } + + public InstanceReference() { + + } + + public int getInstanceId() { + return instanceId; + } + + public String getValueAsXmlString() { + return valueAsXmlString; + } + + public String getValueAsJsonString() { + return valueAsJsonString; + } + + public boolean getIsNull() { + return isNull; + } + + public ClassReference getClassReference() { + return classReference; + } + + public void setInstanceId(int instanceId) { + this.instanceId = instanceId; + } + + public void setValueAsXmlString(String valueAsXmlString) { + this.valueAsXmlString = valueAsXmlString; + } + + public void setValueAsJsonString(String valueAsJsonString) { + this.valueAsJsonString = valueAsJsonString; + } + + public void setIsNull(boolean isNull) { + this.isNull = isNull; + } + + public void setClassReference(ClassReference classReference) { + this.classReference = classReference; + } +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/MethodReference.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/MethodReference.java new file mode 100644 index 00000000..5069e434 --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/MethodReference.java @@ -0,0 +1,119 @@ +package com.github.gilesi.instrumentation.models; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.lang.reflect.Type; + +import com.github.gilesi.instrumentation.Logger; + +public class MethodReference { + private String methodSignature = null; + private ClassReference[] parameterTypes = null; + private ClassReference returnType = null; + private boolean isConstructor = false; + private GenericReference[] genericParameters = null; + private ClassReference genericReturn = null; + + public MethodReference(Executable executable) { + methodSignature = getOriginName(executable); + parameterTypes = Arrays.asList(executable.getParameterTypes()).stream().map(t -> new ClassReference(t)).toArray(ClassReference[]::new); + if (executable instanceof Method) { + returnType = new ClassReference(((Method)executable).getReturnType()); + isConstructor = false; + if (((Method)executable).getReturnType() != null) { + Type genericReturnType = ((Method)executable).getGenericReturnType(); + if (genericReturnType instanceof Class) { + genericReturn = new ClassReference((Class)genericReturnType); + } + } + } else if (executable instanceof Constructor) { + returnType = new ClassReference(((Constructor)executable).getDeclaringClass()); + isConstructor = true; + } else { + isConstructor = false; + } + + genericParameters = Arrays.asList(executable.getTypeParameters()).stream().map(t -> new GenericReference(t)).toArray(GenericReference[]::new); + } + + public MethodReference() { + + } + + /** + * Gets the origin name from an Exectuable object with the signature if possible. + *

+ * E.g. for a constructor: public com.github.gilesi.tests.legacy.Foo(java.lang.String) + * for a method: public static java.lang.String com.github.gilesi.tests.legacy.Foo.HelloEveryone(java.lang.String[]) + * + * @param origin The executable to get an origin name string from + * @return the origin name string matching the passed in executable + */ + private static String getOriginName(Executable origin) { + String originName; + + if (origin instanceof Method) { + Method method = (Method) origin; + originName = method.toGenericString(); + } else if (origin instanceof Constructor) { + Constructor constructor = (Constructor) origin; + originName = constructor.toGenericString(); + } else { + int loggerInstance = Logger.GetInstance(); + Logger.Log(loggerInstance, "BUGBUGBUG: Unknown Originating Executable Type!"); + System.err.println(origin.getClass().getName()); + originName = origin.toString(); + } + return originName; + } + + public String getMethodSignature() { + return methodSignature; + } + + public ClassReference[] getParameterTypes() { + return parameterTypes; + } + + public ClassReference getReturnType() { + return returnType; + } + + public boolean getIsConstructor() { + return isConstructor; + } + + public GenericReference[] getGenericParameters() { + return genericParameters; + } + + public ClassReference getGenericReturn() { + return genericReturn; + } + + public void setMethodSignature(String methodSignature) { + this.methodSignature = methodSignature; + } + + public void setParameterTypes(ClassReference[] parameterTypes) { + this.parameterTypes = parameterTypes; + } + + public void setReturnType(ClassReference returnType) { + this.returnType = returnType; + } + + public void setIsConstructor(boolean isConstructor) { + this.isConstructor = isConstructor; + } + + public void setGenericParameters(GenericReference[] genericParameters) { + this.genericParameters = genericParameters; + } + + public void setGenericReturn(ClassReference genericReturn) { + this.genericReturn = genericReturn; + } +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/PartialTrace.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/PartialTrace.java new file mode 100644 index 00000000..03dbe1e8 --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/PartialTrace.java @@ -0,0 +1,53 @@ +package com.github.gilesi.instrumentation.models; + +import java.util.List; + +public class PartialTrace { + private List preCallArguments; + //private long timeStampEntry; + private InstanceReference instanceReference; + private MethodReference methodReference; + + public PartialTrace( + List preCallArguments, + //long timeStampEntry, + InstanceReference instanceReference, + MethodReference methodReference) { + this.preCallArguments = preCallArguments; + //this.timeStampEntry = timeStampEntry; + this.instanceReference = instanceReference; + this.methodReference = methodReference; + } + + public List getPreCallArguments() { + return preCallArguments; + } + + public void setPreCallArguments(List preCallArguments) { + this.preCallArguments = preCallArguments; + } + + /*public long getTimeStampEntry() { + return timeStampEntry; + } + + public void setTimeStampEntry(long timeStampEntry) { + this.timeStampEntry = timeStampEntry; + }*/ + + public InstanceReference getInstanceReference() { + return instanceReference; + } + + public MethodReference getMethodReference() { + return methodReference; + } + + public void setInstanceReference(InstanceReference instanceReference) { + this.instanceReference = instanceReference; + } + + public void setMethodReference(MethodReference methodReference) { + this.methodReference = methodReference; + } +} \ No newline at end of file diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/TestTraceResults.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/TestTraceResults.java new file mode 100644 index 00000000..e277a415 --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/TestTraceResults.java @@ -0,0 +1,6 @@ +package com.github.gilesi.instrumentation.models; + +public class TestTraceResults { + public Trace Trace; + public String Test; +} diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/Trace.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/Trace.java new file mode 100644 index 00000000..50770aaa --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/models/Trace.java @@ -0,0 +1,92 @@ +package com.github.gilesi.instrumentation.models; + +import java.util.List; + +public class Trace extends PartialTrace { + private List postCallArguments; + private InstanceReference returnedValue; + //private long timeStampExit; + private InstanceReference exceptionThrown; + //private String[] stackTrace; + private String confName; + private String[] confDescriptors; + + public Trace( + List preCallArguments, + List postCallArguments, + InstanceReference returnedValue, + //long timeStampEntry, + //long timeStampExit, + InstanceReference exceptionThrown, + //String[] stackTrace, + String confName, + String[] confDescriptors, + InstanceReference instanceReference, + MethodReference methodReference) { + super(preCallArguments, /*timeStampEntry,*/ instanceReference, methodReference); + + this.postCallArguments = postCallArguments; + this.returnedValue = returnedValue; + //this.timeStampExit = timeStampExit; + this.exceptionThrown = exceptionThrown; + //this.stackTrace = stackTrace; + this.confName = confName; + this.confDescriptors = confDescriptors; + } + + public List getPostCallArguments() { + return postCallArguments; + } + + public void setPostCallArguments(List postCallArguments) { + this.postCallArguments = postCallArguments; + } + + public InstanceReference getReturnedValue() { + return returnedValue; + } + + public void setReturnedValue(InstanceReference returnedValue) { + this.returnedValue = returnedValue; + } + + /*public long getTimeStampExit() { + return timeStampExit; + } + + public void setTimeStampExit(long timeStampExit) { + this.timeStampExit = timeStampExit; + }*/ + + public InstanceReference getExceptionThrown() { + return exceptionThrown; + } + + public void setExceptionThrown(InstanceReference exceptionThrown) { + this.exceptionThrown = exceptionThrown; + } + + /*public String[] getStackTrace() { + return stackTrace; + } + + public void setStackTrace(String[] stackTrace) { + this.stackTrace = stackTrace; + }*/ + + public String getConfName() { + return confName; + } + + public void setConfName(String confName) { + this.confName = confName; + } + + public String[] getConfDescriptors() { + return confDescriptors; + } + + public void setConfDescriptors(String[] confDescriptors) { + this.confDescriptors = confDescriptors; + } +} \ No newline at end of file diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/visitors/CommonAdvisor.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/visitors/CommonAdvisor.java new file mode 100644 index 00000000..f2bdd066 --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/visitors/CommonAdvisor.java @@ -0,0 +1,185 @@ +package com.github.gilesi.instrumentation.visitors; + +import com.github.gilesi.instrumentation.Logger; +import com.github.gilesi.instrumentation.TraceCollector; +import com.github.gilesi.instrumentation.TraceFactory; +import com.github.gilesi.instrumentation.models.PartialTrace; +import com.github.gilesi.instrumentation.models.Trace; + +import java.lang.reflect.Executable; +import java.util.ArrayList; +import java.util.Arrays; + +public class CommonAdvisor { + private static final String JUNIT_REFLECTION_UTILS_INVOKE_METHOD_FQN = "org.junit.platform.commons.util.ReflectionUtils.invokeMethod"; + private static final String JUNIT_FRAMEWORK_METHOD_RUN_REFLECTIVE_CALL = "org.junit.runners.model.FrameworkMethod$1.runReflectiveCall"; + private static final String UNKNOWN_TEST_METHOD_NAME = "UnknownTestPackage.UnknownTestClass.Unknown"; + private static final ArrayList StackTraces = new ArrayList<>(); + + public static Object commonEnter( + Executable origin, + Object[] arguments, + Object instance) { + StackTraceElement[] stackTraceElements = new Throwable().getStackTrace(); + String[] stackTrace = Arrays.stream(stackTraceElements).skip(2).map(t -> String.format("%s.%s", t.getClassName(), t.getMethodName())).toArray(String[]::new); + + if (isAcceptedTrace(stackTrace)) { + PartialTrace partialTrace = TraceFactory.getPartialMethodTrace(origin, arguments, instance); + + // Need to fetch this asap to not be off on the measurements! + //long entryMarker = System.nanoTime() - TraceFactory.ProcessEntryTimeStamp; + //partialTrace.setTimeStampEntry(entryMarker); + + return partialTrace; + } else { + return null; + } + } + + public static void commonExit( + Executable origin, + Object[] arguments, + Object returned, + Object instance, + Throwable thrown, + PartialTrace entryTrace, + boolean isConstructor) { + // Need to fetch this asap to not be off on the measurements! + long exitMarker = System.nanoTime() - TraceFactory.ProcessEntryTimeStamp; + + if (entryTrace == null) { + return; + } + + StackTraceElement[] stackTraceElements = new Throwable().getStackTrace(); + String[] currentStackTrace = Arrays.stream(stackTraceElements).skip(2).map(t -> String.format("%s.%s", t.getClassName(), t.getMethodName())).toArray(String[]::new); + + synchronized (StackTraces) { + // Remove existing stacktrace + for (int StoredPreviousStackTraceIndex = 0; StoredPreviousStackTraceIndex < StackTraces.size(); StoredPreviousStackTraceIndex++) { + String[] StoredPreviousStackTrace = StackTraces.get(StoredPreviousStackTraceIndex); + + if (StoredPreviousStackTrace.length != currentStackTrace.length) { + continue; + } + + boolean match = true; + + for (int i = 0; i < StoredPreviousStackTrace.length; i++) { + String methodName = StoredPreviousStackTrace[i]; + if (!methodName.equals(currentStackTrace[i])) { + match = false; + break; + } + } + + if (match) { + StackTraces.remove(StoredPreviousStackTraceIndex); + break; + } + } + } + + String testName = getTestMethodName(currentStackTrace); + + if (testName != null && !testName.isEmpty()) { + int index; + for (index = 0; index < currentStackTrace.length; index++) { + if (currentStackTrace[index].equals(testName)) { + break; + } + } + currentStackTrace = Arrays.stream(currentStackTrace).limit(index + 1).toArray(String[]::new); + } + + Trace trace = TraceFactory.getMethodTrace(origin, arguments, returned, instance, thrown, entryTrace, currentStackTrace, isConstructor); + //trace.setTimeStampExit(exitMarker); + TraceCollector.addMethodTrace(testName, trace); + } + + private static String getTestMethodName(String[] stackTrace) { + for (int i = 3; i < stackTrace.length; i++) { + String methodName = stackTrace[i]; + if (methodName.equals(JUNIT_REFLECTION_UTILS_INVOKE_METHOD_FQN)) { + return stackTrace[i - 3]; + } + } + + for (int i = 5; i < stackTrace.length; i++) { + String methodName = stackTrace[i]; + if (methodName.equals(JUNIT_FRAMEWORK_METHOD_RUN_REFLECTIVE_CALL)) { + return stackTrace[i - 5]; + } + } + + return UNKNOWN_TEST_METHOD_NAME; + } + + private static String[] getCutOffStackTrace(String[] stackTrace) { + String testName = getTestMethodName(stackTrace); + ArrayList cutOffStackTrace = new ArrayList<>(); + for (String stackTraceMethodName : stackTrace) { + if (stackTraceMethodName.startsWith("org.junit.") || + stackTraceMethodName.startsWith("sun.") || + stackTraceMethodName.startsWith("jdk.") || + stackTraceMethodName.startsWith("java.")) { + continue; + } + + cutOffStackTrace.add(stackTraceMethodName); + + if (stackTraceMethodName.equals(testName)) { + break; + } + } + + return cutOffStackTrace.stream().toArray(String[]::new); + } + + private static boolean isAcceptedTrace(String[] stackTrace) { + synchronized (StackTraces) { + int loggerInstance = Logger.GetInstance(); + + stackTrace = getCutOffStackTrace(stackTrace); + + Logger.Log(loggerInstance, "++CommonAdvisor::isAcceptedTrace"); + + Logger.Log(loggerInstance, " Incoming StackTrace:"); + for (String methodName : stackTrace) { + Logger.Log(loggerInstance, String.format(" %s", methodName)); + } + + for (String[] previousStackTrace : StackTraces) { + previousStackTrace = getCutOffStackTrace(previousStackTrace); + + // An existing stacktrace can only be smaller than us if they overlap + if (previousStackTrace.length >= stackTrace.length) { + continue; + } + + boolean matchesExistingStackTrace = true; + for (int i = 0; i < previousStackTrace.length; i++) { + String oldMethod = previousStackTrace[previousStackTrace.length - 1 - i]; + String newMethod = stackTrace[stackTrace.length - 1 -i]; + + if (!oldMethod.equals(newMethod)) { + matchesExistingStackTrace = false; + break; + } + } + + if (matchesExistingStackTrace) { + Logger.Log(loggerInstance, " Rejected Trace"); + Logger.Log(loggerInstance, "--CommonAdvisor::isAcceptedTrace"); + + return false; + } + } + + Logger.Log(loggerInstance, " Accepted Trace"); + Logger.Log(loggerInstance, "--CommonAdvisor::isAcceptedTrace"); + StackTraces.add(stackTrace); + return true; + } + } +} \ No newline at end of file diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/visitors/ConstructorAdvisor.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/visitors/ConstructorAdvisor.java new file mode 100644 index 00000000..ad6a3ac0 --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/visitors/ConstructorAdvisor.java @@ -0,0 +1,27 @@ +package com.github.gilesi.instrumentation.visitors; + +import com.github.gilesi.instrumentation.models.PartialTrace; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.implementation.bytecode.assign.Assigner; + +import java.lang.reflect.Executable; + +public class ConstructorAdvisor { + @Advice.OnMethodEnter(inline = false) + public static Object enter( + @Advice.Origin Executable origin, + @Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] arguments, + @Advice.This(typing = Assigner.Typing.DYNAMIC, optional = true) Object instance) { + return CommonAdvisor.commonEnter(origin, arguments, instance); + } + + @Advice.OnMethodExit(inline = false) + public static void exit( + @Advice.Origin Executable origin, + @Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] arguments, + @Advice.Return(typing = Assigner.Typing.DYNAMIC) Object returned, + @Advice.This(typing = Assigner.Typing.DYNAMIC, optional = true) Object instance, + @Advice.Enter(typing = Assigner.Typing.DYNAMIC) PartialTrace entryTrace) { + CommonAdvisor.commonExit(origin, arguments, returned, instance, null, entryTrace, true); + } +} \ No newline at end of file diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/visitors/LoggingListener.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/visitors/LoggingListener.java new file mode 100644 index 00000000..13a5eaf2 --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/visitors/LoggingListener.java @@ -0,0 +1,51 @@ +package com.github.gilesi.instrumentation.visitors; + +import com.github.gilesi.instrumentation.Logger; +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.utility.JavaModule; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; + +public class LoggingListener implements AgentBuilder.Listener { + private int loggerInstance = Logger.GetInstance(); + + @Override + public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, + boolean loaded) { + } + + @Override + public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, + JavaModule module, boolean loaded, DynamicType dynamicType) { + + } + + @Override + public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, + JavaModule module, boolean loaded) { + } + + @Override + public void onError(String typeName, ClassLoader classLoader, JavaModule module, + boolean loaded, Throwable throwable) { + Logger.Log(loggerInstance, String.format("BYTEBUDDY INSTRUMENTATION ERROR:")); + Logger.Log(loggerInstance, String.format("Type Name: %s", typeName)); + Logger.Log(loggerInstance, String.format("Loaded: %s", loaded)); + Logger.Log(loggerInstance, String.format("Exception: %s", throwable)); + + StringWriter writer = new StringWriter(); + throwable.printStackTrace(new PrintWriter(writer)); + String stackTrace = writer.toString(); + + Logger.Log(loggerInstance, String.format("Stack Trace: %s", stackTrace)); + } + + @Override + public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, + boolean loaded) { + } +} \ No newline at end of file diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/visitors/MethodAdvisor.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/visitors/MethodAdvisor.java new file mode 100644 index 00000000..d3d0efdb --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/visitors/MethodAdvisor.java @@ -0,0 +1,28 @@ +package com.github.gilesi.instrumentation.visitors; + +import com.github.gilesi.instrumentation.models.PartialTrace; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.implementation.bytecode.assign.Assigner; + +import java.lang.reflect.Executable; + +public class MethodAdvisor { + @Advice.OnMethodEnter(inline = false) + public static Object enter( + @Advice.Origin Executable origin, + @Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] arguments, + @Advice.This(typing = Assigner.Typing.DYNAMIC, optional = true) Object instance) { + return CommonAdvisor.commonEnter(origin, arguments, instance); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, inline = false) + public static void exit( + @Advice.Origin Executable origin, + @Advice.AllArguments(typing = Assigner.Typing.DYNAMIC) Object[] arguments, + @Advice.Return(typing = Assigner.Typing.DYNAMIC) Object returned, + @Advice.This(typing = Assigner.Typing.DYNAMIC, optional = true) Object instance, + @Advice.Thrown Throwable thrown, + @Advice.Enter(typing = Assigner.Typing.DYNAMIC) PartialTrace entryTrace) { + CommonAdvisor.commonExit(origin, arguments, returned, instance, thrown, entryTrace, false); + } +} \ No newline at end of file diff --git a/Instrumentation/src/main/java/com/github/gilesi/instrumentation/visitors/MethodInstrumentor.java b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/visitors/MethodInstrumentor.java new file mode 100644 index 00000000..ca99b90a --- /dev/null +++ b/Instrumentation/src/main/java/com/github/gilesi/instrumentation/visitors/MethodInstrumentor.java @@ -0,0 +1,81 @@ +package com.github.gilesi.instrumentation.visitors; + +import com.github.gilesi.confgen.models.Class; +import com.github.gilesi.confgen.models.Method; +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; + +import java.lang.instrument.Instrumentation; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +public class MethodInstrumentor { + // commented out due to hooking issues + // yet to investigate + /*private static ElementMatcher.Junction getConstructorElementMatcherFromClassParameter(Class classParameter) { + ElementMatcher.Junction descriptorMatcher = null; + for (String descriptorParameter : classParameter.ClassDescriptors) { + if (descriptorMatcher == null) { + descriptorMatcher = hasDescriptor(descriptorParameter); + } else { + descriptorMatcher = descriptorMatcher.or(hasDescriptor(descriptorParameter)); + } + } + return descriptorMatcher; + }*/ + + private static ElementMatcher.Junction getMethodElementMatcherFromClassParameter(Class classParameter) { + ElementMatcher.Junction classMatcher = null; + for (Method methodParameter : classParameter.ClassMethods) { + ElementMatcher.Junction methodMatcher = named(methodParameter.MethodName); + // commented out due to hooking issues + // yet to investigate + /*ElementMatcher.Junction descriptorMatcher = null; + for (String descriptorParameter : methodParameter.MethodDescriptors) { + if (descriptorMatcher == null) { + descriptorMatcher = hasDescriptor(descriptorParameter); + } else { + descriptorMatcher = descriptorMatcher.or(hasDescriptor(descriptorParameter)); + } + } + + if (descriptorMatcher != null) { + methodMatcher = methodMatcher.and(descriptorMatcher); + }*/ + + if (classMatcher == null) { + classMatcher = methodMatcher; + } else { + classMatcher = classMatcher.or(methodMatcher); + } + } + return classMatcher; + } + + public static void instrumentClassForTracingAgent(Instrumentation inst, Class classParameter) { + ElementMatcher.Junction methodMatcher = getMethodElementMatcherFromClassParameter(classParameter); + //ElementMatcher.Junction constructorMatcher = getConstructorElementMatcherFromClassParameter(classParameter); + + new AgentBuilder.Default() + .with(new LoggingListener()) + .type(named(classParameter.ClassName)) + .transform((builder, + typeDescription, + classLoader, + javaModule, + protectionDomain) -> + builder + .visit( + Advice.to(MethodAdvisor.class).on(ElementMatchers.not(ElementMatchers.isTypeInitializer().or(ElementMatchers.isConstructor())).and(methodMatcher)) + ) + .visit( + Advice.to(ConstructorAdvisor.class).on(ElementMatchers.not(ElementMatchers.isTypeInitializer()).and(ElementMatchers.isConstructor())/*.and(constructorMatcher)*/) + ) + ) + .with(AgentBuilder.RedefinitionStrategy.REDEFINITION) + .with(AgentBuilder.TypeStrategy.Default.REDEFINE).installOn(inst); + } +} \ No newline at end of file diff --git a/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/Agent.java b/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/Agent.java deleted file mode 100644 index 11374572..00000000 --- a/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/Agent.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.github.maracas.gilesi.instrumentation; - -import com.fasterxml.jackson.databind.ObjectMapper; -import net.bytebuddy.agent.ByteBuddyAgent; - -import java.io.File; -import java.io.IOException; -import java.lang.instrument.Instrumentation; - -public class Agent { - private static void installAgent(String arg, Instrumentation inst) throws IOException { - ByteBuddyAgent.install(); - Runtime.getRuntime().addShutdownHook(new Thread(MethodTracer::SaveTraceResults)); - - ObjectMapper objectMapper = new ObjectMapper(); - InstrumentationParameters instrumentationParameters = objectMapper.readValue(new File(arg), InstrumentationParameters.class); - - // Note: constructors get "" as their name in bytebuddy methodmatcher code - // Note2: Not possible to specifically say what type with arg list? Maybe looking into it - // Note3: we may want to take in a json because that's gonna be complex otherwise to instrument - for (InstrumentationParameter instrumentationParameter : instrumentationParameters.ClassesToInstrument) { - MethodTracer.instrumentClassForTracingAgent(inst, instrumentationParameter); - } - } - - public static void premain(String arg, Instrumentation inst) throws IOException { - installAgent(arg, inst); - } - - public static void agentmain(String arg, Instrumentation inst) throws IOException { - installAgent(arg, inst); - } -} diff --git a/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/ConstructorVisitor.java b/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/ConstructorVisitor.java deleted file mode 100644 index 05f7688b..00000000 --- a/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/ConstructorVisitor.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.github.maracas.gilesi.instrumentation; - -import net.bytebuddy.asm.Advice; - -import java.lang.reflect.Executable; - -import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC; - -public class ConstructorVisitor { - @Advice.OnMethodEnter - public static Object enter( - @Advice.Origin Executable origin, - @Advice.AllArguments(readOnly = false, typing = DYNAMIC) Object[] arguments) { - MethodTracer.addNotYetCompletedMethodTrace(origin, arguments, null); - return null; - } - - @Advice.OnMethodExit - public static void exit( - @Advice.Origin Executable origin, - @Advice.AllArguments(readOnly = false, typing = DYNAMIC) Object[] arguments, - @Advice.Return(readOnly = false, typing = DYNAMIC) Object returned, - @Advice.This Object instance) { - MethodTracer.addMethodTrace(origin, arguments, returned, instance); - } -} diff --git a/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/InstrumentationParameter.java b/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/InstrumentationParameter.java deleted file mode 100644 index 48c9707b..00000000 --- a/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/InstrumentationParameter.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.github.maracas.gilesi.instrumentation; - -import java.util.List; - -public class InstrumentationParameter { - public String ClassName; - public List ClassMethodsOrConstructors; -} diff --git a/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/InstrumentationParameters.java b/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/InstrumentationParameters.java deleted file mode 100644 index 3709c04b..00000000 --- a/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/InstrumentationParameters.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.maracas.gilesi.instrumentation; - -import java.util.List; - -public class InstrumentationParameters { - public List ClassesToInstrument; -} diff --git a/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/MethodTrace.java b/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/MethodTrace.java deleted file mode 100644 index 18f20e68..00000000 --- a/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/MethodTrace.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.github.maracas.gilesi.instrumentation; - -import java.util.List; - -public class MethodTrace { - public String methodSignature; - public SerializableData instance; - public List preCallArguments; - public List postCallArguments; - public SerializableData returnedValue; - public long timeStampEntry; - public long timeStampExit; - - public MethodTrace(String methodSignature, SerializableData instance, List preCallArguments, - List postCallArguments, SerializableData returnedValue, long timeStampEntry, - long timeStampExit) - { - this.instance = instance; - this.methodSignature = methodSignature; - this.timeStampEntry = timeStampEntry; - this.postCallArguments = postCallArguments; - this.preCallArguments = preCallArguments; - this.returnedValue = returnedValue; - this.timeStampExit = timeStampExit; - } - - public MethodTrace() {} - - @Override - public String toString() { - return "MethodTrace{" + - "methodSignature='" + methodSignature + '\'' + - ", instance=" + instance + - ", preCallArguments=" + preCallArguments + - ", postCallArguments=" + postCallArguments + - ", returnedValue=" + returnedValue + - ", timeStampEntry=" + timeStampEntry + - ", timeStampExit=" + timeStampExit + - '}'; - } - - public long getTimeStampEntry() { - return timeStampEntry; - } - - public long getTimeStampExit() { - return timeStampExit; - } -} \ No newline at end of file diff --git a/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/MethodTracer.java b/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/MethodTracer.java deleted file mode 100644 index d1bcd5a0..00000000 --- a/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/MethodTracer.java +++ /dev/null @@ -1,222 +0,0 @@ -package com.github.maracas.gilesi.instrumentation; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import net.bytebuddy.agent.builder.AgentBuilder; -import net.bytebuddy.asm.Advice; -import net.bytebuddy.matcher.ElementMatchers; - -import java.io.File; -import java.lang.instrument.Instrumentation; -import java.lang.reflect.Constructor; -import java.lang.reflect.Executable; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import static net.bytebuddy.matcher.ElementMatchers.named; - -public class MethodTracer { - private static final ArrayList notYetCompletedMethodTraces = new ArrayList<>(); - private static final ArrayList methodTraces = new ArrayList<>(); - - public static void instrumentClassForTracingAgent(Instrumentation inst, InstrumentationParameter instrumentationParameter) { - String[] methodsArray = new String[instrumentationParameter.ClassMethodsOrConstructors.size()]; - instrumentationParameter.ClassMethodsOrConstructors.toArray(methodsArray); - - new AgentBuilder.Default() - .type(named(instrumentationParameter.ClassName)) - .transform((builder, typeDescription, classLoader, javaModule, protectionDomain) -> - builder - .visit(Advice - .to(StaticMethodVisitor.class) - .on(ElementMatchers - .not( - ElementMatchers.isTypeInitializer() - .or(ElementMatchers.isConstructor()) - ).and(ElementMatchers.isStatic()) - .and(ElementMatchers.namedOneOf(methodsArray)) - ) - ) - .visit(Advice - .to(ConstructorVisitor.class) - .on(ElementMatchers - .not( - ElementMatchers.isTypeInitializer() - ).and(ElementMatchers.isConstructor()) - .and(ElementMatchers.namedOneOf(methodsArray)) - ) - ) - .visit(Advice - .to(MethodVisitor.class) - .on(ElementMatchers - .not( - ElementMatchers.isTypeInitializer() - .or(ElementMatchers.isConstructor()) - .or(ElementMatchers.isStatic()) - ).and(ElementMatchers.namedOneOf(methodsArray)) - ) - ) - ) - .with(AgentBuilder.RedefinitionStrategy.REDEFINITION) - .with(AgentBuilder.TypeStrategy.Default.REDEFINE) - .installOn(inst); - } - - /** - * Gets the origin name from an Exectuable object with the signature if possible. - *

- * E.g. for a constructor: public com.github.maracas.gilesi.tests.legacy.Foo(java.lang.String) - * for a method: public static java.lang.String com.github.maracas.gilesi.tests.legacy.Foo.HelloEveryone(java.lang.String[]) - * - * @param origin The executable to get an origin name string from - * @return the origin name string matching the passed in executable - */ - private static String getOriginName(Executable origin) { - String originName; - - if (origin instanceof Method) { - Method method = (Method)origin; - originName = method.toGenericString(); - } else if (origin instanceof Constructor) { - Constructor constructor = (Constructor)origin; - originName = constructor.toGenericString(); - } else { - System.out.println("BUGBUGBUG: Unknown Originating Executable Type!"); - System.out.println(origin.getClass().getName()); - originName = origin.toString(); - } - return originName; - } - - private static List getArgumentsAsSerializableData(Object[] arguments) { - List argumentStrings = new ArrayList<>(); - - if (arguments != null) { - for (Object argument : arguments) { - argumentStrings.add(SerializableData.GetFromObject(argument)); - } - } - - return argumentStrings; - } - - /** - * Collects information about a method execution call at entry, - * for further collection later on exit - * This method generates an incomplete partial method trace with on entry information, - * and caches it temporarily for use on exit. - * Callers must call later addMethodTrace on exit to complete the trace and make it available. - *

- * This method is public so it can be accessed as part of the modified instrumented classes - * without security problems at the JVM level - * - * @param origin The originating trace Executable object - * @param arguments The originating trace argument list on entry - */ - public static void addNotYetCompletedMethodTrace(Executable origin, Object[] arguments, Object instance) { - String originName = getOriginName(origin); - List argumentString = getArgumentsAsSerializableData(arguments); - - long timeStampEntry = System.currentTimeMillis(); - notYetCompletedMethodTraces.add(new MethodTrace(originName, SerializableData.GetFromObject(instance), argumentString, null, null, timeStampEntry, -1)); - } - - private static MethodTrace getMatchingIncompleteEntryTrace(String originName, SerializableData serializableInstance, long timeStampExit) { - MethodTrace originatingMethodTrace = null; - - if (!serializableInstance.isNull) { - for (MethodTrace methodTrace : notYetCompletedMethodTraces) { - if (methodTrace.methodSignature.equals(originName) && - methodTrace.instance.instanceId == serializableInstance.instanceId - && methodTrace.timeStampEntry <= timeStampExit) { - originatingMethodTrace = methodTrace; - break; - } - } - } - - // It is possible we cannot find a trace with the instanceId, because, it was a constructor entry - // In such case, look for other cases - if (originatingMethodTrace == null) { - for (MethodTrace methodTrace : notYetCompletedMethodTraces) { - if (methodTrace.methodSignature.equals(originName) && methodTrace.instance.isNull - && methodTrace.timeStampEntry <= timeStampExit) { - originatingMethodTrace = methodTrace; - break; - } - } - } - - return originatingMethodTrace; - } - - /** - * Collects information about a method execution call at exit, - * with further data completion with an earlier partial trace if available from entry. - * This method generates an incomplete partial method trace with on exit information, - * and completes it with entry information if available, to cache a complete trace. - * Callers must call earlier addNotYetCompletedMethodTrace to provide entry information to - * have complete trace data. - *

- * Callers must call later getMethodTraces to retrieve the trace information. - *

- * This method is public so it can be accessed as part of the modified instrumented classes - * without security problems at the JVM level - * - * @param origin The originating trace Executable object - * @param arguments The originating trace argument list on entry - * @param returned The data returned by the originating trace - */ - public static void addMethodTrace(Executable origin, Object[] arguments, Object returned, Object instance) { - long timeStampExit = System.currentTimeMillis(); - - String originName = getOriginName(origin); - List argumentString = getArgumentsAsSerializableData(arguments); - SerializableData returnString = SerializableData.GetFromObject(returned); - - SerializableData serializableInstance = SerializableData.GetFromObject(instance); - MethodTrace originatingMethodTrace = getMatchingIncompleteEntryTrace(originName, serializableInstance, timeStampExit); - - List preCallArguments = null; - long timeStampEntry = -1; - - if (originatingMethodTrace != null) { - preCallArguments = originatingMethodTrace.preCallArguments; - timeStampEntry = originatingMethodTrace.timeStampEntry; - notYetCompletedMethodTraces.remove(originatingMethodTrace); - } - - methodTraces.add(new MethodTrace(originName, serializableInstance, preCallArguments, argumentString, returnString, timeStampEntry, timeStampExit)); - } - - /** - * This method returns the complete list of every cached complete method traces - * during program execution. - * - * @return The list of every cached trace during program execution - */ - public static Collection getMethodTraces() { - return methodTraces; - } - - /** - * Generates the tracing results text file on disk - */ - public static void SaveTraceResults() { - File outputFile = new File("MethodTraces.json"); - System.out.println("Saving trace results in progress..."); - System.out.println("Output location: " + outputFile.getAbsolutePath()); - - try { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.enable(SerializationFeature.INDENT_OUTPUT); - objectMapper.writeValue(outputFile, methodTraces); - } catch (Exception e) { - e.printStackTrace(); - } - - System.out.println("Saving trace results in progress... Done!"); - } -} \ No newline at end of file diff --git a/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/MethodVisitor.java b/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/MethodVisitor.java deleted file mode 100644 index 9123b712..00000000 --- a/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/MethodVisitor.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.github.maracas.gilesi.instrumentation; - -import net.bytebuddy.asm.Advice; - -import java.lang.reflect.Executable; - -import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC; - -public class MethodVisitor { - @Advice.OnMethodEnter - public static Object enter( - @Advice.Origin Executable origin, - @Advice.AllArguments(readOnly = false, typing = DYNAMIC) Object[] arguments, - @Advice.This Object instance) { - MethodTracer.addNotYetCompletedMethodTrace(origin, arguments, instance); - return null; - } - - @Advice.OnMethodExit - public static void exit( - @Advice.Origin Executable origin, - @Advice.AllArguments(readOnly = false, typing = DYNAMIC) Object[] arguments, - @Advice.Return(readOnly = false, typing = DYNAMIC) Object returned, - @Advice.This Object instance) { - MethodTracer.addMethodTrace(origin, arguments, returned, instance); - } -} diff --git a/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/SerializableData.java b/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/SerializableData.java deleted file mode 100644 index 4881f8da..00000000 --- a/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/SerializableData.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.github.maracas.gilesi.instrumentation; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; - -public class SerializableData { - private static final ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); - - public String fullyQualifiedTypeName; - public String dataAsJson; - public int instanceId; - public boolean isNull; - public boolean isSerialized; - - public SerializableData(String fullyQualifiedTypeName, String dataAsJson, int instanceId, boolean isNull, boolean isSerialized) { - this.fullyQualifiedTypeName = fullyQualifiedTypeName; - this.dataAsJson = dataAsJson; - this.instanceId = instanceId; - this.isNull = isNull; - this.isSerialized = isSerialized; - } - - public SerializableData() {} - - public static SerializableData GetFromObject(Object object) { - if (object == null) { - return new SerializableData(null, null, -1, true, false); - } - - // /!\ CAUTION - // - // The JVM Specification __clearly__ states that the hashcode returned by the Object implementation - // (the one obtained here) is not claimed to be valid, and is not an address in memory. - // Indeed, how come do we have 32 bit integers on a 64 bit addressable system...? - // While it's __unlikely__ to encounter the same value here for our purposes, the risk is NON 0, and - // we must always check other factors to know if a value is identical to one or another, like the - // fully qualified name, or the parameter list! You've been warned... - // - // PS: if you got a better idea, I'm all ears :) - // - int instanceId = System.identityHashCode(object); - String objectClassName = object.getClass().getName(); - - String serializedString = null; - boolean isSerialized = false; - - try { - serializedString = objectMapper.writeValueAsString(object); - isSerialized = true; - } catch (JsonProcessingException e) { - //System.out.println("Cannot serialize specific argument: " + e); - } - - return new SerializableData(objectClassName, serializedString, instanceId, false, isSerialized); - } - - /*public String getJavaCodeEquivalentAsString() { - return "TODO!"; - }*/ - - @Override - public String toString() { - return "SerializableData{" + - "fullyQualifiedTypeName='" + fullyQualifiedTypeName + '\'' + - ", dataAsJson='" + dataAsJson + '\'' + - ", instanceId=" + instanceId + - ", isNull=" + isNull + - ", isSerialized=" + isSerialized + - '}'; - } -} \ No newline at end of file diff --git a/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/StaticMethodVisitor.java b/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/StaticMethodVisitor.java deleted file mode 100644 index 7d48908b..00000000 --- a/Instrumentation/src/main/java/com/github/maracas/gilesi/instrumentation/StaticMethodVisitor.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.github.maracas.gilesi.instrumentation; - -import net.bytebuddy.asm.Advice; - -import java.lang.reflect.Executable; - -import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC; - -public class StaticMethodVisitor { - @Advice.OnMethodEnter - public static Object enter( - @Advice.Origin Executable origin, - @Advice.AllArguments(readOnly = false, typing = DYNAMIC) Object[] arguments) { - MethodTracer.addNotYetCompletedMethodTrace(origin, arguments, null); - return null; - } - - @Advice.OnMethodExit - public static void exit( - @Advice.Origin Executable origin, - @Advice.AllArguments(readOnly = false, typing = DYNAMIC) Object[] arguments, - @Advice.Return(readOnly = false, typing = DYNAMIC) Object returned) { - MethodTracer.addMethodTrace(origin, arguments, returned, null); - } -} diff --git a/Maestro/.gitignore b/Maestro/.gitignore new file mode 100644 index 00000000..d4c6a6aa --- /dev/null +++ b/Maestro/.gitignore @@ -0,0 +1,39 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/* +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/Maestro/build.gradle b/Maestro/build.gradle new file mode 100644 index 00000000..0bdfc681 --- /dev/null +++ b/Maestro/build.gradle @@ -0,0 +1,81 @@ +plugins { + id 'application' + id 'com.github.johnrengelman.shadow' version '8.1.1' +} + +group 'com.github.gilesi.maestro' +version '1.0-SNAPSHOT' + + +compileJava { + options.encoding = 'UTF-8' +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} + +repositories { + mavenCentral() + mavenLocal() + maven { + url 'https://packages.jetbrains.team/maven/p/ij/intellij-dependencies' + } +} + +dependencies { + implementation 'com.fasterxml.jackson.core:jackson-core:2.18.1' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.18.1' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.1' + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.18.1' + implementation 'com.fasterxml.jackson:jackson-base:2.18.1' + implementation 'org.apache.maven:maven-core:3.9.9' + implementation 'org.apache.maven.shared:maven-invoker:3.3.0' + testImplementation platform('org.junit:junit-bom:5.11.3') + testImplementation 'org.junit.jupiter:junit-jupiter' + implementation 'org.apache.logging.log4j:log4j-core:2.24.0' + implementation 'org.apache.logging.log4j:log4j-api:2.24.1' +} + +jar { + manifest { + attributes 'Main-Class': 'com.github.gilesi.maestro.Main', + 'Multi-Release': 'true' + } +} + +application { + mainClass = 'com.github.gilesi.maestro.Main' +} + +description = 'Main distribution.' + +shadowJar { + archiveBaseName.set('com.github.gilesi.maestro') + archiveClassifier.set('') + archiveVersion.set('') + mergeServiceFiles() +} + +distributions { + shadow { + distributionBaseName = 'com.github.gilesi.maestro' + } +} + +apply plugin: 'java' +apply plugin: 'idea' + +idea { + module { + downloadJavadoc = true + downloadSources = true + } +} + +run { + jvmArgs = [ + "-XX:InitialHeapSize=2G", + "-XX:MaxHeapSize=32G" + ] +} \ No newline at end of file diff --git a/Maestro/gradle/wrapper/gradle-wrapper.jar b/Maestro/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..249e5832 Binary files /dev/null and b/Maestro/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Maestro/gradle/wrapper/gradle-wrapper.properties b/Maestro/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..5c07b32a --- /dev/null +++ b/Maestro/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon May 27 14:15:06 CEST 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/Maestro/gradlew b/Maestro/gradlew new file mode 100755 index 00000000..1b6c7873 --- /dev/null +++ b/Maestro/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +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 + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/Maestro/gradlew.bat b/Maestro/gradlew.bat new file mode 100644 index 00000000..ac1b06f9 --- /dev/null +++ b/Maestro/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Maestro/settings.gradle b/Maestro/settings.gradle new file mode 100644 index 00000000..ac900376 --- /dev/null +++ b/Maestro/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'Maestro' + diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/Constants.java b/Maestro/src/main/java/com/github/gilesi/maestro/Constants.java new file mode 100644 index 00000000..8b28e585 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/Constants.java @@ -0,0 +1,41 @@ +package com.github.gilesi.maestro; + +import java.util.Hashtable; +import java.util.Map; + +public class Constants { + // These paths should already exist on the end user machine for now and never change + public static final String CONF_GEN_LOCATION = "ConfGen/build/libs/com.github.gilesi.confgen.jar"; + public static final String TEST_GEN_LOCATION = "TestGenerator/build/libs/com.github.gilesi.testgenerator.jar"; + public static final String TRACE_DIFF_LOCATION = "TraceDiff/build/libs/com.github.gilesi.tracediff.jar"; + public static final String INSTRUMENTATION_LOCATION = "Instrumentation/build/libs/com.github.gilesi.instrumentation.jar"; + + public static final String JAVA_21_BINARY_LOCATION = "/usr/lib/jvm/java-21-openjdk-amd64"; + + // These paths are dynamically created by the tool but must stay consistent within methods + public static final String targetAgentName = "com.github.gilesi.instrumentation.jar"; + public static final String targetConfigurationName = "TestWorkflowConfiguration.json"; + + public static final Map JAVA_VERSIONS = new Hashtable<>() { + { + put("1.5", "/usr/lib/jvm/java-8-openjdk-amd64"); + put("1.6", "/usr/lib/jvm/java-8-openjdk-amd64"); + put("1.7", "/usr/lib/jvm/java-8-openjdk-amd64"); + put("1.8", "/usr/lib/jvm/java-8-openjdk-amd64"); + put("11", "/usr/lib/jvm/java-11-openjdk-amd64"); + put("17", "/usr/lib/jvm/java-17-openjdk-amd64"); + put("21", JAVA_21_BINARY_LOCATION); + } + }; + + public static final String GREEN_TEST = "Green"; + public static final String RED_TEST = "Red"; + + public static final String SUREFIRE_VERSION = "2.8"; + public static final String SUREFIRE_GROUPID = "org.apache.maven.plugins"; + public static final String SUREFIRE_ARTIFACTID = "maven-surefire-plugin"; + + public static boolean SKIP_INSTRUMENTATION_RAN_ALREADY_FOR_DEBUGGING = false; + + public static final String M2_REPOSITORY = "/home/gus/.m2/repository"; +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/FileUtils.java b/Maestro/src/main/java/com/github/gilesi/maestro/FileUtils.java new file mode 100644 index 00000000..8b134433 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/FileUtils.java @@ -0,0 +1,130 @@ +package com.github.gilesi.maestro; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Objects; +import java.util.regex.Pattern; + +public class FileUtils { + public static void deleteDirectory(File fi) { + for (File file : Objects.requireNonNull(fi.listFiles())) { + if (file.isDirectory()) { + deleteDirectory(file); + } + file.delete(); + } + fi.delete(); + } + + public static void deleteDirectoryIfExists(String path) { + Path pa = Path.of(path); + if (Files.exists(pa)) { + deleteDirectory(new File(path)); + } + } + + public static ArrayList enumerateDirectory(File fi, String wildcard, boolean recursive) { + ArrayList files = new ArrayList<>(); + Pattern pattern = Pattern.compile(wildcard); + for (File file : Objects.requireNonNull(fi.listFiles())) { + if (file.isDirectory() && recursive) { + files.addAll(enumerateDirectory(file, wildcard, true)); + } else { + if (pattern.matcher(file.getName()).matches()) { + files.add(file.getPath()); + } + } + } + return files; + } + + public static ArrayList enumerateFiles(String path, String wildcard, boolean recursive) { + Path pa = Path.of(path); + if (Files.exists(pa)) { + return enumerateDirectory(new File(path), wildcard, recursive); + } + return new ArrayList<>(); + } + + public static void ensureDirectoryExistsAndIsEmpty(String path) throws IOException { + Path pa = Path.of(path); + deleteDirectoryIfExists(path); + Files.createDirectory(pa); + } + + public static void backupFile(String fileLocation) throws IOException { + Path filePath = Path.of(fileLocation); + Path backupFilePath = Path.of("%s.orig".formatted(fileLocation)); + + if (!Files.exists(filePath)) { + return; + } + + if (Files.exists(backupFilePath)) { + Files.delete(backupFilePath); + } + + Files.copy(filePath, backupFilePath); + } + + public static void restoreFile(String fileLocation) throws IOException { + Path filePath = Path.of(fileLocation); + Path backupFilePath = Path.of("%s.orig".formatted(fileLocation)); + + if (!Files.exists(backupFilePath)) { + return; + } + + if (Files.exists(filePath)) { + Files.delete(filePath); + } + + Files.move(backupFilePath, filePath); + } + + public static void copyFolder(File source, File destination) { + if (source.isDirectory()) { + if (!destination.exists()) { + destination.mkdirs(); + } + + String[] files = source.list(); + + for (String file : files) { + File srcFile = new File(source, file); + File destFile = new File(destination, file); + + copyFolder(srcFile, destFile); + } + } else { + InputStream in = null; + OutputStream out = null; + + try { + in = new FileInputStream(source); + out = new FileOutputStream(destination); + + byte[] buffer = new byte[1024]; + + int length; + while ((length = in.read(buffer)) > 0) { + out.write(buffer, 0, length); + } + } catch (Exception e) { + try { + in.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + + try { + out.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } + } +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/Main.java b/Maestro/src/main/java/com/github/gilesi/maestro/Main.java new file mode 100644 index 00000000..d65a3abe --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/Main.java @@ -0,0 +1,44 @@ +package com.github.gilesi.maestro; + +import com.github.gilesi.maestro.compsuite.CompSuite; +import org.apache.maven.shared.invoker.MavenInvocationException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class Main { + public static final Logger logger = LogManager.getLogger("maestro"); + public static final Logger loggerMaven = LogManager.getLogger("maven"); + public static final Logger loggerConfGen = LogManager.getLogger("confgen"); + public static final Logger loggerTraceDiff = LogManager.getLogger("tracediff"); + public static final Logger loggerTraceGen = LogManager.getLogger("tracegen"); + public static final Logger loggerAgent = LogManager.getLogger("agent"); + + public static void main(String[] args) throws MavenInvocationException, IOException, InterruptedException { + if (System.getenv("MAVEN_HOME") == null || !Files.exists(Path.of(System.getenv("MAVEN_HOME"))) || Files.isRegularFile(Path.of(System.getenv("MAVEN_HOME")))) { + logger.info("You must specify the MAVEN_HOME environment variable pointing to a valid Maven directory"); + return; + } + + String projectToRunOn = null; + + if (args.length < 2) { + logger.info("Usage: "); + return; + } + + String datasetOutput = args[0]; + String GilesiRepositoryLocation = args[1]; + + if (args.length == 3) { + projectToRunOn = args[2]; + } + + String dataset = Path.of(datasetOutput).resolve("incompatibilities.json").toString(); + + CompSuite.runOnDataset(GilesiRepositoryLocation, dataset, datasetOutput, projectToRunOn); + } +} \ No newline at end of file diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/Orchestrator.java b/Maestro/src/main/java/com/github/gilesi/maestro/Orchestrator.java new file mode 100644 index 00000000..c48ff7fa --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/Orchestrator.java @@ -0,0 +1,267 @@ +package com.github.gilesi.maestro; + +import com.github.gilesi.maestro.buildsystem.configuration.maven.PomHelper; +import com.github.gilesi.maestro.runners.maven.Log4JLogger; +import com.github.gilesi.maestro.runners.maven.ProjectRunner; +import com.github.gilesi.maestro.tools.ConfGen; +import com.github.gilesi.maestro.tools.TestGen; +import org.apache.maven.shared.invoker.MavenInvocationException; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; + +public class Orchestrator { + // TODO: More robust logging here using JUnit perhaps... + // TODO: Simplify required arg to run here + // TODO: Ideally we would want to deduce the version from the provided source but not always doable + // TODO: Could we also fetch source jars ourselves somehow or source loc? + // TODO: Maybe the dependency name is also doable to extract here, need to see how... + // TODO: Fix cross plat! + // TODO: picocli? + + public static void handleProject(String clientLocation, String libraryLocation, String GilesiRepositoryLocation, String dependencyName, String dependencyVersion, String outputTestProject) throws XmlPullParserException, IOException, MavenInvocationException, InterruptedException { + FileUtils.deleteDirectoryIfExists(outputTestProject); + Path outputTestProjectPath = Path.of(outputTestProject); + + Path libraryLocationPath = Path.of(libraryLocation); + Path libraryConfig = libraryLocationPath.resolve(Constants.targetConfigurationName); + + // Create the results directory + Files.createDirectories(outputTestProjectPath); + + // Run ConfGen on Library first + // TODO: Check how meta projects get handled here exactly... + ConfGen.runConfGen(GilesiRepositoryLocation, libraryConfig.toString(), outputTestProject, libraryLocation); + + Path InstrumentationAgentEffectiveLocation = Path.of("%s%s".formatted(GilesiRepositoryLocation, Constants.INSTRUMENTATION_LOCATION)); + + // Modify Client Build System to use the instrumentation agent + ArrayList clientProjectPomFiles = FileUtils.enumerateFiles(clientLocation, "pom.xml", true); + ArrayList clientProjectGradleBuildFiles = FileUtils.enumerateFiles(clientLocation, "build.gradle", true); + + ArrayList allProjectFiles = new ArrayList<>(); + allProjectFiles.addAll(clientProjectPomFiles); + allProjectFiles.addAll(clientProjectGradleBuildFiles); + + copyInstrumentationToolAndConfiguration(allProjectFiles, InstrumentationAgentEffectiveLocation, libraryConfig); + + orchestrateMavenProjects(clientProjectPomFiles); + orchestrateGradleProjects(clientProjectGradleBuildFiles); + + // Run the client tests + executeMavenProjectTests(clientProjectPomFiles, clientLocation); + executeGradleProjectTests(clientProjectGradleBuildFiles, clientLocation); + + TestGen.runTestGen(GilesiRepositoryLocation, outputTestProject, outputTestProjectPath.resolve("src").resolve("test").resolve("java").toString(), libraryConfig.toString()); + + lastMinuteCleanup(allProjectFiles); + + // Generate Gradle test project here in dir outputTestProject, with dep dependencyName of version dependencyVersion + + String buildGradleProjectFile = """ + plugins { + id 'java' + } + + group = 'testProject' + version = '1.0-SNAPSHOT' + + repositories { + mavenCentral() + mavenLocal() + } + + dependencies { + testImplementation '%s:%s' + testImplementation 'com.thoughtworks.xstream:xstream:1.4.20' + testImplementation platform('org.junit:junit-bom:5.10.0') + testImplementation 'org.junit.jupiter:junit-jupiter' + } + + test { + useJUnitPlatform() + }""".formatted(dependencyName, dependencyVersion); + + Files.writeString(outputTestProjectPath.resolve("build.gradle"), buildGradleProjectFile); + Files.copy(Path.of(GilesiRepositoryLocation).resolve("Maestro").resolve("gradlew"), outputTestProjectPath.resolve("gradlew")); + Files.copy(Path.of(GilesiRepositoryLocation).resolve("Maestro").resolve("gradlew.bat"), outputTestProjectPath.resolve("gradlew.bat")); + + // Test test project (3 times to be sure) + + // Update test project dependency version at version dependencyNewVersion + + // Test test project with dependency at version dependencyNewVersion (3 times to be sure) + + // Collect results of the execution + + // Write report + } + + public static void copyInstrumentationToolAndConfiguration(ArrayList allProjectFiles, Path InstrumentationAgentEffectiveLocation, Path libraryConfig) throws IOException { + for (String clientBuildFile : allProjectFiles) { + Path clientBuildFilePath = Path.of(clientBuildFile); + Path clientProjectLocationPath = clientBuildFilePath.getParent().toAbsolutePath(); + Path agentDest = clientProjectLocationPath.resolve(Constants.targetAgentName); + Path workflowDest = clientProjectLocationPath.resolve(Constants.targetConfigurationName); + + Path clientBuildFileBackup = Path.of("%s.gilesi.bak".formatted(clientBuildFile)); + if (Files.exists(clientBuildFileBackup)) { + Files.deleteIfExists(clientBuildFilePath); + Files.move(clientBuildFileBackup, clientBuildFilePath); + } + + Files.deleteIfExists(agentDest); + Files.deleteIfExists(workflowDest); + + //Files.copy(InstrumentationAgentEffectiveLocation, agentDest); + //Files.copy(libraryConfig, workflowDest); + } + } + + public static void orchestrateMavenProjects(ArrayList clientProjectPomFiles) throws IOException, XmlPullParserException { + for (String clientBuildFile : clientProjectPomFiles) { + Path clientBuildFilePath = Path.of(clientBuildFile); + Files.copy(clientBuildFilePath, Path.of("%s.gilesi.bak".formatted(clientBuildFile))); + + String clientSurefireArgLine = "-Dnet.bytebuddy.experimental=true -javaagent:%s=%s".formatted(Constants.targetAgentName, Constants.targetConfigurationName); + PomHelper.addSureFire(clientBuildFile, clientSurefireArgLine); + } + } + + public static void orchestrateGradleProjects(ArrayList clientProjectGradleBuildFiles) throws IOException { + for (String clientBuildFile : clientProjectGradleBuildFiles) { + String clientTestingBlock = """ + testing { + \tsuites { + \t\ttest { + \t\t\tuseJUnitJupiter() + \t\t\ttargets { + \t\t\t\tall { + \t\t\t\t\ttestTask.configure { + \t\t\t\t\t\tsystemProperty 'net.bytebuddy.experimental', 'true' + \t\t\t\t\t\tjvmArgs = ['-XX:+EnableDynamicAgentLoading', '-javaagent:%s=%s'] + \t\t\t\t\t} + \t\t\t\t} + \t\t\t} + \t\t} + \t} + }""".formatted(Constants.targetAgentName, Constants.targetConfigurationName); + + Path clientBuildFilePath = Path.of(clientBuildFile); + + String buildFileContent = Files.readString(clientBuildFilePath); + if (!buildFileContent.contains(clientTestingBlock)) { + buildFileContent += "\n" + clientTestingBlock; + + Files.move(clientBuildFilePath, Path.of("%s.gilesi.bak".formatted(clientBuildFile))); + Files.writeString(clientBuildFilePath, buildFileContent); + } + } + } + + public static void executeMavenProjectTests(ArrayList clientProjectPomFiles, String clientLocation) throws MavenInvocationException { + // This is the maven path + if (!clientProjectPomFiles.isEmpty()) { + if (clientProjectPomFiles.stream().anyMatch(t -> t.replace("%s%c".formatted(clientLocation, File.separatorChar), "").replace(clientLocation, "").equals("pom.xml"))) { + ProjectRunner.runMavenGoalOnRepository(clientLocation, "-fn test -Danimal.sniffer.skip=true", null, "21", new Log4JLogger(), null); + } else { + for (String clientPomFile : clientProjectPomFiles) { + Path clientPomFilePath = Path.of(clientPomFile); + Path clientProjectLocationPath = clientPomFilePath.getParent().toAbsolutePath(); + + ProjectRunner.runMavenGoalOnRepository(clientProjectLocationPath.toString(), "-fn test -Danimal.sniffer.skip=true", null, "21", new Log4JLogger(), null); + } + } + } + } + + public static void executeGradleProjectTests(ArrayList clientProjectGradleBuildFiles, String clientLocation) throws IOException, InterruptedException { + // This is the gradle path + if (!clientProjectGradleBuildFiles.isEmpty()) { + if (clientProjectGradleBuildFiles.stream().anyMatch + ( + t -> + t + .replace( + "%s%c" + .formatted( + clientLocation, + File.separatorChar + ), + "" + ) + .replace( + clientLocation, + "" + ) + .equals("build.gradle") + )) { + ProcessUtils.runExternalCommand(new String[]{ + "/usr/bin/env", + "bash", + Path.of(clientLocation).resolve("gradlew").toString(), + "test", + "--warning-mode", + "all" + }, new File(clientLocation), Main.logger); + + // TODO: Check for underlying platform + /*ProcessUtils.runExternalCommand(new String[]{ + "cmd.exe", + "-C", + Path.of(clientLocation).resolve("gradlew").toString(), + "test", + "--warning-mode", + "all" + }, new File(clientLocation), Main.logger);*/ + } else { + for (String clientGradleBuildFile : clientProjectGradleBuildFiles) { + Path clientGradleBuildFilePath = Path.of(clientGradleBuildFile); + Path clientProjectLocationPath = clientGradleBuildFilePath.getParent().toAbsolutePath(); + + ProcessUtils.runExternalCommand(new String[]{ + "/usr/bin/env", + "bash", + clientProjectLocationPath.resolve("gradlew").toString(), + "test", + "--warning-mode", + "all" + }, new File(clientProjectLocationPath.toString()), Main.logger); + + // TODO: Check for underlying platform + /*ProcessUtils.runExternalCommand(new String[]{ + "cmd.exe", + "-C", + clientProjectLocationPath.resolve("gradlew.bat").toString(), + "test", + "--warning-mode", + "all" + }, new File(clientProjectLocationPath.toString()), Main.logger);*/ + } + } + } + } + + public static void lastMinuteCleanup(ArrayList allProjectFiles) throws IOException { + // Last minute cleanup + for (String clientBuildFile : allProjectFiles) { + Path clientBuildFilePath = Path.of(clientBuildFile); + Path clientProjectLocationPath = clientBuildFilePath.getParent().toAbsolutePath(); + Path agentDest = clientProjectLocationPath.resolve(Constants.targetAgentName); + Path workflowDest = clientProjectLocationPath.resolve(Constants.targetConfigurationName); + + Path clientBuildFileBackup = Path.of("%s.gilesi.bak".formatted(clientBuildFile)); + if (Files.exists(clientBuildFileBackup)) { + Files.deleteIfExists(clientBuildFilePath); + Files.move(clientBuildFileBackup, clientBuildFilePath); + } + + Files.deleteIfExists(agentDest); + Files.deleteIfExists(workflowDest); + } + } +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/ProcessUtils.java b/Maestro/src/main/java/com/github/gilesi/maestro/ProcessUtils.java new file mode 100644 index 00000000..42fd75ff --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/ProcessUtils.java @@ -0,0 +1,26 @@ +package com.github.gilesi.maestro; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; + +public class ProcessUtils { + public static int runExternalCommand(String[] command, File directory, org.apache.logging.log4j.Logger logger) throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder(command); + if (directory != null) { + pb.directory(directory); + } + pb.redirectErrorStream(true); + Process p = pb.start(); + BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); + + String line; + + while ((line = reader.readLine()) != null) { + logger.info(line); + } + + return p.waitFor(); + } +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/RunResult.java b/Maestro/src/main/java/com/github/gilesi/maestro/RunResult.java new file mode 100644 index 00000000..477843a4 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/RunResult.java @@ -0,0 +1,22 @@ +package com.github.gilesi.maestro; + +import com.github.gilesi.maestro.testing.ReportItem; + +import java.util.ArrayList; + +public class RunResult { + public String ProjectId = ""; + public boolean ProjectIsMissingLibrarySourceCode = false; + public boolean ProjectCannotBeReproduced = false; // TODO + public boolean ProjectCannotBeInstrumented = false; + public boolean ProjectFailedToLoadAgent = false; + public boolean ProjectIsMissingTraceFiles = false; + public boolean ProjectIsMissingSpecificTraces = false; + public boolean ProjectFailedToGenerateAnyTraceTestSrc = false; + public boolean ProjectFailedToCompileGeneratedTests = false; + public boolean ProjectFailedToExecuteGeneratedTests = false; + public boolean ProjectFailedToCompileGeneratedTestsWithNewVersion = false; + public boolean ProjectFailedToExecuteGeneratedTestsWithNewVersion = false; + public ArrayList TestExecutionReportWithOldVersion; + public ArrayList TestExecutionReportWithNewVersion; +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/buildsystem/configuration/maven/ConfigurationUtils.java b/Maestro/src/main/java/com/github/gilesi/maestro/buildsystem/configuration/maven/ConfigurationUtils.java new file mode 100644 index 00000000..b7b92a36 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/buildsystem/configuration/maven/ConfigurationUtils.java @@ -0,0 +1,31 @@ +package com.github.gilesi.maestro.buildsystem.configuration.maven; + +import org.codehaus.plexus.util.xml.Xpp3Dom; + +public class ConfigurationUtils { + public static Xpp3Dom getDomValueObject(String name, String value) { + Xpp3Dom domValue = new Xpp3Dom(name); + domValue.setValue(value); + return domValue; + } + + public static Xpp3Dom getDomArrayObject(String arrayName, String name, String[] values) { + Xpp3Dom valuesDom = new Xpp3Dom(arrayName); + for (String val : values) { + addStringValueIfNotEmpty(valuesDom, name, val); + } + return valuesDom; + } + + public static void addStringValueIfNotEmpty(Xpp3Dom config, String name, String value) { + if (value != null && !value.isEmpty()) { + config.addChild(getDomValueObject(name, value)); + } + } + + public static void addStringArrayValueIfNotEmpty(Xpp3Dom config, String arrayName, String name, String[] values) { + if (values != null && values.length > 0) { + config.addChild(getDomArrayObject(arrayName, name, values)); + } + } +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/buildsystem/configuration/maven/IPluginConfiguration.java b/Maestro/src/main/java/com/github/gilesi/maestro/buildsystem/configuration/maven/IPluginConfiguration.java new file mode 100644 index 00000000..0fbe2835 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/buildsystem/configuration/maven/IPluginConfiguration.java @@ -0,0 +1,7 @@ +package com.github.gilesi.maestro.buildsystem.configuration.maven; + +import org.codehaus.plexus.util.xml.Xpp3Dom; + +public interface IPluginConfiguration { + Xpp3Dom serialize(); +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/buildsystem/configuration/maven/PomHelper.java b/Maestro/src/main/java/com/github/gilesi/maestro/buildsystem/configuration/maven/PomHelper.java new file mode 100644 index 00000000..673039e2 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/buildsystem/configuration/maven/PomHelper.java @@ -0,0 +1,154 @@ +package com.github.gilesi.maestro.buildsystem.configuration.maven; + +import com.github.gilesi.maestro.Constants; +import org.apache.maven.model.Build; +import org.apache.maven.model.Dependency; +import org.apache.maven.model.Model; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.apache.maven.model.io.xpp3.MavenXpp3Writer; +import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; + +public class PomHelper { + + public static Model readPomFile(String pomFilePath) throws IOException, XmlPullParserException { + MavenXpp3Reader pomReader = new MavenXpp3Reader(); + Path pomPath = Path.of(pomFilePath); + InputStream pomStream = Files.newInputStream(pomPath); + Model pomModel = pomReader.read(pomStream); + pomModel.setPomFile(new File(pomFilePath)); + pomStream.close(); + return pomModel; + } + + private static void writePomFile(Model pomModel, String pomFilePath) throws IOException { + MavenXpp3Writer pomWriter = new MavenXpp3Writer(); + FileWriter pomWriteStream = new FileWriter(pomFilePath); + pomWriter.write(pomWriteStream, pomModel); + pomWriteStream.close(); + } + + private static boolean doesBuildContainPluginGroupId(Build bld, String groupId) { + for (Plugin plg : bld.getPlugins()) { + if (plg.getGroupId().equalsIgnoreCase(groupId)) { + return true; + } + } + return false; + } + + private static boolean doesModelContainDependencyGroupId(Model pomModel, String groupId) { + for (Dependency plg : pomModel.getDependencies()) { + if (plg.getGroupId().equalsIgnoreCase(groupId)) { + return true; + } + } + return false; + } + + private static Plugin getPluginByGroupId(Build bld, String groupId) throws FileNotFoundException { + for (Plugin plg : bld.getPlugins()) { + if (plg.getGroupId().equalsIgnoreCase(groupId)) { + return plg; + } + } + throw new FileNotFoundException(); + } + + private static Xpp3Dom buildSureFireConfiguration() { + SureFirePluginConfiguration sureFireConfiguration = new SureFirePluginConfiguration(); + return sureFireConfiguration.serialize(); + } + + private static Xpp3Dom buildSureFireConfiguration(String argLine) { + SureFirePluginConfiguration sureFireConfiguration = new SureFirePluginConfiguration(); + sureFireConfiguration.setArgLine(argLine); + return sureFireConfiguration.serialize(); + } + + private static Plugin getSureFirePlugin() { + return buildPlugin(Constants.SUREFIRE_GROUPID, Constants.SUREFIRE_ARTIFACTID, Constants.SUREFIRE_VERSION); + } + + private static Dependency buildDependency(String groupId, String artifactId, String version) { + Dependency dependency = new Dependency(); + dependency.setGroupId(groupId); + dependency.setArtifactId(artifactId); + dependency.setVersion(version); + return dependency; + } + + private static Plugin buildPlugin(String groupId, String artifactId, String version) { + Plugin plugin = new Plugin(); + plugin.setGroupId(groupId); + plugin.setArtifactId(artifactId); + plugin.setVersion(version); + return plugin; + } + + private static Plugin buildSureFirePlugin() { + Plugin surePlugin = getSureFirePlugin(); + Xpp3Dom config = buildSureFireConfiguration(); + surePlugin.setConfiguration(config); + return surePlugin; + } + + private static Plugin buildSureFirePlugin(String argLine) { + Plugin surePlugin = getSureFirePlugin(); + Xpp3Dom config = buildSureFireConfiguration(argLine); + surePlugin.setConfiguration(config); + return surePlugin; + } + + private static void removeExistingPlugin(Build bld, String groupId) throws FileNotFoundException { + // Get rid of all previous instances of the plugin in case one already exists + while (doesBuildContainPluginGroupId(bld, groupId)) { + bld.removePlugin(getPluginByGroupId(bld, groupId)); + } + } + + private static void removeExistingSureFirePlugins(Build bld) throws FileNotFoundException { + removeExistingPlugin(bld, Constants.SUREFIRE_GROUPID); + } + + public static void addSureFire(String pomFilePath) throws XmlPullParserException, IOException { + Model pomModel = readPomFile(pomFilePath); + Build bld = pomModel.getBuild(); + + if (bld == null) { + bld = new Build(); + } + + removeExistingSureFirePlugins(bld); + + Plugin surePlugin = buildSureFirePlugin(); + + bld.addPlugin(surePlugin); + + pomModel.setBuild(bld); + writePomFile(pomModel, pomFilePath); + } + + public static void addSureFire(String pomFilePath, String argLine) throws XmlPullParserException, IOException { + Model pomModel = readPomFile(pomFilePath); + Build bld = pomModel.getBuild(); + + if (bld == null) { + bld = new Build(); + } + + removeExistingSureFirePlugins(bld); + + Plugin surePlugin = buildSureFirePlugin(argLine); + + bld.addPlugin(surePlugin); + + pomModel.setBuild(bld); + writePomFile(pomModel, pomFilePath); + } +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/buildsystem/configuration/maven/SureFirePluginConfiguration.java b/Maestro/src/main/java/com/github/gilesi/maestro/buildsystem/configuration/maven/SureFirePluginConfiguration.java new file mode 100644 index 00000000..afc70c5b --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/buildsystem/configuration/maven/SureFirePluginConfiguration.java @@ -0,0 +1,103 @@ +package com.github.gilesi.maestro.buildsystem.configuration.maven; + +import org.codehaus.plexus.util.xml.Xpp3Dom; + +public class SureFirePluginConfiguration implements IPluginConfiguration { + private int forkCount = 1; + private boolean reuseForks = false; + private String argLine = "-Xmx2G -XX:MaxMetaspaceSize=2G"; + private String parallel = "methods"; + private int threadCount = 1; + private String forkedProcessTimeoutInSeconds = "40"; + private String forkedProcessExitTimeoutInSeconds = "40"; + private String parallelTestsTimeoutInSeconds = "30"; + private String parallelTestsTimeoutForcedInSeconds = "30"; + + public int getForkCount() { + return forkCount; + } + + public void setForkCount(int forkCount) { + this.forkCount = forkCount; + } + + public int getThreadCount() { + return threadCount; + } + + public void setThreadCount(int threadCount) { + this.threadCount = threadCount; + } + + public String getArgLine() { + return argLine; + } + + public void setArgLine(String argLine) { + this.argLine = argLine; + } + + public String getForkedProcessExitTimeoutInSeconds() { + return forkedProcessExitTimeoutInSeconds; + } + + public void setForkedProcessExitTimeoutInSeconds(String forkedProcessExitTimeoutInSeconds) { + this.forkedProcessExitTimeoutInSeconds = forkedProcessExitTimeoutInSeconds; + } + + public String getForkedProcessTimeoutInSeconds() { + return forkedProcessTimeoutInSeconds; + } + + public void setForkedProcessTimeoutInSeconds(String forkedProcessTimeoutInSeconds) { + this.forkedProcessTimeoutInSeconds = forkedProcessTimeoutInSeconds; + } + + public String getParallelTestsTimeoutForcedInSeconds() { + return parallelTestsTimeoutForcedInSeconds; + } + + public void setParallelTestsTimeoutForcedInSeconds(String parallelTestsTimeoutForcedInSeconds) { + this.parallelTestsTimeoutForcedInSeconds = parallelTestsTimeoutForcedInSeconds; + } + + public String getParallelTestsTimeoutInSeconds() { + return parallelTestsTimeoutInSeconds; + } + + public void setParallelTestsTimeoutInSeconds(String parallelTestsTimeoutInSeconds) { + this.parallelTestsTimeoutInSeconds = parallelTestsTimeoutInSeconds; + } + + public String getParallel() { + return parallel; + } + + public void setParallel(String parallel) { + this.parallel = parallel; + } + + public boolean getReuseForks() { + return reuseForks; + } + + public void setReuseForks(boolean reuseForks) { + this.reuseForks = reuseForks; + } + + public Xpp3Dom serialize() { + Xpp3Dom config = new Xpp3Dom("configuration"); + + ConfigurationUtils.addStringValueIfNotEmpty(config, "forkCount", Integer.toString(forkCount)); + ConfigurationUtils.addStringValueIfNotEmpty(config, "reuseForks", Boolean.toString(reuseForks)); + ConfigurationUtils.addStringValueIfNotEmpty(config, "argLine", argLine); + ConfigurationUtils.addStringValueIfNotEmpty(config, "parallel", parallel); + ConfigurationUtils.addStringValueIfNotEmpty(config, "threadCount", Integer.toString(threadCount)); + ConfigurationUtils.addStringValueIfNotEmpty(config, "forkedProcessTimeoutInSeconds", forkedProcessTimeoutInSeconds); + ConfigurationUtils.addStringValueIfNotEmpty(config, "forkedProcessExitTimeoutInSeconds", forkedProcessExitTimeoutInSeconds); + ConfigurationUtils.addStringValueIfNotEmpty(config, "parallelTestsTimeoutInSeconds", parallelTestsTimeoutInSeconds); + ConfigurationUtils.addStringValueIfNotEmpty(config, "parallelTestsTimeoutForcedInSeconds", parallelTestsTimeoutForcedInSeconds); + + return config; + } +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/compsuite/CompSuite.java b/Maestro/src/main/java/com/github/gilesi/maestro/compsuite/CompSuite.java new file mode 100644 index 00000000..11d9aa83 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/compsuite/CompSuite.java @@ -0,0 +1,627 @@ +package com.github.gilesi.maestro.compsuite; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.maven.shared.invoker.MavenInvocationException; + +import com.github.gilesi.maestro.Constants; +import com.github.gilesi.maestro.FileUtils; +import com.github.gilesi.maestro.Main; +import com.github.gilesi.maestro.Orchestrator; +import com.github.gilesi.maestro.RunResult; +import com.github.gilesi.maestro.runners.maven.TestAssertedLogger; +import com.github.gilesi.maestro.tools.ConfGen; +import com.github.gilesi.maestro.tools.TestGen; + +public class CompSuite { + + private static String GILESI_INSTRUMENTATION_JAR_LOADED_MARKER_FILE_NAME = "gilesi.instrumentation_loaded_1.marker"; + + public static Map runRegressionTestsOnDataset( + String dataset, + String datasetOutput + ) throws IOException, MavenInvocationException { + + CompsuiteReproduction compsuiteReproduction = new CompsuiteReproduction(dataset); + CompSuiteIncompatibility[] incompatibilities = compsuiteReproduction.getIncompatibilities(); + Map mapList = new HashMap<>(); + Path datasetOutputPath = Path.of(datasetOutput); + + for (CompSuiteIncompatibility incompatibility : incompatibilities) { + CompsuiteReproduction.printIncompatibility(incompatibility, Main.logger); + + TestAssertedLogger oldTestAssertedLogger = CompsuiteReproduction.runTestOnClientUsingOldVersionOfLibrary( + datasetOutputPath, + incompatibility, + null, + Main.loggerMaven + ); + + TestAssertedLogger newTestAssertedLogger = CompsuiteReproduction.runTestOnClientUsingNewVersionOfLibrary( + datasetOutputPath, + incompatibility, + null, + Main.loggerMaven + ); + + Main.logger.info("Project ID: %s - OLD TEST FAILED: %s - NEW TEST FAILED: %s".formatted(incompatibility.id, + oldTestAssertedLogger.HasTestFailed(), + newTestAssertedLogger.HasTestFailed())); + + mapList.put( + incompatibility, + new CompSuiteRegressionResults( + !oldTestAssertedLogger.HasTestFailed(), + !newTestAssertedLogger.HasTestFailed() + ) + ); + } + + return mapList; + } + + public static void runOnDataset(String dataset, + String datasetOutput, + CompSuiteRunnable actionInterface, + String projectId) throws IOException, MavenInvocationException, InterruptedException { + + CompsuiteReproduction compsuiteReproduction = new CompsuiteReproduction(dataset); + CompSuiteIncompatibility[] incompatibilities = compsuiteReproduction.getIncompatibilities(); + Map mapList = new HashMap<>(); + List runResults = new ArrayList<>(); + + Path datasetOutputPath = Path.of(datasetOutput); + + for (CompSuiteIncompatibility incompatibility : incompatibilities) { + if (projectId != null && !projectId.equals(incompatibility.id)) { + continue; + } + + CompsuiteReproduction.printIncompatibility(incompatibility, Main.logger); + + RunResult runResult = new RunResult(); + runResult.ProjectId = incompatibility.id; + + Path oldDestinationProjectPath = CompsuiteReproduction.getIncompatibilityOldLibraryProjectPath(incompatibility, datasetOutputPath); + Path newDestinationProjectPath = CompsuiteReproduction.getIncompatibilityNewLibraryProjectPath(incompatibility, datasetOutputPath); + + Path incompatibilityFolderPath = datasetOutputPath.resolve(incompatibility.id); + String oldLibraryLocation = incompatibilityFolderPath.resolve("lib-old").toString(); + String newLibraryLocation = incompatibilityFolderPath.resolve("lib-new").toString(); + + if (!Files.exists(Path.of(oldLibraryLocation))) { + runResult.ProjectIsMissingLibrarySourceCode = true; + Main.logger.info("+++++++++++++++++++++++++ PROJECT HAS NO OLD LIB SRC: " + incompatibility.id + " ++++++++++++++++++++++++++++++++"); + runResults.add(runResult); + continue; + } + + if (!Files.exists(Path.of(newLibraryLocation))) { + runResult.ProjectIsMissingLibrarySourceCode = true; + Main.logger.info("+++++++++++++++++++++++++ PROJECT HAS NO NEW LIB SRC: " + incompatibility.id + " ++++++++++++++++++++++++++++++++"); + runResults.add(runResult); + continue; + } + + // Heap + if (incompatibility.id.equals("i-23")) { + runResult.ProjectCannotBeInstrumented = true; + Main.logger.info("+++++++++++++++++++++++++ PROJECT IS SKIPPED: " + incompatibility.id + " ++++++++++++++++++++++++++++++++"); + runResults.add(runResult); + continue; + } + + // Hangs and never finishes + if (incompatibility.id.equals("i-45")) { + runResult.ProjectCannotBeInstrumented = true; + Main.logger.info("+++++++++++++++++++++++++ PROJECT IS SKIPPED: " + incompatibility.id + " ++++++++++++++++++++++++++++++++"); + runResults.add(runResult); + continue; + } + + String testCmd = CompsuiteReproduction.getIncompatibilityTestCommand(incompatibility); + + TestAssertedLogger oldTestAssertedLogger = null; + TestAssertedLogger oldV2TestAssertedLogger = null; + TestAssertedLogger newTestAssertedLogger = null; + TestAssertedLogger newV2TestAssertedLogger = null; + + if (!Constants.SKIP_INSTRUMENTATION_RAN_ALREADY_FOR_DEBUGGING) { + + // Cleanup + FileUtils.deleteDirectoryIfExists(incompatibilityFolderPath.resolve("generated").toString()); + FileUtils.deleteDirectoryIfExists(incompatibilityFolderPath.resolve("old-generated").toString()); + + String oldJavaToolOptions = actionInterface.preRunOld(incompatibility, + oldDestinationProjectPath, + testCmd); + if (oldJavaToolOptions == null) { + continue; + } + + Main.logger.info("Testing old project"); + CompsuiteReproduction.printIncompatibility(incompatibility, Main.loggerMaven); + + oldTestAssertedLogger = CompsuiteReproduction.runTestOnClientUsingOldVersionOfLibrary( + datasetOutputPath, + incompatibility, + oldJavaToolOptions, + Main.loggerMaven + ); + } + + if (!Constants.SKIP_INSTRUMENTATION_RAN_ALREADY_FOR_DEBUGGING) { + + // Cleanup + FileUtils.deleteDirectoryIfExists(incompatibilityFolderPath.resolve("old-v2-generated").toString()); + + String oldJavaToolOptions = actionInterface.preRunOldV2(incompatibility, + oldDestinationProjectPath, + testCmd); + if (oldJavaToolOptions == null) { + continue; + } + + Main.logger.info("Testing old v2 project"); + CompsuiteReproduction.printIncompatibility(incompatibility, Main.loggerMaven); + + oldV2TestAssertedLogger = CompsuiteReproduction.runTestOnClientUsingOldVersionOfLibrary( + datasetOutputPath, + incompatibility, + oldJavaToolOptions, + Main.loggerMaven + ); + } + + if (!Constants.SKIP_INSTRUMENTATION_RAN_ALREADY_FOR_DEBUGGING) { + + // Cleanup + FileUtils.deleteDirectoryIfExists(incompatibilityFolderPath.resolve("new-generated").toString()); + + String newJavaToolOptions = actionInterface.preRunNew(incompatibility, + newDestinationProjectPath, + testCmd); + if (newJavaToolOptions == null) { + continue; + } + + Main.logger.info("Testing new project"); + CompsuiteReproduction.printIncompatibility(incompatibility, Main.loggerMaven); + + newTestAssertedLogger = CompsuiteReproduction.runTestOnClientUsingNewVersionOfLibrary( + datasetOutputPath, + incompatibility, + newJavaToolOptions, + Main.loggerMaven + ); + } + + if (!Constants.SKIP_INSTRUMENTATION_RAN_ALREADY_FOR_DEBUGGING) { + + // Cleanup + FileUtils.deleteDirectoryIfExists(incompatibilityFolderPath.resolve("new-v2-generated").toString()); + + String newV2JavaToolOptions = actionInterface.preRunNewV2(incompatibility, + newDestinationProjectPath, + testCmd); + if (newV2JavaToolOptions == null) { + continue; + } + + Main.logger.info("Testing new v2 project"); + CompsuiteReproduction.printIncompatibility(incompatibility, Main.loggerMaven); + + newV2TestAssertedLogger = CompsuiteReproduction.runTestOnClientUsingNewVersionOfLibrary( + datasetOutputPath, + incompatibility, + newV2JavaToolOptions, + Main.loggerMaven + ); + } + + actionInterface.postRunOld(incompatibility, + oldDestinationProjectPath, + oldTestAssertedLogger, + runResult); + + /*actionInterface.postRunNew(incompatibility, + newDestinationProjectPath, + newTestAssertedLogger, + runResult);*/ + + /*Main.logger.info("Project ID: %s - OLD TEST FAILED: %s - NEW TEST FAILED: %s".formatted(incompatibility.id, + oldTestAssertedLogger.HasTestFailed(), + newTestAssertedLogger.HasTestFailed())); + + mapList.put( + incompatibility, + new CompSuiteRegressionResults( + !oldTestAssertedLogger.HasTestFailed(), + !newTestAssertedLogger.HasTestFailed() + ) + );*/ + + runResults.add(runResult); + } + + Main.logger.info("ProjectId,ProjectIsMissingLibrarySourceCode,ProjectCannotBeReproduced,ProjectCannotBeInstrumented,ProjectFailedToLoadAgent,ProjectIsMissingTraceFiles,ProjectIsMissingSpecificTraces,ProjectFailedToGenerateAnyTraceTestSrc,ProjectFailedToCompileGeneratedTests,ProjectFailedToExecuteGeneratedTests,ProjectFailedToCompileGeneratedTestsWithNewVersion,ProjectFailedToExecuteGeneratedTestsWithNewVersion"); + for (RunResult runResult : runResults) { + Main.logger.info("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s".formatted( + runResult.ProjectId, + runResult.ProjectIsMissingLibrarySourceCode, + runResult.ProjectCannotBeReproduced, + runResult.ProjectCannotBeInstrumented, + runResult.ProjectFailedToLoadAgent, + runResult.ProjectIsMissingTraceFiles, + runResult.ProjectIsMissingSpecificTraces, + runResult.ProjectFailedToGenerateAnyTraceTestSrc, + runResult.ProjectFailedToCompileGeneratedTests, + runResult.ProjectFailedToExecuteGeneratedTests, + runResult.ProjectFailedToCompileGeneratedTestsWithNewVersion, + runResult.ProjectFailedToExecuteGeneratedTestsWithNewVersion)); + } + + int fineCount = 0; + + for (RunResult runResult : runResults) { + Main.logger.info("-----------------------------------"); + Main.logger.info("Id: %s".formatted(runResult.ProjectId)); + //Main.logger.info("IsMissingLibrarySourceCode: %s".formatted(runResult.ProjectIsMissingLibrarySourceCode)); + if (runResult.ProjectIsMissingLibrarySourceCode) + continue; + //Main.logger.info("CannotBeReproduced: %s".formatted(runResult.ProjectCannotBeReproduced)); + if (runResult.ProjectCannotBeReproduced) + continue; + //Main.logger.info("CannotBeInstrumented: %s".formatted(runResult.ProjectCannotBeInstrumented)); + if (runResult.ProjectCannotBeInstrumented) + continue; + //Main.logger.info("FailedToLoadAgent: %s".formatted(runResult.ProjectFailedToLoadAgent)); + if (runResult.ProjectFailedToLoadAgent) + continue; + //Main.logger.info("IsMissingTraceFiles: %s".formatted(runResult.ProjectIsMissingTraceFiles)); + if (runResult.ProjectIsMissingTraceFiles) + continue; + Main.logger.info("Project is fine."); + fineCount++; + /*Main.logger.info("IsMissingSpecificTraces: %s".formatted(runResult.ProjectIsMissingSpecificTraces)); + Main.logger.info("FailedToGenerateAnyTraceTestSrc: %s".formatted(runResult.ProjectFailedToGenerateAnyTraceTestSrc)); + if (runResult.ProjectFailedToGenerateAnyTraceTestSrc) + continue; + + Main.logger.info("FailedToCompileGeneratedTests: %s".formatted(runResult.ProjectFailedToCompileGeneratedTests)); + if (runResult.ProjectFailedToCompileGeneratedTests) + continue; + + Main.logger.info("FailedToExecuteGeneratedTests: %s".formatted(runResult.ProjectFailedToExecuteGeneratedTests)); + Main.logger.info("FailedToCompileGeneratedTestsWithNewVersion: %s".formatted(runResult.ProjectFailedToCompileGeneratedTestsWithNewVersion)); + Main.logger.info("FailedToExecuteGeneratedTestsWithNewVersion: %s".formatted(runResult.ProjectFailedToExecuteGeneratedTestsWithNewVersion)); + Main.logger.info("TestExecutionReportWithOldVersion:"); + + if (runResult.TestExecutionReportWithOldVersion != null) { + for (ReportItem item : runResult.TestExecutionReportWithOldVersion) { + Main.logger.info("\"%s\",\"%s\",\"%s\",\"%s\"".formatted(item.Name, item.Version, item.getTestName(), item.getTestResult())); + } + } + + Main.logger.info("TestExecutionReportWithNewVersion:"); + if (runResult.TestExecutionReportWithNewVersion != null) { + for (ReportItem item : runResult.TestExecutionReportWithNewVersion) { + Main.logger.info("\"%s\",\"%s\",\"%s\",\"%s\"".formatted(item.Name, item.Version, item.getTestName(), item.getTestResult())); + } + }*/ + + Main.logger.info("-----------------------------------"); + } + + Main.logger.info("Number of validated fine projects: %d".formatted(fineCount)); + } + + private static String installInstrumentationAgentOntoProject( + Path GilesiRepositoryLocationPath, + Path libraryLocationPath, + Path clientLocationPath, + Path generatedFolderPath + ) throws IOException, InterruptedException { + + FileUtils.deleteDirectoryIfExists(generatedFolderPath.toString()); + + Path libraryConfig = libraryLocationPath.resolve(Constants.targetConfigurationName); + + // Run ConfGen on Library first + // TODO: Check how meta projects get handled here exactly... + ConfGen.runConfGen(GilesiRepositoryLocationPath.toString(), + libraryConfig.toString(), + generatedFolderPath.toString(), + libraryLocationPath.toString()); + + Path InstrumentationAgentEffectiveLocation = GilesiRepositoryLocationPath.resolve(Constants.INSTRUMENTATION_LOCATION); + + // Modify Client Build System to use the instrumentation agent + ArrayList clientProjectPomFiles = FileUtils.enumerateFiles(clientLocationPath.toString(), + "pom.xml", + true); + ArrayList clientProjectGradleBuildFiles = FileUtils.enumerateFiles(clientLocationPath.toString(), + "build.gradle", + true); + + ArrayList allProjectFiles = new ArrayList<>(); + allProjectFiles.addAll(clientProjectPomFiles); + allProjectFiles.addAll(clientProjectGradleBuildFiles); + + Orchestrator.copyInstrumentationToolAndConfiguration(allProjectFiles, + InstrumentationAgentEffectiveLocation, + libraryConfig); + + String javaToolOptions = "-javaagent:%s=%s".formatted(InstrumentationAgentEffectiveLocation, + libraryConfig.toString()); + + Orchestrator.orchestrateGradleProjects(clientProjectGradleBuildFiles); + + return javaToolOptions; + } + + public static void runOnDataset(String GilesiRepositoryLocation, + String dataset, + String datasetOutput, + String projectToRunOn) throws IOException, MavenInvocationException, InterruptedException { + Path datasetOutputPath = Path.of(datasetOutput); + + runOnDataset(dataset, datasetOutput, new CompSuiteRunnable() { + @Override + public String preRunOld(CompSuiteIncompatibility incompatibility, + Path oldDestinationProjectPath, + String testCmd) throws IOException, InterruptedException { + Path incompatibilityFolderPath = datasetOutputPath.resolve(incompatibility.id); + + CompsuiteReproduction.printIncompatibility(incompatibility, Main.loggerConfGen); + + return installInstrumentationAgentOntoProject( + Path.of(GilesiRepositoryLocation), + incompatibilityFolderPath.resolve("lib-old"), + oldDestinationProjectPath, + incompatibilityFolderPath.resolve("old-generated") + ); + + //return null; + } + + @Override + public String preRunOldV2(CompSuiteIncompatibility incompatibility, + Path oldDestinationProjectPath, + String testCmd) throws IOException, InterruptedException { + Path incompatibilityFolderPath = datasetOutputPath.resolve(incompatibility.id); + + CompsuiteReproduction.printIncompatibility(incompatibility, Main.loggerConfGen); + + return installInstrumentationAgentOntoProject( + Path.of(GilesiRepositoryLocation), + incompatibilityFolderPath.resolve("lib-old"), + oldDestinationProjectPath, + incompatibilityFolderPath.resolve("old-v2-generated") + ); + + //return null; + } + + @Override + public String preRunNew(CompSuiteIncompatibility incompatibility, + Path newDestinationProjectPath, + String testCmd) throws IOException, InterruptedException { + Path incompatibilityFolderPath = datasetOutputPath.resolve(incompatibility.id); + + CompsuiteReproduction.printIncompatibility(incompatibility, Main.loggerConfGen); + + return installInstrumentationAgentOntoProject( + Path.of(GilesiRepositoryLocation), + incompatibilityFolderPath.resolve("lib-new"), + newDestinationProjectPath, + incompatibilityFolderPath.resolve("new-generated") + ); + + /*Path incompatibilityFolderPath = datasetOutputPath.resolve(incompatibility.id); + + CompsuiteReproduction.printIncompatibility(incompatibility, Main.loggerConfGen); + + return installInstrumentationAgentOntoProject( + Path.of(GilesiRepositoryLocation), + incompatibilityFolderPath.resolve("lib-new"), + newDestinationProjectPath, + incompatibilityFolderPath.resolve("new-v2-generated") + );*/ + + //return null; + } + + @Override + public String preRunNewV2(CompSuiteIncompatibility incompatibility, + Path newDestinationProjectPath, + String testCmd) throws IOException, InterruptedException { + Path incompatibilityFolderPath = datasetOutputPath.resolve(incompatibility.id); + + CompsuiteReproduction.printIncompatibility(incompatibility, Main.loggerConfGen); + + return installInstrumentationAgentOntoProject( + Path.of(GilesiRepositoryLocation), + incompatibilityFolderPath.resolve("lib-new"), + newDestinationProjectPath, + incompatibilityFolderPath.resolve("new-v2-generated") + ); + + //return null; + } + + @Override + public void postRunOld(CompSuiteIncompatibility incompatibility, + Path oldDestinationProjectPath, + TestAssertedLogger oldTestAssertedLogger, + RunResult runResult) throws IOException, InterruptedException { + Path incompatibilityFolderPath = datasetOutputPath.resolve(incompatibility.id); + + Path generatedOlbLibVersionFolderPath = incompatibilityFolderPath.resolve("old-generated"); + + Path generatedOldLibVersionTracesFolderPath = generatedOlbLibVersionFolderPath.resolve("traces"); + Path generatedOldLibMarkersFolderPath = generatedOlbLibVersionFolderPath.resolve("markers"); + Path generatedOldLibLogsFolderPath = generatedOlbLibVersionFolderPath.resolve("logs"); + + Path generatedOlbV2LibVersionFolderPath = incompatibilityFolderPath.resolve("old-v2-generated"); + + Path generatedOldV2LibVersionTracesFolderPath = generatedOlbV2LibVersionFolderPath.resolve("traces"); + Path generatedOldV2LibMarkersFolderPath = generatedOlbV2LibVersionFolderPath.resolve("markers"); + Path generatedOldV2LibLogsFolderPath = generatedOlbV2LibVersionFolderPath.resolve("logs"); + + Path generateNewLibVersionFolderPath = incompatibilityFolderPath.resolve("new-generated"); + + Path generatedNewLibVersionTracesFolderPath = generateNewLibVersionFolderPath.resolve("traces"); + Path generatedNewLibMarkersFolderPath = generateNewLibVersionFolderPath.resolve("markers"); + Path generatedNewLibLogsFolderPath = generateNewLibVersionFolderPath.resolve("logs"); + + Path generateNewV2LibVersionFolderPath = incompatibilityFolderPath.resolve("new-v2-generated"); + + Path generatedNewV2LibVersionTracesFolderPath = generateNewV2LibVersionFolderPath.resolve("traces"); + Path generatedNewV2LibMarkersFolderPath = generateNewV2LibVersionFolderPath.resolve("markers"); + Path generatedNewV2LibLogsFolderPath = generateNewV2LibVersionFolderPath.resolve("logs"); + + ArrayList generatedOldLibLogFiles = FileUtils.enumerateFiles(generatedOldLibLogsFolderPath.toString(), + ".*\\.log", + true); + ArrayList generatedOldV2LibLogFiles = FileUtils.enumerateFiles(generatedOldV2LibLogsFolderPath.toString(), + ".*\\.log", + true); + ArrayList generatedNewLibLogFiles = FileUtils.enumerateFiles(generatedNewLibLogsFolderPath.toString(), + ".*\\.log", + true); + ArrayList generatedNewV2LibLogFiles = FileUtils.enumerateFiles(generatedNewV2LibLogsFolderPath.toString(), + ".*\\.log", + true); + + CompsuiteReproduction.printIncompatibility(incompatibility, Main.loggerAgent); + + for (String file : generatedOldLibLogFiles) { + List fileLines = Files.readAllLines(Path.of(file)); + for (String line : fileLines) { + Main.loggerAgent.info("[%s-agent] %s".formatted("old-%s".formatted(incompatibility.id), line)); + } + } + + for (String file : generatedOldV2LibLogFiles) { + List fileLines = Files.readAllLines(Path.of(file)); + for (String line : fileLines) { + Main.loggerAgent.info("[%s-agent] %s".formatted("old-v2-%s".formatted(incompatibility.id), line)); + } + } + + for (String file : generatedNewLibLogFiles) { + List fileLines = Files.readAllLines(Path.of(file)); + for (String line : fileLines) { + Main.loggerAgent.info("[%s-agent] %s".formatted("new-%s".formatted(incompatibility.id), line)); + } + } + + for (String file : generatedNewV2LibLogFiles) { + List fileLines = Files.readAllLines(Path.of(file)); + for (String line : fileLines) { + Main.loggerAgent.info("[%s-agent] %s".formatted("new-v2-%s".formatted(incompatibility.id), line)); + } + } + + if (!Files.exists(generatedOldLibMarkersFolderPath.resolve(GILESI_INSTRUMENTATION_JAR_LOADED_MARKER_FILE_NAME))) { + runResult.ProjectFailedToLoadAgent = true; + Main.logger.info("+++++++++++++++++++++++++ OLD PROJECT FAILED TO LOAD OUR AGENT: " + incompatibility.id + " ++++++++++++++++++++++++++++++++"); + return; + } + + if (!Files.exists(generatedOldV2LibMarkersFolderPath.resolve(GILESI_INSTRUMENTATION_JAR_LOADED_MARKER_FILE_NAME))) { + runResult.ProjectFailedToLoadAgent = true; + Main.logger.info("+++++++++++++++++++++++++ OLD V2 PROJECT FAILED TO LOAD OUR AGENT: " + incompatibility.id + " ++++++++++++++++++++++++++++++++"); + return; + } + + if (!Files.exists(generatedNewLibMarkersFolderPath.resolve(GILESI_INSTRUMENTATION_JAR_LOADED_MARKER_FILE_NAME))) { + runResult.ProjectFailedToLoadAgent = true; + Main.logger.info("+++++++++++++++++++++++++ NEW PROJECT FAILED TO LOAD OUR AGENT: " + incompatibility.id + " ++++++++++++++++++++++++++++++++"); + return; + } + + if (!Files.exists(generatedNewV2LibMarkersFolderPath.resolve(GILESI_INSTRUMENTATION_JAR_LOADED_MARKER_FILE_NAME))) { + runResult.ProjectFailedToLoadAgent = true; + Main.logger.info("+++++++++++++++++++++++++ NEW V2 PROJECT FAILED TO LOAD OUR AGENT: " + incompatibility.id + " ++++++++++++++++++++++++++++++++"); + return; + } + + boolean generatedTestTraces = !FileUtils.enumerateFiles(generatedOldLibVersionTracesFolderPath.toString(), + ".*\\.json", + true).isEmpty(); + if (!generatedTestTraces) { + runResult.ProjectIsMissingTraceFiles = true; + Main.logger.info("+++++++++++++++++++++++++ PROJECT FAILED TO GENERATE OLD LIB VERSION TEST TRACES: " + incompatibility.id + " ++++++++++++++++++++++++++++++++"); + return; + } + + generatedTestTraces = !FileUtils.enumerateFiles(generatedOldV2LibVersionTracesFolderPath.toString(), + ".*\\.json", + true).isEmpty(); + if (!generatedTestTraces) { + runResult.ProjectIsMissingTraceFiles = true; + Main.logger.info("+++++++++++++++++++++++++ PROJECT FAILED TO GENERATE OLD V2 LIB VERSION TEST TRACES: " + incompatibility.id + " ++++++++++++++++++++++++++++++++"); + return; + } + + generatedTestTraces = !FileUtils.enumerateFiles(generatedNewLibVersionTracesFolderPath.toString(), + ".*\\.json", + true).isEmpty(); + if (!generatedTestTraces) { + runResult.ProjectIsMissingTraceFiles = true; + Main.logger.info("+++++++++++++++++++++++++ PROJECT FAILED TO GENERATE NEW LIB VERSION TEST TRACES: " + incompatibility.id + " ++++++++++++++++++++++++++++++++"); + return; + } + + generatedTestTraces = !FileUtils.enumerateFiles(generatedNewV2LibVersionTracesFolderPath.toString(), + ".*\\.json", + true).isEmpty(); + if (!generatedTestTraces) { + runResult.ProjectIsMissingTraceFiles = true; + Main.logger.info("+++++++++++++++++++++++++ PROJECT FAILED TO GENERATE NEW V2 LIB VERSION TEST TRACES: " + incompatibility.id + " ++++++++++++++++++++++++++++++++"); + return; + } + + String oldLibraryLocation = incompatibilityFolderPath.resolve("lib-old").toString(); + Path oldLibraryLocationPath = Path.of(oldLibraryLocation); + Path oldLibraryConfig = oldLibraryLocationPath.resolve(Constants.targetConfigurationName); + + CompsuiteReproduction.printIncompatibility(incompatibility, Main.loggerTraceDiff); + + /*TestGen.runTraceDiff(GilesiRepositoryLocation, + generatedOldLibVersionTracesFolderPath.toString(), + generatedNewLibVersionTracesFolderPath.toString(), + oldLibraryConfig.toString());*/ + + TestGen.runTraceDiff(GilesiRepositoryLocation, + generatedOldLibVersionTracesFolderPath.toString(), + generatedOldV2LibVersionTracesFolderPath.toString(), + oldLibraryConfig.toString()); + + TestGen.runTraceDiff(GilesiRepositoryLocation, + generatedNewLibVersionTracesFolderPath.toString(), + generatedNewV2LibVersionTracesFolderPath.toString(), + oldLibraryConfig.toString()); + + CompsuiteReproduction.printIncompatibility(incompatibility, Main.loggerTraceGen); + } + + @Override + public void postRunNew(CompSuiteIncompatibility incompatibility, + Path newDestinationProjectPath, + TestAssertedLogger newTestAssertedLogger, + RunResult runResult) throws IOException, InterruptedException { + + } + }, projectToRunOn); + } +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/compsuite/CompSuiteIncompatibility.java b/Maestro/src/main/java/com/github/gilesi/maestro/compsuite/CompSuiteIncompatibility.java new file mode 100644 index 00000000..b29b15a9 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/compsuite/CompSuiteIncompatibility.java @@ -0,0 +1,17 @@ +package com.github.gilesi.maestro.compsuite; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class CompSuiteIncompatibility { + public String id; + public String client; + public String url; + public String sha; + public String lib; + public String old; + @JsonProperty("new") + public String _new; + public String test; + public String submodule; + public String test_cmd; +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/compsuite/CompSuiteRegressionResults.java b/Maestro/src/main/java/com/github/gilesi/maestro/compsuite/CompSuiteRegressionResults.java new file mode 100644 index 00000000..65fd3a62 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/compsuite/CompSuiteRegressionResults.java @@ -0,0 +1,23 @@ +package com.github.gilesi.maestro.compsuite; + +public class CompSuiteRegressionResults { + public boolean oldPassed; + public boolean newPassed; + + public CompSuiteRegressionResults(boolean oldPassed, boolean newPassed) { + this.oldPassed = oldPassed; + this.newPassed = newPassed; + } + + public boolean isNewPassed() { + return newPassed; + } + + public boolean isOldPassed() { + return oldPassed; + } + + public boolean isRegressionReproduced() { + return oldPassed && !newPassed; + } +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/compsuite/CompSuiteRunnable.java b/Maestro/src/main/java/com/github/gilesi/maestro/compsuite/CompSuiteRunnable.java new file mode 100644 index 00000000..4e5abd10 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/compsuite/CompSuiteRunnable.java @@ -0,0 +1,31 @@ +package com.github.gilesi.maestro.compsuite; + +import com.github.gilesi.maestro.RunResult; +import com.github.gilesi.maestro.runners.maven.TestAssertedLogger; + +import java.io.IOException; +import java.nio.file.Path; + +public interface CompSuiteRunnable { + String preRunOld(CompSuiteIncompatibility incompatibility, + Path oldDestinationProjectPath, + String testCmd) throws IOException, InterruptedException; + String preRunOldV2(CompSuiteIncompatibility incompatibility, + Path oldDestinationProjectPath, + String testCmd) throws IOException, InterruptedException; + void postRunOld(CompSuiteIncompatibility incompatibility, + Path oldDestinationProjectPath, + TestAssertedLogger oldTestAssertedLogger, + RunResult runResult) throws IOException, InterruptedException; + + String preRunNew(CompSuiteIncompatibility incompatibility, + Path newDestinationProjectPath, + String testCmd) throws IOException, InterruptedException; + String preRunNewV2(CompSuiteIncompatibility incompatibility, + Path newDestinationProjectPath, + String testCmd) throws IOException, InterruptedException; + void postRunNew(CompSuiteIncompatibility incompatibility, + Path newDestinationProjectPath, + TestAssertedLogger newTestAssertedLogger, + RunResult runResult) throws IOException, InterruptedException; +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/compsuite/CompsuiteReproduction.java b/Maestro/src/main/java/com/github/gilesi/maestro/compsuite/CompsuiteReproduction.java new file mode 100644 index 00000000..86db6541 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/compsuite/CompsuiteReproduction.java @@ -0,0 +1,270 @@ +package com.github.gilesi.maestro.compsuite; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; + +import org.apache.maven.shared.invoker.MavenInvocationException; + +import com.fasterxml.jackson.core.exc.StreamReadException; +import com.fasterxml.jackson.databind.DatabindException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.gilesi.maestro.Constants; +import com.github.gilesi.maestro.FileUtils; +import com.github.gilesi.maestro.Main; +import com.github.gilesi.maestro.runners.maven.Log4JLogger; +import com.github.gilesi.maestro.runners.maven.ProjectRunner; +import com.github.gilesi.maestro.runners.maven.TestAssertedLogger; + +public class CompsuiteReproduction { + + private static String LIBRARY_OLD_VERSION_FOLDER_NAME = "old"; + private static String LIBRARY_NEW_VERSION_FOLDER_NAME = "new"; + + private CompSuiteIncompatibility[] incompatibilities; + + public CompsuiteReproduction(String dataset) throws StreamReadException, DatabindException, IOException { + incompatibilities = new ObjectMapper().readValue(new File(dataset), CompSuiteIncompatibility[].class); + } + + public CompSuiteIncompatibility[] getIncompatibilities() { + return incompatibilities; + } + + public static void printIncompatibility(CompSuiteIncompatibility incompatibility, org.apache.logging.log4j.Logger logger) { + logger.info("-----------------------------------"); + logger.info("id: %s".formatted(incompatibility.id)); + logger.info("client: %s".formatted(incompatibility.client)); + logger.info("url: %s".formatted(incompatibility.url)); + logger.info("sha: %s".formatted(incompatibility.sha)); + logger.info("lib: %s".formatted(incompatibility.lib)); + logger.info("old: %s".formatted(incompatibility.old)); + logger.info("new: %s".formatted(incompatibility._new)); + logger.info("test: %s".formatted(incompatibility.test)); + logger.info("submodule: %s".formatted(incompatibility.submodule)); + logger.info("test_cmd: %s".formatted(incompatibility.test_cmd)); + logger.info("-----------------------------------"); + } + + public void cloneDataset(String datasetOutput) throws IOException, InterruptedException { + FileUtils.deleteDirectoryIfExists(datasetOutput); + Path datasetOutputPath = Path.of(datasetOutput); + Files.createDirectories(datasetOutputPath); + + for (CompSuiteIncompatibility incompatibility : incompatibilities) { + printIncompatibility(incompatibility, Main.logger); + + Path oldDestinationProjectPath = datasetOutputPath.resolve(incompatibility.id).resolve(LIBRARY_OLD_VERSION_FOLDER_NAME); + FileUtils.deleteDirectoryIfExists(oldDestinationProjectPath.toString()); + Files.createDirectories(oldDestinationProjectPath); + + Main.logger.info("Cloning old at: %s".formatted(oldDestinationProjectPath)); + FetchGit.cloneProject(incompatibility.url, incompatibility.sha, oldDestinationProjectPath); + + Path newDestinationProjectPath = datasetOutputPath.resolve(incompatibility.id).resolve(LIBRARY_NEW_VERSION_FOLDER_NAME); + FileUtils.deleteDirectoryIfExists(newDestinationProjectPath.toString()); + Files.createDirectories(newDestinationProjectPath); + + Main.logger.info("Cloning new at: %s".formatted(newDestinationProjectPath)); + FetchGit.cloneProject( + incompatibility.url, + "tags/%s-%s" + .formatted( + incompatibility.lib + .replace(":", "--"), + incompatibility._new + ), + newDestinationProjectPath + ); + } + } + + public static String getIncompatibilityTestCommand(CompSuiteIncompatibility incompatibility) { + String testCmd = "test -fn -Drat.ignoreErrors=true -DtrimStackTrace=false -DfailIfNoTests=false -Dtest=%s" + .formatted(incompatibility.test); + + if (!incompatibility.test_cmd.equals("N/A")) { + testCmd = incompatibility.test_cmd; + + if (testCmd.startsWith("mvn ")) { + testCmd = testCmd.substring(4); + } + } + + return testCmd; + } + + private static Path getIncompatibilityLibraryProjectPath( + CompSuiteIncompatibility incompatibility, + Path datasetOutputPath, + String projectFolder + ) { + Path destinationProjectPath = datasetOutputPath.resolve(incompatibility.id).resolve(projectFolder); + + if (!incompatibility.submodule.equals("N/A")) { + destinationProjectPath = destinationProjectPath.resolve(incompatibility.submodule); + } + + return destinationProjectPath; + } + + public static Path getIncompatibilityOldLibraryProjectPath( + CompSuiteIncompatibility incompatibility, + Path datasetOutputPath + ) { + return getIncompatibilityLibraryProjectPath(incompatibility, datasetOutputPath, LIBRARY_OLD_VERSION_FOLDER_NAME); + } + + public static Path getIncompatibilityNewLibraryProjectPath( + CompSuiteIncompatibility incompatibility, + Path datasetOutputPath + ) { + return getIncompatibilityLibraryProjectPath(incompatibility, datasetOutputPath, LIBRARY_NEW_VERSION_FOLDER_NAME); + } + + private static TestAssertedLogger runTestOnClient( + Path datasetOutputPath, + String projectFolder, + CompSuiteIncompatibility incompatibility, + String additionalJavaToolParameters, + org.apache.logging.log4j.Logger logger + ) throws MavenInvocationException { + + Path destinationProjectPath = getIncompatibilityLibraryProjectPath(incompatibility, datasetOutputPath, projectFolder); + String testCmd = getIncompatibilityTestCommand(incompatibility); + + TestAssertedLogger testAssertedLogger = new TestAssertedLogger( + new Log4JLogger( + logger, + org.apache.maven.shared.invoker.InvokerLogger.INFO, + "%s-%s".formatted(projectFolder, incompatibility.id) + ) + ); + + ProjectRunner.runMavenGoalOnRepository(destinationProjectPath.toString(), + testCmd, + null, + "1.8", + testAssertedLogger, + additionalJavaToolParameters); + + return testAssertedLogger; + } + + public static TestAssertedLogger runTestOnClientUsingOldVersionOfLibrary( + Path datasetOutputPath, + CompSuiteIncompatibility incompatibility, + String additionalJavaToolParameters, + org.apache.logging.log4j.Logger logger + ) throws MavenInvocationException { + return runTestOnClient(datasetOutputPath, LIBRARY_OLD_VERSION_FOLDER_NAME, incompatibility, additionalJavaToolParameters, logger); + } + + public static TestAssertedLogger runTestOnClientUsingNewVersionOfLibrary( + Path datasetOutputPath, + CompSuiteIncompatibility incompatibility, + String additionalJavaToolParameters, + org.apache.logging.log4j.Logger logger + ) throws MavenInvocationException { + return runTestOnClient(datasetOutputPath, LIBRARY_NEW_VERSION_FOLDER_NAME, incompatibility, additionalJavaToolParameters, logger); + } + + public void copySourcesForDataset(Path datasetOutputPath) throws IOException, MavenInvocationException { + copyOldSourcesForDataset(datasetOutputPath, Path.of(Constants.M2_REPOSITORY)); + copyNewSourcesForDataset(datasetOutputPath, Path.of(Constants.M2_REPOSITORY)); + } + + private void copyOldSourcesForDataset(Path datasetOutputPath, Path m2RepositoryPath) throws IOException, MavenInvocationException { + for (CompSuiteIncompatibility incompatibility : incompatibilities) { + String[] pathComponents = incompatibility.lib.split(":")[0].split("\\."); + String secondaryPathComponent = incompatibility.lib.split(":")[1]; + String tiertiaryPathComponent = incompatibility.old; + + Path fileSystemRepoPath = m2RepositoryPath; + + for (String pathComponent : pathComponents) { + fileSystemRepoPath = fileSystemRepoPath.resolve(pathComponent); + } + + fileSystemRepoPath = fileSystemRepoPath.resolve(secondaryPathComponent).resolve(tiertiaryPathComponent); + + printIncompatibility(incompatibility, Main.logger); + + ArrayList srcCandidates = FileUtils.enumerateFiles(fileSystemRepoPath.toString(), ".*sources\\.jar", false); + + if (srcCandidates.size() == 0) { + Main.logger.info("%s Missing sources for lib, aborting!".formatted(incompatibility.id)); + continue; + } + + Path oldDestinationProjectPath = datasetOutputPath.resolve(incompatibility.id).resolve("lib-old"); + FileUtils.deleteDirectoryIfExists(oldDestinationProjectPath.toString()); + Files.createDirectories(oldDestinationProjectPath); + + for (String cand : srcCandidates) { + System.out.println("Extracting " + cand); + extractJar(cand, oldDestinationProjectPath.toString()); + } + } + } + + private void copyNewSourcesForDataset(Path datasetOutputPath, Path m2RepositoryPath) throws IOException, MavenInvocationException { + for (CompSuiteIncompatibility incompatibility : incompatibilities) { + String[] pathComponents = incompatibility.lib.split(":")[0].split("\\."); + String secondaryPathComponent = incompatibility.lib.split(":")[1]; + String tiertiaryPathComponent = incompatibility._new; + + Path fileSystemRepoPath = m2RepositoryPath; + + for (String pathComponent : pathComponents) { + fileSystemRepoPath = fileSystemRepoPath.resolve(pathComponent); + } + + fileSystemRepoPath = fileSystemRepoPath.resolve(secondaryPathComponent).resolve(tiertiaryPathComponent); + + printIncompatibility(incompatibility, Main.logger); + + ArrayList srcCandidates = FileUtils.enumerateFiles(fileSystemRepoPath.toString(), ".*sources\\.jar", false); + + if (srcCandidates.size() == 0) { + Main.logger.info("%s Missing sources for lib, aborting!".formatted(incompatibility.id)); + continue; + } + + Path newDestinationProjectPath = datasetOutputPath.resolve(incompatibility.id).resolve("lib-new"); + FileUtils.deleteDirectoryIfExists(newDestinationProjectPath.toString()); + Files.createDirectories(newDestinationProjectPath); + + for (String cand : srcCandidates) { + System.out.println("Extracting " + cand); + extractJar(cand, newDestinationProjectPath.toString()); + } + } + } + + private static void extractJar(String jar, String destdir) throws java.io.IOException { + java.util.jar.JarFile jarfile = new java.util.jar.JarFile(new java.io.File(jar)); + java.util.Enumeration enu = jarfile.entries(); + while (enu.hasMoreElements()) { + java.util.jar.JarEntry je = enu.nextElement(); + + java.io.File fl = new java.io.File(destdir, je.getName()); + if (!fl.exists()) { + fl.getParentFile().mkdirs(); + fl = new java.io.File(destdir, je.getName()); + } + if (je.isDirectory()) { + continue; + } + java.io.InputStream is = jarfile.getInputStream(je); + java.io.FileOutputStream fo = new java.io.FileOutputStream(fl); + while (is.available() > 0) { + fo.write(is.read()); + } + fo.close(); + is.close(); + } + } +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/compsuite/FetchGit.java b/Maestro/src/main/java/com/github/gilesi/maestro/compsuite/FetchGit.java new file mode 100644 index 00000000..801f4ac3 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/compsuite/FetchGit.java @@ -0,0 +1,67 @@ +package com.github.gilesi.maestro.compsuite; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; + +public class FetchGit { + private static String[] getGitCloneCommands(String repoUrl, String hash) { + return new String[]{ + "git init", + "git remote add origin " + repoUrl, + "git fetch --depth 1 origin " + hash, + "git reset --hard FETCH_HEAD", + "git submodule init", + "git submodule update" + }; + } + + // Needed for some compsuite projects + // TODO: say which ones right here... + private static String[] getGitFullCloneCommands(String repoUrl, String hash) { + return new String[]{ + "git clone %s .".formatted(repoUrl), + "git checkout %s".formatted(hash), + "git submodule init", + "git submodule update" + }; + } + + private static boolean deleteDirectory(File fi) { + boolean globalResult = true; + + for (File file : Objects.requireNonNull(fi.listFiles())) { + if (file.isDirectory()) { + deleteDirectory(file); + } + if (!file.delete()) { + globalResult = false; + } + } + if (!fi.delete()) { + globalResult = false; + } + + return globalResult; + } + + public static void cloneProject(String cloneUrl, String commit, Path repositoryDestination) throws IOException, InterruptedException { + String[] commands = getGitFullCloneCommands(cloneUrl, commit); + + if (Files.exists(repositoryDestination)) { + if (deleteDirectory(new File(repositoryDestination.toString()))) { + Files.createDirectory(repositoryDestination); + } + } else { + Files.createDirectory(repositoryDestination); + } + + for (String cmd : commands) { + File fi = new File(repositoryDestination.toString()); + Process p1 = Runtime.getRuntime().exec(cmd.split(" "), null, fi); + p1.waitFor(); + } + } +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/runners/maven/ConsoleLogger.java b/Maestro/src/main/java/com/github/gilesi/maestro/runners/maven/ConsoleLogger.java new file mode 100644 index 00000000..59fd4e85 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/runners/maven/ConsoleLogger.java @@ -0,0 +1,248 @@ +package com.github.gilesi.maestro.runners.maven; + +/* + * 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.shared.invoker.InvocationOutputHandler; +import org.apache.maven.shared.invoker.InvokerLogger; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Date; + +/** + * Offers a logger that writes to a print stream like {@link java.lang.System#out}. + * + * @since 2.0.9 + */ +public class ConsoleLogger implements InvokerLogger, InvocationOutputHandler { + + /** + * The threshold used to filter messages. + */ + private int threshold; + + /** + * Creates a new logger that writes to {@link java.lang.System#out} and has a threshold of {@link #INFO}. + */ + public ConsoleLogger() { + this(INFO); + } + + /** + * Creates a new logger that writes to the specified print stream. + * + * @param threshold The threshold for the logger. + */ + public ConsoleLogger(int threshold) { + setThreshold(threshold); + } + + /** + * Writes the specified message and exception to the print stream. + * + * @param level The priority level of the message. + * @param message The message to log, may be null. + * @param error The exception to log, may be null. + */ + private void log(int level, String message, Throwable error) { + if (level > threshold) { + // don't log when it doesn't match your threshold. + return; + } + + if (message == null && error == null) { + // don't log when there's nothing to log. + return; + } + + StringBuilder buffer = new StringBuilder(); + String logLevel = "INFO"; + + switch (level) { + case (DEBUG) -> logLevel = "DEBUG"; + case (INFO) -> logLevel = "INFO"; + case (WARN) -> logLevel = "WARN"; + case (ERROR) -> logLevel = "ERROR"; + case (FATAL) -> logLevel = "FATAL"; + default -> { + } + } + + buffer.append("[Maven] "); + + if (message != null) { + buffer.append(message); + } + + if (error != null) { + StringWriter writer = new StringWriter(); + PrintWriter pWriter = new PrintWriter(writer); + + error.printStackTrace(pWriter); + + if (message != null) { + buffer.append('\n'); + } + + buffer.append("Error:\n"); + buffer.append(writer); + } + + System.out.printf("[%s] [%s] %s%n", new Date(), logLevel, buffer); + } + + /** + * {@inheritDoc} + */ + public void debug(String message) { + log(DEBUG, message, null); + } + + /** + * {@inheritDoc} + */ + public void debug(String message, Throwable throwable) { + log(DEBUG, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void info(String message) { + log(INFO, message, null); + } + + /** + * {@inheritDoc} + */ + public void info(String message, Throwable throwable) { + log(INFO, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void warn(String message) { + log(WARN, message, null); + } + + /** + * {@inheritDoc} + */ + public void warn(String message, Throwable throwable) { + log(WARN, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void error(String message) { + log(ERROR, message, null); + } + + /** + * {@inheritDoc} + */ + public void error(String message, Throwable throwable) { + log(ERROR, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void fatalError(String message) { + log(FATAL, message, null); + } + + /** + * {@inheritDoc} + */ + public void fatalError(String message, Throwable throwable) { + log(FATAL, message, throwable); + } + + /** + *

isDebugEnabled.

+ * + * @return a boolean. + */ + public boolean isDebugEnabled() { + return threshold >= DEBUG; + } + + /** + *

isErrorEnabled.

+ * + * @return a boolean. + */ + public boolean isErrorEnabled() { + return threshold >= ERROR; + } + + /** + *

isFatalErrorEnabled.

+ * + * @return a boolean. + */ + public boolean isFatalErrorEnabled() { + return threshold >= FATAL; + } + + /** + *

isInfoEnabled.

+ * + * @return a boolean. + */ + public boolean isInfoEnabled() { + return threshold >= INFO; + } + + /** + *

isWarnEnabled.

+ * + * @return a boolean. + */ + public boolean isWarnEnabled() { + return threshold >= WARN; + } + + /** + *

Getter for the field threshold.

+ * + * @return an int. + */ + public int getThreshold() { + return threshold; + } + + /** + * {@inheritDoc} + */ + public void setThreshold(int threshold) { + this.threshold = threshold; + } + + /** + * {@inheritDoc} + */ + public void consumeLine(String line) { + log(INFO, line, null); + } +} \ No newline at end of file diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/runners/maven/Log4JLogger.java b/Maestro/src/main/java/com/github/gilesi/maestro/runners/maven/Log4JLogger.java new file mode 100644 index 00000000..c3881aaa --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/runners/maven/Log4JLogger.java @@ -0,0 +1,271 @@ +package com.github.gilesi.maestro.runners.maven; + +/* + * 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.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.maven.shared.invoker.InvocationOutputHandler; +import org.apache.maven.shared.invoker.InvokerLogger; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * Offers a logger that writes to a print stream like {@link java.lang.System#out}. + * + * @since 2.0.9 + */ +public class Log4JLogger implements InvokerLogger, InvocationOutputHandler { + + /** + * The print stream to write to, never null. + */ + private final Logger out; + + /** + * The threshold used to filter messages. + */ + private int threshold; + + private String prefix; + + /** + * Creates a new logger that writes to {@link java.lang.System#out} and has a threshold of {@link #INFO}. + */ + public Log4JLogger() { + this(LogManager.getLogger(), INFO, ""); + } + + public Log4JLogger(String prefix) { + this(LogManager.getLogger(), INFO, prefix); + } + + /** + * Creates a new logger that writes to the specified print stream. + * + * @param out The print stream to write to, must not be null. + * @param threshold The threshold for the logger. + */ + public Log4JLogger(Logger out, int threshold, String prefix) { + if (out == null) { + throw new NullPointerException("missing output stream"); + } + this.prefix = prefix; + this.out = out; + setThreshold(threshold); + } + + /** + * Writes the specified message and exception to the print stream. + * + * @param level The priority level of the message. + * @param message The message to log, may be null. + * @param error The exception to log, may be null. + */ + private void log(int level, String message, Throwable error) { + if (level > threshold) { + // don't log when it doesn't match your threshold. + return; + } + + if (message == null && error == null) { + // don't log when there's nothing to log. + return; + } + + StringBuilder buffer = new StringBuilder(); + Level logLevel = Level.INFO; + + switch (level) { + case (DEBUG) -> logLevel = Level.DEBUG; + case (INFO) -> logLevel = Level.INFO; + case (WARN) -> logLevel = Level.WARN; + case (ERROR) -> logLevel = Level.ERROR; + case (FATAL) -> logLevel = Level.FATAL; + default -> { + } + } + + if (prefix != null && !prefix.isEmpty()) { + buffer.append("[%s-Maven] ".formatted(prefix)); + } else { + buffer.append("[Maven] "); + } + + if (message != null) { + buffer.append(message); + } + + if (error != null) { + StringWriter writer = new StringWriter(); + PrintWriter pWriter = new PrintWriter(writer); + + error.printStackTrace(pWriter); + + if (message != null) { + buffer.append('\n'); + } + + buffer.append("Error:\n"); + buffer.append(writer); + } + + out.log(logLevel, buffer.toString()); + } + + /** + * {@inheritDoc} + */ + public void debug(String message) { + log(DEBUG, message, null); + } + + /** + * {@inheritDoc} + */ + public void debug(String message, Throwable throwable) { + log(DEBUG, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void info(String message) { + log(INFO, message, null); + } + + /** + * {@inheritDoc} + */ + public void info(String message, Throwable throwable) { + log(INFO, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void warn(String message) { + log(WARN, message, null); + } + + /** + * {@inheritDoc} + */ + public void warn(String message, Throwable throwable) { + log(WARN, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void error(String message) { + log(ERROR, message, null); + } + + /** + * {@inheritDoc} + */ + public void error(String message, Throwable throwable) { + log(ERROR, message, throwable); + } + + /** + * {@inheritDoc} + */ + public void fatalError(String message) { + log(FATAL, message, null); + } + + /** + * {@inheritDoc} + */ + public void fatalError(String message, Throwable throwable) { + log(FATAL, message, throwable); + } + + /** + *

isDebugEnabled.

+ * + * @return a boolean. + */ + public boolean isDebugEnabled() { + return threshold >= DEBUG; + } + + /** + *

isErrorEnabled.

+ * + * @return a boolean. + */ + public boolean isErrorEnabled() { + return threshold >= ERROR; + } + + /** + *

isFatalErrorEnabled.

+ * + * @return a boolean. + */ + public boolean isFatalErrorEnabled() { + return threshold >= FATAL; + } + + /** + *

isInfoEnabled.

+ * + * @return a boolean. + */ + public boolean isInfoEnabled() { + return threshold >= INFO; + } + + /** + *

isWarnEnabled.

+ * + * @return a boolean. + */ + public boolean isWarnEnabled() { + return threshold >= WARN; + } + + /** + *

Getter for the field threshold.

+ * + * @return an int. + */ + public int getThreshold() { + return threshold; + } + + /** + * {@inheritDoc} + */ + public void setThreshold(int threshold) { + this.threshold = threshold; + } + + /** + * {@inheritDoc} + */ + public void consumeLine(String line) { + log(INFO, line, null); + } +} \ No newline at end of file diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/runners/maven/ProjectRunner.java b/Maestro/src/main/java/com/github/gilesi/maestro/runners/maven/ProjectRunner.java new file mode 100644 index 00000000..7f4f1f5d --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/runners/maven/ProjectRunner.java @@ -0,0 +1,181 @@ +package com.github.gilesi.maestro.runners.maven; + +import com.github.gilesi.maestro.Constants; +import com.github.gilesi.maestro.Main; +import org.apache.maven.model.*; +import org.apache.maven.shared.invoker.*; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import static com.github.gilesi.maestro.buildsystem.configuration.maven.PomHelper.readPomFile; + +public class ProjectRunner { + + public static int runPomGoals(String pomFilePath, List pomGoals, List pomProperties, + boolean batchMode, String userSettingsFilePath, Object logger, String javaToolOptions) + throws MavenInvocationException { + + String javaVersion = "8"; + + try { + Model pomModel = readPomFile(pomFilePath); + Properties props = pomModel.getProperties(); + if (props.stringPropertyNames().contains("java.compiler.version")) { + javaVersion = props.getProperty("java.compiler.version"); + Main.logger.info("Debug: Using java.compiler.version=" + javaVersion); + } else if (props.stringPropertyNames().contains("maven.compiler.source")) { + javaVersion = props.getProperty("maven.compiler.source"); + Main.logger.info("Debug: Using maven.compiler.source=" + javaVersion); + } else if (props.stringPropertyNames().contains("maven.compiler.target")) { + javaVersion = props.getProperty("maven.compiler.target"); + Main.logger.info("Debug: Using maven.compiler.target=" + javaVersion); + } else if (props.stringPropertyNames().contains("maven.compile.source")) { + javaVersion = props.getProperty("maven.compile.source"); + Main.logger.info("Debug: Using maven.compile.source=" + javaVersion); + } else if (props.stringPropertyNames().contains("maven.compile.target")) { + javaVersion = props.getProperty("maven.compile.target"); + Main.logger.info("Debug: Using maven.compile.target=" + javaVersion); + } + } catch (Exception ignored) { + Main.logger.info("Debug: Getting java specific version unfortunately failed."); + } + + return runPomGoals(pomFilePath, pomGoals, pomProperties, batchMode, userSettingsFilePath, javaVersion, logger, + javaToolOptions); + } + + public static int runPomGoals(String pomFilePath, List pomGoals, List pomProperties, + boolean batchMode, String userSettingsFilePath, String javaVersion, Object logger, String javaToolOptions) + throws MavenInvocationException { + String javaHome = Constants.JAVA_VERSIONS.getOrDefault(javaVersion, System.getenv("JAVA_HOME")); + return runPomGoalsInternal(pomFilePath, pomGoals, pomProperties, batchMode, userSettingsFilePath, javaHome, + javaVersion, logger, javaToolOptions); + } + + public static int runPomGoalsInternal(String pomFilePath, List pomGoals, List pomProperties, + boolean batchMode, String userSettingsFilePath, String JavaHome, String JavaVersion, Object logger, + String javaToolOptions) throws MavenInvocationException { + File pomFile = new File(pomFilePath); + + InvokerLogger invokerLogger = null; + InvocationOutputHandler invocationOutputHandler = null; + + if (logger instanceof InvokerLogger tmp) { + invokerLogger = tmp; + } + + if (logger instanceof InvocationOutputHandler tmp) { + invocationOutputHandler = tmp; + } + + File projectDirectory = new File(Path.of(pomFilePath).getParent().toString()); + + Properties properties = new Properties(); + pomProperties.forEach(p -> properties.setProperty(p, "true")); + + properties.setProperty("maven.compiler.source", JavaVersion); + properties.setProperty("maven.compiler.target", JavaVersion); + properties.setProperty("maven.compile.source", JavaVersion); + properties.setProperty("maven.compile.target", JavaVersion); + + properties.setProperty("maven.source.skip", "true"); + properties.setProperty("assembly.skipAssembly", "true"); + properties.setProperty("shade.skip", "true"); + properties.setProperty("maven.war.skip", "true"); + properties.setProperty("maven.rar.skip", "true"); + properties.setProperty("changelog.skip", "true"); + properties.setProperty("checkstyle.skip", "true"); + properties.setProperty("maven.doap.skip", "true"); + properties.setProperty("maven.javadoc.skip", "true"); + properties.setProperty("maven.jxr.skip", "true"); + properties.setProperty("linkcheck.skip", "true"); + properties.setProperty("pmd.skip", "true"); + properties.setProperty("mpir.skip", "true"); + properties.setProperty("gpg.skip", "true"); + properties.setProperty("jdepend.skip", "true"); + + String javacLocation = "%s%sbin%sjavac".formatted(JavaHome, File.separator, File.separator); + pomGoals.add("-Dmaven.compiler.fork=true"); + if (Files.exists(Path.of(javacLocation))) { + pomGoals.add("-Dmaven.compiler.executable=%s".formatted(javacLocation)); + } + + InvocationRequest request = new DefaultInvocationRequest(); + + Integer timeout = 30 * 60; // 30 minutes in seconds + + request.setShellEnvironmentInherited(false); + request.setPomFile(pomFile); + request.setGoals(pomGoals); + request.setProperties(properties); + request.setAlsoMake(true); + request.setBatchMode(true); + request.setTimeoutInSeconds(timeout); + + request.setJavaHome(new File(JavaHome)); + request.setBaseDirectory(projectDirectory); + request.setInputStream(InputStream.nullInputStream()); + if (invocationOutputHandler != null) { + request.setOutputHandler(invocationOutputHandler); + request.setErrorHandler(invocationOutputHandler); + } + + if (userSettingsFilePath != null && !userSettingsFilePath.isEmpty() && !userSettingsFilePath.isBlank()) { + Path pa = Path.of(userSettingsFilePath); + if (Files.exists(pa) && Files.isRegularFile(pa)) { + request.setUserSettingsFile(new File(userSettingsFilePath)); + } + } + + if (javaToolOptions != null && !javaToolOptions.isEmpty()) { + request.addShellEnvironment("JAVA_TOOL_OPTIONS", javaToolOptions); + } + + Main.logger.info("Building with pom=%s goals=%s properties=%s%n" + .formatted(pomFilePath, + pomGoals, + properties)); + + try { + Invoker invoker = new DefaultInvoker(); + if (invokerLogger != null) { + invoker.setLogger(invokerLogger); + } + + invoker.setMavenHome(new File(System.getenv("MAVEN_HOME"))); + + InvocationResult result = invoker.execute(request); + + Main.logger.info("Building with pom={} goals={} properties={} returned {}", + pomFile, + pomGoals, + properties, + result.getExitCode()); + + return result.getExitCode(); + } catch (MavenInvocationException e) { + throw e; + } + } + + public static boolean runMavenGoalOnRepository(String repositoryDirectory, String goal, String userSettingsFilePath, + String javaVersion, Object logger, String javaToolOptions) throws MavenInvocationException { + Main.logger.info("ExecuteMavenGoalOnDuetsRepository Entry"); + String pomFilePath = "%s%spom.xml".formatted(repositoryDirectory, File.separator); + ArrayList goals = new ArrayList<>(); + goals.add(goal); + + if (javaVersion != null && !javaVersion.isBlank()) { + return runPomGoals(pomFilePath, goals, new ArrayList<>(), false, userSettingsFilePath, javaVersion, logger, + javaToolOptions) == 0; + } else { + return runPomGoals(pomFilePath, goals, new ArrayList<>(), false, userSettingsFilePath, logger, + javaToolOptions) == 0; + } + } +} \ No newline at end of file diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/runners/maven/TestAssertedLogger.java b/Maestro/src/main/java/com/github/gilesi/maestro/runners/maven/TestAssertedLogger.java new file mode 100644 index 00000000..7fdbf8df --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/runners/maven/TestAssertedLogger.java @@ -0,0 +1,192 @@ +package com.github.gilesi.maestro.runners.maven; + +import org.apache.maven.shared.invoker.InvocationOutputHandler; +import org.apache.maven.shared.invoker.InvokerLogger; + +import java.io.IOException; + +public class TestAssertedLogger implements InvokerLogger, InvocationOutputHandler { + + private InvokerLogger invokerLogger = null; + private InvocationOutputHandler invocationOutputHandler = null; + private boolean testFailed = false; + + public TestAssertedLogger(Object logger) { + if (logger instanceof InvokerLogger tmp) { + invokerLogger = tmp; + } + + if (logger instanceof InvocationOutputHandler tmp) { + invocationOutputHandler = tmp; + } + } + + + public void debug(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.debug(message); + } + } + + public void debug(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.debug(message, throwable); + } + } + + public boolean isDebugEnabled() { + if (invokerLogger != null) { + return invokerLogger.isDebugEnabled(); + } + + return false; + } + + public void info(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.info(message); + } + } + + public void info(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.info(message, throwable); + } + } + + public boolean isInfoEnabled() { + if (invokerLogger != null) { + return invokerLogger.isInfoEnabled(); + } + + return false; + } + + public void warn(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.warn(message); + } + } + + public void warn(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.warn(message, throwable); + } + } + + public boolean isWarnEnabled() { + if (invokerLogger != null) { + return invokerLogger.isWarnEnabled(); + } + + return false; + } + + public void error(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.error(message); + } + } + + public void error(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.error(message, throwable); + } + } + + public boolean isErrorEnabled() { + if (invokerLogger != null) { + return invokerLogger.isErrorEnabled(); + } + + return false; + } + + public void fatalError(String message) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.fatalError(message); + } + } + + public void fatalError(String message, Throwable throwable) { + if (message.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invokerLogger != null) { + invokerLogger.fatalError(message, throwable); + } + } + + public boolean isFatalErrorEnabled() { + if (invokerLogger != null) { + return invokerLogger.isFatalErrorEnabled(); + } + + return false; + } + + public int getThreshold() { + if (invokerLogger != null) { + return invokerLogger.getThreshold(); + } + + return 0; + } + + public void setThreshold(int threshold) { + if (invokerLogger != null) { + invokerLogger.setThreshold(threshold); + } + } + + public void consumeLine(String line) throws IOException { + if (line.contains("<<< FAILURE!")) { + testFailed = true; + } + + if (invocationOutputHandler != null) { + invocationOutputHandler.consumeLine(line); + } + } + + public boolean HasTestFailed() { + return testFailed; + } +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/testing/ReportItem.java b/Maestro/src/main/java/com/github/gilesi/maestro/testing/ReportItem.java new file mode 100644 index 00000000..ecf527ae --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/testing/ReportItem.java @@ -0,0 +1,65 @@ +package com.github.gilesi.maestro.testing; + +import com.github.gilesi.maestro.Constants; +import com.github.gilesi.maestro.testing.models.Testcase; + +public class ReportItem { + public String Name; + public String Version; + private String TestName; + private String TestResult; + private String ErrorDetails; + + public ReportItem(String projectName, String projectCommit, Testcase testcase) { + this.TestName = "%s.%s".formatted(testcase.ClassName, testcase.Name); + this.TestResult = ((testcase.Error != null) || (testcase.Failure != null)) ? Constants.RED_TEST : Constants.GREEN_TEST; + this.ErrorDetails = (testcase.Error != null) ? testcase.Error.Message : ""; + this.Name = projectName; + this.Version = projectCommit; + } + + public String getName() { + return Name; + } + + public void setName(String name) { + Name = name; + } + + public String getVersion() { + return Version; + } + + public void setVersion(String version) { + Version = version; + } + + public String getTestName() { + return TestName; + } + + public void setTestName(String testName) { + TestName = testName; + } + + public String getTestResult() { + return TestResult; + } + + public void setTestResult(String testResult) { + TestResult = testResult; + } + + public String getErrorDetails() { + return ErrorDetails; + } + + public void setErrorDetails(String errorDetails) { + ErrorDetails = errorDetails; + } + + @Override + public String toString() { + return "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"".formatted(Name, Version, TestName, TestResult, ErrorDetails); + } +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/testing/XmlParser.java b/Maestro/src/main/java/com/github/gilesi/maestro/testing/XmlParser.java new file mode 100644 index 00000000..f65889df --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/testing/XmlParser.java @@ -0,0 +1,33 @@ +package com.github.gilesi.maestro.testing; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.github.gilesi.maestro.testing.models.Testsuite; +import com.github.gilesi.maestro.testing.models.Testsuites; + +import java.io.File; +import java.io.IOException; + +public class XmlParser { + + private static final XmlMapper mapper = XmlMapper + .builder() + .defaultUseWrapper(false) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .build(); + + public static Testsuite deserializeTestsuite(String XmlPath) throws IOException { + File fi = new File(XmlPath); + return mapper.readValue(fi, Testsuite.class); + } + + public static Testsuites deserializeTestsuites(String XmlPath) throws IOException { + File fi = new File(XmlPath); + return mapper.readValue(fi, Testsuites.class); + } + + public static void serialize(String XmlPath, Object obj) throws IOException { + File fi = new File(XmlPath); + mapper.writeValue(fi, obj); + } +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/testing/gradle/GradleHarness.java b/Maestro/src/main/java/com/github/gilesi/maestro/testing/gradle/GradleHarness.java new file mode 100644 index 00000000..aca3abf2 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/testing/gradle/GradleHarness.java @@ -0,0 +1,52 @@ +package com.github.gilesi.maestro.testing.gradle; + +import com.github.gilesi.maestro.FileUtils; +import com.github.gilesi.maestro.Main; +import com.github.gilesi.maestro.testing.ReportItem; +import com.github.gilesi.maestro.testing.XmlParser; +import com.github.gilesi.maestro.testing.models.Testcase; +import com.github.gilesi.maestro.testing.models.Testsuite; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; + +public class GradleHarness { + private static void buildReportXml(String projectName, String projectCommit, String targetLocation, + ArrayList reportItems) throws IOException { + String sureFireReportsLocation = "%s%stest-results%stest".formatted(targetLocation, File.separator, + File.separator); + ArrayList reportXmls = FileUtils.enumerateFiles(sureFireReportsLocation, ".*\\.xml", false); + + for (String xml : reportXmls) { + Testsuite testsuite = XmlParser.deserializeTestsuite(xml); + Testcase[] list = testsuite.getTestcase(); + if (list == null) { + continue; + } + for (Testcase testcase : list) { + reportItems.add(new ReportItem(projectName, projectCommit, testcase)); + } + } + } + + public static ArrayList getProjectTestReports(String projectPathFriendlyName, String commit, + String buildGradleFileLocation) { + ArrayList reportItems = new ArrayList<>(); + + Path buildGradleFilePath = Path.of(buildGradleFileLocation); + String targetLocation = "%s%sbuild".formatted(buildGradleFilePath.getParent().toAbsolutePath(), File.separator); + + if (Files.isDirectory(Path.of(targetLocation))) { + try { + buildReportXml(projectPathFriendlyName, commit, targetLocation, reportItems); + } catch (Exception e) { + Main.logger.info(e); + } + } + + return reportItems; + } +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/testing/models/Error.java b/Maestro/src/main/java/com/github/gilesi/maestro/testing/models/Error.java new file mode 100644 index 00000000..61196123 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/testing/models/Error.java @@ -0,0 +1,17 @@ +package com.github.gilesi.maestro.testing.models; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlCData; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText; + +public class Error { + @JacksonXmlProperty(isAttribute = true, localName = "message") + public String Message; + + @JacksonXmlProperty(isAttribute = true, localName = "type") + public String Type; + + @JacksonXmlText + @JacksonXmlCData + private String Data; +} \ No newline at end of file diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/testing/models/Properties.java b/Maestro/src/main/java/com/github/gilesi/maestro/testing/models/Properties.java new file mode 100644 index 00000000..cb262086 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/testing/models/Properties.java @@ -0,0 +1,13 @@ +package com.github.gilesi.maestro.testing.models; + +public class Properties { + private Property[] property; + + public Property[] getProperty() { + return property; + } + + public void setProperty(Property[] value) { + this.property = value; + } +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/testing/models/Property.java b/Maestro/src/main/java/com/github/gilesi/maestro/testing/models/Property.java new file mode 100644 index 00000000..71425334 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/testing/models/Property.java @@ -0,0 +1,22 @@ +package com.github.gilesi.maestro.testing.models; + +public class Property { + private String name; + private String value; + + public String getName() { + return name; + } + + public void setName(String value) { + this.name = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} \ No newline at end of file diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/testing/models/Testcase.java b/Maestro/src/main/java/com/github/gilesi/maestro/testing/models/Testcase.java new file mode 100644 index 00000000..9894c929 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/testing/models/Testcase.java @@ -0,0 +1,23 @@ +package com.github.gilesi.maestro.testing.models; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; + +public class Testcase { + @JacksonXmlElementWrapper(localName = "error") + public com.github.gilesi.maestro.testing.models.Error Error; + + @JacksonXmlElementWrapper(localName = "failure") + public Error Failure; + + @JacksonXmlElementWrapper(localName = "name") + public String Name; + + @JacksonXmlElementWrapper(localName = "classname") + public String ClassName; + + @JacksonXmlElementWrapper(localName = "time") + public String Time; + + @JacksonXmlElementWrapper(localName = "system-out") + public String SystemOut; +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/testing/models/Testsuite.java b/Maestro/src/main/java/com/github/gilesi/maestro/testing/models/Testsuite.java new file mode 100644 index 00000000..4e820cec --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/testing/models/Testsuite.java @@ -0,0 +1,103 @@ +package com.github.gilesi.maestro.testing.models; + +public class Testsuite { + private Properties properties; + private Testcase[] testcase; + private String xmlnsXsi; + private String xsiNoNamespaceSchemaLocation; + private String version; + private String name; + private String time; + private String tests; + private String errors; + private String skipped; + private String failures; + + public Properties getProperties() { + return properties; + } + + public void setProperties(Properties value) { + this.properties = value; + } + + public Testcase[] getTestcase() { + return testcase; + } + + public void setTestcase(Testcase[] value) { + this.testcase = value; + } + + public String getXmlnsXsi() { + return xmlnsXsi; + } + + public void setXmlnsXsi(String value) { + this.xmlnsXsi = value; + } + + public String getXsiNoNamespaceSchemaLocation() { + return xsiNoNamespaceSchemaLocation; + } + + public void setXsiNoNamespaceSchemaLocation(String value) { + this.xsiNoNamespaceSchemaLocation = value; + } + + public String getVersion() { + return version; + } + + public void setVersion(String value) { + this.version = value; + } + + public String getName() { + return name; + } + + public void setName(String value) { + this.name = value; + } + + public String getTime() { + return time; + } + + public void setTime(String value) { + this.time = value; + } + + public String getTests() { + return tests; + } + + public void setTests(String value) { + this.tests = value; + } + + public String getErrors() { + return errors; + } + + public void setErrors(String value) { + this.errors = value; + } + + public String getSkipped() { + return skipped; + } + + public void setSkipped(String value) { + this.skipped = value; + } + + public String getFailures() { + return failures; + } + + public void setFailures(String value) { + this.failures = value; + } +} \ No newline at end of file diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/testing/models/Testsuites.java b/Maestro/src/main/java/com/github/gilesi/maestro/testing/models/Testsuites.java new file mode 100644 index 00000000..b614caa9 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/testing/models/Testsuites.java @@ -0,0 +1,13 @@ +package com.github.gilesi.maestro.testing.models; + +public class Testsuites { + private Testsuite[] testsuites; + + public Testsuite[] getTestsuites() { + return testsuites; + } + + public void setTestsuites(Testsuite[] value) { + this.testsuites = value; + } +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/testing/surefire/SurefireHarness.java b/Maestro/src/main/java/com/github/gilesi/maestro/testing/surefire/SurefireHarness.java new file mode 100644 index 00000000..b2ebc523 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/testing/surefire/SurefireHarness.java @@ -0,0 +1,49 @@ +package com.github.gilesi.maestro.testing.surefire; + +import com.github.gilesi.maestro.FileUtils; +import com.github.gilesi.maestro.Main; +import com.github.gilesi.maestro.testing.ReportItem; +import com.github.gilesi.maestro.testing.models.Testcase; +import com.github.gilesi.maestro.testing.models.Testsuite; +import com.github.gilesi.maestro.testing.XmlParser; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; + +public class SurefireHarness { + private static void buildReportXml(String projectName, String projectCommit, String targetLocation, ArrayList reportItems) throws IOException { + String sureFireReportsLocation = "%s%ssurefire-reports".formatted(targetLocation, File.separator); + ArrayList reportXmls = FileUtils.enumerateFiles(sureFireReportsLocation, ".*\\.xml", false); + + for (String xml : reportXmls) { + Testsuite testsuite = XmlParser.deserializeTestsuite(xml); + Testcase[] list = testsuite.getTestcase(); + if (list == null) { + continue; + } + for (Testcase testcase : list) { + reportItems.add(new ReportItem(projectName, projectCommit, testcase)); + } + } + } + + public static ArrayList getProjectTestReports(String projectPathFriendlyName, String commit, String pomFileLocation) { + ArrayList reportItems = new ArrayList<>(); + + Path pomFilePath = Path.of(pomFileLocation); + String targetLocation = "%s%starget".formatted(pomFilePath.getParent().toAbsolutePath(), File.separator); + + if (Files.isDirectory(Path.of(targetLocation))) { + try { + buildReportXml(projectPathFriendlyName, commit, targetLocation, reportItems); + } catch (Exception e) { + Main.logger.info(e); + } + } + + return reportItems; + } +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/tools/ConfGen.java b/Maestro/src/main/java/com/github/gilesi/maestro/tools/ConfGen.java new file mode 100644 index 00000000..3d3650b2 --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/tools/ConfGen.java @@ -0,0 +1,23 @@ +package com.github.gilesi.maestro.tools; + +import com.github.gilesi.maestro.Constants; +import com.github.gilesi.maestro.Main; +import com.github.gilesi.maestro.ProcessUtils; + +import java.io.IOException; +import java.nio.file.Path; + +public class ConfGen { + public static void runConfGen(String GilesiRepositoryLocation, String libraryConfig, String outputTestProject, String libraryLocation) throws IOException, InterruptedException { + ProcessUtils.runExternalCommand(new String[]{ + Path.of(Constants.JAVA_21_BINARY_LOCATION, "bin", "java").toString(), + "-Xmx8g", + "-Xms8g", + "-jar", + Path.of(GilesiRepositoryLocation).resolve(Constants.CONF_GEN_LOCATION).toString(), + libraryConfig, + outputTestProject, + libraryLocation + }, null, Main.loggerConfGen); + } +} diff --git a/Maestro/src/main/java/com/github/gilesi/maestro/tools/TestGen.java b/Maestro/src/main/java/com/github/gilesi/maestro/tools/TestGen.java new file mode 100644 index 00000000..5f0d2c8d --- /dev/null +++ b/Maestro/src/main/java/com/github/gilesi/maestro/tools/TestGen.java @@ -0,0 +1,44 @@ +package com.github.gilesi.maestro.tools; + +import com.github.gilesi.maestro.Constants; +import com.github.gilesi.maestro.Main; +import com.github.gilesi.maestro.ProcessUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.nio.file.Path; + +public class TestGen { + public static final Logger logger = LogManager.getLogger(); + + public static void runTestGen(String GilesiRepositoryLocation, String traceFile, String outputCodeDirectory, String libraryConfig) throws IOException, InterruptedException { + ProcessUtils.runExternalCommand(new String[]{ + Path.of(Constants.JAVA_21_BINARY_LOCATION, "bin", "java").toString(), + "-Xmx8g", + "-Xms8g", + "-jar", + Path.of(GilesiRepositoryLocation).resolve(Constants.TEST_GEN_LOCATION).toString(), + traceFile, + outputCodeDirectory, + libraryConfig + }, null, Main.loggerTraceGen); + } + + public static void runTraceDiff(String GilesiRepositoryLocation, String traceFile, String outputCodeDirectory, String libraryConfig) throws IOException, InterruptedException { + ProcessUtils.runExternalCommand(new String[]{ + Path.of(Constants.JAVA_21_BINARY_LOCATION, "bin", "java").toString(), + "-Xmx32768m", + "-Xms16384m", + "-Xss1024m", + "-XX:ParallelGCThreads=8", + "-XX:InitiatingHeapOccupancyPercent=70", + "-XX:+UnlockDiagnosticVMOptions", + "-jar", + Path.of(GilesiRepositoryLocation).resolve(Constants.TRACE_DIFF_LOCATION).toString(), + traceFile, + outputCodeDirectory, + libraryConfig + }, null, Main.loggerTraceDiff); + } +} \ No newline at end of file diff --git a/Maestro/src/main/resources/log4j2.xml b/Maestro/src/main/resources/log4j2.xml new file mode 100644 index 00000000..41e78008 --- /dev/null +++ b/Maestro/src/main/resources/log4j2.xml @@ -0,0 +1,82 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SampleRun.sh b/SampleRun.sh new file mode 100755 index 00000000..8472cbc1 --- /dev/null +++ b/SampleRun.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# maestro requires the use of other subprojects that must be precompiled before running, and pointed to it via the gilesi repo arg. +# first compile everything +sh ./compile.sh + +export MAVEN_HOME="/snap/intellij-idea-ultimate/current/plugins/maven/lib/maven3" + +# then run maestro for sample usage +# this commands starts the experience on the compsuite dataset copied at "/home/gus/Datasets/compsuite3", and limits it to just the project id "i-3" +# note that the compsuite dataset folder layout is our own format, and omitting "i-3" will run the entire experience on every project. +java -Xms8g -Xmx8g -jar ./Maestro/build/libs/com.github.gilesi.maestro.jar "/home/gus/Datasets/compsuite3" "$PWD" + +# list of configurable hardocded options scattered throughout the entire project: +# Maestro/src/main/java/com/github/gilesi/maestro/Constants.java +# TestGenerator/src/main/java/com/github/gilesi/testgenerator/StaticConfiguration.java diff --git a/Samples/Gradle/.gitignore b/Samples/Gradle/.gitignore new file mode 100644 index 00000000..d4c6a6aa --- /dev/null +++ b/Samples/Gradle/.gitignore @@ -0,0 +1,39 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/* +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/Samples/Gradle/gradle/wrapper/gradle-wrapper.jar b/Samples/Gradle/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..033e24c4 Binary files /dev/null and b/Samples/Gradle/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Samples/Gradle/gradle/wrapper/gradle-wrapper.properties b/Samples/Gradle/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..1af9e093 --- /dev/null +++ b/Samples/Gradle/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/Samples/Gradle/gradlew b/Samples/Gradle/gradlew new file mode 100644 index 00000000..cbc4dfc1 --- /dev/null +++ b/Samples/Gradle/gradlew @@ -0,0 +1,246 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +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 + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/Samples/Gradle/gradlew.bat b/Samples/Gradle/gradlew.bat new file mode 100644 index 00000000..4b4ef2d7 --- /dev/null +++ b/Samples/Gradle/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Samples/Gradle/sampleclient/.gitignore b/Samples/Gradle/sampleclient/.gitignore new file mode 100644 index 00000000..d4c6a6aa --- /dev/null +++ b/Samples/Gradle/sampleclient/.gitignore @@ -0,0 +1,39 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/* +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/Samples/Gradle/sampleclient/build.gradle b/Samples/Gradle/sampleclient/build.gradle new file mode 100644 index 00000000..fd2060e5 --- /dev/null +++ b/Samples/Gradle/sampleclient/build.gradle @@ -0,0 +1,23 @@ +plugins { + id 'java' +} + +group = 'com.github.gilesi.samples.sampleclient' +version = '1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + implementation project(':samplelibrary') + implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.1' +} + +testing { + suites { + test { + useJUnitJupiter() + } + } +} \ No newline at end of file diff --git a/Samples/Gradle/sampleclient/gradle/wrapper/gradle-wrapper.jar b/Samples/Gradle/sampleclient/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..033e24c4 Binary files /dev/null and b/Samples/Gradle/sampleclient/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Samples/Gradle/sampleclient/gradle/wrapper/gradle-wrapper.properties b/Samples/Gradle/sampleclient/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..1af9e093 --- /dev/null +++ b/Samples/Gradle/sampleclient/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/Samples/Gradle/sampleclient/gradlew b/Samples/Gradle/sampleclient/gradlew new file mode 100644 index 00000000..cbc4dfc1 --- /dev/null +++ b/Samples/Gradle/sampleclient/gradlew @@ -0,0 +1,246 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +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 + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/Samples/Gradle/sampleclient/gradlew.bat b/Samples/Gradle/sampleclient/gradlew.bat new file mode 100644 index 00000000..4b4ef2d7 --- /dev/null +++ b/Samples/Gradle/sampleclient/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Samples/sampleclient/src/main/java/com/github/maracas/gilesi/samples/sampleclient/C1.java b/Samples/Gradle/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/C1.java similarity index 54% rename from Samples/sampleclient/src/main/java/com/github/maracas/gilesi/samples/sampleclient/C1.java rename to Samples/Gradle/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/C1.java index 07fec197..d9d039c3 100644 --- a/Samples/sampleclient/src/main/java/com/github/maracas/gilesi/samples/sampleclient/C1.java +++ b/Samples/Gradle/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/C1.java @@ -1,6 +1,6 @@ -package com.github.maracas.gilesi.samples.sampleclient; +package com.github.gilesi.samples.sampleclient; -import com.github.maracas.gilesi.samples.samplelibrary.A2; +import com.github.gilesi.samples.samplelibrary.A2; public class C1 { public static int Do(int i) { diff --git a/Samples/Gradle/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/C2.java b/Samples/Gradle/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/C2.java new file mode 100644 index 00000000..8867bcc0 --- /dev/null +++ b/Samples/Gradle/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/C2.java @@ -0,0 +1,9 @@ +package com.github.gilesi.samples.sampleclient; + +import com.github.gilesi.samples.samplelibrary.A2; + +public class C2 { + public static int bar(A2 a) { + return a.foo(1); + } +} diff --git a/Samples/Gradle/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/C3.java b/Samples/Gradle/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/C3.java new file mode 100644 index 00000000..5f344168 --- /dev/null +++ b/Samples/Gradle/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/C3.java @@ -0,0 +1,10 @@ +package com.github.gilesi.samples.sampleclient; + +import com.github.gilesi.samples.samplelibrary.A; + +public class C3 { + public static int Do(int i) { + A.A1 a = new A.A1(5); + return a.foo(1); + } +} diff --git a/Samples/sampleclient/src/main/java/com/github/maracas/gilesi/samples/sampleclient/Main.java b/Samples/Gradle/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/Main.java similarity index 75% rename from Samples/sampleclient/src/main/java/com/github/maracas/gilesi/samples/sampleclient/Main.java rename to Samples/Gradle/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/Main.java index 5a148ab5..19cfb299 100644 --- a/Samples/sampleclient/src/main/java/com/github/maracas/gilesi/samples/sampleclient/Main.java +++ b/Samples/Gradle/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/Main.java @@ -1,6 +1,6 @@ -package com.github.maracas.gilesi.samples.sampleclient; +package com.github.gilesi.samples.sampleclient; -import com.github.maracas.gilesi.samples.samplelibrary.A; +import com.github.gilesi.samples.samplelibrary.A; public class Main { public static void main(String[] args) { diff --git a/Samples/sampleclient/src/main/java/com/github/maracas/gilesi/samples/sampleclient/Main2.java b/Samples/Gradle/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/Main2.java similarity index 93% rename from Samples/sampleclient/src/main/java/com/github/maracas/gilesi/samples/sampleclient/Main2.java rename to Samples/Gradle/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/Main2.java index 5dfa4462..3beeb9eb 100644 --- a/Samples/sampleclient/src/main/java/com/github/maracas/gilesi/samples/sampleclient/Main2.java +++ b/Samples/Gradle/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/Main2.java @@ -1,7 +1,7 @@ -package com.github.maracas.gilesi.samples.sampleclient; +package com.github.gilesi.samples.sampleclient; -import com.github.maracas.gilesi.samples.samplelibrary.Foo; -import com.github.maracas.gilesi.samples.samplelibrary.Foo2; +import com.github.gilesi.samples.samplelibrary.Foo; +import com.github.gilesi.samples.samplelibrary.Foo2; import java.util.ArrayList; import java.util.Arrays; @@ -69,8 +69,7 @@ public static void main(String[] args) { System.out.println(); - List peopleList = new ArrayList<>(); - peopleList.addAll(Arrays.asList(people)); + List peopleList = new ArrayList<>(Arrays.asList(people)); System.out.println("People List Right now:"); for (String pe : peopleList) { @@ -92,8 +91,6 @@ public static void main(String[] args) { Foo.SomeValueStoredHere = Foo.SomeValueStoredHere + Foo.SomeValueStoredHere; - - Foo2 ff = new Foo2("yo"); System.out.println(ff.sayHelloFoo()); @@ -153,8 +150,7 @@ public static void main(String[] args) { System.out.println(); - peopleList = new ArrayList<>(); - peopleList.addAll(Arrays.asList(people)); + peopleList = new ArrayList<>(Arrays.asList(people)); System.out.println("People List Right now:"); for (String pe : peopleList) { diff --git a/Samples/Gradle/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/SelfCaller.java b/Samples/Gradle/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/SelfCaller.java new file mode 100644 index 00000000..ffa1cecf --- /dev/null +++ b/Samples/Gradle/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/SelfCaller.java @@ -0,0 +1,9 @@ +package com.github.gilesi.samples.sampleclient; + +import com.github.gilesi.samples.samplelibrary.SelfCallTest; + +public class SelfCaller { + public static void call() { + SelfCallTest.FirstAPI(); + } +} diff --git a/Samples/Gradle/sampleclient/src/test/java/CTest.java b/Samples/Gradle/sampleclient/src/test/java/CTest.java new file mode 100644 index 00000000..1c0f259d --- /dev/null +++ b/Samples/Gradle/sampleclient/src/test/java/CTest.java @@ -0,0 +1,33 @@ +import com.github.gilesi.samples.sampleclient.C1; +import com.github.gilesi.samples.sampleclient.C3; +import com.github.gilesi.samples.samplelibrary.A2; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CTest { + @Test + public void Test() { + C1 c1 = new C1(); + int s = C1.Do(3); + assertEquals(21, s); + } + + @Test + public void ClientATest() { + com.github.gilesi.samples.samplelibrary.A2 a = new A2(5); + a = a.getOurselves(); + int r = a.foo(1); + assertEquals(11, r); + + A2 a2 = new A2(4); + a2.testingArraysLol(null, null, null); + } + + @Test + public void Test3() { + C3 c3 = new C3(); + int s = C3.Do(3); + assertEquals(7, s); + } +} diff --git a/Samples/sampleclient/src/test/java/FooTest.java b/Samples/Gradle/sampleclient/src/test/java/FooTest.java similarity index 73% rename from Samples/sampleclient/src/test/java/FooTest.java rename to Samples/Gradle/sampleclient/src/test/java/FooTest.java index 1534ca9c..e34b122b 100644 --- a/Samples/sampleclient/src/test/java/FooTest.java +++ b/Samples/Gradle/sampleclient/src/test/java/FooTest.java @@ -1,5 +1,6 @@ -import com.github.maracas.gilesi.samples.samplelibrary.Foo; -import com.github.maracas.gilesi.samples.samplelibrary.Foo2; +import com.github.gilesi.samples.samplelibrary.A; +import com.github.gilesi.samples.samplelibrary.Foo; +import com.github.gilesi.samples.samplelibrary.Foo2; import org.junit.jupiter.api.Test; import java.util.ArrayList; @@ -51,15 +52,13 @@ public void mainTest() { assertEquals(var10, "420"); assertEquals(var11, "testestest696969696"); - java.lang.String[] var15 = new String[] { "Toto", "Tata", "Titi", "JP", "PA", "JR" }; + java.lang.String[] var15 = new String[]{"Toto", "Tata", "Titi", "JP", "PA", "JR"}; java.lang.String var14 = Foo.HelloEveryone(var15); assertEquals(var14, "Hello [Toto, Tata, Titi, JP, PA, JR]"); - assertArrayEquals(var15, new String[] { "Toto", "Tata", "Titi", "JP", "PA", "JR" }); + assertArrayEquals(var15, new String[]{"Toto", "Tata", "Titi", "JP", "PA", "JR"}); - java.util.ArrayList var17 = new ArrayList(); - var17.addAll(Arrays.stream(new String[] { "Toto", "Tata", "Titi", "JP", "PA", "JR" }).toList()); - java.util.ArrayList var171 = new ArrayList(); - var171.addAll(Arrays.stream(new String[] { "NewPersonA", "NewPersonB", "NewPersonC", "NewPersonD" }).toList()); + ArrayList var17 = new ArrayList(Arrays.stream(new String[]{"Toto", "Tata", "Titi", "JP", "PA", "JR"}).toList()); + ArrayList var171 = new ArrayList(Arrays.stream(new String[]{"NewPersonA", "NewPersonB", "NewPersonC", "NewPersonD"}).toList()); java.lang.String var16 = Foo.HelloEveryone2(var17); assertEquals(var16, "Hello TotoTataTitiJPPAJR"); assertEquals(var17, var171); @@ -109,15 +108,13 @@ public void mainTest() { assertEquals(var10, "420"); assertEquals(var11, "testestest696969696"); - java.lang.String[] var27 = new String[] { "Toto", "Tata", "Titi", "JP", "PA", "JR" }; + java.lang.String[] var27 = new String[]{"Toto", "Tata", "Titi", "JP", "PA", "JR"}; java.lang.String var26 = Foo2.jhgbjtjbh(var27); assertEquals(var26, "Hello [Toto, Tata, Titi, JP, PA, JR]"); - assertArrayEquals(var27, new String[] { "Toto", "Tata", "Titi", "JP", "PA", "JR" }); + assertArrayEquals(var27, new String[]{"Toto", "Tata", "Titi", "JP", "PA", "JR"}); - java.util.ArrayList var29 = new ArrayList(); - java.util.ArrayList var291 = new ArrayList(); - var29.addAll(Arrays.stream(new String[] { "Toto", "Tata", "Titi", "JP", "PA", "JR" }).toList()); - var291.addAll(Arrays.stream(new String[] { "NewPersonA", "NewPersonB", "NewPersonC", "NewPersonD" }).toList()); + ArrayList var29 = new ArrayList(Arrays.stream(new String[]{"Toto", "Tata", "Titi", "JP", "PA", "JR"}).toList()); + ArrayList var291 = new ArrayList(Arrays.stream(new String[]{"NewPersonA", "NewPersonB", "NewPersonC", "NewPersonD"}).toList()); java.lang.String var28 = Foo2.njrhbgtujhu(var29); assertEquals(var28, "Hello TotoTataTitiJPPAJR"); assertEquals(var29, var291); @@ -127,4 +124,21 @@ public void mainTest() { assertEquals(var18, "the life the universe and everything"); assertEquals(var19, 42); } + + @Test + public void TestFoo() { + A a = new A(5); + int result = a.foo(1); + assertEquals(result, 7); + } + + @Test + public void TestFooIsLifeNice() { + A a = new A(42); + int result = a.foo(1); + + a.CallBack(this::TestFoo); + + assertEquals(result, 69); + } } \ No newline at end of file diff --git a/Samples/Gradle/sampleclient/src/test/java/SelfTest.java b/Samples/Gradle/sampleclient/src/test/java/SelfTest.java new file mode 100644 index 00000000..8bb017e5 --- /dev/null +++ b/Samples/Gradle/sampleclient/src/test/java/SelfTest.java @@ -0,0 +1,9 @@ +import com.github.gilesi.samples.sampleclient.SelfCaller; +import org.junit.jupiter.api.Test; + +public class SelfTest { + @Test + public void test() { + SelfCaller.call(); + } +} diff --git a/Samples/Gradle/samplelibrary/.gitignore b/Samples/Gradle/samplelibrary/.gitignore new file mode 100644 index 00000000..d4c6a6aa --- /dev/null +++ b/Samples/Gradle/samplelibrary/.gitignore @@ -0,0 +1,39 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/* +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/Samples/Gradle/samplelibrary/TestWorkflowConfiguration.xml b/Samples/Gradle/samplelibrary/TestWorkflowConfiguration.xml new file mode 100644 index 00000000..718e18af --- /dev/null +++ b/Samples/Gradle/samplelibrary/TestWorkflowConfiguration.xml @@ -0,0 +1,204 @@ + + + + com.github.gilesi.samples.samplelibrary.Foo2 + + + triuetyg + + (I)Ljava/lang/String; + + + + TESTING + + (Ljava/lang/String;)Ljava/lang/String; + + + + sayHelloFoo + + ()Ljava/lang/String; + + + + hello + + (Ljava/lang/String;)V + ()Ljava/lang/String; + + + + njrhbgtujhu + + (Ljava/util/Collection;)Ljava/lang/String; + + + + hellow + + (Ljava/lang/String;)Ljava/lang/String; + + + + jhgbjtjbh + + ([Ljava/lang/String;)Ljava/lang/String; + + + + + (Ljava/lang/String;)V + + + + com.github.gilesi.samples.samplelibrary.A2 + + + getOurselves + + ()Lcom/github/gilesi/samples/samplelibrary/A2; + + + + foo + + (I)I + + + + sideEffect + + ()V + + + + + (I)V + + + + com.github.gilesi.samples.samplelibrary.Foo + + + HelloEveryone2 + + (Ljava/util/Collection;)Ljava/lang/String; + + + + TESTING + + (Ljava/lang/String;)Ljava/lang/String; + + + + meaningof + + (I)Ljava/lang/String; + + + + sayHelloFoo + + ()Ljava/lang/String; + + + + hello + + (Ljava/lang/String;)V + ()Ljava/lang/String; + + + + hellow + + (Ljava/lang/String;)Ljava/lang/String; + + + + HelloEveryone + + ([Ljava/lang/String;)Ljava/lang/String; + + + + + (Ljava/lang/String;)V + + + + com.github.gilesi.samples.samplelibrary.A + + + CallBack + + (Lcom/github/gilesi/samples/samplelibrary/IDoSomething;)V + + + + foo + + (I)I + + + + + (I)V + + + + + CTest.ClientATest + CTest.Test + FooTest.TestFoo + FooTest.TestFooIsLifeNice + FooTest.mainTest + com.github.gilesi.samples.sampleclient.C1.Do + com.github.gilesi.samples.sampleclient.C2.bar + com.github.gilesi.samples.sampleclient.Main.DoWork + com.github.gilesi.samples.sampleclient.Main.main + com.github.gilesi.samples.sampleclient.Main2.main + + + CTest.CTest + FooTest.FooTest + com.github.gilesi.samples.sampleclient.C1.C1 + com.github.gilesi.samples.sampleclient.C2.C2 + com.github.gilesi.samples.sampleclient.Main.Main + com.github.gilesi.samples.sampleclient.Main2.Main2 + + + com.github.gilesi.samples.samplelibrary.A.CallBack + com.github.gilesi.samples.samplelibrary.A.foo + com.github.gilesi.samples.samplelibrary.A2.foo + com.github.gilesi.samples.samplelibrary.A2.getOurselves + com.github.gilesi.samples.samplelibrary.A2.sideEffect + com.github.gilesi.samples.samplelibrary.Foo.HelloEveryone + com.github.gilesi.samples.samplelibrary.Foo.HelloEveryone2 + com.github.gilesi.samples.samplelibrary.Foo.TESTING + com.github.gilesi.samples.samplelibrary.Foo.hello + com.github.gilesi.samples.samplelibrary.Foo.hello + com.github.gilesi.samples.samplelibrary.Foo.hellow + com.github.gilesi.samples.samplelibrary.Foo.meaningof + com.github.gilesi.samples.samplelibrary.Foo.sayHelloFoo + com.github.gilesi.samples.samplelibrary.Foo2.TESTING + com.github.gilesi.samples.samplelibrary.Foo2.hello + com.github.gilesi.samples.samplelibrary.Foo2.hello + com.github.gilesi.samples.samplelibrary.Foo2.hellow + com.github.gilesi.samples.samplelibrary.Foo2.jhgbjtjbh + com.github.gilesi.samples.samplelibrary.Foo2.njrhbgtujhu + com.github.gilesi.samples.samplelibrary.Foo2.sayHelloFoo + com.github.gilesi.samples.samplelibrary.Foo2.triuetyg + com.github.gilesi.samples.samplelibrary.IDoSomething.Do + + + com.github.gilesi.samples.samplelibrary.A.A + com.github.gilesi.samples.samplelibrary.A2.A2 + com.github.gilesi.samples.samplelibrary.Foo.Foo + com.github.gilesi.samples.samplelibrary.Foo2.Foo2 + com.github.gilesi.samples.samplelibrary.IDoSomething.IDoSomething + + /home/gus/Git/gilesi/Results + \ No newline at end of file diff --git a/Samples/samplelibrary/build.gradle b/Samples/Gradle/samplelibrary/build.gradle similarity index 63% rename from Samples/samplelibrary/build.gradle rename to Samples/Gradle/samplelibrary/build.gradle index 4f6e9b56..7068af54 100644 --- a/Samples/samplelibrary/build.gradle +++ b/Samples/Gradle/samplelibrary/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java' } -group = 'com.github.maracas.gilesi.samples.samplelibrary' +group = 'com.github.gilesi.samples.samplelibrary' version = '1.0-SNAPSHOT' repositories { @@ -11,9 +11,10 @@ repositories { dependencies { implementation 'com.fasterxml:jackson-xml-databind:0.6.2' - implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.1' - testImplementation platform('org.junit:junit-bom:5.9.1') + implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.2' + testImplementation platform('org.junit:junit-bom:5.11.3') testImplementation 'org.junit.jupiter:junit-jupiter' + testImplementation 'com.thoughtworks.xstream:xstream:1.4.20' } test { diff --git a/Samples/Gradle/samplelibrary/gradle/wrapper/gradle-wrapper.jar b/Samples/Gradle/samplelibrary/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..033e24c4 Binary files /dev/null and b/Samples/Gradle/samplelibrary/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Samples/Gradle/samplelibrary/gradle/wrapper/gradle-wrapper.properties b/Samples/Gradle/samplelibrary/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..1af9e093 --- /dev/null +++ b/Samples/Gradle/samplelibrary/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/Samples/Gradle/samplelibrary/gradlew b/Samples/Gradle/samplelibrary/gradlew new file mode 100644 index 00000000..cbc4dfc1 --- /dev/null +++ b/Samples/Gradle/samplelibrary/gradlew @@ -0,0 +1,246 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +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 + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/Samples/Gradle/samplelibrary/gradlew.bat b/Samples/Gradle/samplelibrary/gradlew.bat new file mode 100644 index 00000000..4b4ef2d7 --- /dev/null +++ b/Samples/Gradle/samplelibrary/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Samples/Gradle/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/A.java b/Samples/Gradle/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/A.java new file mode 100644 index 00000000..ab5da5d6 --- /dev/null +++ b/Samples/Gradle/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/A.java @@ -0,0 +1,38 @@ +package com.github.gilesi.samples.samplelibrary; + +public class A { + private final int j; + + public A(int j) { + this.j = j; + } + + public int foo(int i) { + if (j == 42) { + return 69; + } + + return j + (i * 2); + } + + public void CallBack(IDoSomething doSomething) { + doSomething.Do(); + foo(2); + } + + public static class A1 { + private final int j; + + public A1(int j) { + this.j = j; + } + + public int foo(int i) { + if (j == 42) { + return 69; + } + + return j + (i * 2); + } + } +} diff --git a/Samples/Gradle/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/A2.java b/Samples/Gradle/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/A2.java new file mode 100644 index 00000000..5966b95c --- /dev/null +++ b/Samples/Gradle/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/A2.java @@ -0,0 +1,25 @@ +package com.github.gilesi.samples.samplelibrary; + +public class A2 { + private int i; + + public A2(int i) { + this.i = i; + } + + public int foo(int i) { + return i + 2 * this.i; + } + + public void sideEffect() { + this.i *= 2; + } + + public A2 getOurselves() { + return this; + } + + public int[][] testingArraysLol(String[][] stringsss, Boolean[] bools, Long[][][] looooooongs) { + return new int[][] {new int[] {1,2},new int[] {4,5}}; + } +} diff --git a/Samples/samplelibrary/src/main/java/com/github/maracas/gilesi/samples/samplelibrary/Foo.java b/Samples/Gradle/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/Foo.java similarity index 89% rename from Samples/samplelibrary/src/main/java/com/github/maracas/gilesi/samples/samplelibrary/Foo.java rename to Samples/Gradle/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/Foo.java index 500b94ec..4109500d 100644 --- a/Samples/samplelibrary/src/main/java/com/github/maracas/gilesi/samples/samplelibrary/Foo.java +++ b/Samples/Gradle/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/Foo.java @@ -1,8 +1,7 @@ -package com.github.maracas.gilesi.samples.samplelibrary; +package com.github.gilesi.samples.samplelibrary; import java.util.Arrays; import java.util.Collection; -import java.util.stream.Collectors; public class Foo { public static String SomeValueStoredHere = "ha"; @@ -24,7 +23,7 @@ public static String HelloEveryone(String[] familly) { } public static String HelloEveryone2(Collection familly) { - String fam = familly.stream().collect(Collectors.joining()); + String fam = String.join("", familly); familly.clear(); familly.add("NewPersonA"); familly.add("NewPersonB"); diff --git a/Samples/samplelibrary/src/main/java/com/github/maracas/gilesi/samples/samplelibrary/Foo2.java b/Samples/Gradle/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/Foo2.java similarity index 89% rename from Samples/samplelibrary/src/main/java/com/github/maracas/gilesi/samples/samplelibrary/Foo2.java rename to Samples/Gradle/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/Foo2.java index 9d833c39..34b98d84 100644 --- a/Samples/samplelibrary/src/main/java/com/github/maracas/gilesi/samples/samplelibrary/Foo2.java +++ b/Samples/Gradle/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/Foo2.java @@ -1,8 +1,7 @@ -package com.github.maracas.gilesi.samples.samplelibrary; +package com.github.gilesi.samples.samplelibrary; import java.util.Arrays; import java.util.Collection; -import java.util.stream.Collectors; public class Foo2 { public static String SomeValueStoredHere = "ha"; @@ -24,7 +23,7 @@ public static String jhgbjtjbh(String[] familly) { } public static String njrhbgtujhu(Collection familly) { - String fam = familly.stream().collect(Collectors.joining()); + String fam = String.join("", familly); familly.clear(); familly.add("NewPersonA"); familly.add("NewPersonB"); diff --git a/Samples/Gradle/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/IDoSomething.java b/Samples/Gradle/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/IDoSomething.java new file mode 100644 index 00000000..fff02e20 --- /dev/null +++ b/Samples/Gradle/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/IDoSomething.java @@ -0,0 +1,5 @@ +package com.github.gilesi.samples.samplelibrary; + +public interface IDoSomething { + void Do(); +} diff --git a/Samples/Gradle/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/SelfCallTest.java b/Samples/Gradle/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/SelfCallTest.java new file mode 100644 index 00000000..2efae323 --- /dev/null +++ b/Samples/Gradle/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/SelfCallTest.java @@ -0,0 +1,11 @@ +package com.github.gilesi.samples.samplelibrary; + +public class SelfCallTest { + public static void FirstAPI() { + SecondAPI(); + } + + public static void SecondAPI() { + + } +} diff --git a/Samples/samplelibrary/src/test/java/ATest.java b/Samples/Gradle/samplelibrary/src/test/java/ATest.java similarity index 82% rename from Samples/samplelibrary/src/test/java/ATest.java rename to Samples/Gradle/samplelibrary/src/test/java/ATest.java index 6ae67154..b86dd475 100644 --- a/Samples/samplelibrary/src/test/java/ATest.java +++ b/Samples/Gradle/samplelibrary/src/test/java/ATest.java @@ -1,4 +1,4 @@ -import com.github.maracas.gilesi.samples.samplelibrary.A; +import com.github.gilesi.samples.samplelibrary.A; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -15,6 +15,9 @@ public void TestFoo() { public void TestFooIsLifeNice() { A a = new A(42); int result = a.foo(1); + + a.CallBack(this::TestFoo); + assertEquals(result, 69); } } diff --git a/Samples/Gradle/samplelibrary/src/test/java/CTest.java b/Samples/Gradle/samplelibrary/src/test/java/CTest.java new file mode 100644 index 00000000..67efedd0 --- /dev/null +++ b/Samples/Gradle/samplelibrary/src/test/java/CTest.java @@ -0,0 +1,34 @@ +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.DomDriver; +import com.thoughtworks.xstream.security.AnyTypePermission; +import com.thoughtworks.xstream.XStreamException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CTest { + @Test + public void Test3() throws XStreamException { + java.lang.Integer var2 = 5; + com.github.gilesi.samples.samplelibrary.A.A1 var1 = new com.github.gilesi.samples.samplelibrary.A.A1(var2); + + java.lang.Integer var4 = 1; + java.lang.Integer var3 = var1.foo(var4); + assertEquals("7", getToXml(var3)); + } + + public static String getToXml(Object obj) { + DomDriver domDriver = new DomDriver(); + XStream xStream = new XStream(domDriver); + return xStream.toXML(obj); + } + + @SuppressWarnings("unchecked") + public static T getFromXml(String xmlString) { + DomDriver domDriver = new DomDriver(); + XStream xStream = new XStream(domDriver); + xStream.addPermission(AnyTypePermission.ANY); + Object obj = xStream.fromXML(xmlString); + return (T) obj; + } +} \ No newline at end of file diff --git a/Samples/Gradle/settings.gradle b/Samples/Gradle/settings.gradle new file mode 100644 index 00000000..61edb789 --- /dev/null +++ b/Samples/Gradle/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'com.github.gilesi.samples' +include 'samplelibrary', 'sampleclient' \ No newline at end of file diff --git a/Samples/Maven/pom.xml b/Samples/Maven/pom.xml new file mode 100644 index 00000000..75e15923 --- /dev/null +++ b/Samples/Maven/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + com.github.gilesi.samples + sample2 + pom + Sample + Sample project + 1.0-SNAPSHOT + + + junit + junit + 4.13.2 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.11.0-M2 + test + + + + 11 + 11 + + + samplelibrary + sampleclient + + \ No newline at end of file diff --git a/Samples/Maven/sampleclient/pom.xml b/Samples/Maven/sampleclient/pom.xml new file mode 100644 index 00000000..4dabb1e5 --- /dev/null +++ b/Samples/Maven/sampleclient/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + com.github.gilesi.samples + sample2 + 1.0-SNAPSHOT + + sampleclient + 1.0-SNAPSHOT + + 11 + 11 + + + + junit + junit + 4.13.2 + test + + + com.github.gilesi.samples + samplelibrary + 1.0-SNAPSHOT + + + org.junit.jupiter + junit-jupiter-engine + 5.11.0-M2 + test + + + + + + maven-surefire-plugin + 3.2.5 + + 1 + false + -Dnet.bytebuddy.experimental=true -javaagent:com.github.gilesi.instrumentation.jar=TestWorkflowConfiguration.json + methods + 1 + 40 + 40 + 30 + 30 + + + + + diff --git a/Samples/Maven/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/C1.java b/Samples/Maven/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/C1.java new file mode 100644 index 00000000..d9d039c3 --- /dev/null +++ b/Samples/Maven/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/C1.java @@ -0,0 +1,11 @@ +package com.github.gilesi.samples.sampleclient; + +import com.github.gilesi.samples.samplelibrary.A2; + +public class C1 { + public static int Do(int i) { + A2 a = new A2(5); + a.sideEffect(); + return C2.bar(a); + } +} diff --git a/Samples/Maven/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/C2.java b/Samples/Maven/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/C2.java new file mode 100644 index 00000000..8867bcc0 --- /dev/null +++ b/Samples/Maven/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/C2.java @@ -0,0 +1,9 @@ +package com.github.gilesi.samples.sampleclient; + +import com.github.gilesi.samples.samplelibrary.A2; + +public class C2 { + public static int bar(A2 a) { + return a.foo(1); + } +} diff --git a/Samples/Maven/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/Main.java b/Samples/Maven/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/Main.java new file mode 100644 index 00000000..19cfb299 --- /dev/null +++ b/Samples/Maven/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/Main.java @@ -0,0 +1,15 @@ +package com.github.gilesi.samples.sampleclient; + +import com.github.gilesi.samples.samplelibrary.A; + +public class Main { + public static void main(String[] args) { + System.out.println("Hello world!"); + DoWork(); + } + + public static void DoWork() { + System.out.println("Is the answer of life the universe and everything nice? " + new A(42).foo(42)); + System.out.println("What about 5 and 1? " + new A(5).foo(1)); + } +} \ No newline at end of file diff --git a/Samples/Maven/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/Main2.java b/Samples/Maven/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/Main2.java new file mode 100644 index 00000000..3beeb9eb --- /dev/null +++ b/Samples/Maven/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/Main2.java @@ -0,0 +1,174 @@ +package com.github.gilesi.samples.sampleclient; + +import com.github.gilesi.samples.samplelibrary.Foo; +import com.github.gilesi.samples.samplelibrary.Foo2; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Main2 { + + public static void main(String[] args) { + Foo f = new Foo("yo"); + System.out.println(f.sayHelloFoo()); + + System.out.println("[Foo] Constructing now"); + Foo f2 = new Foo("yo2"); + System.out.println("[Foo] End of constructor"); + + System.out.println(); + + System.out.println("[hello] Calling now"); + String result = f2.hello(); + System.out.println("[hello] End of call: [" + result + "]"); + + System.out.println(); + + System.out.println("[sayHelloFoo] Calling now"); + result = f.sayHelloFoo(); + System.out.println("[sayHelloFoo] End of call: [" + result + "]"); + + System.out.println(); + + System.out.println("[hello] Calling now"); + result = f.hello(); + System.out.println("[hello] End of call: [" + result + "]"); + + System.out.println(); + + System.out.println("[hellow] Calling now"); + result = f.hellow("testestest"); + System.out.println("[hellow] End of call: [" + result + "]"); + + System.out.println(); + + System.out.println("[hellow] Calling now"); + result = f.hellow("testestest696969696"); + System.out.println("[hellow] End of call: [" + result + "]"); + + System.out.println(); + + System.out.println("[TESTING(STATIC)] Calling now"); + result = Foo.TESTING("YO"); + System.out.println("[TESTING(STATIC)] End of call: [" + result + "]"); + + System.out.println(); + + System.out.println("[TESTING(STATIC)] Calling now"); + result = Foo.TESTING("testestest696969696"); + System.out.println("[TESTING(STATIC)] End of call: [" + result + "]"); + + System.out.println(); + + String[] people = new String[]{"Toto", "Tata", "Titi", "JP", "PA", "JR"}; + + System.out.println("[HelloEveryone(STATIC)] Calling now"); + result = Foo.HelloEveryone(people); + System.out.println("[HelloEveryone(STATIC)] End of call: [" + result + "]"); + + System.out.println(); + + List peopleList = new ArrayList<>(Arrays.asList(people)); + + System.out.println("People List Right now:"); + for (String pe : peopleList) { + System.out.println(pe); + } + + System.out.println("[HelloEveryone2(STATIC)] Calling now"); + result = Foo.HelloEveryone2(peopleList); + System.out.println("[HelloEveryone2(STATIC)] End of call: [" + result + "]"); + + System.out.println("People List Right now:"); + for (String pe : peopleList) { + System.out.println(pe); + } + + System.out.println(Foo.meaningof(42)); + System.out.println(); + + Foo.SomeValueStoredHere = Foo.SomeValueStoredHere + Foo.SomeValueStoredHere; + + + Foo2 ff = new Foo2("yo"); + System.out.println(ff.sayHelloFoo()); + + System.out.println("[Foo2] Constructing now"); + Foo2 ff2 = new Foo2("yo2"); + System.out.println("[Foo2] End of constructor"); + + System.out.println(); + + System.out.println("[hello] Calling now"); + result = ff2.hello(); + System.out.println("[hello] End of call: [" + result + "]"); + + System.out.println(); + + System.out.println("[sayHelloFoo2] Calling now"); + result = ff.sayHelloFoo(); + System.out.println("[sayHelloFoo2] End of call: [" + result + "]"); + + System.out.println(); + + System.out.println("[hello] Calling now"); + result = ff.hello(); + System.out.println("[hello] End of call: [" + result + "]"); + + System.out.println(); + + System.out.println("[hellow] Calling now"); + result = ff.hellow("testestest"); + System.out.println("[hellow] End of call: [" + result + "]"); + + System.out.println(); + + System.out.println("[hellow] Calling now"); + result = ff.hellow("testestest696969696"); + System.out.println("[hellow] End of call: [" + result + "]"); + + System.out.println(); + + System.out.println("[TESTING(STATIC)] Calling now"); + result = Foo2.TESTING("YO"); + System.out.println("[TESTING(STATIC)] End of call: [" + result + "]"); + + System.out.println(); + + System.out.println("[TESTING(STATIC)] Calling now"); + result = Foo2.TESTING("testestest696969696"); + System.out.println("[TESTING(STATIC)] End of call: [" + result + "]"); + + System.out.println(); + + people = new String[]{"Toto", "Tata", "Titi", "JP", "PA", "JR"}; + + System.out.println("[HelloEveryone(STATIC)] Calling now"); + result = Foo2.jhgbjtjbh(people); + System.out.println("[HelloEveryone(STATIC)] End of call: [" + result + "]"); + + System.out.println(); + + peopleList = new ArrayList<>(Arrays.asList(people)); + + System.out.println("People List Right now:"); + for (String pe : peopleList) { + System.out.println(pe); + } + + System.out.println("[HelloEveryone2(STATIC)] Calling now"); + result = Foo2.njrhbgtujhu(peopleList); + System.out.println("[HelloEveryone2(STATIC)] End of call: [" + result + "]"); + + System.out.println("People List Right now:"); + for (String pe : peopleList) { + System.out.println(pe); + } + + System.out.println(Foo2.triuetyg(42)); + System.out.println(); + + Foo2.SomeValueStoredHere = Foo2.SomeValueStoredHere + Foo2.SomeValueStoredHere; + } +} \ No newline at end of file diff --git a/Samples/sampleclient/src/test/java/CTest.java b/Samples/Maven/sampleclient/src/test/java/CTest.java similarity index 51% rename from Samples/sampleclient/src/test/java/CTest.java rename to Samples/Maven/sampleclient/src/test/java/CTest.java index 6cbd048f..9fe2886a 100644 --- a/Samples/sampleclient/src/test/java/CTest.java +++ b/Samples/Maven/sampleclient/src/test/java/CTest.java @@ -1,5 +1,5 @@ -import com.github.maracas.gilesi.samples.samplelibrary.A2; -import com.github.maracas.gilesi.samples.sampleclient.C1; +import com.github.gilesi.samples.sampleclient.C1; +import com.github.gilesi.samples.samplelibrary.A2; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -8,14 +8,18 @@ public class CTest { @Test public void Test() { C1 c1 = new C1(); - int s = c1.Do(3); + int s = C1.Do(3); assertEquals(s, 21); } @Test public void ClientATest() { - com.github.maracas.gilesi.samples.samplelibrary.A2 a = new A2(5); + com.github.gilesi.samples.samplelibrary.A2 a = new A2(5); + a = a.getOurselves(); int r = a.foo(1); assertEquals(r, 11); + + A2 a2 = new A2(4); + a2.testingArraysLol(null, null, null); } } diff --git a/Samples/Maven/sampleclient/src/test/java/FooTest.java b/Samples/Maven/sampleclient/src/test/java/FooTest.java new file mode 100644 index 00000000..e34b122b --- /dev/null +++ b/Samples/Maven/sampleclient/src/test/java/FooTest.java @@ -0,0 +1,144 @@ +import com.github.gilesi.samples.samplelibrary.A; +import com.github.gilesi.samples.samplelibrary.Foo; +import com.github.gilesi.samples.samplelibrary.Foo2; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class FooTest { + @Test + public void mainTest() { + java.lang.String var2 = "yo"; + Foo var1 = new Foo(var2); + assertEquals(var2, "yo"); + + java.lang.String var3 = var1.sayHelloFoo(); + assertEquals(var3, "Hello in Foo!"); + + java.lang.String var5 = "yo2"; + Foo var4 = new Foo(var5); + assertEquals(var5, "yo2"); + + java.lang.String var6 = var4.hello(); + assertEquals(var6, "Hello yo2"); + + var3 = var1.sayHelloFoo(); + assertEquals(var3, "Hello in Foo!"); + + java.lang.String var7 = var1.hello(); + assertEquals(var7, "Hello yo"); + + java.lang.String var9 = "testestest"; + java.lang.String var8 = var1.hellow(var9); + assertEquals(var8, "Hello: testestest"); + assertEquals(var9, "testestest"); + + java.lang.String var11 = "testestest696969696"; + java.lang.String var10 = var1.hellow(var11); + assertEquals(var10, "420"); + assertEquals(var11, "testestest696969696"); + + java.lang.String var13 = "YO"; + java.lang.String var12 = Foo.TESTING(var13); + assertEquals(var12, "HELLO: YO"); + assertEquals(var13, "YO"); + + var11 = "testestest696969696"; + var10 = Foo.TESTING(var11); + assertEquals(var10, "420"); + assertEquals(var11, "testestest696969696"); + + java.lang.String[] var15 = new String[]{"Toto", "Tata", "Titi", "JP", "PA", "JR"}; + java.lang.String var14 = Foo.HelloEveryone(var15); + assertEquals(var14, "Hello [Toto, Tata, Titi, JP, PA, JR]"); + assertArrayEquals(var15, new String[]{"Toto", "Tata", "Titi", "JP", "PA", "JR"}); + + ArrayList var17 = new ArrayList(Arrays.stream(new String[]{"Toto", "Tata", "Titi", "JP", "PA", "JR"}).toList()); + ArrayList var171 = new ArrayList(Arrays.stream(new String[]{"NewPersonA", "NewPersonB", "NewPersonC", "NewPersonD"}).toList()); + java.lang.String var16 = Foo.HelloEveryone2(var17); + assertEquals(var16, "Hello TotoTataTitiJPPAJR"); + assertEquals(var17, var171); + + java.lang.Integer var19 = 42; + java.lang.String var18 = Foo.meaningof(var19); + assertEquals(var18, "the life the universe and everything"); + assertEquals(var19, 42); + + var2 = "yo"; + Foo2 var20 = new Foo2(var2); + assertEquals(var2, "yo"); + + var3 = var20.sayHelloFoo(); + assertEquals(var3, "Hello in Foo!"); + + var5 = "yo2"; + Foo2 var21 = new Foo2(var5); + assertEquals(var5, "yo2"); + + java.lang.String var22 = var21.hello(); + assertEquals(var22, "Hello yo2"); + + var3 = var20.sayHelloFoo(); + assertEquals(var3, "Hello in Foo!"); + + java.lang.String var23 = var20.hello(); + assertEquals(var23, "Hello yo"); + + var9 = "testestest"; + java.lang.String var24 = var20.hellow(var9); + assertEquals(var24, "Hello: testestest"); + assertEquals(var9, "testestest"); + + var11 = "testestest696969696"; + var10 = var20.hellow(var11); + assertEquals(var10, "420"); + assertEquals(var11, "testestest696969696"); + + var13 = "YO"; + java.lang.String var25 = Foo2.TESTING(var13); + assertEquals(var25, "HELLO: YO"); + assertEquals(var13, "YO"); + + var11 = "testestest696969696"; + var10 = Foo2.TESTING(var11); + assertEquals(var10, "420"); + assertEquals(var11, "testestest696969696"); + + java.lang.String[] var27 = new String[]{"Toto", "Tata", "Titi", "JP", "PA", "JR"}; + java.lang.String var26 = Foo2.jhgbjtjbh(var27); + assertEquals(var26, "Hello [Toto, Tata, Titi, JP, PA, JR]"); + assertArrayEquals(var27, new String[]{"Toto", "Tata", "Titi", "JP", "PA", "JR"}); + + ArrayList var29 = new ArrayList(Arrays.stream(new String[]{"Toto", "Tata", "Titi", "JP", "PA", "JR"}).toList()); + ArrayList var291 = new ArrayList(Arrays.stream(new String[]{"NewPersonA", "NewPersonB", "NewPersonC", "NewPersonD"}).toList()); + java.lang.String var28 = Foo2.njrhbgtujhu(var29); + assertEquals(var28, "Hello TotoTataTitiJPPAJR"); + assertEquals(var29, var291); + + var19 = 42; + var18 = Foo2.triuetyg(var19); + assertEquals(var18, "the life the universe and everything"); + assertEquals(var19, 42); + } + + @Test + public void TestFoo() { + A a = new A(5); + int result = a.foo(1); + assertEquals(result, 7); + } + + @Test + public void TestFooIsLifeNice() { + A a = new A(42); + int result = a.foo(1); + + a.CallBack(this::TestFoo); + + assertEquals(result, 69); + } +} \ No newline at end of file diff --git a/Samples/Maven/sampleclient/target/maven-archiver/pom.properties b/Samples/Maven/sampleclient/target/maven-archiver/pom.properties new file mode 100644 index 00000000..01523d44 --- /dev/null +++ b/Samples/Maven/sampleclient/target/maven-archiver/pom.properties @@ -0,0 +1,3 @@ +artifactId=sampleclient +groupId=com.github.gilesi.samples +version=1.0-SNAPSHOT diff --git a/Samples/Maven/sampleclient/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/Samples/Maven/sampleclient/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 00000000..e69de29b diff --git a/Samples/Maven/sampleclient/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/Samples/Maven/sampleclient/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 00000000..6e4487f4 --- /dev/null +++ b/Samples/Maven/sampleclient/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,4 @@ +/home/gus/Git/gilesi/Samples/Maven/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/Main.java +/home/gus/Git/gilesi/Samples/Maven/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/C1.java +/home/gus/Git/gilesi/Samples/Maven/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/Main2.java +/home/gus/Git/gilesi/Samples/Maven/sampleclient/src/main/java/com/github/gilesi/samples/sampleclient/C2.java diff --git a/Samples/Maven/sampleclient/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/Samples/Maven/sampleclient/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 00000000..e69de29b diff --git a/Samples/Maven/sampleclient/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/Samples/Maven/sampleclient/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 00000000..1fb78123 --- /dev/null +++ b/Samples/Maven/sampleclient/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -0,0 +1,2 @@ +/home/gus/Git/gilesi/Samples/Maven/sampleclient/src/test/java/CTest.java +/home/gus/Git/gilesi/Samples/Maven/sampleclient/src/test/java/FooTest.java diff --git a/Samples/Maven/sampleclient/target/surefire-reports/CTest.txt b/Samples/Maven/sampleclient/target/surefire-reports/CTest.txt new file mode 100644 index 00000000..8a08b499 --- /dev/null +++ b/Samples/Maven/sampleclient/target/surefire-reports/CTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: CTest +------------------------------------------------------------------------------- +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.310 s -- in CTest diff --git a/Samples/Maven/sampleclient/target/surefire-reports/FooTest.txt b/Samples/Maven/sampleclient/target/surefire-reports/FooTest.txt new file mode 100644 index 00000000..b959bdb5 --- /dev/null +++ b/Samples/Maven/sampleclient/target/surefire-reports/FooTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: FooTest +------------------------------------------------------------------------------- +Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.588 s -- in FooTest diff --git a/Samples/Maven/sampleclient/target/surefire-reports/TEST-CTest.xml b/Samples/Maven/sampleclient/target/surefire-reports/TEST-CTest.xml new file mode 100644 index 00000000..641cca70 --- /dev/null +++ b/Samples/Maven/sampleclient/target/surefire-reports/TEST-CTest.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/Maven/sampleclient/target/surefire-reports/TEST-FooTest.xml b/Samples/Maven/sampleclient/target/surefire-reports/TEST-FooTest.xml new file mode 100644 index 00000000..7d62c74f --- /dev/null +++ b/Samples/Maven/sampleclient/target/surefire-reports/TEST-FooTest.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/Maven/samplelibrary/TestWorkflowConfiguration.xml b/Samples/Maven/samplelibrary/TestWorkflowConfiguration.xml new file mode 100644 index 00000000..68fdd80b --- /dev/null +++ b/Samples/Maven/samplelibrary/TestWorkflowConfiguration.xml @@ -0,0 +1,204 @@ + + + + com.github.gilesi.samples.samplelibrary.Foo2 + + + triuetyg + + (I)Ljava/lang/String; + + + + TESTING + + (Ljava/lang/String;)Ljava/lang/String; + + + + sayHelloFoo + + ()Ljava/lang/String; + + + + hello + + (Ljava/lang/String;)V + ()Ljava/lang/String; + + + + njrhbgtujhu + + (Ljava/util/Collection;)Ljava/lang/String; + + + + hellow + + (Ljava/lang/String;)Ljava/lang/String; + + + + jhgbjtjbh + + ([Ljava/lang/String;)Ljava/lang/String; + + + + + (Ljava/lang/String;)V + + + + com.github.gilesi.samples.samplelibrary.A2 + + + getOurselves + + ()Lcom/github/gilesi/samples/samplelibrary/A2; + + + + foo + + (I)I + + + + sideEffect + + ()V + + + + + (I)V + + + + com.github.gilesi.samples.samplelibrary.Foo + + + HelloEveryone2 + + (Ljava/util/Collection;)Ljava/lang/String; + + + + TESTING + + (Ljava/lang/String;)Ljava/lang/String; + + + + meaningof + + (I)Ljava/lang/String; + + + + sayHelloFoo + + ()Ljava/lang/String; + + + + hello + + (Ljava/lang/String;)V + ()Ljava/lang/String; + + + + hellow + + (Ljava/lang/String;)Ljava/lang/String; + + + + HelloEveryone + + ([Ljava/lang/String;)Ljava/lang/String; + + + + + (Ljava/lang/String;)V + + + + com.github.gilesi.samples.samplelibrary.A + + + CallBack + + (Lcom/github/gilesi/samples/samplelibrary/IDoSomething;)V + + + + foo + + (I)I + + + + + (I)V + + + + + CTest.ClientATest + CTest.Test + FooTest.TestFoo + FooTest.TestFooIsLifeNice + FooTest.mainTest + com.github.gilesi.samples.sampleclient.C1.Do + com.github.gilesi.samples.sampleclient.C2.bar + com.github.gilesi.samples.sampleclient.Main.DoWork + com.github.gilesi.samples.sampleclient.Main.main + com.github.gilesi.samples.sampleclient.Main2.main + + + CTest.CTest + FooTest.FooTest + com.github.gilesi.samples.sampleclient.C1.C1 + com.github.gilesi.samples.sampleclient.C2.C2 + com.github.gilesi.samples.sampleclient.Main.Main + com.github.gilesi.samples.sampleclient.Main2.Main2 + + + com.github.gilesi.samples.samplelibrary.A.CallBack + com.github.gilesi.samples.samplelibrary.A.foo + com.github.gilesi.samples.samplelibrary.A2.foo + com.github.gilesi.samples.samplelibrary.A2.getOurselves + com.github.gilesi.samples.samplelibrary.A2.sideEffect + com.github.gilesi.samples.samplelibrary.Foo.HelloEveryone + com.github.gilesi.samples.samplelibrary.Foo.HelloEveryone2 + com.github.gilesi.samples.samplelibrary.Foo.TESTING + com.github.gilesi.samples.samplelibrary.Foo.hello + com.github.gilesi.samples.samplelibrary.Foo.hello + com.github.gilesi.samples.samplelibrary.Foo.hellow + com.github.gilesi.samples.samplelibrary.Foo.meaningof + com.github.gilesi.samples.samplelibrary.Foo.sayHelloFoo + com.github.gilesi.samples.samplelibrary.Foo2.TESTING + com.github.gilesi.samples.samplelibrary.Foo2.hello + com.github.gilesi.samples.samplelibrary.Foo2.hello + com.github.gilesi.samples.samplelibrary.Foo2.hellow + com.github.gilesi.samples.samplelibrary.Foo2.jhgbjtjbh + com.github.gilesi.samples.samplelibrary.Foo2.njrhbgtujhu + com.github.gilesi.samples.samplelibrary.Foo2.sayHelloFoo + com.github.gilesi.samples.samplelibrary.Foo2.triuetyg + com.github.gilesi.samples.samplelibrary.IDoSomething.Do + + + com.github.gilesi.samples.samplelibrary.A.A + com.github.gilesi.samples.samplelibrary.A2.A2 + com.github.gilesi.samples.samplelibrary.Foo.Foo + com.github.gilesi.samples.samplelibrary.Foo2.Foo2 + com.github.gilesi.samples.samplelibrary.IDoSomething.IDoSomething + + /home/gus/Git/gilesi/MyTestProject + \ No newline at end of file diff --git a/Samples/Maven/samplelibrary/pom.xml b/Samples/Maven/samplelibrary/pom.xml new file mode 100644 index 00000000..7494d3f1 --- /dev/null +++ b/Samples/Maven/samplelibrary/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + samplelibrary + 1.0-SNAPSHOT + + + com.github.gilesi.samples + sample2 + 1.0-SNAPSHOT + + jar + + + 11 + 11 + + + + junit + junit + 4.13.2 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.11.0-M2 + test + + + \ No newline at end of file diff --git a/Samples/samplelibrary/src/main/java/com/github/maracas/gilesi/samples/samplelibrary/A.java b/Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/A.java similarity index 51% rename from Samples/samplelibrary/src/main/java/com/github/maracas/gilesi/samples/samplelibrary/A.java rename to Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/A.java index 2957916b..b9751faa 100644 --- a/Samples/samplelibrary/src/main/java/com/github/maracas/gilesi/samples/samplelibrary/A.java +++ b/Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/A.java @@ -1,7 +1,8 @@ -package com.github.maracas.gilesi.samples.samplelibrary; +package com.github.gilesi.samples.samplelibrary; public class A { - private int j = 0; + private final int j; + public A(int j) { this.j = j; } @@ -13,4 +14,9 @@ public int foo(int i) { return j + (i * 2); } + + public void CallBack(IDoSomething doSomething) { + doSomething.Do(); + foo(2); + } } diff --git a/Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/A2.java b/Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/A2.java new file mode 100644 index 00000000..4b350610 --- /dev/null +++ b/Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/A2.java @@ -0,0 +1,25 @@ +package com.github.gilesi.samples.samplelibrary; + +public class A2 extends A { + private int i; + + public A2(int i) { + this.i = i; + } + + public int foo(int i) { + return i + 2 * this.i; + } + + public void sideEffect() { + this.i *= 2; + } + + public A2 getOurselves() { + return this; + } + + public int[][] testingArraysLol(String[][] stringsss, Boolean[] bools, Long[][][] looooooongs) { + return new int[][] {new int[] {1,2},new int[] {4,5}}; + } +} diff --git a/Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/Foo.java b/Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/Foo.java new file mode 100644 index 00000000..4109500d --- /dev/null +++ b/Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/Foo.java @@ -0,0 +1,60 @@ +package com.github.gilesi.samples.samplelibrary; + +import java.util.Arrays; +import java.util.Collection; + +public class Foo { + public static String SomeValueStoredHere = "ha"; + private final String value; + + public Foo(String value) { + this.value = value; + } + + public static String TESTING(String WHY) { + if (WHY.equals("testestest696969696")) { + return "420"; + } + return "HELLO: " + WHY; + } + + public static String HelloEveryone(String[] familly) { + return "Hello " + Arrays.toString(familly); + } + + public static String HelloEveryone2(Collection familly) { + String fam = String.join("", familly); + familly.clear(); + familly.add("NewPersonA"); + familly.add("NewPersonB"); + familly.add("NewPersonC"); + familly.add("NewPersonD"); + return "Hello " + fam; + } + + public static String meaningof(int value) { + if (value == 42) { + return "the life the universe and everything"; + } + return String.valueOf(value); + } + + public String sayHelloFoo() { + return "Hello in Foo!"; + } + + public void hello(String a) { + System.out.println("Hello: " + a); + } + + public String hello() { + return "Hello " + value; + } + + public String hellow(String a) { + if (a.equals("testestest696969696")) { + return "420"; + } + return "Hello: " + a; + } +} diff --git a/Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/Foo2.java b/Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/Foo2.java new file mode 100644 index 00000000..34b98d84 --- /dev/null +++ b/Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/Foo2.java @@ -0,0 +1,60 @@ +package com.github.gilesi.samples.samplelibrary; + +import java.util.Arrays; +import java.util.Collection; + +public class Foo2 { + public static String SomeValueStoredHere = "ha"; + private final String value; + + public Foo2(String value) { + this.value = value; + } + + public static String TESTING(String WHY) { + if (WHY.equals("testestest696969696")) { + return "420"; + } + return "HELLO: " + WHY; + } + + public static String jhgbjtjbh(String[] familly) { + return "Hello " + Arrays.toString(familly); + } + + public static String njrhbgtujhu(Collection familly) { + String fam = String.join("", familly); + familly.clear(); + familly.add("NewPersonA"); + familly.add("NewPersonB"); + familly.add("NewPersonC"); + familly.add("NewPersonD"); + return "Hello " + fam; + } + + public static String triuetyg(int value) { + if (value == 42) { + return "the life the universe and everything"; + } + return String.valueOf(value); + } + + public String sayHelloFoo() { + return "Hello in Foo!"; + } + + public void hello(String a) { + System.out.println("Hello: " + a); + } + + public String hello() { + return "Hello " + value; + } + + public String hellow(String a) { + if (a.equals("testestest696969696")) { + return "420"; + } + return "Hello: " + a; + } +} diff --git a/Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/IDoSomething.java b/Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/IDoSomething.java new file mode 100644 index 00000000..fff02e20 --- /dev/null +++ b/Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/IDoSomething.java @@ -0,0 +1,5 @@ +package com.github.gilesi.samples.samplelibrary; + +public interface IDoSomething { + void Do(); +} diff --git a/Samples/Maven/samplelibrary/src/test/java/ATest.java b/Samples/Maven/samplelibrary/src/test/java/ATest.java new file mode 100644 index 00000000..b86dd475 --- /dev/null +++ b/Samples/Maven/samplelibrary/src/test/java/ATest.java @@ -0,0 +1,23 @@ +import com.github.gilesi.samples.samplelibrary.A; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ATest { + @Test + public void TestFoo() { + A a = new A(5); + int result = a.foo(1); + assertEquals(result, 7); + } + + @Test + public void TestFooIsLifeNice() { + A a = new A(42); + int result = a.foo(1); + + a.CallBack(this::TestFoo); + + assertEquals(result, 69); + } +} diff --git a/Samples/Maven/samplelibrary/target/maven-archiver/pom.properties b/Samples/Maven/samplelibrary/target/maven-archiver/pom.properties new file mode 100644 index 00000000..0f0623ba --- /dev/null +++ b/Samples/Maven/samplelibrary/target/maven-archiver/pom.properties @@ -0,0 +1,3 @@ +artifactId=samplelibrary +groupId=com.github.gilesi.samples +version=1.0-SNAPSHOT diff --git a/Samples/Maven/samplelibrary/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/Samples/Maven/samplelibrary/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 00000000..e69de29b diff --git a/Samples/Maven/samplelibrary/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/Samples/Maven/samplelibrary/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 00000000..c3b4bc88 --- /dev/null +++ b/Samples/Maven/samplelibrary/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,5 @@ +/home/gus/Git/gilesi/Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/Foo.java +/home/gus/Git/gilesi/Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/IDoSomething.java +/home/gus/Git/gilesi/Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/Foo2.java +/home/gus/Git/gilesi/Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/A2.java +/home/gus/Git/gilesi/Samples/Maven/samplelibrary/src/main/java/com/github/gilesi/samples/samplelibrary/A.java diff --git a/Samples/Maven/samplelibrary/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/Samples/Maven/samplelibrary/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 00000000..e69de29b diff --git a/Samples/Maven/samplelibrary/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/Samples/Maven/samplelibrary/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 00000000..e987ae06 --- /dev/null +++ b/Samples/Maven/samplelibrary/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -0,0 +1 @@ +/home/gus/Git/gilesi/Samples/Maven/samplelibrary/src/test/java/ATest.java diff --git a/Samples/Maven/samplelibrary/target/surefire-reports/ATest.txt b/Samples/Maven/samplelibrary/target/surefire-reports/ATest.txt new file mode 100644 index 00000000..475da903 --- /dev/null +++ b/Samples/Maven/samplelibrary/target/surefire-reports/ATest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: ATest +------------------------------------------------------------------------------- +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.076 s -- in ATest diff --git a/Samples/Maven/samplelibrary/target/surefire-reports/TEST-ATest.xml b/Samples/Maven/samplelibrary/target/surefire-reports/TEST-ATest.xml new file mode 100644 index 00000000..79b4f370 --- /dev/null +++ b/Samples/Maven/samplelibrary/target/surefire-reports/TEST-ATest.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/sampleclient/build.gradle b/Samples/sampleclient/build.gradle deleted file mode 100644 index ae58e2d2..00000000 --- a/Samples/sampleclient/build.gradle +++ /dev/null @@ -1,23 +0,0 @@ -plugins { - id 'java' -} - -group = 'com.github.maracas.gilesi.samples.sampleclient' -version = '1.0-SNAPSHOT' - -repositories { - mavenCentral() -} - -dependencies { - implementation project(':samplelibrary') - implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.1' - testImplementation platform('org.junit:junit-bom:5.9.1') - testImplementation 'org.junit.jupiter:junit-jupiter' -} - -test { - useJUnitPlatform() - systemProperty 'net.bytebuddy.experimental', 'true' - jvmArgs = ['-XX:+EnableDynamicAgentLoading', '-javaagent:C:\\Users\\Gus\\Documents\\GitHub\\gilesi\\Instrumentation\\build\\libs\\com.github.maracas.gilesi.instrumentation.jar="C:\\Users\\Gus\\Documents\\GitHub\\gilesi\\TestWorkflowConfiguration.json"'] -} \ No newline at end of file diff --git a/Samples/sampleclient/src/main/java/com/github/maracas/gilesi/samples/sampleclient/C2.java b/Samples/sampleclient/src/main/java/com/github/maracas/gilesi/samples/sampleclient/C2.java deleted file mode 100644 index 2277ded0..00000000 --- a/Samples/sampleclient/src/main/java/com/github/maracas/gilesi/samples/sampleclient/C2.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.github.maracas.gilesi.samples.sampleclient; - -import com.github.maracas.gilesi.samples.samplelibrary.A2; - -public class C2 { - public static int bar(A2 a) { - return a.foo(1); - } -} diff --git a/Samples/samplelibrary/src/main/java/com/github/maracas/gilesi/samples/samplelibrary/A2.java b/Samples/samplelibrary/src/main/java/com/github/maracas/gilesi/samples/samplelibrary/A2.java deleted file mode 100644 index bfcaef3f..00000000 --- a/Samples/samplelibrary/src/main/java/com/github/maracas/gilesi/samples/samplelibrary/A2.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.github.maracas.gilesi.samples.samplelibrary; - -public class A2 { - private int i; - public A2(int i) { - this.i = i; - } - - public int foo(int i) { - return i + 2 * this.i; - } - - public void sideEffect() { - this.i *= 2; - } -} diff --git a/Samples/settings.gradle b/Samples/settings.gradle deleted file mode 100644 index f5840b90..00000000 --- a/Samples/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -rootProject.name = 'com.github.maracas.gilesi.samples' -include 'samplelibrary', 'sampleclient' \ No newline at end of file diff --git a/TestGenerator/.gitignore b/TestGenerator/.gitignore new file mode 100644 index 00000000..d4c6a6aa --- /dev/null +++ b/TestGenerator/.gitignore @@ -0,0 +1,39 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/* +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/TestGenerator/build.gradle b/TestGenerator/build.gradle new file mode 100644 index 00000000..d7f64a88 --- /dev/null +++ b/TestGenerator/build.gradle @@ -0,0 +1,77 @@ +plugins { + id 'application' + id 'com.github.johnrengelman.shadow' version '8.1.1' +} + +group 'com.github.gilesi.testgenerator' +version '1.0-SNAPSHOT' + + +compileJava { + options.encoding = 'UTF-8' +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} + +repositories { + mavenCentral() + mavenLocal() + maven { + url 'https://packages.jetbrains.team/maven/p/ij/intellij-dependencies' + } +} + +dependencies { + implementation 'com.fasterxml.jackson.core:jackson-core:2.18.1' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.2' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.1' + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.17.2' + implementation 'com.fasterxml.jackson:jackson-base:2.18.1' + implementation 'org.apache.commons:commons-text:1.12.0' + implementation 'com.thoughtworks.xstream:xstream:1.4.20' +} + +jar { + manifest { + attributes 'Main-Class': 'com.github.gilesi.testgenerator.Main', + 'Multi-Release': 'true' + } +} + +application { + mainClass = 'com.github.gilesi.testgenerator.Main' +} + +description = 'Main distribution.' + +shadowJar { + archiveBaseName.set('com.github.gilesi.testgenerator') + archiveClassifier.set('') + archiveVersion.set('') + mergeServiceFiles() +} + +distributions { + shadow { + distributionBaseName = 'com.github.gilesi.testgenerator' + } +} + +apply plugin: 'java' +apply plugin: 'idea' + +idea { + module { + downloadJavadoc = true + downloadSources = true + } +} + +run { + jvmArgs = [ + "-XX:InitialHeapSize=2G", + "-XX:MaxHeapSize=2G" + ] +} \ No newline at end of file diff --git a/TestGenerator/gradle/wrapper/gradle-wrapper.jar b/TestGenerator/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..033e24c4 Binary files /dev/null and b/TestGenerator/gradle/wrapper/gradle-wrapper.jar differ diff --git a/TestGenerator/gradle/wrapper/gradle-wrapper.properties b/TestGenerator/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..1af9e093 --- /dev/null +++ b/TestGenerator/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/TestGenerator/gradlew b/TestGenerator/gradlew new file mode 100644 index 00000000..cbc4dfc1 --- /dev/null +++ b/TestGenerator/gradlew @@ -0,0 +1,246 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +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 + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/TestGenerator/gradlew.bat b/TestGenerator/gradlew.bat new file mode 100644 index 00000000..4b4ef2d7 --- /dev/null +++ b/TestGenerator/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/TestGenerator/src/main/java/com/github/gilesi/confgen/models/Class.java b/TestGenerator/src/main/java/com/github/gilesi/confgen/models/Class.java new file mode 100644 index 00000000..d8e63389 --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/confgen/models/Class.java @@ -0,0 +1,7 @@ +package com.github.gilesi.confgen.models; + +public class Class { + public String ClassName; + public Method[] ClassMethods; + public String[] ClassDescriptors; +} diff --git a/TestGenerator/src/main/java/com/github/gilesi/confgen/models/InstrumentationParameters.java b/TestGenerator/src/main/java/com/github/gilesi/confgen/models/InstrumentationParameters.java new file mode 100644 index 00000000..2fa9ceac --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/confgen/models/InstrumentationParameters.java @@ -0,0 +1,7 @@ +package com.github.gilesi.confgen.models; + +public class InstrumentationParameters { + public Class[] InstrumentedAPIClasses; + public String[] LibraryTypes; + public String traceOutputLocation; +} diff --git a/TestGenerator/src/main/java/com/github/gilesi/confgen/models/Method.java b/TestGenerator/src/main/java/com/github/gilesi/confgen/models/Method.java new file mode 100644 index 00000000..8ef6c1e1 --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/confgen/models/Method.java @@ -0,0 +1,6 @@ +package com.github.gilesi.confgen.models; + +public class Method { + public String MethodName; + public String[] MethodDescriptors; +} \ No newline at end of file diff --git a/TestGenerator/src/main/java/com/github/gilesi/instrumentation/ConfigurationReader.java b/TestGenerator/src/main/java/com/github/gilesi/instrumentation/ConfigurationReader.java new file mode 100644 index 00000000..27f4c5ae --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/instrumentation/ConfigurationReader.java @@ -0,0 +1,12 @@ +package com.github.gilesi.instrumentation; + +import com.github.gilesi.confgen.models.InstrumentationParameters; + +import java.io.File; +import java.io.IOException; + +public class ConfigurationReader { + public static InstrumentationParameters parseInstrumentationparameters(File file) throws IOException { + return XmlUtils.objectMapper.readValue(file, InstrumentationParameters.class); + } +} diff --git a/TestGenerator/src/main/java/com/github/gilesi/instrumentation/XmlUtils.java b/TestGenerator/src/main/java/com/github/gilesi/instrumentation/XmlUtils.java new file mode 100644 index 00000000..3b46a7f2 --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/instrumentation/XmlUtils.java @@ -0,0 +1,51 @@ +package com.github.gilesi.instrumentation; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.DomDriver; +import java.io.File; +import java.io.FileWriter; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class XmlUtils { + private static DomDriver domDriver = new DomDriver(); + private static XStream xStream = new XStream(domDriver); + private static Object syncObj = new Object(); + public static final ObjectMapper objectMapper = new ObjectMapper() + .enable(SerializationFeature.INDENT_OUTPUT) + //.enable(SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL) + .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) + .disable(SerializationFeature.FAIL_ON_SELF_REFERENCES) + .disable(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS); + + public static String getToXml(Object obj) { + return xStream.toXML(obj); + } + + public static String getToJson(Object obj) throws JsonProcessingException { + return objectMapper.writeValueAsString(obj); + } + + public static void saveToUniquePath(String basePath, String baseName, String baseExtension, String content) { + synchronized (syncObj) { + int padding = 1; + String finalFileName = String.format("%s%s%s_%d.%s", basePath, File.separatorChar, baseName, padding, baseExtension); + + while (Files.exists(Paths.get(finalFileName))) { + finalFileName = String.format("%s%s%s_%d.%s", basePath, File.separatorChar, baseName, ++padding, baseExtension); + } + + try { + FileWriter outputFile = new FileWriter(finalFileName); + outputFile.write(content); + outputFile.close(); + } catch (Throwable e) { + System.err.println("ERROR"); + e.printStackTrace(); + } + } + } +} diff --git a/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/ClassReference.java b/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/ClassReference.java new file mode 100644 index 00000000..9e25ea02 --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/ClassReference.java @@ -0,0 +1,44 @@ +package com.github.gilesi.instrumentation.models; + +public class ClassReference { + private String fullyQualifiedTypeName; + private ClassReference[] interfaces; + private ClassReference superClass; + private GenericReference[] genericParameters; + + public ClassReference() { + + } + + public String getFullyQualifiedTypeName() { + return fullyQualifiedTypeName; + } + + public ClassReference[] getInterfaces() { + return interfaces; + } + + public ClassReference getSuperClass() { + return superClass; + } + + public GenericReference[] getGenericParameters() { + return genericParameters; + } + + public void setFullyQualifiedTypeName(String fullyQualifiedTypeName) { + this.fullyQualifiedTypeName = fullyQualifiedTypeName; + } + + public void setInterfaces(ClassReference[] interfaces) { + this.interfaces = interfaces; + } + + public void setSuperClass(ClassReference superClass) { + this.superClass = superClass; + } + + public void setGenericParameters(GenericReference[] genericParameters) { + this.genericParameters = genericParameters; + } +} diff --git a/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/GenericReference.java b/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/GenericReference.java new file mode 100644 index 00000000..10eca61e --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/GenericReference.java @@ -0,0 +1,26 @@ +package com.github.gilesi.instrumentation.models; + +public class GenericReference { + private String name; + private ClassReference[] classBounds; + + public GenericReference() { + + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public ClassReference[] getClassBounds() { + return classBounds; + } + + public void getClassBounds(ClassReference[] classBounds) { + this.classBounds = classBounds; + } +} diff --git a/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/InstanceReference.java b/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/InstanceReference.java new file mode 100644 index 00000000..2170cc86 --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/InstanceReference.java @@ -0,0 +1,49 @@ +package com.github.gilesi.instrumentation.models; + +public class InstanceReference { + private int instanceId = -1; + private String valueAsXmlString = null; + private String valueAsJsonString = null; + private boolean isNull = true; + private ClassReference classReference = null; + + public int getInstanceId() { + return instanceId; + } + + public String getValueAsXmlString() { + return valueAsXmlString; + } + + public String getValueAsJsonString() { + return valueAsJsonString; + } + + public boolean getIsNull() { + return isNull; + } + + public ClassReference getClassReference() { + return classReference; + } + + public void setInstanceId(int instanceId) { + this.instanceId = instanceId; + } + + public void setValueAsXmlString(String valueAsXmlString) { + this.valueAsXmlString = valueAsXmlString; + } + + public void setValueAsJsonString(String valueAsJsonString) { + this.valueAsJsonString = valueAsJsonString; + } + + public void setIsNull(boolean isNull) { + this.isNull = isNull; + } + + public void setClassReference(ClassReference classReference) { + this.classReference = classReference; + } +} diff --git a/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/MethodReference.java b/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/MethodReference.java new file mode 100644 index 00000000..94dc8eda --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/MethodReference.java @@ -0,0 +1,62 @@ +package com.github.gilesi.instrumentation.models; + +public class MethodReference { + private String methodSignature; + private ClassReference[] parameterTypes; + private ClassReference returnType; + private boolean isConstructor; + private GenericReference[] genericParameters; + private ClassReference genericReturn; + + public MethodReference() { + + } + + public String getMethodSignature() { + return methodSignature; + } + + public ClassReference[] getParameterTypes() { + return parameterTypes; + } + + public ClassReference getReturnType() { + return returnType; + } + + public boolean getIsConstructor() { + return isConstructor; + } + + public GenericReference[] getGenericParameters() { + return genericParameters; + } + + public ClassReference getGenericReturn() { + return genericReturn; + } + + public void setMethodSignature(String methodSignature) { + this.methodSignature = methodSignature; + } + + public void setParameterTypes(ClassReference[] parameterTypes) { + this.parameterTypes = parameterTypes; + } + + public void setReturnType(ClassReference returnType) { + this.returnType = returnType; + } + + public void setIsConstructor(boolean isConstructor) { + this.isConstructor = isConstructor; + } + + public void setGenericParameters(GenericReference[] genericParameters) { + this.genericParameters = genericParameters; + } + + public void setGenericReturn(ClassReference genericReturn) { + this.genericReturn = genericReturn; + } +} diff --git a/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/PartialTrace.java b/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/PartialTrace.java new file mode 100644 index 00000000..d2a7d482 --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/PartialTrace.java @@ -0,0 +1,57 @@ +package com.github.gilesi.instrumentation.models; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public class PartialTrace { + private List preCallArguments; + private long timeStampEntry; + private InstanceReference instanceReference; + private MethodReference methodReference; + + @JsonCreator + public PartialTrace( + @JsonProperty("preCallArguments") List preCallArguments, + @JsonProperty("timeStampEntry") long timeStampEntry, + @JsonProperty("instanceReference") InstanceReference instanceReference, + @JsonProperty("methodReference") MethodReference methodReference) { + this.preCallArguments = preCallArguments; + this.timeStampEntry = timeStampEntry; + this.instanceReference = instanceReference; + this.methodReference = methodReference; + } + + public List getPreCallArguments() { + return preCallArguments; + } + + public void setPreCallArguments(List preCallArguments) { + this.preCallArguments = preCallArguments; + } + + public long getTimeStampEntry() { + return timeStampEntry; + } + + public void setTimeStampEntry(long timeStampEntry) { + this.timeStampEntry = timeStampEntry; + } + + public InstanceReference getInstanceReference() { + return instanceReference; + } + + public MethodReference getMethodReference() { + return methodReference; + } + + public void setInstanceReference(InstanceReference instanceReference) { + this.instanceReference = instanceReference; + } + + public void setMethodReference(MethodReference methodReference) { + this.methodReference = methodReference; + } +} \ No newline at end of file diff --git a/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/TestTraceResults.java b/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/TestTraceResults.java new file mode 100644 index 00000000..e277a415 --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/TestTraceResults.java @@ -0,0 +1,6 @@ +package com.github.gilesi.instrumentation.models; + +public class TestTraceResults { + public Trace Trace; + public String Test; +} diff --git a/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/Trace.java b/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/Trace.java new file mode 100644 index 00000000..9dee5a0a --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/instrumentation/models/Trace.java @@ -0,0 +1,96 @@ +package com.github.gilesi.instrumentation.models; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public class Trace extends PartialTrace { + private List postCallArguments; + private InstanceReference returnedValue; + private long timeStampExit; + private InstanceReference exceptionThrown; + private String[] stackTrace; + private String confName; + private String[] confDescriptors; + + @JsonCreator + public Trace( + @JsonProperty("preCallArguments") List preCallArguments, + @JsonProperty("postCallArguments") List postCallArguments, + @JsonProperty("returnedValue") InstanceReference returnedValue, + @JsonProperty("timeStampEntry") long timeStampEntry, + @JsonProperty("timeStampExit") long timeStampExit, + @JsonProperty("exceptionThrown") InstanceReference exceptionThrown, + @JsonProperty("stackTrace") String[] stackTrace, + @JsonProperty("confName") String confName, + @JsonProperty("confDescriptors") String[] confDescriptors, + @JsonProperty("instanceReference") InstanceReference instanceReference, + @JsonProperty("methodReference") MethodReference methodReference) { + super(preCallArguments, timeStampEntry, instanceReference, methodReference); + + this.postCallArguments = postCallArguments; + this.returnedValue = returnedValue; + this.timeStampExit = timeStampExit; + this.exceptionThrown = exceptionThrown; + this.stackTrace = stackTrace; + this.confName = confName; + this.confDescriptors = confDescriptors; + } + + public List getPostCallArguments() { + return postCallArguments; + } + + public void setPostCallArguments(List postCallArguments) { + this.postCallArguments = postCallArguments; + } + + public InstanceReference getReturnedValue() { + return returnedValue; + } + + public void setReturnedValue(InstanceReference returnedValue) { + this.returnedValue = returnedValue; + } + + public long getTimeStampExit() { + return timeStampExit; + } + + public void setTimeStampExit(long timeStampExit) { + this.timeStampExit = timeStampExit; + } + + public InstanceReference getExceptionThrown() { + return exceptionThrown; + } + + public void setExceptionThrown(InstanceReference exceptionThrown) { + this.exceptionThrown = exceptionThrown; + } + + public String[] getStackTrace() { + return stackTrace; + } + + public void setStackTrace(String[] stackTrace) { + this.stackTrace = stackTrace; + } + + public String getConfName() { + return confName; + } + + public void setConfName(String confName) { + this.confName = confName; + } + + public String[] getConfDescriptors() { + return confDescriptors; + } + + public void setConfDescriptors(String[] confDescriptors) { + this.confDescriptors = confDescriptors; + } +} \ No newline at end of file diff --git a/TestGenerator/src/main/java/com/github/gilesi/testgenerator/ClassUtils.java b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/ClassUtils.java new file mode 100644 index 00000000..2f4f8c86 --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/ClassUtils.java @@ -0,0 +1,116 @@ +package com.github.gilesi.testgenerator; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; + +import com.github.gilesi.instrumentation.models.Trace; + +public class ClassUtils { + private static void generateClassFileHeader(StringBuilder stringBuilder, String packageName) { + if (!packageName.isEmpty()) { + stringBuilder.append("package %s;\n\n".formatted(packageName)); + } + + stringBuilder.append("import com.thoughtworks.xstream.XStream;\n"); + stringBuilder.append("import com.thoughtworks.xstream.io.xml.DomDriver;\n"); + stringBuilder.append("import com.thoughtworks.xstream.security.AnyTypePermission;\n"); + stringBuilder.append("import com.thoughtworks.xstream.XStreamException;\n"); + stringBuilder.append("import org.junit.jupiter.api.Test;\n"); + stringBuilder.append("import java.lang.Throwable;\n"); + stringBuilder.append("\n"); + stringBuilder.append("import static org.junit.jupiter.api.Assertions.assertEquals;\n"); + stringBuilder.append("import static org.junit.jupiter.api.Assertions.assertArrayEquals;\n"); + stringBuilder.append("\n"); + } + + public static void generateClassFile(ArrayList methodTraces, String testPath) throws IOException { + Path mainOutputPath = Path.of(testPath).toAbsolutePath(); + if (!Files.isDirectory(mainOutputPath)) { + Files.createDirectories(mainOutputPath); + } + + Path packageOutputPath = mainOutputPath.toAbsolutePath(); + + if (!StaticConfiguration.GENERATED_PACKAGE_NAME.isEmpty()) { + for (String element : StaticConfiguration.GENERATED_PACKAGE_NAME.split("\\.")) { + packageOutputPath = packageOutputPath.resolve(element); + } + + if (!Files.isDirectory(packageOutputPath)) { + Files.createDirectories(packageOutputPath); + } + } + + Path classOutputPath = packageOutputPath.resolve("%s.java".formatted(StaticConfiguration.GENERATED_CLASS_NAME)); + + StringBuilder stringBuilder = new StringBuilder(); + + generateClassFileHeader(stringBuilder, StaticConfiguration.GENERATED_PACKAGE_NAME); + + stringBuilder.append("public class %s {\n".formatted(StaticConfiguration.GENERATED_CLASS_NAME)); + + stringBuilder.append("\t@Test\n"); + stringBuilder + .append("\tpublic void %s() throws Throwable {\n".formatted(StaticConfiguration.GENERATED_TEST_NAME)); + + VariableStackHandler variableStackHandler = new VariableStackHandler(); + + StringBuilder testMethodStringBuilder = new StringBuilder(); + + for (int i = 0; i < methodTraces.size(); i++) { + System.out.println("Processing Trace %d out of %d".formatted(i + 1, methodTraces.size())); + + Trace trace = methodTraces.get(i); + + try { + TraceUtils.parseTrace(i, trace, variableStackHandler, testMethodStringBuilder); + + if (i != methodTraces.size() - 1) { + testMethodStringBuilder.append("\n"); + } + } catch (Exception e) { + System.out.println("Unable to convert trace to code!"); + e.printStackTrace(); + MarkerUtils.writeMarker(Arrays.stream(e.toString().split("\\n")).toList(), mainOutputPath.toString()); + + stringBuilder.append("\t\t// %s \n".formatted(trace.getMethodReference().getMethodSignature())); + } + } + + String padding = "\t\t"; + String paddedString = String.format("%s%s", padding, testMethodStringBuilder.toString() + .substring(0, testMethodStringBuilder.length() - 1).replace("\n", String.format("\n%s", padding))); + stringBuilder.append(String.format("%s\n\t}\n", paddedString)); + + stringBuilder.append(""" + + \tpublic static String getToXml(Object obj) { + \t\tDomDriver domDriver = new DomDriver(); + \t\tXStream xStream = new XStream(domDriver); + \t\treturn xStream.toXML(obj); + \t} + """); + + stringBuilder.append(""" + + \t@SuppressWarnings("unchecked") + \tpublic static T getFromXml(String xmlString) { + \t\tDomDriver domDriver = new DomDriver(); + \t\tXStream xStream = new XStream(domDriver); + \t\txStream.addPermission(AnyTypePermission.ANY); + \t\tObject obj = xStream.fromXML(xmlString); + \t\treturn (T) obj; + \t} + """); + + stringBuilder.append("}\n"); + stringBuilder.append("\n"); + + System.out.println(classOutputPath); + Files.deleteIfExists(classOutputPath); + Files.write(Files.createFile(classOutputPath), stringBuilder.toString().getBytes()); + } +} diff --git a/TestGenerator/src/main/java/com/github/gilesi/testgenerator/DescriptorUtils.java b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/DescriptorUtils.java new file mode 100644 index 00000000..b7c14386 --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/DescriptorUtils.java @@ -0,0 +1,129 @@ +package com.github.gilesi.testgenerator; + +import java.util.ArrayList; +import java.util.List; + +import com.github.gilesi.testgenerator.exceptions.UnsupportedJavaPrimitiveTypeException; + +public class DescriptorUtils { + public static int getFirstArgLength(String name) throws UnsupportedJavaPrimitiveTypeException { + if (name.length() == 0) { + return 0; + } + + return switch (name.charAt(0)) { + case 'I' -> 1; + case 'V' -> 1; + case 'Z' -> 1; + case 'B' -> 1; + case 'C' -> 1; + case 'S' -> 1; + case 'D' -> 1; + case 'F' -> 1; + case 'J' -> 1; + case 'L' -> name.indexOf(";") + 1; + case '[' -> getFirstArgLength(name.substring(1)) + 1; + default -> throw new UnsupportedJavaPrimitiveTypeException(name); + }; + } + + public static String getCleanedType(String FQN) { + int currentCharacterIndex = 0; + char currentCharacter = FQN.charAt(currentCharacterIndex); + while (currentCharacter == '[') { + currentCharacter = FQN.charAt(++currentCharacterIndex); + } + + if (((currentCharacter == 'I' || currentCharacter == 'V' || + currentCharacter == 'Z' || currentCharacter == 'B' || + currentCharacter == 'C' || currentCharacter == 'S' || + currentCharacter == 'D' || currentCharacter == 'F' || + currentCharacter == 'J') && FQN.length() == currentCharacterIndex + 1) || + (currentCharacter == 'L' && FQN.endsWith(";"))) { + try { + return JvmTypeToLangType(FQN).replace("$", "."); + } catch (UnsupportedJavaPrimitiveTypeException e) { + System.out.println(String.format("Unsupported Java Primitive Type Exception! %c", currentCharacter)); + e.printStackTrace(); + } + } + return FQN.replace("$", "."); + } + + private static String JvmTypeToLangType(String name) throws UnsupportedJavaPrimitiveTypeException { + return switch (name.charAt(0)) { + case 'I' -> "java.lang.Integer"; + case 'V' -> "java.lang.Void"; + case 'Z' -> "java.lang.Boolean"; + case 'B' -> "java.lang.Byte"; + case 'C' -> "java.lang.Character"; + case 'S' -> "java.lang.Short"; + case 'D' -> "java.lang.Double"; + case 'F' -> "java.lang.Float"; + case 'J' -> "java.lang.Long"; + case 'L' -> name.substring(1, name.length() - 1).replace("/", "."); + case '[' -> "%s[]".formatted(JvmTypeToLangType(name.substring(1))); + default -> throw new UnsupportedJavaPrimitiveTypeException(name); + }; + } + + public static void convertDescriptorToNormalTypesTest(String descr) throws UnsupportedJavaPrimitiveTypeException { + int startArg = descr.indexOf("("); + int endArg = descr.lastIndexOf(")"); + + String returns = descr.substring(endArg + 1, descr.length()); + String argusStr = descr.substring(startArg + 1, endArg); + + int length = 1; + List arguments = new ArrayList<>(); + String returnValue = null; + + do { + length = getFirstArgLength(argusStr); + String curArg = argusStr.substring(0, length); + argusStr = argusStr.substring(length); + if (curArg.length() != 0) { + arguments.add(getCleanedType(curArg)); + } + } while (length != 0); + + if (!returns.equals("V")) { + returnValue = getCleanedType(returns); + } + + System.out.println("Descriptor: " + descr); + + if (arguments.size() != 0) { + System.out.println("Arguments: (%s)".formatted(String.join(", ", arguments))); + } + + if (returnValue != null) { + System.out.println("Returns: " + returnValue); + } + } + + public static String getCleanedVar(String fullyQualifiedTypeName) { + String returnType = fullyQualifiedTypeName.replace("$", "."); + + try { + var el = returnType.split("\\."); + Integer.valueOf(el[el.length - 1]); + + // so this is a number, replace type with "var" as we have no idea. + returnType = "var"; + } catch (NumberFormatException e) { + // Not a number, continue + } + + if (returnType.contains("..")) { + // Bad type, use var + returnType = "var"; + } + + if (returnType.endsWith(".")) { + returnType = "var"; + } + + return returnType; + } +} diff --git a/TestGenerator/src/main/java/com/github/gilesi/testgenerator/Main.java b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/Main.java new file mode 100644 index 00000000..39a3ce3b --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/Main.java @@ -0,0 +1,74 @@ +package com.github.gilesi.testgenerator; + +import com.github.gilesi.confgen.models.InstrumentationParameters; +import com.github.gilesi.instrumentation.ConfigurationReader; +import com.github.gilesi.instrumentation.models.Trace; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; + +public class Main { + public static InstrumentationParameters instrumentationParameters; + + private static void processArguments(String[] args) throws IOException { + if (args.length != 3) { + System.out.println("Usage: "); + return; + } + + String traceXmlFolder = args[0]; + String testPath = args[1]; + String libraryConfigPath = args[2]; + + if (!Files.exists(Path.of(traceXmlFolder)) || !Files.isDirectory(Path.of(traceXmlFolder))) { + System.out.println( + "Argument 1 is not a Folder containing trace files. It either does not exist or is not a directory."); + return; + } + + Files.createDirectories(Path.of(testPath)); + + if (!Files.exists(Path.of(testPath)) || !Files.isDirectory(Path.of(testPath))) { + System.out.println( + "Argument 2 is not a path to contain generated java test code. It either does not exist or is not a directory."); + return; + } + + if (!Files.exists(Path.of(libraryConfigPath)) || Files.isDirectory(Path.of(libraryConfigPath))) { + System.out.println( + "Argument 3 is not a path to a library configuration. It either does not exist or is not a file."); + return; + } + + ArrayList methodTraces = TraceReader.readTraces(traceXmlFolder); + + if (methodTraces.isEmpty()) { + System.out.println("No traces found!"); + } + + // Get the parameters + File configurationFile = new File(libraryConfigPath); + if (!configurationFile.exists()) { + System.err.printf("ERROR: the passed in configuration file (%s) does not exist or could not be found!%n", libraryConfigPath); + return; + } + + try { + instrumentationParameters = ConfigurationReader.parseInstrumentationparameters(configurationFile); + } catch (IOException ioException) { + System.err.printf("ERROR: Could not parse instrumentation configuration file (%s)!%n", libraryConfigPath); + System.err.println(ioException.getMessage()); + ioException.printStackTrace(); + return; + } + + ClassUtils.generateClassFile(methodTraces, testPath); + } + + public static void main(String[] args) throws IOException { + processArguments(args); + } +} \ No newline at end of file diff --git a/TestGenerator/src/main/java/com/github/gilesi/testgenerator/MarkerUtils.java b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/MarkerUtils.java new file mode 100644 index 00000000..b9e14055 --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/MarkerUtils.java @@ -0,0 +1,26 @@ +package com.github.gilesi.testgenerator; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; + +public class MarkerUtils { + public static void writeMarker(List failure, String output) { + int mainPadding = 1; + String tracesFileName = String.format("%s%sgilesi.testgen_failure_%d.marker", output, File.separatorChar, + mainPadding); + + while (Files.exists(Paths.get(tracesFileName))) { + tracesFileName = String.format("%s%sgilesi.testgen_failure_%d.marker", output, File.separatorChar, + ++mainPadding); + } + + try { + Files.write(new File(tracesFileName).toPath(), failure); + } catch (Throwable e) { + System.err.println("ERROR while saving marker"); + e.printStackTrace(); + } + } +} diff --git a/TestGenerator/src/main/java/com/github/gilesi/testgenerator/StaticConfiguration.java b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/StaticConfiguration.java new file mode 100644 index 00000000..1e2cd6de --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/StaticConfiguration.java @@ -0,0 +1,12 @@ +package com.github.gilesi.testgenerator; + +public class StaticConfiguration { + public static final String GENERATED_TEST_NAME = "ClientSourcedGeneratedTest"; + public static final String GENERATED_PACKAGE_NAME = "com.gilesi.testgen.generated"; + public static final String GENERATED_CLASS_NAME = "GilesiGeneratedTestClass"; + + public static final boolean USE_ALTERNATIVE_ASSERTION_MODE = false; + public static final boolean GENERATE_INPUT_VALUES_ASSERTIONS_POST_METHOD_CALL = false; + + public static final boolean SKIP_LONG_ARG_STRING_FOR_DEBUGGING = true; +} \ No newline at end of file diff --git a/TestGenerator/src/main/java/com/github/gilesi/testgenerator/TraceReader.java b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/TraceReader.java new file mode 100644 index 00000000..acc9da93 --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/TraceReader.java @@ -0,0 +1,48 @@ +package com.github.gilesi.testgenerator; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.github.gilesi.instrumentation.models.TestTraceResults; +import com.github.gilesi.instrumentation.models.Trace; + +public class TraceReader { + private static final ObjectMapper objectMapper = new ObjectMapper() + .enable(SerializationFeature.INDENT_OUTPUT) + // .enable(SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL) + .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) + .disable(SerializationFeature.FAIL_ON_SELF_REFERENCES) + .disable(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS); + + // This is ugly, I know + private static int GetTraceNumber(Path i) { + return Integer.valueOf(i.getFileName().toString().split("_")[1].replace(".json", "")); + } + + public static ArrayList readTraces(String traceXmlFolder) throws IOException { + ArrayList traces = new ArrayList<>(); + + List traceFiles = Files.list(Path.of(traceXmlFolder)) + .sorted(Comparator.comparingInt(TraceReader::GetTraceNumber)).toList(); + + for (Path testTraceFile : traceFiles) { + try { + TestTraceResults testTraceResults = objectMapper.readValue(new File(testTraceFile.toString()), + TestTraceResults.class); + traces.add(testTraceResults.Trace); + } catch (Exception e) { + System.out.println("ERROR while deserializing a trace!"); + e.printStackTrace(); + } + } + + return traces; + } +} diff --git a/TestGenerator/src/main/java/com/github/gilesi/testgenerator/TraceUtils.java b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/TraceUtils.java new file mode 100644 index 00000000..afd6a5bf --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/TraceUtils.java @@ -0,0 +1,279 @@ +package com.github.gilesi.testgenerator; + +import java.util.List; + +import com.github.gilesi.instrumentation.models.ClassReference; +import com.github.gilesi.instrumentation.models.InstanceReference; +import com.github.gilesi.instrumentation.models.Trace; +import com.github.gilesi.testgenerator.review.ArgLineGenerateResult; +import com.github.gilesi.testgenerator.review.ArgumentUtils; +import com.github.gilesi.testgenerator.review.AssertionUtils; +import com.github.gilesi.testgenerator.review.SerializationUtils; +import com.github.gilesi.testgenerator.exceptions.SerializationException; + +public class TraceUtils { + private static void handleConstructor(VariableStackHandler variableStackHandler, + InstanceReference instanceData, String instanceVariableName, String methodName, String argumentLine, + boolean argLineResultSuccess, StringBuilder stringBuilder, Trace trace) throws SerializationException { + String callLine = ""; + + if (!variableStackHandler.DoesVariableExist(instanceData.getInstanceId())) { + if (argLineResultSuccess) { + variableStackHandler.CreateNewVariable(instanceData.getInstanceId(), methodName); + } + + instanceVariableName = String.format("%s %s", methodName, instanceVariableName); + + callLine = String.format("%s = new %s(%s);\n", instanceVariableName, methodName, + argumentLine); + } else { + String tmpInstanceType = variableStackHandler.getVariableType(instanceData.getInstanceId()); + if (!tmpInstanceType.equals("var")) { + callLine = String.format("%s = (%s)(new %s(%s));\n", instanceVariableName, tmpInstanceType, methodName, + argumentLine); + } else { + callLine = String.format("%s = new %s(%s);\n", instanceVariableName, methodName, + argumentLine); + } + } + + if (argLineResultSuccess) { + stringBuilder.append(callLine); + } else { + stringBuilder.append(String.format("// %s", callLine)); + } + + String returnAssertionLine = AssertionUtils.traceToAssert(trace, variableStackHandler); + + if (returnAssertionLine != null && !returnAssertionLine.isEmpty()) { + if (argLineResultSuccess) { + stringBuilder.append(String.format("%s\n", returnAssertionLine)); + } else { + stringBuilder.append(String.format("// %s\n", returnAssertionLine)); + } + } + } + + private static void handleMethod(int traceId, VariableStackHandler variableStackHandler, + InstanceReference instanceData, String instanceVariableName, String methodName, String argumentLine, + boolean argLineResultSuccess, StringBuilder stringBuilder, Trace trace) throws SerializationException { + InstanceReference returnData = trace.getReturnedValue(); + + String callLine = ""; + + if (returnData != null) { + String returnVariableName = VariableStackHandler.getVariableName(returnData.getInstanceId()); + int returnDataInstanceId = returnData.getInstanceId(); + + // String rFQN = + // DescriptorUtils.getCleanedType(returnData.getClassReference().getFullyQualifiedTypeName()); + // String rReturnType = DescriptorUtils.getCleanedVar(rFQN); + + ClassReference compatibleClassReference = SerializationUtils.getBetterType(returnData.getClassReference(), + trace.getMethodReference().getReturnType()); + String FQN = DescriptorUtils.getCleanedType(compatibleClassReference.getFullyQualifiedTypeName()); + String returnType = DescriptorUtils.getCleanedVar(FQN); + + if (!variableStackHandler.DoesVariableExist(returnDataInstanceId)) { + if (argLineResultSuccess) { + variableStackHandler.CreateNewVariable(returnDataInstanceId, returnType); + } else { + String warnMessage = String.format( + "[Trace ID: %d]: Instance Call would have created the now missing variable instance: %d.", + traceId + 1, returnDataInstanceId); + + // Log the warning to the current string builder and the console. + System.out.println(warnMessage); + stringBuilder.append(String.format("// %s\n", warnMessage)); + } + + returnVariableName = String.format("%s %s", returnType, returnVariableName); + } else { + String tmpReturnType = variableStackHandler.getVariableType(returnDataInstanceId); + if (!tmpReturnType.equals("var")) { + returnType = tmpReturnType; + } + } + + // TODO: check if type equals "var" here which would be a terrible mistake. + callLine = String.format("%s = (%s)%s(%s);\n", returnVariableName, returnType, methodName, argumentLine); + } else { + callLine = String.format("%s(%s);\n", methodName, argumentLine); + } + + if (argLineResultSuccess) { + stringBuilder.append(callLine); + } else { + stringBuilder.append(String.format("// %s", callLine)); + } + + if (returnData != null) { + String returnAssertionLine = AssertionUtils.traceToAssert(trace, variableStackHandler); + + if (returnAssertionLine != null && !returnAssertionLine.isEmpty()) { + if (argLineResultSuccess) { + stringBuilder.append(String.format("%s\n", returnAssertionLine)); + } else { + stringBuilder.append(String.format("// %s\n", returnAssertionLine)); + } + } + } + } + + private static boolean handleMethodInstance(int traceId, VariableStackHandler variableStackHandler, + InstanceReference instanceData, String instanceVariableName, String methodName, String argumentLine, + boolean argLineResultSuccess, StringBuilder stringBuilder, Trace trace) throws SerializationException { + InstanceReference returnData = trace.getReturnedValue(); + + // Holds whenever or not an issue with instances was found + // If true, we should not create new variables and we should comment out the + // whole code. + // As the code simply is not generable. + boolean issuesDetected = false; + + // When we have an instance dependent call, we need the instance the call is + // performed onto to exist + // If it does not exist, then we cannot generate this code + // Log that, and comment out the code we would have generated. + // And make sure we do not create a variable for the return value if a thing. + if (!variableStackHandler.DoesVariableExist(instanceData.getInstanceId())) { + String warnMessage = String.format( + "[Trace ID: %d]: Instance Call to %s cannot be generated because it depends on %d (%s) that does not already exist.", + traceId + 1, methodName, instanceData.getInstanceId(), + instanceData.getClassReference().getFullyQualifiedTypeName()); + + // Log the warning to the current string builder and the console. + System.out.println(warnMessage); + stringBuilder.append(String.format("// %s\n", warnMessage)); + + issuesDetected = true; + } + + // An instance method call is not called by its fully qualified name (i.e. + // bar.man.foo() but by instanceidvar.foo()), format the method name correctly. + String instanceMethodCallName = methodName.split("\\.")[methodName.split("\\.").length - 1]; + if (!variableStackHandler.DoesVariableExist(instanceData.getInstanceId())) { + methodName = String.format("%s.%s", instanceVariableName, instanceMethodCallName); + } else { + String tmpInstanceType = variableStackHandler.getVariableType(instanceData.getInstanceId()); + if (!tmpInstanceType.equals("var")) { + // TODO: use the true api type here (we know this info... but lack it in the + // trace files currently...) + methodName = String.format("((%s)%s).%s", tmpInstanceType, instanceVariableName, + instanceMethodCallName); + } else { + methodName = String.format("%s.%s", instanceVariableName, instanceMethodCallName); + } + } + + String callLine = ""; + + if (returnData != null) { + String returnVariableName = VariableStackHandler.getVariableName(returnData.getInstanceId()); + int returnDataInstanceId = returnData.getInstanceId(); + + // String rFQN = + // DescriptorUtils.getCleanedType(returnData.getClassReference().getFullyQualifiedTypeName()); + // String rReturnType = DescriptorUtils.getCleanedVar(rFQN); + + ClassReference compatibleClassReference = SerializationUtils.getBetterType(returnData.getClassReference(), + trace.getMethodReference().getReturnType()); + String FQN = DescriptorUtils.getCleanedType(compatibleClassReference.getFullyQualifiedTypeName()); + String returnType = DescriptorUtils.getCleanedVar(FQN); + + if (!variableStackHandler.DoesVariableExist(returnDataInstanceId)) { + if (argLineResultSuccess && !issuesDetected) { + variableStackHandler.CreateNewVariable(returnDataInstanceId, returnType); + } else { + String warnMessage = String.format( + "[Trace ID: %d]: Instance Call would have created the now missing variable instance: %d.", + traceId + 1, returnDataInstanceId); + + // Log the warning to the current string builder and the console. + System.out.println(warnMessage); + stringBuilder.append(String.format("// %s\n", warnMessage)); + } + + returnVariableName = String.format("%s %s", returnType, returnVariableName); + } else { + String tmpReturnType = variableStackHandler.getVariableType(returnDataInstanceId); + if (!tmpReturnType.equals("var")) { + returnType = tmpReturnType; + } + } + + // TODO: check if type equals "var" here which would be a terrible mistake. + callLine = String.format("%s = (%s)%s(%s);\n", returnVariableName, returnType, methodName, argumentLine); + } else { + callLine = String.format("%s(%s);\n", methodName, argumentLine); + } + + if (argLineResultSuccess && !issuesDetected) { + stringBuilder.append(callLine); + } else { + stringBuilder.append(String.format("// %s", callLine)); + } + + if (returnData != null) { + String returnAssertionLine = AssertionUtils.traceToAssert(trace, variableStackHandler); + + if (returnAssertionLine != null && !returnAssertionLine.isEmpty()) { + if (argLineResultSuccess && !issuesDetected) { + stringBuilder.append(String.format("%s\n", returnAssertionLine)); + } else { + stringBuilder.append(String.format("// %s\n", returnAssertionLine)); + } + } + } + + return !issuesDetected; + } + + public static void parseTrace(int traceId, Trace trace, VariableStackHandler variableStackHandler, + StringBuilder stringBuilder) throws SerializationException { + String methodName = trace.getMethodReference().getMethodSignature().split("\\(")[0]; + methodName = methodName.split(" ")[methodName.split(" ").length - 1].replace("$", "."); + + InstanceReference instanceData = trace.getInstanceReference(); + + String instanceVariableName = ""; + if (instanceData != null) { + instanceVariableName = VariableStackHandler.getVariableName(instanceData.getInstanceId()); + } + + ArgLineGenerateResult argLineResult = ArgumentUtils.GenerateArgLine(trace, variableStackHandler); + + for (String str : argLineResult.variableList) { + stringBuilder.append(String.format("%s\n", str)); + } + + boolean isInstanceCall = instanceData != null && !instanceData.getClassReference() + .getFullyQualifiedTypeName().replace("$", ".").equals(methodName); + + boolean success = true; + + if (trace.getMethodReference().getIsConstructor()) { + handleConstructor(variableStackHandler, instanceData, instanceVariableName, methodName, + argLineResult.argumentsForMethodCall, argLineResult.Success, stringBuilder, trace); + } else if (isInstanceCall) { + success = handleMethodInstance(traceId, variableStackHandler, instanceData, instanceVariableName, + methodName, argLineResult.argumentsForMethodCall, argLineResult.Success, stringBuilder, trace); + } else { + handleMethod(traceId, variableStackHandler, instanceData, instanceVariableName, methodName, + argLineResult.argumentsForMethodCall, argLineResult.Success, stringBuilder, trace); + } + + if (StaticConfiguration.GENERATE_INPUT_VALUES_ASSERTIONS_POST_METHOD_CALL) { + List postCallParameterAssertionLine = AssertionUtils.traceArgumentsToAssert(trace, + variableStackHandler); + + for (String varAssert : postCallParameterAssertionLine) { + if (argLineResult.Success && success) { + stringBuilder.append(String.format("%s\n", varAssert)); + } else { + stringBuilder.append(String.format("// %s\n", varAssert)); + } + } + } + } +} \ No newline at end of file diff --git a/TestGenerator/src/main/java/com/github/gilesi/testgenerator/VariableStackHandler.java b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/VariableStackHandler.java new file mode 100644 index 00000000..2d22578c --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/VariableStackHandler.java @@ -0,0 +1,25 @@ +package com.github.gilesi.testgenerator; + +import java.util.HashMap; + +public class VariableStackHandler { + private HashMap variableInstances = new HashMap<>(); + + public void CreateNewVariable(int instanceId, String variableType) { + if (!DoesVariableExist(instanceId)) { + variableInstances.put(instanceId, variableType); + } + } + + public boolean DoesVariableExist(int instanceId) { + return variableInstances.containsKey(instanceId); + } + + public static String getVariableName(int instanceId) { + return String.format("var%d", instanceId); + } + + public String getVariableType(int instanceId) { + return variableInstances.get(instanceId); + } +} diff --git a/TestGenerator/src/main/java/com/github/gilesi/testgenerator/exceptions/SerializationException.java b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/exceptions/SerializationException.java new file mode 100644 index 00000000..e502a6b1 --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/exceptions/SerializationException.java @@ -0,0 +1,7 @@ +package com.github.gilesi.testgenerator.exceptions; + +public class SerializationException extends Exception { + public SerializationException(String message) { + super(message); + } +} diff --git a/TestGenerator/src/main/java/com/github/gilesi/testgenerator/exceptions/UnsupportedJavaPrimitiveTypeException.java b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/exceptions/UnsupportedJavaPrimitiveTypeException.java new file mode 100644 index 00000000..47ae435c --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/exceptions/UnsupportedJavaPrimitiveTypeException.java @@ -0,0 +1,7 @@ +package com.github.gilesi.testgenerator.exceptions; + +public class UnsupportedJavaPrimitiveTypeException extends Exception { + public UnsupportedJavaPrimitiveTypeException(String primitive) { + super("Unsupported primitive type: %s".formatted(primitive)); + } +} diff --git a/TestGenerator/src/main/java/com/github/gilesi/testgenerator/review/ArgLineGenerateResult.java b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/review/ArgLineGenerateResult.java new file mode 100644 index 00000000..7fc2ce84 --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/review/ArgLineGenerateResult.java @@ -0,0 +1,15 @@ +package com.github.gilesi.testgenerator.review; + +import java.util.List; + +public class ArgLineGenerateResult { + public boolean Success; + public String argumentsForMethodCall; + public List variableList; + + public ArgLineGenerateResult(boolean Success, String argumentsForMethodCall, List variableList) { + this.Success = Success; + this.argumentsForMethodCall = argumentsForMethodCall; + this.variableList = variableList; + } +} \ No newline at end of file diff --git a/TestGenerator/src/main/java/com/github/gilesi/testgenerator/review/ArgumentUtils.java b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/review/ArgumentUtils.java new file mode 100644 index 00000000..f803fdcc --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/review/ArgumentUtils.java @@ -0,0 +1,192 @@ +package com.github.gilesi.testgenerator.review; + +import java.util.ArrayList; +import java.util.List; + +import com.github.gilesi.instrumentation.models.ClassReference; +import com.github.gilesi.instrumentation.models.InstanceReference; +import com.github.gilesi.instrumentation.models.Trace; +import com.github.gilesi.testgenerator.DescriptorUtils; +import com.github.gilesi.testgenerator.VariableStackHandler; + +public class ArgumentUtils { + public static ArgLineGenerateResult GenerateArgLine(Trace trace, VariableStackHandler variableStackHandler) { + boolean Succeeded = true; + + List variableDeclarationList = new ArrayList<>(); + List preCallArguments = new ArrayList<>(); + + for (int i = 0; i < trace.getPreCallArguments().size(); i++) { + boolean localSucceeded = true; + String localArgumentType = ""; + int localArgumentInstanceId = 0; + boolean localAddArgument = false; + + InstanceReference preCallArgument = trace.getPreCallArguments().get(i); + + if (preCallArgument.getIsNull()) { + preCallArguments.add("null"); + continue; + } + + ClassReference correspondingMethodArg = trace.getMethodReference().getParameterTypes()[i]; + + String argFQN = DescriptorUtils.getCleanedType(correspondingMethodArg.getFullyQualifiedTypeName()); + String argReturnType = DescriptorUtils.getCleanedVar(argFQN); + + // TODO: check if type equals "var" here which would be a terrible mistake. + // Has to be defined already so no need to check here + if (!argReturnType.equals("var")) { + preCallArguments.add("(%s)%s".formatted(argReturnType, + VariableStackHandler.getVariableName(preCallArgument.getInstanceId()))); + } else { + System.out.println(String.format( + "WARNING: Unable to get a proper type for agument in method. This should NEVER happen! %d", + preCallArgument.getInstanceId())); + preCallArguments + .add("%s".formatted(VariableStackHandler.getVariableName(preCallArgument.getInstanceId()))); + } + + String argumentVariable = ""; + // It is possible for us to use the exact sane instance twice during the call to + // this method + // Therefore, also keep a list of variables we define temporarily given we + // commit this only at the end of this routine. + Integer instanceIdForArgument = preCallArgument.getInstanceId(); + boolean variableAlreadyComitted = variableStackHandler.DoesVariableExist(instanceIdForArgument); + if (variableAlreadyComitted) { + argumentVariable = VariableStackHandler.getVariableName(preCallArgument.getInstanceId()); + } else { + ClassReference compatibleClassReference = SerializationUtils + .getBetterType(preCallArgument.getClassReference(), correspondingMethodArg); + String FQN = DescriptorUtils.getCleanedType(compatibleClassReference.getFullyQualifiedTypeName()); + String returnType = DescriptorUtils.getCleanedVar(FQN); + + // We do not handle lambdas currently, so fail here. + if (preCallArgument.getClassReference().getFullyQualifiedTypeName().contains("Lambda/")) { + localSucceeded = false; + } else { + // Variable does not exist yet, commit to it later + localArgumentInstanceId = instanceIdForArgument; + localArgumentType = returnType; + localAddArgument = true; + } + + argumentVariable = String.format("%s %s", returnType, + VariableStackHandler.getVariableName(preCallArgument.getInstanceId())); + } + + String argumentValue = ""; + if (preCallArgument.getValueAsXmlString() != null && !preCallArgument.getValueAsXmlString().isEmpty()) { + try { + argumentValue = SerializationUtils.serializableDataToJava(preCallArgument, correspondingMethodArg); + } catch (Exception e) { + System.out.println("Unable to generate code for serializing data to java!"); + e.printStackTrace(); + // fail... + // We do not have a value we can deal with, see if it already exists to use this + // instead. + if (variableAlreadyComitted) { + // This is a bit redundant as we will write var1 = var1, fix later, purely + // cosmetic. + argumentValue = VariableStackHandler.getVariableName(preCallArgument.getInstanceId()); + } else { + // We cant do much, fail. + argumentValue = "\"%s\"".formatted(preCallArgument.getValueAsXmlString()); + localSucceeded = false; + } + } + } else { + // We do not have a value we can deal with, see if it already exists to use this + // instead. + if (variableAlreadyComitted) { + // This is a bit redundant as we will write var1 = var1, fix later, purely + // cosmetic. + argumentValue = VariableStackHandler.getVariableName(preCallArgument.getInstanceId()); + } else { + // We cant do much, fail. + argumentValue = "\"UNKNOWN_DATA_VALUE\""; + localSucceeded = false; + } + } + + if (variableAlreadyComitted) { + String definedType = variableStackHandler.getVariableType(instanceIdForArgument); + + // Define the variable before the method + // TODO: check if type equals "var" here which would be a terrible mistake. + if (!definedType.equals("var")) { + if (!definedType.equals("java.lang.Integer") && + !definedType.equals("java.lang.Void") && + !definedType.equals("java.lang.Boolean") && + !definedType.equals("java.lang.Byte") && + !definedType.equals("java.lang.Character") && + !definedType.equals("java.lang.Short") && + !definedType.equals("java.lang.Double") && + !definedType.equals("java.lang.Float") && + !definedType.equals("java.lang.Long") && + !definedType.equals("java.lang.String")) { + if (argumentValue.startsWith("-")) { + // TODO: Only works with integer! + if (localSucceeded) { + variableDeclarationList + .add("%s = (%s)(%s);".formatted(argumentVariable, definedType, argumentValue)); + } else { + variableDeclarationList + .add("// %s = (%s)(%s);".formatted(argumentVariable, definedType, argumentValue)); + } + } else { + if (localSucceeded) { + variableDeclarationList + .add("%s = (%s)%s;".formatted(argumentVariable, definedType, argumentValue)); + } else { + variableDeclarationList + .add("// %s = (%s)%s;".formatted(argumentVariable, definedType, argumentValue)); + } + } + } else { + if (localSucceeded) { + variableDeclarationList + .add("%s = %s;".formatted(argumentVariable, argumentValue)); + } else { + variableDeclarationList + .add("// %s = %s;".formatted(argumentVariable, argumentValue)); + } + } + } else { + System.out.println(String.format( + "WARNING: Unable to get a proper type for agument in method. This should NEVER happen! %d", + instanceIdForArgument)); + if (localSucceeded) { + variableDeclarationList.add("%s = %s;".formatted(argumentVariable, argumentValue)); + } else { + variableDeclarationList.add("// %s = %s;".formatted(argumentVariable, argumentValue)); + } + } + } else { + // Define the variable before the method + if (localSucceeded) { + variableDeclarationList.add("%s = %s;".formatted(argumentVariable, argumentValue)); + } else { + variableDeclarationList.add("// %s = %s;".formatted(argumentVariable, argumentValue)); + } + } + + if (localSucceeded && localAddArgument) { + if (!variableStackHandler.DoesVariableExist(localArgumentInstanceId)) { + variableStackHandler.CreateNewVariable(localArgumentInstanceId, localArgumentType); + } + } + + if (!localSucceeded) { + Succeeded = false; + } + } + + String formattedArgumentsForUseInMethodCall = String.join(", ", preCallArguments); + + // Return if we succeeded, the list of stuff to pass as method arguments, and + // the list of variable declarations to add if any + return new ArgLineGenerateResult(Succeeded, formattedArgumentsForUseInMethodCall, variableDeclarationList); + } +} \ No newline at end of file diff --git a/TestGenerator/src/main/java/com/github/gilesi/testgenerator/review/AssertionUtils.java b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/review/AssertionUtils.java new file mode 100644 index 00000000..fb0e0db5 --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/review/AssertionUtils.java @@ -0,0 +1,166 @@ +package com.github.gilesi.testgenerator.review; + +import java.util.ArrayList; +import java.util.List; + +import com.github.gilesi.instrumentation.models.ClassReference; +import com.github.gilesi.instrumentation.models.InstanceReference; +import com.github.gilesi.instrumentation.models.Trace; +import com.github.gilesi.testgenerator.StaticConfiguration; +import com.github.gilesi.testgenerator.VariableStackHandler; +import com.github.gilesi.testgenerator.exceptions.SerializationException; + +public class AssertionUtils { + public static String serializableDataToCode(InstanceReference instanceReference, ClassReference classReference, + VariableStackHandler variableStackHandler, boolean failOnLambdas) throws SerializationException { + if (instanceReference.getValueAsXmlString() != null) { + if (instanceReference.getIsNull()) { + return "null"; + } + + String dataLine = SerializationUtils.serializableDataToJava(instanceReference, classReference); + + if (failOnLambdas + && instanceReference.getClassReference().getFullyQualifiedTypeName().contains("..Lambda/")) { + throw new SerializationException(String.format("We cannot handle lamdas at the moment! %d %s", instanceReference.getInstanceId(), instanceReference.getClassReference().getFullyQualifiedTypeName())); + } + + return dataLine; + } else if (variableStackHandler.DoesVariableExist(instanceReference.getInstanceId())) { + return VariableStackHandler.getVariableName(instanceReference.getInstanceId()); + } else if (failOnLambdas) { + throw new SerializationException(String.format("We cannot serialize this! %d %s", instanceReference.getInstanceId(), instanceReference.getClassReference().getFullyQualifiedTypeName())); + } else { + return "// Unserializable data with instance id: " + instanceReference.getInstanceId(); + } + } + + public static List traceArgumentsToAssert(Trace methodTrace, VariableStackHandler variableStackHandler) + throws SerializationException { + List argList = new ArrayList<>(); + + for (int i = 0; i < methodTrace.getPostCallArguments().size(); i++) { + InstanceReference postCallArgument = methodTrace.getPostCallArguments().get(i); + + if (postCallArgument == null) { + // argList.add("null"); + continue; + } + + if (postCallArgument.getIsNull() + && postCallArgument.getClassReference().getFullyQualifiedTypeName().equals("Object") + && postCallArgument.getValueAsXmlString().equals("null")) { + // argList.add("null"); + continue; + } + + ClassReference correspondingMethodArg = methodTrace.getMethodReference().getParameterTypes()[i]; + + // We return a value that we managed to serialize + String assertionCodeLine; + try { + assertionCodeLine = getReturnAssertionCodeLine(postCallArgument, correspondingMethodArg, + variableStackHandler, true, + StaticConfiguration.USE_ALTERNATIVE_ASSERTION_MODE); + } catch (SerializationException e) { + System.out.println("Unable to generate code for serializing data to java!"); + e.printStackTrace(); + + assertionCodeLine = "// %s".formatted(getReturnAssertionCodeLine(postCallArgument, + correspondingMethodArg, variableStackHandler, false, + StaticConfiguration.USE_ALTERNATIVE_ASSERTION_MODE)); + } + argList.add(assertionCodeLine); + } + + return argList; + } + + private static String getReturnAssertionCodeLine(InstanceReference returnedValueInstance, + ClassReference returnClassReference, + VariableStackHandler variableStackHandler, boolean failOnLambdas, boolean useAltAssertionMode) + throws SerializationException { + int returnedValueInstanceId = returnedValueInstance.getInstanceId(); + String returnedValueDataAsXml = returnedValueInstance.getValueAsXmlString(); + + if (returnedValueDataAsXml != null) { + String instanceVarName = VariableStackHandler.getVariableName(returnedValueInstanceId); + + if (returnedValueDataAsXml.equals("null")) { + return generateAssertEquals("null", instanceVarName); + } + + if (failOnLambdas + && returnedValueInstance.getClassReference().getFullyQualifiedTypeName().contains("..Lambda/")) { + throw new SerializationException("We cannot handle lamdas at the moment!"); + } + + String assertionLine; + + if (useAltAssertionMode) { + String valueAsCode = serializableDataToCode(returnedValueInstance, + returnClassReference, variableStackHandler, true); + + if (valueAsCode.endsWith("[].class)")) { + assertionLine = generateAssertArrayEquals(valueAsCode, instanceVarName); + } else { + assertionLine = generateAssertEquals(valueAsCode, instanceVarName); + } + } else { + String valueAsCode = SerializationUtils.generateStringJavaLine(returnedValueDataAsXml); + assertionLine = generateAssertEquals(valueAsCode, "getToXml(%s)".formatted(instanceVarName)); + } + + return assertionLine; + } else if (variableStackHandler.DoesVariableExist(returnedValueInstanceId)) { + if (useAltAssertionMode) { + // TODO: array + return String.format("// %s", generateAssertEquals("getFromXml(\"UNKNOWN_DATA_VALUE\")", + VariableStackHandler.getVariableName(returnedValueInstanceId))); + } else { + return String.format("// %s", generateAssertEquals("UNKNOWN_DATA_VALUE", "getToXml(%s)" + .formatted(VariableStackHandler.getVariableName(returnedValueInstanceId)))); + } + } else if (failOnLambdas) { + throw new SerializationException(String.format("We cannot serialize this! %d %s", returnedValueInstance.getInstanceId(), returnedValueInstance.getClassReference().getFullyQualifiedTypeName())); + } else { + return "// Unserializable data with instance id: %d".formatted(returnedValueInstance.getInstanceId()); + } + } + + public static String traceToAssert(Trace methodTrace, VariableStackHandler variableStackHandler) + throws SerializationException { + String cleanedMethodName = methodTrace.getMethodReference().getMethodSignature().split("\\(")[0]; + cleanedMethodName = cleanedMethodName.split(" ")[cleanedMethodName.split(" ").length - 1].replace("$", "."); + + InstanceReference traceData = methodTrace.getReturnedValue(); + + if (!methodTrace.getMethodReference().getIsConstructor() && traceData != null) { + String assertionCodeLine; + try { + assertionCodeLine = getReturnAssertionCodeLine(traceData, + methodTrace.getMethodReference().getReturnType(), variableStackHandler, true, + StaticConfiguration.USE_ALTERNATIVE_ASSERTION_MODE); + } catch (SerializationException e) { + System.out.println("Unable to generate code for serializing data to java!"); + e.printStackTrace(); + + assertionCodeLine = "// %s" + .formatted(getReturnAssertionCodeLine(traceData, + methodTrace.getMethodReference().getReturnType(), variableStackHandler, false, + StaticConfiguration.USE_ALTERNATIVE_ASSERTION_MODE)); + } + return assertionCodeLine; + } + + return null; + } + + private static String generateAssertEquals(String expected, String actual) { + return "assertEquals(%s, %s);".formatted(expected, actual); + } + + private static String generateAssertArrayEquals(String expected, String actual) { + return "assertArrayEquals(%s, %s);".formatted(expected, actual); + } +} \ No newline at end of file diff --git a/TestGenerator/src/main/java/com/github/gilesi/testgenerator/review/SerializationUtils.java b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/review/SerializationUtils.java new file mode 100644 index 00000000..bf901ed5 --- /dev/null +++ b/TestGenerator/src/main/java/com/github/gilesi/testgenerator/review/SerializationUtils.java @@ -0,0 +1,159 @@ +package com.github.gilesi.testgenerator.review; + +import com.github.gilesi.instrumentation.models.ClassReference; +import com.github.gilesi.instrumentation.models.InstanceReference; +import com.github.gilesi.testgenerator.DescriptorUtils; +import com.github.gilesi.testgenerator.Main; +import com.github.gilesi.testgenerator.StaticConfiguration; +import com.github.gilesi.testgenerator.exceptions.SerializationException; +import org.apache.commons.text.StringEscapeUtils; + +import java.util.ArrayList; +import java.util.List; + +public class SerializationUtils { + public static ClassReference getCompatibleClassReference(ClassReference classReference) { + String FQN = classReference.getFullyQualifiedTypeName(); + if (FQN.startsWith("java.")) { + return classReference; + } + + if (FQN.replace("[]", "").equals("int") || + FQN.replace("[]", "").equals("void") || + FQN.replace("[]", "").equals("boolean") || + FQN.replace("[]", "").equals("byte") || + FQN.replace("[]", "").equals("char") || + FQN.replace("[]", "").equals("short") || + FQN.replace("[]", "").equals("double") || + FQN.replace("[]", "").equals("float") || + FQN.replace("[]", "").equals("long") || + FQN.replace("[]", "").equals("string")) { + return classReference; + } + + String[] libraryTypes = Main.instrumentationParameters.LibraryTypes; + + for (String libraryType : libraryTypes) { + if (FQN.equals(libraryType)) { + return classReference; + } + } + + if (classReference.getSuperClass() != null) { + ClassReference compatibleClassReference = getCompatibleClassReference(classReference.getSuperClass()); + if (compatibleClassReference != null) { + return compatibleClassReference; + } + } + + for (ClassReference interfaceClassReference : classReference.getInterfaces()) { + ClassReference compatibleClassReference = getCompatibleClassReference(interfaceClassReference); + if (compatibleClassReference != null) { + return compatibleClassReference; + } + } + + return null; + } + + public static ClassReference getBetterType(ClassReference actualType, ClassReference desiredType) { + ClassReference compatibleClassReference = getCompatibleClassReference(actualType); + boolean isValid = compatibleClassReference != null && !compatibleClassReference.getFullyQualifiedTypeName().equals("java.lang.Object"); + + if (!isValid) { + compatibleClassReference = getCompatibleClassReference(desiredType); + isValid = compatibleClassReference != null && !compatibleClassReference.getFullyQualifiedTypeName().equals("java.lang.Object"); + if (!isValid) { + System.out.println("WARNING: Argument type is not supported by the library of Java Runtime Environment."); + System.out.println("WARNING: Actual Type: " + actualType.getFullyQualifiedTypeName()); + System.out.println("WARNING: Desired Type: " + desiredType.getFullyQualifiedTypeName()); + } + return desiredType; + } else { + return compatibleClassReference; + } + } + + public static String serializableDataToJava(InstanceReference serializableData, ClassReference argClass) + throws SerializationException { + String valueToBeEqualTo = serializableData.getValueAsXmlString(); + + ClassReference compatibleClassReference = getBetterType(serializableData.getClassReference(), argClass); + String FQN = DescriptorUtils.getCleanedType(compatibleClassReference.getFullyQualifiedTypeName()); + + if (!FQN.equals("java.lang.Integer") && + !FQN.equals("java.lang.Void") && + !FQN.equals("java.lang.Boolean") && + !FQN.equals("java.lang.Byte") && + !FQN.equals("java.lang.Character") && + !FQN.equals("java.lang.Short") && + !FQN.equals("java.lang.Double") && + !FQN.equals("java.lang.Float") && + !FQN.equals("java.lang.Long") && + !FQN.equals("java.lang.String")) { + + String valueAsCode = generateStringJavaLine(serializableData.getValueAsXmlString()); + valueToBeEqualTo = "getFromXml(%s)".formatted(valueAsCode); + } else { + int start = valueToBeEqualTo.indexOf(">"); + int end = valueToBeEqualTo.lastIndexOf(" valueToBeEqualTo.length() - 1 - 3) { + throw new SerializationException("Malformed XML data"); + } + + valueToBeEqualTo = valueToBeEqualTo.substring(start + 1, end); + + if (FQN.equals("java.lang.Character")) { + valueToBeEqualTo = "'%s'".formatted(valueToBeEqualTo); + if (valueToBeEqualTo.equals("''")) { + valueToBeEqualTo = "java.lang.Character.MIN_VALUE"; + } + } else if (FQN.equals("java.lang.String")) { + valueToBeEqualTo = generateStringJavaLine(valueToBeEqualTo); + } else if (FQN.equals("java.lang.Float")) { + valueToBeEqualTo = "%sF".formatted(valueToBeEqualTo); + } else if (FQN.equals("java.lang.Long")) { + valueToBeEqualTo = "%sL".formatted(valueToBeEqualTo); + } + } + + return valueToBeEqualTo; + } + + public static String generateStringJavaLine(String stringContent) { + String valueAsCode = "\"%s\"".formatted(StringEscapeUtils.escapeJava(stringContent)); + + //int maxStringSize = (int) Math.pow(2, 16); + // TODO: CHeck why this still fails + int maxStringSize = 1024; + + if (valueAsCode.length() > maxStringSize) { + if (!StaticConfiguration.SKIP_LONG_ARG_STRING_FOR_DEBUGGING) { + List sectionList = new ArrayList<>(); + + for (int start2 = 0; start2 < valueAsCode.length(); start2 += maxStringSize) { + int end2 = start2 + maxStringSize; + if (start2 + maxStringSize > valueAsCode.length()) { + end2 = valueAsCode.length(); + } + + String sectionOfDataAsXml = valueAsCode.substring(start2, end2); + String readyToUseSection = "new String(\"%s\")" + .formatted(StringEscapeUtils.escapeJava(sectionOfDataAsXml)); + sectionList.add(readyToUseSection); + } + + valueAsCode = String.join(" + ", sectionList.toArray(String[]::new)); + } else { + valueAsCode = "\"SNIP!\""; + } + } + + return valueAsCode; + } +} diff --git a/TraceDiff/.gitignore b/TraceDiff/.gitignore new file mode 100644 index 00000000..d4c6a6aa --- /dev/null +++ b/TraceDiff/.gitignore @@ -0,0 +1,39 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/* +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/TraceView/build.gradle b/TraceDiff/build.gradle similarity index 52% rename from TraceView/build.gradle rename to TraceDiff/build.gradle index c8c091b6..2014ba84 100644 --- a/TraceView/build.gradle +++ b/TraceDiff/build.gradle @@ -1,9 +1,9 @@ plugins { id 'application' - id 'com.github.johnrengelman.shadow' version '7.0.0' + id 'com.github.johnrengelman.shadow' version '8.1.1' } -group 'com.github.maracas.gilesi.traceview' +group 'com.github.gilesi.tracediff' version '1.0-SNAPSHOT' @@ -11,7 +11,7 @@ compileJava { options.encoding = 'UTF-8' } -tasks.withType(JavaCompile) { +tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' } @@ -24,25 +24,30 @@ repositories { } dependencies { - implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.2' - implementation 'org.apache.commons:commons-text:1.10.0' + implementation 'com.fasterxml.jackson.core:jackson-core:2.18.1' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.2' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.1' + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.17.2' + implementation 'com.fasterxml.jackson:jackson-base:2.18.1' + implementation 'org.apache.commons:commons-text:1.12.0' + implementation 'com.thoughtworks.xstream:xstream:1.4.20' } jar { manifest { - attributes 'Main-Class': 'com.github.maracas.gilesi.traceview.Main', + attributes 'Main-Class': 'com.github.gilesi.tracediff.Main', 'Multi-Release': 'true' } } application { - mainClass = 'com.github.maracas.gilesi.traceview.Main' + mainClass = 'com.github.gilesi.tracediff.Main' } description = 'Main distribution.' shadowJar { - archiveBaseName.set('com.github.maracas.gilesi.traceview') + archiveBaseName.set('com.github.gilesi.tracediff') archiveClassifier.set('') archiveVersion.set('') mergeServiceFiles() @@ -50,7 +55,7 @@ shadowJar { distributions { shadow { - distributionBaseName = 'com.github.maracas.gilesi.traceview' + distributionBaseName = 'com.github.gilesi.tracediff' } } diff --git a/TraceDiff/gradle/wrapper/gradle-wrapper.jar b/TraceDiff/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..033e24c4 Binary files /dev/null and b/TraceDiff/gradle/wrapper/gradle-wrapper.jar differ diff --git a/TraceDiff/gradle/wrapper/gradle-wrapper.properties b/TraceDiff/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..1af9e093 --- /dev/null +++ b/TraceDiff/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/TraceDiff/gradlew b/TraceDiff/gradlew new file mode 100644 index 00000000..cbc4dfc1 --- /dev/null +++ b/TraceDiff/gradlew @@ -0,0 +1,246 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +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 + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/TraceDiff/gradlew.bat b/TraceDiff/gradlew.bat new file mode 100644 index 00000000..4b4ef2d7 --- /dev/null +++ b/TraceDiff/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/TraceDiff/src/main/java/com/github/gilesi/confgen/models/Class.java b/TraceDiff/src/main/java/com/github/gilesi/confgen/models/Class.java new file mode 100644 index 00000000..d8e63389 --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/confgen/models/Class.java @@ -0,0 +1,7 @@ +package com.github.gilesi.confgen.models; + +public class Class { + public String ClassName; + public Method[] ClassMethods; + public String[] ClassDescriptors; +} diff --git a/TraceDiff/src/main/java/com/github/gilesi/confgen/models/InstrumentationParameters.java b/TraceDiff/src/main/java/com/github/gilesi/confgen/models/InstrumentationParameters.java new file mode 100644 index 00000000..2fa9ceac --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/confgen/models/InstrumentationParameters.java @@ -0,0 +1,7 @@ +package com.github.gilesi.confgen.models; + +public class InstrumentationParameters { + public Class[] InstrumentedAPIClasses; + public String[] LibraryTypes; + public String traceOutputLocation; +} diff --git a/TraceDiff/src/main/java/com/github/gilesi/confgen/models/Method.java b/TraceDiff/src/main/java/com/github/gilesi/confgen/models/Method.java new file mode 100644 index 00000000..8ef6c1e1 --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/confgen/models/Method.java @@ -0,0 +1,6 @@ +package com.github.gilesi.confgen.models; + +public class Method { + public String MethodName; + public String[] MethodDescriptors; +} \ No newline at end of file diff --git a/TraceDiff/src/main/java/com/github/gilesi/instrumentation/ConfigurationReader.java b/TraceDiff/src/main/java/com/github/gilesi/instrumentation/ConfigurationReader.java new file mode 100644 index 00000000..27f4c5ae --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/instrumentation/ConfigurationReader.java @@ -0,0 +1,12 @@ +package com.github.gilesi.instrumentation; + +import com.github.gilesi.confgen.models.InstrumentationParameters; + +import java.io.File; +import java.io.IOException; + +public class ConfigurationReader { + public static InstrumentationParameters parseInstrumentationparameters(File file) throws IOException { + return XmlUtils.objectMapper.readValue(file, InstrumentationParameters.class); + } +} diff --git a/TraceDiff/src/main/java/com/github/gilesi/instrumentation/XmlUtils.java b/TraceDiff/src/main/java/com/github/gilesi/instrumentation/XmlUtils.java new file mode 100644 index 00000000..3b46a7f2 --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/instrumentation/XmlUtils.java @@ -0,0 +1,51 @@ +package com.github.gilesi.instrumentation; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.DomDriver; +import java.io.File; +import java.io.FileWriter; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class XmlUtils { + private static DomDriver domDriver = new DomDriver(); + private static XStream xStream = new XStream(domDriver); + private static Object syncObj = new Object(); + public static final ObjectMapper objectMapper = new ObjectMapper() + .enable(SerializationFeature.INDENT_OUTPUT) + //.enable(SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL) + .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) + .disable(SerializationFeature.FAIL_ON_SELF_REFERENCES) + .disable(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS); + + public static String getToXml(Object obj) { + return xStream.toXML(obj); + } + + public static String getToJson(Object obj) throws JsonProcessingException { + return objectMapper.writeValueAsString(obj); + } + + public static void saveToUniquePath(String basePath, String baseName, String baseExtension, String content) { + synchronized (syncObj) { + int padding = 1; + String finalFileName = String.format("%s%s%s_%d.%s", basePath, File.separatorChar, baseName, padding, baseExtension); + + while (Files.exists(Paths.get(finalFileName))) { + finalFileName = String.format("%s%s%s_%d.%s", basePath, File.separatorChar, baseName, ++padding, baseExtension); + } + + try { + FileWriter outputFile = new FileWriter(finalFileName); + outputFile.write(content); + outputFile.close(); + } catch (Throwable e) { + System.err.println("ERROR"); + e.printStackTrace(); + } + } + } +} diff --git a/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/ClassReference.java b/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/ClassReference.java new file mode 100644 index 00000000..a3595e25 --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/ClassReference.java @@ -0,0 +1,65 @@ +package com.github.gilesi.instrumentation.models; + +import java.util.ArrayList; +import java.util.Objects; + +public class ClassReference { + private String fullyQualifiedTypeName; + private ArrayList interfaces; + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ClassReference that)) return false; + + return Objects.equals(getFullyQualifiedTypeName(), that.getFullyQualifiedTypeName()) && Objects.equals(getInterfaces(), that.getInterfaces()) && Objects.equals(getSuperClass(), that.getSuperClass()) && Objects.equals(getGenericParameters(), that.getGenericParameters()); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(getFullyQualifiedTypeName()); + result = 31 * result + Objects.hashCode(getInterfaces()); + result = 31 * result + Objects.hashCode(getSuperClass()); + result = 31 * result + Objects.hashCode(getGenericParameters()); + return result; + } + + private ClassReference superClass; + private ArrayList genericParameters; + + public ClassReference() { + + } + + public String getFullyQualifiedTypeName() { + return fullyQualifiedTypeName; + } + + public ArrayList getInterfaces() { + return interfaces; + } + + public ClassReference getSuperClass() { + return superClass; + } + + public ArrayList getGenericParameters() { + return genericParameters; + } + + public void setFullyQualifiedTypeName(String fullyQualifiedTypeName) { + this.fullyQualifiedTypeName = fullyQualifiedTypeName; + } + + public void setInterfaces(ArrayList interfaces) { + this.interfaces = interfaces; + } + + public void setSuperClass(ClassReference superClass) { + this.superClass = superClass; + } + + public void setGenericParameters(ArrayList genericParameters) { + this.genericParameters = genericParameters; + } +} diff --git a/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/GenericReference.java b/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/GenericReference.java new file mode 100644 index 00000000..d0fad5f1 --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/GenericReference.java @@ -0,0 +1,44 @@ +package com.github.gilesi.instrumentation.models; + +import java.util.ArrayList; +import java.util.Objects; + +public class GenericReference { + private String name; + private ArrayList classBounds; + + public GenericReference() { + + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public ArrayList getClassBounds() { + return classBounds; + } + + public void getClassBounds(ArrayList classBounds) { + this.classBounds = classBounds; + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof GenericReference that)) return false; + + return Objects.equals(getName(), that.getName()) && Objects.equals(getClassBounds(), that.getClassBounds()); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(getName()); + result = 31 * result + Objects.hashCode(getClassBounds()); + return result; + } +} diff --git a/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/InstanceReference.java b/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/InstanceReference.java new file mode 100644 index 00000000..d4338a7c --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/InstanceReference.java @@ -0,0 +1,69 @@ +package com.github.gilesi.instrumentation.models; + +import java.util.Objects; + +public class InstanceReference { + private int instanceId = -1; + private String valueAsXmlString = null; + private String valueAsJsonString = null; + private boolean isNull = true; + private ClassReference classReference = null; + + public int getInstanceId() { + return instanceId; + } + + public String getValueAsXmlString() { + return valueAsXmlString; + } + + public String getValueAsJsonString() { + return valueAsJsonString; + } + + public boolean getIsNull() { + return isNull; + } + + public ClassReference getClassReference() { + return classReference; + } + + public void setInstanceId(int instanceId) { + this.instanceId = instanceId; + } + + public void setValueAsXmlString(String valueAsXmlString) { + this.valueAsXmlString = valueAsXmlString; + } + + public void setValueAsJsonString(String valueAsJsonString) { + this.valueAsJsonString = valueAsJsonString; + } + + public void setIsNull(boolean isNull) { + this.isNull = isNull; + } + + public void setClassReference(ClassReference classReference) { + this.classReference = classReference; + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof InstanceReference that)) return false; + + return getInstanceId() == that.getInstanceId() && isNull == that.isNull && Objects.equals(getValueAsXmlString(), that.getValueAsXmlString()) && Objects.equals(getValueAsJsonString(), that.getValueAsJsonString()) && Objects.equals(getClassReference(), that.getClassReference()); + } + + @Override + public int hashCode() { + int result = getInstanceId(); + result = 31 * result + Objects.hashCode(getValueAsXmlString()); + result = 31 * result + Objects.hashCode(getValueAsJsonString()); + result = 31 * result + Boolean.hashCode(isNull); + result = 31 * result + Objects.hashCode(getClassReference()); + return result; + } +} diff --git a/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/MethodReference.java b/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/MethodReference.java new file mode 100644 index 00000000..7a2e85d7 --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/MethodReference.java @@ -0,0 +1,84 @@ +package com.github.gilesi.instrumentation.models; + +import java.util.ArrayList; +import java.util.Objects; + +public class MethodReference { + private String methodSignature; + private ArrayList parameterTypes; + private ClassReference returnType; + private boolean isConstructor; + private ArrayList genericParameters; + private ClassReference genericReturn; + + public MethodReference() { + + } + + public String getMethodSignature() { + return methodSignature; + } + + public ArrayList getParameterTypes() { + return parameterTypes; + } + + public ClassReference getReturnType() { + return returnType; + } + + public boolean getIsConstructor() { + return isConstructor; + } + + public ArrayList getGenericParameters() { + return genericParameters; + } + + public ClassReference getGenericReturn() { + return genericReturn; + } + + public void setMethodSignature(String methodSignature) { + this.methodSignature = methodSignature; + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MethodReference that)) return false; + + return isConstructor == that.isConstructor && Objects.equals(getMethodSignature(), that.getMethodSignature()) && Objects.equals(getParameterTypes(), that.getParameterTypes()) && Objects.equals(getReturnType(), that.getReturnType()) && Objects.equals(getGenericParameters(), that.getGenericParameters()) && Objects.equals(getGenericReturn(), that.getGenericReturn()); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(getMethodSignature()); + result = 31 * result + Objects.hashCode(getParameterTypes()); + result = 31 * result + Objects.hashCode(getReturnType()); + result = 31 * result + Boolean.hashCode(isConstructor); + result = 31 * result + Objects.hashCode(getGenericParameters()); + result = 31 * result + Objects.hashCode(getGenericReturn()); + return result; + } + + public void setParameterTypes(ArrayList parameterTypes) { + this.parameterTypes = parameterTypes; + } + + public void setReturnType(ClassReference returnType) { + this.returnType = returnType; + } + + public void setIsConstructor(boolean isConstructor) { + this.isConstructor = isConstructor; + } + + public void setGenericParameters(ArrayList genericParameters) { + this.genericParameters = genericParameters; + } + + public void setGenericReturn(ClassReference genericReturn) { + this.genericReturn = genericReturn; + } +} diff --git a/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/PartialTrace.java b/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/PartialTrace.java new file mode 100644 index 00000000..d5da4445 --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/PartialTrace.java @@ -0,0 +1,75 @@ +package com.github.gilesi.instrumentation.models; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; +import java.util.Objects; + +public class PartialTrace { + private ArrayList preCallArguments; + //private long timeStampEntry; + private InstanceReference instanceReference; + private MethodReference methodReference; + + @JsonCreator + public PartialTrace( + @JsonProperty("preCallArguments") ArrayList preCallArguments, + //@JsonProperty("timeStampEntry") long timeStampEntry, + @JsonProperty("instanceReference") InstanceReference instanceReference, + @JsonProperty("methodReference") MethodReference methodReference) { + this.preCallArguments = preCallArguments; + //this.timeStampEntry = timeStampEntry; + this.instanceReference = instanceReference; + this.methodReference = methodReference; + } + + public ArrayList getPreCallArguments() { + return preCallArguments; + } + + public void setPreCallArguments(ArrayList preCallArguments) { + this.preCallArguments = preCallArguments; + } + + /*public long getTimeStampEntry() { + return timeStampEntry; + } + + public void setTimeStampEntry(long timeStampEntry) { + this.timeStampEntry = timeStampEntry; + }*/ + + public InstanceReference getInstanceReference() { + return instanceReference; + } + + public MethodReference getMethodReference() { + return methodReference; + } + + public void setInstanceReference(InstanceReference instanceReference) { + this.instanceReference = instanceReference; + } + + public void setMethodReference(MethodReference methodReference) { + this.methodReference = methodReference; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PartialTrace that)) return false; + + return /*getTimeStampEntry() == that.getTimeStampEntry() &&*/ Objects.equals(getPreCallArguments(), that.getPreCallArguments()) && Objects.equals(getInstanceReference(), that.getInstanceReference()) && Objects.equals(getMethodReference(), that.getMethodReference()); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(getPreCallArguments()); + //result = 31 * result + Long.hashCode(getTimeStampEntry()); + result = 31 * result + Objects.hashCode(getInstanceReference()); + result = 31 * result + Objects.hashCode(getMethodReference()); + return result; + } +} \ No newline at end of file diff --git a/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/TestTraceResults.java b/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/TestTraceResults.java new file mode 100644 index 00000000..936aaa54 --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/TestTraceResults.java @@ -0,0 +1,23 @@ +package com.github.gilesi.instrumentation.models; + +import java.util.Objects; + +public class TestTraceResults { + public Trace Trace; + public String Test; + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TestTraceResults that)) return false; + + return Objects.equals(Trace, that.Trace) && Objects.equals(Test, that.Test); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(Trace); + result = 31 * result + Objects.hashCode(Test); + return result; + } +} diff --git a/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/Trace.java b/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/Trace.java new file mode 100644 index 00000000..412b01a2 --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/instrumentation/models/Trace.java @@ -0,0 +1,120 @@ +package com.github.gilesi.instrumentation.models; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; +import java.util.Objects; + +public class Trace extends PartialTrace { + private ArrayList postCallArguments; + private InstanceReference returnedValue; + //private long timeStampExit; + private InstanceReference exceptionThrown; + //private ArrayList stackTrace; + + private String confName; + private ArrayList confDescriptors; + + @JsonCreator + public Trace( + @JsonProperty("preCallArguments") ArrayList preCallArguments, + @JsonProperty("postCallArguments") ArrayList postCallArguments, + @JsonProperty("returnedValue") InstanceReference returnedValue, + //@JsonProperty("timeStampEntry") long timeStampEntry, + //@JsonProperty("timeStampExit") long timeStampExit, + @JsonProperty("exceptionThrown") InstanceReference exceptionThrown, + //@JsonProperty("stackTrace") ArrayList stackTrace, + @JsonProperty("confName") String confName, + @JsonProperty("confDescriptors") ArrayList confDescriptors, + @JsonProperty("instanceReference") InstanceReference instanceReference, + @JsonProperty("methodReference") MethodReference methodReference) { + super(preCallArguments, /*timeStampEntry,*/ instanceReference, methodReference); + + this.postCallArguments = postCallArguments; + this.returnedValue = returnedValue; + //this.timeStampExit = timeStampExit; + this.exceptionThrown = exceptionThrown; + //this.stackTrace = stackTrace; + this.confName = confName; + this.confDescriptors = confDescriptors; + } + + public ArrayList getPostCallArguments() { + return postCallArguments; + } + + public void setPostCallArguments(ArrayList postCallArguments) { + this.postCallArguments = postCallArguments; + } + + public InstanceReference getReturnedValue() { + return returnedValue; + } + + public void setReturnedValue(InstanceReference returnedValue) { + this.returnedValue = returnedValue; + } + + /*public long getTimeStampExit() { + return timeStampExit; + } + + public void setTimeStampExit(long timeStampExit) { + this.timeStampExit = timeStampExit; + }*/ + + public InstanceReference getExceptionThrown() { + return exceptionThrown; + } + + public void setExceptionThrown(InstanceReference exceptionThrown) { + this.exceptionThrown = exceptionThrown; + } + + /*public ArrayList getStackTrace() { + return stackTrace; + } + + public void setStackTrace(ArrayList stackTrace) { + this.stackTrace = stackTrace; + }*/ + + public String getConfName() { + return confName; + } + + public void setConfName(String confName) { + this.confName = confName; + } + + public ArrayList getConfDescriptors() { + return confDescriptors; + } + + public void setConfDescriptors(ArrayList confDescriptors) { + this.confDescriptors = confDescriptors; + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Trace trace)) return false; + if (!super.equals(o)) return false; + + return /*getTimeStampExit() == trace.getTimeStampExit() &&*/ Objects.equals(getPostCallArguments(), trace.getPostCallArguments()) && Objects.equals(getReturnedValue(), trace.getReturnedValue()) && Objects.equals(getExceptionThrown(), trace.getExceptionThrown()) /*&& Objects.equals(getStackTrace(), trace.getStackTrace())*/ && Objects.equals(getConfName(), trace.getConfName()) && Objects.equals(getConfDescriptors(), trace.getConfDescriptors()); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + Objects.hashCode(getPostCallArguments()); + result = 31 * result + Objects.hashCode(getReturnedValue()); + //result = 31 * result + Long.hashCode(getTimeStampExit()); + result = 31 * result + Objects.hashCode(getExceptionThrown()); + //result = 31 * result + Objects.hashCode(getStackTrace()); + result = 31 * result + Objects.hashCode(getConfName()); + result = 31 * result + Objects.hashCode(getConfDescriptors()); + return result; + } +} \ No newline at end of file diff --git a/TraceDiff/src/main/java/com/github/gilesi/tracediff/ClassUtils.java b/TraceDiff/src/main/java/com/github/gilesi/tracediff/ClassUtils.java new file mode 100644 index 00000000..f4d2c3ca --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/tracediff/ClassUtils.java @@ -0,0 +1,24 @@ +package com.github.gilesi.tracediff; + +import java.util.ArrayList; + +import com.github.gilesi.instrumentation.models.Trace; + +public class ClassUtils { + public static void generateClassFile(ArrayList methodTraces) { + VariableStackHandler variableStackHandler = new VariableStackHandler(); + + for (int i = 0; i < methodTraces.size(); i++) { + System.out.println("Processing Trace %d out of %d".formatted(i + 1, methodTraces.size())); + + Trace trace = methodTraces.get(i); + + try { + TraceUtils.parseTrace(i, trace, variableStackHandler); + } catch (Exception e) { + System.out.println("Unable to convert trace to code!"); + e.printStackTrace(); + } + } + } +} diff --git a/TraceDiff/src/main/java/com/github/gilesi/tracediff/DescriptorUtils.java b/TraceDiff/src/main/java/com/github/gilesi/tracediff/DescriptorUtils.java new file mode 100644 index 00000000..ca75570e --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/tracediff/DescriptorUtils.java @@ -0,0 +1,129 @@ +package com.github.gilesi.tracediff; + +import java.util.ArrayList; +import java.util.List; + +import com.github.gilesi.tracediff.exceptions.UnsupportedJavaPrimitiveTypeException; + +public class DescriptorUtils { + public static int getFirstArgLength(String name) throws UnsupportedJavaPrimitiveTypeException { + if (name.length() == 0) { + return 0; + } + + return switch (name.charAt(0)) { + case 'I' -> 1; + case 'V' -> 1; + case 'Z' -> 1; + case 'B' -> 1; + case 'C' -> 1; + case 'S' -> 1; + case 'D' -> 1; + case 'F' -> 1; + case 'J' -> 1; + case 'L' -> name.indexOf(";") + 1; + case '[' -> getFirstArgLength(name.substring(1)) + 1; + default -> throw new UnsupportedJavaPrimitiveTypeException(name); + }; + } + + public static String getCleanedType(String FQN) { + int currentCharacterIndex = 0; + char currentCharacter = FQN.charAt(currentCharacterIndex); + while (currentCharacter == '[') { + currentCharacter = FQN.charAt(++currentCharacterIndex); + } + + if (((currentCharacter == 'I' || currentCharacter == 'V' || + currentCharacter == 'Z' || currentCharacter == 'B' || + currentCharacter == 'C' || currentCharacter == 'S' || + currentCharacter == 'D' || currentCharacter == 'F' || + currentCharacter == 'J') && FQN.length() == currentCharacterIndex + 1) || + (currentCharacter == 'L' && FQN.endsWith(";"))) { + try { + return JvmTypeToLangType(FQN).replace("$", "."); + } catch (UnsupportedJavaPrimitiveTypeException e) { + System.out.println(String.format("Unsupported Java Primitive Type Exception! %c", currentCharacter)); + e.printStackTrace(); + } + } + return FQN.replace("$", "."); + } + + private static String JvmTypeToLangType(String name) throws UnsupportedJavaPrimitiveTypeException { + return switch (name.charAt(0)) { + case 'I' -> "java.lang.Integer"; + case 'V' -> "java.lang.Void"; + case 'Z' -> "java.lang.Boolean"; + case 'B' -> "java.lang.Byte"; + case 'C' -> "java.lang.Character"; + case 'S' -> "java.lang.Short"; + case 'D' -> "java.lang.Double"; + case 'F' -> "java.lang.Float"; + case 'J' -> "java.lang.Long"; + case 'L' -> name.substring(1, name.length() - 1).replace("/", "."); + case '[' -> "%s[]".formatted(JvmTypeToLangType(name.substring(1))); + default -> throw new UnsupportedJavaPrimitiveTypeException(name); + }; + } + + public static void convertDescriptorToNormalTypesTest(String descr) throws UnsupportedJavaPrimitiveTypeException { + int startArg = descr.indexOf("("); + int endArg = descr.lastIndexOf(")"); + + String returns = descr.substring(endArg + 1, descr.length()); + String argusStr = descr.substring(startArg + 1, endArg); + + int length = 1; + List arguments = new ArrayList<>(); + String returnValue = null; + + do { + length = getFirstArgLength(argusStr); + String curArg = argusStr.substring(0, length); + argusStr = argusStr.substring(length); + if (curArg.length() != 0) { + arguments.add(getCleanedType(curArg)); + } + } while (length != 0); + + if (!returns.equals("V")) { + returnValue = getCleanedType(returns); + } + + System.out.println("Descriptor: " + descr); + + if (arguments.size() != 0) { + System.out.println("Arguments: (%s)".formatted(String.join(", ", arguments))); + } + + if (returnValue != null) { + System.out.println("Returns: " + returnValue); + } + } + + public static String getCleanedVar(String fullyQualifiedTypeName) { + String returnType = fullyQualifiedTypeName.replace("$", "."); + + try { + var el = returnType.split("\\."); + Integer.valueOf(el[el.length - 1]); + + // so this is a number, replace type with "var" as we have no idea. + returnType = "var"; + } catch (NumberFormatException e) { + // Not a number, continue + } + + if (returnType.contains("..")) { + // Bad type, use var + returnType = "var"; + } + + if (returnType.endsWith(".")) { + returnType = "var"; + } + + return returnType; + } +} diff --git a/TraceDiff/src/main/java/com/github/gilesi/tracediff/Main.java b/TraceDiff/src/main/java/com/github/gilesi/tracediff/Main.java new file mode 100644 index 00000000..c71b63db --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/tracediff/Main.java @@ -0,0 +1,245 @@ +package com.github.gilesi.tracediff; + +import com.github.gilesi.confgen.models.InstrumentationParameters; +import com.github.gilesi.instrumentation.ConfigurationReader; +import com.github.gilesi.instrumentation.models.InstanceReference; +import com.github.gilesi.instrumentation.models.Trace; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.*; + +public class Main { + public static InstrumentationParameters instrumentationParameters; + + private static void processArguments(String[] args) throws IOException { + if (args.length != 3) { + //System.out.println("Usage: "); + return; + } + + String traceOldLibVersionXmlFolder = args[0]; + String traceNewLibVersionXmlFolder = args[1]; + String libraryConfigPath = args[2]; + + //System.out.println("OLD: " + traceOldLibVersionXmlFolder); + //System.out.println("NEW: " + traceNewLibVersionXmlFolder); + + if (!Files.exists(Path.of(traceOldLibVersionXmlFolder)) || !Files.isDirectory(Path.of(traceOldLibVersionXmlFolder))) { + //System.out.println( + // "Argument 1 is not a Folder containing trace files. It either does not exist or is not a directory."); + return; + } + + if (!Files.exists(Path.of(traceNewLibVersionXmlFolder)) || !Files.isDirectory(Path.of(traceNewLibVersionXmlFolder))) { + //System.out.println( + // "Argument 2 is not a Folder containing trace files. It either does not exist or is not a directory."); + return; + } + + if (!Files.exists(Path.of(libraryConfigPath)) || Files.isDirectory(Path.of(libraryConfigPath))) { + //System.out.println( + // "Argument 3 is not a path to a library configuration. It either does not exist or is not a file."); + return; + } + + ArrayList oldLibVersionMethodTraces = TraceReader.readTraces(traceOldLibVersionXmlFolder); + + if (oldLibVersionMethodTraces.isEmpty()) { + //System.out.println("No old lib version traces found!"); + } + + ArrayList newLibVersionMethodTraces = TraceReader.readTraces(traceNewLibVersionXmlFolder); + + if (newLibVersionMethodTraces.isEmpty()) { + //System.out.println("No new lib version traces found!"); + } + + // Get the parameters + File configurationFile = new File(libraryConfigPath); + if (!configurationFile.exists()) { + System.err.printf("ERROR: the passed in configuration file (%s) does not exist or could not be found!%n", libraryConfigPath); + return; + } + + try { + instrumentationParameters = ConfigurationReader.parseInstrumentationparameters(configurationFile); + } catch (IOException ioException) { + System.err.printf("ERROR: Could not parse instrumentation configuration file (%s)!%n", libraryConfigPath); + System.err.println(ioException.getMessage()); + ioException.printStackTrace(); + return; + } + + //ClassUtils.generateClassFile(oldLibVersionMethodTraces); + //ClassUtils.generateClassFile(newLibVersionMethodTraces); + + normalizeTraceFiles(oldLibVersionMethodTraces); + normalizeTraceFiles(newLibVersionMethodTraces); + + generateConcatenatedTraceFile(traceOldLibVersionXmlFolder, oldLibVersionMethodTraces); + generateConcatenatedTraceFile(traceNewLibVersionXmlFolder, newLibVersionMethodTraces); + + boolean runsMatch = true; + int i = 0; + for (i = 0; i < Math.min(oldLibVersionMethodTraces.size(), newLibVersionMethodTraces.size()); i++) { + Trace oldTrace = oldLibVersionMethodTraces.get(i); + Trace newTrace = newLibVersionMethodTraces.get(i); + + if (!oldTrace.equals(newTrace)) { + //System.out.println("TRACE RUNS (STRICT) MATCHING BREAK FOUND AT INDEX: " + i); + //System.out.println("V1: " + oldTrace.getMethodReference().getMethodSignature()); + //System.out.println("V2: " + newTrace.getMethodReference().getMethodSignature()); + runsMatch = false; + break; + } + } + //System.out.println("TRACE RUNS MATCHING (STRICT): " + runsMatch); + + //System.out.println("TRACE RUNS MATCHING V1 Count: " + oldLibVersionMethodTraces.size()); + //System.out.println("TRACE RUNS MATCHING V2 Count: " + newLibVersionMethodTraces.size()); + boolean runsCountMatch = oldLibVersionMethodTraces.size() == newLibVersionMethodTraces.size(); + //System.out.println("TRACE RUNS MATCHING (COUNT): " + runsCountMatch); + + boolean runsSigMatch = true; + int j = 0; + for (j = 0; j < Math.min(oldLibVersionMethodTraces.size(), newLibVersionMethodTraces.size()); j++) { + Trace oldTrace = oldLibVersionMethodTraces.get(j); + Trace newTrace = newLibVersionMethodTraces.get(j); + + if (!oldTrace.getMethodReference().getMethodSignature().equals(newTrace.getMethodReference().getMethodSignature())) { + //System.out.println("TRACE RUNS MATCHING (SIGNATURE) BREAK FOUND AT INDEX: " + j); + //System.out.println("V1: " + oldTrace.getMethodReference().getMethodSignature()); + //System.out.println("V2: " + newTrace.getMethodReference().getMethodSignature()); + runsSigMatch = false; + break; + } + } + //System.out.println("TRACE RUNS MATCHING (SIGNATURE): " + runsSigMatch); + + String leftId = traceOldLibVersionXmlFolder.replace("/home/gus/Datasets/compsuite3/", "").replace("/traces", "").replace("-generated", ""); + String rightId = traceNewLibVersionXmlFolder.replace("/home/gus/Datasets/compsuite3/", "").replace("/traces", "").replace("-generated", ""); + + String oldTraceStrict = runsMatch ? "" : oldLibVersionMethodTraces.get(i).getMethodReference().getMethodSignature(); + String newTraceStrict = runsMatch ? "" : newLibVersionMethodTraces.get(i).getMethodReference().getMethodSignature(); + + String oldTraceCount = runsCountMatch ? "" : Integer.toString(oldLibVersionMethodTraces.size()); + String newTraceCount = runsCountMatch ? "" : Integer.toString(newLibVersionMethodTraces.size()); + + String oldTraceSig = runsSigMatch ? "" : oldLibVersionMethodTraces.get(j).getMethodReference().getMethodSignature(); + String newTraceSig = runsSigMatch ? "" : newLibVersionMethodTraces.get(j).getMethodReference().getMethodSignature(); + + //System.out.println("left-id|right-id|strict-break|strict-break-left|strict-break-right|count-break|left-count|right-count|signature-break|signature-break-left|signature-break-right"); + System.out.println(leftId + "|" + rightId + "|" + !runsMatch + "|" + oldTraceStrict + "|" + newTraceStrict + "|" + !runsCountMatch + "|" + oldTraceCount + "|" + newTraceCount + "|" + !runsSigMatch + "|" + oldTraceSig + "|" + newTraceSig); + } + + private static void normalizeTraceFiles(ArrayList oldLibVersionMethodTraces) { + ArrayList instanceOldLibVerList = new ArrayList<>(); + + for (Trace trace : oldLibVersionMethodTraces) { + //trace.setTimeStampEntry(0); + + if (trace.getPreCallArguments() != null) { + + ArrayList preCallArguments = new ArrayList<>(); + + for (InstanceReference instanceReference : trace.getPreCallArguments()) { + if (instanceOldLibVerList.contains(instanceReference.getInstanceId())) { + instanceReference.setInstanceId(instanceOldLibVerList.indexOf(instanceReference.getInstanceId()) + 1); + } else { + instanceOldLibVerList.add(instanceReference.getInstanceId()); + instanceReference.setInstanceId(instanceOldLibVerList.size()); + } + + preCallArguments.add(instanceReference); + } + + trace.setPreCallArguments(preCallArguments); + } + + if (trace.getPostCallArguments() != null) { + + ArrayList postCallArguments = new ArrayList<>(); + + for (InstanceReference instanceReference : trace.getPostCallArguments()) { + if (instanceOldLibVerList.contains(instanceReference.getInstanceId())) { + instanceReference.setInstanceId(instanceOldLibVerList.indexOf(instanceReference.getInstanceId()) + 1); + } else { + instanceOldLibVerList.add(instanceReference.getInstanceId()); + instanceReference.setInstanceId(instanceOldLibVerList.size()); + } + + postCallArguments.add(instanceReference); + } + + trace.setPostCallArguments(postCallArguments); + } + + if (trace.getReturnedValue() != null) { + InstanceReference instanceReference = trace.getReturnedValue(); + if (instanceOldLibVerList.contains(instanceReference.getInstanceId())) { + instanceReference.setInstanceId(instanceOldLibVerList.indexOf(instanceReference.getInstanceId()) + 1); + } else { + instanceOldLibVerList.add(instanceReference.getInstanceId()); + instanceReference.setInstanceId(instanceOldLibVerList.size()); + } + trace.setReturnedValue(instanceReference); + } + + if (trace.getInstanceReference() != null) { + InstanceReference instanceReference = trace.getInstanceReference(); + if (instanceOldLibVerList.contains(instanceReference.getInstanceId())) { + instanceReference.setInstanceId(instanceOldLibVerList.indexOf(instanceReference.getInstanceId()) + 1); + } else { + instanceOldLibVerList.add(instanceReference.getInstanceId()); + instanceReference.setInstanceId(instanceOldLibVerList.size()); + } + trace.setInstanceReference(instanceReference); + } + + //trace.setTimeStampExit(0); + + if (trace.getExceptionThrown() != null) { + InstanceReference instanceReference = trace.getExceptionThrown(); + if (instanceOldLibVerList.contains(instanceReference.getInstanceId())) { + instanceReference.setInstanceId(instanceOldLibVerList.indexOf(instanceReference.getInstanceId()) + 1); + } else { + instanceOldLibVerList.add(instanceReference.getInstanceId()); + instanceReference.setInstanceId(instanceOldLibVerList.size()); + } + trace.setExceptionThrown(instanceReference); + } + + //trace.setStackTrace(new ArrayList<>()); + } + } + + private static void generateConcatenatedTraceFile(String traceOldLibVersionXmlFolder, ArrayList oldLibVersionMethodTraces) throws IOException { + String leftIdJson = traceOldLibVersionXmlFolder.replace("/home/gus/Datasets/compsuite3/", "").replace("/traces", "").replace("-generated", "").replace("/", "-"); + String leftdest = "/home/gus/Datasets/results/" + leftIdJson + ".concatenated.normalized.json"; + + if (Files.exists(Path.of(leftdest))) { + Files.delete(Path.of(leftdest)); + } + + for (Trace trace : oldLibVersionMethodTraces) { + String jason = XmlUtils.getToJson(trace); + + if (Files.exists(Path.of(leftdest))) { + Files.writeString(Path.of(leftdest), ",\n" + jason, StandardOpenOption.APPEND); + } + else { + Files.writeString(Path.of(leftdest), jason, StandardOpenOption.CREATE); + } + } + } + + public static void main(String[] args) throws IOException { + //System.out.println("Hello from tracediff"); + processArguments(args); + //System.out.println("Bye from tracediff"); + } +} \ No newline at end of file diff --git a/TraceDiff/src/main/java/com/github/gilesi/tracediff/TraceReader.java b/TraceDiff/src/main/java/com/github/gilesi/tracediff/TraceReader.java new file mode 100644 index 00000000..7e1ecd6a --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/tracediff/TraceReader.java @@ -0,0 +1,48 @@ +package com.github.gilesi.tracediff; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.github.gilesi.instrumentation.models.TestTraceResults; +import com.github.gilesi.instrumentation.models.Trace; + +public class TraceReader { + private static final ObjectMapper objectMapper = new ObjectMapper() + .enable(SerializationFeature.INDENT_OUTPUT) + // .enable(SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL) + .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) + .disable(SerializationFeature.FAIL_ON_SELF_REFERENCES) + .disable(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS); + + // This is ugly, I know + private static int GetTraceNumber(Path i) { + return Integer.valueOf(i.getFileName().toString().split("_")[1].replace(".json", "")); + } + + public static ArrayList readTraces(String traceXmlFolder) throws IOException { + ArrayList traces = new ArrayList<>(); + + List traceFiles = Files.list(Path.of(traceXmlFolder)) + .sorted(Comparator.comparingInt(TraceReader::GetTraceNumber)).toList(); + + for (Path testTraceFile : traceFiles) { + try { + TestTraceResults testTraceResults = objectMapper.readValue(new File(testTraceFile.toString()), + TestTraceResults.class); + traces.add(testTraceResults.Trace); + } catch (Exception e) { + System.out.println("ERROR while deserializing a trace! " + testTraceFile); + //e.printStackTrace(); + } + } + + return traces; + } +} diff --git a/TraceDiff/src/main/java/com/github/gilesi/tracediff/TraceUtils.java b/TraceDiff/src/main/java/com/github/gilesi/tracediff/TraceUtils.java new file mode 100644 index 00000000..f269f9a5 --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/tracediff/TraceUtils.java @@ -0,0 +1,165 @@ +package com.github.gilesi.tracediff; + +import com.github.gilesi.instrumentation.models.ClassReference; +import com.github.gilesi.instrumentation.models.InstanceReference; +import com.github.gilesi.instrumentation.models.Trace; +import com.github.gilesi.tracediff.review.ArgLineGenerateResult; +import com.github.gilesi.tracediff.review.ArgumentUtils; +import com.github.gilesi.tracediff.review.SerializationUtils; +import com.github.gilesi.tracediff.exceptions.SerializationException; + +public class TraceUtils { + private static void handleConstructor(VariableStackHandler variableStackHandler, + InstanceReference instanceData, String instanceVariableName, String methodName, String argumentLine, + boolean argLineResultSuccess, Trace trace) throws SerializationException { + if (!variableStackHandler.DoesVariableExist(instanceData.getInstanceId())) { + if (argLineResultSuccess) { + variableStackHandler.CreateNewVariable(instanceData.getInstanceId(), methodName); + } + + instanceVariableName = String.format("%s %s", methodName, instanceVariableName); + } + } + + private static void handleMethod(int traceId, VariableStackHandler variableStackHandler, + InstanceReference instanceData, String instanceVariableName, String methodName, String argumentLine, + boolean argLineResultSuccess, Trace trace) throws SerializationException { + InstanceReference returnData = trace.getReturnedValue(); + + if (returnData != null) { + String returnVariableName = VariableStackHandler.getVariableName(returnData.getInstanceId()); + int returnDataInstanceId = returnData.getInstanceId(); + + ClassReference compatibleClassReference = SerializationUtils.getBetterType(returnData.getClassReference(), + trace.getMethodReference().getReturnType()); + String FQN = DescriptorUtils.getCleanedType(compatibleClassReference.getFullyQualifiedTypeName()); + String returnType = DescriptorUtils.getCleanedVar(FQN); + + if (!variableStackHandler.DoesVariableExist(returnDataInstanceId)) { + if (argLineResultSuccess) { + variableStackHandler.CreateNewVariable(returnDataInstanceId, returnType); + } else { + String warnMessage = String.format( + "[Trace ID: %d]: Instance Call would have created the now missing variable instance: %d.", + traceId + 1, returnDataInstanceId); + + // Log the warning to the current string builder and the console. + System.out.println(warnMessage); + } + + returnVariableName = String.format("%s %s", returnType, returnVariableName); + } else { + String tmpReturnType = variableStackHandler.getVariableType(returnDataInstanceId); + if (!tmpReturnType.equals("var")) { + returnType = tmpReturnType; + } + } + } + } + + private static boolean handleMethodInstance(int traceId, VariableStackHandler variableStackHandler, + InstanceReference instanceData, String instanceVariableName, String methodName, String argumentLine, + boolean argLineResultSuccess, Trace trace) throws SerializationException { + InstanceReference returnData = trace.getReturnedValue(); + + // Holds whenever or not an issue with instances was found + // If true, we should not create new variables and we should comment out the + // whole code. + // As the code simply is not generable. + boolean issuesDetected = false; + + // When we have an instance dependent call, we need the instance the call is + // performed onto to exist + // If it does not exist, then we cannot generate this code + // Log that, and comment out the code we would have generated. + // And make sure we do not create a variable for the return value if a thing. + if (!variableStackHandler.DoesVariableExist(instanceData.getInstanceId())) { + String warnMessage = String.format( + "[Trace ID: %d]: Instance Call to %s cannot be generated because it depends on %d (%s) that does not already exist.", + traceId + 1, methodName, instanceData.getInstanceId(), + instanceData.getClassReference().getFullyQualifiedTypeName()); + + // Log the warning to the current string builder and the console. + System.out.println(warnMessage); + + issuesDetected = true; + } + + // An instance method call is not called by its fully qualified name (i.e. + // bar.man.foo() but by instanceidvar.foo()), format the method name correctly. + String instanceMethodCallName = methodName.split("\\.")[methodName.split("\\.").length - 1]; + if (!variableStackHandler.DoesVariableExist(instanceData.getInstanceId())) { + methodName = String.format("%s.%s", instanceVariableName, instanceMethodCallName); + } else { + String tmpInstanceType = variableStackHandler.getVariableType(instanceData.getInstanceId()); + if (!tmpInstanceType.equals("var")) { + // TODO: use the true api type here (we know this info... but lack it in the + // trace files currently...) + methodName = String.format("((%s)%s).%s", tmpInstanceType, instanceVariableName, + instanceMethodCallName); + } else { + methodName = String.format("%s.%s", instanceVariableName, instanceMethodCallName); + } + } + + if (returnData != null) { + String returnVariableName = VariableStackHandler.getVariableName(returnData.getInstanceId()); + int returnDataInstanceId = returnData.getInstanceId(); + + ClassReference compatibleClassReference = SerializationUtils.getBetterType(returnData.getClassReference(), + trace.getMethodReference().getReturnType()); + String FQN = DescriptorUtils.getCleanedType(compatibleClassReference.getFullyQualifiedTypeName()); + String returnType = DescriptorUtils.getCleanedVar(FQN); + + if (!variableStackHandler.DoesVariableExist(returnDataInstanceId)) { + if (argLineResultSuccess && !issuesDetected) { + variableStackHandler.CreateNewVariable(returnDataInstanceId, returnType); + } else { + String warnMessage = String.format( + "[Trace ID: %d]: Instance Call would have created the now missing variable instance: %d.", + traceId + 1, returnDataInstanceId); + + // Log the warning to the current string builder and the console. + System.out.println(warnMessage); + } + + returnVariableName = String.format("%s %s", returnType, returnVariableName); + } else { + String tmpReturnType = variableStackHandler.getVariableType(returnDataInstanceId); + if (!tmpReturnType.equals("var")) { + returnType = tmpReturnType; + } + } + } + + return !issuesDetected; + } + + public static void parseTrace(int traceId, Trace trace, VariableStackHandler variableStackHandler) throws SerializationException { + String methodName = trace.getMethodReference().getMethodSignature().split("\\(")[0]; + methodName = methodName.split(" ")[methodName.split(" ").length - 1].replace("$", "."); + + InstanceReference instanceData = trace.getInstanceReference(); + + String instanceVariableName = ""; + if (instanceData != null) { + instanceVariableName = VariableStackHandler.getVariableName(instanceData.getInstanceId()); + } + + ArgLineGenerateResult argLineResult = ArgumentUtils.GenerateArgLine(trace, variableStackHandler); + + boolean isInstanceCall = instanceData != null && !instanceData.getClassReference() + .getFullyQualifiedTypeName().replace("$", ".").equals(methodName); + + if (trace.getMethodReference().getIsConstructor()) { + handleConstructor(variableStackHandler, instanceData, instanceVariableName, methodName, + argLineResult.argumentsForMethodCall, argLineResult.Success, trace); + } else if (isInstanceCall) { + handleMethodInstance(traceId, variableStackHandler, instanceData, instanceVariableName, + methodName, argLineResult.argumentsForMethodCall, argLineResult.Success, trace); + } else { + handleMethod(traceId, variableStackHandler, instanceData, instanceVariableName, methodName, + argLineResult.argumentsForMethodCall, argLineResult.Success, trace); + } + } +} \ No newline at end of file diff --git a/TraceDiff/src/main/java/com/github/gilesi/tracediff/VariableStackHandler.java b/TraceDiff/src/main/java/com/github/gilesi/tracediff/VariableStackHandler.java new file mode 100644 index 00000000..9879bb69 --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/tracediff/VariableStackHandler.java @@ -0,0 +1,25 @@ +package com.github.gilesi.tracediff; + +import java.util.HashMap; + +public class VariableStackHandler { + private HashMap variableInstances = new HashMap<>(); + + public void CreateNewVariable(int instanceId, String variableType) { + if (!DoesVariableExist(instanceId)) { + variableInstances.put(instanceId, variableType); + } + } + + public boolean DoesVariableExist(int instanceId) { + return variableInstances.containsKey(instanceId); + } + + public static String getVariableName(int instanceId) { + return String.format("var%d", instanceId); + } + + public String getVariableType(int instanceId) { + return variableInstances.get(instanceId); + } +} diff --git a/TraceDiff/src/main/java/com/github/gilesi/tracediff/XmlUtils.java b/TraceDiff/src/main/java/com/github/gilesi/tracediff/XmlUtils.java new file mode 100644 index 00000000..e92fab20 --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/tracediff/XmlUtils.java @@ -0,0 +1,27 @@ +package com.github.gilesi.tracediff; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.DomDriver; + +public class XmlUtils { + private static DomDriver domDriver = new DomDriver(); + private static XStream xStream = new XStream(domDriver); + private static Object syncObj = new Object(); + public static final ObjectMapper objectMapper = new ObjectMapper() + .enable(SerializationFeature.INDENT_OUTPUT) + //.enable(SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL) + .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) + .disable(SerializationFeature.FAIL_ON_SELF_REFERENCES) + .disable(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS); + + public static String getToXml(Object obj) { + return xStream.toXML(obj); + } + + public static String getToJson(Object obj) throws JsonProcessingException { + return objectMapper.writeValueAsString(obj); + } +} diff --git a/TraceDiff/src/main/java/com/github/gilesi/tracediff/exceptions/SerializationException.java b/TraceDiff/src/main/java/com/github/gilesi/tracediff/exceptions/SerializationException.java new file mode 100644 index 00000000..274c6689 --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/tracediff/exceptions/SerializationException.java @@ -0,0 +1,7 @@ +package com.github.gilesi.tracediff.exceptions; + +public class SerializationException extends Exception { + public SerializationException(String message) { + super(message); + } +} diff --git a/TraceDiff/src/main/java/com/github/gilesi/tracediff/exceptions/UnsupportedJavaPrimitiveTypeException.java b/TraceDiff/src/main/java/com/github/gilesi/tracediff/exceptions/UnsupportedJavaPrimitiveTypeException.java new file mode 100644 index 00000000..3354b27f --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/tracediff/exceptions/UnsupportedJavaPrimitiveTypeException.java @@ -0,0 +1,7 @@ +package com.github.gilesi.tracediff.exceptions; + +public class UnsupportedJavaPrimitiveTypeException extends Exception { + public UnsupportedJavaPrimitiveTypeException(String primitive) { + super("Unsupported primitive type: %s".formatted(primitive)); + } +} diff --git a/TraceDiff/src/main/java/com/github/gilesi/tracediff/review/ArgLineGenerateResult.java b/TraceDiff/src/main/java/com/github/gilesi/tracediff/review/ArgLineGenerateResult.java new file mode 100644 index 00000000..a763747f --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/tracediff/review/ArgLineGenerateResult.java @@ -0,0 +1,15 @@ +package com.github.gilesi.tracediff.review; + +import java.util.List; + +public class ArgLineGenerateResult { + public boolean Success; + public String argumentsForMethodCall; + public List variableList; + + public ArgLineGenerateResult(boolean Success, String argumentsForMethodCall, List variableList) { + this.Success = Success; + this.argumentsForMethodCall = argumentsForMethodCall; + this.variableList = variableList; + } +} \ No newline at end of file diff --git a/TraceDiff/src/main/java/com/github/gilesi/tracediff/review/ArgumentUtils.java b/TraceDiff/src/main/java/com/github/gilesi/tracediff/review/ArgumentUtils.java new file mode 100644 index 00000000..182fef21 --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/tracediff/review/ArgumentUtils.java @@ -0,0 +1,192 @@ +package com.github.gilesi.tracediff.review; + +import java.util.ArrayList; +import java.util.List; + +import com.github.gilesi.instrumentation.models.ClassReference; +import com.github.gilesi.instrumentation.models.InstanceReference; +import com.github.gilesi.instrumentation.models.Trace; +import com.github.gilesi.tracediff.DescriptorUtils; +import com.github.gilesi.tracediff.VariableStackHandler; + +public class ArgumentUtils { + public static ArgLineGenerateResult GenerateArgLine(Trace trace, VariableStackHandler variableStackHandler) { + boolean Succeeded = true; + + List variableDeclarationList = new ArrayList<>(); + List preCallArguments = new ArrayList<>(); + + for (int i = 0; i < trace.getPreCallArguments().size(); i++) { + boolean localSucceeded = true; + String localArgumentType = ""; + int localArgumentInstanceId = 0; + boolean localAddArgument = false; + + InstanceReference preCallArgument = trace.getPreCallArguments().get(i); + + if (preCallArgument.getIsNull()) { + preCallArguments.add("null"); + continue; + } + + ClassReference correspondingMethodArg = trace.getMethodReference().getParameterTypes().get(i); + + String argFQN = DescriptorUtils.getCleanedType(correspondingMethodArg.getFullyQualifiedTypeName()); + String argReturnType = DescriptorUtils.getCleanedVar(argFQN); + + // TODO: check if type equals "var" here which would be a terrible mistake. + // Has to be defined already so no need to check here + if (!argReturnType.equals("var")) { + preCallArguments.add("(%s)%s".formatted(argReturnType, + VariableStackHandler.getVariableName(preCallArgument.getInstanceId()))); + } else { + System.out.println(String.format( + "WARNING: Unable to get a proper type for agument in method. This should NEVER happen! %d", + preCallArgument.getInstanceId())); + preCallArguments + .add("%s".formatted(VariableStackHandler.getVariableName(preCallArgument.getInstanceId()))); + } + + String argumentVariable = ""; + // It is possible for us to use the exact sane instance twice during the call to + // this method + // Therefore, also keep a list of variables we define temporarily given we + // commit this only at the end of this routine. + Integer instanceIdForArgument = preCallArgument.getInstanceId(); + boolean variableAlreadyComitted = variableStackHandler.DoesVariableExist(instanceIdForArgument); + if (variableAlreadyComitted) { + argumentVariable = VariableStackHandler.getVariableName(preCallArgument.getInstanceId()); + } else { + ClassReference compatibleClassReference = SerializationUtils + .getBetterType(preCallArgument.getClassReference(), correspondingMethodArg); + String FQN = DescriptorUtils.getCleanedType(compatibleClassReference.getFullyQualifiedTypeName()); + String returnType = DescriptorUtils.getCleanedVar(FQN); + + // We do not handle lambdas currently, so fail here. + if (preCallArgument.getClassReference().getFullyQualifiedTypeName().contains("Lambda/")) { + localSucceeded = false; + } else { + // Variable does not exist yet, commit to it later + localArgumentInstanceId = instanceIdForArgument; + localArgumentType = returnType; + localAddArgument = true; + } + + argumentVariable = String.format("%s %s", returnType, + VariableStackHandler.getVariableName(preCallArgument.getInstanceId())); + } + + String argumentValue = ""; + if (preCallArgument.getValueAsXmlString() != null && !preCallArgument.getValueAsXmlString().isEmpty()) { + try { + argumentValue = SerializationUtils.serializableDataToJava(preCallArgument, correspondingMethodArg); + } catch (Exception e) { + System.out.println("Unable to generate code for serializing data to java!"); + e.printStackTrace(); + // fail... + // We do not have a value we can deal with, see if it already exists to use this + // instead. + if (variableAlreadyComitted) { + // This is a bit redundant as we will write var1 = var1, fix later, purely + // cosmetic. + argumentValue = VariableStackHandler.getVariableName(preCallArgument.getInstanceId()); + } else { + // We cant do much, fail. + argumentValue = "\"%s\"".formatted(preCallArgument.getValueAsXmlString()); + localSucceeded = false; + } + } + } else { + // We do not have a value we can deal with, see if it already exists to use this + // instead. + if (variableAlreadyComitted) { + // This is a bit redundant as we will write var1 = var1, fix later, purely + // cosmetic. + argumentValue = VariableStackHandler.getVariableName(preCallArgument.getInstanceId()); + } else { + // We cant do much, fail. + argumentValue = "\"UNKNOWN_DATA_VALUE\""; + localSucceeded = false; + } + } + + if (variableAlreadyComitted) { + String definedType = variableStackHandler.getVariableType(instanceIdForArgument); + + // Define the variable before the method + // TODO: check if type equals "var" here which would be a terrible mistake. + if (!definedType.equals("var")) { + if (!definedType.equals("java.lang.Integer") && + !definedType.equals("java.lang.Void") && + !definedType.equals("java.lang.Boolean") && + !definedType.equals("java.lang.Byte") && + !definedType.equals("java.lang.Character") && + !definedType.equals("java.lang.Short") && + !definedType.equals("java.lang.Double") && + !definedType.equals("java.lang.Float") && + !definedType.equals("java.lang.Long") && + !definedType.equals("java.lang.String")) { + if (argumentValue.startsWith("-")) { + // TODO: Only works with integer! + if (localSucceeded) { + variableDeclarationList + .add("%s = (%s)(%s);".formatted(argumentVariable, definedType, argumentValue)); + } else { + variableDeclarationList + .add("// %s = (%s)(%s);".formatted(argumentVariable, definedType, argumentValue)); + } + } else { + if (localSucceeded) { + variableDeclarationList + .add("%s = (%s)%s;".formatted(argumentVariable, definedType, argumentValue)); + } else { + variableDeclarationList + .add("// %s = (%s)%s;".formatted(argumentVariable, definedType, argumentValue)); + } + } + } else { + if (localSucceeded) { + variableDeclarationList + .add("%s = %s;".formatted(argumentVariable, argumentValue)); + } else { + variableDeclarationList + .add("// %s = %s;".formatted(argumentVariable, argumentValue)); + } + } + } else { + System.out.println(String.format( + "WARNING: Unable to get a proper type for agument in method. This should NEVER happen! %d", + instanceIdForArgument)); + if (localSucceeded) { + variableDeclarationList.add("%s = %s;".formatted(argumentVariable, argumentValue)); + } else { + variableDeclarationList.add("// %s = %s;".formatted(argumentVariable, argumentValue)); + } + } + } else { + // Define the variable before the method + if (localSucceeded) { + variableDeclarationList.add("%s = %s;".formatted(argumentVariable, argumentValue)); + } else { + variableDeclarationList.add("// %s = %s;".formatted(argumentVariable, argumentValue)); + } + } + + if (localSucceeded && localAddArgument) { + if (!variableStackHandler.DoesVariableExist(localArgumentInstanceId)) { + variableStackHandler.CreateNewVariable(localArgumentInstanceId, localArgumentType); + } + } + + if (!localSucceeded) { + Succeeded = false; + } + } + + String formattedArgumentsForUseInMethodCall = String.join(", ", preCallArguments); + + // Return if we succeeded, the list of stuff to pass as method arguments, and + // the list of variable declarations to add if any + return new ArgLineGenerateResult(Succeeded, formattedArgumentsForUseInMethodCall, variableDeclarationList); + } +} \ No newline at end of file diff --git a/TraceDiff/src/main/java/com/github/gilesi/tracediff/review/SerializationUtils.java b/TraceDiff/src/main/java/com/github/gilesi/tracediff/review/SerializationUtils.java new file mode 100644 index 00000000..73c6ec5d --- /dev/null +++ b/TraceDiff/src/main/java/com/github/gilesi/tracediff/review/SerializationUtils.java @@ -0,0 +1,157 @@ +package com.github.gilesi.tracediff.review; + +import com.github.gilesi.instrumentation.models.ClassReference; +import com.github.gilesi.instrumentation.models.InstanceReference; +import com.github.gilesi.tracediff.DescriptorUtils; +import com.github.gilesi.tracediff.Main; +import com.github.gilesi.tracediff.exceptions.SerializationException; +import org.apache.commons.text.StringEscapeUtils; + +import java.util.ArrayList; +import java.util.List; + +public class SerializationUtils { + public static ClassReference getCompatibleClassReference(ClassReference classReference) { + String FQN = classReference.getFullyQualifiedTypeName(); + if (FQN.startsWith("java.")) { + return classReference; + } + + if (FQN.replace("[]", "").equals("int") || + FQN.replace("[]", "").equals("void") || + FQN.replace("[]", "").equals("boolean") || + FQN.replace("[]", "").equals("byte") || + FQN.replace("[]", "").equals("char") || + FQN.replace("[]", "").equals("short") || + FQN.replace("[]", "").equals("double") || + FQN.replace("[]", "").equals("float") || + FQN.replace("[]", "").equals("long") || + FQN.replace("[]", "").equals("string")) { + return classReference; + } + + String[] libraryTypes = Main.instrumentationParameters.LibraryTypes; + + for (String libraryType : libraryTypes) { + if (FQN.equals(libraryType)) { + return classReference; + } + } + + if (classReference.getSuperClass() != null) { + ClassReference compatibleClassReference = getCompatibleClassReference(classReference.getSuperClass()); + if (compatibleClassReference != null) { + return compatibleClassReference; + } + } + + for (ClassReference interfaceClassReference : classReference.getInterfaces()) { + ClassReference compatibleClassReference = getCompatibleClassReference(interfaceClassReference); + if (compatibleClassReference != null) { + return compatibleClassReference; + } + } + + return null; + } + + public static ClassReference getBetterType(ClassReference actualType, ClassReference desiredType) { + ClassReference compatibleClassReference = getCompatibleClassReference(actualType); + boolean isValid = compatibleClassReference != null + && !compatibleClassReference.getFullyQualifiedTypeName().equals("java.lang.Object"); + + if (!isValid) { + compatibleClassReference = getCompatibleClassReference(desiredType); + isValid = compatibleClassReference != null + && !compatibleClassReference.getFullyQualifiedTypeName().equals("java.lang.Object"); + if (!isValid) { + System.out + .println("WARNING: Argument type is not supported by the library of Java Runtime Environment."); + System.out.println("WARNING: Actual Type: " + actualType.getFullyQualifiedTypeName()); + System.out.println("WARNING: Desired Type: " + desiredType.getFullyQualifiedTypeName()); + } + return desiredType; + } else { + return compatibleClassReference; + } + } + + public static String serializableDataToJava(InstanceReference serializableData, ClassReference argClass) + throws SerializationException { + String valueToBeEqualTo = serializableData.getValueAsXmlString(); + + ClassReference compatibleClassReference = getBetterType(serializableData.getClassReference(), argClass); + String FQN = DescriptorUtils.getCleanedType(compatibleClassReference.getFullyQualifiedTypeName()); + + if (!FQN.equals("java.lang.Integer") && + !FQN.equals("java.lang.Void") && + !FQN.equals("java.lang.Boolean") && + !FQN.equals("java.lang.Byte") && + !FQN.equals("java.lang.Character") && + !FQN.equals("java.lang.Short") && + !FQN.equals("java.lang.Double") && + !FQN.equals("java.lang.Float") && + !FQN.equals("java.lang.Long") && + !FQN.equals("java.lang.String")) { + + String valueAsCode = generateStringJavaLine(serializableData.getValueAsXmlString()); + valueToBeEqualTo = "getFromXml(%s)".formatted(valueAsCode); + } else { + int start = valueToBeEqualTo.indexOf(">"); + int end = valueToBeEqualTo.lastIndexOf(" valueToBeEqualTo.length() - 1 - 3) { + throw new SerializationException("Malformed XML data"); + } + + valueToBeEqualTo = valueToBeEqualTo.substring(start + 1, end); + + if (FQN.equals("java.lang.Character")) { + valueToBeEqualTo = "'%s'".formatted(valueToBeEqualTo); + if (valueToBeEqualTo.equals("''")) { + valueToBeEqualTo = "java.lang.Character.MIN_VALUE"; + } + } else if (FQN.equals("java.lang.String")) { + valueToBeEqualTo = generateStringJavaLine(valueToBeEqualTo); + } else if (FQN.equals("java.lang.Float")) { + valueToBeEqualTo = "%sF".formatted(valueToBeEqualTo); + } else if (FQN.equals("java.lang.Long")) { + valueToBeEqualTo = "%sL".formatted(valueToBeEqualTo); + } + } + + return valueToBeEqualTo; + } + + public static String generateStringJavaLine(String stringContent) { + String valueAsCode = "\"%s\"".formatted(StringEscapeUtils.escapeJava(stringContent)); + + // int maxStringSize = (int) Math.pow(2, 16); + // TODO: CHeck why this still fails + int maxStringSize = 1024; + + if (valueAsCode.length() > maxStringSize) { + List sectionList = new ArrayList<>(); + + for (int start2 = 0; start2 < valueAsCode.length(); start2 += maxStringSize) { + int end2 = start2 + maxStringSize; + if (start2 + maxStringSize > valueAsCode.length()) { + end2 = valueAsCode.length(); + } + + String sectionOfDataAsXml = valueAsCode.substring(start2, end2); + String readyToUseSection = "new String(\"%s\")" + .formatted(StringEscapeUtils.escapeJava(sectionOfDataAsXml)); + sectionList.add(readyToUseSection); + } + + valueAsCode = String.join(" + ", sectionList.toArray(String[]::new)); + } + + return valueAsCode; + } +} diff --git a/TraceView/src/main/java/com/github/maracas/gilesi/instrumentation/MethodTrace.java b/TraceView/src/main/java/com/github/maracas/gilesi/instrumentation/MethodTrace.java deleted file mode 100644 index beec0f6e..00000000 --- a/TraceView/src/main/java/com/github/maracas/gilesi/instrumentation/MethodTrace.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.github.maracas.gilesi.instrumentation; - -import java.util.List; - -public class MethodTrace { - public String methodSignature; - public SerializableData instance; - public List preCallArguments; - public List postCallArguments; - public SerializableData returnedValue; - public long timeStampEntry; - public long timeStampExit; - - public MethodTrace(String methodSignature, SerializableData instance, List preCallArguments, - List postCallArguments, SerializableData returnedValue, long timeStampEntry, - long timeStampExit) - { - this.instance = instance; - this.methodSignature = methodSignature; - this.timeStampEntry = timeStampEntry; - this.postCallArguments = postCallArguments; - this.preCallArguments = preCallArguments; - this.returnedValue = returnedValue; - this.timeStampExit = timeStampExit; - } - - public MethodTrace() {} - - @Override - public String toString() { - return "MethodTrace{" + - "methodSignature='" + methodSignature + '\'' + - ", instance=" + instance + - ", preCallArguments=" + preCallArguments + - ", postCallArguments=" + postCallArguments + - ", returnedValue=" + returnedValue + - ", timeStampEntry=" + timeStampEntry + - ", timeStampExit=" + timeStampExit + - '}'; - } - - public long getTimeStampEntry() { - return timeStampEntry; - } - - public long getTimeStampExit() { - return timeStampExit; - } -} diff --git a/TraceView/src/main/java/com/github/maracas/gilesi/instrumentation/SerializableData.java b/TraceView/src/main/java/com/github/maracas/gilesi/instrumentation/SerializableData.java deleted file mode 100644 index d38403ed..00000000 --- a/TraceView/src/main/java/com/github/maracas/gilesi/instrumentation/SerializableData.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.github.maracas.gilesi.instrumentation; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; - -public class SerializableData { - private static final ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); - - public String fullyQualifiedTypeName; - public String dataAsJson; - public int instanceId; - public boolean isNull; - public boolean isSerialized; - - public SerializableData(String fullyQualifiedTypeName, String dataAsJson, int instanceId, boolean isNull, boolean isSerialized) { - this.fullyQualifiedTypeName = fullyQualifiedTypeName; - this.dataAsJson = dataAsJson; - this.instanceId = instanceId; - this.isNull = isNull; - this.isSerialized = isSerialized; - } - - public SerializableData() {} - - public static SerializableData GetFromObject(Object object) { - if (object == null) { - return new SerializableData(null, null, -1, true, false); - } - - // /!\ CAUTION - // - // The JVM Specification __clearly__ states that the hashcode returned by the Object implementation - // (the one obtained here) is not claimed to be valid, and is not an address in memory. - // Indeed, how come do we have 32 bit integers on a 64 bit addressable system...? - // While it's __unlikely__ to encounter the same value here for our purposes, the risk is NON 0, and - // we must always check other factors to know if a value is identical to one or another, like the - // fully qualified name, or the parameter list! You've been warned... - // - // PS: if you got a better idea, I'm all ears :) - // - int instanceId = System.identityHashCode(object); - String objectClassName = object.getClass().getName(); - - String serializedString = null; - boolean isSerialized = false; - - try { - serializedString = objectMapper.writeValueAsString(object); - isSerialized = true; - } catch (JsonProcessingException e) { - //System.out.println("Cannot serialize specific argument: " + e); - } - - return new SerializableData(objectClassName, serializedString, instanceId, false, isSerialized); - } - - /*public String getJavaCodeEquivalentAsString() { - return "TODO!"; - }*/ - - @Override - public String toString() { - return "SerializableData{" + - "fullyQualifiedTypeName='" + fullyQualifiedTypeName + '\'' + - ", dataAsJson='" + dataAsJson + '\'' + - ", instanceId=" + instanceId + - ", isNull=" + isNull + - ", isSerialized=" + isSerialized + - '}'; - } -} diff --git a/TraceView/src/main/java/com/github/maracas/gilesi/traceview/Main.java b/TraceView/src/main/java/com/github/maracas/gilesi/traceview/Main.java deleted file mode 100644 index 8c8901c7..00000000 --- a/TraceView/src/main/java/com/github/maracas/gilesi/traceview/Main.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.github.maracas.gilesi.traceview; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.maracas.gilesi.instrumentation.MethodTrace; -import com.github.maracas.gilesi.instrumentation.SerializableData; -import org.apache.commons.text.StringEscapeUtils; - -import java.io.File; -import java.io.IOException; -import java.util.*; - -public class Main { - // The currently free index for variables to define in our java source code - private static int varIndex = 0; - // The map of instance ids to variable indexes - private static HashMap mapOfInstancesToIndexes = new HashMap(); - // The list of instance ids we already defined - private static List listOfDefinedInstances = new ArrayList(); - - private static String getInstanceVarName(int instanceId) { - int i; - - if (mapOfInstancesToIndexes.containsKey(instanceId)) { - i = mapOfInstancesToIndexes.get(instanceId); - } else { - i = varIndex++; - mapOfInstancesToIndexes.put(instanceId, i); - } - - return "var" + i; - } - - private static String serializableDataToCode(SerializableData arg) { - if (arg.isNull) { - return "null"; - } else if (arg.isSerialized) { - return serializableDataToJava(arg); - } else if (mapOfInstancesToIndexes.containsKey(arg.instanceId)) { - return getInstanceVarName(arg.instanceId); - } else { - return "InstanceId{" + arg.instanceId + "}"; - } - } - - private static String getCleanedType(String FQN) { - if (FQN.startsWith("[L") && FQN.endsWith(";")) { - FQN = FQN.substring(2, FQN.length() - 1) + "[]"; - } - return FQN; - } - - private static String serializableDataToJava(SerializableData serializableData) { - String valueToBeEqualTo = serializableData.dataAsJson; - String FQN = getCleanedType(serializableData.fullyQualifiedTypeName); - - if (!FQN.equals("java.lang.String") && - !FQN.equals("java.lang.Integer") && - !FQN.equals("java.lang.Double") && - !FQN.equals("java.lang.Float") && - !FQN.equals("java.lang.Long")) { - valueToBeEqualTo = "new ObjectMapper().readValue(\"" + StringEscapeUtils.escapeJava(serializableData.dataAsJson) + "\", " + FQN + ".class)"; - } - return valueToBeEqualTo; - } - - private static String getReturnStatementAsCode(SerializableData serializableData) { - if (listOfDefinedInstances.contains(serializableData.instanceId)) { - return getInstanceVarName(serializableData.instanceId); - } else { - String returnType = getCleanedType(serializableData.fullyQualifiedTypeName); - listOfDefinedInstances.add(serializableData.instanceId); - return returnType + " " + getInstanceVarName(serializableData.instanceId); - } - } - - private static String traceToCode(MethodTrace methodTrace) { - String cleanedMethodName = methodTrace.methodSignature.split("\\(")[0]; - cleanedMethodName = cleanedMethodName.split(" ")[cleanedMethodName.split(" ").length - 1]; - - boolean isConstructor = !methodTrace.instance.isNull && methodTrace.instance.fullyQualifiedTypeName.equals(cleanedMethodName); - boolean isInstanceCall = !methodTrace.instance.isNull && !methodTrace.instance.fullyQualifiedTypeName.equals(cleanedMethodName); - - if (isConstructor) { - cleanedMethodName = "new " + cleanedMethodName; - } else if (isInstanceCall) { - String methodNameOnly = cleanedMethodName.split("\\.")[cleanedMethodName.split("\\.").length - 1]; - cleanedMethodName = getInstanceVarName(methodTrace.instance.instanceId) + "." + methodNameOnly; - } - - if (isConstructor) { - // TODO: we need to fix the tracer so for constructors we get to return an instance, and instead leave instance field empty, to make more sense and simplify this part too!! - // TODO: also we may want seen indexes to keep the order in the json in general for traces, like which index we added something at for sanity, can be useful when ordering things properly! - // TODO: last but not least, we can return instances that either previously existed or never did. - // TODO: We could return null as well! If we do, then we got a type, just set to null, how do we check that even..? - - // Value returned is of course, the instance! - - cleanedMethodName = getReturnStatementAsCode(methodTrace.instance) + " = " + cleanedMethodName; - } else if (!methodTrace.returnedValue.isNull) { - // We return a value that we managed to serialize - cleanedMethodName = getReturnStatementAsCode(methodTrace.returnedValue) + " = " + cleanedMethodName; - } - - List argList = new ArrayList<>(); - for (SerializableData arg : methodTrace.preCallArguments) { - String argumentValueAsCode = serializableDataToCode(arg); - String argumentDeclaration = getReturnStatementAsCode(arg) + " = " + argumentValueAsCode; - - argList.add(getInstanceVarName(arg.instanceId)); - cleanedMethodName = argumentDeclaration + ";\n" + cleanedMethodName; - } - - return cleanedMethodName + "(" + String.join(",", argList) + ");"; - } - - private static String traceArgumentsToAssert(MethodTrace methodTrace) { - List argList = new ArrayList<>(); - for (SerializableData arg : methodTrace.postCallArguments) { - String argumentValueAsCode = serializableDataToCode(arg); - argList.add("assert " + getInstanceVarName(arg.instanceId) + ".equals(" + argumentValueAsCode + ") : \"Value is not equal to recorded value from Trace!\";"); - } - - return String.join("\n", argList); - } - - private static String traceToAssert(MethodTrace methodTrace) { - String cleanedMethodName = methodTrace.methodSignature.split("\\(")[0]; - cleanedMethodName = cleanedMethodName.split(" ")[cleanedMethodName.split(" ").length - 1]; - boolean isConstructor = !methodTrace.instance.isNull && methodTrace.instance.fullyQualifiedTypeName.equals(cleanedMethodName); - - if (isConstructor) { - // Nothing to do! - } else if (!methodTrace.returnedValue.isNull) { - // We return a value that we managed to serialize - String returnValueAsCode = serializableDataToCode(methodTrace.returnedValue); - return "assert " + getInstanceVarName(methodTrace.returnedValue.instanceId) + ".equals(" + returnValueAsCode + ") : \"Value is not equal to recorded value from Trace!\";"; - } - - return null; - } - - private static MethodTrace[] readMethodTraces(String filePathStr) throws IOException { - ObjectMapper objectMapper = new ObjectMapper(); - return objectMapper.readValue(new File(filePathStr), MethodTrace[].class); - } - - private static List getTraceEventsFromMethodTraces(MethodTrace[] methodTraces) { - List events = new ArrayList<>(); - - for (MethodTrace methodTrace : methodTraces) { - events.add(new TraceEvent("Entry", methodTrace.timeStampEntry, methodTrace)); - events.add(new TraceEvent("Exit", methodTrace.timeStampExit, methodTrace)); - } - - return events.stream().sorted(Comparator.comparing(TraceEvent::At)).toList(); - } - - private static void printMethodTraceAsCode(TraceEvent event) { - MethodTrace trace = event.About(); - - String methodCallLine = traceToCode(trace); - String returnAssertionLine = traceToAssert(trace); - String postCallParameterAssertionLine = traceArgumentsToAssert(trace); - - if (methodCallLine != null && !methodCallLine.isEmpty()) { - System.out.println(methodCallLine); - } - - if (returnAssertionLine != null && !returnAssertionLine.isEmpty()) { - System.out.println(returnAssertionLine); - } - - if (postCallParameterAssertionLine != null && !postCallParameterAssertionLine.isEmpty()) { - System.out.println(postCallParameterAssertionLine); - } - } - - public static void main(String[] args) throws IOException { - MethodTrace[] methodTraces = readMethodTraces(args[0]); - List eventsSortedByTimeStamp = getTraceEventsFromMethodTraces(methodTraces); - - /*for (TraceEvent event : eventsSortedByTimeStamp) { - System.out.println("[" + event.At() + "] " + event.What() + " of [" + event.About().methodSignature + "]"); - }*/ - - for (TraceEvent event : eventsSortedByTimeStamp) { - if (event.What().equals("Entry")) { - printMethodTraceAsCode(event); - System.out.println(); - } - } - } -} \ No newline at end of file diff --git a/TraceView/src/main/java/com/github/maracas/gilesi/traceview/TraceEvent.java b/TraceView/src/main/java/com/github/maracas/gilesi/traceview/TraceEvent.java deleted file mode 100644 index 5e6d79c8..00000000 --- a/TraceView/src/main/java/com/github/maracas/gilesi/traceview/TraceEvent.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.maracas.gilesi.traceview; - -import com.github.maracas.gilesi.instrumentation.MethodTrace; - -public record TraceEvent(String What, long At, MethodTrace About) { - -} diff --git a/cleanup.cmd b/cleanup.cmd new file mode 100644 index 00000000..a2df1643 --- /dev/null +++ b/cleanup.cmd @@ -0,0 +1,35 @@ +@echo off + +rmdir /Q /S .\CompSuiteHarness\.gradle\ +rmdir /Q /S .\CompSuiteHarness\bin\ +rmdir /Q /S .\CompSuiteHarness\build\ + +rmdir /Q /S .\ConfGen\.gradle\ +rmdir /Q /S .\ConfGen\bin\ +rmdir /Q /S .\ConfGen\build\ + +rmdir /Q /S .\Instrumentation\.gradle\ +rmdir /Q /S .\Instrumentation\bin\ +rmdir /Q /S .\Instrumentation\build\ + +rmdir /Q /S .\Maestro\.gradle\ +rmdir /Q /S .\Maestro\bin\ +rmdir /Q /S .\Maestro\build\ + +rmdir /Q /S .\Results\ + +rmdir /Q /S .\Samples\Gradle\.gradle\ + +rmdir /Q /S .\Samples\Gradle\sampleclient\bin\ +rmdir /Q /S .\Samples\Gradle\sampleclient\build\ + +rmdir /Q /S .\Samples\Gradle\samplelibrary\bin\ +rmdir /Q /S .\Samples\Gradle\samplelibrary\build\ + +rmdir /Q /S .\TestGenerator\.gradle\ +rmdir /Q /S .\TestGenerator\bin\ +rmdir /Q /S .\TestGenerator\build\ + +rmdir /Q /S .\TraceDiff\.gradle\ +rmdir /Q /S .\TraceDiff\bin\ +rmdir /Q /S .\TraceDiff\build\ \ No newline at end of file diff --git a/cleanup.sh b/cleanup.sh new file mode 100644 index 00000000..3ac5e92d --- /dev/null +++ b/cleanup.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +rm -rf ./CompSuiteHarness/.gradle/ +rm -rf ./CompSuiteHarness/bin/ +rm -rf ./CompSuiteHarness/build/ + +rm -rf ./ConfGen/.gradle/ +rm -rf ./ConfGen/bin/ +rm -rf ./ConfGen/build/ + +rm -rf ./Instrumentation/.gradle/ +rm -rf ./Instrumentation/bin/ +rm -rf ./Instrumentation/build/ + +rm -rf ./Maestro/.gradle/ +rm -rf ./Maestro/bin/ +rm -rf ./Maestro/build/ + +rm -rf ./Results/ + +rm -rf ./Samples/Gradle/.gradle/ + +rm -rf ./Samples/Gradle/sampleclient/bin/ +rm -rf ./Samples/Gradle/sampleclient/build/ + +rm -rf ./Samples/Gradle/samplelibrary/bin/ +rm -rf ./Samples/Gradle/samplelibrary/build/ + +rm -rf ./TestGenerator/.gradle/ +rm -rf ./TestGenerator/bin/ +rm -rf ./TestGenerator/build/ + +rm -rf ./TraceDiff/.gradle/ +rm -rf ./TraceDiff/bin/ +rm -rf ./TraceDiff/build/ \ No newline at end of file diff --git a/compile.cmd b/compile.cmd new file mode 100644 index 00000000..eb8b5d80 --- /dev/null +++ b/compile.cmd @@ -0,0 +1,60 @@ +@echo off + +set JAVA_HOME=C:\Program Files\Microsoft\jdk-21.0.4.7-hotspot + +echo. +echo =========================================================== +echo Building ConfGen +echo =========================================================== +echo. + +REM build confgen +cd ConfGen +call .\gradlew.bat shadowJar --warning-mode all +cd .. + + +echo. +echo =========================================================== +echo Building TestGenerator +echo =========================================================== +echo. + +REM build testgenerator +cd TestGenerator +call .\gradlew.bat shadowJar --warning-mode all +cd .. + + +echo. +echo =========================================================== +echo Building TraceDiff +echo =========================================================== +echo. + +REM build tracediff +cd TraceDiff +call .\gradlew.bat shadowJar --warning-mode all +cd .. + +echo. +echo =========================================================== +echo Building Agent +echo =========================================================== +echo. + +REM build tool +cd Instrumentation +call .\gradlew.bat shadowJar --warning-mode all +cd .. + +echo. +echo =========================================================== +echo Building Maestro +echo =========================================================== +echo. + +REM build Maestro +cd Maestro +call .\gradlew.bat shadowJar --warning-mode all +cd .. \ No newline at end of file diff --git a/compile.sh b/compile.sh new file mode 100755 index 00000000..6e3ee66b --- /dev/null +++ b/compile.sh @@ -0,0 +1,102 @@ +#!/bin/bash + +echo +echo =========================================================== +echo Building ConfGen +echo =========================================================== +echo + +# build confgen +cd ConfGen +sh ./gradlew shadowJar --warning-mode all +cd .. + + +echo +echo =========================================================== +echo Building TestGenerator +echo =========================================================== +echo + +# build testgenerator +cd TestGenerator +sh ./gradlew shadowJar --warning-mode all +cd .. + + +echo +echo =========================================================== +echo Building TraceDiff +echo =========================================================== +echo + +# build tracediff +cd TraceDiff +sh ./gradlew shadowJar --warning-mode all +cd .. + +echo +echo =========================================================== +echo Building Agent +echo =========================================================== +echo + +# build tool +cd Instrumentation +sh ./gradlew shadowJar --warning-mode all +cd .. + +echo +echo =========================================================== +echo Building Maestro +echo =========================================================== +echo + +# build maestro +cd Maestro +sh ./gradlew shadowJar --warning-mode all +cd .. + +echo +echo =========================================================== +echo Building Illico +echo =========================================================== +echo + +# build illico +cd Illico +sh ./gradlew shadowJar --warning-mode all +cd .. + +echo +echo =========================================================== +echo Building ConfGenCoverage +echo =========================================================== +echo + +# build confgencoverage +cd ConfGenCoverage +sh ./gradlew shadowJar --warning-mode all +cd .. + +echo +echo =========================================================== +echo Building Coverage +echo =========================================================== +echo + +# build coverage +cd Coverage +sh ./gradlew shadowJar --warning-mode all +cd .. + +echo +echo =========================================================== +echo Building IllicoCoverage +echo =========================================================== +echo + +# build illicocoverage +cd IllicoCoverage +sh ./gradlew shadowJar --warning-mode all +cd .. diff --git a/run-clean-workflow.cmd b/run-clean-workflow.cmd index 4a93a296..e9490cd4 100644 --- a/run-clean-workflow.cmd +++ b/run-clean-workflow.cmd @@ -9,18 +9,16 @@ cd ConfGen rmdir /Q /S build cd .. -cd TraceView +cd TestGenerator rmdir /Q /S build cd .. -cd Instrumentation +cd TraceDiff rmdir /Q /S build cd .. -del "%CD%\TestWorkflowConfiguration.json" - -cd Samples -del sampleclient\MethodTraces.json +cd Instrumentation +rmdir /Q /S build cd .. -del %CD%\Samples\sampleclient\TraceView.Output.txt \ No newline at end of file +rmdir /Q /S Results \ No newline at end of file diff --git a/run-clean-workflow.sh b/run-clean-workflow.sh new file mode 100755 index 00000000..44e60bce --- /dev/null +++ b/run-clean-workflow.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +cd Samples/Gradle +rm -rf ./sampleclient/build +rm -rf ./samplelibrary/build +cd ../.. + +cd ConfGen +rm -rf ./build +cd .. + +cd TestGenerator +rm -rf ./build +cd .. + +cd TraceDiff +rm -rf ./build +cd .. + +cd Instrumentation +rm -rf ./build +cd .. + +rm -rf ./Results \ No newline at end of file diff --git a/run-test-workflow.cmd b/run-test-workflow.cmd index e9ef45d9..02ee1166 100644 --- a/run-test-workflow.cmd +++ b/run-test-workflow.cmd @@ -1,38 +1,59 @@ @echo off -set JAVA_HOME=C:\Users\Gus\.jdks\openjdk-21.0.1 + +call run-clean-workflow.cmd +call compile.cmd + +set JAVA_HOME=C:\Program Files\Microsoft\jdk-21.0.4.7-hotspot + +mkdir Results\ + +echo. +echo =========================================================== +echo Building Samples +echo =========================================================== +echo. REM build samples -cd Samples -rmdir /Q /S sampleclient\build -rmdir /Q /S samplelibrary\build -call .\gradlew.bat jar -cd .. - -REM build confgen -cd ConfGen -rmdir /Q /S build -call .\gradlew.bat shadowJar -cd .. - -REM build traceview -cd TraceView -rmdir /Q /S build -call .\gradlew.bat shadowJar -cd .. - -REM build tool -cd Instrumentation -rmdir /Q /S build -call .\gradlew.bat shadowJar -cd .. - -del "%CD%\TestWorkflowConfiguration.json" -%JAVA_HOME%\bin\java.exe -jar "%CD%\ConfGen\build\libs\com.github.maracas.gilesi.confgen.jar" "%CD%\TestWorkflowConfiguration.json" "%CD%\Samples\samplelibrary" "%CD%\Samples\sampleclient" - -cd Samples -del sampleclient\MethodTraces.json -call .\gradlew.bat sampleclient:test -cd .. - -del %CD%\Samples\sampleclient\TraceView.Output.txt -%JAVA_HOME%\bin\java.exe -jar "%CD%\TraceView\build\libs\com.github.maracas.gilesi.traceview.jar" "%CD%\Samples\sampleclient\MethodTraces.json" > %CD%\Samples\sampleclient\TraceView.Output.txt \ No newline at end of file +cd Samples\Gradle +call .\gradlew.bat jar --warning-mode all +cd ..\.. + + +echo. +echo =========================================================== +echo Running ConfGen +echo =========================================================== +echo. + +"%JAVA_HOME%\bin\java.exe" -jar "%CD%\ConfGen\build\libs\com.github.gilesi.confgen.jar" "%CD%\Results\TestWorkflowConfiguration.json" "%CD%\Results\generated" "%CD%\Samples\Gradle\samplelibrary" "%CD%\Samples\Gradle\sampleclient" + + +echo. +echo =========================================================== +echo Running Sample's Client Test Suite with Agent +echo =========================================================== +echo. + +set JAVA_TOOL_OPTIONS=-XX:+EnableDynamicAgentLoading -javaagent:%CD%\Instrumentation\build\libs\com.github.gilesi.instrumentation.jar=%CD%\Results\TestWorkflowConfiguration.json + +cd Samples\Gradle +call .\gradlew.bat sampleclient:test --warning-mode all +cd ..\.. + +set JAVA_TOOL_OPTIONS= + +echo. +echo =========================================================== +echo Running TestGenerator +echo =========================================================== +echo. + +"%JAVA_HOME%\bin\java.exe" -jar "%CD%\TestGenerator\build\libs\com.github.gilesi.testgenerator.jar" "%CD%\Results\generated\traces" "%CD%\Results\generated\tests\src\test\java" + +echo. +echo =========================================================== +echo Running TraceDiff +echo =========================================================== +echo. + +"%JAVA_HOME%\bin\java.exe" -jar "%CD%\TraceDiff\build\libs\com.github.gilesi.tracediff.jar" "%CD%\Results\generated\traces" "%CD%\Results\generated\tests\src\test\java" \ No newline at end of file diff --git a/run-test-workflow.sh b/run-test-workflow.sh new file mode 100755 index 00000000..cb71fd47 --- /dev/null +++ b/run-test-workflow.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +sh ./run-clean-workflow.sh +sh ./compile.sh + +mkdir Results/ + +echo +echo =========================================================== +echo Building Samples +echo =========================================================== +echo + +# build samples +cd Samples/Gradle +sh ./gradlew jar --warning-mode all +cd ../.. + + +echo +echo =========================================================== +echo Running ConfGen +echo =========================================================== +echo + +"$JAVA_HOME/bin/java" -jar "$PWD/ConfGen/build/libs/com.github.gilesi.confgen.jar" "$PWD/Results/TestWorkflowConfiguration.json" "$PWD/Results/generated" "$PWD/Samples/Gradle/samplelibrary" "$PWD/Samples/Gradle/sampleclient" + + +echo +echo =========================================================== +echo $'Running Sample\'s Client Test Suite with Agent' +echo =========================================================== +echo + +export JAVA_TOOL_OPTIONS="-XX:+EnableDynamicAgentLoading -javaagent:$PWD/Instrumentation/build/libs/com.github.gilesi.instrumentation.jar=$PWD/Results/TestWorkflowConfiguration.json" + +cd Samples/Gradle +sh ./gradlew sampleclient:test --warning-mode all +cd ../.. + +export JAVA_TOOL_OPTIONS="" + +echo +echo =========================================================== +echo Running TestGenerator +echo =========================================================== +echo + +"$JAVA_HOME/bin/java" -jar "$PWD/TestGenerator/build/libs/com.github.gilesi.testgenerator.jar" "$PWD/Results/generated/traces" "$PWD/Results/generated/tests/src/test/java" + +echo +echo =========================================================== +echo Running TraceDiff +echo =========================================================== +echo + +"$JAVA_HOME/bin/java" -jar "$PWD/TraceDiff/build/libs/com.github.gilesi.tracediff.jar" "$PWD/Results/generated/traces" "$PWD/Results/generated/tests/src/test/java" \ No newline at end of file diff --git a/test.cmd b/test.cmd new file mode 100644 index 00000000..ba81285a --- /dev/null +++ b/test.cmd @@ -0,0 +1 @@ +%JAVA_HOME%\bin\java.exe -Dnet.bytebuddy.experimental=true -XX:+EnableDynamicAgentLoading -javaagent:%CD%\Instrumentation\build\libs\com.github.gilesi.instrumentation.jar="%CD%\TestWorkflowConfiguration.xml" -cp %CD%\Samples\sampleclient\build\libs\sampleclient-1.0-SNAPSHOT.jar;%CD%\Samples\samplelibrary\build\libs\samplelibrary-1.0-SNAPSHOT.jar com.github.gilesi.samples.sampleclient.Main2 \ No newline at end of file