-
Notifications
You must be signed in to change notification settings - Fork 46
Mutate values to improve code coverage #213 #713
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
Mutate values to improve code coverage #213 #713
Conversation
@@ -436,7 +439,34 @@ class UtBotSymbolicEngine( | |||
limitValuesCreatedByFieldAccessors = 500 | |||
}) | |||
} | |||
fuzzedValues.forEach { values -> | |||
|
|||
val frequencyRandom = FrequencyRandom(Random(221)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't it be a configurable constant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It shouldn't because its a random seed for a Random. No difference to fuzzing if value is changed.
fuzzedValues.forEach { values -> | ||
|
||
val frequencyRandom = FrequencyRandom(Random(221)) | ||
val factor = maxOf(0, UtSettings.fuzzingRandomMutationsFactor) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, I'd prefer to see coerceAtLeast
function here since it shows the semantics better
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me it hides the value when variable name is long:
var onlyPositiveValue = theRandomValuedRetrievedFromFile.coearceAtLeast(0)
and
var onlyPositiveValue = maxOf(0, theRandomValuedRetrievedFromFile)
But I don't have any preferable way here.
val fvi = fuzzedValues.iterator() | ||
while (fvi.hasNext()) { | ||
yield(fvi.next()) | ||
// if we stuck switch to "next + several mutations" mode |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, specify what stuck
means and how you detect it. Is it connected with (attempts + 1) % (factor / 100) == 0
below? It's not an obvious condition for me as well
while (fvi.hasNext()) { | ||
yield(fvi.next()) | ||
// if we stuck switch to "next + several mutations" mode | ||
if ((attempts + 1) % (factor / 100) == 0 && coveredInstructionValues.isNotEmpty()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
factor / 100
can be zero, can`t it?
return@flow | ||
} | ||
// Update the seeded values sometimes | ||
// This is necessary because some values cannot do a good values in mutation in any case | ||
if (frequencyRandom.random.nextInt(1, 101) <= 50) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't is just nextBoolean()
?
return buildString { | ||
append(value.substring(0, position)) | ||
if (random.nextBoolean()) { | ||
append(charToMutate - random.nextInt(1, 128)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What 128
means here?
} | ||
|
||
private fun tryRemoveChar(random: Random, value: String, position: Int): String? { | ||
if (value.isEmpty() || position >= value.length) return null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could be just position >= value.length
@@ -148,3 +149,5 @@ fun objectModelProviders(idGenerator: IdentityPreservingIdGenerator<Int>): Model | |||
PrimitiveWrapperModelProvider, | |||
) | |||
} | |||
|
|||
fun defaultMutators(): List<ModelMutator> = listOf(StringRandomMutator(50), NumberRandomMutator(50)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
constants without names
parameters: List<FuzzedValue>, | ||
random: Random, | ||
) : List<FuzzedValue> { | ||
return parameters.mapIndexed { index, fuzzedValue -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here and in other functions expression body might be used
value: FuzzedValue, | ||
random: Random | ||
) : FuzzedValue? { | ||
return if (random.nextInt(1, 101) < probability) value else null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
random.nextInt(1, 101) < probability
used in many places. May you create a function for it?
48d6e4b
to
a2aa85c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall lgtm
* Wraps sequence of values, iterates through them and mutates. | ||
* | ||
* Mutation when possible is generated after every value of source sequence and then supplies values until it needed. | ||
* [statistics] should is not updated by this method, but can be changed by caller. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should not be updated?
yield(fvi.next()) | ||
// Fuzzing can generate values that doesn't recover new paths. | ||
// So, fuzzing tries to mutate values on each loop | ||
// if there are too much attempts to find new paths without mutations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that don't recover
and too many
newValues[index] = value | ||
} | ||
} | ||
return newValues.takeIf { it != values } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How many values do we have here? Wouldn't it be too expensive to check the equality of the two lists? Maybe we can store some flag that a mutation has happened?
.mapIndexed { index, fuzzedValue -> | ||
mutate(description, index, fuzzedValue, random)?.let { mutated -> | ||
FuzzedParameter(index, mutated) | ||
} | ||
} | ||
.filterNotNull() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mapIndexedNotNull
/** | ||
* Stores information that can be useful for fuzzing such as coverage, run count, etc. | ||
*/ | ||
interface FuzzerStatistics<K> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Misspell in the file name
* @return the index of the chosen item. | ||
*/ | ||
fun Random.chooseOne(frequencies: DoubleArray): Int { | ||
val total = frequencies.sum() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps, is frequencies could be a big array, we can create a custom collection for such frequencies that knowns information about its elements, such as total sum, statistics and other
|
||
private fun tryAddChar(random: Random, value: String, position: Int): String { | ||
val charToMutate = if (value.isNotEmpty()) { | ||
value[random.nextInt(value.length)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
value.random(random)?
Description
This commit adds a mutation mechanism to the fuzzing for improving code coverage by applying random mutations for numbers and strings.
Fixes #213
Type of Change
How Has This Been Tested?
Manual Scenario
Turn on only fuzzing mode. These examples can find (usually do for default utbot settings):
If results are failed try to increase test generation time.
Checklist: