Skip to content

Commit 322a1d5

Browse files
authored
Merge pull request #1631 from WebFuzzing/examples-data-pool
Examples data pool
2 parents 55505dd + 2b741b7 commit 322a1d5

12 files changed

Lines changed: 206 additions & 18 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.foo.rest.examples.spring.openapi.v3.examplepool
2+
3+
import org.springframework.boot.SpringApplication
4+
import org.springframework.boot.autoconfigure.SpringBootApplication
5+
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
6+
import org.springframework.http.MediaType
7+
import org.springframework.http.ResponseEntity
8+
import org.springframework.web.bind.annotation.PostMapping
9+
import org.springframework.web.bind.annotation.RequestBody
10+
import org.springframework.web.bind.annotation.RequestMapping
11+
import org.springframework.web.bind.annotation.RestController
12+
13+
@RestController
14+
@RequestMapping(path = ["/api/examplepool"])
15+
@SpringBootApplication(exclude = [SecurityAutoConfiguration::class])
16+
open class ExamplePoolApplication {
17+
18+
companion object {
19+
@JvmStatic
20+
fun main(args: Array<String>) {
21+
SpringApplication.run(ExamplePoolApplication::class.java, *args)
22+
}
23+
}
24+
25+
@PostMapping(consumes = [MediaType.APPLICATION_JSON_VALUE])
26+
fun post(@RequestBody dto: ExamplePoolDto) : ResponseEntity<String>{
27+
28+
if(dto.x== "foo" && dto.y== "bar"){
29+
return ResponseEntity.ok("OK")
30+
}
31+
32+
return ResponseEntity.status(400).build()
33+
}
34+
}
35+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.foo.rest.examples.spring.openapi.v3.examplepool
2+
3+
class ExamplePoolDto(
4+
5+
var x: String? = null,
6+
7+
var y: String? = null
8+
)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
openapi: 3.1.0
2+
info:
3+
title: OpenAPI definition
4+
version: v0
5+
servers:
6+
- url: http://localhost:8080
7+
description: Generated server url
8+
paths:
9+
/api/examplepool:
10+
post:
11+
tags:
12+
- example-pool-application
13+
operationId: post
14+
requestBody:
15+
content:
16+
application/json:
17+
schema:
18+
allOf:
19+
- $ref: '#/components/schemas/ExamplePoolDto'
20+
examples:
21+
first:
22+
value:
23+
x: foo
24+
y: '123'
25+
second:
26+
value:
27+
x: '456'
28+
y: bar
29+
required: true
30+
responses:
31+
'200':
32+
description: OK
33+
content:
34+
'*/*':
35+
schema:
36+
type: string
37+
components:
38+
schemas:
39+
ExamplePoolDto:
40+
type: object
41+
properties:
42+
x:
43+
type: string
44+
y:
45+
type: string
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.foo.rest.examples.spring.openapi.v3.examplepool
2+
3+
import com.foo.rest.examples.spring.openapi.v3.SpringController
4+
import org.evomaster.client.java.controller.problem.ProblemInfo
5+
import org.evomaster.client.java.controller.problem.RestProblem
6+
7+
class ExamplePoolController : SpringController(ExamplePoolApplication::class.java){
8+
9+
10+
override fun getProblemInfo(): ProblemInfo {
11+
return RestProblem(
12+
"http://localhost:$sutPort/openapi-examplepool.yaml",
13+
null
14+
)
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.evomaster.e2etests.spring.openapi.v3.examplepool
2+
3+
import com.foo.rest.examples.spring.openapi.v3.examplepool.ExamplePoolController
4+
import org.evomaster.core.problem.rest.data.HttpVerb
5+
import org.evomaster.e2etests.spring.openapi.v3.SpringTestBase
6+
import org.junit.jupiter.api.Assertions
7+
import org.junit.jupiter.api.BeforeAll
8+
import org.junit.jupiter.api.Test
9+
10+
class ExamplePoolEMTest : SpringTestBase(){
11+
12+
companion object {
13+
@BeforeAll
14+
@JvmStatic
15+
fun init() {
16+
initClass(ExamplePoolController())
17+
}
18+
}
19+
20+
@Test
21+
fun testRunEM() {
22+
23+
runTestHandlingFlakyAndCompilation(
24+
"ExamplePool",
25+
100
26+
) { args: MutableList<String> ->
27+
28+
setOption(args, "useObjectExampleDataPool", "true")
29+
setOption(args, "blackBox", "true")
30+
setOption(args, "base", baseUrlOfSut)
31+
setOption(args, "schema", "src/main/resources/static/openapi-examplepool.yaml")
32+
33+
val solution = initAndRun(args)
34+
35+
Assertions.assertTrue(solution.individuals.size >= 1)
36+
37+
assertHasAtLeastOne(solution, HttpVerb.POST, 400, "/api/examplepool", null)
38+
assertHasAtLeastOne(solution, HttpVerb.POST, 200, "/api/examplepool", "OK")
39+
}
40+
}
41+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3191,6 +3191,9 @@ class EMConfig {
31913191
" If so, those will be added to the data pool.")
31923192
var useDictionaryDataPool = false
31933193

3194+
@Cfg("Feed the individual entries of object examples to the data pool.")
3195+
var useObjectExampleDataPool = true
3196+
31943197
@Cfg("Specify the naming strategy for test cases.")
31953198
var namingStrategy = defaultTestCaseNamingStrategy
31963199

core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -784,7 +784,7 @@ object RestActionBuilderV3 {
784784
gene = OptionalGene(name, gene)
785785
}
786786

787-
val contentTypeGene = EnumGene<String>("contentType", bodies.keys)
787+
val contentTypeGene = EnumGene<String>(RestGeneSpecialNames.CONTENT_TYPE.name, bodies.keys)
788788
val bodyParam = BodyParam(gene, contentTypeGene)
789789
.apply { this.description = description }
790790

@@ -1815,10 +1815,10 @@ object RestActionBuilderV3 {
18151815
val n = examples.map{it.second} // names
18161816

18171817
val exampleGene = if(examples.isNotEmpty()){
1818-
ChoiceGene(UserExamplesGene.EXAMPLES_NAME, v, valueNames = n)
1818+
ChoiceGene(RestGeneSpecialNames.SCHEMA_EXAMPLES.name, v, valueNames = n)
18191819
} else null
18201820
val defaultGene = if(defaultValue != null){
1821-
duplicateObjectWithExampleFields("default", mainGene, defaultValue)
1821+
duplicateObjectWithExampleFields(RestGeneSpecialNames.DEFAULT.name, mainGene, defaultValue)
18221822
} else null
18231823

18241824
/*
@@ -1839,6 +1839,7 @@ object RestActionBuilderV3 {
18391839
if(exampleValue.has(f.name)){
18401840
val e = exampleValue.get(f.name)
18411841
if(e.isTextual){
1842+
//WARN: if modify this might need update feedObjectExamplesToDataPool
18421843
EnumGene<String>(f.name, listOf(asRawString(e.textValue())), 0, false)
18431844
} else if(e.isObject) {
18441845
val nested = f.getWrappedGene(ObjectGene::class.java)
@@ -1998,12 +1999,12 @@ object RestActionBuilderV3 {
19981999
val defaultGene = if(defaultValue != null){
19992000
when{
20002001
NumberGene::class.java.isAssignableFrom(geneClass)
2001-
-> EnumGene("default", listOf(defaultValue.toString()),0,true)
2002+
-> EnumGene(RestGeneSpecialNames.DEFAULT.name, listOf(defaultValue.toString()),0,true)
20022003

20032004
geneClass == StringGene::class.java
20042005
|| geneClass == Base64StringGene::class.java
20052006
|| geneClass == RegexGene::class.java
2006-
-> EnumGene<String>("default", listOf(asRawString(defaultValue)),0,false)
2007+
-> EnumGene<String>(RestGeneSpecialNames.DEFAULT.name, listOf(asRawString(defaultValue)),0,false)
20072008

20082009
//TODO Arrays
20092010
else -> {
@@ -2021,12 +2022,12 @@ object RestActionBuilderV3 {
20212022
val exampleGene = if(examples.isNotEmpty()){
20222023
when{
20232024
NumberGene::class.java.isAssignableFrom(geneClass)
2024-
-> EnumGene(UserExamplesGene.EXAMPLES_NAME, v,0,true, n)
2025+
-> EnumGene(RestGeneSpecialNames.SCHEMA_EXAMPLES.name, v,0,true, n)
20252026

20262027
geneClass == StringGene::class.java
20272028
|| geneClass == Base64StringGene::class.java
20282029
|| geneClass == RegexGene::class.java
2029-
-> EnumGene<String>(UserExamplesGene.EXAMPLES_NAME, v,0,false, n)
2030+
-> EnumGene<String>(RestGeneSpecialNames.SCHEMA_EXAMPLES.name, v,0,false, n)
20302031

20312032
//TODO Arrays
20322033
else -> {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.evomaster.core.problem.rest.builder
2+
3+
enum class RestGeneSpecialNames {
4+
/**
5+
* Name given to enum genes representing data examples coming from OpenAPI schema
6+
*/
7+
SCHEMA_EXAMPLES,
8+
9+
/**
10+
* Name representing a "default" entry value, as declared in OpenAPI schema
11+
*/
12+
DEFAULT,
13+
CONTENT_TYPE
14+
}

core/src/main/kotlin/org/evomaster/core/problem/rest/service/sampler/AbstractRestSampler.kt

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import org.evomaster.core.problem.httpws.service.HttpWsSampler
1818
import org.evomaster.core.problem.rest.*
1919
import org.evomaster.core.problem.rest.builder.RestActionBuilderV3
2020
import org.evomaster.core.problem.rest.builder.RestActionBuilderV3.buildActionBasedOnUrl
21+
import org.evomaster.core.problem.rest.builder.RestGeneSpecialNames
2122
import org.evomaster.core.problem.rest.data.Endpoint
2223
import org.evomaster.core.problem.rest.data.HttpVerb
2324
import org.evomaster.core.problem.rest.data.RestCallAction
@@ -35,10 +36,11 @@ import org.evomaster.core.problem.rest.service.AIResponseClassifier
3536
import org.evomaster.core.problem.rest.service.RestIndividualBuilder
3637
import org.evomaster.core.remote.SutProblemException
3738
import org.evomaster.core.search.action.Action
39+
import org.evomaster.core.search.gene.collection.EnumGene
3840
import org.evomaster.core.search.gene.wrapper.CustomMutationRateGene
3941
import org.evomaster.core.search.gene.wrapper.OptionalGene
4042
import org.evomaster.core.search.gene.string.StringGene
41-
import org.evomaster.core.search.service.WarningsAggregator
43+
import org.evomaster.core.search.service.DataPool
4244
import org.evomaster.core.search.tracer.Traceable
4345
import org.evomaster.core.search.warning.GeneralWarning
4446
import org.evomaster.core.search.warning.WarningCategory
@@ -47,6 +49,7 @@ import org.slf4j.LoggerFactory
4749
import javax.annotation.PostConstruct
4850

4951

52+
5053
abstract class AbstractRestSampler : HttpWsSampler<RestIndividual>() {
5154

5255
companion object {
@@ -68,6 +71,9 @@ abstract class AbstractRestSampler : HttpWsSampler<RestIndividual>() {
6871
@Inject
6972
protected lateinit var externalServiceHandler: HttpWsExternalServiceHandler
7073

74+
@Inject
75+
protected lateinit var dataPool: DataPool
76+
7177
protected val adHocInitialIndividuals: MutableList<RestIndividual> = mutableListOf()
7278

7379
lateinit var schemaHolder: RestSchema
@@ -139,6 +145,10 @@ abstract class AbstractRestSampler : HttpWsSampler<RestIndividual>() {
139145
initializeDerivedParamRules(problem.derivedParams)
140146
}
141147

148+
if(config.useObjectExampleDataPool){
149+
feedObjectExamplesToDataPool(actionCluster)
150+
}
151+
142152
initSqlInfo(infoDto)
143153

144154
initHostnameInfo(infoDto)
@@ -162,6 +172,8 @@ abstract class AbstractRestSampler : HttpWsSampler<RestIndividual>() {
162172
log.debug("Done initializing {}", AbstractRestSampler::class.simpleName)
163173
}
164174

175+
176+
165177
override fun sampleRandomAction(noAuthP: Double): HttpWsAction {
166178

167179
val action = super.sampleRandomAction(noAuthP)
@@ -334,6 +346,10 @@ abstract class AbstractRestSampler : HttpWsSampler<RestIndividual>() {
334346
initSeededTests()
335347
}
336348

349+
if(config.useObjectExampleDataPool){
350+
feedObjectExamplesToDataPool(actionCluster)
351+
}
352+
337353
log.debug("Done initializing {}", AbstractRestSampler::class.simpleName)
338354
}
339355

@@ -434,4 +450,18 @@ abstract class AbstractRestSampler : HttpWsSampler<RestIndividual>() {
434450
}
435451
}
436452

453+
private fun feedObjectExamplesToDataPool(actionCluster: Map<String, Action>) {
454+
455+
actionCluster.values.asSequence()
456+
.flatMap { it.seeAllGenes() }
457+
.filterIsInstance<EnumGene<String>>()
458+
//Not the cleanest option, but adding a further tag on Gene would likely had been worse.
459+
//This is based on what done in RestActionBuilderV3
460+
.filter{!it.treatAsNotString && it.valueNames==null && it.values.size == 1}
461+
.filter{g -> RestGeneSpecialNames.entries.none { e -> e.name == g.name } }
462+
.forEach {
463+
dataPool.addValue(it.name, it.getValueAsRawString())
464+
}
465+
}
466+
437467
}

core/src/main/kotlin/org/evomaster/core/search/gene/collection/EnumGene.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ class EnumGene<T : Comparable<T>>(
3636
* to avoid specifying exact types. Still, should not be printed out as string.
3737
* Recall that an enum is just a group of constants that cannot be mutated
3838
*/
39-
private val treatAsNotString : Boolean = false,
39+
val treatAsNotString : Boolean = false,
4040
/**
4141
* An optional list of 'names' for each/some of the values in this enumeration.
4242
* This is usually just extra information, eg, to recognize named "examples" in OpenAPI schemas
4343
*/
44-
private val valueNames: List<String?>? = null
44+
val valueNames: List<String?>? = null
4545
) : SimpleGene(name), UserExamplesGene {
4646

4747
companion object {

0 commit comments

Comments
 (0)