Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
16 changes: 12 additions & 4 deletions core/src/main/kotlin/org/evomaster/core/output/dto/DtoOutput.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package org.evomaster.core.output.dto

import org.evomaster.core.output.Lines
import org.evomaster.core.output.OutputFormat
import java.nio.file.Path

/**
Expand All @@ -12,12 +10,22 @@ import java.nio.file.Path
interface DtoOutput {

/**
* Writes a DTO class in the corresponding [org.evomaster.core.output.OutputFormat].
*
* @param testSuitePath under which the java class must be written
* @param testSuitePackage under which the java class must be written
* @param outputFormat forwarded to the [Lines] helper class and for setting the .java extension in the generated file
* @param dtoClass to be written to filesystem
*/
fun writeClass(testSuitePath: Path, testSuitePackage: String, outputFormat: OutputFormat, dtoClass: DtoClass)
fun writeClass(testSuitePath: Path, testSuitePackage: String, dtoClass: DtoClass)

/**
* Writes an ObjectMapper class in the corresponding [org.evomaster.core.output.OutputFormat] for
* Jackson/RestAssured to use when serializing DTOs.
*
* @param testSuitePath under which the class must be written
* @param testSuitePackage under which the class must be written
*/
fun writeObjectMapperClass(testSuitePath: Path, testSuitePackage: String)

/**
* @param dtoName that will be instantiated for payload
Expand Down
24 changes: 7 additions & 17 deletions core/src/main/kotlin/org/evomaster/core/output/dto/DtoWriter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,25 +49,15 @@ class DtoWriter(

fun write(testSuitePath: Path, testSuitePackage: String, solution: Solution<*>) {
calculateDtos(solution)
val dtoOutput = when {
outputFormat.isJava() -> JavaDtoOutput(outputFormat)
outputFormat.isKotlin() -> KotlinDtoOutput(outputFormat)
else -> throw IllegalStateException("$outputFormat output format does not support DTOs as request payloads.")
}
dtoCollector.forEach {
when {
outputFormat.isJava() -> JavaDtoOutput().writeClass(
testSuitePath,
testSuitePackage,
outputFormat,
it.value
)

outputFormat.isKotlin() -> KotlinDtoOutput().writeClass(
testSuitePath,
testSuitePackage,
outputFormat,
it.value
)

else -> throw IllegalStateException("$outputFormat output format does not support DTOs as request payloads.")
}
dtoOutput.writeClass(testSuitePath, testSuitePackage, it.value)
}
dtoOutput.writeObjectMapperClass(testSuitePath, testSuitePackage)
}

fun containsDtos(): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ class GeneToDto(
) {

private var dtoOutput: DtoOutput = if (outputFormat.isJava()) {
JavaDtoOutput()
JavaDtoOutput(outputFormat)
} else if (outputFormat.isKotlin()){
KotlinDtoOutput()
KotlinDtoOutput(outputFormat)
} else {
throw IllegalStateException("$outputFormat output format does not support DTOs as request payloads.")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,28 @@ import org.evomaster.core.output.TestSuiteFileName
import org.evomaster.core.utils.StringUtils
import java.nio.file.Path

class JavaDtoOutput: JvmDtoOutput() {
class JavaDtoOutput(val outputFormat: OutputFormat): JvmDtoOutput() {

override fun writeClass(testSuitePath: Path, testSuitePackage: String, outputFormat: OutputFormat, dtoClass: DtoClass) {
override fun writeClass(testSuitePath: Path, testSuitePackage: String, dtoClass: DtoClass) {
val dtoFilename = TestSuiteFileName(appendDtoPackage(dtoClass.name))
val lines = Lines(outputFormat)
setPackage(lines, testSuitePackage)
addImports(lines)
initClass(lines, dtoFilename.getClassName())
initDtoClass(lines, dtoFilename.getClassName())
addClassContent(lines, dtoClass)
closeClass(lines)
saveToDisk(lines.toString(), getTestSuitePath(testSuitePath, dtoFilename, outputFormat))
}

override fun writeObjectMapperClass(testSuitePath: Path, testSuitePackage: String) {
val mapperFilename = TestSuiteFileName(appendDtoPackage(customControlCharMapperFactory))
val lines = Lines(outputFormat)
setPackage(lines, testSuitePackage)
addMapperImports(lines)
addMapperContentClass(lines)
saveToDisk(lines.toString(), getTestSuitePath(testSuitePath, mapperFilename, outputFormat))
}

override fun getNewObjectStatement(dtoName: String, dtoVarName: String): String {
return "$dtoName $dtoVarName = new $dtoName();"
}
Expand All @@ -35,7 +44,7 @@ class JavaDtoOutput: JvmDtoOutput() {
return "$listVarName.add($value);"
}

private fun initClass(lines: Lines, dtoFilename: String) {
private fun initDtoClass(lines: Lines, dtoFilename: String) {
lines.add("@JsonInclude(JsonInclude.Include.NON_NULL)")
lines.add("public class $dtoFilename {")
lines.addEmpty()
Expand Down Expand Up @@ -81,4 +90,56 @@ class JavaDtoOutput: JvmDtoOutput() {
lines.addEmpty()
}
}

private fun addMapperContentClass(lines: Lines) {
lines.add("public class $customControlCharMapperFactory implements Jackson2ObjectMapperFactory {")
lines.addEmpty()
lines.indented {
lines.add("@Override")
lines.add("public ObjectMapper create(Type cls, String charset) {")
lines.indented {
lines.add("ObjectMapper mapper = new ObjectMapper();")
lines.add("mapper.registerModule(new Jdk8Module());")
lines.add("mapper.getFactory().setCharacterEscapes(new NoEscapeControlChars());")
lines.add("return mapper;")
}
lines.add("}")
}
lines.addEmpty()
lines.add("}")
lines.addEmpty()
lines.add("class NoEscapeControlChars extends CharacterEscapes {")
lines.addEmpty()
lines.indented {
lines.add("private final int[] escapes;")
lines.addEmpty()
lines.add("public NoEscapeControlChars() {")
lines.indented {
lines.add("int[] std = CharacterEscapes.standardAsciiEscapesForJSON();")
lines.add("escapes = java.util.Arrays.copyOf(std, std.length);")
lines.add("for (int i = 0; i < 0x20; i++) {")
lines.indented {
lines.add("escapes[i] = CharacterEscapes.ESCAPE_NONE;")
}
lines.add("}")
}
lines.add("}")
lines.addEmpty()
lines.add("@Override")
lines.add("public int[] getEscapeCodesForAscii() {")
lines.indented {
lines.add("return escapes;")
}
lines.add("}")
lines.addEmpty()
lines.add("@Override")
lines.add("public SerializableString getEscapeSequence(int ch) {")
lines.indented {
lines.add("return null;")
}
lines.add("}")
lines.addEmpty()
}
lines.add("}")
}
}
13 changes: 13 additions & 0 deletions core/src/main/kotlin/org/evomaster/core/output/dto/JvmDtoOutput.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import java.nio.file.Path

abstract class JvmDtoOutput: DtoOutput {

val customControlCharMapperFactory = "CustomControlCharMapperFactory"

protected fun setPackage(lines: Lines, suitePackage: String) {
val pkgPrefix = if (suitePackage.isNotEmpty()) "$suitePackage." else ""
lines.addStatement("package ${pkgPrefix}dto")
Expand All @@ -23,6 +25,17 @@ abstract class JvmDtoOutput: DtoOutput {
lines.addEmpty()
}

protected fun addMapperImports(lines: Lines) {
lines.addStatement("import com.fasterxml.jackson.core.SerializableString")
lines.addStatement("import com.fasterxml.jackson.core.io.CharacterEscapes")
lines.addStatement("import com.fasterxml.jackson.databind.ObjectMapper")
lines.addStatement("import com.fasterxml.jackson.datatype.jdk8.Jdk8Module")
lines.addEmpty()
lines.addStatement("import io.restassured.path.json.mapper.factory.Jackson2ObjectMapperFactory")
lines.addStatement("import java.lang.reflect.Type")
lines.addEmpty()
}

protected fun appendDtoPackage(name: String): String {
return "dto.$name"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,26 @@ import org.evomaster.core.output.OutputFormat
import org.evomaster.core.output.TestSuiteFileName
import java.nio.file.Path

class KotlinDtoOutput: JvmDtoOutput() {
class KotlinDtoOutput(val outputFormat: OutputFormat): JvmDtoOutput() {

override fun writeClass(testSuitePath: Path, testSuitePackage: String, outputFormat: OutputFormat, dtoClass: DtoClass) {
override fun writeClass(testSuitePath: Path, testSuitePackage: String, dtoClass: DtoClass) {
val dtoFilename = TestSuiteFileName(appendDtoPackage(dtoClass.name))
val lines = Lines(outputFormat)
setPackage(lines, testSuitePackage)
addImports(lines)
declareClass(lines, dtoFilename.getClassName(), dtoClass)
declareDtoClass(lines, dtoFilename.getClassName(), dtoClass)
saveToDisk(lines.toString(), getTestSuitePath(testSuitePath, dtoFilename, outputFormat))
}

override fun writeObjectMapperClass(testSuitePath: Path, testSuitePackage: String) {
val mapperFilename = TestSuiteFileName(appendDtoPackage(customControlCharMapperFactory))
val lines = Lines(outputFormat)
setPackage(lines, testSuitePackage)
addMapperImports(lines)
addMapperContentClass(lines)
saveToDisk(lines.toString(), getTestSuitePath(testSuitePath, mapperFilename, outputFormat))
}

override fun getNewObjectStatement(dtoName: String, dtoVarName: String): String {
return "val $dtoVarName = $dtoName()"
}
Expand All @@ -32,7 +41,7 @@ class KotlinDtoOutput: JvmDtoOutput() {
return "$listVarName.add($value)"
}

private fun declareClass(lines: Lines, dtoFilename: String, dtoClass: DtoClass) {
private fun declareDtoClass(lines: Lines, dtoFilename: String, dtoClass: DtoClass) {
lines.add("@JsonInclude(JsonInclude.Include.NON_NULL)")
lines.add("class $dtoFilename(")
addVariables(lines, dtoClass)
Expand All @@ -49,4 +58,53 @@ class KotlinDtoOutput: JvmDtoOutput() {
}
}

private fun addMapperContentClass(lines: Lines) {
lines.add("class $customControlCharMapperFactory : Jackson2ObjectMapperFactory {")
lines.addEmpty()
lines.indented {
lines.add("override fun create(cls: Type, charset: String): ObjectMapper {")
lines.indented {
lines.add("val mapper = ObjectMapper()")
lines.add("mapper.registerModule(Jdk8Module())")
lines.add("mapper.factory.setCharacterEscapes(NoEscapeControlChars())")
lines.add("return mapper")
}
lines.add("}")
}
lines.addEmpty()
lines.add("}")
lines.addEmpty()
lines.add("class NoEscapeControlChars : CharacterEscapes() {")
lines.addEmpty()
lines.indented {
lines.add("private val escapes: IntArray")
lines.addEmpty()
lines.add("init {")
lines.indented {
lines.add("val std = CharacterEscapes.standardAsciiEscapesForJSON()")
lines.add("escapes = std.copyOf(std.size)")
lines.add("for (i in 0..0x1f) {")
lines.indented {
lines.add("escapes[i] = CharacterEscapes.ESCAPE_NONE")
}
lines.add("}")
}
lines.add("}")
lines.addEmpty()
lines.add("override fun getEscapeCodesForAscii(): IntArray {")
lines.indented {
lines.add("return escapes")
}
lines.add("}")
lines.addEmpty()
lines.add("override fun getEscapeSequence(ch: Int): SerializableString? {")
lines.indented {
lines.add("return null")
}
lines.add("}")
lines.addEmpty()
}
lines.add("}")
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,7 @@ class TestSuiteWriter {
val pkgPrefix = if (name.getPackage().isNotEmpty()) "${name.getPackage()}." else ""
addImport("${pkgPrefix}dto.*", lines)
addImport("java.util.ArrayList", lines)
addImport("io.restassured.config.ObjectMapperConfig", lines)
}

addImport("java.util.List", lines)
Expand Down Expand Up @@ -812,6 +813,17 @@ class TestSuiteWriter {
lines.indented {
lines.add(".jsonConfig(JsonConfig.jsonConfig().numberReturnType(JsonPathConfig.NumberReturnType.DOUBLE))")
lines.add(".redirect(redirectConfig().followRedirects(false))")
if (config.dtoSupportedForPayload() && containsDtos) {
lines.indented {
lines.add(".objectMapperConfig(")
lines.indented {
lines.add("ObjectMapperConfig.objectMapperConfig()")
val newQualifier = if (format.isJava()) "new" else ""
lines.add(".jackson2ObjectMapperFactory($newQualifier CustomControlCharMapperFactory())")
}
lines.add(")")
}
}
}
lines.appendSemicolon()
}
Expand Down