/*
* 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.assertTrue;
import static org.junit.Assert.fail;
import java.util.NoSuchElementException;
import java.util.Random;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import ccre.log.LogLevel;
import ccre.log.VerifyingLogger;
import ccre.scheduler.VirtualTime;
import ccre.testing.CountingFloatOutput;
import ccre.util.Utils;
import ccre.util.Values;
@SuppressWarnings("javadoc")
public class FloatOutputTest {
private static final String ERROR_STRING = "safeSet purposeful failure.";
private final FloatOutput evil = (v) -> {
throw new NoSuchElementException(ERROR_STRING);
};
private CountingFloatOutput cfo, cfo2;
private FloatCell fs;
@Before
public void setUp() throws Exception {
// For derivative testing
VirtualTime.startFakeTime();
fs = new FloatCell();
cfo = new CountingFloatOutput();
cfo2 = new CountingFloatOutput();
VerifyingLogger.begin();
}
@After
public void tearDown() throws Exception {
VerifyingLogger.checkAndEnd();
fs = null;
cfo = null;
cfo2 = null;
VirtualTime.endFakeTime();
}
@Test
public void testIgnored() {
FloatOutput.ignored.set(0);
}
@Test
public void testIgnoredCombine() {
assertEquals(cfo, FloatOutput.ignored.combine(cfo));
}
@Test(expected = NullPointerException.class)
public void testIgnoredCombineNull() {
FloatOutput.ignored.combine((FloatOutput) null);
}
@Test
public void testGetSetEventFloat() {
for (float f : Values.interestingFloats) {
EventOutput setE = cfo.eventSet(f);
cfo.ifExpected = true;
cfo.valueExpected = f;
setE.event();
cfo.check();
}
}
@Test
public void testGetSetEventFloatInput() {
EventOutput setE = cfo.eventSet(fs);
for (float f : Values.interestingFloats) {
fs.set(f);
cfo.ifExpected = true;
cfo.valueExpected = f;
setE.event();
cfo.check();
}
}
@Test(expected = NullPointerException.class)
public void testGetSetEventFloatInputNull() {
cfo.eventSet(null);
}
@Test
public void testSetWhenFloatEventInput() {
for (float f : Values.interestingFloats) {
EventCell es = new EventCell();
cfo.setWhen(f, es);
cfo.ifExpected = true;
cfo.valueExpected = f;
es.event();
cfo.check();
}
}
@Test(expected = NullPointerException.class)
public void testSetWhenFloatEventInputNull() {
cfo.setWhen(0, null);
}
@Test
public void testSetWhenFloatInputEventInput() {
EventCell setE = new EventCell();
cfo.setWhen(fs, setE);
for (float f : Values.interestingFloats) {
fs.set(f);
cfo.ifExpected = true;
cfo.valueExpected = f;
setE.event();
cfo.check();
}
}
@Test(expected = NullPointerException.class)
public void testSetWhenFloatInputEventInputNullA() {
cfo.setWhen(null, EventInput.never);
}
@Test(expected = NullPointerException.class)
public void testSetWhenFloatInputEventInputNullB() {
cfo.setWhen(FloatInput.zero, null);
}
@Test
public void testCombine() {
FloatOutput combine = cfo.combine(cfo2);
for (float f : Values.interestingFloats) {
cfo.ifExpected = cfo2.ifExpected = true;
cfo.valueExpected = cfo2.valueExpected = f;
combine.set(f);
cfo.check();
cfo2.check();
}
}
@Test(expected = NullPointerException.class)
public void testCombineNull() {
cfo.combine(null);
}
@Test
public void testStaticCombineSimplification() {
assertEquals(FloatOutput.combine(), FloatOutput.ignored);
assertEquals(FloatOutput.combine(new FloatOutput[] { cfo }), cfo);
}
@Test
public void testStaticCombine() {
for (int n = 0; n < 20; n++) {
CountingFloatOutput[] cfos = new CountingFloatOutput[n];
for (int i = 0; i < n; i++) {
cfos[i] = new CountingFloatOutput();
}
FloatOutput combined = FloatOutput.combine(cfos);
for (float f : Values.interestingFloats) {
for (int i = 0; i < n; i++) {
cfos[i].ifExpected = true;
cfos[i].valueExpected = f;
}
combined.set(f);
for (int i = 0; i < n; i++) {
cfos[i].check();
}
}
}
}
@Test(expected = NullPointerException.class)
public void testStaticCombineNull() {
FloatOutput.combine((FloatOutput[]) null);
}
@Test(expected = NullPointerException.class)
public void testStaticCombineNullElem() {
FloatOutput.combine(new FloatOutput[] { null });
}
@Test(expected = NullPointerException.class)
public void testStaticCombineNullEarlierElem() {
FloatOutput.combine(null, cfo);
}
@Test(expected = NullPointerException.class)
public void testStaticCombineNullLaterElem() {
FloatOutput.combine(cfo, null);
}
@Test
public void testNegate() {
FloatOutput neg = cfo.negate();
for (float f : Values.interestingFloats) {
cfo.ifExpected = true;
cfo.valueExpected = -f;
neg.set(f);
cfo.check();
}
}
@Test
public void testOutputDeadzone() {
for (float zone : Values.lessInterestingFloats) {
if (!Float.isFinite(zone) || zone < 0) {
continue;
}
FloatOutput fout = cfo.outputDeadzone(zone);
for (float v : Values.lessInterestingFloats) {
if (Float.isNaN(v)) {
continue;
}
if (Math.abs(v) < Math.abs(zone)) {
cfo.valueExpected = 0;
} else {
cfo.valueExpected = v;
}
cfo.ifExpected = true;
fout.set(v);
cfo.check();
}
}
}
@Test(expected = IllegalArgumentException.class)
public void testOutputDeadzoneNaN() {
cfo.outputDeadzone(Float.NaN);
}
@Test(expected = IllegalArgumentException.class)
public void testOutputDeadzoneNegative() {
cfo.outputDeadzone(-1);
}
@Test(expected = IllegalArgumentException.class)
public void testOutputDeadzoneInfinity() {
cfo.outputDeadzone(Float.POSITIVE_INFINITY);
}
@Test
public void testAddRamping() { // TODO: flesh this out a bit more
EventCell update = new EventCell();
FloatOutput fo = cfo.addRamping(0.2f, update);
for (int i = 0; i < 5; i++) {
cfo.ifExpected = true;
cfo.valueExpected = 0;
update.event();
cfo.check();
}
float last = 0;
for (float i = -5f; i < 5f; i++) {
fo.set(i);
int j = 0;
while (true) {
cfo.ifExpected = true;
last = cfo.valueExpected = Utils.updateRamping(last, i, 0.2f);
update.event();
cfo.check();
if (last == i) {
break;
}
if (++j >= 100) {
fail("never reached target");
}
}
}
}
@Test(expected = NullPointerException.class)
public void testAddRampingNull() {
cfo.addRamping(0.2f, null);
}
@Test
public void testViaDerivative() throws InterruptedException {
Random rand = new Random(1);
FloatOutput fo = cfo.viaDerivative();
float j = 0;
for (float i = -10.0f; i <= 10.0f; i += 0.5f) {
cfo.valueExpected = (i - j); // for subsequent times around the
// loop
fo.set(i);
cfo.check();
for (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;
cfo.ifExpected = true;
cfo.valueExpected = 1000 * delta / timeDelta;
cfo.maxDelta = 0.00001f * Math.abs(cfo.valueExpected);
fo.set(j);
cfo.check();
}
VirtualTime.forward(1000);
cfo.ifExpected = true;
}
}
@Test
public void testFilter() {
for (boolean not : new boolean[] { false, true }) {
BooleanCell allowDeny = new BooleanCell();
FloatCell out = new FloatCell();
FloatOutput fo = not ? out.filterNot(allowDeny) : out.filter(allowDeny);
int j = 0;
float expect = 0;
for (int i = 0; i < 10; i++) {
for (float f : Values.interestingFloats) {
boolean nvalue = ((j % 19) < 10) ^ not;
if (allowDeny.get() == not && nvalue != not) {
float f2 = Values.getRandomFloat();
// switching to active: expect an update as necessary
fo.set(f2);
assertEquals(Float.floatToIntBits(expect), Float.floatToIntBits(out.get()));
allowDeny.set(nvalue);
expect = f2;
assertEquals(Float.floatToIntBits(expect), Float.floatToIntBits(out.get()));
} else {
allowDeny.set(nvalue);
}
fo.set(f);
if (allowDeny.get() ^ not) {
expect = f;
}
assertEquals(Float.floatToIntBits(expect), Float.floatToIntBits(out.get()));
j++;
}
}
}
}
@Test(expected = NullPointerException.class)
public void testFilterNull() {
cfo.filter(null);
}
@Test(expected = NullPointerException.class)
public void testFilterNotNull() {
cfo.filterNot(null);
}
@Test
public void testFromBoolean1() {
FloatCell offV = new FloatCell();
FloatCell onV = new FloatCell();
Boolean lastSent = null;
BooleanOutput b1 = cfo.fromBoolean(offV, onV);
for (float off : Values.interestingFloats) {
cfo.ifExpected = (lastSent != null && !lastSent);
cfo.valueExpected = off;
offV.set(off);
cfo.check();
for (float on : Values.interestingFloats) {
cfo.ifExpected = (lastSent != null && lastSent);
cfo.valueExpected = on;
onV.set(on);
cfo.check();
for (boolean b : Values.interestingBooleans) {
cfo.valueExpected = b ? on : off;
cfo.ifExpected = (lastSent == null || lastSent != b);
b1.set(b);
cfo.check();
lastSent = b;
}
}
boolean b = Values.getRandomBoolean();
cfo.ifExpected = (lastSent != null && lastSent != b);
cfo.valueExpected = b ? onV.get() : off;
b1.set(b);
cfo.check();
lastSent = b;
}
}
@Test(expected = NullPointerException.class)
public void testFromBoolean1NullA() {
cfo.fromBoolean(null, FloatInput.zero);
}
@Test(expected = NullPointerException.class)
public void testFromBoolean1NullB() {
cfo.fromBoolean(FloatInput.zero, null);
}
@Test
public void testFromBoolean2() {
for (float off : Values.interestingFloats) {
Boolean lastSent = null;
FloatCell onV = new FloatCell();
BooleanOutput b1 = cfo.fromBoolean(off, onV);
for (float on : Values.interestingFloats) {
cfo.ifExpected = (lastSent != null && lastSent);
cfo.valueExpected = on;
onV.set(on);
cfo.check();
for (boolean b : Values.interestingBooleans) {
cfo.valueExpected = b ? on : off;
cfo.ifExpected = (lastSent == null || lastSent != b);
b1.set(b);
cfo.check();
lastSent = b;
}
}
}
}
@Test(expected = NullPointerException.class)
public void testFromBoolean2Null() {
cfo.fromBoolean(0, null);
}
@Test
public void testFromBoolean3() {
for (float on : Values.interestingFloats) {
Boolean lastSent = null;
FloatCell offV = new FloatCell();
BooleanOutput b1 = cfo.fromBoolean(offV, on);
for (float off : Values.interestingFloats) {
cfo.ifExpected = (lastSent != null && !lastSent);
cfo.valueExpected = on;
offV.set(off);
cfo.check();
for (boolean b : Values.interestingBooleans) {
cfo.valueExpected = b ? on : off;
cfo.ifExpected = (lastSent == null || lastSent != b);
b1.set(b);
cfo.check();
lastSent = b;
}
}
}
}
@Test(expected = NullPointerException.class)
public void testFromBoolean3Null() {
cfo.fromBoolean(null, 0);
}
@Test
public void testFromBoolean4() {
for (float off : Values.interestingFloats) {
for (float on : Values.interestingFloats) {
Boolean lastSent = null;
BooleanOutput b1 = cfo.fromBoolean(off, on);
for (boolean b : Values.interestingBooleans) {
cfo.valueExpected = b ? on : off;
cfo.ifExpected = (lastSent == null || lastSent != b);
b1.set(b);
cfo.check();
lastSent = b;
}
}
}
}
@Test
public void testSafeSet() {
VerifyingLogger.configure(LogLevel.SEVERE, "Error during channel propagation", (t) -> t.getClass() == NoSuchElementException.class && ERROR_STRING.equals(t.getMessage()));
evil.safeSet(0);
VerifyingLogger.check();
VerifyingLogger.configure(LogLevel.SEVERE, "Error during channel propagation", (t) -> t.getClass() == NoSuchElementException.class && ERROR_STRING.equals(t.getMessage()));
evil.safeSet(1);
VerifyingLogger.check();
}
@Test(expected = NoSuchElementException.class)
public void testExceptionPropagationFalse() {
evil.set(1);
}
@Test(expected = NoSuchElementException.class)
public void testCombineWithError1CausesError() {
cfo.ifExpected = true;
cfo.valueExpected = 1.2f;
cfo.combine(evil).set(1.2f);
}
@Test(expected = NoSuchElementException.class)
public void testCombineWithError2CausesError() {
cfo.ifExpected = true;
cfo.valueExpected = 1;
evil.combine(cfo).set(1);
}
@Test
public void testCombineWithError1Succeeds() {
cfo.ifExpected = true;
cfo.valueExpected = 1;
VerifyingLogger.configure(LogLevel.SEVERE, "Error during channel propagation", (t) -> t.getClass() == NoSuchElementException.class && ERROR_STRING.equals(t.getMessage()));
cfo.combine(evil).safeSet(1);
VerifyingLogger.check();
cfo.check();
}
@Test
public void testCombineWithError2Succeeds() {
cfo.ifExpected = true;
cfo.valueExpected = 1;
VerifyingLogger.configure(LogLevel.SEVERE, "Error during channel propagation", (t) -> t.getClass() == NoSuchElementException.class && ERROR_STRING.equals(t.getMessage()));
evil.combine(cfo).safeSet(1);
VerifyingLogger.check();
cfo.check();
}
@Test
public void testCombineWithError3CausesError() {
boolean errored = false;
try {
evil.combine(evil).set(1);
} catch (NoSuchElementException ex) {
errored = true;
assertEquals(ex.getSuppressed().length, 1);
assertTrue(ex.getSuppressed()[0] instanceof NoSuchElementException);
}
assertTrue(errored);
}
@Test
public void testStaticCombineSingleError() {
for (int n = 1; n < 6; n++) {
CountingFloatOutput[] cfos = new CountingFloatOutput[n];
for (int i = 0; i < n; i++) {
cfos[i] = new CountingFloatOutput();
}
for (int bad = 0; bad < n; bad++) {
FloatOutput[] reals = new FloatOutput[n];
System.arraycopy(cfos, 0, reals, 0, n);
reals[bad] = evil;
FloatOutput combined = FloatOutput.combine(reals);
for (float f : Values.interestingFloats) {
for (int i = 0; i < n; i++) {
cfos[i].ifExpected = i != bad;
cfos[i].valueExpected = f;
}
try {
combined.set(f);
fail();
} catch (NoSuchElementException ex) {
assertEquals(0, ex.getSuppressed().length);
}
for (int i = 0; i < n; i++) {
cfos[i].check();
}
}
}
}
}
@Test
public void testStaticCombineManyErrors() {
for (int n = 1; n < 6; n++) {
CountingFloatOutput[] cfos = new CountingFloatOutput[n];
for (int i = 0; i < n; i++) {
cfos[i] = new CountingFloatOutput();
}
for (int bad = 0; bad < 4 * n * n; bad++) {
boolean[] evils = new boolean[n];
int evil_count = 0;
FloatOutput[] reals = new FloatOutput[n];
System.arraycopy(cfos, 0, reals, 0, n);
for (int i = 0; i < n; i++) {
evils[i] = Values.getRandomBoolean();
if (evils[i]) {
evil_count++;
reals[i] = evil;
}
}
FloatOutput combined = FloatOutput.combine(reals);
for (float f : Values.interestingFloats) {
for (int i = 0; i < n; i++) {
cfos[i].ifExpected = !evils[i];
cfos[i].valueExpected = f;
}
try {
combined.set(f);
assertEquals(0, evil_count);
} catch (NoSuchElementException ex) {
Throwable[] suppressed = ex.getSuppressed();
assertEquals(evil_count - 1, suppressed.length);
for (int i = 0; i < suppressed.length; i++) {
assertTrue(suppressed[i] instanceof NoSuchElementException);
}
}
for (int i = 0; i < n; i++) {
cfos[i].check();
}
}
}
}
}
@Test
public void testNegateIf() {
for (boolean init : new boolean[] { false, true }) {
BooleanCell cond = new BooleanCell(init);
FloatOutput neg = cfo.negateIf(cond);
for (float v : Values.interestingFloats) {
cfo.ifExpected = true;
cfo.valueExpected = cond.get() ? -v : v;
neg.set(v);
cfo.check();
if (Values.getRandomBoolean()) {
cfo.ifExpected = true;
cfo.valueExpected = cond.get() ? v : -v;
cond.toggle();
cfo.check();
}
}
}
}
@Test(expected = NullPointerException.class)
public void testNegateIfNull() {
fs.negateIf(null);
}
@Test
public void testCell() {
for (float of : Values.interestingFloats) {
FloatOutput out = cfo::set;
cfo.ifExpected = true;
cfo.valueExpected = of;
FloatIO fio = out.cell(of);
cfo.check();
cfo2.ifExpected = true;
cfo2.valueExpected = of;
fio.send(cfo2);
cfo2.check();
for (int i = 0; i < 20; i++) {
float f = Values.getRandomFloat();
cfo.ifExpected = cfo2.ifExpected = Float.floatToIntBits(fio.get()) != Float.floatToIntBits(f);
cfo.valueExpected = cfo2.valueExpected = f;
fio.set(f);
cfo2.check();
cfo.check();
assertEquals(f, fio.get(), 0);
}
}
}
}