diff --git a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt index 1fbb3d1188..262efca03b 100644 --- a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt +++ b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt @@ -346,8 +346,8 @@ class EMConfig { val modifiedOptions = modifiedOptions(options, cff) - checkForExperimentalSettings(modifiedOptions) - checkForInternalSettings(modifiedOptions) + checkForExperimentalSettings(modifiedOptions.keys) + checkForInternalSettings(modifiedOptions.keys) checkDependsOn(modifiedOptions) } @@ -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) : 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") @@ -369,7 +378,15 @@ class EMConfig { } } - private fun checkDependsOn(modifiedOptions: Set) { + private fun checkDependsOn(modifiedOptions: Map) { + + /* + 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 } @@ -381,12 +398,29 @@ 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().forEach { a -> val target = a.otherFieldName if(!allNames.contains(target)){ @@ -394,9 +428,23 @@ class EMConfig { " 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.") + } } } } @@ -448,18 +496,19 @@ class EMConfig { } } - private fun modifiedOptions(options: OptionSet, cff: ConfigsFromFile?) : Set{ + private fun modifiedOptions(options: OptionSet, cff: ConfigsFromFile?) : Map{ val detected = OptionSet::class.java.getDeclaredField("detectedOptions") .apply { setAccessible(true) } .get(options) as Map> - 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) } } diff --git a/core/src/test/kotlin/org/evomaster/core/EMConfigTest.kt b/core/src/test/kotlin/org/evomaster/core/EMConfigTest.kt index 98cc8d3bd3..34a789c9cd 100644 --- a/core/src/test/kotlin/org/evomaster/core/EMConfigTest.kt +++ b/core/src/test/kotlin/org/evomaster/core/EMConfigTest.kt @@ -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 { + cfg.updateProperties(parser.parse("--generateNeo4jData", "true", "--extractNeo4jExecutionInfo", "false")) + } + } }