/* * Copyright 2015-2016 Cel Skeggs * * This file is part of the CCRE, the Common Chicken Runtime Engine. * * The CCRE is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * The CCRE is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with the CCRE. If not, see <http://www.gnu.org/licenses/>. */ package ccre.channel; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.NoSuchElementException; import java.util.Random; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import ccre.log.LogLevel; import ccre.log.VerifyingLogger; import ccre.scheduler.VirtualTime; import ccre.testing.CountingEventOutput; import ccre.testing.CountingFloatOutput; import ccre.timers.Ticker; import ccre.util.Values; @SuppressWarnings("javadoc") public class FloatInputTest { private static final String ERROR_STRING = "Purposeful failure."; private CountingFloatOutput cfo; private FloatInput fi; private FloatCell fs, fs2; @BeforeClass public static void setUpClass() { VerifyingLogger.begin(); } @AfterClass public static void tearDownClass() { VerifyingLogger.checkAndEnd(); } @Before public void setUp() throws Exception { // For derivative testing. VirtualTime.startFakeTime(); cfo = new CountingFloatOutput(); fi = FloatInput.always(7.3f); fs = new FloatCell(); fs2 = new FloatCell(); } @After public void tearDown() throws Exception { cfo = null; fi = null; fs = fs2 = null; VirtualTime.endFakeTime(); } @Test public void testAlways() { for (float f : Values.interestingFloats) { FloatInput always = FloatInput.always(f); assertEquals(f, always.get(), 0); cfo.ifExpected = true; cfo.valueExpected = f; always.send(cfo); cfo.check(); } } @Test public void testZero() { FloatInput always = FloatInput.zero; assertEquals(0, always.get(), 0); cfo.ifExpected = true; cfo.valueExpected = 0; always.send(cfo); cfo.check(); } private boolean gotProperly; private float result; @Test public void testSend() { CountingEventOutput expected = new CountingEventOutput(); gotProperly = false; CancelOutput cex = expected::event; fi = new FloatInput() { @Override public CancelOutput onUpdate(EventOutput notify) { assertFalse(gotProperly); cfo.check();// the earlier setup from outside this for (float f : Values.interestingFloats) { result = f; cfo.ifExpected = true; cfo.valueExpected = f; notify.event(); cfo.check(); } gotProperly = true; return cex; } @Override public float get() { return result; } }; for (float f : Values.interestingFloats) { result = f; assertEquals(f, fi.get(), 0); } for (float initial : Values.interestingFloats) { cfo.ifExpected = true; cfo.valueExpected = result = initial; assertFalse(gotProperly); assertEquals(cex, fi.send(cfo)); assertTrue(gotProperly); gotProperly = false; cfo.check(); // the real check is in onUpdate above } } @Test(expected = NullPointerException.class) public void testSendNull() { fi.send(null); } private void tryEach(FloatInput fi, FloatInput fi2) { for (float f1 : Values.interestingFloats) { fs.set(f1); for (float f2 : Values.interestingFloats) { fs2.set(f2); assertEquals(fi.get(), fi2.get(), 0); } } } @Test public void testPlusFloatInput() { tryEach(fs.plus(fs2), FloatOperation.addition.of(fs.asInput(), fs2.asInput())); } @Test(expected = NullPointerException.class) public void testPlusNull() { fi.plus(null); } @Test public void testMinusFloatInput() { tryEach(fs.minus(fs2), FloatOperation.subtraction.of(fs.asInput(), fs2.asInput())); } @Test(expected = NullPointerException.class) public void testMinusNull() { fi.minus(null); } @Test public void testMinusRevFloatInput() { tryEach(fs.minusRev(fs2), FloatOperation.subtraction.of(fs2.asInput(), fs.asInput())); } @Test(expected = NullPointerException.class) public void testMinusRevNull() { fi.minusRev(null); } @Test public void testMultipliedByFloatInput() { tryEach(fs.multipliedBy(fs2), FloatOperation.multiplication.of(fs.asInput(), fs2.asInput())); } @Test(expected = NullPointerException.class) public void testMultipliedByNull() { fi.multipliedBy(null); } @Test public void testDividedByFloatInput() { tryEach(fs.dividedBy(fs2), FloatOperation.division.of(fs.asInput(), fs2.asInput())); } @Test(expected = NullPointerException.class) public void testDividedByNull() { fi.dividedBy(null); } @Test public void testDividedByRevFloatInput() { tryEach(fs.dividedByRev(fs2), FloatOperation.division.of(fs2.asInput(), fs.asInput())); } @Test(expected = NullPointerException.class) public void testDividedByRevNull() { fi.dividedByRev(null); } @Test public void testModuloFloatInput() { tryEach(fs.modulo(fs2), FloatOperation.modulation.of(fs.asInput(), fs2.asInput())); } @Test(expected = NullPointerException.class) public void testModuloNull() { fi.modulo(null); } @Test public void testModuloRevFloatInput() { tryEach(fs.moduloRev(fs2), FloatOperation.modulation.of(fs2.asInput(), fs.asInput())); } @Test(expected = NullPointerException.class) public void testModuloRevNull() { fi.moduloRev(null); } private void trySet(FloatInput fi, FloatInput fi2) { for (float f1 : Values.interestingFloats) { fs.set(f1); assertEquals(fi.get(), fi2.get(), 0); } } @Test public void testPlusFloat() { for (float f2 : Values.interestingFloats) { trySet(fs.plus(f2), FloatOperation.addition.of(fs.asInput(), f2)); } } @Test public void testMinusFloat() { for (float f2 : Values.interestingFloats) { trySet(fs.minus(f2), FloatOperation.subtraction.of(fs.asInput(), f2)); } } @Test public void testMinusRevFloat() { for (float f2 : Values.interestingFloats) { trySet(fs.minusRev(f2), FloatOperation.subtraction.of(f2, fs.asInput())); } } @Test public void testMultipliedByFloat() { for (float f2 : Values.interestingFloats) { trySet(fs.multipliedBy(f2), FloatOperation.multiplication.of(fs.asInput(), f2)); } } @Test public void testDividedByFloat() { for (float f2 : Values.interestingFloats) { trySet(fs.dividedBy(f2), FloatOperation.division.of(fs.asInput(), f2)); } } @Test public void testDividedByRevFloat() { for (float f2 : Values.interestingFloats) { trySet(fs.dividedByRev(f2), FloatOperation.division.of(f2, fs.asInput())); } } @Test public void testModuloFloat() { for (float f2 : Values.interestingFloats) { trySet(fs.modulo(f2), FloatOperation.modulation.of(fs.asInput(), f2)); } } @Test public void testModuloRevFloat() { for (float f2 : Values.interestingFloats) { trySet(fs.moduloRev(f2), FloatOperation.modulation.of(f2, fs.asInput())); } } @Test public void testAtLeastFloat() { for (float threshold : Values.interestingFloats) { if (Float.isNaN(threshold)) { continue; } BooleanInput bi = fs.atLeast(threshold); BooleanCell bs = new BooleanCell(); bi.send(bs); for (float test : Values.interestingFloats) { fs.set(test); assertEquals(test >= threshold, bi.get()); assertEquals(test >= threshold, bs.get()); } } } @Test(expected = IllegalArgumentException.class) public void testAtLeastFloatInvalid() { fs.atLeast(Float.NaN); } @Test public void testAtLeastFloatInput() { FloatCell ts = new FloatCell(); BooleanInput bi = fs.atLeast(ts); BooleanCell bs = new BooleanCell(); bi.send(bs); for (float threshold : Values.interestingFloats) { ts.set(threshold); for (float test : Values.interestingFloats) { fs.set(test); assertEquals(test >= threshold, bi.get()); assertEquals(test >= threshold, bs.get()); } } } @Test(expected = NullPointerException.class) public void testAtLeastNull() { fi.atLeast(null); } @Test public void testAtMostFloat() { for (float threshold : Values.interestingFloats) { if (Float.isNaN(threshold)) { continue; } BooleanInput bi = fs.atMost(threshold); BooleanCell bs = new BooleanCell(); bi.send(bs); for (float test : Values.interestingFloats) { fs.set(test); assertEquals(test <= threshold, bi.get()); assertEquals(test <= threshold, bs.get()); } } } @Test(expected = IllegalArgumentException.class) public void testAtMostFloatInvalid() { fs.atMost(Float.NaN); } @Test public void testAtMostFloatInput() { FloatCell ts = new FloatCell(); BooleanInput bi = fs.atMost(ts); BooleanCell bs = new BooleanCell(); bi.send(bs); for (float threshold : Values.interestingFloats) { ts.set(threshold); for (float test : Values.interestingFloats) { fs.set(test); assertEquals(test <= threshold, bi.get()); assertEquals(test <= threshold, bs.get()); } } } @Test(expected = NullPointerException.class) public void testAtMostNull() { fi.atMost(null); } @Test public void testOutsideRangeFloatFloat() { BooleanCell bs = new BooleanCell(); for (float min : Values.shorterInterestingFloats) { if (Float.isNaN(min)) { continue; } for (float max : Values.shorterInterestingFloats) { if (Float.isNaN(max)) { continue; } BooleanInput bi = fs.outsideRange(min, max); CancelOutput unbind = bi.send(bs); for (float test : Values.shorterInterestingFloats) { fs.set(test); assertEquals(test < min || test > max, bi.get()); assertEquals(test < min || test > max, bs.get()); } unbind.cancel(); } } } @Test(expected = IllegalArgumentException.class) public void testOutsideRangeFloatInvalidA() { fs.outsideRange(Float.NaN, 1); } @Test(expected = IllegalArgumentException.class) public void testOutsideRangeFloatInvalidB() { fs.outsideRange(0, Float.NaN); } @Test public void testOutsideRangeFloatInputFloatInput() { FloatCell min = new FloatCell(), max = new FloatCell(); BooleanInput bi = fs.outsideRange(min, max); BooleanCell bs = new BooleanCell(); bi.send(bs); for (float minv : Values.interestingFloats) { min.set(minv); for (float maxv : Values.interestingFloats) { max.set(maxv); for (float test : Values.interestingFloats) { fs.set(test); assertEquals(test < minv || test > maxv, bi.get()); assertEquals(test < minv || test > maxv, bs.get()); } } } } @Test(expected = NullPointerException.class) public void testOutsideRangeNullA() { fi.outsideRange(null, fs); } @Test(expected = NullPointerException.class) public void testOutsideRangeNullB() { fi.outsideRange(fs, null); } @Test public void testInRangeFloatFloat() { for (float min : Values.shorterInterestingFloats) { if (Float.isNaN(min)) { continue; } for (float max : Values.shorterInterestingFloats) { if (Float.isNaN(max)) { continue; } BooleanInput bi = fs.inRange(min, max); BooleanCell bs = new BooleanCell(); bi.send(bs); for (float test : Values.shorterInterestingFloats) { fs.set(test); assertEquals(test >= min && test <= max, bi.get()); assertEquals(test >= min && test <= max, bs.get()); } } } } @Test(expected = IllegalArgumentException.class) public void testInRangeFloatInvalidA() { fs.inRange(Float.NaN, 1); } @Test(expected = IllegalArgumentException.class) public void testInRangeFloatInvalidB() { fs.inRange(0, Float.NaN); } @Test public void testInRangeFloatInputFloatInput() { FloatCell min = new FloatCell(), max = new FloatCell(); BooleanInput bi = fs.inRange(min, max); BooleanCell bs = new BooleanCell(); bi.send(bs); for (float minv : Values.interestingFloats) { min.set(minv); for (float maxv : Values.interestingFloats) { max.set(maxv); for (float test : Values.interestingFloats) { fs.set(test); assertEquals(test >= minv && test <= maxv, bi.get()); assertEquals(test >= minv && test <= maxv, bs.get()); } } } } @Test(expected = NullPointerException.class) public void testInRangeNullA() { fi.inRange(null, fs); } @Test(expected = NullPointerException.class) public void testInRangeNullB() { fi.inRange(fs, null); } @Test public void testNegated() { FloatInput neg = fs.negated(); for (float v : Values.interestingFloats) { fs.set(v); assertEquals(v, -neg.get(), 0); } } @Test public void testAbsolute() { FloatInput abs = fs.absolute(); for (float v : Values.interestingFloats) { fs.set(v); assertEquals(Math.abs(v), abs.get(), 0); } } @Test public void testOnChange() { CountingEventOutput ceo = new CountingEventOutput(); fs.onChange().send(ceo); for (float v : Values.interestingFloats) { ceo.ifExpected = true; fs.set(v); ceo.check(); fs.set(v); ceo.check();// should not happen } } @Test public void testOnChangeBy() { for (float d : Values.lessInterestingFloats) { if (Float.isNaN(d) || d < 0) { continue; } FloatCell fs = new FloatCell(); CountingEventOutput ceo = new CountingEventOutput(); fs.onChangeBy(d).send(ceo); float last = 0; ceo.ifExpected = true; fs.set(Float.NEGATIVE_INFINITY); ceo.check(); ceo.ifExpected = true; fs.set(0); ceo.check(); for (float v : Values.lessInterestingFloats) { // note: if onChangeBy(0), then everything should trigger this. if (Math.abs(last - v) >= Math.abs(d) && last != v) { last = v; ceo.ifExpected = true; } else { ceo.ifExpected = false; } fs.set(v); ceo.check(); fs.set(v); ceo.check();// should not happen } } } @Test(expected = IllegalArgumentException.class) public void testOnChangeByNaN() { fs.onChangeBy(Float.NaN); } @Test(expected = IllegalArgumentException.class) public void testOnChangeByNegative() { fs.onChangeBy(-1); } @Test(expected = IllegalArgumentException.class) public void testOnChangeByPInf() { fs.onChangeBy(Float.POSITIVE_INFINITY); } @Test(expected = IllegalArgumentException.class) public void testOnChangeByNInf() { fs.onChangeBy(Float.NEGATIVE_INFINITY); } @Test public void testDeadzone() { for (float zone : Values.lessInterestingFloats) { if (!Float.isFinite(zone) || zone < 0) { continue; } FloatInput fin = fs.deadzone(zone); for (float v : Values.lessInterestingFloats) { if (Float.isNaN(v)) { continue; } fs.set(v); if (Math.abs(v) < Math.abs(zone)) { assertEquals(0, fin.get(), 0.000001f); } else { assertEquals(v, fin.get(), 0.000001f); } } } } @Test(expected = IllegalArgumentException.class) public void testDeadzoneNaN() { fs.deadzone(Float.NaN); } @Test(expected = IllegalArgumentException.class) public void testDeadzoneNegative() { fs.deadzone(-1); } @Test(expected = IllegalArgumentException.class) public void testDeadzoneInfinite() { fs.deadzone(Float.POSITIVE_INFINITY); } @Test public void testNormalize() { FloatCell zs = new FloatCell(), os = new FloatCell(); FloatInput fin1 = fs.normalize(zs, os); for (float zero : Values.shorterInterestingFloats) { if (!Float.isFinite(zero)) { continue; } zs.set(zero); FloatInput fin2 = fs.normalize(zero, os); for (float one : Values.shorterInterestingFloats) { if (!Float.isFinite(one) || !Float.isFinite(one - zero) || zero == one) { continue; } os.set(one); FloatInput fin3 = fs.normalize(zs, one); FloatInput fin4 = fs.normalize(zero, one); for (float v : Values.shorterInterestingFloats) { fs.set(v); assertEquals((v - zero) / (one - zero), fin1.get(), 0.0001f); assertEquals((v - zero) / (one - zero), fin2.get(), 0.0001f); assertEquals((v - zero) / (one - zero), fin3.get(), 0.0001f); assertEquals((v - zero) / (one - zero), fin4.get(), 0.0001f); } fs.set(zero); assertEquals(0, fin1.get(), 0); assertEquals(0, fin2.get(), 0); assertEquals(0, fin3.get(), 0); assertEquals(0, fin4.get(), 0); fs.set(one); assertEquals(1, fin1.get(), 0); assertEquals(1, fin2.get(), 0); assertEquals(1, fin3.get(), 0); assertEquals(1, fin4.get(), 0); if (Float.isFinite(2 * zero - one)) { fs.set(2 * zero - one); assertEquals(-1, fin1.get(), 0.000001f); assertEquals(-1, fin2.get(), 0.000001f); assertEquals(-1, fin3.get(), 0.000001f); assertEquals(-1, fin4.get(), 0.000001f); } } } } @Test(expected = IllegalArgumentException.class) public void testNormalizeSame() { fs.normalize(3.2f, 3.2f); } @Test(expected = IllegalArgumentException.class) public void testNormalizeNaNA() { fs.normalize(Float.NaN, 1); } @Test(expected = IllegalArgumentException.class) public void testNormalizeNaNB() { fs.normalize(0, Float.NaN); } @Test(expected = IllegalArgumentException.class) public void testNormalizeInfA() { fs.normalize(Float.POSITIVE_INFINITY, 1); } @Test(expected = IllegalArgumentException.class) public void testNormalizeInfB() { fs.normalize(0, Float.POSITIVE_INFINITY); } @Test(expected = IllegalArgumentException.class) public void testNormalizeNInfA() { fs.normalize(Float.NEGATIVE_INFINITY, 1); } @Test(expected = IllegalArgumentException.class) public void testNormalizeNInfB() { fs.normalize(0, Float.NEGATIVE_INFINITY); } @Test(expected = IllegalArgumentException.class) public void testNormalizeNaNAI() { fs.normalize(Float.NaN, fs); } @Test(expected = IllegalArgumentException.class) public void testNormalizeNaNBI() { fs.normalize(fs, Float.NaN); } @Test(expected = IllegalArgumentException.class) public void testNormalizeInfAI() { fs.normalize(Float.POSITIVE_INFINITY, fs); } @Test(expected = IllegalArgumentException.class) public void testNormalizeInfBI() { fs.normalize(fs, Float.POSITIVE_INFINITY); } @Test(expected = IllegalArgumentException.class) public void testNormalizeNInfAI() { fs.normalize(Float.NEGATIVE_INFINITY, fs); } @Test(expected = IllegalArgumentException.class) public void testNormalizeNInfBI() { fs.normalize(fs, Float.NEGATIVE_INFINITY); } @Test(expected = IllegalArgumentException.class) public void testNormalizeHugeRange() { fs.normalize(-Float.MAX_VALUE, Float.MAX_VALUE); } @Test public void testFilterUpdates() { for (boolean not : new boolean[] { false, true }) { FloatCell a = new FloatCell(), out = new FloatCell(); BooleanCell allowDeny = new BooleanCell(true); fi = not ? a.filterUpdatesNot(allowDeny) : a.filterUpdates(allowDeny); fi.send(out); float expect = 0; int n = 0; for (int i = 0; i < 3; i++) { for (float f : Values.interestingFloats) { allowDeny.set(((n++ % 9) < 5) ^ not); a.set(f); if (allowDeny.get() ^ not) { expect = a.get(); } assertEquals(expect, fi.get(), 0); assertEquals(expect, out.get(), 0); } } } } @Test(expected = NullPointerException.class) public void testFilterUpdatesNull() { fi.filterUpdates(null); } @Test(expected = NullPointerException.class) public void testFilterUpdatesNotNull() { fi.filterUpdatesNot(null); } @Test public void testWithRamping() { EventCell update = new EventCell(); FloatInput fi = fs.withRamping(0.2f, update); for (float i = -5f; i < 5f; i++) { float a = fi.get(); fs.set(i); float b = fi.get(); assertEquals(a, b, 0); int j = 0; while (true) { float old = fi.get(); update.event(); float delta = Math.abs(fi.get() - old); if (fi.get() == i) { break; } assertEquals(delta, 0.2f, 0.000001f); if (++j >= 100) { fail("never reached target"); } } } } @Test(expected = NullPointerException.class) public void testWithRampingNull() { fi.withRamping(0.2f, null); } @Test(expected = IllegalArgumentException.class) public void testWithRampingInvalid() { fi.withRamping(Float.NaN, EventInput.never); } @Test public void testWithRampingInput() { EventCell update = new EventCell(); FloatCell ramp = new FloatCell(Float.NaN); FloatInput fi = fs.withRamping(ramp, update); for (float f : Values.lessInterestingFloats) { if (f <= 0) { continue; } ramp.set(f); for (float i = -25f * f; i < 25f * f; i++) { float a = fi.get(); fs.set(i); float b = fi.get(); assertEquals(a, b, 0); int j = 0; while (true) { float old = fi.get(); update.event(); float delta = Math.abs(fi.get() - old); if (fi.get() == i) { break; } assertEquals(delta, f, 0.000005f * f); if (++j >= 100) { fail("never reached target"); } } } } } @Test(expected = NullPointerException.class) public void testWithRampingInputNullA() { fi.withRamping(FloatInput.zero, null); } @Test(expected = NullPointerException.class) public void testWithRampingInputNullB() { fi.withRamping(null, EventInput.never); } @Test public void testCreateRampingEvent() {// TODO: perhaps flesh these tests out // a bit more? FloatCell fi = new FloatCell(); EventOutput update = fs.createRampingEvent(0.2f, fi); for (float i = -5f; i < 5f; i++) { float a = fi.get(); fs.set(i); float b = fi.get(); assertEquals(a, b, 0); int j = 0; while (true) { float old = fi.get(); update.event(); float delta = Math.abs(fi.get() - old); if (fi.get() == i) { break; } assertEquals(delta, 0.2f, 0.000001f); if (++j >= 100) { fail("never reached target"); } } } } @Test(expected = NullPointerException.class) public void testCreateRampingEventNull() { fi.createRampingEvent(0.2f, null); } @Test(expected = IllegalArgumentException.class) public void testCreateRampingEventInvalid() { fi.createRampingEvent(Float.NaN, fs); } @Test public void testCreateRampingEventInput() { FloatCell fi = new FloatCell(); FloatCell ramp = new FloatCell(Float.NaN); EventOutput update = fs.createRampingEvent(ramp, fi); for (float f : Values.lessInterestingFloats) { if (f <= 0) { continue; } ramp.set(f); for (float i = -25f * f; i < 25f * f; i++) { float a = fi.get(); fs.set(i); float b = fi.get(); assertEquals(a, b, 0); int j = 0; while (true) { float old = fi.get(); update.event(); float delta = Math.abs(fi.get() - old); if (fi.get() == i) { break; } assertEquals(delta, f, 0.000005f * f); if (++j >= 100) { fail("never reached target"); } } } } } @Test(expected = NullPointerException.class) public void testCreateRampingEventInputNullA() { fi.createRampingEvent(FloatInput.zero, null); } @Test(expected = NullPointerException.class) public void testCreateRampingEventInputNullB() { fi.createRampingEvent(null, FloatOutput.ignored); } @SuppressWarnings("deprecation") @Test public void testDerivative() throws InterruptedException { Random rand = new Random(1); FloatInput fi = fs.derivative(); for (float i = -10.0f; i <= 10.0f; i += 0.5f) { fs.set(i); for (float j = i; j <= i + 20.0f;) { float delta = (rand.nextInt(30) + 1) / 10.0f; long timeDelta = rand.nextInt(2000) + 1; VirtualTime.forward(timeDelta); j += delta; fs.set(j); assertEquals(1000 * delta / timeDelta, fi.get(), 0.00001f * (1000 * delta / timeDelta)); } VirtualTime.forward(1000); } } @Test public void testDerivativeRepSlower() throws InterruptedException { FloatInput fi = fs.derivative(20); Ticker ti = new Ticker(21); try { fs.set(0.5f); ti.send(fs.eventAccumulate(1)); for (int i = 0; i < 20; i++) { VirtualTime.forward(20); assertEquals(0, fi.get(), 0); VirtualTime.forward(1); assertEquals(1.0 / 0.001, fi.get(), 0); } } finally { ti.terminate(); } } @Test public void testDerivativeRepFaster() throws InterruptedException { FloatInput fi = fs.derivative(20); Ticker ti = new Ticker(18); try { ti.send(fs.eventAccumulate(1)); VirtualTime.forward(18); VirtualTime.forward(9); for (int i = 0; i < 20; i++) { VirtualTime.forward(9); assertEquals(1.0f / 0.018f, fi.get(), 0); } } finally { ti.terminate(); } } @Test public void testSendError() { CountingEventOutput expected = new CountingEventOutput(); CountingEventOutput expected2 = new CountingEventOutput(); fi = new FloatInput() { @Override public CancelOutput onUpdate(EventOutput notify) { expected.event(); return CancelOutput.nothing; } @Override public float get() { expected2.event(); return 3; } }; FloatOutput evil = new FloatOutput() { @Override public void set(float value) { cfo.set(value); // TODO: check logging. throw new NoSuchElementException(ERROR_STRING); } }; expected.ifExpected = expected2.ifExpected = cfo.ifExpected = true; cfo.valueExpected = 3; VerifyingLogger.configure(LogLevel.SEVERE, "Error during channel propagation", (t) -> t.getClass() == NoSuchElementException.class && ERROR_STRING.equals(t.getMessage())); fi.send(evil); VerifyingLogger.check(); expected.check(); expected2.check(); cfo.check(); } @Test public void testNegatedIf() { for (boolean init : new boolean[] { false, true }) { BooleanCell cond = new BooleanCell(init); FloatInput neg = fs.negatedIf(cond); for (float v : Values.interestingFloats) { fs.set(v); assertEquals(v, cond.get() ? -neg.get() : neg.get(), 0); if (Values.getRandomBoolean()) { cond.toggle(); } } } } @Test(expected = NullPointerException.class) public void testNegatedIfNull() { fs.negatedIf(null); } }