Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ The following operations are available for any `IntervalUnion<T, TSize>`:
| `getBounds()` | Gets the upper and lower bound of the set. |
| `contains()` (`in`) | Determines whether a value lies in the set. |
| `minus()` (`-`) | Subtract an interval from the set. |
| `plus()` (`+`) | Add an interval from the set. |
| `plus()` (`+`) | Add an interval to the set. |
| `shift()` | Move the interval by a specified offset. |
| `intersects()` | Determines whether another interval intersects with this set. |
| `setEquals()` | Determines whether a set represents the same values. |
| `iterator()` | Iterate over all intervals in the union, in order. |
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ if (publishPropertiesFile.exists()) {
publishProperties.load(java.io.FileInputStream(publishPropertiesFile))
}
group = "io.github.whathecode.kotlinx.interval"
version = "1.0.1"
version = "2.0.0"
nexusPublishing {
repositories {
sonatype {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class InstantInterval( start: Instant, isStartIncluded: Boolean, end: Instant, i
internal val Operations = object : IntervalTypeOperations<Instant, Duration>(
InstantOperations,
DurationOperations,
getDistanceTo = { (InstantOperations.additiveIdentity - it).absoluteValue },
getDistance = { a, b -> (b - a).absoluteValue },
unsafeValueAt = { InstantOperations.additiveIdentity + it.absoluteValue }
)
{
Expand Down
118 changes: 103 additions & 15 deletions kotlinx.interval.testcases/src/commonMain/kotlin/IntervalTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import kotlin.test.*
* Tests for [Interval] which creates intervals for testing using [a], which should be smaller than [b],
* which should be smaller than [c].
* For evenly-spaced types of [T], the distance between [a] and [b], and [b] and [c], should be greater than the spacing
* between subsequent values in the set.
* between any two subsequent values in the set.
*/
@Suppress( "FunctionName" )
abstract class IntervalTest<T : Comparable<T>, TSize : Comparable<TSize>>(
Expand Down Expand Up @@ -146,20 +146,6 @@ abstract class IntervalTest<T : Comparable<T>, TSize : Comparable<TSize>>(
assertEquals( zero, oneValue.size )
}

@Test
fun size_can_be_greater_than_max_value()
{
val fullRange = createClosedInterval( operations.minValue, operations.maxValue ).size
val identity = valueOperations.additiveIdentity
val rangeBelowIdentity = createClosedInterval( operations.minValue, identity ).size
val rangeAboveIdentity = createClosedInterval( identity, operations.maxValue ).size

assertEquals(
fullRange,
sizeOperations.unsafeAdd( rangeBelowIdentity, rangeAboveIdentity )
)
}

@Test
fun getBounds_returns_canonicalized_interval()
{
Expand Down Expand Up @@ -410,6 +396,108 @@ abstract class IntervalTest<T : Comparable<T>, TSize : Comparable<TSize>>(
assertEquals( expected, bNextC + ab )
}

@Test
fun shift_succeeds()
{
val bcIntervals = createAllInclusionTypeIntervals( b, c )
val shiftSize = abSize
val expectedShiftSize: T = valueOperations.unsafeSubtract( b, a )

for ( bc in bcIntervals )
{
val shifted = bc.shift( shiftSize )
val expectedInterval = createInterval(
valueOperations.unsafeAdd( b, expectedShiftSize ),
bc.isStartIncluded,
valueOperations.unsafeAdd( c, expectedShiftSize ),
bc.isEndIncluded
)
assertEquals( expectedInterval, shifted.shiftedInterval )
assertEquals( shiftSize, shifted.offsetAmount )
assertEquals( bc.size, shifted.shiftedInterval.size )
}
}

@Test
fun shift_using_invertedDirection_succeeds()
{
val bcIntervals = createAllInclusionTypeIntervals( b, c )
val shiftSize = abSize
val expectedShiftSize: T = valueOperations.unsafeSubtract( b, a )

for ( bc in bcIntervals )
{
val shifted = bc.shift( shiftSize, invertDirection = true )
val expectedInterval = createInterval(
a,
bc.isStartIncluded,
valueOperations.unsafeSubtract( c, expectedShiftSize ),
bc.isEndIncluded
)
assertEquals( expectedInterval, shifted.shiftedInterval )
assertEquals( shiftSize, shifted.offsetAmount )
assertEquals( bc.size, shifted.shiftedInterval.size )
}
}

@Test
fun shift_negative_amount_equals_shift_positive_amount_using_invertedDirection()
{
if ( !sizeOperations.isSignedType ) return

val bc = createClosedInterval( b, c )
val abSizeNegative = sizeOperations.unsafeSubtract( sizeOperations.additiveIdentity, abSize )
val shiftNegative = bc.shift( abSizeNegative )

val shiftPositive = bc.shift( abSize, invertDirection = true )
assertEquals( shiftPositive.shiftedInterval, shiftNegative.shiftedInterval )
assertEquals( abSizeNegative, shiftNegative.offsetAmount )
}

@Test
fun shift_by_zero_returns_same_interval()
{
val toShift = createAllInclusionTypeIntervals( a, b )
for ( original in toShift )
{
val zero = sizeOperations.additiveIdentity

val shiftResult = original.shift( zero )
assertSame( original, shiftResult.shiftedInterval )

val shiftResultInverted = original.shift( zero, invertDirection = true )
assertSame( original, shiftResultInverted.shiftedInterval )
}
}

@Test
fun shift_with_overflow_shifts_up_to_maximum_value()
{
val bottomHalf = createClosedInterval( operations.minValue, valueOperations.additiveIdentity )
val maxRange = operations.getDistance( operations.minValue, operations.maxValue )

val shifted = bottomHalf.shift( maxRange )

assertEquals( operations.maxValue, shifted.shiftedInterval.upperBound )
assertEquals( bottomHalf.size, shifted.shiftedInterval.size )
val expectedShift = sizeOperations.unsafeSubtract( maxRange, bottomHalf.size )
assertEquals( expectedShift, shifted.offsetAmount )
}

@Test
fun shift_using_invertedDirection_with_overflow_shifts_down_to_minimum_value()
{
val upperHalf = createClosedInterval( valueOperations.additiveIdentity, operations.maxValue )
val maxRange = operations.getDistance( operations.minValue, operations.maxValue )

val shifted = upperHalf.shift( maxRange, invertDirection = true )

assertEquals( operations.minValue, shifted.shiftedInterval.lowerBound )
assertEquals( upperHalf.size, shifted.shiftedInterval.size )
val expectedShift = sizeOperations.unsafeSubtract( maxRange, upperHalf.size )
assertEquals( expectedShift, shifted.offsetAmount )
}

@Test
fun intersects_for_fully_contained_intervals()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,41 +14,168 @@ open class IntervalTypeOperationsTest<T : Comparable<T>, TSize : Comparable<TSiz
)
{
private val valueOperations = operations.valueOperations
private val valueZero = valueOperations.additiveIdentity

private val sizeOperations = operations.sizeOperations
private val sizeZero = sizeOperations.additiveIdentity


@Test
fun minValue_can_be_converted_back_and_forth_using_TSize()
{
val min: T = operations.minValue
val minSize: TSize = operations.getDistanceTo( min )
val minSize: TSize = operations.getDistance( valueZero, min )
val minSizeValue: T = operations.unsafeValueAt( minSize )
val subtractedMinSize: T = valueOperations.unsafeSubtract( valueOperations.additiveIdentity, minSizeValue )
val subtractedMinSize: T = valueOperations.unsafeSubtract( valueZero, minSizeValue )
assertEquals( min, subtractedMinSize )
}

@Test
fun maxValue_can_be_converted_back_and_forth_using_TSize()
{
val max: T = operations.maxValue
val maxSize: TSize = operations.getDistanceTo( max )
val maxSize: TSize = operations.getDistance( valueZero, max )
val maxSizeValue: T = operations.unsafeValueAt( maxSize )
assertEquals( max, maxSizeValue )
}

@Test
fun unsafeValueAt_is_always_positive()
fun TSize_can_represent_full_range()
{
val rangeBelowZero = operations.getDistance( operations.minValue, valueZero )
val rangeAboveZero = operations.getDistance( valueZero, operations.maxValue )
val fullRange = operations.getDistance( operations.minValue, operations.maxValue )

assertEquals(
fullRange,
sizeOperations.unsafeAdd( rangeBelowZero, rangeAboveZero )
)
}

@Test
fun getDistance_is_commutative()
{
val zero = valueOperations.additiveIdentity
val value = operations.unsafeValueAt( positiveSize )

val distance1 = operations.getDistance( valueZero, value )
val distance2 = operations.getDistance( value, valueZero )

assertEquals( positiveSize, distance1 )
assertEquals( positiveSize, distance2 )
}

@Test
fun getDistance_is_always_positive()
{
if ( !sizeOperations.isSignedType ) return

val negativeSize = sizeOperations.unsafeSubtract( sizeZero, positiveSize )
val valueAtNegative = operations.unsafeValueAt( negativeSize )

val distance1 = operations.getDistance( valueZero, valueAtNegative )
val distance2 = operations.getDistance( valueAtNegative, valueZero )

assertEquals( positiveSize, distance1 )
assertEquals( positiveSize, distance2 )
}

@Test
fun unsafeValueAt_is_always_positive()
{
val valueAtPositive = operations.unsafeValueAt( positiveSize )
assertTrue( valueAtPositive > zero, "$valueAtPositive isn't > $zero." )
assertTrue( valueAtPositive > valueZero, "$valueAtPositive isn't > $valueZero." )

if ( sizeOperations.isSignedType )
{
val negativeSize = sizeOperations.unsafeSubtract( sizeOperations.additiveIdentity, positiveSize )
val negativeSize = sizeOperations.unsafeSubtract( sizeZero, positiveSize )
val valueAtNegative = operations.unsafeValueAt( negativeSize )
assertTrue( valueAtNegative > zero, "$valueAtNegative isn't > $zero." )
assertTrue( valueAtNegative > valueZero, "$valueAtNegative isn't > $valueZero." )
}
}

@Test
fun unsafeShift_with_positive_value()
{
val shiftedRight = operations.unsafeShift( valueZero, positiveSize, invertDirection = false )

val positiveValue = operations.unsafeValueAt( positiveSize )
assertEquals( positiveValue, shiftedRight )

val shiftedLeft = operations.unsafeShift( positiveValue, positiveSize, invertDirection = true )
assertEquals( valueZero, shiftedLeft )
}

@Test
fun unsafeShift_with_negative_value()
{
if ( !sizeOperations.isSignedType ) return

val negativeSize = sizeOperations.unsafeSubtract( sizeZero, positiveSize )
assertEquals(
operations.unsafeShift( valueZero, positiveSize, invertDirection = true ),
operations.unsafeShift( valueZero, negativeSize, invertDirection = false )
)
assertEquals(
operations.unsafeShift( valueZero, positiveSize, invertDirection = false ),
operations.unsafeShift( valueZero, negativeSize, invertDirection = true )
)
}

@Test
fun unsafeShift_zero()
{
val shiftedRight = operations.unsafeShift( valueZero, sizeZero, invertDirection = false )
assertEquals( valueZero, shiftedRight )

val shiftedLeft = operations.unsafeShift( valueZero, sizeZero, invertDirection = true )
assertEquals( valueZero, shiftedLeft )
}

@Test
fun unsafeShift_full_range()
{
val fullRange = operations.getDistance( operations.minValue, operations.maxValue )

val toMax = operations.unsafeShift( operations.minValue, fullRange, invertDirection = false )
assertEquals( operations.maxValue, toMax )

val toMin = operations.unsafeShift( operations.maxValue, fullRange, invertDirection = true )
assertEquals( operations.minValue, toMin )
}

@Test
fun unsafeShift_can_overflow()
{
val max = valueOperations.maxValue
val shiftMaxOverflow = operations.unsafeShift( valueOperations.maxValue, positiveSize, invertDirection = false )

val min = valueOperations.minValue
val shiftMinOverflow = operations.unsafeShift( valueOperations.minValue, positiveSize, invertDirection = true )

// Types either overflow, or are coerced to max value (e.g. floating points).
assertTrue( shiftMaxOverflow < max || shiftMaxOverflow == max )
assertTrue( shiftMinOverflow > min || shiftMinOverflow == min )
}

@Test
fun unsafeShift_with_coerced_max_size_values()
{
val maxShift = sizeOperations.maxValue
val maxOverflow = sizeOperations.unsafeAdd( maxShift, maxShift )
if ( maxShift == maxOverflow )
{
assertFailsWith<IllegalArgumentException> {
operations.unsafeShift( valueZero, maxShift, invertDirection = false )
}
}

val minShift = sizeOperations.minValue
val minOverflow = sizeOperations.unsafeSubtract( minShift, maxShift )
if ( minShift == minOverflow )
{
assertFailsWith<IllegalArgumentException> {
operations.unsafeShift(valueZero, minShift, invertDirection = true)
}
}
}
}
Loading
Loading