Skip to content

Commit ce4abdb

Browse files
authored
Merge pull request #1340 from WebFuzzing/phg/kotlinDtoOutput
Add kotlin as an option for DTO generation in test cases
2 parents 69c0f08 + 7a41d4a commit ce4abdb

File tree

10 files changed

+164
-106
lines changed

10 files changed

+164
-106
lines changed

core/src/main/kotlin/org/evomaster/core/Main.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -865,7 +865,7 @@ class Main {
865865
val writer = injector.getInstance(TestSuiteWriter::class.java)
866866

867867
// TODO: support Kotlin for DTOs
868-
if (config.problemType == EMConfig.ProblemType.REST && config.dtoForRequestPayload && config.outputFormat.isJava()) {
868+
if (config.problemType == EMConfig.ProblemType.REST && config.dtoForRequestPayload && config.outputFormat.isJavaOrKotlin()) {
869869
writer.writeDtos(solution.getFileName().name)
870870
}
871871

core/src/main/kotlin/org/evomaster/core/output/dto/DtoWriter.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ import java.nio.file.Path
3333
* Using DTOs provides a major advantage towards code readability and sustainability. It is more flexible and scales
3434
* better.
3535
*/
36-
class DtoWriter {
36+
class DtoWriter(
37+
val outputFormat: OutputFormat
38+
) {
3739

3840
private val log: Logger = LoggerFactory.getLogger(DtoWriter::class.java)
3941

@@ -43,11 +45,12 @@ class DtoWriter {
4345
*/
4446
private val dtoCollector: MutableMap<String, DtoClass> = mutableMapOf()
4547

46-
fun write(testSuitePath: Path, testSuitePackage: String, outputFormat: OutputFormat, actionDefinitions: List<Action>) {
48+
fun write(testSuitePath: Path, testSuitePackage: String, actionDefinitions: List<Action>) {
4749
calculateDtos(actionDefinitions)
4850
dtoCollector.forEach {
4951
when {
5052
outputFormat.isJava() -> JavaDtoOutput().writeClass(testSuitePath, testSuitePackage, outputFormat, it.value)
53+
outputFormat.isKotlin() -> KotlinDtoOutput().writeClass(testSuitePath, testSuitePackage, outputFormat, it.value)
5154
else -> throw IllegalStateException("$outputFormat output format does not support DTOs as request payloads.")
5255
}
5356
}
@@ -162,7 +165,7 @@ class DtoWriter {
162165
return when (field) {
163166
// TODO: handle nested arrays, objects and extend type system for dto fields
164167
is StringGene -> "String"
165-
is IntegerGene -> "Integer"
168+
is IntegerGene -> if (outputFormat.isJava()) "Integer" else "Int"
166169
is LongGene -> "Long"
167170
is DoubleGene -> "Double"
168171
is FloatGene -> "Float"

core/src/main/kotlin/org/evomaster/core/output/dto/GeneToDto.kt

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,16 @@ import org.evomaster.core.utils.StringUtils
2424
* Provides a mapping between a Gene and its DTO representation at use. Takes in the [OutputFormat] to delegate
2525
* writing of statements to the corresponding [DtoOutput]
2626
*/
27-
class GeneToDto(outputFormat: OutputFormat) {
28-
29-
private var dtoOutput: DtoOutput
30-
31-
init {
32-
if (outputFormat.isJava()) {
33-
dtoOutput = JavaDtoOutput()
34-
} else {
35-
throw IllegalStateException("$outputFormat output format does not support DTOs as request payloads.")
36-
}
27+
class GeneToDto(
28+
val outputFormat: OutputFormat
29+
) {
30+
31+
private var dtoOutput: DtoOutput = if (outputFormat.isJava()) {
32+
JavaDtoOutput()
33+
} else if (outputFormat.isKotlin()){
34+
KotlinDtoOutput()
35+
} else {
36+
throw IllegalStateException("$outputFormat output format does not support DTOs as request payloads.")
3737
}
3838

3939
/**
@@ -85,16 +85,16 @@ class GeneToDto(outputFormat: OutputFormat) {
8585

8686
includedFields.forEach {
8787
val leafGene = it.getLeafGene()
88-
val attributeName = StringUtils.capitalization(it.name)
88+
val attributeName = it.name
8989
when (leafGene) {
9090
is ObjectGene -> {
91-
val childDtoCall = getDtoCall(leafGene, attributeName, counter)
91+
val childDtoCall = getDtoCall(leafGene, StringUtils.capitalization(attributeName), counter)
9292

9393
result.addAll(childDtoCall.objectCalls)
9494
result.add(dtoOutput.getSetterStatement(dtoVarName, attributeName, childDtoCall.varName))
9595
}
9696
is ArrayGene<*> -> {
97-
val childDtoCall = getDtoCall(leafGene, attributeName, counter)
97+
val childDtoCall = getDtoCall(leafGene, StringUtils.capitalization(attributeName), counter)
9898

9999
result.addAll(childDtoCall.objectCalls)
100100
result.add(dtoOutput.getSetterStatement(dtoVarName, attributeName, childDtoCall.varName))
@@ -136,7 +136,7 @@ class GeneToDto(outputFormat: OutputFormat) {
136136
private fun getListType(fieldName: String, gene: Gene): String {
137137
return when (gene) {
138138
is StringGene -> "String"
139-
is IntegerGene -> "Integer"
139+
is IntegerGene -> if (outputFormat.isJava()) "Integer" else "Int"
140140
is LongGene -> "Long"
141141
is DoubleGene -> "Double"
142142
is FloatGene -> "Float"

core/src/main/kotlin/org/evomaster/core/output/dto/JavaDtoOutput.kt

Lines changed: 6 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ import org.evomaster.core.output.Lines
44
import org.evomaster.core.output.OutputFormat
55
import org.evomaster.core.output.TestSuiteFileName
66
import org.evomaster.core.utils.StringUtils
7-
import java.nio.file.Files
87
import java.nio.file.Path
98

10-
class JavaDtoOutput: DtoOutput {
9+
class JavaDtoOutput: JvmDtoOutput() {
1110

1211
override fun writeClass(testSuitePath: Path, testSuitePackage: String, outputFormat: OutputFormat, dtoClass: DtoClass) {
1312
val dtoFilename = TestSuiteFileName(appendDtoPackage(dtoClass.name))
@@ -25,7 +24,7 @@ class JavaDtoOutput: DtoOutput {
2524
}
2625

2726
override fun getSetterStatement(dtoVarName: String, attributeName: String, value: String): String {
28-
return "$dtoVarName.set${attributeName}($value);"
27+
return "$dtoVarName.set${StringUtils.capitalization(attributeName)}($value);"
2928
}
3029

3130
override fun getNewListStatement(listType: String, listVarName: String): String {
@@ -36,26 +35,16 @@ class JavaDtoOutput: DtoOutput {
3635
return "$listVarName.add($value);"
3736
}
3837

39-
private fun setPackage(lines: Lines, suitePackage: String) {
40-
val pkgPrefix = if (suitePackage.isNotEmpty()) "$suitePackage." else ""
41-
lines.add("package ${pkgPrefix}dto;")
42-
lines.addEmpty()
43-
}
44-
45-
private fun addImports(lines: Lines) {
46-
lines.add("import java.util.Optional;")
47-
lines.addEmpty()
48-
lines.add("import com.fasterxml.jackson.annotation.JsonInclude;")
49-
lines.add("import shaded.com.fasterxml.jackson.annotation.JsonProperty;")
50-
lines.addEmpty()
51-
}
52-
5338
private fun initClass(lines: Lines, dtoFilename: String) {
5439
lines.add("@JsonInclude(JsonInclude.Include.NON_NULL)")
5540
lines.add("public class $dtoFilename {")
5641
lines.addEmpty()
5742
}
5843

44+
private fun closeClass(lines: Lines) {
45+
lines.add("}")
46+
}
47+
5948
private fun addClassContent(lines: Lines, dtoClass: DtoClass) {
6049
addVariables(lines, dtoClass)
6150
addGettersAndSetters(lines, dtoClass)
@@ -92,24 +81,4 @@ class JavaDtoOutput: DtoOutput {
9281
lines.addEmpty()
9382
}
9483
}
95-
96-
private fun closeClass(lines: Lines) {
97-
lines.add("}")
98-
}
99-
100-
private fun appendDtoPackage(name: String): String {
101-
return "dto.$name"
102-
}
103-
104-
private fun getTestSuitePath(testSuitePath: Path, dtoFilename: TestSuiteFileName, outputFormat: OutputFormat) : Path{
105-
return testSuitePath.resolve(dtoFilename.getAsPath(outputFormat))
106-
}
107-
108-
private fun saveToDisk(testFileContent: String, path: Path) {
109-
Files.createDirectories(path.parent)
110-
Files.deleteIfExists(path)
111-
Files.createFile(path)
112-
113-
path.toFile().appendText(testFileContent)
114-
}
11584
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.evomaster.core.output.dto
2+
3+
import org.evomaster.core.output.Lines
4+
import org.evomaster.core.output.OutputFormat
5+
import org.evomaster.core.output.TestSuiteFileName
6+
import java.nio.file.Files
7+
import java.nio.file.Path
8+
9+
abstract class JvmDtoOutput: DtoOutput {
10+
11+
protected fun setPackage(lines: Lines, suitePackage: String) {
12+
val pkgPrefix = if (suitePackage.isNotEmpty()) "$suitePackage." else ""
13+
lines.addStatement("package ${pkgPrefix}dto")
14+
lines.addEmpty()
15+
}
16+
17+
protected fun addImports(lines: Lines) {
18+
lines.addStatement("import java.util.Optional")
19+
lines.addEmpty()
20+
lines.addStatement("import com.fasterxml.jackson.annotation.JsonInclude")
21+
lines.addStatement("import shaded.com.fasterxml.jackson.annotation.JsonProperty")
22+
lines.addEmpty()
23+
}
24+
25+
protected fun appendDtoPackage(name: String): String {
26+
return "dto.$name"
27+
}
28+
29+
protected fun getTestSuitePath(testSuitePath: Path, dtoFilename: TestSuiteFileName, outputFormat: OutputFormat) : Path{
30+
return testSuitePath.resolve(dtoFilename.getAsPath(outputFormat))
31+
}
32+
33+
protected fun saveToDisk(testFileContent: String, path: Path) {
34+
Files.createDirectories(path.parent)
35+
Files.deleteIfExists(path)
36+
Files.createFile(path)
37+
38+
path.toFile().appendText(testFileContent)
39+
}
40+
41+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package org.evomaster.core.output.dto
2+
3+
import org.evomaster.core.output.Lines
4+
import org.evomaster.core.output.OutputFormat
5+
import org.evomaster.core.output.TestSuiteFileName
6+
import java.nio.file.Path
7+
8+
class KotlinDtoOutput: JvmDtoOutput() {
9+
10+
override fun writeClass(testSuitePath: Path, testSuitePackage: String, outputFormat: OutputFormat, dtoClass: DtoClass) {
11+
val dtoFilename = TestSuiteFileName(appendDtoPackage(dtoClass.name))
12+
val lines = Lines(outputFormat)
13+
setPackage(lines, testSuitePackage)
14+
addImports(lines)
15+
declareClass(lines, dtoFilename.getClassName(), dtoClass)
16+
saveToDisk(lines.toString(), getTestSuitePath(testSuitePath, dtoFilename, outputFormat))
17+
}
18+
19+
override fun getNewObjectStatement(dtoName: String, dtoVarName: String): String {
20+
return "val $dtoVarName = $dtoName()"
21+
}
22+
23+
override fun getSetterStatement(dtoVarName: String, attributeName: String, value: String): String {
24+
return "$dtoVarName.${attributeName} = $value"
25+
}
26+
27+
override fun getNewListStatement(listType: String, listVarName: String): String {
28+
return "val $listVarName = mutableListOf<$listType>()"
29+
}
30+
31+
override fun getAddElementToListStatement(listVarName: String, value: String): String {
32+
return "$listVarName.add($value)"
33+
}
34+
35+
private fun declareClass(lines: Lines, dtoFilename: String, dtoClass: DtoClass) {
36+
lines.add("@JsonInclude(JsonInclude.Include.NON_NULL)")
37+
lines.add("data class $dtoFilename(")
38+
addVariables(lines, dtoClass)
39+
lines.add(")")
40+
}
41+
42+
private fun addVariables(lines: Lines, dtoClass: DtoClass) {
43+
dtoClass.fields.forEach {
44+
lines.indented {
45+
lines.add("@JsonProperty(\"${it.name}\")")
46+
lines.add("var ${it.name}: ${it.type}? = null,")
47+
}
48+
lines.addEmpty()
49+
}
50+
}
51+
52+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ abstract class HttpWsTestCaseWriter : ApiTestCaseWriter() {
386386

387387
// TODO add support for kotlin
388388
var dtoVar: String? = null
389-
if (config.dtoForRequestPayload && format.isJava()) {
389+
if (config.dtoForRequestPayload && format.isJavaOrKotlin()) {
390390
dtoVar = writeDto(call, lines)
391391
}
392392

@@ -585,7 +585,7 @@ abstract class HttpWsTestCaseWriter : ApiTestCaseWriter() {
585585
}
586586

587587
private fun shouldUseDtoForPayload(dtoVar: String?): Boolean {
588-
return config.dtoForRequestPayload && format.isJava() && dtoVar?.isNotEmpty() == true
588+
return config.dtoForRequestPayload && format.isJavaOrKotlin() && dtoVar?.isNotEmpty() == true
589589
}
590590

591591
private fun writeStringifiedPayload(lines: Lines, send: String, bodyLines: List<String>, isMultiLine: Boolean) {

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ class TestSuiteWriter {
212212
val testSuiteFileName = TestSuiteFileName(solutionFilename)
213213
val testSuitePath = getTestSuitePath(testSuiteFileName, config).parent
214214
val restSampler = sampler as AbstractRestSampler
215-
DtoWriter().write(testSuitePath, testSuiteFileName.getPackage(), config.outputFormat, restSampler.getActionDefinitions())
215+
DtoWriter(config.outputFormat).write(testSuitePath, testSuiteFileName.getPackage(), restSampler.getActionDefinitions())
216216
}
217217

218218
private fun handleResetDatabaseInput(solution: Solution<*>): String {
@@ -419,14 +419,15 @@ class TestSuiteWriter {
419419
//in Kotlin this should not be imported
420420
addImport("java.util.Map", lines)
421421
addImport("java.util.Arrays", lines)
422+
}
423+
424+
if (format.isJavaOrKotlin()) {
425+
422426
if (config.dtoForRequestPayload) {
423427
val pkgPrefix = if (name.getPackage().isNotEmpty()) "${name.getPackage()}." else ""
424428
addImport("${pkgPrefix}dto.*", lines)
425429
addImport("java.util.ArrayList", lines)
426430
}
427-
}
428-
429-
if (format.isJavaOrKotlin()) {
430431

431432
addImport("java.util.List", lines)
432433
addImport(EMTestUtils::class.java.name +".*", lines, true)

0 commit comments

Comments
 (0)