diff --git a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/Interval.kt b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/Interval.kt index ca544c6cff..b27d497765 100644 --- a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/Interval.kt +++ b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/Interval.kt @@ -120,8 +120,8 @@ data class Interval( fun compareEnds(other: Interval): Int { val timeComparison: Int = end.compareTo(other.end) return if (timeComparison != 0) timeComparison - else if (startInclusivity == other.startInclusivity) 0 - else if (startInclusivity == Inclusivity.Inclusive) 1 + else if (endInclusivity == other.endInclusivity) 0 + else if (endInclusivity == Inclusivity.Inclusive) 1 else -1 } diff --git a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/GeneralOps.kt b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/GeneralOps.kt index a900e146f9..d0cf69a844 100644 --- a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/GeneralOps.kt +++ b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/GeneralOps.kt @@ -148,7 +148,7 @@ interface GeneralOps, THIS: GeneralOps>: Timeline val mapped = collect(opts.transformBounds(boundsTransformer)).flatMap { val nested = f(it) - nested.value.collect(CollectOptions(nested.interval)) + nested.value.collect(nested.interval) } if (truncate) truncateList(mapped, opts) else mapped diff --git a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/SerialConstantOps.kt b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/SerialConstantOps.kt index feb139ef8d..ba12f6906b 100644 --- a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/SerialConstantOps.kt +++ b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/SerialConstantOps.kt @@ -15,7 +15,7 @@ interface SerialConstantOps>: SerialOps /** [(DOC)][notEqualTo] Returns a [Windows] that is `true` when this and another profile are not equal. */ fun > notEqualTo(other: OTHER) = - map2Values(::Windows, other, BinaryOperation.combineOrNull { l, r, _ -> l == r }) + map2Values(::Windows, other, BinaryOperation.combineOrNull { l, r, _ -> l != r }) override fun changes() = detectEdges(BinaryOperation.combineOrNull { l, r, _-> l != r }) diff --git a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/SerialOps.kt b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/SerialOps.kt index 55a8fe5a36..3c5ea9e7ba 100644 --- a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/SerialOps.kt +++ b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/SerialOps.kt @@ -1,7 +1,9 @@ package gov.nasa.jpl.aerie.timeline.ops import gov.nasa.jpl.aerie.timeline.* +import gov.nasa.jpl.aerie.timeline.Interval.Companion.at import gov.nasa.jpl.aerie.timeline.collections.profiles.Windows +import gov.nasa.jpl.aerie.timeline.util.coalesceList import gov.nasa.jpl.aerie.timeline.util.map2Serial import gov.nasa.jpl.aerie.timeline.util.truncateList @@ -83,7 +85,10 @@ interface SerialOps>: SegmentOps { * @return a coalesced flattened profile; an instance of the return type of [ctor] */ fun , R: Any, NESTED: SerialOps, RESULT: GeneralOps, RESULT>> flatMap2Values(ctor: (Timeline, RESULT>) -> RESULT, other: SerialOps, op: BinaryOperation) = - unsafeOperate(ctor) { opts -> map2Serial(collect(opts), other.collect(opts), op).flatMap { it.value.collect(CollectOptions(it.interval, true)) } } + unsafeOperate(ctor) { opts -> + map2Serial(collect(opts), other.collect(opts), op) + .flatMap { it.value.collect(CollectOptions(it.interval, true)) } + } /** * [(DOC)][detectEdges] Uses a [BinaryOperation] as a predicate to highlight edges between segments. @@ -105,34 +110,19 @@ interface SerialOps>: SegmentOps { var buffer: Segment? = null val result = collect(CollectOptions(bounds, false)) .flatMap { currentSegment -> - val leftEdge: Boolean? - val rightEdge: Boolean? - val previous = buffer buffer = currentSegment val currentInterval = currentSegment.interval - val leftEdgeInterval = Interval.at(currentInterval.start) - val rightEdgeInterval = Interval.at(currentInterval.end) + val leftEdgeInterval = at(currentInterval.start) + val rightEdgeInterval = at(currentInterval.end) - rightEdge = if (currentInterval.end.isEqualTo(bounds.end) && currentInterval.endInclusivity == bounds.endInclusivity) { - if (bounds.includesEnd()) false else null - } else { - edgePredicate.invoke(currentSegment.value, null, rightEdgeInterval) - } + val rightEdge = edgePredicate(currentSegment.value, null, rightEdgeInterval) - leftEdge = if (previous != null) { - if (previous.interval.compareEndToStart(currentInterval) == 0) { - edgePredicate.invoke(previous.value, currentSegment.value, leftEdgeInterval) - } else { - edgePredicate.invoke(null, currentSegment.value, leftEdgeInterval) - } + val leftEdge = if (previous == null || previous.interval.compareEndToStart(currentInterval) == -1) { + edgePredicate(null, currentSegment.value, leftEdgeInterval) } else { - if (currentInterval.start.isEqualTo(bounds.start) && currentInterval.startInclusivity == bounds.startInclusivity) { - if (bounds.includesStart()) false else null - } else { - edgePredicate.invoke(null, currentSegment.value, leftEdgeInterval) - } + edgePredicate(previous.value, currentSegment.value, leftEdgeInterval) } listOfNotNull( @@ -144,7 +134,7 @@ interface SerialOps>: SegmentOps { Segment(rightEdgeInterval, rightEdge).transpose() ) } - truncateList(result, opts) + truncateList(coalesceList(result, Segment::valueEquals), opts) } /** diff --git a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/util/LinearEquation.kt b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/util/LinearEquation.kt index 4275dd8199..c366b7d919 100644 --- a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/util/LinearEquation.kt +++ b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/util/LinearEquation.kt @@ -154,10 +154,9 @@ data class LinearEquation( } /***/ - override fun equals(other: Any?): Boolean { - return if (other !is LinearEquation) false - else initialValue == other.valueAt(initialTime) && rate == other.rate - } + override fun equals(other: Any?) = + if (other !is LinearEquation) false + else initialValue == other.valueAt(initialTime) && rate == other.rate /***/ override fun hashCode(): Int { diff --git a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/util/BoundList.kt b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/util/ListCollector.kt similarity index 100% rename from timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/util/BoundList.kt rename to timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/util/ListCollector.kt diff --git a/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/BoundsTransformerTest.kt b/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/BoundsTransformerTest.kt new file mode 100644 index 0000000000..1a8c158b5a --- /dev/null +++ b/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/BoundsTransformerTest.kt @@ -0,0 +1,18 @@ +package gov.nasa.jpl.aerie.timeline + +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration.seconds +import gov.nasa.jpl.aerie.timeline.Interval.Companion.between +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class BoundsTransformerTest { + @Test + fun shift() { + val transformer = BoundsTransformer.shift(seconds(1)) + + assertEquals( + between(seconds(-1), seconds(1)), + transformer(between(seconds(0), seconds(2))) + ) + } +} diff --git a/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/IntervalTest.kt b/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/IntervalTest.kt index 38637d8139..9dff1ef246 100644 --- a/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/IntervalTest.kt +++ b/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/IntervalTest.kt @@ -165,6 +165,7 @@ class IntervalTest { assertEquals(1, between(seconds(0), seconds(1)).compareEnds(at(seconds(0)))) assertEquals(-1, between(seconds(0), seconds(1), Exclusive).compareEnds(at(seconds(1)))) assertEquals(0, between(seconds(0), seconds(1)).compareEnds(at(seconds(1)))) + assertEquals(-1, betweenClosedOpen(seconds(0), seconds(1)).compareEnds(at(seconds(1)))) } @Test diff --git a/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/collections/profiles/NumbersTest.kt b/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/collections/profiles/NumbersTest.kt new file mode 100644 index 0000000000..d6d7f2ed35 --- /dev/null +++ b/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/collections/profiles/NumbersTest.kt @@ -0,0 +1,27 @@ +package gov.nasa.jpl.aerie.timeline.collections.profiles + +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration.seconds +import gov.nasa.jpl.aerie.timeline.Interval +import gov.nasa.jpl.aerie.timeline.Interval.Companion.between +import gov.nasa.jpl.aerie.timeline.Segment +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* + +class NumbersTest { + + @Test + fun plus() { + val four = Numbers(4) + val five = Numbers(Segment(between(Duration.ZERO, seconds(1)), 5)).assignGaps(Numbers(0)) + + assertIterableEquals( + listOf( + Segment(between(Duration.ZERO, seconds(1)), 9), + Segment(between(seconds(1), seconds(2), Interval.Inclusivity.Exclusive, Interval.Inclusivity.Inclusive), 4) + ), + four.plus(five).collect(between(Duration.ZERO, seconds(2))) + ) + } +} diff --git a/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/ops/GeneralOpsTest.kt b/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/ops/GeneralOpsTest.kt new file mode 100644 index 0000000000..96cc5eaf0c --- /dev/null +++ b/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/ops/GeneralOpsTest.kt @@ -0,0 +1,213 @@ +package gov.nasa.jpl.aerie.timeline.ops + +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration.milliseconds +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration.seconds +import gov.nasa.jpl.aerie.timeline.* +import gov.nasa.jpl.aerie.timeline.Interval.Companion.at +import gov.nasa.jpl.aerie.timeline.Interval.Companion.between +import gov.nasa.jpl.aerie.timeline.Interval.Companion.betweenClosedOpen +import gov.nasa.jpl.aerie.timeline.collections.Intervals +import gov.nasa.jpl.aerie.timeline.collections.profiles.Discrete +import gov.nasa.jpl.aerie.timeline.collections.profiles.Numbers +import gov.nasa.jpl.aerie.timeline.collections.profiles.Windows +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class GeneralOpsTest { + + @Test + fun operate() { + val result = Discrete(Segment(between(seconds(0), seconds(1)), "hello")).unsafeOperate { + collect(it).map { s -> Segment(s.interval, s.value + " world")} + }.collect() + + val expected = listOf(Segment(between(seconds(0), seconds(1)), "hello world")) + + assertIterableEquals(expected, result) + } + + @Test + fun operateAutoCoalesce() { + val result = Discrete( + Segment(between(seconds(0), seconds(1)), "hello world"), + Segment(between(seconds(1), seconds(2)), "hello there") + ).unsafeOperate { + collect(it).map { s -> Segment(s.interval, s.value.substring(0..4))} + }.collect() + + val expected = listOf(Segment(between(seconds(0), seconds(2)), "hello")) + + assertIterableEquals(expected, result) + } + + @Test + fun operateType() { + // this is just a test to make sure the return type of unsafeOperate is correct. + // we would get a compile error if it failed. + @Suppress("UNUSED_VARIABLE") + val result: Windows = Windows(true).unsafeOperate { collect(it) } + } + + @Test + fun inspect() { + var count: Int? = null + val tl = Intervals( + at(seconds(1)), + at(seconds(2)) + ).inspect { + count = it.size + } + + assertNull(count) + + tl.collect() + + assertEquals(2, count) + } + + @Test + fun unset() { + val result = Intervals( + between(seconds(0), seconds(2)), + between(seconds(2), seconds(3)), + between(seconds(3), seconds(5)), + between(seconds(10), seconds(11)) + ).unset(between(seconds(1), seconds(4))).collect() + + assertIterableEquals( + listOf( + betweenClosedOpen(seconds(0), seconds(1)), + between(seconds(4), seconds(5), Interval.Inclusivity.Exclusive, Interval.Inclusivity.Inclusive), + between(seconds(10), seconds(11)) + ), + result + ) + } + + @Test + fun filter() { + val result = Numbers( + Segment(at(seconds(1)), 4), + Segment(at(seconds(2)), 5) + ).filter { it.value.toInt() % 2 == 0 }.collect() + + assertIterableEquals( + listOf(Segment(at(seconds(1)), 4)), + result + ) + } + + @Test + fun filterPreserveMargin() { + val intervals = Intervals( + between(seconds(-1), seconds(1)), + between(seconds(1), seconds(4)), + between(seconds(4), seconds(8)), + ) + + // without preserve margin + assertIterableEquals( + listOf(between(seconds(1), seconds(4))), + intervals.filter(false) { it.duration().noShorterThan(Duration.of(2, Duration.SECOND)) } + .collect(between(seconds(0), seconds(5))) + ) + + // with preserve margin and truncate margin + // notice that the marginal intervals are retained and then later truncated to within the bounds + assertIterableEquals( + listOf( + between(seconds(0), seconds(1)), + between(seconds(1), seconds(4)), + between(seconds(4), seconds(5)), + ), + intervals.filter(true) { it.duration().noShorterThan(Duration.of(2, Duration.SECOND)) } + .collect(between(seconds(0), seconds(5))) + ) + + // with preserve margin, without truncate margin + // notice that the marginal intervals are retained and NOT truncated later + assertIterableEquals( + intervals.collect(), + intervals.filter(true) { it.duration().noShorterThan(Duration.of(2, Duration.SECOND)) } + .collect(CollectOptions(between(seconds(0), seconds(5)), false)) + ) + } + + @Test + fun map() { + val result = Intervals( + at(seconds(1)), + between(seconds(2), seconds(3)) + ).unsafeMap(::Windows, BoundsTransformer.IDENTITY, false) { Segment(it.interval, it.interval.isPoint()) } + .collect() + + assertIterableEquals( + listOf( + Segment(at(seconds(1)), true), + Segment(between(seconds(2), seconds(3)), false), + ), + result + ) + } + + @Test + fun shiftBoundsTransform() { + val intervals = Intervals( + between(seconds(-1), seconds(0)), + between(seconds(2), seconds(4)), + ).shift(Duration.SECOND) + + val expected = listOf( + between(seconds(0), seconds(1)), + between(seconds(3), seconds(5)) + ) + + assertIterableEquals( + expected, + intervals.collect() + ) + + assertIterableEquals( + expected, + intervals.collect(between(seconds(0), seconds(5))) + ) + } + + @Test + fun shiftOutOfBounds() { + val intervals = Intervals(at(seconds(3))).shift(seconds(3)) + + assertIterableEquals( + listOf(), + intervals.collect(between(seconds(0), seconds(5))) + ) + } + + @Test + fun flatMapTest() { + val result = Intervals( + between(seconds(2), seconds(8)), + between(seconds(0), seconds(3)) + ) + // converts each interval to a windows object. + // false for the first half of the interval, true for the second half + .unsafeFlatMap(::Windows, BoundsTransformer.IDENTITY, false) { + val midpoint = it.interval.start.plus(it.interval.end).dividedBy(2) + Segment( + it.interval.interval, + Windows(false).set(Windows(Segment(between(midpoint, Duration.MAX_VALUE), true))) + ) + } + .collect() + + val expected = listOf( + Segment(betweenClosedOpen(seconds(0), milliseconds(1500)), false), + Segment(betweenClosedOpen(milliseconds(1500), seconds(2)), true), + Segment(betweenClosedOpen(seconds(2), seconds(5)), false), + Segment(between(seconds(5), seconds(8)), true) + ) + + assertIterableEquals(expected, result) + } +} diff --git a/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/ops/SerialOpsTest.kt b/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/ops/SerialOpsTest.kt new file mode 100644 index 0000000000..6f6434eefd --- /dev/null +++ b/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/ops/SerialOpsTest.kt @@ -0,0 +1,51 @@ +package gov.nasa.jpl.aerie.timeline.ops + +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration.milliseconds +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration.seconds +import gov.nasa.jpl.aerie.timeline.BinaryOperation +import gov.nasa.jpl.aerie.timeline.Interval +import gov.nasa.jpl.aerie.timeline.Interval.Companion.at +import gov.nasa.jpl.aerie.timeline.Interval.Companion.between +import gov.nasa.jpl.aerie.timeline.Interval.Companion.betweenClosedOpen +import gov.nasa.jpl.aerie.timeline.Segment +import gov.nasa.jpl.aerie.timeline.collections.profiles.Discrete +import org.junit.jupiter.api.Assertions.assertIterableEquals +import org.junit.jupiter.api.Test + +class SerialOpsTest { + + // set, assignGaps, and map2Values are not tested here because they are trivial delegations to map2Serial. + // see Map2SerialTest.kt + + @Test + fun detectEdges() { + val result = Discrete( + Segment(betweenClosedOpen(seconds(0), seconds(1)), "hello"), + Segment(between(seconds(1), seconds(2)), "oooo"), + Segment(between(seconds(2), seconds(3), Interval.Inclusivity.Exclusive), "aaaa"), + Segment(between(seconds(5), seconds(6)), "ao") + ).detectEdges(BinaryOperation.cases( + { l, _ -> l.endsWith('o') }, + { r, _ -> r.startsWith('o') }, + { l, r, _ -> l.endsWith(r.first()) } + )) + + assertIterableEquals( + listOf( + Segment(betweenClosedOpen(seconds(0), seconds(1)), false), + Segment(at(seconds(1)), true), + Segment(between(seconds(1), seconds(3), Interval.Inclusivity.Exclusive, Interval.Inclusivity.Inclusive), false), + Segment(betweenClosedOpen(seconds(5), seconds(6)), false), + Segment(at(seconds(6)), true) + ), + result.collect() + ) + + // collecting on smaller bounds that start in the middle of "oooo" to make sure it does not get truncated + // and count the bounds start as the segment start. + assertIterableEquals( + listOf(Segment(between(milliseconds(1500), seconds(3)), false)), + result.collect(between(milliseconds(1500), seconds(4))) + ) + } +} diff --git a/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/ops/coalesce/CoalesceSegmentsTest.kt b/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/ops/coalesce/CoalesceSegmentsTest.kt deleted file mode 100644 index e3e2c9cfee..0000000000 --- a/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/ops/coalesce/CoalesceSegmentsTest.kt +++ /dev/null @@ -1,28 +0,0 @@ -package gov.nasa.jpl.aerie.timeline.ops.coalesce - -import gov.nasa.jpl.aerie.timeline.Interval -import gov.nasa.jpl.aerie.timeline.Interval.Inclusivity.* -import gov.nasa.jpl.aerie.timeline.Segment -import gov.nasa.jpl.aerie.timeline.dur -import gov.nasa.jpl.aerie.timeline.util.coalesceList -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Test - -val bounds = Interval.between(dur(0), dur(3)) - -class CoalesceSegmentsTest { - - @Test - fun isNoopOnCoalescedProfile() { - fun supplier() = listOf( - Segment(Interval.between(dur(0), dur(1), Inclusive, Exclusive), false), - Segment(Interval.between(dur(1), dur(2), Inclusive, Inclusive), true), - Segment(Interval.between(dur(2), dur(3), Exclusive, Inclusive), false), - ) - - val expected = supplier() - val result = coalesceList(supplier(), Segment::valueEquals) - - assertIterableEquals(expected, result) - } -} diff --git a/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/util/CoalesceTest.kt b/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/util/CoalesceTest.kt new file mode 100644 index 0000000000..0f05c2b63a --- /dev/null +++ b/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/util/CoalesceTest.kt @@ -0,0 +1,38 @@ +package gov.nasa.jpl.aerie.timeline.util + +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration.seconds +import gov.nasa.jpl.aerie.timeline.Interval.Companion.at +import gov.nasa.jpl.aerie.timeline.Interval.Companion.between +import gov.nasa.jpl.aerie.timeline.Interval.Inclusivity.* +import gov.nasa.jpl.aerie.timeline.Segment +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class CoalesceTest { + + @Test + fun isNoopOnAlreadyCoalescedList() { + fun supplier() = listOf( + Segment(between(seconds(0), seconds(1), Inclusive, Exclusive), false), + Segment(between(seconds(1), seconds(2), Inclusive, Inclusive), true), + Segment(between(seconds(2), seconds(3), Exclusive, Inclusive), false), + ) + + val expected = supplier() + val result = coalesceList(supplier(), Segment::valueEquals) + + assertIterableEquals(expected, result) + } + + @Test + fun removeEmptySegment() { + val result = coalesceList(listOf( + Segment(at(seconds(1)), false), + Segment(between(seconds(1), seconds(2)), true) + ), Segment::valueEquals) + + val expected = listOf(Segment(between(seconds(1), seconds(2)), true)) + + assertIterableEquals(expected, result) + } +} diff --git a/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/util/LinearEquationTest.kt b/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/util/LinearEquationTest.kt new file mode 100644 index 0000000000..53ca5d7644 --- /dev/null +++ b/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/util/LinearEquationTest.kt @@ -0,0 +1,91 @@ +package gov.nasa.jpl.aerie.timeline.util + +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration.seconds +import gov.nasa.jpl.aerie.timeline.Interval.Companion.between +import gov.nasa.jpl.aerie.timeline.Interval.Companion.betweenClosedOpen +import gov.nasa.jpl.aerie.timeline.Segment +import gov.nasa.jpl.aerie.timeline.collections.profiles.Real +import gov.nasa.jpl.aerie.timeline.collections.profiles.Windows +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* + +class LinearEquationTest { + + @Test + fun shiftInitialTime() { + val original = LinearEquation(Duration.ZERO, 1.0, 1.0) + + val shifted = original.shiftInitialTime(Duration.SECOND) + + assertEquals( + LinearEquation(Duration.SECOND, 2.0, 1.0), + shifted + ) + } + + @Test + fun intervalsLessThan() { + assertIterableEquals( + Windows(true).collect(), + LinearEquation(1.0).intervalsLessThan(LinearEquation(2.0)).collect() + ) + + assertIterableEquals( + Windows(false).collect(), + LinearEquation(2.0).intervalsLessThan(LinearEquation(1.0)).collect() + ) + + assertIterableEquals( + Windows(Segment(between(Duration.ZERO, Duration.MAX_VALUE), false)).assignGaps(Windows(true)).collect(), + LinearEquation(Duration.ZERO, 0.0, 1.0).intervalsLessThan(LinearEquation(0.0)).collect() + ) + } + + @Test + fun findRoot() { + assertNull(LinearEquation(1.0).findRoot()) + + assertEquals( + seconds(2), + LinearEquation(Duration.ZERO, 2.0, -1.0).findRoot() + ) + } + + @Test + fun abs() { + assertIterableEquals( + Real(5.0).collect(), + LinearEquation(5.0).abs().collect() + ) + + assertIterableEquals( + Real(5.0).collect(), + LinearEquation(-5.0).abs().collect() + ) + + assertIterableEquals( + Real( + Segment(betweenClosedOpen(Duration.MIN_VALUE, Duration.SECOND), LinearEquation(Duration.SECOND, 0.0, -2.0)), + Segment(between(Duration.SECOND, Duration.MAX_VALUE), LinearEquation(Duration.SECOND, 0.0, 2.0)) + ).collect(), + LinearEquation(Duration.ZERO, 2.0, -2.0).abs().collect() + ) + } + + @Test + fun equals() { + assertTrue( + LinearEquation(5.0) == LinearEquation(Duration.SECOND, 5.0, 0.0) + ) + + assertTrue( + LinearEquation(Duration.ZERO, 0.0, 2.0) == LinearEquation(Duration.SECOND, 2.0, 2.0) + ) + + assertFalse( + LinearEquation(Duration.ZERO, 0.5, 2.0) == LinearEquation(Duration.SECOND, 2.0, 2.0) + ) + } +} diff --git a/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/util/BoundListTest.kt b/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/util/ListCollectorTest.kt similarity index 98% rename from timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/util/BoundListTest.kt rename to timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/util/ListCollectorTest.kt index ef2b9dccc6..c50fb09c75 100644 --- a/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/util/BoundListTest.kt +++ b/timeline/src/test/kotlin/gov/nasa/jpl/aerie/timeline/util/ListCollectorTest.kt @@ -9,7 +9,7 @@ import gov.nasa.jpl.aerie.timeline.collections.Intervals val bounds = between(seconds(0), seconds(10)) -class BoundListTest { +class ListCollectorTest { @Test fun isNoopOnArrayInBounds() { val input = listOf(