2020-12-26 14:12:05 -08:00
|
|
|
// 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.
|
2016-01-02 03:02:34 -08:00
|
|
|
|
2014-08-04 14:19:01 -04:00
|
|
|
package edu.wpi.first.wpilibj;
|
|
|
|
|
|
2016-05-20 12:07:40 -04:00
|
|
|
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.assertSame;
|
|
|
|
|
import static org.junit.Assert.assertThat;
|
|
|
|
|
|
2020-12-29 22:45:16 -08:00
|
|
|
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;
|
|
|
|
|
|
2014-08-04 14:19:01 -04:00
|
|
|
/**
|
2016-05-20 12:07:40 -04:00
|
|
|
* This class should not be run as a test explicitly. Instead it should be extended by tests that
|
|
|
|
|
* use the InterruptableSensorBase.
|
2014-08-04 14:19:01 -04:00
|
|
|
*/
|
|
|
|
|
public abstract class AbstractInterruptTest extends AbstractComsSetup {
|
2016-05-20 12:07:40 -04:00
|
|
|
private InterruptableSensorBase m_interruptable = null;
|
2015-06-25 15:07:55 -04:00
|
|
|
|
|
|
|
|
private InterruptableSensorBase getInterruptable() {
|
2016-05-20 12:07:40 -04:00
|
|
|
if (m_interruptable == null) {
|
|
|
|
|
m_interruptable = giveInterruptableSensorBase();
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
2016-05-20 12:07:40 -04:00
|
|
|
return m_interruptable;
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@After
|
|
|
|
|
public void interruptTeardown() {
|
2016-05-20 12:07:40 -04:00
|
|
|
if (m_interruptable != null) {
|
2015-06-25 15:07:55 -04:00
|
|
|
freeInterruptableSensorBase();
|
2016-05-20 12:07:40 -04:00
|
|
|
m_interruptable = null;
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-29 22:45:16 -08:00
|
|
|
/** Give the interruptable sensor base that interrupts can be attached to. */
|
2015-06-25 15:07:55 -04:00
|
|
|
abstract InterruptableSensorBase giveInterruptableSensorBase();
|
|
|
|
|
|
|
|
|
|
/**
|
2016-05-20 12:07:40 -04:00
|
|
|
* Cleans up the interruptable sensor base. This is only called if {@link
|
|
|
|
|
* #giveInterruptableSensorBase()} is called.
|
2015-06-25 15:07:55 -04:00
|
|
|
*/
|
|
|
|
|
abstract void freeInterruptableSensorBase();
|
|
|
|
|
|
2020-12-29 22:45:16 -08:00
|
|
|
/** Perform whatever action is required to set the interrupt high. */
|
2015-06-25 15:07:55 -04:00
|
|
|
abstract void setInterruptHigh();
|
|
|
|
|
|
2020-12-29 22:45:16 -08:00
|
|
|
/** Perform whatever action is required to set the interrupt low. */
|
2015-06-25 15:07:55 -04:00
|
|
|
abstract void setInterruptLow();
|
|
|
|
|
|
|
|
|
|
private class InterruptCounter {
|
2016-05-20 12:07:40 -04:00
|
|
|
private final AtomicInteger m_count = new AtomicInteger();
|
2015-06-25 15:07:55 -04:00
|
|
|
|
|
|
|
|
void increment() {
|
2016-05-20 12:07:40 -04:00
|
|
|
m_count.addAndGet(1);
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int getCount() {
|
2016-05-20 12:07:40 -04:00
|
|
|
return m_count.get();
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
2016-05-20 12:07:40 -04:00
|
|
|
}
|
2015-06-25 15:07:55 -04:00
|
|
|
|
|
|
|
|
private class TestInterruptHandlerFunction extends InterruptHandlerFunction<InterruptCounter> {
|
2016-05-20 12:07:40 -04:00
|
|
|
protected final AtomicBoolean m_exceptionThrown = new AtomicBoolean(false);
|
2020-12-29 22:45:16 -08:00
|
|
|
/** Stores the time that the interrupt fires. */
|
2016-05-20 12:07:40 -04:00
|
|
|
final AtomicLong m_interruptFireTime = new AtomicLong();
|
2020-12-29 22:45:16 -08:00
|
|
|
/** Stores if the interrupt has completed at least once. */
|
2016-05-20 12:07:40 -04:00
|
|
|
final AtomicBoolean m_interruptComplete = new AtomicBoolean(false);
|
2020-12-29 22:45:16 -08:00
|
|
|
|
2016-05-20 12:07:40 -04:00
|
|
|
protected Exception m_ex;
|
|
|
|
|
final InterruptCounter m_counter;
|
2015-06-25 15:07:55 -04:00
|
|
|
|
|
|
|
|
TestInterruptHandlerFunction(InterruptCounter counter) {
|
2016-05-20 12:07:40 -04:00
|
|
|
m_counter = counter;
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void interruptFired(int interruptAssertedMask, InterruptCounter param) {
|
2017-12-10 21:52:49 -08:00
|
|
|
m_interruptFireTime.set(RobotController.getFPGATime());
|
2016-05-20 12:07:40 -04:00
|
|
|
m_counter.increment();
|
2015-06-25 15:07:55 -04:00
|
|
|
try {
|
|
|
|
|
// This won't cause the test to fail
|
2016-05-20 12:07:40 -04:00
|
|
|
assertSame(m_counter, param);
|
2015-06-25 15:07:55 -04:00
|
|
|
} catch (Exception ex) {
|
|
|
|
|
// So we must throw the exception within the test
|
2016-05-20 12:07:40 -04:00
|
|
|
m_exceptionThrown.set(true);
|
|
|
|
|
m_ex = ex;
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
2016-05-20 12:07:40 -04:00
|
|
|
m_interruptComplete.set(true);
|
|
|
|
|
}
|
2015-06-25 15:07:55 -04:00
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public InterruptCounter overridableParameter() {
|
2016-05-20 12:07:40 -04:00
|
|
|
return m_counter;
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
2016-05-20 12:07:40 -04:00
|
|
|
}
|
2015-06-25 15:07:55 -04:00
|
|
|
|
|
|
|
|
@Test(timeout = 1000)
|
|
|
|
|
public void testSingleInterruptsTriggering() throws Exception {
|
|
|
|
|
// Given
|
|
|
|
|
final InterruptCounter counter = new InterruptCounter();
|
|
|
|
|
TestInterruptHandlerFunction function = new TestInterruptHandlerFunction(counter);
|
|
|
|
|
|
|
|
|
|
// When
|
|
|
|
|
getInterruptable().requestInterrupts(function);
|
|
|
|
|
getInterruptable().enableInterrupts();
|
|
|
|
|
|
|
|
|
|
setInterruptLow();
|
|
|
|
|
Timer.delay(0.01);
|
|
|
|
|
// Note: Utility.getFPGATime() is used because double values can turn over
|
|
|
|
|
// after the robot has been running for a long time
|
2017-12-10 21:52:49 -08:00
|
|
|
final long interruptTriggerTime = RobotController.getFPGATime();
|
2015-06-25 15:07:55 -04:00
|
|
|
setInterruptHigh();
|
|
|
|
|
|
|
|
|
|
// Delay until the interrupt is complete
|
2016-05-20 12:07:40 -04:00
|
|
|
while (!function.m_interruptComplete.get()) {
|
2019-11-20 23:13:15 -05:00
|
|
|
Timer.delay(0.005);
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Then
|
|
|
|
|
assertEquals("The interrupt did not fire the expected number of times", 1, counter.getCount());
|
|
|
|
|
// If the test within the interrupt failed
|
2016-05-20 12:07:40 -04:00
|
|
|
if (function.m_exceptionThrown.get()) {
|
|
|
|
|
throw function.m_ex;
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
final long range = 10000; // in microseconds
|
|
|
|
|
assertThat(
|
|
|
|
|
"The interrupt did not fire within the expected time period (values in milliseconds)",
|
2016-05-20 12:07:40 -04:00
|
|
|
function.m_interruptFireTime.get(),
|
2020-12-29 22:45:16 -08:00
|
|
|
both(greaterThan(interruptTriggerTime - range))
|
|
|
|
|
.and(lessThan(interruptTriggerTime + range)));
|
2015-06-25 15:07:55 -04:00
|
|
|
assertThat(
|
|
|
|
|
"The readRisingTimestamp() did not return the correct value (values in seconds)",
|
|
|
|
|
getInterruptable().readRisingTimestamp(),
|
2020-12-29 22:45:16 -08:00
|
|
|
both(greaterThan((interruptTriggerTime - range) / 1e6))
|
|
|
|
|
.and(lessThan((interruptTriggerTime + range) / 1e6)));
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
2015-11-01 09:11:52 -08:00
|
|
|
@Test(timeout = 2000)
|
2019-08-01 01:19:48 -04:00
|
|
|
public void testMultipleInterruptsTriggering() {
|
2015-06-25 15:07:55 -04:00
|
|
|
// Given
|
|
|
|
|
final InterruptCounter counter = new InterruptCounter();
|
|
|
|
|
TestInterruptHandlerFunction function = new TestInterruptHandlerFunction(counter);
|
|
|
|
|
|
|
|
|
|
// When
|
|
|
|
|
getInterruptable().requestInterrupts(function);
|
|
|
|
|
getInterruptable().enableInterrupts();
|
|
|
|
|
|
|
|
|
|
final int fireCount = 50;
|
|
|
|
|
for (int i = 0; i < fireCount; i++) {
|
|
|
|
|
setInterruptLow();
|
|
|
|
|
setInterruptHigh();
|
|
|
|
|
// Wait for the interrupt to complete before moving on
|
2016-05-20 12:07:40 -04:00
|
|
|
while (!function.m_interruptComplete.getAndSet(false)) {
|
2019-11-20 23:13:15 -05:00
|
|
|
Timer.delay(0.005);
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Then
|
2020-12-29 22:45:16 -08:00
|
|
|
assertEquals(
|
|
|
|
|
"The interrupt did not fire the expected number of times", fireCount, counter.getCount());
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
2020-12-29 22:45:16 -08:00
|
|
|
/** The timeout length for this test in seconds. */
|
2015-06-25 15:07:55 -04:00
|
|
|
private static final int synchronousTimeout = 5;
|
|
|
|
|
|
|
|
|
|
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
|
|
|
|
public void testSynchronousInterruptsTriggering() {
|
|
|
|
|
// Given
|
|
|
|
|
getInterruptable().requestInterrupts();
|
|
|
|
|
|
2019-11-20 23:13:15 -05:00
|
|
|
final double synchronousDelay = synchronousTimeout / 2.0;
|
2020-12-29 22:45:16 -08:00
|
|
|
final Runnable runnable =
|
|
|
|
|
() -> {
|
|
|
|
|
Timer.delay(synchronousDelay);
|
|
|
|
|
setInterruptLow();
|
|
|
|
|
setInterruptHigh();
|
|
|
|
|
};
|
2015-06-25 15:07:55 -04:00
|
|
|
|
|
|
|
|
// When
|
|
|
|
|
|
|
|
|
|
// Note: the long time value is used because doubles can flip if the robot
|
|
|
|
|
// is left running for long enough
|
2017-12-10 21:52:49 -08:00
|
|
|
final long startTimeStamp = RobotController.getFPGATime();
|
2016-05-20 12:07:40 -04:00
|
|
|
new Thread(runnable).start();
|
2015-06-25 15:07:55 -04:00
|
|
|
// Delay for twice as long as the timeout so the test should fail first
|
|
|
|
|
getInterruptable().waitForInterrupt(synchronousTimeout * 2);
|
2017-12-10 21:52:49 -08:00
|
|
|
final long stopTimeStamp = RobotController.getFPGATime();
|
2015-06-25 15:07:55 -04:00
|
|
|
|
|
|
|
|
// Then
|
|
|
|
|
// The test will not have timed out and:
|
|
|
|
|
final double interruptRunTime = (stopTimeStamp - startTimeStamp) * 1e-6;
|
2020-12-29 22:45:16 -08:00
|
|
|
assertEquals(
|
|
|
|
|
"The interrupt did not run for the expected amount of time (units in seconds)",
|
|
|
|
|
synchronousDelay,
|
|
|
|
|
interruptRunTime,
|
|
|
|
|
0.1);
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
2015-12-24 21:10:47 -08:00
|
|
|
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
|
|
|
|
public void testSynchronousInterruptsWaitResultTimeout() {
|
|
|
|
|
// Given
|
|
|
|
|
getInterruptable().requestInterrupts();
|
|
|
|
|
|
2020-12-29 22:45:16 -08:00
|
|
|
// Don't fire interrupt. Expect it to timeout.
|
|
|
|
|
InterruptableSensorBase.WaitResult result =
|
|
|
|
|
getInterruptable().waitForInterrupt(synchronousTimeout / 2);
|
2015-12-24 21:10:47 -08:00
|
|
|
|
2020-12-29 22:45:16 -08:00
|
|
|
assertEquals(
|
|
|
|
|
"The interrupt did not time out correctly.",
|
|
|
|
|
result,
|
|
|
|
|
InterruptableSensorBase.WaitResult.kTimeout);
|
2015-12-24 21:10:47 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
|
|
|
|
public void testSynchronousInterruptsWaitResultRisingEdge() {
|
|
|
|
|
// Given
|
|
|
|
|
getInterruptable().requestInterrupts();
|
|
|
|
|
|
2019-11-20 23:13:15 -05:00
|
|
|
final double synchronousDelay = synchronousTimeout / 2.0;
|
2020-12-29 22:45:16 -08:00
|
|
|
final Runnable runnable =
|
|
|
|
|
() -> {
|
|
|
|
|
Timer.delay(synchronousDelay);
|
|
|
|
|
setInterruptLow();
|
|
|
|
|
setInterruptHigh();
|
|
|
|
|
};
|
2015-12-24 21:10:47 -08:00
|
|
|
|
2016-05-20 12:07:40 -04:00
|
|
|
new Thread(runnable).start();
|
2015-12-24 21:10:47 -08:00
|
|
|
// Delay for twice as long as the timeout so the test should fail first
|
2020-12-29 22:45:16 -08:00
|
|
|
InterruptableSensorBase.WaitResult result =
|
|
|
|
|
getInterruptable().waitForInterrupt(synchronousTimeout * 2);
|
2015-12-24 21:10:47 -08:00
|
|
|
|
2020-12-29 22:45:16 -08:00
|
|
|
assertEquals(
|
|
|
|
|
"The interrupt did not fire on the rising edge.",
|
|
|
|
|
result,
|
2016-05-20 12:07:40 -04:00
|
|
|
InterruptableSensorBase.WaitResult.kRisingEdge);
|
2015-12-24 21:10:47 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test(timeout = (long) (synchronousTimeout * 1e3))
|
|
|
|
|
public void testSynchronousInterruptsWaitResultFallingEdge() {
|
|
|
|
|
// Given
|
|
|
|
|
getInterruptable().requestInterrupts();
|
|
|
|
|
getInterruptable().setUpSourceEdge(false, true);
|
|
|
|
|
|
2019-11-20 23:13:15 -05:00
|
|
|
final double synchronousDelay = synchronousTimeout / 2.0;
|
2020-12-29 22:45:16 -08:00
|
|
|
final Runnable runnable =
|
|
|
|
|
() -> {
|
|
|
|
|
Timer.delay(synchronousDelay);
|
|
|
|
|
setInterruptHigh();
|
|
|
|
|
setInterruptLow();
|
|
|
|
|
};
|
2015-12-24 21:10:47 -08:00
|
|
|
|
2016-05-20 12:07:40 -04:00
|
|
|
new Thread(runnable).start();
|
2015-12-24 21:10:47 -08:00
|
|
|
// Delay for twice as long as the timeout so the test should fail first
|
2020-12-29 22:45:16 -08:00
|
|
|
InterruptableSensorBase.WaitResult result =
|
|
|
|
|
getInterruptable().waitForInterrupt(synchronousTimeout * 2);
|
2015-12-24 21:10:47 -08:00
|
|
|
|
2020-12-29 22:45:16 -08:00
|
|
|
assertEquals(
|
|
|
|
|
"The interrupt did not fire on the falling edge.",
|
|
|
|
|
result,
|
2016-05-20 12:07:40 -04:00
|
|
|
InterruptableSensorBase.WaitResult.kFallingEdge);
|
2015-12-24 21:10:47 -08:00
|
|
|
}
|
|
|
|
|
|
2015-11-01 09:11:52 -08:00
|
|
|
@Test(timeout = 4000)
|
2015-06-25 15:07:55 -04:00
|
|
|
public void testDisableStopsInterruptFiring() {
|
|
|
|
|
final InterruptCounter counter = new InterruptCounter();
|
|
|
|
|
TestInterruptHandlerFunction function = new TestInterruptHandlerFunction(counter);
|
|
|
|
|
|
|
|
|
|
// When
|
|
|
|
|
getInterruptable().requestInterrupts(function);
|
|
|
|
|
getInterruptable().enableInterrupts();
|
|
|
|
|
|
|
|
|
|
final int fireCount = 50;
|
|
|
|
|
for (int i = 0; i < fireCount; i++) {
|
|
|
|
|
setInterruptLow();
|
|
|
|
|
setInterruptHigh();
|
|
|
|
|
// Wait for the interrupt to complete before moving on
|
2016-05-20 12:07:40 -04:00
|
|
|
while (!function.m_interruptComplete.getAndSet(false)) {
|
2019-11-20 23:13:15 -05:00
|
|
|
Timer.delay(0.005);
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
getInterruptable().disableInterrupts();
|
|
|
|
|
// TestBench.out().println("Finished disabling the robot");
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < fireCount; i++) {
|
|
|
|
|
setInterruptLow();
|
|
|
|
|
setInterruptHigh();
|
|
|
|
|
// Just wait because the interrupt should not fire
|
2019-11-20 23:13:15 -05:00
|
|
|
Timer.delay(0.005);
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Then
|
2020-12-29 22:45:16 -08:00
|
|
|
assertEquals(
|
|
|
|
|
"The interrupt did not fire the expected number of times", fireCount, counter.getCount());
|
2015-06-25 15:07:55 -04:00
|
|
|
}
|
2014-08-04 14:19:01 -04:00
|
|
|
}
|