Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 63 additions & 14 deletions core/src/main/kotlin/org/evomaster/core/EMConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,8 @@ class EMConfig {

val modifiedOptions = modifiedOptions(options, cff)

checkForExperimentalSettings(modifiedOptions)
checkForInternalSettings(modifiedOptions)
checkForExperimentalSettings(modifiedOptions.keys)
checkForInternalSettings(modifiedOptions.keys)

checkDependsOn(modifiedOptions)
}
Expand All @@ -356,7 +356,16 @@ class EMConfig {
* Can only be called on BOOLEAN options.
* Calling on something else would be a bug in EM.
*/
private fun isOptionTrue(fieldName: String) : Boolean{
private fun isOptionTrue(fieldName: String, modifiedOptions: Map<String,String>) : Boolean{

if(modifiedOptions.containsKey(fieldName)) {
//the option is going to be modified. so ignore current state, and look at modification
return try{
parseBooleanStrict(modifiedOptions[fieldName])
}catch (e: Exception){
throw IllegalArgumentException("The modification for property '$fieldName' does not contain a boolean value.", e)
}
}

val field = getConfigurationProperties().find { it.name == fieldName }
?: throw IllegalArgumentException("The property called '$fieldName' does not exist")
Expand All @@ -369,7 +378,15 @@ class EMConfig {
}
}

private fun checkDependsOn(modifiedOptions: Set<String>) {
private fun checkDependsOn(modifiedOptions: Map<String,String>) {

/*
If we have "A depends -> B", deactivating "B" would not trigger a configuration error.
However, explicitly setting A on, while B is off or going to be put off, that would trigger an error.

The idea is that we can deactivate features like B without having to worry about others that depend on them.
However, if we explicitly ask for any of those latter, then B must be on.
*/

val properties = getConfigurationProperties()
val allNames = properties.map { it.name }
Expand All @@ -381,22 +398,53 @@ class EMConfig {
throw IllegalStateException("Invalid @DependsOnTrueFor definition for ${p.name}." +
" The target '$target' does not exist.")
}
//has this option been modified manually by the user? if not, there is nothing to check
if(modifiedOptions.contains(p.name) && !isOptionTrue(target)){
throw ConfigProblemException("You are explicitly setting the value of '${p.name}'," +
//has this option been modified manually by the user? if not, there is nothing to check.
if(modifiedOptions.contains(p.name)){
//however, if boolean, putting it explicitly to false should not trigger any check
val type = p.returnType.javaType
if(type is Class<*> && java.lang.Boolean.TYPE.isAssignableFrom(type)){
val on = try{
parseBooleanStrict(modifiedOptions[p.name])
}catch (e: Exception){
throw IllegalArgumentException("The boolean property called '$p.name' is set with non-boolean value: ${modifiedOptions[p.name]}")
}
if(!on){
//boolean and off, so no dependencies to check
return@forEach
}
}

if(!isOptionTrue(target, modifiedOptions)){
throw ConfigProblemException("You are explicitly setting the value of '${p.name}'," +
" which depends on '$target' being 'true', which is not currently.")
}
}
}

p.annotations.filterIsInstance<DependsOnFalseFor>().forEach { a ->
val target = a.otherFieldName
if(!allNames.contains(target)){
throw IllegalStateException("Invalid @DependsOnFalseFor definition for ${p.name}." +
" The target '$target' does not exist.")
}
//has this option been modified manually by the user? if not, there is nothing to check
if(modifiedOptions.contains(p.name) && isOptionTrue(target)){
throw ConfigProblemException("You are explicitly setting the value of '${p.name}'," +
" which depends on '$target' being 'false', which is not currently.")
if(modifiedOptions.contains(p.name)){
val type = p.returnType.javaType
if(type is Class<*> && java.lang.Boolean.TYPE.isAssignableFrom(type)){
val on = try{
parseBooleanStrict(modifiedOptions[p.name])
}catch (e: Exception){
throw IllegalArgumentException("The boolean property called '$p.name' is set with non-boolean value: ${modifiedOptions[p.name]}")
}
if(!on){
//boolean and off, so no dependencies to check
return@forEach
}
}
if( isOptionTrue(target, modifiedOptions)) {
throw ConfigProblemException("You are explicitly setting the value of '${p.name}'," +
" which depends on '$target' being 'false', which is not currently.")
}
}
}
}
Expand Down Expand Up @@ -448,18 +496,19 @@ class EMConfig {
}
}

private fun modifiedOptions(options: OptionSet, cff: ConfigsFromFile?) : Set<String>{
private fun modifiedOptions(options: OptionSet, cff: ConfigsFromFile?) : Map<String,String>{

val detected = OptionSet::class.java.getDeclaredField("detectedOptions")
.apply { setAccessible(true) }
.get(options) as Map<String,AbstractOptionSpec<*>>

val names = detected.filter { it.value !is NonOptionArgumentSpec }.keys
val modified = detected.filter { it.value !is NonOptionArgumentSpec }
.mapValues { (key, value) -> value.value(options).toString()}

return if(cff == null) {
names.toSet()
modified
} else {
names.toMutableSet().plus(cff.configs.keys)
modified.toMutableMap().plus(cff.configs)
}
}

Expand Down
31 changes: 31 additions & 0 deletions core/src/test/kotlin/org/evomaster/core/EMConfigTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -810,4 +810,35 @@ internal class EMConfigTest{
"--problemType", "ASYNCAPI", "--createTests", "false", "--seedTestCases", "true", "--seedTestCasesPath", "seeds.json"))
}
}


@Test
fun testSetFalseOnDependsOn(){

val parser = EMConfig.getOptionParser()

val cfg = EMConfig()
cfg.blackBox = false
//generateNeo4jData depends on true for extractNeo4jExecutionInfo

cfg.updateProperties(parser.parse("--generateNeo4jData", "true", "--extractNeo4jExecutionInfo", "true"))
assertEquals(true, cfg.generateNeo4jData)
assertEquals(true, cfg.extractNeo4jExecutionInfo)

cfg.updateProperties(parser.parse("--generateNeo4jData", "false", "--extractNeo4jExecutionInfo", "true"))
assertEquals(false, cfg.generateNeo4jData)
assertEquals(true, cfg.extractNeo4jExecutionInfo)

cfg.updateProperties(parser.parse("--generateNeo4jData", "false", "--extractNeo4jExecutionInfo", "false"))
assertEquals(false, cfg.generateNeo4jData)
assertEquals(false, cfg.extractNeo4jExecutionInfo)

cfg.updateProperties(parser.parse("--generateNeo4jData", "true", "--extractNeo4jExecutionInfo", "true"))
assertEquals(true, cfg.generateNeo4jData)
assertEquals(true, cfg.extractNeo4jExecutionInfo)

assertThrows<ConfigProblemException> {
cfg.updateProperties(parser.parse("--generateNeo4jData", "true", "--extractNeo4jExecutionInfo", "false"))
}
}
}
Loading