All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.IntervalTest.kt Maven / Gradle / Ivy

The newest version!
package io.github.whathecode.kotlinx.interval.test

import io.github.whathecode.kotlinx.interval.Interval
import io.github.whathecode.kotlinx.interval.IntervalTypeOperations
import io.github.whathecode.kotlinx.interval.IntervalUnion
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.
 */
abstract class IntervalTest, TSize : Comparable>(
    private val a: T,
    private val b: T,
    private val c: T,
    /**
     * Expected size of the interval between [a] and [b].
     */
    private val abSize: TSize,
    /**
     * Provide access to the predefined set of operators of [T] and [TSize] and conversions between them.
     */
    private val operations: IntervalTypeOperations
)
{
    private val valueOperations = operations.valueOperations
    private val sizeOperations = operations.sizeOperations

    /**
     * Value which lies as far beyond [c] as [b] lies beyond [a].
     */
    private val d = valueOperations.unsafeAdd( c, valueOperations.unsafeSubtract( b, a ) )

    private fun createInterval( start: T, isStartIncluded: Boolean, end: T, isEndIncluded: Boolean ) =
        Interval( start, isStartIncluded, end, isEndIncluded, operations )
    private fun createClosedInterval( start: T, end: T ): Interval = createInterval( start, true, end, true )
    private fun createOpenInterval( start: T, end: T ): Interval = createInterval( start, false, end, false )

    /**
     * Create a closed, open, and both half-open intervals using [start] and [end].
     */
    private fun createAllInclusionTypeIntervals( start: T, end: T ): List> = listOf(
        createClosedInterval( start, end ),
        createOpenInterval( start, end ),
        createInterval( start, true, end, false ),
        createInterval( start, false, end, true )
    )


    @Test
    fun is_correct_test_configuration()
    {
        assertTrue( a < b && b < c )

        // For evenly-spaced types, the distance between a-b, and b-c, should be greater than the spacing.
        val spacing = valueOperations.spacing
        if ( spacing != null )
        {
            val abSize = valueOperations.unsafeSubtract( b, a )
            val bcSize = valueOperations.unsafeSubtract( c, b )
            assertTrue( abSize > spacing && bcSize > spacing )
        }
    }

    @Test
    fun constructing_open_or_half_open_intervals_with_same_start_and_end_fails()
    {
        assertFailsWith { createOpenInterval( a, a ) }
        assertFailsWith { createInterval( a, true, a, false ) }
        assertFailsWith { createInterval( a, false, a, true ) }
    }

    @Test
    fun isOpenInterval()
    {
        val openInterval = createOpenInterval( a, b )
        assertTrue( openInterval.isOpenInterval )

        val notOpen = createAllInclusionTypeIntervals( a, b ) - openInterval
        notOpen.forEach { assertFalse( it.isOpenInterval ) }
    }

    @Test
    fun isClosedInterval()
    {
        val closedInterval = createClosedInterval( a, b )
        assertTrue( closedInterval.isClosedInterval )

        val notClosed = createAllInclusionTypeIntervals( a, b ) - closedInterval
        notClosed.forEach { assertFalse( it.isClosedInterval ) }
    }

    @Test
    fun isReversed()
    {
        val notReversed = createAllInclusionTypeIntervals( a, b ) + createClosedInterval( a, a )
        notReversed.forEach { assertFalse( it.isReversed ) }

        val reversed = createAllInclusionTypeIntervals( b, a )
        reversed.forEach { assertTrue( it.isReversed ) }
    }

    @Test
    fun lowerBound_and_upperBound()
    {
        val notReversed = createAllInclusionTypeIntervals( a, b )
        notReversed.forEach {
            assertEquals( a, it.lowerBound )
            assertEquals( it.isStartIncluded, it.isLowerBoundIncluded )
            assertEquals( b, it.upperBound )
            assertEquals( it.isEndIncluded, it.isUpperBoundIncluded )
        }

        val reversed = createAllInclusionTypeIntervals( b, a )
        reversed.forEach {
            assertEquals( a, it.lowerBound )
            assertEquals( it.isEndIncluded, it.isLowerBoundIncluded )
            assertEquals( b, it.upperBound )
            assertEquals( it.isStartIncluded, it.isUpperBoundIncluded )
        }

        val single = createClosedInterval( a, a )
        assertEquals( a, single.lowerBound )
        assertEquals( a, single.upperBound )
        assertTrue( single.isLowerBoundIncluded )
        assertTrue( single.isUpperBoundIncluded )
    }

    @Test
    fun size_for_normal_and_reverse_intervals_is_the_same()
    {
        val abIntervals = createAllInclusionTypeIntervals( a, b ) + createAllInclusionTypeIntervals( b, a )
        abIntervals.forEach { assertEquals( abSize, it.size ) }
    }

    @Test
    fun size_for_empty_interval_is_zero()
    {
        val zero = sizeOperations.additiveIdentity
        val emptyInterval = createClosedInterval( a, a )
        assertEquals( zero, emptyInterval.size )
    }

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

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

    @Test
    fun getBounds_returns_canonicalized_interval()
    {
        createAllInclusionTypeIntervals( a, b )
            .forEach { assertEquals( it.canonicalize(), it.getBounds() ) }
    }

    @Test
    fun contains_for_values_within_interval()
    {
        val acIntervals = createAllInclusionTypeIntervals( a, c ) + createAllInclusionTypeIntervals( c, a )
        acIntervals.forEach { assertTrue( b in it ) }
    }

    @Test
    fun contains_for_values_on_endpoints_of_half_open_intervals()
    {
        val onlyAIncluded = listOf(
            createInterval( a, true, b, false ),
            createInterval( b, false, a, true )
        )
        onlyAIncluded.forEach { assertTrue(a in it && b !in it ) }
    }

    @Test
    fun contains_for_values_on_endpoints_of_open_intervals()
    {
        val openIntervals = listOf( createOpenInterval( a, b ), createOpenInterval( b, a ) )
        openIntervals.forEach { assertTrue( a !in it && b !in it ) }
    }

    @Test
    fun minus_for_interval_lying_within()
    {
        val adIntervals = createAllInclusionTypeIntervals( a, d )
        val bcIntervals = createAllInclusionTypeIntervals( b, c )

        for ( ad in adIntervals ) for ( bc in bcIntervals )
        {
            val expected = setOf(
                createInterval( a, ad.isStartIncluded, b, !bc.isStartIncluded ),
                createInterval( c, !bc.isEndIncluded, d, ad.isEndIncluded )
            )
            assertUnionEquals( expected, ad - bc )
        }
    }

    @Test
    fun minus_for_partial_overlapping_interval()
    {
        val acIntervals = createAllInclusionTypeIntervals( a, c )
        val bdIntervals = createAllInclusionTypeIntervals( b, d )

        for ( ac in acIntervals ) for ( bd in bdIntervals )
        {
            assertEquals(
                createInterval( a, ac.isStartIncluded, b, !bd.isStartIncluded ),
                ac - bd
            )
        }
    }

    @Test
    fun minus_for_matching_interval()
    {
        val abIntervals = createAllInclusionTypeIntervals( a, b )

        for ( ab in abIntervals )
            assertTrue( (ab - ab).isEmpty() )
    }

    @Test
    fun minus_for_encompassing_interval()
    {
        val bcIntervals = createAllInclusionTypeIntervals( b, c )
        val adIntervals = createAllInclusionTypeIntervals( a, d )

        for ( bc in bcIntervals ) for ( ad in adIntervals )
            assertTrue( (bc - ad).isEmpty() )
    }

    @Test
    fun minus_for_nonoverlapping_interval()
    {
        val abIntervals = createAllInclusionTypeIntervals( a, b )
        val cdIntervals = createAllInclusionTypeIntervals( c, d )

        for ( ab in abIntervals ) for ( cd in cdIntervals )
            assertEquals( ab, (ab - cd).singleOrNull() )
    }

    @Test
    fun minus_for_overlapping_intervals_with_touching_endpoints()
    {
        val abWithA = createClosedInterval( a, b )
        val abWithoutA = createInterval( a, false, b, true )
        assertEquals(
            createClosedInterval( a, a ),
            (abWithA - abWithoutA).singleOrNull()
        )

        val abWithB = createClosedInterval( a, b )
        val abWithoutB = createInterval ( a, true, b, false )
        assertEquals(
            createClosedInterval( b, b ),
            (abWithB - abWithoutB).singleOrNull()
        )

        val abClosed = createClosedInterval( a, b )
        val abOpen = createOpenInterval( a, b )
        assertEquals(
            setOf( createClosedInterval( a, a ), createClosedInterval( b, b ) ),
            (abClosed - abOpen).toSet()
        )
    }

    @Test
    fun minus_for_neighbouring_interval_with_touching_endpoints()
    {
        val abWithB = createClosedInterval( a, b )
        val bcWithB = createClosedInterval( b, c )
        assertEquals(
            createInterval( a, true, b, false ),
            (abWithB - bcWithB)
        )

        val bcWithoutB = createOpenInterval( b, c )
        assertEquals( abWithB, (abWithB - bcWithoutB).singleOrNull() )
    }

    @Test
    fun plus_for_interval_lying_within()
    {
        val adIntervals = createAllInclusionTypeIntervals( a, d )
        val bcIntervals = createAllInclusionTypeIntervals( b, c )

        for ( ad in adIntervals ) for ( bc in bcIntervals )
            assertEquals( ad, (ad + bc).singleOrNull() )
    }

    @Test
    fun plus_for_partial_overlapping_interval()
    {
        val acIntervals = createAllInclusionTypeIntervals( a, c )
        val bdIntervals = createAllInclusionTypeIntervals( b, d )

        for ( ac in acIntervals ) for ( bd in bdIntervals )
        {
            assertEquals(
                createInterval( a, ac.isStartIncluded, d, bd.isEndIncluded ),
                (ac + bd).singleOrNull()
            )
        }
    }

    @Test
    fun plus_for_partial_overlapping_reversed_interval()
    {
        val acIntervals = createAllInclusionTypeIntervals( a, c ).map { it.reverse() }
        val bdIntervals = createAllInclusionTypeIntervals( b, d ).map { it.reverse() }

        for ( ac in acIntervals ) for ( bd in bdIntervals )
        {
            assertEquals(
                createInterval( a, ac.isLowerBoundIncluded, d, bd.isUpperBoundIncluded ),
                (ac + bd).singleOrNull()
            )
        }
    }

    @Test
    fun plus_for_matching_interval()
    {
        val abIntervals = createAllInclusionTypeIntervals( a, b )

        for ( ab in abIntervals )
            assertEquals( ab, (ab + ab).singleOrNull() )
    }

    @Test
    fun plus_for_encompassing_interval()
    {
        val bcIntervals = createAllInclusionTypeIntervals( b, c )
        val adIntervals = createAllInclusionTypeIntervals( a, d )

        for ( bc in bcIntervals ) for ( ad in adIntervals )
            assertEquals( ad, (bc + ad).singleOrNull() )
    }

    @Test
    fun plus_for_nonoverlapping_interval()
    {
        val abIntervals = createAllInclusionTypeIntervals( a, b )
        val cdIntervals = createAllInclusionTypeIntervals( c, d )

        for ( ab in abIntervals ) for ( cd in cdIntervals )
        {
            assertUnionEquals( setOf( ab, cd ), ab + cd )
            assertUnionEquals( setOf( ab, cd ), cd + ab )
        }
    }

    @Test
    fun plus_for_overlapping_intervals_with_touching_endpoints()
    {
        val abWithA = createClosedInterval( a, b )
        val abWithoutA = createInterval( a, false, b, true )
        assertEquals( abWithA, (abWithA + abWithoutA).singleOrNull() )

        val abWithB = createClosedInterval( a, b )
        val abWithoutB = createInterval ( a, true, b, false )
        assertEquals( abWithB, (abWithB + abWithoutB).singleOrNull() )

        val abClosed = createClosedInterval( a, b )
        val abOpen = createOpenInterval( a, b )
        assertEquals( abClosed, (abClosed + abOpen).singleOrNull() )
    }

    @Test
    fun plus_for_neighbouring_interval_with_touching_endpoints()
    {
        val abWithB = createClosedInterval( a, b )
        val bcWithB = createClosedInterval( b, c )
        assertEquals(
            createClosedInterval( a, c ),
            (abWithB + bcWithB).singleOrNull()
        )

        val bcWithoutB = createOpenInterval( b, c )
        assertEquals(
            createInterval( a, true, c, false ),
            (abWithB + bcWithoutB).singleOrNull()
        )
    }

    @Test
    fun plus_for_adjacent_intervals()
    {
        // Don't test non-evenly-spaced types.
        val spacing = valueOperations.spacing ?: return

        val ab = createClosedInterval( a, b )
        val bNext = valueOperations.unsafeAdd( b, spacing )
        val bNextC = createClosedInterval( bNext, c )

        val expected = createClosedInterval( a, c )
        assertEquals( expected, ab + bNextC )
        assertEquals( expected, bNextC + ab )
    }

    @Test
    fun intersects_for_fully_contained_intervals()
    {
        val ad = createClosedInterval( a, d )

        val withinIntervals = createAllInclusionTypeIntervals( b, c )
        val onEndPointIntervals = createAllInclusionTypeIntervals( a, b ) + createAllInclusionTypeIntervals( c, d )
        (withinIntervals + onEndPointIntervals).forEach { assertIntersects( ad, it, true ) }
    }

    @Test
    fun intersects_for_partial_overlapping_interval()
    {
        val acIntervals = createAllInclusionTypeIntervals( a, c )
        val bdIntervals = createAllInclusionTypeIntervals( b, d )

        for ( ac in acIntervals ) for ( bd in bdIntervals )
            assertIntersects( ac, bd, true )
    }

    @Test
    fun intersects_for_nonoverlapping_intervals()
    {
        val abIntervals = createAllInclusionTypeIntervals( a, b )
        val cdIntervals = createAllInclusionTypeIntervals( c, d )

        for ( ab in abIntervals ) for ( cd in cdIntervals )
            assertIntersects( ab, cd, false )
    }

    @Test
    fun intersects_for_touching_endpoints()
    {
        val abWithB = createClosedInterval( a, b )
        val bcWithB = createClosedInterval( b, c )
        assertIntersects( abWithB, bcWithB, true )

        val abWithoutB = createOpenInterval( a, b )
        val bcWithoutB = createOpenInterval( b, c )
        assertIntersects( abWithoutB, bcWithoutB, false )

        assertIntersects( abWithB, bcWithoutB, false )
        assertIntersects( abWithoutB, bcWithB, false )
    }

    @Test
    fun nonReversed_reversed_when_isReversed()
    {
        val reversed = createAllInclusionTypeIntervals( b, a )
        for ( original in reversed )
        {
            val normal = original.nonReversed()
            assertEquals( original.reverse(), normal )
        }
    }

    @Test
    fun nonReversed_returns_same_instance_when_not_isReversed()
    {
        val normal = createAllInclusionTypeIntervals( a, b )
        for ( original in normal )
        {
            val unchanged = original.nonReversed()
            assertSame( original, unchanged )
        }
    }

    @Test
    fun reverse_succeeds()
    {
        val toReverse = createAllInclusionTypeIntervals( a, b )
        for ( original in toReverse )
        {
            val reversed = original.reverse()
            assertEquals( original.start, reversed.end )
            assertEquals( original.isStartIncluded, reversed.isEndIncluded )
            assertEquals( original.end, reversed.start )
            assertEquals( original.isEndIncluded, reversed.isStartIncluded )
        }
    }

    @Test
    fun canonicalize_returns_same_instance_if_already_canonical()
    {
        // For evenly-spaced types, only closed intervals are canonical.
        val canonicalIntervals =
            if ( valueOperations.spacing == null ) createAllInclusionTypeIntervals( a, b )
            else listOf( createClosedInterval( a, b ) )

        canonicalIntervals.forEach { assertSame( it, it.canonicalize() ) }
    }

    @Test
    fun canonicalize_reverses_interval_if_reversed()
    {
        // For evenly-spaced types, excluded bounds would also be canonicalized.
        val reversedIntervals =
            if ( valueOperations.spacing == null ) createAllInclusionTypeIntervals( b, a )
            else listOf( createClosedInterval( b, a ) )

        reversedIntervals.forEach { assertEquals( it.reverse(), it.canonicalize() ) }
    }

    @Test
    fun canonicalize_makes_exclusive_bounds_inclusive()
    {
        // Don't test non-evenly-spaced types.
        val spacing = valueOperations.spacing ?: return

        val bPrev = valueOperations.unsafeSubtract( b, spacing )
        val aNext = valueOperations.unsafeAdd( a, spacing )

        val bExclusive = createInterval( a, true, b, false )
        assertEquals( createClosedInterval( a, bPrev ), bExclusive.canonicalize() )

        val aExclusive = createInterval( a, false, b, true )
        assertEquals( createClosedInterval( aNext, b ), aExclusive.canonicalize() )

        val abExclusive = createOpenInterval( a, b )
        assertEquals( createClosedInterval( aNext, bPrev ), abExclusive.canonicalize() )
    }

    @Test
    fun setEquals_returns_true_for_set_with_same_bounds()
    {
        createAllInclusionTypeIntervals( a, b ).forEach {
            val exact = createInterval( it.start, it.isStartIncluded, it.end, it.isEndIncluded )
            assertTrue( it.setEquals( exact ) )

            val reverse = it.reverse()
            assertTrue( it.setEquals( reverse ) )
        }
    }

    @Test
    fun setEquals_returns_false_for_differing_sets()
    {
        val abIntervals = createAllInclusionTypeIntervals( a, b )
        val acIntervals = createAllInclusionTypeIntervals( a, c )
        for ( ab in abIntervals ) for ( ac in acIntervals )
            assertFalse( ab.setEquals( ac ) )
    }

    @Test
    fun setEquals_for_evenly_spaced_types_with_differing_bounds_can_still_equal()
    {
        // Don't test non-evenly-spaced types.
        val spacing = valueOperations.spacing ?: return

        val bNext = valueOperations.unsafeAdd( b, spacing )
        val bPrev = valueOperations.unsafeSubtract( b, spacing )

        // [a, b] == [a, bNext)
        val bEndInclusive = createClosedInterval( a, b )
        val bNextExclusive = createInterval( a, true, bNext, false )
        assertTrue( bEndInclusive.setEquals( bNextExclusive ) )

        // [a, b) != [a, bNext)
        val bEndExclusive = createInterval( a, true, b, false )
        assertFalse( bEndExclusive.setEquals( bNextExclusive ) )

        // [b, c] == (bPrev, c]
        val bStartInclusive = createClosedInterval( b, c )
        val bPrevExclusive = createInterval( bPrev, false, c, true )
        assertTrue( bStartInclusive.setEquals( bPrevExclusive ) )

        // (b, c] != (bPrev, c]
        val bStartExclusive = createInterval( b, false, c, true )
        assertFalse( bStartExclusive.setEquals( bPrevExclusive ) )

        // [b, b] == (bPrev, bNext)
        val justB = createClosedInterval( b, b )
        val bNextPrevExclusive = createInterval( bPrev, false, bNext, false )
        assertTrue( justB.setEquals( bNextPrevExclusive ) )
    }

    @Test
    fun toString_matches_common_math_notation()
    {
        val closed = createClosedInterval( a, b )
        assertEquals( "[$a, $b]", closed.toString() )

        val open = createOpenInterval( a, b )
        assertEquals( "($a, $b)", open.toString() )

        val leftHalfOpen = createInterval( a, false, b, true )
        assertEquals( "($a, $b]", leftHalfOpen.toString() )

        val rightHalfOpen = createInterval( a, true, b, false )
        assertEquals( "[$a, $b)", rightHalfOpen.toString() )
    }

    private fun assertIntersects( interval1: Interval, interval2: Interval, intersects: Boolean )
    {
        assertEquals( intersects, interval1.intersects( interval2 ) )
        assertEquals( intersects, interval2.intersects( interval1 ) )

        // Reversing intervals should have no effect on whether they intersect or not.
        val interval1Reversed = interval1.reverse()
        val interval2Reversed = interval2.reverse()

        assertEquals( intersects, interval1.intersects( interval2Reversed ) )
        assertEquals( intersects, interval2Reversed.intersects( interval1 ) )

        assertEquals( intersects, interval1Reversed.intersects( interval2 ) )
        assertEquals( intersects, interval2.intersects( interval1Reversed ) )

        assertEquals( intersects, interval1Reversed.intersects( interval2Reversed ) )
        assertEquals( intersects, interval2Reversed.intersects( interval1Reversed ) )
    }

    /**
     * Used to compare unions with multiple intervals.
     */
    private fun assertUnionEquals( expected: Set>, actual: IntervalUnion )
    {
        require( expected.size > 1 )
            { "This comparison should only be used when multiple intervals are expected." }

        assertEquals( expected, actual.toSet() )
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy