Skip to content

Commit 7cdd834

Browse files
authored
Merge pull request #1478 from WebFuzzing/fix-GeneRandomizedTest
bugfixes BigDecimalGene.setValueWithDecimal() and MapGene.randomize() / enhancing GeneSamplerForTest
2 parents 802cb6d + a81238e commit 7cdd834

6 files changed

Lines changed: 104 additions & 18 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.evomaster.core.search.gene
2+
3+
/**
4+
* Exception thrown to indicate that a failure occurred during the process
5+
* of gene randomization. This exception typically signifies that an it was
6+
* not possible to generate a random valid gene value given the gene constraints.
7+
*
8+
* @param message The detail message providing more context about the specific failure.
9+
*/
10+
class GeneRandomizationFailed(message: String) : RuntimeException(message) {
11+
12+
}

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

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import org.evomaster.client.java.instrumentation.shared.TaintInputName
44
import org.evomaster.core.output.OutputFormat
55
import org.evomaster.core.problem.util.ParamUtil
66
import org.evomaster.core.search.gene.Gene
7+
import org.evomaster.core.search.gene.GeneRandomizationFailed
78
import org.evomaster.core.search.gene.interfaces.CollectionGene
89
import org.evomaster.core.search.gene.numeric.IntegerGene
910
import org.evomaster.core.search.gene.numeric.LongGene
@@ -49,6 +50,8 @@ abstract class MapGene<K, V>(
4950
private val log: Logger = LoggerFactory.getLogger(MapGene::class.java)
5051
const val MAX_SIZE = 5
5152

53+
const val MAX_RANDOMIZE_ATTEMPTS = 100
54+
5255
fun isStringMap(gene: MapGene<*, *>) = gene.template.first is StringGene && gene.template.second is StringGene
5356
}
5457

@@ -61,17 +64,31 @@ abstract class MapGene<K, V>(
6164
}
6265

6366
override fun randomize(randomness: Randomness, tryToForceNewValue: Boolean) {
64-
65-
//maybe not so important here to complicate code to enable forceNewValue
67+
/*
68+
* tryToForceNewValue is not supported for MapGene, as the value of MapGene is determined by its elements,
69+
* and it is hard to determine whether the value is new or not. thus we ignore tryToForceNewValue for MapGene.
70+
*/
6671

6772
killAllChildren()
6873
log.trace("Randomizing MapGene")
69-
val n = randomness.nextInt(getMinSizeOrDefault(), getMaxSizeUsedInRandomize())
70-
(0 until n).forEach {
74+
75+
val minSize = getMinSizeOrDefault()
76+
val targetSize = randomness.nextInt(minSize, getMaxSizeUsedInRandomize())
77+
val maxAddElementCount = maxOf(MAX_RANDOMIZE_ATTEMPTS, targetSize)
78+
var addElementCount = 0
79+
80+
while (elements.size < targetSize && addElementCount < maxAddElementCount) {
7181
val gene = createRandomElement(randomness, false)
72-
// if the key of gene exists, the value would be replaced with the latest one
82+
83+
// Observe that, if the key of gene exists, the value would be replaced with the latest one
7384
addElement(gene)
85+
addElementCount++
7486
}
87+
88+
if (elements.size < minSize) {
89+
throw GeneRandomizationFailed("Couldn't generate a valid MapGene after $addElementCount attempts.")
90+
}
91+
7592
}
7693

7794
override fun adaptiveSelectSubsetToMutate(randomness: Randomness, internalGenes: List<Gene>, mwc: MutationWeightControl, additionalGeneMutationInfo: AdditionalGeneMutationInfo): List<Pair<Gene, AdditionalGeneMutationInfo?>> {
@@ -303,4 +320,4 @@ abstract class MapGene<K, V>(
303320

304321
override fun getDefaultMaxSize() = (if (getMinSizeOrDefault() >= MAX_SIZE) (getMinSizeOrDefault() + MAX_SIZE) else MAX_SIZE)
305322

306-
}
323+
}

core/src/main/kotlin/org/evomaster/core/search/gene/numeric/BigDecimalGene.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ class BigDecimalGene(
284284
private fun getRoundingMode() = DEFAULT_ROUNDING_MODE
285285

286286
private fun setValueWithDecimal(bd: BigDecimal, precision: Int?, scale: Int?){
287-
value = if (precision == null){
287+
val nextValue = if (precision == null){
288288
if (scale == null) bd
289289
else bd.setScale(scale, getRoundingMode())
290290
} else{
@@ -294,6 +294,7 @@ class BigDecimalGene(
294294
else this
295295
}
296296
}
297+
value = nextValue
297298
}
298299

299300
private fun getMaxUsedInSearchAsLong() : Long{
@@ -385,9 +386,9 @@ class BigDecimalGene(
385386
override fun checkForLocallyValidIgnoringChildren(): Boolean {
386387
if (!super.checkForLocallyValidIgnoringChildren())
387388
return false
388-
if (max != null && value > getMaximum())
389+
if (value > getMaximum())
389390
return false
390-
if (min != null && value < getMinimum())
391+
if (value < getMinimum())
391392
return false
392393
return true
393394
}

core/src/test/kotlin/org/evomaster/core/search/gene/BigDecimalGeneTest.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import org.evomaster.core.search.service.Randomness
1111
import org.junit.jupiter.api.Assertions.assertTrue
1212
import org.junit.jupiter.api.Test
1313
import java.math.BigDecimal
14+
import java.math.MathContext
15+
import java.math.RoundingMode
1416

1517
class BigDecimalGeneTest {
1618

@@ -45,4 +47,5 @@ class BigDecimalGeneTest {
4547
assertTrue(gene.isLocallyValid(), "BigDecimalRange with range [${gene.min},${gene.max}] and initial value ${initialValue} lead to ${gene.value} after ${it} mutations")
4648
}
4749
}
48-
}
50+
51+
}

core/src/test/kotlin/org/evomaster/core/search/gene/GeneSamplerForTests.kt

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -623,23 +623,58 @@ object GeneSamplerForTests {
623623

624624
val min = rand.nextInt(0, 2)
625625

626+
val minSize = rand.choose(listOf(null, min))
627+
val maxSize = rand.choose(listOf(null, min + rand.nextInt(1, 3)))
628+
val printablePairGene = if (minSize == 2) {
629+
samplePairGeneWithAtLeastTwoKeyValues(rand) { samplePrintablePairGene(rand) }
630+
} else {
631+
samplePrintablePairGene(rand)
632+
}
633+
626634
return FixedMapGene(
627635
name = "rand MapGene",
628-
minSize = rand.choose(listOf(null, min)),
629-
maxSize = rand.choose(listOf(null, min + rand.nextInt(1, 3))),
630-
template = samplePrintablePairGene(rand)
636+
minSize = minSize,
637+
maxSize = maxSize,
638+
template = printablePairGene
631639
)
632640
}
633641

634-
fun sampleFlexibleMapGene(rand: Randomness): FlexibleMapGene<*> {
635642

643+
private fun <T : PairGene<*, *>> samplePairGeneWithAtLeastTwoKeyValues(rand: Randomness, sampler: () -> T): T {
644+
return generateSequence { sampler() }
645+
.first { genePair ->
646+
val keyGene = genePair.first
647+
648+
// If it cannot change, it cannot have "at least two values"
649+
if (!keyGene.isMutable()) return@first false
650+
651+
// Initialize the first instance and compare to the second instance with forced new value
652+
val firstInstance = keyGene.copy().apply { doInitialize(rand) }
653+
val secondInstance = firstInstance.copy().apply { randomize(rand, true) }
654+
655+
!firstInstance.containsSameValueAs(secondInstance)
656+
}
657+
}
658+
659+
fun sampleFlexibleMapGene(rand: Randomness): FlexibleMapGene<*> {
660+
// 1. Sample minSize and maxSize
636661
val min = rand.nextInt(0, 2)
662+
val minSize = rand.choose(listOf(null, min))
663+
val maxSize = rand.choose(listOf(null, min + rand.nextInt(1, 3)))
664+
665+
// 2. Sample pairGeneTemplate
666+
val printableFlexiblePairGene = if (minSize == 2) {
667+
samplePairGeneWithAtLeastTwoKeyValues(rand) { samplePrintableFlexiblePairGene(rand) }
668+
} else {
669+
samplePrintableFlexiblePairGene(rand)
670+
}
637671

672+
// 3. Create FlexibleMapGene
638673
return FlexibleMapGene(
639674
name = "rand MapGene",
640-
minSize = rand.choose(listOf(null, min)),
641-
maxSize = rand.choose(listOf(null, min + rand.nextInt(1, 3))),
642-
template = samplePrintableFlexiblePairGene(rand)
675+
minSize = minSize,
676+
maxSize = maxSize,
677+
template = printableFlexiblePairGene
643678
)
644679
}
645680

core/src/test/kotlin/org/evomaster/core/search/gene/MapGeneTest.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import org.evomaster.core.search.gene.collection.PairGene
88
import org.evomaster.core.search.gene.numeric.IntegerGene
99
import org.evomaster.core.search.gene.wrapper.FlexibleGene
1010
import org.evomaster.core.search.gene.string.StringGene
11+
import org.evomaster.core.search.service.Randomness
1112
import org.junit.jupiter.api.Assertions.*
1213
import org.junit.jupiter.api.Test
1314

@@ -120,4 +121,21 @@ internal class MapGeneTest{
120121
assertEquals(1, fMap.getAllElements().size)
121122
assertEquals(element, fMap.getViewOfChildren().first() as PairGene<*,*>)
122123
}
123-
}
124+
125+
@Test
126+
fun testGeneRandomizationFailedIsRisen() {
127+
val enumValues = listOf("ONE") // Only one value, so we can't have more than one entry in the map
128+
val enumKey = EnumGene("key", enumValues)
129+
val strValue = StringGene("value", "foo")
130+
131+
// minSize is 2, but only 1 unique key is available
132+
val map = FixedMapGene("FailMap", enumKey, strValue, minSize = 2, maxSize = 5)
133+
134+
val randomness = Randomness()
135+
136+
assertThrows(GeneRandomizationFailed::class.java) {
137+
map.randomize(randomness, false)
138+
}
139+
}
140+
141+
}

0 commit comments

Comments
 (0)