Skip to content

Commit

Permalink
Merge pull request #1142 from WebFuzzing/phg/numberStringQueryParam
Browse files Browse the repository at this point in the history
Phg/number string query param
  • Loading branch information
arcuri82 authored Dec 26, 2024
2 parents b23d0da + 1c07e24 commit 94025d4
Show file tree
Hide file tree
Showing 13 changed files with 232 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ open class GraphQLActionTestCaseNamingStrategy(
) : ActionTestCaseNamingStrategy(solution, languageConventionFormatter) {


override fun expandName(individual: EvaluatedIndividual<*>, nameTokens: MutableList<String>, ambiguitySolver: ((Action) -> List<String>)?): String {
override fun expandName(individual: EvaluatedIndividual<*>, nameTokens: MutableList<String>, ambiguitySolvers: List<(Action) -> List<String>>): String {
val evaluatedAction = individual.evaluatedMainActions().last()
val action = evaluatedAction.action as GraphQLAction

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class NamingHelperNumberedTestCaseNamingStrategy(

private val namingHelper = NamingHelper()

override fun expandName(individual: EvaluatedIndividual<*>, nameTokens: MutableList<String>, ambiguitySolver: ((Action) -> List<String>)?): String {
override fun expandName(individual: EvaluatedIndividual<*>, nameTokens: MutableList<String>, ambiguitySolvers: List<(Action) -> List<String>>): String {
return namingHelper.suggestName(individual)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ open class NumberedTestCaseNamingStrategy(
}

// numbered strategy will not expand the name unless it is using the namingHelper
override fun expandName(individual: EvaluatedIndividual<*>, nameTokens: MutableList<String>, ambiguitySolver: ((Action) -> List<String>)?): String {
override fun expandName(individual: EvaluatedIndividual<*>, nameTokens: MutableList<String>, ambiguitySolvers: List<(Action) -> List<String>>): String {
return ""
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ open class RPCActionTestCaseNamingStrategy(
languageConventionFormatter: LanguageConventionFormatter
) : ActionTestCaseNamingStrategy(solution, languageConventionFormatter) {

override fun expandName(individual: EvaluatedIndividual<*>, nameTokens: MutableList<String>, ambiguitySolver: ((Action) -> List<String>)?): String {
override fun expandName(individual: EvaluatedIndividual<*>, nameTokens: MutableList<String>, ambiguitySolvers: List<(Action) -> List<String>>): String {
val evaluatedAction = individual.evaluatedMainActions().last()
val action = evaluatedAction.action as RPCCallAction

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import org.evomaster.core.search.Solution
import org.evomaster.core.search.action.Action
import org.evomaster.core.search.action.EvaluatedAction
import org.evomaster.core.search.gene.BooleanGene
import org.evomaster.core.search.gene.Gene
import org.evomaster.core.search.gene.numeric.NumberGene
import org.evomaster.core.search.gene.optional.OptionalGene
import org.evomaster.core.search.gene.string.StringGene
import javax.ws.rs.core.MediaType

open class RestActionTestCaseNamingStrategy(
Expand All @@ -20,16 +24,18 @@ open class RestActionTestCaseNamingStrategy(
private val nameWithQueryParameters: Boolean,
) : ActionTestCaseNamingStrategy(solution, languageConventionFormatter) {

override fun expandName(individual: EvaluatedIndividual<*>, nameTokens: MutableList<String>, ambiguitySolver: ((Action) -> List<String>)?): String {
override fun expandName(individual: EvaluatedIndividual<*>, nameTokens: MutableList<String>, ambiguitySolvers: List<(Action) -> List<String>>): String {
val evaluatedAction = individual.evaluatedMainActions().last()
val action = evaluatedAction.action as RestCallAction

nameTokens.add(action.verb.toString().lowercase())
nameTokens.add(on)
if (ambiguitySolver == null) {
if (ambiguitySolvers.isEmpty()) {
nameTokens.add(getPath(action.path.nameQualifier))
} else {
nameTokens.addAll(ambiguitySolver(action))
// TODO: max chars check. Idea: if len(name) + len(resultTokens) + len(foreach:solverResult) <= MAX_CHARS ---> OK
// else keep only name + acceptedSolverResults + resultTokens
ambiguitySolvers.forEach { solver -> nameTokens.addAll(solver(action)) }
}
addResult(individual, nameTokens)

Expand All @@ -54,18 +60,38 @@ open class RestActionTestCaseNamingStrategy(
* following solvers.
*/
override fun resolveAmbiguities(duplicatedIndividuals: Set<EvaluatedIndividual<*>>): Map<EvaluatedIndividual<*>, String> {
val solvedAmbiguities = mutableMapOf<EvaluatedIndividual<*>, String>()
val workingCopy = duplicatedIndividuals.toMutableSet()

val pathDisambiguatedIndividuals = solvePathAmbiguities(workingCopy)
solvedAmbiguities.putAll(pathDisambiguatedIndividuals)
removeSolvedDuplicates(workingCopy, pathDisambiguatedIndividuals.keys)
val ambiguitySolversPerIndividual = mutableMapOf<EvaluatedIndividual<*>, MutableList<(Action) -> List<String>>>()

val queryParamsDisambiguatedIndividuals = solveQueryParamsAmbiguities(workingCopy)
solvedAmbiguities.putAll(queryParamsDisambiguatedIndividuals)
removeSolvedDuplicates(workingCopy, queryParamsDisambiguatedIndividuals.keys)
val pathDisambiguatedIndividuals = getPathDisambiguationIndividuals(workingCopy)
pathDisambiguatedIndividuals.forEach {
ambiguitySolversPerIndividual[it] = mutableListOf(::pathAmbiguitySolver)
}

val queryParamsDisambiguatedIndividuals = getQueryParamsDisambiguationIndividuals(workingCopy)
queryParamsDisambiguatedIndividuals.forEach {
if (ambiguitySolversPerIndividual.containsKey(it)) {
ambiguitySolversPerIndividual[it]?.add(::queryParamsAmbiguitySolver)
} else {
ambiguitySolversPerIndividual[it] = mutableListOf(::queryParamsAmbiguitySolver)
}
}

return solvedAmbiguities
return collectSolvedNames(ambiguitySolversPerIndividual)
}

/*
* When two or more individuals share a name, no disambiguation is performed.
* Otherwise, we would just be increasing test case name length without having actually disambiguated.
*/
private fun collectSolvedNames(ambiguitySolversPerIndividual: Map<EvaluatedIndividual<*>, MutableList<(Action) -> List<String>>>): Map<EvaluatedIndividual<*>, String> {
return ambiguitySolversPerIndividual
.map { it.key to expandName(it.key, mutableListOf(), it.value) }
.groupBy({ it.second }, { it.first })
.filter { it.value.size == 1 }
.flatMap { entry -> entry.value.map { key -> key to entry.key } }
.toMap()
}

/*
Expand All @@ -74,7 +100,7 @@ open class RestActionTestCaseNamingStrategy(
* differs and when said individual does not have a parameter as a last element since it might differ in the
* parameter name but not the rest of the path.
*/
private fun solvePathAmbiguities(duplicatedIndividuals: MutableSet<EvaluatedIndividual<*>>): Map<EvaluatedIndividual<*>, String> {
private fun getPathDisambiguationIndividuals(duplicatedIndividuals: MutableSet<EvaluatedIndividual<*>>): List<EvaluatedIndividual<*>> {
return duplicatedIndividuals
.groupBy {
var path = (it.evaluatedMainActions().last().action as RestCallAction).path
Expand All @@ -85,12 +111,10 @@ open class RestActionTestCaseNamingStrategy(
val isLastAParam = path.isLastElementAParameter()
Pair(toStringPath, isLastAParam)
}
.filter { it.value.size == 1 && !it.key.second }
.mapNotNull { entry ->
val eInd = entry.value[0]
eInd to expandName(eInd, mutableListOf(), ::pathAmbiguitySolver)
}
.toMap()
.filter { it.value.isNotEmpty() && !it.key.second }
.flatMap { it.value }
.toList()

}

/*
Expand Down Expand Up @@ -123,17 +147,16 @@ open class RestActionTestCaseNamingStrategy(
* The filter call ensures that we are only performing this disambiguation when there's only one individual that
* differs and the list of query params is not empty.
*/
private fun solveQueryParamsAmbiguities(duplicatedIndividuals: MutableSet<EvaluatedIndividual<*>>): Map<EvaluatedIndividual<*>, String> {
private fun getQueryParamsDisambiguationIndividuals(duplicatedIndividuals: MutableSet<EvaluatedIndividual<*>>): List<EvaluatedIndividual<*>> {
return duplicatedIndividuals
.groupBy {
(it.evaluatedMainActions().last().action as RestCallAction).parameters.filterIsInstance<QueryParam>()
val restAction = it.evaluatedMainActions().last().action as RestCallAction
restAction.path.getOnlyUsableQueries(restAction.parameters)
.filter { queryParam -> queryParam.getGeneForQuery().staticCheckIfImpactPhenotype() }
}
.filter { it.value.size == 1 && it.key.isNotEmpty()}
.mapNotNull { entry ->
val eInd = entry.value[0]
eInd to expandName(eInd, mutableListOf(), ::queryParamsAmbiguitySolver)
}
.toMap()
.filter { it.value.isNotEmpty() && it.key.isNotEmpty()}
.flatMap { it.value }
.toList()
}

/*
Expand All @@ -142,9 +165,9 @@ open class RestActionTestCaseNamingStrategy(
private fun queryParamsAmbiguitySolver(action: Action): List<String> {
val restAction = action as RestCallAction
val result = mutableListOf<String>()
result.add(getPath(restAction.path.nameQualifier))

val queryParams = restAction.parameters.filterIsInstance<QueryParam>()
val queryParams = restAction.path.getOnlyUsableQueries(restAction.parameters)
.filter { it.getGeneForQuery().staticCheckIfImpactPhenotype() }
result.add(with)
result.add(if (queryParams.size > 1) "${queryParam}s" else queryParam)
if (nameWithQueryParameters) {
Expand All @@ -161,14 +184,45 @@ open class RestActionTestCaseNamingStrategy(
result.add(and)
}
}

val numberQueryParams = getNegativeNumberQueryParams(queryParams)
numberQueryParams.forEachIndexed { index, queryParam ->
result.add("negative")
result.add(queryParam.name)
if (index != numberQueryParams.lastIndex) {
result.add(and)
}
}

val emptyStringQueryParams = getEmptyStringQueryParams(queryParams)
emptyStringQueryParams.forEachIndexed { index, queryParam ->
result.add("empty")
result.add(queryParam.name)
if (index != emptyStringQueryParams.lastIndex) {
result.add(and)
}
}
}

private fun getBooleanQueryParams(queryParams: List<QueryParam>): List<QueryParam> {
return queryParams.filter { it.getGeneForQuery() is BooleanGene && (it.getGeneForQuery() as BooleanGene).value }
return queryParams.filter {
val booleanGene = it.getGeneForQuery().getWrappedGene(BooleanGene::class.java)
booleanGene != null && booleanGene.staticCheckIfImpactPhenotype() && booleanGene.value
}
}

private fun removeSolvedDuplicates(duplicatedIndividuals: MutableSet<EvaluatedIndividual<*>>, disambiguatedIndividuals: Set<EvaluatedIndividual<*>>) {
duplicatedIndividuals.removeAll(disambiguatedIndividuals)
private fun getNegativeNumberQueryParams(queryParams: List<QueryParam>): List<QueryParam> {
return queryParams.filter {
val numberGene = it.getGeneForQuery().getWrappedGene(NumberGene::class.java)
numberGene != null && numberGene.staticCheckIfImpactPhenotype() && numberGene.value.toLong() < 0
}
}

private fun getEmptyStringQueryParams(queryParams: List<QueryParam>): List<QueryParam> {
return queryParams.filter {
val stringGene = it.getGeneForQuery().getWrappedGene(StringGene::class.java)
stringGene != null && stringGene.staticCheckIfImpactPhenotype() && stringGene.getValueAsRawString().trim().isEmpty()
}
}

private fun isGetCall(evaluatedAction: EvaluatedAction): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ abstract class TestCaseNamingStrategy(
/**
* @param individual containing information for the test about to be named
* @param nameTokens list to collect the identifiers which will be formatted into the test case name
* @param ambiguitySolver function receiving an action and returning a list of strings that will be added to the test case name
* @param ambiguitySolvers list of functions receiving an action and returning a list of strings that will be added to the test case name
*
* @return a String with extra information that will be included in the test name, regarding the EvaluatedIndividual
*/
protected abstract fun expandName(individual: EvaluatedIndividual<*>, nameTokens: MutableList<String>, ambiguitySolver: ((Action) -> List<String>)? = null): String
protected abstract fun expandName(individual: EvaluatedIndividual<*>, nameTokens: MutableList<String>, ambiguitySolvers: List<(Action) -> List<String>> = emptyList()): String

/**
* @param duplicatedIndividuals set containing the EvaluatedIndividuals sharing the same name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,11 +297,14 @@ class RestPath(path: String) {
return params.filter(usableQueryParamsFunction()).size
}

fun resolveOnlyQuery(params: List<Param>): List<String> {

fun getOnlyUsableQueries(params: List<Param>): List<QueryParam> {
return params
.filter(usableQueryParamsFunction())
.filterIsInstance<QueryParam>()
}

fun resolveOnlyQuery(params: List<Param>): List<String> {
return getOnlyUsableQueries(params)
.map { q ->
val name = encode(q.name)

Expand Down Expand Up @@ -581,4 +584,4 @@ class RestPath(path: String) {
val path = doComputeToString(reduced, false)
return RestPath(path)
}
}
}
11 changes: 9 additions & 2 deletions core/src/main/kotlin/org/evomaster/core/search/gene/Gene.kt
Original file line number Diff line number Diff line change
Expand Up @@ -300,15 +300,22 @@ abstract class Gene(
* Wrapper genes, and only those, will override this method to check their children
*/
@Suppress("BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER")
open fun <T,K> getWrappedGene(klass: Class<K>) : T? where T : Gene, T : K{
open fun <T,K> getWrappedGene(klass: Class<K>, strict: Boolean = false) : T? where T : Gene, T : K{

if(this.javaClass == klass){
if(matchingClass(klass,strict)){
return this as T
}

return null
}

protected fun matchingClass(klass: Class<*>, strict: Boolean) : Boolean{
if(strict){
return this.javaClass == klass
}
return klass.isAssignableFrom(this.javaClass)
}

/**
* there might be a need to repair gene based on some constraints, e.g., DateGene and TimeGene
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ class ChoiceGene<T>(
}

@Suppress("BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER")
override fun <T,K> getWrappedGene(klass: Class<K>) : T? where T : Gene, T: K{
if(this.javaClass == klass){
override fun <T,K> getWrappedGene(klass: Class<K>, strict: Boolean) : T? where T : Gene, T: K{
if(matchingClass(klass,strict)){
return this as T
}
return activeGene().getWrappedGene(klass)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ class CustomMutationRateGene<out T>(
}

@Suppress("BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER")
override fun <T,K> getWrappedGene(klass: Class<K>) : T? where T : Gene, T: K{
if(this.javaClass == klass){
override fun <T,K> getWrappedGene(klass: Class<K>, strict: Boolean) : T? where T : Gene, T: K{
if(matchingClass(klass,strict)){
return this as T
}
return gene.getWrappedGene(klass)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ class FlexibleGene(name: String,
}

@Suppress("BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER")
override fun <T,K> getWrappedGene(klass: Class<K>) : T? where T : Gene, T: K{
if(this.javaClass == klass){
override fun <T,K> getWrappedGene(klass: Class<K>, strict: Boolean) : T? where T : Gene, T: K{
if(matchingClass(klass,strict)){
return this as T
}
return gene.getWrappedGene(klass)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ abstract class SelectableWrapperGene(name: String,
}

@Suppress("BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER")
override fun <T,K> getWrappedGene(klass: Class<K>) : T? where T : Gene, T: K{
if(this.javaClass == klass){
override fun <T,K> getWrappedGene(klass: Class<K>, strict: Boolean) : T? where T : Gene, T: K{
if(matchingClass(klass,strict)){
return this as T
}
return gene.getWrappedGene(klass)
Expand Down
Loading

0 comments on commit 94025d4

Please sign in to comment.