Files
allwpilib/wpilibj/src/test/java/org/wpilib/event/BooleanEventTest.java

496 lines
13 KiB
Java
Raw Normal View History

// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
2025-11-07 19:55:43 -05:00
package org.wpilib.event;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.Test;
class BooleanEventTest {
@Test
void testBinaryCompositions() {
var loop = new EventLoop();
var andCounter = new AtomicInteger(0);
var orCounter = new AtomicInteger(0);
assertEquals(0, andCounter.get());
assertEquals(0, orCounter.get());
new BooleanEvent(loop, () -> true).and(() -> false).ifHigh(andCounter::incrementAndGet);
new BooleanEvent(loop, () -> true).or(() -> false).ifHigh(orCounter::incrementAndGet);
loop.poll();
assertEquals(0, andCounter.get());
assertEquals(1, orCounter.get());
}
/**
* Tests that composed edge events only execute on edges (two rising edge events composed with
* and() should only execute when both signals are on the rising edge).
*/
@Test
void testBinaryCompositionsWithEdgeDecorators() {
var loop = new EventLoop();
var bool1 = new AtomicBoolean(false);
var bool2 = new AtomicBoolean(false);
var bool3 = new AtomicBoolean(false);
var bool4 = new AtomicBoolean(false);
var counter = new AtomicInteger(0);
var event1 = new BooleanEvent(loop, bool1::get).rising();
var event2 = new BooleanEvent(loop, bool2::get).rising();
var event3 = new BooleanEvent(loop, bool3::get).rising();
var event4 = new BooleanEvent(loop, bool4::get).rising();
event1.and(event2).ifHigh(counter::incrementAndGet);
event3.or(event4).ifHigh(counter::incrementAndGet);
assertEquals(0, counter.get());
bool1.set(true);
bool2.set(true);
bool3.set(true);
bool4.set(true);
loop.poll(); // Both actions execute
assertEquals(2, counter.get());
loop.poll(); // Nothing should happen since nothing is on rising edge
assertEquals(2, counter.get());
bool1.set(false);
bool2.set(false);
bool3.set(false);
bool4.set(false);
loop.poll(); // Nothing should happen
assertEquals(2, counter.get());
bool1.set(true);
loop.poll(); // Nothing should happen since only Bool 1 is on rising edge
assertEquals(2, counter.get());
bool2.set(true);
loop.poll(); // Bool 2 is on rising edge, but Bool 1 isn't, nothing should happen
assertEquals(2, counter.get());
bool1.set(false);
bool2.set(false);
loop.poll(); // Nothing should happen
assertEquals(2, counter.get());
bool1.set(true);
bool2.set(true);
loop.poll(); // Bool 1 and 2 are on rising edge, increments counter once
assertEquals(3, counter.get());
bool3.set(true);
loop.poll(); // Bool 3 is on rising edge, increments counter once
assertEquals(4, counter.get());
loop.poll(); // Nothing should happen, Bool 3 isn't on rising edge
assertEquals(4, counter.get());
bool4.set(true);
loop.poll(); // Bool 4 is on rising edge, increments counter once
assertEquals(5, counter.get());
loop.poll(); // Nothing should happen, Bool 4 isn't on rising edge
assertEquals(5, counter.get());
}
@Test
void testBinaryCompositionLoopSemantics() {
var loop1 = new EventLoop();
var loop2 = new EventLoop();
var bool1 = new AtomicBoolean(true);
var bool2 = new AtomicBoolean(true);
var counter1 = new AtomicInteger(0);
var counter2 = new AtomicInteger(0);
new BooleanEvent(loop1, bool1::get)
.and(new BooleanEvent(loop2, bool2::get))
.ifHigh(counter1::incrementAndGet);
new BooleanEvent(loop2, bool2::get)
.and(new BooleanEvent(loop1, bool1::get))
.ifHigh(counter2::incrementAndGet);
assertEquals(0, counter1.get());
assertEquals(0, counter2.get());
loop1.poll(); // 1st event executes, Bool 1 and 2 are true, increments counter
assertEquals(1, counter1.get());
assertEquals(0, counter2.get());
loop2.poll(); // 2nd event executes, Bool 1 and 2 are true, increments counter
assertEquals(1, counter1.get());
assertEquals(1, counter2.get());
bool2.set(false);
loop1.poll(); // 1st event executes, Bool 2 is still true because loop 2 hasn't updated it,
// increments counter
assertEquals(2, counter1.get());
assertEquals(1, counter2.get());
loop2.poll(); // 2nd event executes, Bool 2 is now false because this loop updated it, does
// nothing
assertEquals(2, counter1.get());
assertEquals(1, counter2.get());
loop1.poll(); // All bools are updated at this point, nothing should happen
assertEquals(2, counter1.get());
assertEquals(1, counter2.get());
bool2.set(true);
loop2.poll(); // 2nd event executes, Bool 2 is true because this loop updated it, increments
// counter
assertEquals(2, counter1.get());
assertEquals(2, counter2.get());
loop1
.poll(); // 1st event executes, Bool 2 is true because loop 2 updated it, increments counter
assertEquals(3, counter1.get());
assertEquals(2, counter2.get());
bool1.set(false);
loop2.poll(); // 2nd event executes, Bool 1 is still true because loop 1 hasn't updated it,
// increments counter
assertEquals(3, counter1.get());
assertEquals(3, counter2.get());
loop1.poll(); // 1st event executes, Bool 1 is false because this loop updated it, does nothing
assertEquals(3, counter1.get());
assertEquals(3, counter2.get());
loop2.poll(); // All bools are updated at this point, nothing should happen
assertEquals(3, counter1.get());
assertEquals(3, counter2.get());
}
/** Tests the order of actions bound to an event loop. */
@Test
void testPollOrdering() {
var loop = new EventLoop();
var bool1 = new AtomicBoolean(true);
var bool2 = new AtomicBoolean(true);
var enableAssert = new AtomicBoolean(false);
var counter = new AtomicInteger(0);
// This event binds an action to the event loop first
new BooleanEvent(
loop,
() -> {
if (enableAssert.get()) {
counter.incrementAndGet();
assertEquals(1, counter.get() % 3);
}
return bool1.get();
})
// The composed event binds an action to the event loop third
.and(
// This event binds an action to the event loop second
new BooleanEvent(
loop,
() -> {
if (enableAssert.get()) {
counter.incrementAndGet();
assertEquals(2, counter.get() % 3);
}
return bool2.get();
}))
// This binds an action to the event loop fourth
.ifHigh(
() -> {
if (enableAssert.get()) {
counter.incrementAndGet();
assertEquals(0, counter.get() % 3);
}
});
enableAssert.set(true);
loop.poll();
loop.poll();
loop.poll();
loop.poll();
}
@Test
void testEdgeDecorators() {
var loop = new EventLoop();
var bool = new AtomicBoolean(false);
var counter = new AtomicInteger(0);
new BooleanEvent(loop, bool::get).falling().ifHigh(counter::decrementAndGet);
new BooleanEvent(loop, bool::get).rising().ifHigh(counter::incrementAndGet);
assertEquals(0, counter.get());
bool.set(false);
loop.poll();
assertEquals(0, counter.get());
bool.set(true);
loop.poll();
assertEquals(1, counter.get());
bool.set(true);
loop.poll();
assertEquals(1, counter.get());
bool.set(false);
loop.poll();
assertEquals(0, counter.get());
}
/** Tests that binding actions to the same edge event will result in all actions executing. */
@Test
void testEdgeReuse() {
var loop = new EventLoop();
var bool = new AtomicBoolean(false);
var counter = new AtomicInteger(0);
var event = new BooleanEvent(loop, bool::get).rising();
event.ifHigh(counter::incrementAndGet);
event.ifHigh(counter::incrementAndGet);
assertEquals(0, counter.get());
loop.poll();
assertEquals(0, counter.get());
bool.set(true);
loop.poll();
assertEquals(2, counter.get());
loop.poll();
assertEquals(2, counter.get());
bool.set(false);
loop.poll();
assertEquals(2, counter.get());
bool.set(true);
loop.poll();
assertEquals(4, counter.get());
}
/** Tests that all actions execute on separate edge events constructed from the original event. */
@Test
void testEdgeReconstruct() {
var loop = new EventLoop();
var bool = new AtomicBoolean(false);
var counter = new AtomicInteger(0);
var event = new BooleanEvent(loop, bool::get);
event.rising().ifHigh(counter::incrementAndGet);
event.rising().ifHigh(counter::incrementAndGet);
assertEquals(0, counter.get());
loop.poll();
assertEquals(0, counter.get());
bool.set(true);
loop.poll();
assertEquals(2, counter.get());
loop.poll();
assertEquals(2, counter.get());
bool.set(false);
loop.poll();
assertEquals(2, counter.get());
bool.set(true);
loop.poll();
assertEquals(4, counter.get());
}
/**
* Tests that all actions bound to an event will still execute even if the signal is changed
* during the loop poll.
*/
@Test
void testMidLoopBooleanChange() {
var loop = new EventLoop();
var bool = new AtomicBoolean(false);
var counter = new AtomicInteger(0);
var event = new BooleanEvent(loop, bool::get).rising();
event.ifHigh(
() -> {
bool.set(false);
counter.incrementAndGet();
});
event.ifHigh(counter::incrementAndGet);
assertEquals(0, counter.get());
loop.poll();
assertEquals(0, counter.get());
bool.set(true);
loop.poll();
assertEquals(2, counter.get());
loop.poll();
assertEquals(2, counter.get());
bool.set(false);
loop.poll();
assertEquals(2, counter.get());
bool.set(true);
loop.poll();
assertEquals(4, counter.get());
}
/**
* Tests that all actions bound to composed events will still execute even if the composed signal
* changes during the loop poll.
*/
@Test
void testMidLoopBooleanChangeWithComposedEvents() {
var loop = new EventLoop();
var bool1 = new AtomicBoolean(false);
var bool2 = new AtomicBoolean(false);
var bool3 = new AtomicBoolean(false);
var bool4 = new AtomicBoolean(false);
var counter = new AtomicInteger(0);
var event1 = new BooleanEvent(loop, bool1::get);
var event2 = new BooleanEvent(loop, bool2::get);
var event3 = new BooleanEvent(loop, bool3::get);
var event4 = new BooleanEvent(loop, bool4::get);
event1.ifHigh(
() -> {
bool2.set(false);
bool3.set(false);
counter.incrementAndGet();
});
event3
.or(event4)
.ifHigh(
() -> {
bool1.set(false);
counter.incrementAndGet();
});
event1
.and(event2)
.ifHigh(
() -> {
bool4.set(false);
counter.incrementAndGet();
});
assertEquals(0, counter.get());
bool1.set(true);
bool2.set(true);
bool3.set(true);
bool4.set(true);
loop.poll(); // All three actions execute, incrementing the counter three times and setting all
// booleans to false
assertEquals(3, counter.get());
loop.poll(); // Nothing should happen since everything was set to false
assertEquals(3, counter.get());
bool1.set(true);
bool2.set(true);
loop.poll(); // Bool 1 and 2 are true, increments counter twice, Bool 2 gets set to false
assertEquals(5, counter.get());
bool1.set(false);
loop.poll(); // Nothing should happen
assertEquals(5, counter.get());
bool1.set(true);
bool3.set(true);
loop.poll(); // Bool 1 and 3 are true, increments counter twice, Bool 3 gets set to false
assertEquals(7, counter.get());
bool1.set(false);
bool4.set(true);
loop.poll(); // Bool 4 is true, increments counter once
assertEquals(8, counter.get());
}
@Test
void testNegation() {
var loop = new EventLoop();
var bool = new AtomicBoolean(false);
var counter = new AtomicInteger(0);
new BooleanEvent(loop, bool::get).negate().ifHigh(counter::incrementAndGet);
assertEquals(0, counter.get());
loop.poll();
assertEquals(1, counter.get());
bool.set(true);
loop.poll();
assertEquals(1, counter.get());
bool.set(false);
loop.poll();
assertEquals(2, counter.get());
bool.set(true);
loop.poll();
assertEquals(2, counter.get());
}
}