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

312 lines
10 KiB
Java
Raw Normal View History

/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2008-2014. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the 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.assertSame;
import static org.junit.Assert.assertThat;
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;
import edu.wpi.first.wpilibj.test.AbstractComsSetup;
/**
* This class should not be run as a test explicitly. Instead it should be
* extended by tests that use the InterruptableSensorBase
*
* @author jonathanleitschuh
*
*/
public abstract class AbstractInterruptTest extends AbstractComsSetup {
private InterruptableSensorBase interruptable = null;
private InterruptableSensorBase getInterruptable() {
if (interruptable == null) {
interruptable = giveInterruptableSensorBase();
}
return interruptable;
}
@After
public void interruptTeardown() {
if (interruptable != null) {
freeInterruptableSensorBase();
interruptable = null;
}
}
/**
* Give the interruptible sensor base that interrupts can be attached to.
*$
* @return
*/
abstract InterruptableSensorBase giveInterruptableSensorBase();
/**
* Cleans up the interruptible sensor base. This is only called if
* {@link #giveInterruptableSensorBase()} is called.
*/
abstract void freeInterruptableSensorBase();
/**
* 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();
private class InterruptCounter {
private final AtomicInteger count = new AtomicInteger();
void increment() {
count.addAndGet(1);
}
int getCount() {
return count.get();
}
};
private class TestInterruptHandlerFunction extends InterruptHandlerFunction<InterruptCounter> {
protected final AtomicBoolean exceptionThrown = new AtomicBoolean(false);
/** Stores the time that the interrupt fires */
final AtomicLong interruptFireTime = new AtomicLong();
/** Stores if the interrupt has completed at least once */
final AtomicBoolean interruptComplete = new AtomicBoolean(false);
protected Exception ex;
final InterruptCounter counter;
TestInterruptHandlerFunction(InterruptCounter counter) {
this.counter = counter;
}
@Override
public void interruptFired(int interruptAssertedMask, InterruptCounter param) {
interruptFireTime.set(Utility.getFPGATime());
counter.increment();
try {
// This won't cause the test to fail
assertSame(counter, param);
} catch (Exception ex) {
// So we must throw the exception within the test
exceptionThrown.set(true);
this.ex = ex;
}
interruptComplete.set(true);
};
@Override
public InterruptCounter overridableParameter() {
return counter;
}
};
@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
final long interruptTriggerTime = Utility.getFPGATime();
setInterruptHigh();
// Delay until the interrupt is complete
while (!function.interruptComplete.get()) {
Timer.delay(.005);
}
// Then
assertEquals("The interrupt did not fire the expected number of times", 1, counter.getCount());
// If the test within the interrupt failed
if (function.exceptionThrown.get()) {
throw function.ex;
}
final long range = 10000; // in microseconds
assertThat(
"The interrupt did not fire within the expected time period (values in milliseconds)",
function.interruptFireTime.get(),
both(greaterThan(interruptTriggerTime - range)).and(lessThan(interruptTriggerTime + range)));
assertThat(
"The readRisingTimestamp() did not return the correct value (values in seconds)",
getInterruptable().readRisingTimestamp(),
both(greaterThan((interruptTriggerTime - range) / 1e6)).and(
lessThan((interruptTriggerTime + range) / 1e6)));
}
@Test(timeout = 2000)
public void testMultipleInterruptsTriggering() throws Exception {
// 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
while (!function.interruptComplete.getAndSet(false)) {
Timer.delay(.005);
}
}
// Then
assertEquals("The interrupt did not fire the expected number of times", fireCount,
counter.getCount());
}
/** The timeout length for this test in seconds */
private static final int synchronousTimeout = 5;
@Test(timeout = (long) (synchronousTimeout * 1e3))
public void testSynchronousInterruptsTriggering() {
// Given
getInterruptable().requestInterrupts();
final double synchronousDelay = synchronousTimeout / 2.;
Runnable r = new Runnable() {
@Override
public void run() {
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 = Utility.getFPGATime();
new Thread(r).start();
// Delay for twice as long as the timeout so the test should fail first
getInterruptable().waitForInterrupt(synchronousTimeout * 2);
final long stopTimeStamp = Utility.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, .1);
}
@Test(timeout = (long) (synchronousTimeout * 1e3))
public void testSynchronousInterruptsWaitResultTimeout() {
// Given
getInterruptable().requestInterrupts();
//Don't fire interrupt. Expect it to timeout.
InterruptableSensorBase.WaitResult result = getInterruptable().waitForInterrupt(synchronousTimeout / 2);
assertEquals("The interrupt did not time out correctly.", result, InterruptableSensorBase.WaitResult.kTimeout);
}
@Test(timeout = (long) (synchronousTimeout * 1e3))
public void testSynchronousInterruptsWaitResultRisingEdge() {
// Given
getInterruptable().requestInterrupts();
final double synchronousDelay = synchronousTimeout / 2.;
Runnable r = new Runnable() {
@Override
public void run() {
Timer.delay(synchronousDelay);
setInterruptLow();
setInterruptHigh();
}
};
new Thread(r).start();
// Delay for twice as long as the timeout so the test should fail first
InterruptableSensorBase.WaitResult result = getInterruptable().waitForInterrupt(synchronousTimeout * 2);
assertEquals("The interrupt did not fire on the rising edge.", result, InterruptableSensorBase.WaitResult.kRisingEdge);
}
@Test(timeout = (long) (synchronousTimeout * 1e3))
public void testSynchronousInterruptsWaitResultFallingEdge() {
// Given
getInterruptable().requestInterrupts();
getInterruptable().setUpSourceEdge(false, true);
final double synchronousDelay = synchronousTimeout / 2.;
Runnable r = new Runnable() {
@Override
public void run() {
Timer.delay(synchronousDelay);
setInterruptHigh();
setInterruptLow();
}
};
new Thread(r).start();
// Delay for twice as long as the timeout so the test should fail first
InterruptableSensorBase.WaitResult result = getInterruptable().waitForInterrupt(synchronousTimeout * 2);
assertEquals("The interrupt did not fire on the falling edge.", result, InterruptableSensorBase.WaitResult.kFallingEdge);
}
@Test(timeout = 4000)
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
while (!function.interruptComplete.getAndSet(false)) {
Timer.delay(.005);
}
}
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
Timer.delay(.005);
}
// Then
assertEquals("The interrupt did not fire the expected number of times", fireCount,
counter.getCount());
}
}