Files
allwpilib/wpilibjIntegrationTests/src/main/java/edu/wpi/first/wpilibj/AbstractInterruptTest.java

255 lines
8.5 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.
package edu.wpi.first.wpilibj;
import static org.hamcrest.Matchers.both;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import edu.wpi.first.wpilibj.test.AbstractComsSetup;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.After;
import org.junit.Test;
/**
* This class should not be run as a test explicitly. Instead it should be extended by tests that
* use DigitalSource.
*/
public abstract class AbstractInterruptTest extends AbstractComsSetup {
private DigitalSource m_source = null;
private DigitalSource getSource() {
if (m_source == null) {
m_source = giveSource();
}
return m_source;
}
@After
public void interruptTeardown() {
if (m_source != null) {
freeSource();
m_source = null;
}
}
/** Give the sensor source that interrupts can be attached to. */
abstract DigitalSource giveSource();
/** Cleans up the sensor source. This is only called if {@link #giveSource()} is called. */
abstract void freeSource();
/** Perform whatever action is required to set the interrupt high. */
abstract void setInterruptHigh();
/** Perform whatever action is required to set the interrupt low. */
abstract void setInterruptLow();
@Test(timeout = 1000)
public void testSingleInterruptsTriggering() {
// Given
// final InterruptCounter counter = new InterruptCounter();
// TestInterruptHandlerFunction function = new
// TestInterruptHandlerFunction(counter);
AtomicBoolean hasFired = new AtomicBoolean(false);
AtomicInteger counter = new AtomicInteger(0);
AtomicLong interruptFireTime = new AtomicLong();
try (AsynchronousInterrupt interrupt =
new AsynchronousInterrupt(
getSource(),
(a, b) -> {
interruptFireTime.set(RobotController.getFPGATime());
hasFired.set(true);
counter.incrementAndGet();
})) {
interrupt.enable();
setInterruptLow();
Timer.delay(0.01);
final long interruptTriggerTime = RobotController.getFPGATime();
setInterruptHigh();
while (!hasFired.get()) {
Timer.delay(0.005);
}
assertEquals("The interrupt did not fire the expected number of times", 1, counter.get());
final long range = 10000; // in microseconds
assertThat(
"The interrupt did not fire within the expected time period (values in milliseconds)",
interruptFireTime.get(),
both(greaterThan(interruptTriggerTime - range))
.and(lessThan(interruptTriggerTime + range)));
assertThat(
"The readRisingTimestamp() did not return the correct value (values in seconds)",
interrupt.getRisingTimestamp(),
both(greaterThan((interruptTriggerTime - range) / 1e6))
.and(lessThan((interruptTriggerTime + range) / 1e6)));
}
}
@Test(timeout = 2000)
public void testMultipleInterruptsTriggering() {
AtomicBoolean hasFired = new AtomicBoolean(false);
AtomicInteger counter = new AtomicInteger(0);
try (AsynchronousInterrupt interrupt =
new AsynchronousInterrupt(
getSource(),
(a, b) -> {
hasFired.set(true);
counter.incrementAndGet();
})) {
interrupt.enable();
final int fireCount = 50;
for (int i = 0; i < fireCount; i++) {
setInterruptLow();
setInterruptHigh();
// Wait for the interrupt to complete before moving on
while (!hasFired.getAndSet(false)) {
Timer.delay(0.005);
}
}
// Then
assertEquals(
"The interrupt did not fire the expected number of times", fireCount, counter.get());
}
}
/** The timeout length for this test in seconds. */
private static final int synchronousTimeout = 5;
@Test(timeout = (long) (synchronousTimeout * 1e3))
public void testSynchronousInterruptsTriggering() {
try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
final double synchronousDelay = synchronousTimeout / 2.0;
final Runnable runnable =
() -> {
Timer.delay(synchronousDelay);
setInterruptLow();
setInterruptHigh();
};
// When
// Note: the long time value is used because doubles can flip if the robot
// is left running for long enough
final long startTimeStamp = RobotController.getFPGATime();
new Thread(runnable).start();
// Delay for twice as long as the timeout so the test should fail first
interrupt.waitForInterrupt(synchronousTimeout * 2);
final long stopTimeStamp = RobotController.getFPGATime();
// Then
// The test will not have timed out and:
final double interruptRunTime = (stopTimeStamp - startTimeStamp) * 1e-6;
assertEquals(
"The interrupt did not run for the expected amount of time (units in seconds)",
synchronousDelay,
interruptRunTime,
0.1);
}
}
@Test(timeout = (long) (synchronousTimeout * 1e3))
public void testSynchronousInterruptsWaitResultTimeout() {
try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
SynchronousInterrupt.WaitResult result = interrupt.waitForInterrupt(synchronousTimeout / 2);
assertEquals(
"The interrupt did not time out correctly.",
result,
SynchronousInterrupt.WaitResult.kTimeout);
}
}
@Test(timeout = (long) (synchronousTimeout * 1e3))
public void testSynchronousInterruptsWaitResultRisingEdge() {
try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
final double synchronousDelay = synchronousTimeout / 2.0;
final Runnable runnable =
() -> {
Timer.delay(synchronousDelay);
setInterruptLow();
setInterruptHigh();
};
new Thread(runnable).start();
// Delay for twice as long as the timeout so the test should fail first
SynchronousInterrupt.WaitResult result = interrupt.waitForInterrupt(synchronousTimeout * 2);
assertEquals(
"The interrupt did not fire on the rising edge.",
result,
SynchronousInterrupt.WaitResult.kRisingEdge);
}
}
@Test(timeout = (long) (synchronousTimeout * 1e3))
public void testSynchronousInterruptsWaitResultFallingEdge() {
try (SynchronousInterrupt interrupt = new SynchronousInterrupt(getSource())) {
// Given
interrupt.setInterruptEdges(false, true);
final double synchronousDelay = synchronousTimeout / 2.0;
final Runnable runnable =
() -> {
Timer.delay(synchronousDelay);
setInterruptHigh();
setInterruptLow();
};
new Thread(runnable).start();
// Delay for twice as long as the timeout so the test should fail first
SynchronousInterrupt.WaitResult result = interrupt.waitForInterrupt(synchronousTimeout * 2);
assertEquals(
"The interrupt did not fire on the falling edge.",
result,
SynchronousInterrupt.WaitResult.kFallingEdge);
}
}
@Test(timeout = 4000)
public void testDisableStopsInterruptFiring() {
AtomicBoolean interruptComplete = new AtomicBoolean(false);
AtomicInteger counter = new AtomicInteger(0);
try (AsynchronousInterrupt interrupt =
new AsynchronousInterrupt(
getSource(),
(a, b) -> {
interruptComplete.set(true);
counter.incrementAndGet();
})) {
interrupt.enable();
final int fireCount = 50;
for (int i = 0; i < fireCount; i++) {
setInterruptLow();
setInterruptHigh();
// Wait for the interrupt to complete before moving on
while (!interruptComplete.getAndSet(false)) {
Timer.delay(0.005);
}
}
interrupt.disable();
for (int i = 0; i < fireCount; i++) {
setInterruptLow();
setInterruptHigh();
// Just wait because the interrupt should not fire
Timer.delay(0.005);
}
assertEquals(
"The interrupt did not fire the expected number of times", fireCount, counter.get());
}
}
}