Skip to content

Commit d6e76b8

Browse files
authored
Merge branch 'master' into feature/os-distance-operations
2 parents 94dde39 + 3b668bd commit d6e76b8

9 files changed

Lines changed: 197 additions & 4 deletions

File tree

.github/workflows/ci.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ env:
4141
release-jdk: 21
4242
build-jdk: 17
4343
retention-days: 5
44-
debug: true # put to true if need to debug a specific test
44+
debug: false # put to true if need to debug a specific test
4545
debugTestName: "GeneRandomizedTest" # replace with test to debug
4646

4747
# This build is quite expensive (some hours), so we run it whole only on some JVM versions and OSs.
@@ -62,6 +62,11 @@ jobs:
6262
- name: Set skip-jobs variable
6363
id: set-output
6464
run: echo "debug=${{ env.debug }}" >> $GITHUB_OUTPUT
65+
- name: Verify Docker is working fine
66+
run: |
67+
timeout 30s bash -c 'until docker info; do sleep 1; done'
68+
docker version
69+
docker info
6570
6671
6772
debug:

client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/SutController.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1953,6 +1953,7 @@ public final String readFileAsStringFromTestResource(String fileName){
19531953
.lines().collect(Collectors.joining(System.lineSeparator()));
19541954
}
19551955

1956+
19561957
@Override
19571958
public Map<Class, Integer> getExceptionImportanceLevels() {
19581959
return null;

client-java/test-utils-java/src/main/java/org/evomaster/test/utils/EMTestUtils.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ is used in the EvoMaster Core (eg, when making HTTP calls) and
1313
*/
1414

1515
import java.net.URI;
16+
import java.nio.file.Path;
17+
import java.nio.file.Paths;
1618

1719
/**
1820
* Class containing utility functions that can be used in the
@@ -137,4 +139,67 @@ public static boolean isValidURIorEmpty(String uri){
137139
return false;
138140
}
139141
}
142+
143+
/**
144+
* Resolves the absolute path to the Java executable using the given
145+
* JDK environment variable name.
146+
*
147+
* <p>This method expects the environment variable (e.g. {@code JAVA_HOME})
148+
* to point to a JDK installation directory. It appends {@code "bin"} and
149+
* {@code "java"} to construct the full path to the Java executable.</p>
150+
*
151+
*
152+
* @param jdkEnvVarName the name of the JDK environment variable
153+
* (e.g. {@code "JAVA_HOME"})
154+
* @return the absolute path to the Java executable as a String
155+
* @throws RuntimeException if the environment variable is not defined or empty
156+
*/
157+
public static String extractJDKPathWithEnvVarName(String jdkEnvVarName){
158+
return extractPathWithEnvVar(jdkEnvVarName, "bin", "java").toString();
159+
}
160+
161+
/**
162+
* Resolves the absolute path to a System-Under-Test (SUT) JAR file
163+
* using environment variables.
164+
*
165+
*
166+
* @param sutDistEnvVarName the environment variable that contains the base
167+
* directory of the SUT distribution
168+
* @param sutJarEnvVarName the name of the JAR file (or relative path inside the distribution)
169+
* @return the absolute path to the SUT JAR file as a String
170+
* @throws RuntimeException if the distribution environment variable is not defined or empty
171+
*/
172+
public static String extractSutJarNameWithEnvVarName(String sutDistEnvVarName, String sutJarEnvVarName){
173+
return extractPathWithEnvVar(sutDistEnvVarName, sutJarEnvVarName).toString();
174+
}
175+
176+
/**
177+
* Resolves an absolute {@link Path} using the value of a given environment variable
178+
* as the base directory and appending additional path segments.
179+
*
180+
* <p>For example, if {@code envVarName} is {@code "JAVA_HOME"} and
181+
* {@code others} contains {@code "bin", "java"}, this method will return:</p>
182+
*
183+
* <pre>
184+
* $JAVA_HOME/bin/java (Linux/macOS)
185+
* %JAVA_HOME%\bin\java (Windows)
186+
* </pre>
187+
*
188+
* <p>The resulting path is converted to an absolute path.</p>
189+
*
190+
* @param envVarName the name of the environment variable (e.g. {@code "JAVA_HOME"})
191+
* @param others additional path segments to append to the environment variable path
192+
* @return the resolved absolute {@link Path}
193+
* @throws RuntimeException if the environment variable is not defined or empty
194+
*/
195+
private static Path extractPathWithEnvVar(String envVarName, String... others){
196+
String javaHome = System.getenv(envVarName);
197+
198+
if (javaHome == null || javaHome.isEmpty()) {
199+
throw new IllegalArgumentException("Environment variable does not seem to be defined: " + envVarName);
200+
}
201+
202+
Path javaExecutable = Paths.get(javaHome, others);
203+
return javaExecutable.toAbsolutePath();
204+
}
140205
}

core/src/main/kotlin/org/evomaster/core/EMConfig.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,18 @@ class EMConfig {
807807
if(dockerLocalhost && !runningInDocker){
808808
throw ConfigProblemException("Specifying 'dockerLocalhost' only makes sense when running EvoMaster inside Docker.")
809809
}
810+
811+
if(useEnvVarsForPathInTests){
812+
if(problemType != ProblemType.DEFAULT && problemType != ProblemType.REST)
813+
throw ConfigProblemException("'useEnvVarsForPathInTests' can be applied only for REST problem.")
814+
815+
if (jdkEnvVarName.isEmpty())
816+
throw ConfigProblemException("'jdkEnvVarName' must be specified if 'useEnvVarsForPathInTests' is enabled.")
817+
if (sutDistEnvVarName.isEmpty())
818+
throw ConfigProblemException("'sutDistEnvVarName' must be specified if 'useEnvVarsForPathInTests' is enabled.")
819+
if (sutJarEnvVarName.isEmpty())
820+
throw ConfigProblemException("'sutJarEnvVarName' must be specified if 'useEnvVarsForPathInTests' is enabled.")
821+
}
810822
}
811823

812824
private fun checkPropertyConstraints(m: KMutableProperty<*>) {
@@ -2636,6 +2648,26 @@ class EMConfig {
26362648
"Note that flakiness is now supported only for fuzzing REST APIs")
26372649
var handleFlakiness = false
26382650

2651+
@Experimental
2652+
@Cfg("Use environment variables to define the paths required by External Drivers. " +
2653+
"This is necessary when the generated tests are executed on the different machine. " +
2654+
"Note that this setting only affects the generated test cases.")
2655+
var useEnvVarsForPathInTests = false
2656+
2657+
@Experimental
2658+
@Cfg("Specify name of the environment variable that provides the JDK installation path. " +
2659+
"Note that the executable path will be resolved by appending 'bin/java'.")
2660+
var jdkEnvVarName = ""
2661+
2662+
@Experimental
2663+
@Cfg("Specify name of the environment variable that provides the the base distribution directory of the " +
2664+
"SUT, e.g., 'dist' directory of WFD.")
2665+
var sutDistEnvVarName = ""
2666+
2667+
@Experimental
2668+
@Cfg("Specifies the name of the SUT JAR file that will be used together with `sutDistEnvVarName` to resolve the full SUT JAR path.")
2669+
var sutJarEnvVarName = ""
2670+
26392671
@Experimental
26402672
@Cfg("Specify a method to select the first external service spoof IP address.")
26412673
var externalServiceIPSelectionStrategy = ExternalServiceIPSelectionStrategy.NONE

core/src/main/kotlin/org/evomaster/core/output/service/TestSuiteWriter.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ class TestSuiteWriter {
6363
private const val fixture = "_fixture"
6464
private const val browser = "browser"
6565
private var containsDtos = false
66+
67+
private const val EXTRACT_JDK_PATH_ENV_VAR_METHOD = "extractJDKPathWithEnvVarName"
68+
private const val EXTRACT_SUT_PATH_ENV_VAR_METHOD = "extractSutJarNameWithEnvVarName"
69+
6670
}
6771

6872
@Inject
@@ -609,6 +613,10 @@ class TestSuiteWriter {
609613
}
610614

611615
private fun getJavaCommand(): String {
616+
if (config.useEnvVarsForPathInTests){
617+
return ".setJavaCommand($EXTRACT_JDK_PATH_ENV_VAR_METHOD(\"${config.jdkEnvVarName}\"))"
618+
}
619+
612620
if (config.javaCommand != "java") {
613621
val java = config.javaCommand.replace("\\", "\\\\")
614622
return ".setJavaCommand(\"$java\")"
@@ -625,8 +633,12 @@ class TestSuiteWriter {
625633

626634
val wireMockServers = getActiveWireMockServers()
627635

628-
val executable = if (controllerInput.isNullOrBlank()) ""
629-
else "\"$controllerInput\"".replace("\\", "\\\\")
636+
val executable = if (config.useEnvVarsForPathInTests){
637+
"$EXTRACT_SUT_PATH_ENV_VAR_METHOD(\"${config.sutDistEnvVarName}\", \"${config.sutJarEnvVarName}\")"
638+
}else{
639+
if (controllerInput.isNullOrBlank()) ""
640+
else "\"$controllerInput\"".replace("\\", "\\\\")
641+
}
630642

631643
if (config.outputFormat.isJava()) {
632644
if (!config.blackBox || config.bbExperiments) {

core/src/test/kotlin/org/evomaster/core/EMConfigTest.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,4 +719,25 @@ internal class EMConfigTest{
719719
assertEquals(config.maxTestCaseNameLength, different)
720720
}
721721

722+
723+
@Test
724+
fun testUseEnvVarsForPathInTests(){
725+
726+
val parser = EMConfig.getOptionParser()
727+
parser.recognizedOptions()["useEnvVarsForPathInTests"] ?: throw Exception("Cannot find option")
728+
729+
val config = EMConfig()
730+
val optionAllEmpty = parser.parse("--useEnvVarsForPathInTests", "true")
731+
assertThrows(Exception::class.java, {config.updateProperties(optionAllEmpty)})
732+
733+
val optionOneMissing = parser.parse("--useEnvVarsForPathInTests", "true", "--jdkEnvVarName", "JDK_HOME", "--sutDistEnvVarName", "WFC_HOME")
734+
assertThrows(Exception::class.java, {config.updateProperties(optionOneMissing)})
735+
736+
val optionAllSpecified = parser.parse("--useEnvVarsForPathInTests", "true", "--jdkEnvVarName", "JDK_HOME", "--sutDistEnvVarName", "WFC_HOME", "--sutJarEnvVarName", "sut-jar.jar")
737+
assertDoesNotThrow ({config.updateProperties(optionAllSpecified)})
738+
739+
val optionAllSpecifiedNotRest = parser.parse("--useEnvVarsForPathInTests", "true", "--jdkEnvVarName", "JDK_HOME", "--sutDistEnvVarName", "WFC_HOME", "--sutJarEnvVarName", "sut-jar.jar", "--problemType", "RPC")
740+
assertThrows (Exception::class.java,{config.updateProperties(optionAllSpecifiedNotRest)})
741+
742+
}
722743
}

core/src/test/kotlin/org/evomaster/core/output/service/TestSuiteWriterTest.kt

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,59 @@ class TestSuiteWriterTest{
149149
assertTrue(generatedUtils == PyLoader::class.java.getResource("/${TestSuiteWriter.pythonUtilsFilename}").readText())
150150
}
151151

152+
@Test
153+
fun testEmptySuiteWithEnabledEnvVar(){
154+
155+
val injector = getInjector()
156+
157+
val config = injector.getInstance(EMConfig::class.java)
158+
config.createTests = true
159+
config.outputFormat = OutputFormat.KOTLIN_JUNIT_5
160+
config.outputFolder = "$baseTargetFolder/empty_suite_use_env_var"
161+
config.outputFilePrefix = "Foo_testEmptySuiteUseEnvVar"
162+
config.outputFileSuffix = ""
163+
config.useEnvVarsForPathInTests = true
164+
config.jdkEnvVarName = "JAVA_HOME_FAKE"
165+
config.sutDistEnvVarName = "WFC_HOME_FAKE"
166+
config.sutJarEnvVarName = "fake-sut.jar"
167+
168+
val solution = getEmptySolution(config)
169+
170+
171+
//make sure we delete any existing folder from previous test runs
172+
val srcFolder = File(config.outputFolder)
173+
srcFolder.deleteRecursively()
174+
175+
//this is what used by Maven and IntelliJ
176+
val testClassFolder = File("target/test-classes")
177+
val expectedCompiledFile = testClassFolder.toPath()
178+
.resolve("${config.outputFilePrefix}.class")
179+
.toFile()
180+
expectedCompiledFile.delete()
181+
assertFalse(expectedCompiledFile.exists())
182+
183+
184+
val writer = injector.getInstance(TestSuiteWriter::class.java)
185+
186+
187+
//write the test suite
188+
writer.writeTests(solution, FakeController::class.qualifiedName!!, null)
189+
190+
val generatedTest = Paths.get("${config.outputFolder}/${config.outputFilePrefix}.kt")
191+
assertTrue(Files.exists(generatedTest))
192+
193+
/*
194+
here, we only check the generated in text
195+
as it requires to use method in SutHandler
196+
*/
197+
val testContent = String(Files.readAllBytes(generatedTest))
198+
val expectedJdkPath = "org.evomaster.core.output.service.FakeController(extractSutJarNameWithEnvVarName(\"${config.sutDistEnvVarName}\", \"${config.sutJarEnvVarName}\"))"
199+
val expectedSutPath = ".setJavaCommand(extractJDKPathWithEnvVarName(\"${config.jdkEnvVarName}\"))"
200+
201+
assertTrue(testContent.contains(expectedJdkPath))
202+
assertTrue(testContent.contains(expectedSutPath))
203+
}
204+
152205
private fun getEmptySolution(config: EMConfig): Solution<RestIndividual> {
153206
return Solution<RestIndividual>(
154207
mutableListOf(),

docs/options.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ There are 3 types of options:
286286
|`instrumentMR_NET`| __Boolean__. Execute instrumentation for method replace with category NET. Note: this applies only for languages in which instrumentation is applied at runtime, like Java/Kotlin on the JVM. *Default value*: `false`.|
287287
|`instrumentMR_OPENSEARCH`| __Boolean__. Execute instrumentation for method replace with category OPENSEARCH. Note: this applies only for languages in which instrumentation is applied at runtime, like Java/Kotlin on the JVM. *Default value*: `false`.|
288288
|`instrumentMR_REDIS`| __Boolean__. Execute instrumentation for method replace with category REDIS. Note: this applies only for languages in which instrumentation is applied at runtime, like Java/Kotlin on the JVM. *Default value*: `false`.|
289+
|`jdkEnvVarName`| __String__. Specify name of the environment variable that provides the JDK installation path. Note that the executable path will be resolved by appending 'bin/java'. *Default value*: `""`.|
289290
|`languageModelConnector`| __Boolean__. Enable language model connector. *Default value*: `false`.|
290291
|`languageModelConnectorNumberOfThreads`| __Int__. Number of threads for language model connector. No more threads than numbers of processors will be used. *Constraints*: `min=1.0`. *Default value*: `2`.|
291292
|`languageModelName`| __String__. Large-language model name as listed in Ollama. *Default value*: `llama3.2:latest`.|
@@ -321,10 +322,13 @@ There are 3 types of options:
321322
|`ssrf`| __Boolean__. To apply SSRF detection as part of security testing. *Depends on*: `security=true`. *Default value*: `false`.|
322323
|`structureMutationProFS`| __Double__. Specify a probability of applying structure mutator during the focused search. *Constraints*: `probability 0.0-1.0`. *Default value*: `0.0`.|
323324
|`structureMutationProbStrategy`| __Enum__. Specify a strategy to handle a probability of applying structure mutator during the focused search. *Valid values*: `SPECIFIED, SPECIFIED_FS, DPC_TO_SPECIFIED_BEFORE_FS, DPC_TO_SPECIFIED_AFTER_FS, ADAPTIVE_WITH_IMPACT`. *Default value*: `SPECIFIED`.|
325+
|`sutDistEnvVarName`| __String__. Specify name of the environment variable that provides the the base distribution directory of the SUT, e.g., 'dist' directory of WFD. *Default value*: `""`.|
326+
|`sutJarEnvVarName`| __String__. Specifies the name of the SUT JAR file that will be used together with `sutDistEnvVarName` to resolve the full SUT JAR path. *Default value*: `""`.|
324327
|`taintForceSelectionOfGenesWithSpecialization`| __Boolean__. During mutation, force the mutation of genes that have newly discovered specialization from previous fitness evaluations, based on taint analysis. *Default value*: `false`.|
325328
|`targetHeuristicsFile`| __String__. Where the target heuristic values file (if any) is going to be written (in CSV format). It is only used when processFormat is TARGET_HEURISTIC. *Default value*: `targets.csv`.|
326329
|`testResourcePathToSaveMockedResponse`| __String__. Specify test resource path where to save mocked responses as separated files. *Default value*: `""`.|
327330
|`thresholdDistanceForDataPool`| __Int__. Threshold of Levenshtein Distance for key-matching in Data Pool. *Constraints*: `min=0.0`. *Default value*: `2`.|
331+
|`useEnvVarsForPathInTests`| __Boolean__. Use environment variables to define the paths required by External Drivers. This is necessary when the generated tests are executed on the different machine. Note that this setting only affects the generated test cases. *Default value*: `false`.|
328332
|`useGlobalTaintInfoProbability`| __Double__. When sampling new individual, check whether to use already existing info on tainted values. *Constraints*: `probability 0.0-1.0`. *Default value*: `0.0`.|
329333
|`useInsertionForSqlHeuristics`| __Boolean__. Specify whether insertions should be used to calculate SQL heuristics instead of retrieving data from real databases. *Default value*: `false`.|
330334
|`useTestMethodOrder`| __Boolean__. Adds TestMethodOrder annotation for JUnit 5 tests. *Default value*: `false`.|

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@
122122
-->
123123
<jackson.version>2.14.3</jackson.version>
124124
<asm.version>9.9</asm.version>
125-
<testcontainers.version>1.21.0</testcontainers.version>
125+
<testcontainers.version>1.21.4</testcontainers.version>
126126
<testcontainers.mockerserver.version>1.17.2</testcontainers.mockerserver.version>
127127
<mockserver.client.version>5.13.2</mockserver.client.version>
128128
<!-- <selenium.version>4.31.0</selenium.version>-->

0 commit comments

Comments
 (0)