Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import org.evomaster.core.EMConfig
import org.evomaster.core.output.OutputFormat
import org.evomaster.core.problem.rest.data.HttpVerb
import org.evomaster.e2etests.spring.rest.bb.SpringTestBase
import org.evomaster.e2etests.utils.CoveredTargets
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.EnumSource

Expand Down Expand Up @@ -69,4 +72,35 @@ class BBJsonPatchTest : SpringTestBase() {
assertHasAtLeastOne(solution, HttpVerb.PATCH, 400, "/pets/{id}/sequence", null)
}
}

@Test
fun testBlackBoxWithoutJsonPatchSupport() {
val specificOpTargets = listOf(
"JSON_PATCH_ADD",
"JSON_PATCH_REMOVE",
"JSON_PATCH_REPLACE",
"JSON_PATCH_MOVE",
"JSON_PATCH_COPY",
"JSON_PATCH_TEST",
"JSON_PATCH_SEQUENCE"
)

runBlackBoxEM(OutputFormat.KOTLIN_JUNIT_5, "BBJsonPatchEM_NoSupport", 1000, 3, false) { args ->
setOption(args, "disableJsonPatchSupport", "true")

val solution = initAndRun(args)
assertTrue(solution.individuals.size >= 1)
}

val coveredWithFlagOff = specificOpTargets.count { CoveredTargets.isCovered(it) }
println("=== Flag OFF: $coveredWithFlagOff/${specificOpTargets.size} operation-specific targets covered ===")
specificOpTargets.forEach { target ->
println(" [${ if (CoveredTargets.isCovered(target)) "X" else " " }] $target")
}

assertFalse(
CoveredTargets.areCovered(specificOpTargets),
"With disableJsonPatchSupport=true, EvoMaster should NOT cover all JSON Patch operation targets"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import org.evomaster.core.EMConfig
import org.evomaster.core.output.OutputFormat
import org.evomaster.core.problem.rest.data.HttpVerb
import org.evomaster.e2etests.spring.rest.bb.SpringTestBase
import org.evomaster.e2etests.utils.CoveredTargets
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Assumptions.assumeTrue
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.EnumSource

Expand Down Expand Up @@ -67,4 +70,41 @@ class BBXMLTest : SpringTestBase() {
assertHasAtLeastOne(solution, HttpVerb.POST, 200, "/api/bbxml/projects", null)
}
}

@Test
fun testBlackBoxWithoutXmlBodySupport() {
// These targets require a well-formed XML body to reach a 200 response.
// With disableXMLSupport=true, EvoMaster falls back to generic field
// naming (schema ref name or 'body') instead of the actual JAXB element names,
// so Spring's XML deserializer receives structurally wrong documents and returns
// 400 for most requests → the 200-branch targets stay uncovered.
val xmlBodyTargets = listOf(
"XML_TO_STRING",
"EMPLOYEE",
"COMPANY",
"DEPARTMENT",
"ORGANIZATION",
"PERSON_ATTR",
"PROJECT",
"PROJECTS"
)

runBlackBoxEM(OutputFormat.KOTLIN_JUNIT_5, "BBXmlEM_NoSupport", 1000, 3, false) { args ->
setOption(args, "disableXMLSupport", "true")

val solution = initAndRun(args)
assertTrue(solution.individuals.size >= 1)
}

val coveredWithFlagOff = xmlBodyTargets.count { CoveredTargets.isCovered(it) }
println("=== Flag OFF: $coveredWithFlagOff/${xmlBodyTargets.size} XML body targets covered ===")
xmlBodyTargets.forEach { target ->
println(" [${if (CoveredTargets.isCovered(target)) "X" else " "}] $target")
}

assertFalse(
CoveredTargets.areCovered(xmlBodyTargets),
"With disableXMLSupport=true, EvoMaster should NOT cover all XML body targets"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public void testDeterminismOfLog(boolean enableConstraintHandling){
OpenAPI schema = (new OpenAPIParser()).readLocation("swagger-ahm/ahm.json", null, null).getOpenAPI();
isDeterminismConsumer( new ArrayList<>(), (args) -> RestActionBuilderV3.INSTANCE
.getModelsFromSwagger(schema, new LinkedHashMap<>(),
new RestActionBuilderV3.Options(false,enableConstraintHandling,false,0.0,0.0,true,false,false)));
new RestActionBuilderV3.Options(false,enableConstraintHandling,false,0.0,0.0,true,false,false,true,true)));
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public void testDeterminismOfLog(boolean enableConstraintHandling){
OpenAPI schema = (new OpenAPIParser()).readLocation("swagger-ahm/ahm.json", null, null).getOpenAPI();
isDeterminismConsumer( new ArrayList<>(), (args) -> {
RestActionBuilderV3.INSTANCE.getModelsFromSwagger(schema, new LinkedHashMap<>(),
new RestActionBuilderV3.Options(false,enableConstraintHandling,false,0.0,0.0,true,false,false));
new RestActionBuilderV3.Options(false,enableConstraintHandling,false,0.0,0.0,true,false,false,true,true));
});
}

Expand Down
10 changes: 10 additions & 0 deletions core/src/main/kotlin/org/evomaster/core/EMConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1378,6 +1378,16 @@ class EMConfig {
"Only available for JVM languages")
var dtoForRequestPayload = false

@Experimental
@Cfg("Disable JSON Patch (RFC 6902) gene support when the request Content-Type is 'application/json-patch+json'." +
" When true, such endpoints are treated as regular JSON bodies, reproducing the behavior before this feature was introduced.")
var disableJsonPatchSupport = false

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be renamed enableJsonPatchSupport.
also, experimental options should not be on by default.
once we run experiments, and we are happy with them, we put them on by default, and remove the tag Experimental


@Experimental
@Cfg("Disable XML-aware field naming for body genes when the request Content-Type is XML." +
" When true, body gene names fall back to the pre-feature behavior (schema ref name or 'body').")
var disableXMLSupport = false

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename enableXmlSupport, and of course update documentation due to flipped semantics


@Important(6.0)
@Cfg("Host name or IP address of where the SUT EvoMaster Controller Driver is listening on." +
" This option is only needed for white-box testing.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ object RestActionBuilderV3 {
val enableAdvancedFormats: Boolean = true,

val inferFormatFromNames: Boolean = true,

val disableJsonPatchSupport: Boolean = false,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

property should be in "positive" sense, not negative. ie, this should be renamed into jsonPatchSupport, and its semantics flipped, ie, i guess default true here


val disableXMLSupport: Boolean = false,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see previous comment. this should be renamed xmlSupport, with semantics flipped

){
constructor(config: EMConfig): this(
enableConstraintHandling = config.enableSchemaConstraintHandling,
Expand All @@ -125,7 +129,9 @@ object RestActionBuilderV3 {
probUseExamples = config.probRestExamples,
usingWhiteBox = !config.blackBox,
enableAdvancedFormats = config.enableAdvancedFormats,
inferFormatFromNames = config.inferFormatFromNames
inferFormatFromNames = config.inferFormatFromNames,
disableJsonPatchSupport = config.disableJsonPatchSupport,
disableXMLSupport = config.disableXMLSupport,
)

init {
Expand Down Expand Up @@ -750,7 +756,8 @@ object RestActionBuilderV3 {
listOf()
}

val isJsonPatch = verb == HttpVerb.PATCH && bodies.keys.any { it.contains("json-patch") }
val isJsonPatch = !options.disableJsonPatchSupport &&
verb == HttpVerb.PATCH && bodies.keys.any { it.contains("json-patch") }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should check for ignoring case of "json-patch"


val name: String
var gene: Gene
Expand All @@ -776,9 +783,13 @@ object RestActionBuilderV3 {
}
gene = JsonPatchDocumentGene(name, resourceGene)
} else {
// $ref schemas do not carry XML metadata; resolving the reference is required to obtain the correct XML element name from the target schema
val deref = obj.schema.`$ref`?.let { ref -> SchemaUtils.getReferenceSchema(schemaHolder, currentSchema, ref, messages) } ?: obj.schema
name = deref?.xml?.name ?: deref?.`$ref`?.substringAfterLast("/") ?: "body"
if (!options.disableXMLSupport) {
// $ref schemas do not carry XML metadata; resolving the reference is required to obtain the correct XML element name from the target schema
val deref = obj.schema.`$ref`?.let { ref -> SchemaUtils.getReferenceSchema(schemaHolder, currentSchema, ref, messages) } ?: obj.schema
name = deref?.xml?.name ?: deref?.`$ref`?.substringAfterLast("/") ?: "body"
} else {
name = obj.schema.`$ref`?.substringAfterLast("/") ?: "body"
}
gene = getGene(name, obj.schema, schemaHolder, currentSchema, referenceClassDef = null, options = options, messages = messages, examples = examples)
}

Expand Down Expand Up @@ -1135,8 +1146,14 @@ object RestActionBuilderV3 {
}

// Use the XML name from schema.xml.name (the name of the array element in XML)
// if available, otherwise fallback to name + "_item"
val itemName = schema.xml?.name ?: (name + "_item")
// if available, otherwise fallback to name + "_item".
// When XML support is disabled, ignore xml.name and always use the generic
// fallback, so we emulate the pre-XML-support behaviour of EvoMaster.
val itemName = if (!options.disableXMLSupport) {
schema.xml?.name ?: (name + "_item")
} else {
name + "_item"
}
val template = getGene(
itemName,
arrayType,
Expand Down Expand Up @@ -1168,9 +1185,16 @@ object RestActionBuilderV3 {
"object" -> {
val properties = schema.properties ?: emptyMap()

val attributeNames = properties
.filterValues { it.xml?.attribute == true }
.keys
// Only detect XML attribute fields when XML support is enabled. When disabled,
// this stays empty so we fall back to a plain ObjectGene (attributes rendered as
// child elements), matching how EvoMaster behaved before ObjectWithAttributesGene.
val attributeNames = if (!options.disableXMLSupport) {
properties
.filterValues { it.xml?.attribute == true }
.keys
} else {
emptySet()
}

if (attributeNames.isNotEmpty()) {
val fields = properties.map { (propName, propSchema) ->
Expand Down Expand Up @@ -1625,10 +1649,16 @@ object RestActionBuilderV3 {
valueTemplate.copy())
}

val attributeNames = schema.properties
?.filter { (_, propSchema) -> propSchema.xml?.attribute == true }
?.map { it.key }
?: emptyList()
// Same as above: skip XML attribute detection when XML support is disabled, so the object
// is assembled as a plain ObjectGene (pre-XML-support behaviour).
val attributeNames = if (!options.disableXMLSupport) {
schema.properties
?.filter { (_, propSchema) -> propSchema.xml?.attribute == true }
?.map { it.key }
?: emptyList()
} else {
emptyList()
}

if (attributeNames.isNotEmpty()) {
return ObjectWithAttributesGene(
Expand Down
2 changes: 2 additions & 0 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ There are 3 types of options:
|`cgaNeighborhoodModel`| __Enum__. Cellular GA: neighborhood model (RING, L5, C9, C13). *Valid values*: `RING, L5, C9, C13`. *Default value*: `RING`.|
|`classificationRepairThreshold`| __Double__. If using THRESHOLD for AI Classification Repair, specify its value. All classifications with probability equal or above such threshold value will be accepted. *Constraints*: `probability 0.0-1.0`. *Default value*: `0.5`.|
|`collectSqlZ3Stats`| __Boolean__. Collect detailed statistics for Z3-based SQL generation: SAT/UNSAT/error counts, query uniqueness, Z3 execution time, and SMT-LIB generation time. Only meaningful when generateSqlDataWithZ3=true. *Depends on*: `generateSqlDataWithZ3=true`. *Default value*: `false`.|
|`disableJsonPatchSupport`| __Boolean__. Disable JSON Patch (RFC 6902) gene support when the request Content-Type is 'application/json-patch+json'. When true, such endpoints are treated as regular JSON bodies, reproducing the behavior before this feature was introduced. *Default value*: `false`.|
|`disableXMLSupport`| __Boolean__. Disable XML-aware field naming for body genes when the request Content-Type is XML. When true, body gene names fall back to the pre-feature behavior (schema ref name or 'body'). *Default value*: `false`.|
|`discoveredInfoRewardedInFitness`| __Boolean__. If there is new discovered information from a test execution, reward it in the fitness function. *Default value*: `false`.|
|`dockerLocalhost`| __Boolean__. Replace references to 'localhost' to point to the actual host machine. Only needed when running EvoMaster inside Docker. *Default value*: `false`.|
|`dpcTargetTestSize`| __Int__. Specify a max size of a test to be targeted when either DPC_INCREASING or DPC_DECREASING is enabled. *Default value*: `1`.|
Expand Down
Loading