From 87e97abd3071f40dd353d335c0a5653d7ceb183f Mon Sep 17 00:00:00 2001 From: Romihime Date: Mon, 17 Nov 2025 21:22:30 -0300 Subject: [PATCH 1/7] New branch_xml due to changes from master --- .../rest/builder/RestActionBuilderV3.kt | 57 +++- .../evomaster/core/search/gene/ObjectGene.kt | 106 ++++++- .../search/gene/ObjectWithAttributesGene.kt | 125 ++++++++ .../core/search/gene/GeneSamplerForTests.kt | 32 ++ .../core/search/gene/ObjectGeneTest.kt | 29 ++ .../gene/uri/ObjectWithAttributesGeneTest.kt | 300 ++++++++++++++++++ 6 files changed, 633 insertions(+), 16 deletions(-) create mode 100644 core/src/main/kotlin/org/evomaster/core/search/gene/ObjectWithAttributesGene.kt create mode 100644 core/src/test/kotlin/org/evomaster/core/search/gene/uri/ObjectWithAttributesGeneTest.kt diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt index bd6f29290e..77689f4324 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt @@ -709,7 +709,7 @@ object RestActionBuilderV3 { body } - val name = "body" + var name = "body" val description = operation.description ?: null val bodies = resolvedBody.content?.filter { @@ -748,6 +748,11 @@ object RestActionBuilderV3 { listOf() } + val deref = obj.schema.`$ref`?.let { ref -> val name = ref.substringAfterLast("/") + SchemaUtils.getReferenceSchema(schemaHolder, currentSchema, ref, messages) } ?: obj.schema + + name = deref?.xml?.name ?: deref?.`$ref`?.substringAfterLast("/") ?: "body" + var gene = getGene("body", obj.schema, schemaHolder,currentSchema, referenceClassDef = null, options = options, messages = messages, examples = examples) @@ -944,7 +949,38 @@ object RestActionBuilderV3 { } "object" -> { - return createObjectGene(name, schema, schemaHolder,currentSchema, history, referenceClassDef, options, examples, messages) + val properties = schema.properties ?: emptyMap() + + val attributeNames = properties + .filterValues { it.xml?.attribute == true } + .keys + + if (attributeNames.isNotEmpty()) { + val fields = properties.map { (propName, propSchema) -> + getGene( + propName, + propSchema, + schemaHolder, + currentSchema, + history, + referenceClassDef, + options, + false, + examples, + messages + ) + } + + return ObjectWithAttributesGene( + name = schema.xml?.name ?: name, + fixedFields = fields, + refType = referenceClassDef, + isFixed = true, + template = null, + additionalFields = mutableListOf(), + attributeNames = attributeNames + ) + } } //TODO file is a hack. I want to find a more elegant way of dealing with it (BMR) //FIXME is this even a standard type??? @@ -1102,6 +1138,23 @@ object RestActionBuilderV3 { valueTemplate.copy()) } + val attributeNames = schema.properties + ?.filter { (_, propSchema) -> propSchema.xml?.attribute == true } + ?.map { it.key } + ?: emptyList() + + if (attributeNames.isNotEmpty()) { + return ObjectWithAttributesGene( + name = name, + fixedFields = fields, + refType = if (schema is ObjectSchema) referenceTypeName ?: schema.title else null, + isFixed = false, + template = additionalFieldTemplate, + additionalFields = mutableListOf(), + attributeNames = attributeNames.toSet() + ) + } + return assembleObjectGeneWithConstraints( name, schema, diff --git a/core/src/main/kotlin/org/evomaster/core/search/gene/ObjectGene.kt b/core/src/main/kotlin/org/evomaster/core/search/gene/ObjectGene.kt index 05920e0bd3..dadd7e8fc8 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/gene/ObjectGene.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/gene/ObjectGene.kt @@ -5,9 +5,13 @@ import org.evomaster.core.Lazy import org.evomaster.core.logging.LoggingUtil import org.evomaster.core.output.OutputFormat import org.evomaster.core.problem.graphql.GqlConst +import org.evomaster.core.search.gene.collection.ArrayGene import org.evomaster.core.search.gene.collection.EnumGene import org.evomaster.core.search.gene.collection.PairGene import org.evomaster.core.search.gene.collection.TupleGene +import org.evomaster.core.search.gene.numeric.DoubleGene +import org.evomaster.core.search.gene.numeric.FloatGene +import org.evomaster.core.search.gene.numeric.IntegerGene import org.evomaster.core.search.gene.wrapper.FlexibleGene import org.evomaster.core.search.gene.wrapper.OptionalGene import org.evomaster.core.search.gene.placeholder.CycleObjectGene @@ -37,7 +41,7 @@ import java.net.URLEncoder * - type: string * - type: integer */ -class ObjectGene( +open class ObjectGene( name: String, val fixedFields: List, val refType: String? = null, @@ -372,22 +376,96 @@ class ObjectGene( } else if (mode == GeneUtils.EscapeMode.XML) { - // TODO might have to handle here: - /* - Note: this is a very basic support, which should not really depend - much on. Problem is that we would need to access to the XSD schema - to decide when fields should be represented with tags or attributes - */ + fun escapeXmlSafe(s: String): String = + s.replace(Regex("(?", ">") + .replace("\"", """) + .replace("'", "'") + + fun singularize(n: String) = when { + n.endsWith("s") && n.length > 1 -> n.removeSuffix("s") + else -> n + }.replaceFirstChar { it.uppercase() } + + fun unwrap(v: Any?): Any? = when (v) { + is OptionalGene -> v.gene + else -> v + } + + fun cleanXmlValueString(v: String): String = + v.removeSurrounding("\"").let(::escapeXmlSafe) + + fun getPrintedValue(v: Gene): String = + cleanXmlValueString(v.getValueAsPrintableString(previousGenes, GeneUtils.EscapeMode.XML, targetFormat)) + + fun isPrimitiveGene(value: Any?): Boolean = when (unwrap(value)) { + is StringGene, is BooleanGene, is IntegerGene, is DoubleGene, is FloatGene, + is String, is Number, is Boolean -> true + else -> false + } + + fun serializeXml(name: String, value: Any?): String { + + if (name == "#text") { + return when (val v = unwrap(value)) { + is Gene -> getPrintedValue(v) + null -> "" + else -> escapeXmlSafe(v.toString()) + } + } + + val v = unwrap(value) ?: return "<$name>" + + return when (v) { + is ObjectWithAttributesGene -> { + v.getValueAsPrintableString(previousGenes, GeneUtils.EscapeMode.XML, targetFormat) + } + + is ObjectGene -> { + val inner = v.fields.joinToString("") { f -> + serializeXml(f.name, unwrap(f)) + } + "<$name>$inner" + } + + is Collection<*> -> v.joinToString("", "<$name>", "") { + val itemName = singularize(name) + serializeXml(itemName, it) + } + + is Map<*, *> -> v.entries.joinToString("", "<$name>", "") { + serializeXml(it.key.toString(), it.value) + } + + is ArrayGene<*> -> { + val itemName = singularize(name) + v.getViewOfElements().joinToString("", "<$name>", "") { + serializeXml(itemName, it) + } + } + + is Gene -> "<$name>${getPrintedValue(v)}" + + else -> "<$name>${cleanXmlValueString(v.toString())}" + } + } + + val inner = includedFields.joinToString("") { f -> + serializeXml(f.name, unwrap(f)) + } + + val singleField = includedFields.singleOrNull() + val inlinePrimitive = singleField?.let { isPrimitiveGene(unwrap(it)) } == true - buffer.append(openXml(name)) - includedFields.forEach { - //FIXME put back, but then update all broken tests - //buffer.append(openXml(it.name)) - buffer.append(it.getValueAsPrintableString(previousGenes, mode, targetFormat)) - //buffer.append(closeXml(it.name)) + val xmlPayload = if (inlinePrimitive) { + val childValue = getPrintedValue(unwrap(singleField) as Gene) + "<$name>$childValue" + } else { + "<$name>$inner" } - buffer.append(closeXml(name)) + buffer.append(xmlPayload) } else if (mode == GeneUtils.EscapeMode.X_WWW_FORM_URLENCODED) { buffer.append(includedFields.map { diff --git a/core/src/main/kotlin/org/evomaster/core/search/gene/ObjectWithAttributesGene.kt b/core/src/main/kotlin/org/evomaster/core/search/gene/ObjectWithAttributesGene.kt new file mode 100644 index 0000000000..2ff79e1df5 --- /dev/null +++ b/core/src/main/kotlin/org/evomaster/core/search/gene/ObjectWithAttributesGene.kt @@ -0,0 +1,125 @@ +package org.evomaster.core.search.gene + +import org.evomaster.core.output.OutputFormat +import org.evomaster.core.search.gene.collection.PairGene +import org.evomaster.core.search.gene.placeholder.CycleObjectGene +import org.evomaster.core.search.gene.string.StringGene +import org.evomaster.core.search.gene.utils.GeneUtils +import org.evomaster.core.search.gene.wrapper.OptionalGene + +class ObjectWithAttributesGene( + name: String, + fixedFields: List, + refType: String? = null, + isFixed: Boolean, + template: PairGene? = null, + additionalFields: MutableList>? = null, + val attributeNames: Set = emptySet() +) : ObjectGene(name, fixedFields, refType, isFixed, template, additionalFields) { + + constructor(name: String, fields: List, refType: String? = null) : this( + name, fixedFields = fields, refType = refType, isFixed = true, template = null, additionalFields = null, attributeNames = emptySet() + ) + + override fun copyContent(): Gene { + val copiedAdditional = additionalFields + ?.map { it.copy() } + ?.filterIsInstance>() + ?.toMutableList() + + return ObjectWithAttributesGene( + name, + fixedFields.map { it.copy() }, + refType, + isFixed, + template, + copiedAdditional, + attributeNames.toMutableSet() + ) + } + + override fun getValueAsPrintableString( + previousGenes: List, + mode: GeneUtils.EscapeMode?, + targetFormat: OutputFormat?, + extraCheck: Boolean + ): String { + + if (mode != GeneUtils.EscapeMode.XML) { + return super.getValueAsPrintableString(previousGenes, mode, targetFormat, extraCheck) + } + + val includedFields = fixedFields + .filter { it !is CycleObjectGene } + .filter { it !is OptionalGene || (it.isActive && it.gene !is CycleObjectGene) } + .filter { it.isPrintable() } + + val attributeFields = includedFields.filter { attributeNames.contains(it.name) } + val childFields = includedFields.filter { !attributeNames.contains(it.name) } + + // 1) "#text" CANNOT be an attribute + if (attributeFields.any { it.name == "#text" }) { + throw IllegalStateException("#text cannot be used as an attribute in XML") + } + + // 2) Child names must be unique (XML does not allow repeated element names at this level) + val duplicated = childFields + .groupBy { it.name } + .filter { it.value.size > 1 } + .keys + + if (duplicated.isNotEmpty()) { + throw IllegalStateException("Duplicate child elements not allowed in XML: $duplicated") + } + + fun xmlEscape(s: String): String = + s.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'") + + fun printAttribute(field: Gene): String { + val raw = field.getValueAsPrintableString(previousGenes, GeneUtils.EscapeMode.XML, targetFormat) + val clean = raw.removeSurrounding("\"") + return "${field.name}=\"$clean\"" + } + + val attributesString = attributeFields.joinToString(" ") { printAttribute(it) } + val sb = StringBuilder() + + if (attributesString.isEmpty()) { //No childs + sb.append("<$name>") + } else { + sb.append("<$name $attributesString>") + } + + for (child in childFields) { //Childs + + val childXml = child.getValueAsPrintableString( + previousGenes, + GeneUtils.EscapeMode.XML, + targetFormat + ) + + val isInlineValue = child.name == "#text" && !(child is ObjectWithAttributesGene) + + if (isInlineValue) { + sb.append(childXml) + continue + } + + if (child is ObjectWithAttributesGene || child is ObjectGene) { + sb.append(childXml) + continue + } + + sb.append("<${child.name}>") + sb.append(childXml) + sb.append("") + } + + sb.append("") + return sb.toString() + } +} \ No newline at end of file diff --git a/core/src/test/kotlin/org/evomaster/core/search/gene/GeneSamplerForTests.kt b/core/src/test/kotlin/org/evomaster/core/search/gene/GeneSamplerForTests.kt index 3b5cefe766..1970dfe03d 100644 --- a/core/src/test/kotlin/org/evomaster/core/search/gene/GeneSamplerForTests.kt +++ b/core/src/test/kotlin/org/evomaster/core/search/gene/GeneSamplerForTests.kt @@ -133,6 +133,7 @@ object GeneSamplerForTests { PatternCharacterBlockGene::class -> samplePatternCharacterBlock(rand) as T QuantifierRxGene::class -> sampleQuantifierRxGene(rand) as T RegexGene::class -> sampleRegexGene(rand) as T + ObjectWithAttributesGene::class -> sampleObjectGeneWithAttributes(rand) as T //SQL genes SqlJSONPathGene::class -> sampleSqlJSONPathGene(rand) as T @@ -901,4 +902,35 @@ object GeneSamplerForTests { } } + fun sampleObjectGeneWithAttributes(rand: Randomness): ObjectWithAttributesGene { + + val selection = geneClasses.filter { !it.isAbstract } + val isFixed = rand.nextBoolean() + + return if (isFixed) { + ObjectWithAttributesGene( + name = "rand ObjectGeneWithAttributes ${rand.nextInt()}", + fields = listOf( + sample(rand.choose(selection), rand), + sample(rand.choose(selection), rand), + sample(rand.choose(selection), rand) + ) + ) + }else{ + ObjectWithAttributesGene( + name = "rand ObjectGeneWithAttributes ${rand.nextInt()}", + fixedFields = listOf( + sample(rand.choose(selection), rand), + sample(rand.choose(selection), rand), + sample(rand.choose(selection), rand) + ), + refType = null, + isFixed = isFixed, + template = PairGene("template", sampleStringGene(rand), samplePrintableTemplate(selection, rand)), + additionalFields = mutableListOf() + ) + } + } + + } diff --git a/core/src/test/kotlin/org/evomaster/core/search/gene/ObjectGeneTest.kt b/core/src/test/kotlin/org/evomaster/core/search/gene/ObjectGeneTest.kt index 14150feeac..a914cf171d 100644 --- a/core/src/test/kotlin/org/evomaster/core/search/gene/ObjectGeneTest.kt +++ b/core/src/test/kotlin/org/evomaster/core/search/gene/ObjectGeneTest.kt @@ -101,4 +101,33 @@ internal class ObjectGeneTest { assertEquals("{foo,bar,nested{hello}}", actual) } + + @Test + fun testValueAsContent() { + + val root = ObjectGene( + name = "device", + listOf( + StringGene("#text", "XPhone"), + ObjectGene( + name = "location", + listOf( + StringGene("country", "AR"), + ObjectGene( + name = "gps", + listOf( + IntegerGene("#text", 12), + IntegerGene("lon", 34) + ) + ) + ) + ) + ) + ) + + val actual = root.getValueAsPrintableString(mode = GeneUtils.EscapeMode.XML) + val expected = + "XPhoneAR1234" + assertEquals(expected, actual) + } } \ No newline at end of file diff --git a/core/src/test/kotlin/org/evomaster/core/search/gene/uri/ObjectWithAttributesGeneTest.kt b/core/src/test/kotlin/org/evomaster/core/search/gene/uri/ObjectWithAttributesGeneTest.kt new file mode 100644 index 0000000000..41241e25e3 --- /dev/null +++ b/core/src/test/kotlin/org/evomaster/core/search/gene/uri/ObjectWithAttributesGeneTest.kt @@ -0,0 +1,300 @@ +package org.evomaster.core.search.gene.xml + +import org.evomaster.core.search.gene.BooleanGene +import org.evomaster.core.search.gene.ObjectGene +import org.evomaster.core.search.gene.ObjectWithAttributesGene +import org.evomaster.core.search.gene.numeric.IntegerGene +import org.evomaster.core.search.gene.string.StringGene +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.evomaster.core.search.gene.utils.GeneUtils + +class ObjectWithAttributesGeneTest { + + + @Test + fun testXmlPrintWithAttributesAndValue() { + + val person = ObjectWithAttributesGene( + name = "parent", + fixedFields = listOf( + StringGene("attrib1", value = "true"), + ObjectWithAttributesGene( + name = "child1", + fixedFields = listOf( + StringGene("attrib2", value = "-1"), + StringGene("attrib3", value = "bar"), + IntegerGene("#text", value = 42) + ), + isFixed = true, + attributeNames = setOf("attrib2","attrib3") + ), + StringGene("child2", value = "foo"), + ), + isFixed = true, + attributeNames = setOf("attrib1") + ) + val actual = person.getValueAsPrintableString(mode = GeneUtils.EscapeMode.XML) + val expected = "42foo" + assertEquals(expected, actual) + } + + @Test + fun testXmlEmptyObject() { + + val obj = ObjectWithAttributesGene( + name = "empty", + fixedFields = emptyList(), + isFixed = true, + attributeNames = emptySet() + ) + + val actual = obj.getValueAsPrintableString(mode = GeneUtils.EscapeMode.XML) + val expected = "" + + assertEquals(expected, actual) + } + + @Test + fun testXmlEmptyAttributeValue() { + + val obj = ObjectWithAttributesGene( + name = "person", + fixedFields = listOf(StringGene("id", value = "")), + isFixed = true, + attributeNames = setOf("id") + ) + + val actual = obj.getValueAsPrintableString(mode = GeneUtils.EscapeMode.XML) + val expected = "" + + assertEquals(expected, actual) + } + + @Test + fun testXmlNullAttributeValue() { + + val obj = ObjectWithAttributesGene( + name = "item", + fixedFields = listOf(IntegerGene("code", value = null)), + isFixed = true, + attributeNames = setOf("code") + ) + + val actual = obj.getValueAsPrintableString(mode = GeneUtils.EscapeMode.XML) + val expected = "" + + assertEquals(expected, actual) + } + + @Test + fun testXmlEscaping() { + + val obj = ObjectWithAttributesGene( + name = "x", + fixedFields = listOf( + StringGene("attr", "\"<>&'"), + StringGene("#text", "\"<>&'") + ), + isFixed = true, + attributeNames = setOf("attr") + ) + + val actual = obj.getValueAsPrintableString(mode = GeneUtils.EscapeMode.XML) + val expected = ""<>&'" + + assertEquals(expected, actual) + } + + @Test + fun testValueAsTextOnly() { + + val obj = ObjectWithAttributesGene( + name = "item", + fixedFields = listOf( + IntegerGene("#text", value = 42) + ), + isFixed = true, + attributeNames = emptySet() + ) + + val actual = obj.getValueAsPrintableString(mode = GeneUtils.EscapeMode.XML) + val expected = "42" + + assertEquals(expected, actual) + } + + @Test + fun testValueBooleanAsText() { + + val obj = ObjectWithAttributesGene( + name = "flag", + fixedFields = listOf( + BooleanGene("#text", false) + ), + isFixed = true + ) + + val actual = obj.getValueAsPrintableString(mode = GeneUtils.EscapeMode.XML) + val expected = "false" + + assertEquals(expected, actual) + } + + @Test + fun testValueEmptyString() { + + val obj = ObjectWithAttributesGene( + name = "node", + fixedFields = listOf( + StringGene("#text", "") + ), + isFixed = true + ) + + val actual = obj.getValueAsPrintableString(mode = GeneUtils.EscapeMode.XML) + val expected = "" + + assertEquals(expected, actual) + } + + @Test + fun testDeepMixedNesting() { + + val root = ObjectWithAttributesGene( + name = "root", + fixedFields = listOf( + StringGene("id", "root1"), + ObjectGene( + name = "device", + listOf( + StringGene("model", "XPhone"), + ObjectWithAttributesGene( + name = "location", + fixedFields = listOf( + StringGene("country", "AR"), + ObjectGene( + name = "gps", + listOf( + IntegerGene("lat", 12), + IntegerGene("lon", 34) + ) + ) + ), + isFixed = true, + attributeNames = setOf("country") + ) + ) + ) + ), + isFixed = true, + attributeNames = setOf("id") + ) + val actual = root.getValueAsPrintableString(mode = GeneUtils.EscapeMode.XML) + val expected = + "XPhone1234" + assertEquals(expected, actual) + } + + @Test + fun testDeepMixedNestingStartingOG() { + + val root = ObjectGene( + name = "device", + listOf( + StringGene("model", "XPhone"), + ObjectWithAttributesGene( + name = "location", + fixedFields = listOf( + StringGene("country", "AR"), + ObjectGene( + name = "gps", + listOf( + IntegerGene("lat", 12), + IntegerGene("lon", 34) + ) + ) + ), + isFixed = true, + attributeNames = setOf("country") + ) + ) + ) + + val actual = root.getValueAsPrintableString(mode = GeneUtils.EscapeMode.XML) + val expected = + "XPhone1234" + assertEquals(expected, actual) + } + + //tests from ObjectGene + @Test + fun testBooleanSelectionBase(){ + + val foo = StringGene("foo") + val bar = IntegerGene("bar") + val gene = ObjectWithAttributesGene("parent", listOf(foo, bar)) + + val selection = GeneUtils.getBooleanSelection(gene) + + val actual = selection.getValueAsPrintableString(mode = GeneUtils.EscapeMode.BOOLEAN_SELECTION_MODE) + + assertEquals("{foo,bar}", actual) + } + + @Test + fun testBooleanSelectionNested(){ + + val foo = StringGene("foo") + val bar = IntegerGene("bar") + val hello = StringGene("hello") + val nested = ObjectWithAttributesGene("nested", listOf(hello)) + val gene = ObjectWithAttributesGene("parent", listOf(foo, bar, nested)) + + val selection = GeneUtils.getBooleanSelection(gene) + + val actual = selection.getValueAsPrintableString(mode = GeneUtils.EscapeMode.BOOLEAN_SELECTION_MODE) + + assertEquals("{foo,bar,nested{hello}}", actual) + } + + @Test + fun testTextCannotBeAttribute() { + + val ex = org.junit.jupiter.api.assertThrows { + + ObjectWithAttributesGene( + name = "node", + fixedFields = listOf( + StringGene("#text", "value") + ), + isFixed = true, + attributeNames = setOf("#text") // ilegal + ).getValueAsPrintableString(mode = GeneUtils.EscapeMode.XML) + } + + assertEquals("#text cannot be used as an attribute in XML", ex.message) + } + + @Test + fun testDuplicateChildNameThrowsException() { + + val ex = org.junit.jupiter.api.assertThrows { + + ObjectWithAttributesGene( + name = "node", + fixedFields = listOf( + StringGene("child", "a"), + IntegerGene("child", 123) // duplicado + ), + isFixed = true, + attributeNames = emptySet() + ).getValueAsPrintableString(mode = GeneUtils.EscapeMode.XML) + } + + assertEquals( + "Duplicate child elements not allowed in XML: [child]", + ex.message + ) + } +} \ No newline at end of file From c7398858f94669134c4bbad2eff247b97dfa8c6e Mon Sep 17 00:00:00 2001 From: Romihime Date: Wed, 19 Nov 2025 19:48:08 -0300 Subject: [PATCH 2/7] Update HttpWsTestCaseWriter.kt --- .../core/output/service/HttpWsTestCaseWriter.kt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/core/src/main/kotlin/org/evomaster/core/output/service/HttpWsTestCaseWriter.kt b/core/src/main/kotlin/org/evomaster/core/output/service/HttpWsTestCaseWriter.kt index 9cf6db31a7..73e178d487 100644 --- a/core/src/main/kotlin/org/evomaster/core/output/service/HttpWsTestCaseWriter.kt +++ b/core/src/main/kotlin/org/evomaster/core/output/service/HttpWsTestCaseWriter.kt @@ -529,8 +529,21 @@ abstract class HttpWsTestCaseWriter : ApiTestCaseWriter() { } else -> lines.add(".$send(\"$body\")") } + } else if (bodyParam.isXml()) { + + val xml = bodyParam.getValueAsPrintableString(mode = GeneUtils.EscapeMode.XML, targetFormat = format) + + when { + + format.isCsharp() -> { + lines.append("new StringContent($xml, Encoding.UTF8, \"${bodyParam.contentType()}\")") + } + format.isPython() -> { + lines.add("body = $xml") + } + else -> lines.add(".$send($xml)") + } } else { - //TODO XML LoggingUtil.uniqueWarn(log, "Unhandled type for body payload: " + bodyParam.contentType()) } } From 9303840a7906340fd70ae53735ede3def82108ef Mon Sep 17 00:00:00 2001 From: Romihime Date: Wed, 19 Nov 2025 20:03:21 -0300 Subject: [PATCH 3/7] Fix restActionBuilderV3 --- .../evomaster/core/problem/rest/builder/RestActionBuilderV3.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt index 77689f4324..36d2433218 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt @@ -980,6 +980,8 @@ object RestActionBuilderV3 { additionalFields = mutableListOf(), attributeNames = attributeNames ) + }else{ + return createObjectGene(name, schema, schemaHolder,currentSchema, history, referenceClassDef, options, examples, messages) } } //TODO file is a hack. I want to find a more elegant way of dealing with it (BMR) From 43365237611af8cfac5809237dfce841f85d07e2 Mon Sep 17 00:00:00 2001 From: Romihime Date: Wed, 19 Nov 2025 22:16:21 -0300 Subject: [PATCH 4/7] Fix cuantity of genes --- .../org/evomaster/core/search/gene/GeneNumberOfGenesTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/kotlin/org/evomaster/core/search/gene/GeneNumberOfGenesTest.kt b/core/src/test/kotlin/org/evomaster/core/search/gene/GeneNumberOfGenesTest.kt index 298f5ed60b..c2caf6ce40 100644 --- a/core/src/test/kotlin/org/evomaster/core/search/gene/GeneNumberOfGenesTest.kt +++ b/core/src/test/kotlin/org/evomaster/core/search/gene/GeneNumberOfGenesTest.kt @@ -13,7 +13,7 @@ class GeneNumberOfGenesTest : AbstractGeneTest() { This number should not change, unless you explicitly add/remove any gene. if so, update this number accordingly */ - assertEquals(87, geneClasses.size) + assertEquals(88, geneClasses.size) } } From 801575ad71cabdf46fbc13cf97b2564801f9ea3b Mon Sep 17 00:00:00 2001 From: Romihime Date: Mon, 24 Nov 2025 22:01:42 -0300 Subject: [PATCH 5/7] Changes from pr --- .../rest/builder/RestActionBuilderV3.kt | 5 +- .../evomaster/core/search/gene/ObjectGene.kt | 173 ++++++++++-------- .../search/gene/ObjectWithAttributesGene.kt | 33 ++-- .../core/search/gene/ObjectGeneTest.kt | 9 +- .../gene/uri/ObjectWithAttributesGeneTest.kt | 28 ++- 5 files changed, 149 insertions(+), 99 deletions(-) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt index 36d2433218..eb1de1c90a 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt @@ -709,7 +709,6 @@ object RestActionBuilderV3 { body } - var name = "body" val description = operation.description ?: null val bodies = resolvedBody.content?.filter { @@ -751,9 +750,9 @@ object RestActionBuilderV3 { val deref = obj.schema.`$ref`?.let { ref -> val name = ref.substringAfterLast("/") SchemaUtils.getReferenceSchema(schemaHolder, currentSchema, ref, messages) } ?: obj.schema - name = deref?.xml?.name ?: deref?.`$ref`?.substringAfterLast("/") ?: "body" + val name = deref?.xml?.name ?: deref?.`$ref`?.substringAfterLast("/") ?: "body" - var gene = getGene("body", obj.schema, schemaHolder,currentSchema, referenceClassDef = null, options = options, messages = messages, examples = examples) + var gene = getGene(name, obj.schema, schemaHolder,currentSchema, referenceClassDef = null, options = options, messages = messages, examples = examples) if (resolvedBody.required != true && gene !is OptionalGene) { diff --git a/core/src/main/kotlin/org/evomaster/core/search/gene/ObjectGene.kt b/core/src/main/kotlin/org/evomaster/core/search/gene/ObjectGene.kt index dadd7e8fc8..f7c83de77c 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/gene/ObjectGene.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/gene/ObjectGene.kt @@ -82,6 +82,7 @@ open class ObjectGene( private const val PROB_MODIFY_SIZE_ADDITIONAL_FIELDS = 0.1 // the default maximum size for additional fields private const val MAX_SIZE_ADDITIONAL_FIELDS = 5 + const val contentXMLTag = "#text" private val mapper = ObjectMapper() } @@ -341,6 +342,99 @@ open class ObjectGene( return mode == null || mode == GeneUtils.EscapeMode.JSON || mode == GeneUtils.EscapeMode.TEXT } + private fun escapeXmlSafe(s: String): String = + s.replace(Regex("(?", ">") + .replace("\"", """) + .replace("'", "'") + + private fun singularize(n: String): String = + when { + n.endsWith("s") && n.length > 1 -> n.removeSuffix("s") + else -> n + }.replaceFirstChar { it.uppercase() } + + private fun unwrap(v: Any?): Any? = + when (v) { + is OptionalGene -> v.gene + else -> v + } + + public fun cleanXmlValueString(v: String): String = + v.removeSurrounding("\"").let(::escapeXmlSafe) + + private fun getPrintedValue( + previousGenes: List, + v: Gene, + targetFormat: OutputFormat? + ): String = + cleanXmlValueString( + v.getValueAsPrintableString( + previousGenes, + GeneUtils.EscapeMode.XML, + targetFormat + ) + ) + + private fun isPrimitiveGene(value: Any?): Boolean = + when (unwrap(value)) { + is StringGene, is BooleanGene, is IntegerGene, is DoubleGene, is FloatGene, + is String, is Number, is Boolean -> true + else -> false + } + + private fun serializeXml( + previousGenes: List, + name: String, + value: Any?, + targetFormat: OutputFormat? + ): String { + + if (name == contentXMLTag) { + return when (val v = unwrap(value)) { + is Gene -> getPrintedValue(previousGenes, v, targetFormat) + null -> "" + else -> escapeXmlSafe(v.toString()) + } + } + + val v = unwrap(value) ?: return "<$name>" + + return when (v) { + + is ObjectWithAttributesGene -> { + v.getValueAsPrintableString(previousGenes, GeneUtils.EscapeMode.XML, targetFormat) + } + + is ObjectGene -> { + val inner = v.fields.joinToString("") { f -> + serializeXml(previousGenes, f.name, unwrap(f), targetFormat) + } + "<$name>$inner" + } + + is Collection<*> -> v.joinToString("", "<$name>", "") { + val itemName = singularize(name) + serializeXml(previousGenes, itemName, it, targetFormat) + } + + is Map<*, *> -> v.entries.joinToString("", "<$name>", "") { + serializeXml(previousGenes, it.key.toString(), it.value, targetFormat) + } + + is ArrayGene<*> -> { + val itemName = singularize(name) + v.getViewOfElements().joinToString("", "<$name>", "") { + serializeXml(previousGenes, itemName, it, targetFormat) + } + } + + is Gene -> "<$name>${getPrintedValue(previousGenes, v, targetFormat)}" + + else -> "<$name>${cleanXmlValueString(v.toString())}" + } + } override fun getValueAsPrintableString(previousGenes: List, mode: GeneUtils.EscapeMode?, targetFormat: OutputFormat?, extraCheck: Boolean): String { @@ -376,90 +470,15 @@ open class ObjectGene( } else if (mode == GeneUtils.EscapeMode.XML) { - fun escapeXmlSafe(s: String): String = - s.replace(Regex("(?", ">") - .replace("\"", """) - .replace("'", "'") - - fun singularize(n: String) = when { - n.endsWith("s") && n.length > 1 -> n.removeSuffix("s") - else -> n - }.replaceFirstChar { it.uppercase() } - - fun unwrap(v: Any?): Any? = when (v) { - is OptionalGene -> v.gene - else -> v - } - - fun cleanXmlValueString(v: String): String = - v.removeSurrounding("\"").let(::escapeXmlSafe) - - fun getPrintedValue(v: Gene): String = - cleanXmlValueString(v.getValueAsPrintableString(previousGenes, GeneUtils.EscapeMode.XML, targetFormat)) - - fun isPrimitiveGene(value: Any?): Boolean = when (unwrap(value)) { - is StringGene, is BooleanGene, is IntegerGene, is DoubleGene, is FloatGene, - is String, is Number, is Boolean -> true - else -> false - } - - fun serializeXml(name: String, value: Any?): String { - - if (name == "#text") { - return when (val v = unwrap(value)) { - is Gene -> getPrintedValue(v) - null -> "" - else -> escapeXmlSafe(v.toString()) - } - } - - val v = unwrap(value) ?: return "<$name>" - - return when (v) { - is ObjectWithAttributesGene -> { - v.getValueAsPrintableString(previousGenes, GeneUtils.EscapeMode.XML, targetFormat) - } - - is ObjectGene -> { - val inner = v.fields.joinToString("") { f -> - serializeXml(f.name, unwrap(f)) - } - "<$name>$inner" - } - - is Collection<*> -> v.joinToString("", "<$name>", "") { - val itemName = singularize(name) - serializeXml(itemName, it) - } - - is Map<*, *> -> v.entries.joinToString("", "<$name>", "") { - serializeXml(it.key.toString(), it.value) - } - - is ArrayGene<*> -> { - val itemName = singularize(name) - v.getViewOfElements().joinToString("", "<$name>", "") { - serializeXml(itemName, it) - } - } - - is Gene -> "<$name>${getPrintedValue(v)}" - - else -> "<$name>${cleanXmlValueString(v.toString())}" - } - } - val inner = includedFields.joinToString("") { f -> - serializeXml(f.name, unwrap(f)) + serializeXml(previousGenes, f.name, unwrap(f), targetFormat) } val singleField = includedFields.singleOrNull() val inlinePrimitive = singleField?.let { isPrimitiveGene(unwrap(it)) } == true val xmlPayload = if (inlinePrimitive) { - val childValue = getPrintedValue(unwrap(singleField) as Gene) + val childValue = getPrintedValue(previousGenes, unwrap(singleField) as Gene, targetFormat) "<$name>$childValue" } else { "<$name>$inner" diff --git a/core/src/main/kotlin/org/evomaster/core/search/gene/ObjectWithAttributesGene.kt b/core/src/main/kotlin/org/evomaster/core/search/gene/ObjectWithAttributesGene.kt index 2ff79e1df5..1cb76618e6 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/gene/ObjectWithAttributesGene.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/gene/ObjectWithAttributesGene.kt @@ -38,6 +38,22 @@ class ObjectWithAttributesGene( ) } + + private fun printAttribute( + previousGenes: List, + targetFormat: OutputFormat?, + field: Gene + ): String { + val raw = field.getValueAsPrintableString( + previousGenes, + GeneUtils.EscapeMode.XML, + targetFormat + ) + + val clean = cleanXmlValueString(raw) + return "${field.name}=\"$clean\"" + } + override fun getValueAsPrintableString( previousGenes: List, mode: GeneUtils.EscapeMode?, @@ -72,20 +88,7 @@ class ObjectWithAttributesGene( throw IllegalStateException("Duplicate child elements not allowed in XML: $duplicated") } - fun xmlEscape(s: String): String = - s.replace("&", "&") - .replace("<", "<") - .replace(">", ">") - .replace("\"", """) - .replace("'", "'") - - fun printAttribute(field: Gene): String { - val raw = field.getValueAsPrintableString(previousGenes, GeneUtils.EscapeMode.XML, targetFormat) - val clean = raw.removeSurrounding("\"") - return "${field.name}=\"$clean\"" - } - - val attributesString = attributeFields.joinToString(" ") { printAttribute(it) } + val attributesString = attributeFields.joinToString(" ") { printAttribute(previousGenes, targetFormat, it) } val sb = StringBuilder() if (attributesString.isEmpty()) { //No childs @@ -102,7 +105,7 @@ class ObjectWithAttributesGene( targetFormat ) - val isInlineValue = child.name == "#text" && !(child is ObjectWithAttributesGene) + val isInlineValue = child.name == contentXMLTag && !(child is ObjectWithAttributesGene) if (isInlineValue) { sb.append(childXml) diff --git a/core/src/test/kotlin/org/evomaster/core/search/gene/ObjectGeneTest.kt b/core/src/test/kotlin/org/evomaster/core/search/gene/ObjectGeneTest.kt index a914cf171d..aa1448b7d1 100644 --- a/core/src/test/kotlin/org/evomaster/core/search/gene/ObjectGeneTest.kt +++ b/core/src/test/kotlin/org/evomaster/core/search/gene/ObjectGeneTest.kt @@ -127,7 +127,14 @@ internal class ObjectGeneTest { val actual = root.getValueAsPrintableString(mode = GeneUtils.EscapeMode.XML) val expected = - "XPhoneAR1234" + "XPhone" + + "" + + "AR" + + "12" + + "34" + + "" + + "" + + "" assertEquals(expected, actual) } } \ No newline at end of file diff --git a/core/src/test/kotlin/org/evomaster/core/search/gene/uri/ObjectWithAttributesGeneTest.kt b/core/src/test/kotlin/org/evomaster/core/search/gene/uri/ObjectWithAttributesGeneTest.kt index 41241e25e3..fdc4120fb3 100644 --- a/core/src/test/kotlin/org/evomaster/core/search/gene/uri/ObjectWithAttributesGeneTest.kt +++ b/core/src/test/kotlin/org/evomaster/core/search/gene/uri/ObjectWithAttributesGeneTest.kt @@ -35,7 +35,11 @@ class ObjectWithAttributesGeneTest { attributeNames = setOf("attrib1") ) val actual = person.getValueAsPrintableString(mode = GeneUtils.EscapeMode.XML) - val expected = "42foo" + val expected = + "" + + "42" + + "foo" + + "" assertEquals(expected, actual) } @@ -192,7 +196,17 @@ class ObjectWithAttributesGeneTest { ) val actual = root.getValueAsPrintableString(mode = GeneUtils.EscapeMode.XML) val expected = - "XPhone1234" + "" + + "" + + "XPhone" + + "" + + "" + + "12" + + "34" + + "" + + "" + + "" + + "" assertEquals(expected, actual) } @@ -223,7 +237,15 @@ class ObjectWithAttributesGeneTest { val actual = root.getValueAsPrintableString(mode = GeneUtils.EscapeMode.XML) val expected = - "XPhone1234" + "" + + "XPhone" + + "" + + "" + + "12" + + "34" + + "" + + "" + + "" assertEquals(expected, actual) } From 0325e67bb2f4f942b8d937692d16eeffe2037ccd Mon Sep 17 00:00:00 2001 From: Romihime Date: Tue, 25 Nov 2025 07:57:08 -0300 Subject: [PATCH 6/7] Fix --- .../evomaster/core/problem/rest/builder/RestActionBuilderV3.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt index eb1de1c90a..742d99fd5b 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt @@ -752,8 +752,7 @@ object RestActionBuilderV3 { val name = deref?.xml?.name ?: deref?.`$ref`?.substringAfterLast("/") ?: "body" - var gene = getGene(name, obj.schema, schemaHolder,currentSchema, referenceClassDef = null, options = options, messages = messages, examples = examples) - + var gene = getGene("body", obj.schema, schemaHolder,currentSchema, referenceClassDef = null, options = options, messages = messages, examples = examples) if (resolvedBody.required != true && gene !is OptionalGene) { gene = OptionalGene(name, gene) From fb8565e4f06095791d66b0c88d89a1b16712704f Mon Sep 17 00:00:00 2001 From: Romihime Date: Tue, 25 Nov 2025 19:17:37 -0300 Subject: [PATCH 7/7] Try --- .../evomaster/core/problem/rest/builder/RestActionBuilderV3.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt index 742d99fd5b..b3ded8ace9 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt @@ -752,7 +752,7 @@ object RestActionBuilderV3 { val name = deref?.xml?.name ?: deref?.`$ref`?.substringAfterLast("/") ?: "body" - var gene = getGene("body", obj.schema, schemaHolder,currentSchema, referenceClassDef = null, options = options, messages = messages, examples = examples) + var gene = getGene(name, obj.schema, schemaHolder,currentSchema, referenceClassDef = null, options = options, messages = messages, examples = examples) if (resolvedBody.required != true && gene !is OptionalGene) { gene = OptionalGene(name, gene)