Skip to content

Commit

Permalink
Merge pull request #1165 from WebFuzzing/phg/maxCharsHardLimit
Browse files Browse the repository at this point in the history
Implementing hard limit for test case name length, default 80 chars
  • Loading branch information
arcuri82 authored Feb 3, 2025
2 parents 042ac63 + c172349 commit 5f980d2
Show file tree
Hide file tree
Showing 18 changed files with 460 additions and 216 deletions.
3 changes: 3 additions & 0 deletions core/src/main/kotlin/org/evomaster/core/EMConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2437,6 +2437,9 @@ class EMConfig {
@Cfg("Specify the naming strategy for test cases.")
var namingStrategy = defaultTestCaseNamingStrategy

@Cfg("Specify the hard limit for test case name length")
var maxTestCaseNameLength = 80

@Experimental
@Cfg("Specify if true boolean query parameters are included in the test case name." +
" Used for test case naming disambiguation. Only valid for Action based naming strategy.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ import org.evomaster.core.sql.SqlAction
abstract class ActionTestCaseNamingStrategy(
solution: Solution<*>,
private val languageConventionFormatter: LanguageConventionFormatter,
protected val maxTestCaseNameLength: Int
) : NumberedTestCaseNamingStrategy(solution) {

private val testCasesSize = solution.individuals.size

protected val on = "on"
protected val throws = "throws"
protected val returns = "returns"
Expand All @@ -29,7 +32,7 @@ abstract class ActionTestCaseNamingStrategy(
protected val wiremock = "wireMock"

protected fun formatName(nameTokens: List<String>): String {
return "_${languageConventionFormatter.formatName(nameTokens)}"
return if (nameTokens.isNotEmpty()) "_${languageConventionFormatter.formatName(nameTokens)}" else ""
}

private fun fault(faults: Set<FaultCategory>): String {
Expand All @@ -45,17 +48,42 @@ abstract class ActionTestCaseNamingStrategy(
return faults.first().testCaseLabel
}

protected fun addResult(individual: EvaluatedIndividual<*>, nameTokens: MutableList<String>) {
protected fun addResult(individual: EvaluatedIndividual<*>, nameTokens: MutableList<String>, remainingNameChars: Int) {
val detectedFaults = DetectedFaultUtils.getDetectedFaultCategories(individual)
if (detectedFaults.isNotEmpty()) {
nameTokens.add(fault(detectedFaults))
val newRemainingNameChars = if (detectedFaults.isNotEmpty()) {
addNameTokenIfAllowed(nameTokens, fault(detectedFaults), remainingNameChars)
} else {
addActionResult(individual.evaluatedMainActions().last(), nameTokens)
addActionResult(individual.evaluatedMainActions().last(), nameTokens, remainingNameChars)
}
addEnvironmentActions(individual, nameTokens, newRemainingNameChars)
}

protected fun namePrefixChars(): Int {
return "test_".length + testCasesSize + 1
}

protected fun addNameTokensIfAllowed(nameTokens: MutableList<String>, targetStrings: List<String>, remainingNameChars: Int): Int {
val charsToBeUsed = targetStrings.sumOf { it.length }
if ((remainingNameChars - charsToBeUsed) >= 0) {
nameTokens.addAll(targetStrings)
return remainingNameChars - charsToBeUsed
}
addEnvironmentActions(individual, nameTokens)
return remainingNameChars
}

protected fun addNameTokenIfAllowed(nameTokens: MutableList<String>, targetString: String, remainingNameChars: Int): Int {
if (canAddNameTokens(targetString, remainingNameChars)) {
nameTokens.add(targetString)
return remainingNameChars - targetString.length
}
return remainingNameChars
}

private fun canAddNameTokens(targetString: String, remainingNameChars: Int): Boolean {
return (remainingNameChars - targetString.length) >= 0
}

private fun addEnvironmentActions(individual: EvaluatedIndividual<*>, nameTokens: MutableList<String>) {
private fun addEnvironmentActions(individual: EvaluatedIndividual<*>, nameTokens: MutableList<String>, remainingNameChars: Int) {
val initializingActions = individual.individual.seeInitializingActions()
val allActions = individual.individual.seeAllActions()

Expand All @@ -65,8 +93,8 @@ abstract class ActionTestCaseNamingStrategy(
if (usesWireMock(allActions)) initActionNames.add(wiremock)

if (initActionNames.isNotEmpty()) {
nameTokens.add(using)
nameTokens.addAll(initActionNames)
initActionNames.add(0, using)
addNameTokensIfAllowed(nameTokens, initActionNames, remainingNameChars)
}
}

Expand All @@ -82,6 +110,6 @@ abstract class ActionTestCaseNamingStrategy(
return actions.any { it is HttpExternalServiceAction }
}

protected abstract fun addActionResult(evaluatedAction: EvaluatedAction, nameTokens: MutableList<String>)
protected abstract fun addActionResult(evaluatedAction: EvaluatedAction, nameTokens: MutableList<String>, remainingNameChars: Int): Int

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ interface AmbiguitySolver {

/**
* @param action providing information to disambiguate the test case name
* @param remainingNameChars to decide if a token is added to the name or not
*
* @return list of strings to be added to the test case name
*/
fun apply(action: Action): List<String>
fun apply(action: Action, remainingNameChars: Int): List<String>

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.evomaster.core.output.naming

import org.evomaster.core.output.TestWriterUtils
import org.evomaster.core.output.TestWriterUtils.safeVariableName
import org.evomaster.core.problem.graphql.GraphQLAction
import org.evomaster.core.problem.graphql.GraphQlCallResult
import org.evomaster.core.search.EvaluatedIndividual
Expand All @@ -9,18 +9,18 @@ import org.evomaster.core.search.action.EvaluatedAction

open class GraphQLActionTestCaseNamingStrategy(
solution: Solution<*>,
languageConventionFormatter: LanguageConventionFormatter
) : ActionTestCaseNamingStrategy(solution, languageConventionFormatter) {
languageConventionFormatter: LanguageConventionFormatter,
maxTestCaseNameLength: Int,
) : ActionTestCaseNamingStrategy(solution, languageConventionFormatter, maxTestCaseNameLength) {


override fun expandName(individual: EvaluatedIndividual<*>, nameTokens: MutableList<String>, ambiguitySolvers: List<AmbiguitySolver>): String {
val evaluatedAction = individual.evaluatedMainActions().last()
val action = evaluatedAction.action as GraphQLAction
var remainingNameChars = maxTestCaseNameLength - namePrefixChars()

nameTokens.add(action.methodType.toString().lowercase())
nameTokens.add(on)
nameTokens.add(TestWriterUtils.safeVariableName(action.methodName))
addResult(individual, nameTokens)
remainingNameChars = addNameTokensIfAllowed(nameTokens, listOf(action.methodType.toString().lowercase(), on, safeVariableName(action.methodName)), remainingNameChars)
addResult(individual, nameTokens, remainingNameChars)

return formatName(nameTokens)
}
Expand All @@ -30,16 +30,17 @@ open class GraphQLActionTestCaseNamingStrategy(
return emptyMap()
}

override fun addActionResult(evaluatedAction: EvaluatedAction, nameTokens: MutableList<String>) {
override fun addActionResult(evaluatedAction: EvaluatedAction, nameTokens: MutableList<String>, remainingNameChars: Int): Int {
val result = evaluatedAction.result as GraphQlCallResult
nameTokens.add(returns)
nameTokens.add(
val candidateTokens = mutableListOf(returns)
candidateTokens.add(
when {
result.hasErrors() -> error
result.hasNonEmptyData() -> data
else -> empty
}
)
return addNameTokensIfAllowed(nameTokens, candidateTokens, remainingNameChars)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@ open class NumberedTestCaseNamingStrategy(
return emptyMap()
}

// kicking off with an empty mutableListOf for each test case to accumulate their own name tokens
private fun getName(counter: Int, individual: EvaluatedIndividual<*>): String {
return "test_${counter}${expandName(individual, mutableListOf())}"
}

private fun concatName(counter: Int, expandedName: String): String {
return "test_${counter}${expandedName}"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.evomaster.core.output.naming

import org.evomaster.core.output.TestWriterUtils
import org.evomaster.core.output.TestWriterUtils.safeVariableName
import org.evomaster.core.problem.rpc.RPCCallAction
import org.evomaster.core.problem.rpc.RPCCallResult
import org.evomaster.core.search.EvaluatedIndividual
Expand All @@ -10,17 +10,17 @@ import org.evomaster.core.utils.StringUtils

open class RPCActionTestCaseNamingStrategy(
solution: Solution<*>,
languageConventionFormatter: LanguageConventionFormatter
) : ActionTestCaseNamingStrategy(solution, languageConventionFormatter) {
languageConventionFormatter: LanguageConventionFormatter,
maxTestCaseNameLength: Int,
) : ActionTestCaseNamingStrategy(solution, languageConventionFormatter, maxTestCaseNameLength) {

override fun expandName(individual: EvaluatedIndividual<*>, nameTokens: MutableList<String>, ambiguitySolvers: List<AmbiguitySolver>): String {
val evaluatedAction = individual.evaluatedMainActions().last()
val action = evaluatedAction.action as RPCCallAction
var remainingNameChars = maxTestCaseNameLength - namePrefixChars()

nameTokens.add(TestWriterUtils.safeVariableName(action.getSimpleClassName()))
nameTokens.add(on)
nameTokens.add(TestWriterUtils.safeVariableName(action.getExecutedFunctionName()))
addResult(individual, nameTokens)
remainingNameChars = addNameTokensIfAllowed(nameTokens, listOf(safeVariableName(action.getSimpleClassName()), on, safeVariableName(action.getExecutedFunctionName())), remainingNameChars)
addResult(individual, nameTokens, remainingNameChars)

return formatName(nameTokens)
}
Expand All @@ -30,18 +30,20 @@ open class RPCActionTestCaseNamingStrategy(
return emptyMap()
}

override fun addActionResult(evaluatedAction: EvaluatedAction, nameTokens: MutableList<String>) {
override fun addActionResult(evaluatedAction: EvaluatedAction, nameTokens: MutableList<String>, remainingNameChars: Int): Int {
val result = evaluatedAction.result as RPCCallResult
if (result.hasPotentialFault()) {
nameTokens.add(throws)
val candidateTokens = mutableListOf(throws)
val thrownException = StringUtils.extractSimpleClass(result.getExceptionTypeName()?: "")
nameTokens.add(TestWriterUtils.safeVariableName(thrownException))
candidateTokens.add(safeVariableName(thrownException))
return addNameTokensIfAllowed(nameTokens, candidateTokens, remainingNameChars)
} else {
nameTokens.add(returns)
nameTokens.add(when {
val candidateTokens = mutableListOf(returns)
candidateTokens.add(when {
result.failedCall() -> error
else -> success
})
return addNameTokensIfAllowed(nameTokens, candidateTokens, remainingNameChars)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ class TestCaseNamingStrategyFactory(
private val namingStrategy: NamingStrategy,
private val languageConventionFormatter: LanguageConventionFormatter,
private val nameWithQueryParameters: Boolean,
private val maxTestCaseNameLength: Int
) {

constructor(config: EMConfig): this(config.namingStrategy, LanguageConventionFormatter(config.outputFormat), config.nameWithQueryParameters)
constructor(config: EMConfig): this(config.namingStrategy, LanguageConventionFormatter(config.outputFormat), config.nameWithQueryParameters, config.maxTestCaseNameLength)

companion object {
private val log: Logger = LoggerFactory.getLogger(TestCaseNamingStrategyFactory::class.java)
Expand All @@ -33,9 +34,9 @@ class TestCaseNamingStrategyFactory(
private fun actionBasedNamingStrategy(solution: Solution<*>): NumberedTestCaseNamingStrategy {
val individuals = solution.individuals
return when {
individuals.any { it.individual is RestIndividual } -> return RestActionTestCaseNamingStrategy(solution, languageConventionFormatter, nameWithQueryParameters)
individuals.any { it.individual is GraphQLIndividual } -> return GraphQLActionTestCaseNamingStrategy(solution, languageConventionFormatter)
individuals.any { it.individual is RPCIndividual } -> return RPCActionTestCaseNamingStrategy(solution, languageConventionFormatter)
individuals.any { it.individual is RestIndividual } -> return RestActionTestCaseNamingStrategy(solution, languageConventionFormatter, nameWithQueryParameters, maxTestCaseNameLength)
individuals.any { it.individual is GraphQLIndividual } -> return GraphQLActionTestCaseNamingStrategy(solution, languageConventionFormatter, maxTestCaseNameLength)
individuals.any { it.individual is RPCIndividual } -> return RPCActionTestCaseNamingStrategy(solution, languageConventionFormatter, maxTestCaseNameLength)
individuals.any { it.individual is WebIndividual } -> {
log.warn("Web individuals do not have action based test case naming yet. Defaulting to Numbered strategy.")
return NamingHelperNumberedTestCaseNamingStrategy(solution)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,18 @@ class PathAmbiguitySolver : AmbiguitySolver {
* Example: /products/{productName}/configurations/{configurationName}/features/{featureName}
* must now include the name qualifier for configurations
*/
override fun apply(action: Action): List<String> {
override fun apply(action: Action, remainingNameChars: Int): List<String> {
val restAction = action as RestCallAction
val lastPath = restAction.path
var parentPath = restAction.path.parentPath()
val lastPathQualifier = getPath(lastPath.nameQualifier)

var parentPath = lastPath.parentPath()
if (lastPath.isLastElementAParameter()) {
parentPath = parentPath.parentPath()
}
return listOf(getParentPathQualifier(parentPath), getPath(restAction.path.nameQualifier))
val candidateTokens = listOf(getParentPathQualifier(parentPath), lastPathQualifier)

return if (canAddNameTokens(candidateTokens, remainingNameChars)) candidateTokens else listOf(lastPathQualifier)
}

/*
Expand All @@ -33,4 +37,8 @@ class PathAmbiguitySolver : AmbiguitySolver {
return if (parentPathQualifier == "/") "" else getPath(parentPathQualifier)
}

private fun canAddNameTokens(targetString: List<String>, remainingNameChars: Int): Boolean {
return (remainingNameChars - targetString.sumOf { it.length }) >= 0
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,45 +20,70 @@ class QueryParamsAmbiguitySolver(private val nameWithQueryParameters: Boolean) :
/*
* If there are more than one query parameters, then use plural.
*/
override fun apply(action: Action): List<String> {
override fun apply(action: Action, remainingNameChars: Int): List<String> {
val restAction = action as RestCallAction
val result = mutableListOf<String>()

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) {
addQueryParameterNames(queryParams, result)
val withTokens = listOf(with, if (queryParams.size > 1) "${queryParam}s" else queryParam)

if (canAddNameTokens(withTokens, remainingNameChars)) {
result.addAll(withTokens)
if (nameWithQueryParameters) {
val localCharsBudget = remainingNameChars - withTokens.sumOf { it.length }
addQueryParameterNames(queryParams, result, localCharsBudget)
}
}
return result
}

private fun addQueryParameterNames(queryParams: List<QueryParam>, result: MutableList<String>) {
private fun addQueryParameterNames(queryParams: List<QueryParam>, result: MutableList<String>, remainingNameChars: Int) {
var localCharsBudget = addBooleanQueryParams(queryParams, result, remainingNameChars)
localCharsBudget = addNegativeNumbersQueryParams(queryParams, result, localCharsBudget)
addEmptyStringQueryParams(queryParams, result, localCharsBudget)
}

private fun addBooleanQueryParams(queryParams: List<QueryParam>, result: MutableList<String>, remainingNameChars: Int): Int {
val booleanQueryParams = getBooleanQueryParams(queryParams)
var localCharsBudget = remainingNameChars
booleanQueryParams.forEachIndexed { index, queryParam ->
result.add(queryParam.name)
if (index != booleanQueryParams.lastIndex) {
result.add(and)
val localTokens = mutableListOf<String>()
if (index != 0) {
localTokens.add(and)
}
localTokens.add(queryParam.name)
localCharsBudget = addNameTokensIfAllowed(result, localTokens, localCharsBudget)
}
return localCharsBudget
}

private fun addNegativeNumbersQueryParams(queryParams: List<QueryParam>, result: MutableList<String>, remainingNameChars: Int): Int {
val numberQueryParams = getNegativeNumberQueryParams(queryParams)
var localCharsBudget = remainingNameChars
numberQueryParams.forEachIndexed { index, queryParam ->
result.add(negative)
result.add(queryParam.name)
if (index != numberQueryParams.lastIndex) {
result.add(and)
val localTokens = mutableListOf<String>()
if (index != 0) {
localTokens.add(and)
}
localTokens.add(negative)
localTokens.add(queryParam.name)
localCharsBudget = addNameTokensIfAllowed(result, localTokens, localCharsBudget)
}
return localCharsBudget
}

private fun addEmptyStringQueryParams(queryParams: List<QueryParam>, result: MutableList<String>, remainingNameChars: Int) {
val emptyStringQueryParams = getEmptyStringQueryParams(queryParams)
var localCharsBudget = remainingNameChars
emptyStringQueryParams.forEachIndexed { index, queryParam ->
result.add(empty)
result.add(queryParam.name)
if (index != emptyStringQueryParams.lastIndex) {
result.add(and)
val localTokens = mutableListOf<String>()
if (index != 0) {
localTokens.add(and)
}
localTokens.add(empty)
localTokens.add(queryParam.name)
localCharsBudget = addNameTokensIfAllowed(result, localTokens, localCharsBudget)
}
}

Expand All @@ -82,4 +107,17 @@ class QueryParamsAmbiguitySolver(private val nameWithQueryParameters: Boolean) :
stringGene != null && stringGene.staticCheckIfImpactPhenotype() && stringGene.getValueAsRawString().trim().isEmpty()
}
}

private fun addNameTokensIfAllowed(nameTokens: MutableList<String>, targetStrings: List<String>, remainingNameChars: Int): Int {
val charsToBeUsed = targetStrings.sumOf { it.length }
if ((remainingNameChars - charsToBeUsed) >= 0) {
nameTokens.addAll(targetStrings)
return remainingNameChars - charsToBeUsed
}
return remainingNameChars
}

private fun canAddNameTokens(targetString: List<String>, remainingNameChars: Int): Boolean {
return (remainingNameChars - targetString.sumOf { it.length }) >= 0
}
}
Loading

0 comments on commit 5f980d2

Please sign in to comment.