@@ -21,7 +21,9 @@ import io.kotest.property.Exhaustive
2121import io.kotest.property.Gen
2222import io.kotest.property.RandomSource
2323import io.kotest.property.Sample
24+ import io.kotest.property.arbitrary.bind
2425import io.kotest.property.arbitrary.enum
26+ import io.kotest.property.arbitrary.filterNot
2527import io.kotest.property.arbitrary.next
2628import io.kotest.property.asSample
2729import kotlin.random.nextInt
@@ -71,44 +73,94 @@ private class ListContainingNullArb<T>(
7173 is Exhaustive <T > -> gen.toArb()
7274 }
7375
74- private val edgeCaseArb: Arb <EdgeCase > = Arb .enum ()
76+ private val edgeCaseArb: Arb <EdgeCase > = Arb .edgeCase ()
7577
76- override fun edgecase (rs : RandomSource ): List <T ?> =
77- when (edgeCaseArb.next(rs)) {
78- EdgeCase .ALL_NULLS -> List (randomListSize(rs)) { null }
79- EdgeCase .MIN_SIZE -> generateListWithAtLeastOneNullElement(rs, size.first)
80- EdgeCase .MIN_SIZE_ALL_NULLS -> List (size.first) { null }
81- EdgeCase .MAX_SIZE -> generateListWithAtLeastOneNullElement(rs, size.last)
82- EdgeCase .MAX_SIZE_ALL_NULLS -> List (size.last) { null }
78+ override fun sample (rs : RandomSource ): Sample <List <T ?>> =
79+ sample(rs, listSize = randomListSize(rs)).asSample()
80+
81+ private fun sample (rs : RandomSource , listSize : Int ): List <T ?> {
82+ require(listSize > 0 ) { " invalid listSize: $listSize (must be greater than zero)" }
83+ val guaranteedNullIndex = randomGuaranteedNullIndex(rs, listSize)
84+ return List (listSize) { index ->
85+ if (index == guaranteedNullIndex || rs.random.nextDouble() < nullProbability) {
86+ null
87+ } else {
88+ arb.next(rs)
89+ }
8390 }
91+ }
8492
85- override fun sample (rs : RandomSource ): Sample <List <T ?>> =
86- generateListWithAtLeastOneNullElement(rs).asSample()
87-
88- private fun generateListWithAtLeastOneNullElement (
89- rs : RandomSource ,
90- listSize : Int = randomListSize(rs)
91- ): List <T ?> =
92- buildList(listSize) {
93- repeat(listSize) {
94- if (rs.random.nextDouble() < nullProbability) {
95- add(null )
96- } else {
97- add(arb.next(rs))
98- }
93+ override fun edgecase (rs : RandomSource ): List <T ?> {
94+ val (listSizeCategory, nullPositions) = edgeCaseArb.next(rs)
95+
96+ val listSize =
97+ when (listSizeCategory) {
98+ EdgeCase .ListSizeCategory .MIN -> size.first
99+ EdgeCase .ListSizeCategory .MAX -> size.last
100+ EdgeCase .ListSizeCategory .RANDOM -> randomListSize(rs)
99101 }
100- if (! contains(null )) {
101- set(rs.random.nextInt(size), null )
102+
103+ val guaranteedNullIndex =
104+ when (nullPositions) {
105+ EdgeCase .NullPositions .RANDOM -> randomGuaranteedNullIndex(rs, listSize)
106+ EdgeCase .NullPositions .FIRST ,
107+ EdgeCase .NullPositions .LAST ,
108+ EdgeCase .NullPositions .FIRST_AND_LAST ,
109+ EdgeCase .NullPositions .ALL -> - 1
102110 }
111+
112+ val firstIndex = 0
113+ val lastIndex = listSize - 1
114+
115+ return List (listSize) {
116+ val isNull =
117+ when (nullPositions) {
118+ EdgeCase .NullPositions .FIRST -> it == firstIndex
119+ EdgeCase .NullPositions .LAST -> it == lastIndex
120+ EdgeCase .NullPositions .FIRST_AND_LAST -> it == firstIndex || it == lastIndex
121+ EdgeCase .NullPositions .ALL -> true
122+ EdgeCase .NullPositions .RANDOM -> it == guaranteedNullIndex
123+ }
124+ if (isNull) null else arb.next(rs)
103125 }
126+ }
104127
105128 private fun randomListSize (rs : RandomSource ): Int = rs.random.nextInt(size)
106129
107- private enum class EdgeCase {
108- ALL_NULLS ,
109- MIN_SIZE ,
110- MIN_SIZE_ALL_NULLS ,
111- MAX_SIZE ,
112- MAX_SIZE_ALL_NULLS ,
130+ private fun randomGuaranteedNullIndex (rs : RandomSource , listSize : Int ): Int =
131+ rs.random.nextInt(listSize)
132+
133+ private data class EdgeCase (
134+ val listSizeCategory : ListSizeCategory ,
135+ val nullPositions : NullPositions
136+ ) {
137+
138+ enum class ListSizeCategory {
139+ MIN ,
140+ MAX ,
141+ RANDOM ,
142+ }
143+
144+ enum class NullPositions {
145+ FIRST ,
146+ LAST ,
147+ FIRST_AND_LAST ,
148+ ALL ,
149+ RANDOM ,
150+ }
151+ }
152+
153+ private companion object {
154+ fun Arb.Companion.edgeCase (
155+ listSizeCategory : Arb <EdgeCase .ListSizeCategory > = Arb .enum(),
156+ nullPositions : Arb <EdgeCase .NullPositions > = Arb .enum()
157+ ): Arb <EdgeCase > =
158+ Arb .bind(listSizeCategory, nullPositions) { listSizeCategoryValue, nullPositionsValue ->
159+ EdgeCase (listSizeCategoryValue, nullPositionsValue)
160+ }
161+ .filterNot {
162+ it.listSizeCategory == EdgeCase .ListSizeCategory .RANDOM &&
163+ it.nullPositions == EdgeCase .NullPositions .RANDOM
164+ }
113165 }
114166}
0 commit comments