Skip to content

Commit 9dca72b

Browse files
committed
Merge branch 'master' into vulnerability-analyser
2 parents fa37ddd + 6d2b55f commit 9dca72b

File tree

2 files changed

+112
-51
lines changed

2 files changed

+112
-51
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package org.evomaster.core.output.dto
2+
3+
import org.evomaster.core.output.OutputFormat
4+
import org.evomaster.core.output.TestWriterUtils
5+
import org.evomaster.core.problem.rest.param.BodyParam
6+
import org.evomaster.core.search.action.Action
7+
import org.evomaster.core.search.gene.BooleanGene
8+
import org.evomaster.core.search.gene.Gene
9+
import org.evomaster.core.search.gene.ObjectGene
10+
import org.evomaster.core.search.gene.datetime.DateGene
11+
import org.evomaster.core.search.gene.datetime.DateTimeGene
12+
import org.evomaster.core.search.gene.datetime.TimeGene
13+
import org.evomaster.core.search.gene.numeric.DoubleGene
14+
import org.evomaster.core.search.gene.numeric.FloatGene
15+
import org.evomaster.core.search.gene.numeric.IntegerGene
16+
import org.evomaster.core.search.gene.numeric.LongGene
17+
import org.evomaster.core.search.gene.regex.RegexGene
18+
import org.evomaster.core.search.gene.string.Base64StringGene
19+
import org.evomaster.core.search.gene.string.StringGene
20+
import org.evomaster.core.search.gene.utils.GeneUtils
21+
import org.evomaster.core.utils.StringUtils
22+
import org.slf4j.Logger
23+
import org.slf4j.LoggerFactory
24+
import java.nio.file.Path
25+
26+
/**
27+
* When creating tests for REST APIs, enabling the [EMConfig.dtoForRequestPayload] feature causes EVoMaster to use
28+
* DTOs when handling request payloads instead of stringifying the JSON payload.
29+
*
30+
* Using DTOs provides a major advantage towards code readability and sustainability. It is more flexible and scales
31+
* better.
32+
*/
33+
object DtoWriter {
34+
35+
private val log: Logger = LoggerFactory.getLogger(DtoWriter::class.java)
36+
37+
/**
38+
* [MutableMap] that will collect the different DTOs to be handled by the test suite. Keys in the map are the
39+
* DTO name String which translates directly into the .kt or .java class name in filesystem.
40+
*/
41+
private val dtoCollector: MutableMap<String, DtoClass> = mutableMapOf()
42+
43+
fun write(testSuitePath: Path, outputFormat: OutputFormat, actionDefinitions: List<Action>) {
44+
getDtos(actionDefinitions)
45+
dtoCollector.forEach {
46+
when {
47+
outputFormat.isJava() -> JavaDtoWriter.write(testSuitePath, outputFormat, it.value)
48+
else -> throw IllegalStateException("$outputFormat output format does not support DTOs as request payloads.")
49+
}
50+
}
51+
}
52+
53+
private fun getDtos(actionDefinitions: List<Action>) {
54+
actionDefinitions.forEach { action ->
55+
action.getViewOfChildren().find { it is BodyParam }
56+
.let {
57+
val primaryGene = GeneUtils.getWrappedValueGene((it as BodyParam).primaryGene())
58+
// TODO: Payloads could also be json arrays, analyze ArrayGene
59+
if (primaryGene is ObjectGene) {
60+
getDtoFromObject(primaryGene, action.getName())
61+
}
62+
}
63+
}
64+
}
65+
66+
private fun getDtoFromObject(gene: ObjectGene, actionName: String) {
67+
// TODO: Determine strategy for objects that are not defined as a component and do not have a name
68+
val dtoName = gene.refType?:TestWriterUtils.safeVariableName(actionName)
69+
val dtoClass = DtoClass(dtoName)
70+
// TODO: add suport for additionalFields
71+
gene.fixedFields.forEach { field ->
72+
try {
73+
val wrappedGene = GeneUtils.getWrappedValueGene(field)
74+
val dtoField = getDtoField(field.name, wrappedGene)
75+
dtoClass.addField(dtoField)
76+
if (wrappedGene is ObjectGene && !dtoCollector.contains(dtoField.type)) {
77+
getDtoFromObject(wrappedGene, dtoField.type)
78+
}
79+
} catch (ex: Exception) {
80+
log.warn("A failure has occurred when collecting DTOs. \n"
81+
+ "Exception: ${ex.localizedMessage} \n"
82+
+ "At ${ex.stackTrace.joinToString(separator = " \n -> ")}. "
83+
)
84+
assert(false)
85+
}
86+
}
87+
dtoCollector.put(dtoName, dtoClass)
88+
}
89+
90+
private fun getDtoField(fieldName: String, field: Gene?): DtoField {
91+
return DtoField(fieldName, when (field) {
92+
// TODO: handle nested arrays, objects and extend type system for dto fields
93+
is StringGene -> "String"
94+
is IntegerGene -> "Integer"
95+
is LongGene -> "Long"
96+
is DoubleGene -> "Double"
97+
is FloatGene -> "Float"
98+
is Base64StringGene -> "String"
99+
// Time, Date, DateTime and Regex genes will be handled with strings at the moment. In the future we'll evaluate if it's worth having any validation
100+
is DateGene -> "String"
101+
is TimeGene -> "String"
102+
is DateTimeGene -> "String"
103+
is RegexGene -> "String"
104+
is BooleanGene -> "Boolean"
105+
is ObjectGene -> StringUtils.capitalization(fieldName)
106+
else -> throw Exception("Not supported gene at the moment: ${field?.javaClass?.simpleName}")
107+
})
108+
}
109+
}

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

Lines changed: 3 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import org.evomaster.core.output.TestWriterUtils.getWireMockVariableName
1010
import org.evomaster.core.output.TestWriterUtils.handleDefaultStubForAsJavaOrKotlin
1111
import org.evomaster.core.output.dto.DtoClass
1212
import org.evomaster.core.output.dto.DtoField
13+
import org.evomaster.core.output.dto.DtoWriter
1314
import org.evomaster.core.output.dto.JavaDtoWriter
1415
import org.evomaster.core.output.naming.NumberedTestCaseNamingStrategy
1516
import org.evomaster.core.output.naming.TestCaseNamingStrategyFactory
@@ -221,9 +222,8 @@ class TestSuiteWriter {
221222
// TODO: take DTO extraction and writing to a different class
222223
fun writeDtos(solutionFilename: String) {
223224
val testSuitePath = getTestSuitePath(TestSuiteFileName(solutionFilename), config).parent
224-
getDtos().forEach {
225-
JavaDtoWriter.write(testSuitePath, config.outputFormat, it)
226-
}
225+
val restSampler = sampler as AbstractRestSampler
226+
DtoWriter.write(testSuitePath, config.outputFormat, restSampler.getActionDefinitions())
227227
}
228228

229229
private fun handleResetDatabaseInput(solution: Solution<*>): String {
@@ -1096,52 +1096,4 @@ class TestSuiteWriter {
10961096
.toList()
10971097
}
10981098

1099-
private fun getDtos(): List<DtoClass> {
1100-
val restSampler = sampler as AbstractRestSampler
1101-
val result = mutableListOf<DtoClass>()
1102-
restSampler.getActionDefinitions().forEach { action ->
1103-
action.getViewOfChildren().forEach { child ->
1104-
if (child is BodyParam) {
1105-
val primaryGene = GeneUtils.getWrappedValueGene(child.primaryGene())
1106-
// TODO: Payloads could also be json arrays, analyze ArrayGene
1107-
if (primaryGene is ObjectGene) {
1108-
// TODO: Determine strategy for objects that are not defined as a component and do not have a name
1109-
val dtoClass = DtoClass(primaryGene.refType?:TestWriterUtils.safeVariableName(action.getName()))
1110-
primaryGene.fixedFields.forEach { field ->
1111-
try {
1112-
dtoClass.addField(getDtoField(field))
1113-
} catch (ex: Exception) {
1114-
log.warn("A failure has occurred when collecting DTOs. \n"
1115-
+ "Exception: ${ex.localizedMessage} \n"
1116-
+ "At ${ex.stackTrace.joinToString(separator = " \n -> ")}. "
1117-
)
1118-
assert(false)
1119-
}
1120-
}
1121-
result.add(dtoClass)
1122-
}
1123-
}
1124-
}
1125-
}
1126-
return result
1127-
}
1128-
1129-
private fun getDtoField(field: Gene): DtoField {
1130-
val wrappedGene = GeneUtils.getWrappedValueGene(field)
1131-
return DtoField(field.name, when (wrappedGene) {
1132-
// TODO: handle nested arrays, objects and extend type system for dto fields
1133-
is StringGene -> "String"
1134-
is IntegerGene -> "Integer"
1135-
is LongGene -> "Long"
1136-
is DoubleGene -> "Double"
1137-
is FloatGene -> "Float"
1138-
is Base64StringGene -> "String"
1139-
// Time and Date genes will be handled with strings at the moment. In the future we'll evaluate if it's worth having any validation
1140-
is DateGene -> "String"
1141-
is TimeGene -> "String"
1142-
is BooleanGene -> "Boolean"
1143-
else -> throw Exception("Not supported gene at the moment: ${wrappedGene?.javaClass?.simpleName}")
1144-
})
1145-
}
1146-
11471099
}

0 commit comments

Comments
 (0)