Skip to content
Merged
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 @@ -6,6 +6,7 @@ import org.evomaster.core.output.Lines
import org.evomaster.core.output.SqlWriter
import org.evomaster.core.output.TestCase
import org.evomaster.core.output.TestWriterUtils
import org.evomaster.core.problem.api.param.Param
import org.evomaster.core.problem.enterprise.EnterpriseActionResult
import org.evomaster.core.problem.httpws.HttpWsAction
import org.evomaster.core.problem.httpws.HttpWsCallResult
Expand All @@ -15,12 +16,18 @@ import org.evomaster.core.problem.rest.data.RestCallResult
import org.evomaster.core.problem.rest.data.RestIndividual
import org.evomaster.core.problem.rest.link.RestLinkParameter
import org.evomaster.core.problem.rest.param.BodyParam
import org.evomaster.core.problem.rest.param.HeaderParam
import org.evomaster.core.problem.rest.param.PathParam
import org.evomaster.core.problem.rest.param.QueryParam
import org.evomaster.core.problem.rest.service.CallGraphService
import org.evomaster.core.search.action.Action
import org.evomaster.core.search.action.ActionResult
import org.evomaster.core.search.EvaluatedIndividual
import org.evomaster.core.search.Individual
import org.evomaster.core.search.gene.collection.EnumGene
import org.evomaster.core.search.gene.interfaces.NamedExamplesGene
import org.evomaster.core.search.gene.utils.GeneUtils
import org.evomaster.core.search.gene.wrapper.ChoiceGene
import org.evomaster.core.utils.StringUtils
import org.slf4j.LoggerFactory
import java.nio.file.Path
Expand Down Expand Up @@ -542,6 +549,25 @@ class RestTestCaseWriter : HttpWsTestCaseWriter {
return ind.seeFullTreeGenes()
.filter { it.name == RestActionBuilderV3.EXAMPLES_NAME }
.filter { it.staticCheckIfImpactPhenotype() }
.map { it.getValueAsRawString() }
.map {
val name = if(it is NamedExamplesGene){
"(${it.getValueName()?: "-"}) "
} else {
""
}

val param = it.getFirstParent { p -> p is Param }
val pName = when(param) {
is QueryParam -> "QUERY: ${param.name}"
is HeaderParam -> "HEADER: ${param.name}"
is PathParam -> "PATH: ${param.name}"
is BodyParam -> "BODY"
else -> ""
}

val value = it.getValueAsRawString()

"$name$pName -> $value"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -559,21 +559,26 @@ object RestActionBuilderV3 {
example: Any?,
examples: Map<String, Example>?,
messages: MutableList<String>
) : List<Any>{
) : List<Pair<Any,String?>>{

/**
* List of pairs value/name.
* the name if optional, as only defined for "examples"
*/
val data = mutableListOf<Pair<Any, String?>>()

val data = mutableListOf<Any>()
if(example != null){
data.add(example)
data.add(Pair(example,null))
}
if(!examples.isNullOrEmpty()){
examples.values.forEach {
val exm = if(it.`$ref` != null){
SchemaUtils.getReferenceExample(schemaHolder, currentSchema, it.`$ref`, messages)
examples.entries.forEach {
val exm = if(it.value.`$ref` != null){
SchemaUtils.getReferenceExample(schemaHolder, currentSchema, it.value.`$ref`, messages)
} else {
it
it.value
}
if(exm != null) {
data.add(exm.value)
data.add(Pair(exm.value, it.key))
}
}
}
Expand Down Expand Up @@ -780,7 +785,7 @@ object RestActionBuilderV3 {
referenceClassDef: String?,
options: Options,
isInPath: Boolean = false,
examples: List<Any> = listOf(),
examples: List<Pair<Any,String?>> = listOf(),
messages: MutableList<String>
): Gene {

Expand Down Expand Up @@ -974,7 +979,7 @@ object RestActionBuilderV3 {
history: Deque<String>,
referenceTypeName: String?,
options: Options,
examples: List<Any>,
examples: List<Pair<Any,String?>>,
messages: MutableList<String>
): Gene {

Expand Down Expand Up @@ -1131,7 +1136,7 @@ object RestActionBuilderV3 {
history: Deque<String>,
referenceTypeName: String?,
options: Options,
examples: List<Any>,
examples: List<Pair<Any,String?>>,
messages: MutableList<String>
) : Gene{
/*
Expand Down Expand Up @@ -1237,7 +1242,7 @@ object RestActionBuilderV3 {
fields: List<Gene>,
additionalFieldTemplate: PairGene<StringGene, Gene>?,
referenceTypeName: String?,
otherExampleValues: List<Any>,
otherExampleValues: List<Pair<Any,String?>>,
messages: MutableList<String>
) : Gene{
if (fields.isEmpty()) {
Expand Down Expand Up @@ -1269,19 +1274,29 @@ object RestActionBuilderV3 {
val exampleValue = if(options.probUseExamples > 0) schema.example else null
val multiExampleValues = if(options.probUseExamples > 0) schema.examples else null

val examples = mutableListOf<ObjectGene>()
val examples = mutableListOf<Pair<ObjectGene,String?>>()
if(exampleValue != null){
duplicateObjectWithExampleFields(name,mainGene, exampleValue)?.let {
examples.add(it)
examples.add(Pair(it,null))
}
}
if(multiExampleValues != null ){
examples.addAll(multiExampleValues.mapNotNull { duplicateObjectWithExampleFields(name,mainGene, it) })
examples.addAll(multiExampleValues
.mapNotNull { duplicateObjectWithExampleFields(name,mainGene, it) }
.map { Pair(it, null) }
)
}
examples.addAll(otherExampleValues.mapNotNull { duplicateObjectWithExampleFields(name,mainGene, it) })
examples.addAll(otherExampleValues
.mapNotNull { duplicateObjectWithExampleFields(name,mainGene, it.first)
?.let { obj -> Pair(obj,it.second) }
}
)

val v = examples.map { it.first } //values
val n = examples.map{it.second} // names

val exampleGene = if(examples.isNotEmpty()){
ChoiceGene(EXAMPLES_NAME, examples)
ChoiceGene(EXAMPLES_NAME, v, valueNames = n)
} else null
val defaultGene = if(defaultValue != null){
duplicateObjectWithExampleFields("default", mainGene, defaultValue)
Expand Down Expand Up @@ -1372,7 +1387,7 @@ object RestActionBuilderV3 {
options: Options,
collectionTemplate: Gene?,
isInPath: Boolean,
examples: List<Any>,
examples: List<Pair<Any,String?>>,
messages: MutableList<String>
) : Gene{

Expand Down Expand Up @@ -1412,7 +1427,7 @@ object RestActionBuilderV3 {
collectionTemplate: Gene? = null,
//might need to add extra constraints if in path
isInPath: Boolean,
exampleObjects: List<Any>,
exampleObjects: List<Pair<Any,String?>>,
format: String? = null,
messages: MutableList<String>
) : Gene{
Expand Down Expand Up @@ -1544,10 +1559,12 @@ object RestActionBuilderV3 {
val exampleValue = if(options.probUseExamples > 0) schema.example else null
val multiExampleValues = if(options.probUseExamples > 0) schema.examples else null

val examples = mutableListOf<String>()
//value and optional name
val examples = mutableListOf<Pair<String,String?>>()

if(exampleValue != null) {
val raw = asRawString(exampleValue)
examples.add(raw)
examples.add(Pair(raw,null))
val arrayM = if(raw.startsWith("[")) "If you are wrongly passing to it an array of values, " +
"the parser would read it as an array string or simply ignore it. "
else ""
Expand All @@ -1558,9 +1575,9 @@ object RestActionBuilderV3 {
}
if(multiExampleValues != null && multiExampleValues.isNotEmpty()){
//possibly bug in parser, but it was reading strings values double-quoted in this case
examples.addAll(multiExampleValues.map { asRawString(it) })
examples.addAll(multiExampleValues.map { Pair(asRawString(it), null) })
}
examples.addAll( exampleObjects.map { asRawString(it) })
examples.addAll( exampleObjects.map { Pair(asRawString(it.first), it.second) })


val defaultGene = if(defaultValue != null){
Expand All @@ -1581,15 +1598,20 @@ object RestActionBuilderV3 {
}
} else null

//values
val v = examples.map { it.first }
//optional names
val n = examples.map { it.second }

val exampleGene = if(examples.isNotEmpty()){
when{
NumberGene::class.java.isAssignableFrom(geneClass)
-> EnumGene(EXAMPLES_NAME, examples,0,true)
-> EnumGene(EXAMPLES_NAME, v,0,true, n)

geneClass == StringGene::class.java
|| geneClass == Base64StringGene::class.java
|| geneClass == RegexGene::class.java
-> EnumGene<String>(EXAMPLES_NAME, examples,0,false)
-> EnumGene<String>(EXAMPLES_NAME, v,0,false, n)

//TODO Arrays
else -> {
Expand Down Expand Up @@ -1692,7 +1714,7 @@ object RestActionBuilderV3 {
currentSchema: SchemaOpenAPI,
history: Deque<String> = ArrayDeque(),
options: Options,
examples: List<Any>,
examples: List<Pair<Any,String?>>,
messages: MutableList<String>
): Gene {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.evomaster.core.search.gene.collection

import org.evomaster.core.output.OutputFormat
import org.evomaster.core.search.gene.Gene
import org.evomaster.core.search.gene.interfaces.NamedExamplesGene
import org.evomaster.core.search.gene.string.StringGene
import org.evomaster.core.search.gene.root.SimpleGene
import org.evomaster.core.search.gene.utils.GeneUtils
Expand Down Expand Up @@ -30,8 +31,13 @@ class EnumGene<T : Comparable<T>>(
* to avoid specifying exact types. Still, should not be printed out as string.
* Recall that an enum is just a group of constants that cannot be mutated
*/
private val treatAsNotString : Boolean = false
) : SimpleGene(name) {
private val treatAsNotString : Boolean = false,
/**
* An optional list of 'names' for each/some of the values in this enumeration.
* This is usually just extra information, eg, to recognize named "examples" in OpenAPI schemas
*/
private val valueNames: List<String?>? = null
) : SimpleGene(name), NamedExamplesGene {

companion object {

Expand Down Expand Up @@ -81,6 +87,10 @@ class EnumGene<T : Comparable<T>>(
if (index < 0 || index >= values.size) {
throw IllegalArgumentException("Invalid index: $index")
}

if(valueNames != null && valueNames.size != values.size) {
throw IllegalArgumentException("Invalid valueNames size: ${valueNames.size}!=${values.size}")
}
}

if(treatAsNotString && values.isNotEmpty() && values[0] !is String){
Expand All @@ -98,7 +108,7 @@ class EnumGene<T : Comparable<T>>(

override fun copyContent(): Gene {
//recall: "values" is immutable
return EnumGene<T>(name, values, index, treatAsNotString)
return EnumGene<T>(name, values, index, treatAsNotString, valueNames)
}

override fun setValueWithRawString(value: String) {
Expand Down Expand Up @@ -183,6 +193,10 @@ class EnumGene<T : Comparable<T>>(
return values[index].toString()
}

override fun getValueName(): String?{
return valueNames?.get(index)
}

override fun copyValueFrom(other: Gene): Boolean {
if (other !is EnumGene<*>) {
throw IllegalArgumentException("Invalid gene type ${other.javaClass}")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.evomaster.core.search.gene.interfaces

/**
* A gene representing possible different examples provided by the user.
* Such examples might have unique names/ids used to easily identify them
*/
interface NamedExamplesGene {

fun getValueName(): String?
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.evomaster.core.search.gene.wrapper
import org.evomaster.core.logging.LoggingUtil
import org.evomaster.core.output.OutputFormat
import org.evomaster.core.search.gene.Gene
import org.evomaster.core.search.gene.interfaces.NamedExamplesGene
import org.evomaster.core.search.gene.root.CompositeFixedGene
import org.evomaster.core.search.gene.utils.GeneUtils
import org.evomaster.core.search.service.AdaptiveParameterControl
Expand All @@ -26,9 +27,14 @@ class ChoiceGene<T>(
/**
* Potentially, associate different probabilities for the different choices
*/
probabilities: List<Double>? = null
probabilities: List<Double>? = null,
/**
* Optional list of name values for each of choices.
* This is usually just extra information, eg, to recognize named "examples" in OpenAPI schemas
*/
valueNames: List<String?>? = null,

) : CompositeFixedGene(name, geneChoices), WrapperGene where T : Gene {
) : CompositeFixedGene(name, geneChoices), NamedExamplesGene, WrapperGene where T : Gene {

companion object {
private val log: Logger = LoggerFactory.getLogger(ChoiceGene::class.java)
Expand All @@ -39,6 +45,8 @@ class ChoiceGene<T>(

private val probabilities = probabilities?.toList() //make a copy

private val valueNames = valueNames?.toList()

init {
if (geneChoices.isEmpty()) {
throw IllegalArgumentException("The list of gene choices cannot be empty")
Expand All @@ -50,6 +58,9 @@ class ChoiceGene<T>(
if(probabilities != null && probabilities.size != geneChoices.size){
throw IllegalArgumentException("If probabilities are defined, then they must be same number as the genes")
}
if(valueNames != null && valueNames.size != geneChoices.size) {
throw IllegalArgumentException("If value names are defined, then they must be same number as the genes")
}
}


Expand Down Expand Up @@ -167,6 +178,10 @@ class ChoiceGene<T>(
.getValueAsRawString()
}

override fun getValueName(): String?{
return valueNames?.get(activeGeneIndex)
}

/**
* Copies the value of the other gene. The other gene
* does not have to be [ChoiceGene].
Expand Down Expand Up @@ -260,7 +275,8 @@ class ChoiceGene<T>(
name,
activeChoice = this.activeGeneIndex,
geneChoices = this.geneChoices.map { it.copy() }.toList(),
probabilities = probabilities // immutable
probabilities = probabilities, // immutable
valueNames = valueNames // immutable
)

/**
Expand Down
3 changes: 3 additions & 0 deletions release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ Under development in `master` branch.
- Option _--disabledOracleCodes_ to disable the checking of specific fault types based on their WFC fault codes. By default, all fault types are checked for.
- Option _--endpointExclude_ to exclude specific REST endpoints from the fuzzing.

### Improvements

- Generated tests for REST APIs now do have a better summaries for the use of "examples" entries.

### Addressed GitHub Issues

Expand Down