Skip to content

Commit

Permalink
Add unit tests
Browse files Browse the repository at this point in the history
Adds unit tests for a variety of classes in the streamline framework.

These unit tests were added as-needed to debug problems, and aren't meant to give comprehensive or systematic coverage.
  • Loading branch information
David Legg committed Dec 19, 2023
1 parent 404826e commit ec262c7
Show file tree
Hide file tree
Showing 10 changed files with 1,743 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package gov.nasa.jpl.aerie.contrib.streamline.core;

import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.Discrete;
import gov.nasa.jpl.aerie.merlin.framework.Registrar;
import gov.nasa.jpl.aerie.merlin.framework.junit.MerlinExtension;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.ExtendWith;

import static gov.nasa.jpl.aerie.contrib.streamline.core.CellRefV2.autoEffects;
import static gov.nasa.jpl.aerie.contrib.streamline.core.CellRefV2.commutingEffects;
import static gov.nasa.jpl.aerie.contrib.streamline.core.CellRefV2.noncommutingEffects;
import static gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource.resource;
import static gov.nasa.jpl.aerie.contrib.streamline.core.Resources.currentValue;
import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.Discrete.discrete;
import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.monads.DiscreteDynamicsMonad.effect;
import static gov.nasa.jpl.aerie.merlin.framework.ModelActions.delay;
import static gov.nasa.jpl.aerie.merlin.framework.ModelActions.spawn;
import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.ZERO;
import static org.junit.jupiter.api.Assertions.*;

class MutableResourceTest {
@Nested
@ExtendWith(MerlinExtension.class)
@TestInstance(Lifecycle.PER_CLASS)
class NonCommutingEffects {
public NonCommutingEffects(final Registrar registrar) {
Resources.init();
}

private final MutableResource<Discrete<Integer>> cell = MutableResource.resource(discrete(42), noncommutingEffects());

@Test
void gets_initial_value_if_no_effects_are_emitted() {
assertEquals(42, currentValue(cell));
}

@Test
void applies_singleton_effect() {
int initialValue = currentValue(cell);
cell.emit(effect(n -> 3 * n));
assertEquals(3 * initialValue, currentValue(cell));
}

@Test
void applies_sequential_effects_in_order() {
int initialValue = currentValue(cell);
cell.emit(effect(n -> 3 * n));
cell.emit(effect(n -> n + 1));
assertEquals(3 * initialValue + 1, currentValue(cell));
}

@Test
void throws_exception_when_concurrent_effects_are_applied() {
spawn(() -> cell.emit(effect(n -> 3 * n)));
spawn(() -> cell.emit(effect(n -> 3 * n)));
delay(ZERO);
assertInstanceOf(ErrorCatching.Failure.class, cell.getDynamics());
}
}

@Nested
@ExtendWith(MerlinExtension.class)
@TestInstance(Lifecycle.PER_CLASS)
class CommutingEffects {
public CommutingEffects(final Registrar registrar) {
Resources.init();
}

private final MutableResource<Discrete<Integer>> cell = MutableResource.resource(discrete(42), commutingEffects());

@Test
void gets_initial_value_if_no_effects_are_emitted() {
assertEquals(42, currentValue(cell));
}

@Test
void applies_singleton_effect() {
int initialValue = currentValue(cell);
cell.emit(effect(n -> 3 * n));
assertEquals(3 * initialValue, currentValue(cell));
}

@Test
void applies_sequential_effects_in_order() {
int initialValue = currentValue(cell);
cell.emit(effect(n -> 3 * n));
cell.emit(effect(n -> n + 1));
assertEquals(3 * initialValue + 1, currentValue(cell));
}

@Test
void applies_concurrent_effects_in_any_order() {
int initialValue = currentValue(cell);
// These effects do not in fact commute,
// but the point of the commutingEffects is that it *doesn't* check.
spawn(() -> cell.emit(effect(n -> 3 * n)));
spawn(() -> cell.emit(effect(n -> n + 1)));
delay(ZERO);
int result = currentValue(cell);
assertTrue(result == 3*initialValue + 1 || result == 3 * (initialValue + 1));
}
}

@Nested
@ExtendWith(MerlinExtension.class)
@TestInstance(Lifecycle.PER_CLASS)
class AutoEffects {
public AutoEffects(final Registrar registrar) {
Resources.init();
}

private final MutableResource<Discrete<Integer>> cell = MutableResource.resource(discrete(42), autoEffects());

@Test
void gets_initial_value_if_no_effects_are_emitted() {
assertEquals(42, currentValue(cell));
}

@Test
void applies_singleton_effect() {
int initialValue = currentValue(cell);
cell.emit(effect(n -> 3 * n));
assertEquals(3 * initialValue, currentValue(cell));
}

@Test
void applies_sequential_effects_in_order() {
int initialValue = currentValue(cell);
cell.emit(effect(n -> 3 * n));
cell.emit(effect(n -> n + 1));
assertEquals(3 * initialValue + 1, currentValue(cell));
}

@Test
void applies_commuting_concurrent_effects() {
int initialValue = currentValue(cell);
// These effects do not in fact commute,
// but the point of the commutingEffects is that it *doesn't* check.
spawn(() -> cell.emit(effect(n -> 3 * n)));
spawn(() -> cell.emit(effect(n -> 4 * n)));
delay(ZERO);
int result = currentValue(cell);
assertEquals(12 * initialValue, result);
}

@Test
void throws_exception_when_non_commuting_concurrent_effects_are_applied() {
spawn(() -> cell.emit(effect(n -> 3 * n)));
spawn(() -> cell.emit(effect(n -> n + 1)));
delay(ZERO);
assertInstanceOf(ErrorCatching.Failure.class, cell.getDynamics());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package gov.nasa.jpl.aerie.contrib.streamline.core;

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.Optional;

import static gov.nasa.jpl.aerie.contrib.streamline.core.Expiry.NEVER;
import static gov.nasa.jpl.aerie.contrib.streamline.core.Expiry.at;
import static gov.nasa.jpl.aerie.contrib.streamline.core.Expiry.expiry;
import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.HOUR;
import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.MINUTE;
import static org.junit.jupiter.api.Assertions.*;

class ExpiryTest {
@Nested
class Value {
@Test
void never_expiring_has_empty_value() {
assertEquals(Optional.empty(), NEVER.value());
}

@Test
void expiring_at_t_has_value_of_t() {
assertEquals(Optional.of(MINUTE), Expiry.at(MINUTE).value());
}
}

@Nested
class Equals {
@Test
void never_equals_never() {
assertEquals(NEVER, NEVER);
assertEquals(NEVER, expiry(Optional.empty()));
assertEquals(expiry(Optional.empty()), NEVER);
assertEquals(expiry(Optional.empty()), expiry(Optional.empty()));
}

@Test
void at_t_equals_at_t() {
assertEquals(at(MINUTE), at(MINUTE));
}

@Test
void at_t_does_not_equal_never() {
assertNotEquals(at(MINUTE), NEVER);
assertNotEquals(NEVER, at(MINUTE));
}

@Test
void at_t_does_not_equal_at_s() {
assertNotEquals(at(MINUTE), at(HOUR));
assertNotEquals(at(HOUR), at(MINUTE));
}
}

@Nested
class IsNever {
@Test
void never_is_never() {
assertTrue(NEVER.isNever());
}

@Test
void at_t_is_not_never() {
assertFalse(at(MINUTE).isNever());
}
}

@Nested
class Or {
@Test
void never_or_never_returns_never() {
assertEquals(NEVER, NEVER.or(NEVER));
}

@Test
void never_or_at_t_returns_at_t() {
assertEquals(at(MINUTE), NEVER.or(at(MINUTE)));
}

@Test
void at_t_or_never_returns_at_t() {
assertEquals(at(MINUTE), at(MINUTE).or(NEVER));
}

@Test
void at_t_or_at_greater_than_t_returns_at_t() {
assertEquals(at(MINUTE), at(MINUTE).or(at(HOUR)));
}

@Test
void at_greater_than_t_or_at_t_returns_at_t() {
assertEquals(at(MINUTE), at(HOUR).or(at(MINUTE)));
}
}

@Nested
class Minus {
@Test
void never_minus_t_returns_never() {
assertEquals(NEVER, NEVER.minus(MINUTE));
}

@Test
void at_t_minus_s_returns_at_difference() {
assertEquals(at(HOUR.minus(MINUTE)), at(HOUR).minus(MINUTE));
}
}

@Nested
class Comparisons {
@Test
void never_equals_never() {
assertFalse(NEVER.shorterThan(NEVER));
assertTrue(NEVER.noShorterThan(NEVER));
assertFalse(NEVER.longerThan(NEVER));
assertTrue(NEVER.noLongerThan(NEVER));
}

@Test
void at_t_equals_at_t() {
assertFalse(at(MINUTE).shorterThan(at(MINUTE)));
assertTrue(at(MINUTE).noShorterThan(at(MINUTE)));
assertFalse(at(MINUTE).longerThan(at(MINUTE)));
assertTrue(at(MINUTE).noLongerThan(at(MINUTE)));
}

@Test
void at_t_shorter_than_never() {
assertTrue(at(MINUTE).shorterThan(NEVER));
assertFalse(at(MINUTE).noShorterThan(NEVER));
assertFalse(at(MINUTE).longerThan(NEVER));
assertTrue(at(MINUTE).noLongerThan(NEVER));
}

@Test
void never_longer_than_at_t() {
assertFalse(NEVER.shorterThan(at(MINUTE)));
assertTrue(NEVER.noShorterThan(at(MINUTE)));
assertTrue(NEVER.longerThan(at(MINUTE)));
assertFalse(NEVER.noLongerThan(at(MINUTE)));
}

@Test
void at_t_shorter_than_at_greater_than_t() {
assertTrue(at(MINUTE).shorterThan(at(HOUR)));
assertFalse(at(MINUTE).noShorterThan(at(HOUR)));
assertFalse(at(MINUTE).longerThan(at(HOUR)));
assertTrue(at(MINUTE).noLongerThan(at(HOUR)));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package gov.nasa.jpl.aerie.contrib.streamline.debugging;

import gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource;
import gov.nasa.jpl.aerie.contrib.streamline.core.Resource;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.Discrete;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.DiscreteResources;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.Polynomial;
import gov.nasa.jpl.aerie.merlin.framework.junit.MerlinExtension;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;

import static gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource.resource;
import static gov.nasa.jpl.aerie.contrib.streamline.core.monads.ResourceMonad.*;
import static gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.Polynomial.polynomial;
import static gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.PolynomialResources.constant;
import static org.junit.jupiter.api.Assertions.*;

@Nested
@ExtendWith(MerlinExtension.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DependenciesTest {
Resource<Discrete<Boolean>> constantTrue = DiscreteResources.constant(true);
Resource<Polynomial> constant1234 = constant(1234);
Resource<Polynomial> constant5678 = constant(5678);
Resource<Polynomial> polynomialCell = resource(polynomial(1));
Resource<Polynomial> derived = map(constantTrue, constant1234, constant5678,
(b, x, y) -> b.extract() ? x : y);

@Test
void constants_are_named_by_their_value() {
assertTrue(Naming.getName(constantTrue).get().contains("true"));
assertTrue(Naming.getName(constant1234).get().contains("1234"));
assertTrue(Naming.getName(constant5678).get().contains("5678"));
}

@Test
void cell_resources_are_not_inherently_named() {
assertTrue(Naming.getName(polynomialCell).isEmpty());
}

@Test
void derived_resources_are_not_inherently_named() {
assertTrue(Naming.getName(derived).isEmpty());
}

@Test
void constants_have_no_dependencies() {
assertTrue(Dependencies.getDependencies(constantTrue).isEmpty());
assertTrue(Dependencies.getDependencies(constant1234).isEmpty());
assertTrue(Dependencies.getDependencies(constant5678).isEmpty());
}

@Test
void cell_resources_have_no_dependencies() {
assertTrue(Dependencies.getDependencies(polynomialCell).isEmpty());
}

@Test
void derived_resources_have_their_sources_as_dependencies() {
var graphDescription = Dependencies.describeDependencyGraph(derived, true);
assertTrue(graphDescription.contains("true"));
assertTrue(graphDescription.contains("1234"));
assertTrue(graphDescription.contains("5678"));
}
}
Loading

0 comments on commit ec262c7

Please sign in to comment.