Skip to content

starting with cleanUpActions #1236

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,4 @@ Migrations/
/e2e-tests/spring-rest-multidb/target/
docs/.DS_Store
.DS_Store
/e2e-tests/spring-rest-h2-v2/target/
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package bar.examples.it.spring.cleanupcreate

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*


@SpringBootApplication(exclude = [SecurityAutoConfiguration::class])
@RequestMapping(path = ["/api/resources"])
@RestController
open class CleanUpDeleteApplication {

companion object {
@JvmStatic
fun main(args: Array<String>) {
SpringApplication.run(CleanUpDeleteApplication::class.java, *args)
}

private val data = mutableMapOf<Int, String>()

fun reset(){
data.clear()
}

fun numberExistingData() = data.size
}


@PutMapping(path = ["/{id}"])
open fun put(
@PathVariable("id") id: Int
): ResponseEntity<Any> {

data[id] = "Data for $id"
return ResponseEntity.status(200).build()
}

@DeleteMapping(path = ["/{id}"])
open fun delete(@PathVariable("id") id: Int): ResponseEntity<String> {

if(!data.containsKey(id)){
return ResponseEntity.status(404).build()
}

data.remove(id)

return ResponseEntity.status(204).build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package bar.examples.it.spring.cleanupcreate

import bar.examples.it.spring.SpringController

class CleanUpDeleteController : SpringController(CleanUpDeleteApplication::class.java)
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,28 @@ abstract class IntegrationTestRestBase : RestTestBase() {

@BeforeEach
fun initInjector(){
recreateInjectorForWhite()
}

protected fun recreateInjectorForWhite(extraArgs: List<String> = listOf()) {
val args = listOf(
"--sutControllerPort", "" + controllerPort,
"--createConfigPathIfMissing", "false",
"--seed", "42"
)
).plus(extraArgs)

injector = init(args)
}

protected fun recreateInjectorForBlack(extraArgs: List<String> = listOf()){
val args = listOf(
"--blackBox", "true",
"--bbTargetUrl", baseUrlOfSut,
"--bbSwaggerUrl","$baseUrlOfSut/v3/api-docs",
"--createConfigPathIfMissing", "false",
"--seed", "42"
).plus(extraArgs)

injector = init(args)
}

Expand All @@ -38,14 +55,12 @@ abstract class IntegrationTestRestBase : RestTestBase() {
fun getEMConfig() = injector.getInstance(EMConfig::class.java)

/**
* Modified by Onur to be able to create an individual with a given sample type.
* Create and evaluate an individual
*/
fun createIndividual(actions: List<RestCallAction>, sampleT : SampleType = SampleType.SEEDED): EvaluatedIndividual<RestIndividual> {

// val searchGlobalState = injector.getInstance(SearchGlobalState::class.java)

// val ind = RestIndividual(actions.toMutableList(), SampleType.SEEDED)
// ind.doGlobalInitialize(searchGlobalState)
fun createIndividual(
actions: List<RestCallAction>,
sampleT : SampleType = SampleType.SEEDED
): EvaluatedIndividual<RestIndividual> {

val sampler = injector.getInstance(AbstractRestSampler::class.java)
/*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.evomaster.core.problem.rest.cleanupdelete

import bar.examples.it.spring.cleanupcreate.CleanUpDeleteApplication
import bar.examples.it.spring.cleanupcreate.CleanUpDeleteController
import bar.examples.it.spring.nonworkingdelete.NonWorkingDeleteApplication
import bar.examples.it.spring.nonworkingdelete.NonWorkingDeleteController
import com.webfuzzing.commons.faults.FaultCategory
import org.evomaster.core.problem.enterprise.SampleType
import org.evomaster.core.problem.rest.oracle.HttpSemanticsOracle
import org.evomaster.core.problem.rest.IntegrationTestRestBase
import org.evomaster.core.problem.rest.data.RestCallResult
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

class CleanUpDeleteTest: IntegrationTestRestBase() {


companion object {
@BeforeAll
@JvmStatic
fun init() {
initClass(CleanUpDeleteController())
}
}

@BeforeEach
fun initializeTest(){
CleanUpDeleteApplication.reset()
recreateInjectorForBlack(listOf("--blackBoxCleanUp","true"))
}


@Test
fun testDelete() {

assertEquals(0, CleanUpDeleteApplication.numberExistingData())

val pirTest = getPirToRest()

val id = 42

val put = pirTest.fromVerbPath("put", "/api/resources/$id")!!

val x = createIndividual(listOf(put), SampleType.RANDOM)
val resDel = x.evaluatedMainActions()[0].result as RestCallResult
assertEquals(200, resDel.getStatusCode())

assertEquals(1, x.evaluatedMainActions().size)

//delete should had been automatically added
assertEquals(0, CleanUpDeleteApplication.numberExistingData())

}
}
7 changes: 7 additions & 0 deletions core/src/main/kotlin/org/evomaster/core/EMConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2391,6 +2391,13 @@ class EMConfig {
@Cfg("Apply more advanced coverage criteria for black-box testing. This can result in larger generated test suites.")
var advancedBlackBoxCoverage = true

@Experimental
@Cfg("In black-box testing, aim at adding calls to reset the state of the SUT after it has been modified by the test." +
" For example, in REST APIs, DELETE operations are added (if any exist) after each successful POST/PUT." +
" However, this is done heuristically." +
" There is no guarantee the state will be properly cleaned-up, this is just a best effort attempt.")
var blackBoxCleanUp = false

fun timeLimitInSeconds(): Int {
if (maxTimeInSeconds > 0) {
return maxTimeInSeconds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,6 @@ class RestTestCaseWriter : HttpWsTestCaseWriter {
handleResponseAfterTheCall(call, res, responseVariableName, lines)

handleLinkInfo(call, res, responseVariableName, lines)

// if (shouldCheckExpectations() && !res.failedCall()) {
// handleExpectationSpecificLines(call, lines, res, responseVariableName)
// }
}

private fun handleLinkInfo(call: RestCallAction, res: RestCallResult, responseVariableName: String, lines: Lines) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import org.evomaster.core.output.TestCase
import org.evomaster.core.output.TestWriterUtils
import org.evomaster.core.output.TestWriterUtils.getWireMockVariableName
import org.evomaster.core.problem.enterprise.EnterpriseActionResult
import org.evomaster.core.problem.enterprise.EnterpriseIndividual
import org.evomaster.core.problem.externalservice.HostnameResolutionAction
import org.evomaster.core.problem.externalservice.httpws.HttpExternalServiceAction
import org.evomaster.core.problem.externalservice.httpws.param.HttpWsResponseParam
Expand Down Expand Up @@ -223,6 +224,35 @@ abstract class TestCaseWriter {
testSuitePath: Path?
)

protected fun handleCleanUpActions(
lines: Lines,
baseUrlOfSut: String,
ind: EvaluatedIndividual<*>,
insertionVars: MutableList<Pair<String, String>>,
testCaseName: String,
testSuitePath: Path?
){
/*
TODO: right now we only have DELETE in BB REST.
But, if/when we support other types, we will need to refactor this
*/
val individual = ind.individual
if(individual !is EnterpriseIndividual){
return
}
val cleanup = individual.seeCleanUpActions()
if(cleanup.isEmpty()){
return
}

lines.addEmpty(1)
lines.addSingleCommentLine("Cleanup actions")

cleanup.forEach {
addActionLinesPerType(it.flatten().first(),-1,testCaseName,lines,ind.seeResult(it.getLocalId())!!,testSuitePath,baseUrlOfSut)
}
}

/**
* handle action call generation
* @param action is the call to be generated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ class TestSuiteWriter {
private val log: Logger = LoggerFactory.getLogger(TestSuiteWriter::class.java)

private const val baseUrlOfSut = "baseUrlOfSut"
private const val expectationsMasterSwitch = "ems"
private const val fixtureClass = "ControllerFixture"
private const val fixture = "_fixture"
private const val browser = "browser"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ abstract class ApiWsAction(
* auth info
*/
open val auth: AuthenticationInfo,
isCleanUp : Boolean,
/**
* a list of param could be manipulated by evomaster
*/
parameters: List<Param>
) : MainAction(parameters){
) : MainAction(isCleanUp,parameters){

val parameters : List<Param>
get() { return children as List<Param>}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.evomaster.core.problem.enterprise.SampleType
import org.evomaster.core.search.action.ActionComponent
import org.evomaster.core.search.GroupsOfChildren
import org.evomaster.core.search.StructuralElement
import org.evomaster.core.search.action.Action
import org.evomaster.core.search.tracer.TrackOperator

/**
Expand All @@ -28,7 +29,7 @@ abstract class ApiWsIndividual (
*/
children: MutableList<out ActionComponent>,
childTypeVerifier: EnterpriseChildTypeVerifier,
groups : GroupsOfChildren<StructuralElement> = getEnterpriseTopGroups(children, children.size, 0, 0, 0, 0)
groups : GroupsOfChildren<StructuralElement> = getEnterpriseTopGroups(children, children.size, 0, 0, 0, 0, 0),
): EnterpriseIndividual(sampleType, trackOperator, index, children, childTypeVerifier, groups){


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ import org.evomaster.core.sql.SqlAction
}

override fun invoke(t: Class<*>): Boolean {
//TODO use mainType
return ( EnterpriseActionGroup::class.java.isAssignableFrom(t)
return (
//TODO due to reified generics, doesn't seem possible to check mainType in EnterpriseActionGroup
EnterpriseActionGroup::class.java.isAssignableFrom(t)
|| mainType.isAssignableFrom(t)
|| isInitializingAction(t)
|| (secondaryType != null && secondaryType.isAssignableFrom(t)))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ abstract class EnterpriseIndividual(
/**
* if no group definition is specified, then it is assumed that all action are for the MAIN group
*/
groups : GroupsOfChildren<StructuralElement> = getEnterpriseTopGroups(children,children.size,0, 0, 0, 0)
groups : GroupsOfChildren<StructuralElement> = getEnterpriseTopGroups(children,children.size,0, 0, 0, 0, 0),
) : Individual(
trackOperator,
index,
Expand All @@ -73,11 +73,12 @@ abstract class EnterpriseIndividual(
sizeMongo: Int,
sizeDNS: Int,
sizeScheduleTasks: Int,
sizeCleanUp: Int,
) : GroupsOfChildren<StructuralElement>{

if(children.size != sizeSQL +sizeMongo + sizeDNS + sizeScheduleTasks + sizeMain){
if(children.size != sizeSQL +sizeMongo + sizeDNS + sizeScheduleTasks + sizeMain + sizeCleanUp){
throw IllegalArgumentException("Group size mismatch. Expected a total of ${children.size}, but" +
" got main=$sizeMain, sql=$sizeSQL, mongo=$sizeMongo, dns=$sizeDNS, scheduleTasks=$sizeScheduleTasks")
" got main=$sizeMain, sql=$sizeSQL, mongo=$sizeMongo, dns=$sizeDNS, scheduleTasks=$sizeScheduleTasks, sizeCleanUp=$sizeCleanUp")
}
if(sizeSQL < 0){
throw IllegalArgumentException("Negative size for sizeSQL: $sizeSQL")
Expand All @@ -94,6 +95,9 @@ abstract class EnterpriseIndividual(
if(sizeMain < 0){
throw IllegalArgumentException("Negative size for sizeMain: $sizeMain")
}
if(sizeCleanUp < 0){
throw IllegalArgumentException("Negative size for sizeCleanUp: $sizeCleanUp")
}

/*
TODO in future ll need to refactor to handle multiple databases, possibly handled with
Expand Down Expand Up @@ -126,11 +130,16 @@ abstract class EnterpriseIndividual(
)

val initSize = sizeSQL+sizeMongo+sizeDNS+sizeScheduleTasks
val startIndexMain = initSize
val endIndexMain = initSize + sizeMain - 1

val main = ChildGroup<StructuralElement>(GroupsOfChildren.MAIN, {e -> e !is EnvironmentAction },
if(sizeMain == 0) -1 else initSize, if(sizeMain == 0) -1 else initSize + sizeMain - 1)
if(sizeMain == 0) -1 else startIndexMain, if(sizeMain == 0) -1 else initSize + sizeMain - 1)

val cleanup = ChildGroup<StructuralElement>(GroupsOfChildren.CLEANUP, {e -> true},
if(sizeCleanUp == 0) -1 else endIndexMain+1, if(sizeCleanUp == 0) -1 else endIndexMain + sizeCleanUp)

return GroupsOfChildren(children, listOf(db, mongodb, dns, schedule, main))
return GroupsOfChildren(children, listOf(db, mongodb, dns, schedule, main, cleanup))
}
}

Expand Down Expand Up @@ -228,6 +237,11 @@ abstract class EnterpriseIndividual(
}
}


fun seeCleanUpActions() : List<ActionComponent>{
return groupsView()!!.getAllInGroup(GroupsOfChildren.CLEANUP) as List<ActionComponent>
}

fun seeMainActionComponents() : List<ActionComponent>{
return groupsView()!!.getAllInGroup(GroupsOfChildren.MAIN) as List<ActionComponent>
}
Expand Down Expand Up @@ -408,6 +422,10 @@ abstract class EnterpriseIndividual(
}
}

fun addCleanUpAction(action: ActionComponent){
addChildToGroup(action, GroupsOfChildren.CLEANUP)
}

private fun resetInitializingActions(actions: List<SqlAction>){
killChildren { it is SqlAction }
// TODO: Can be merged with DbAction later
Expand Down Expand Up @@ -438,4 +456,23 @@ abstract class EnterpriseIndividual(
override fun seeTopGenes(filter: ActionFilter): List<Gene> {
return seeActions(filter).flatMap { it.seeTopGenes() }
}


fun removeAllCleanUp(){
killAllInGroup(GroupsOfChildren.CLEANUP)
}

/**
* Initialize dynamically added cleanup actions.
* Recompute everything for whole individual might not be feasible,
* as usually this is needed in the middle of a fitness evaluation
*/
fun initializeCleanUpActions(){

handleLocalIdsForAddition(seeCleanUpActions())

// TODO need to handle global initialization.
// this is just a temporary solution, would need to call full doGlobalInitialize(), but that
// needs refactoring to be applied to subset of actions.
}
}
Loading
Loading