Update to 2018_v4 image and new build system. (#598)

* Revert "Force OpenCV to 3.1.0 (#602)"

This reverts commit 50ed55e8e2.

* Removes Simulation

* Removes old build system

* Removes old gtest

* Adds new gmock and gtest

* Updates to new ni-libraries

* removes MyRobot (to be replaced)

* moves files to new location

* Adds new sim backend and new test executables

* updates .styleguide and .gitignore

* Changes cpp WPILibVersion to a function

MSVC throws an AV with the old version.

* Disables USBCamera on all systems except for linux

* 2018 NI Libraries

* New build system
This commit is contained in:
Thad House
2017-08-18 21:35:53 -07:00
committed by Peter Johnson
parent 50ed55e8e2
commit e1195e8b9d
1024 changed files with 64481 additions and 61340 deletions

View File

@@ -0,0 +1,131 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "AnalogInput.h"
#include "AnalogOutput.h"
#include "AnalogTrigger.h"
#include "Counter.h"
#include "TestBench.h"
#include "Timer.h"
#include "gtest/gtest.h"
using namespace frc;
static const double kDelayTime = 0.01;
/**
* A fixture with an analog input and an analog output wired together
*/
class AnalogLoopTest : public testing::Test {
protected:
AnalogInput* m_input;
AnalogOutput* m_output;
void SetUp() override {
m_input = new AnalogInput(TestBench::kFakeAnalogOutputChannel);
m_output = new AnalogOutput(TestBench::kAnalogOutputChannel);
}
void TearDown() override {
delete m_input;
delete m_output;
}
};
/**
* Test analog inputs and outputs by setting one and making sure the other
* matches.
*/
TEST_F(AnalogLoopTest, AnalogInputWorks) {
// Set the output voltage and check if the input measures the same voltage
for (int32_t i = 0; i < 50; i++) {
m_output->SetVoltage(i / 10.0);
Wait(kDelayTime);
EXPECT_NEAR(m_output->GetVoltage(), m_input->GetVoltage(), 0.01);
}
}
/**
* Test if we can use an analog trigger to check if the output is within a
* range correctly.
*/
TEST_F(AnalogLoopTest, AnalogTriggerWorks) {
AnalogTrigger trigger(m_input);
trigger.SetLimitsVoltage(2.0, 3.0);
m_output->SetVoltage(1.0);
Wait(kDelayTime);
EXPECT_FALSE(trigger.GetInWindow())
<< "Analog trigger is in the window (2V, 3V)";
EXPECT_FALSE(trigger.GetTriggerState()) << "Analog trigger is on";
m_output->SetVoltage(2.5);
Wait(kDelayTime);
EXPECT_TRUE(trigger.GetInWindow())
<< "Analog trigger is not in the window (2V, 3V)";
EXPECT_FALSE(trigger.GetTriggerState()) << "Analog trigger is on";
m_output->SetVoltage(4.0);
Wait(kDelayTime);
EXPECT_FALSE(trigger.GetInWindow())
<< "Analog trigger is in the window (2V, 3V)";
EXPECT_TRUE(trigger.GetTriggerState()) << "Analog trigger is not on";
}
/**
* Test if we can count the right number of ticks from an analog trigger with
* a counter.
*/
TEST_F(AnalogLoopTest, AnalogTriggerCounterWorks) {
AnalogTrigger trigger(m_input);
trigger.SetLimitsVoltage(2.0, 3.0);
Counter counter(trigger);
// Turn the analog output low and high 50 times
for (int32_t i = 0; i < 50; i++) {
m_output->SetVoltage(1.0);
Wait(kDelayTime);
m_output->SetVoltage(4.0);
Wait(kDelayTime);
}
// The counter should be 50
EXPECT_EQ(50, counter.Get())
<< "Analog trigger counter did not count 50 ticks";
}
static void InterruptHandler(uint32_t interruptAssertedMask, void* param) {
*reinterpret_cast<int32_t*>(param) = 12345;
}
TEST_F(AnalogLoopTest, AsynchronusInterruptWorks) {
int32_t param = 0;
AnalogTrigger trigger(m_input);
trigger.SetLimitsVoltage(2.0, 3.0);
// Given an interrupt handler that sets an int32_t to 12345
std::shared_ptr<AnalogTriggerOutput> triggerOutput =
trigger.CreateOutput(AnalogTriggerType::kState);
triggerOutput->RequestInterrupts(InterruptHandler, &param);
triggerOutput->EnableInterrupts();
// If the analog output moves from below to above the window
m_output->SetVoltage(0.0);
Wait(kDelayTime);
m_output->SetVoltage(5.0);
triggerOutput->CancelInterrupts();
// Then the int32_t should be 12345
Wait(kDelayTime);
EXPECT_EQ(12345, param) << "The interrupt did not run.";
}

View File

@@ -0,0 +1,50 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "AnalogPotentiometer.h" // NOLINT(build/include_order)
#include "AnalogOutput.h"
#include "ControllerPower.h"
#include "TestBench.h"
#include "Timer.h"
#include "gtest/gtest.h"
using namespace frc;
static const double kScale = 270.0;
static const double kAngle = 180.0;
class AnalogPotentiometerTest : public testing::Test {
protected:
AnalogOutput* m_fakePot;
AnalogPotentiometer* m_pot;
void SetUp() override {
m_fakePot = new AnalogOutput(TestBench::kAnalogOutputChannel);
m_pot =
new AnalogPotentiometer(TestBench::kFakeAnalogOutputChannel, kScale);
}
void TearDown() override {
delete m_fakePot;
delete m_pot;
}
};
TEST_F(AnalogPotentiometerTest, TestInitialSettings) {
m_fakePot->SetVoltage(0.0);
Wait(0.1);
EXPECT_NEAR(0.0, m_pot->Get(), 5.0)
<< "The potentiometer did not initialize to 0.";
}
TEST_F(AnalogPotentiometerTest, TestRangeValues) {
m_fakePot->SetVoltage(kAngle / kScale * ControllerPower::GetVoltage5V());
Wait(0.1);
EXPECT_NEAR(kAngle, m_pot->Get(), 2.0)
<< "The potentiometer did not measure the correct angle.";
}

View File

@@ -0,0 +1,30 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "BuiltInAccelerometer.h" // NOLINT(build/include_order)
#include "Timer.h"
#include "gtest/gtest.h"
using namespace frc;
static constexpr double kAccelerationTolerance = 0.1;
/**
* There's not much we can automatically test with the on-board accelerometer,
* but checking for gravity is probably good enough to tell that it's working.
*/
TEST(BuiltInAccelerometerTest, Accelerometer) {
BuiltInAccelerometer accelerometer;
/* The testbench sometimes shakes a little from a previous test. Give it
some time. */
Wait(1.0);
ASSERT_NEAR(0.0, accelerometer.GetX(), kAccelerationTolerance);
ASSERT_NEAR(1.0, accelerometer.GetY(), kAccelerationTolerance);
ASSERT_NEAR(0.0, accelerometer.GetZ(), kAccelerationTolerance);
}

View File

@@ -0,0 +1,211 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2015-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "CircularBuffer.h" // NOLINT(build/include_order)
#include <array>
#include "gtest/gtest.h"
using namespace frc;
static const std::array<double, 10> values = {
751.848, 766.366, 342.657, 234.252, 716.126,
132.344, 445.697, 22.727, 421.125, 799.913};
static const std::array<double, 8> pushFrontOut = {
799.913, 421.125, 22.727, 445.697, 132.344, 716.126, 234.252, 342.657};
static const std::array<double, 8> pushBackOut = {
342.657, 234.252, 716.126, 132.344, 445.697, 22.727, 421.125, 799.913};
TEST(CircularBufferTest, PushFrontTest) {
CircularBuffer<double> queue(8);
for (auto& value : values) {
queue.PushFront(value);
}
for (size_t i = 0; i < pushFrontOut.size(); i++) {
EXPECT_EQ(pushFrontOut[i], queue[i]);
}
}
TEST(CircularBufferTest, PushBackTest) {
CircularBuffer<double> queue(8);
for (auto& value : values) {
queue.PushBack(value);
}
for (size_t i = 0; i < pushBackOut.size(); i++) {
EXPECT_EQ(pushBackOut[i], queue[i]);
}
}
TEST(CircularBufferTest, PushPopTest) {
CircularBuffer<double> queue(3);
// Insert three elements into the buffer
queue.PushBack(1.0);
queue.PushBack(2.0);
queue.PushBack(3.0);
EXPECT_EQ(1.0, queue[0]);
EXPECT_EQ(2.0, queue[1]);
EXPECT_EQ(3.0, queue[2]);
/*
* The buffer is full now, so pushing subsequent elements will overwrite the
* front-most elements.
*/
queue.PushBack(4.0); // Overwrite 1 with 4
// The buffer now contains 2, 3 and 4
EXPECT_EQ(2.0, queue[0]);
EXPECT_EQ(3.0, queue[1]);
EXPECT_EQ(4.0, queue[2]);
queue.PushBack(5.0); // Overwrite 2 with 5
// The buffer now contains 3, 4 and 5
EXPECT_EQ(3.0, queue[0]);
EXPECT_EQ(4.0, queue[1]);
EXPECT_EQ(5.0, queue[2]);
EXPECT_EQ(5.0, queue.PopBack()); // 5 is removed
// The buffer now contains 3 and 4
EXPECT_EQ(3.0, queue[0]);
EXPECT_EQ(4.0, queue[1]);
EXPECT_EQ(3.0, queue.PopFront()); // 3 is removed
// Leaving only one element with value == 4
EXPECT_EQ(4.0, queue[0]);
}
TEST(CircularBufferTest, ResetTest) {
CircularBuffer<double> queue(5);
for (size_t i = 1; i < 6; i++) {
queue.PushBack(i);
}
queue.Reset();
for (size_t i = 0; i < 5; i++) {
EXPECT_EQ(0.0, queue[i]);
}
}
TEST(CircularBufferTest, ResizeTest) {
CircularBuffer<double> queue(5);
/* Buffer contains {1, 2, 3, _, _}
* ^ front
*/
queue.PushBack(1.0);
queue.PushBack(2.0);
queue.PushBack(3.0);
queue.Resize(2);
EXPECT_EQ(1.0, queue[0]);
EXPECT_EQ(2.0, queue[1]);
queue.Resize(5);
EXPECT_EQ(1.0, queue[0]);
EXPECT_EQ(2.0, queue[1]);
queue.Reset();
/* Buffer contains {_, 1, 2, 3, _}
* ^ front
*/
queue.PushBack(0.0);
queue.PushBack(1.0);
queue.PushBack(2.0);
queue.PushBack(3.0);
queue.PopFront();
queue.Resize(2);
EXPECT_EQ(1.0, queue[0]);
EXPECT_EQ(2.0, queue[1]);
queue.Resize(5);
EXPECT_EQ(1.0, queue[0]);
EXPECT_EQ(2.0, queue[1]);
queue.Reset();
/* Buffer contains {_, _, 1, 2, 3}
* ^ front
*/
queue.PushBack(0.0);
queue.PushBack(0.0);
queue.PushBack(1.0);
queue.PushBack(2.0);
queue.PushBack(3.0);
queue.PopFront();
queue.PopFront();
queue.Resize(2);
EXPECT_EQ(1.0, queue[0]);
EXPECT_EQ(2.0, queue[1]);
queue.Resize(5);
EXPECT_EQ(1.0, queue[0]);
EXPECT_EQ(2.0, queue[1]);
queue.Reset();
/* Buffer contains {3, _, _, 1, 2}
* ^ front
*/
queue.PushBack(3.0);
queue.PushFront(2.0);
queue.PushFront(1.0);
queue.Resize(2);
EXPECT_EQ(1.0, queue[0]);
EXPECT_EQ(2.0, queue[1]);
queue.Resize(5);
EXPECT_EQ(1.0, queue[0]);
EXPECT_EQ(2.0, queue[1]);
queue.Reset();
/* Buffer contains {2, 3, _, _, 1}
* ^ front
*/
queue.PushBack(2.0);
queue.PushBack(3.0);
queue.PushFront(1.0);
queue.Resize(2);
EXPECT_EQ(1.0, queue[0]);
EXPECT_EQ(2.0, queue[1]);
queue.Resize(5);
EXPECT_EQ(1.0, queue[0]);
EXPECT_EQ(2.0, queue[1]);
// Test PushBack() after resize
queue.PushBack(3.0);
EXPECT_EQ(1.0, queue[0]);
EXPECT_EQ(2.0, queue[1]);
EXPECT_EQ(3.0, queue[2]);
// Test PushFront() after resize
queue.PushFront(4.0);
EXPECT_EQ(4.0, queue[0]);
EXPECT_EQ(1.0, queue[1]);
EXPECT_EQ(2.0, queue[2]);
EXPECT_EQ(3.0, queue[3]);
}

View File

@@ -0,0 +1,298 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2016-2017. 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. */
/*----------------------------------------------------------------------------*/
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <thread>
#include "HAL/cpp/priority_condition_variable.h"
#include "HAL/cpp/priority_mutex.h"
#include "TestBench.h"
#include "gtest/gtest.h"
namespace wpilib {
namespace testing {
// Tests that the condition variable class which we wrote ourselves actually
// does work.
class ConditionVariableTest : public ::testing::Test {
protected:
typedef std::unique_lock<hal::priority_mutex> priority_lock;
// Condition variable to test.
hal::priority_condition_variable m_cond;
// Mutex to pass to condition variable when waiting.
hal::priority_mutex m_mutex;
// flags for testing when threads are completed.
std::atomic<bool> m_done1{false}, m_done2{false};
// Threads to use for testing. We want multiple threads to ensure that it
// behaves correctly when multiple processes are waiting on a signal.
std::thread m_watcher1, m_watcher2;
// Information for when running with predicates.
std::atomic<bool> m_pred_var{false};
void ShortSleep(uint32_t time = 10) {
std::this_thread::sleep_for(std::chrono::milliseconds(time));
}
// Start up the given threads with a wait function. The wait function should
// call some version of m_cond.wait and should take as an argument a reference
// to an std::atomic<bool> which it will set to true when it is ready to have
// join called on it.
template <class Function>
void StartThreads(Function wait) {
m_watcher1 = std::thread(wait, std::ref(m_done1));
m_watcher2 = std::thread(wait, std::ref(m_done2));
// Wait briefly to let the lock be unlocked.
ShortSleep();
bool locked = m_mutex.try_lock();
if (locked) m_mutex.unlock();
EXPECT_TRUE(locked) << "The condition variable failed to unlock the lock.";
}
void NotifyAll() { m_cond.notify_all(); }
void NotifyOne() { m_cond.notify_one(); }
// Test that all the threads are notified by a notify_all() call.
void NotifyAllTest() {
NotifyAll();
// Wait briefly to let the lock be re-locked.
ShortSleep();
EXPECT_TRUE(m_done1) << "watcher1 failed to be notified.";
EXPECT_TRUE(m_done2) << "watcher2 failed to be notified.";
}
// For use when testing predicates. First tries signalling the threads with
// the predicate set to false (and ensures that they do not activate) and then
// tests with the predicate set to true.
void PredicateTest() {
m_pred_var = false;
NotifyAll();
ShortSleep();
EXPECT_FALSE(m_done1) << "watcher1 didn't pay attention to its predicate.";
EXPECT_FALSE(m_done2) << "watcher2 didn't pay attention to its predicate.";
m_pred_var = true;
NotifyAllTest();
}
// Used by the WaitFor and WaitUntil tests to test that, without a predicate,
// the timeout works properly.
void WaitTimeTest(bool wait_for) {
std::atomic<bool> timed_out{true};
auto wait_until = [this, &timed_out, wait_for](std::atomic<bool>& done) {
priority_lock lock(m_mutex);
done = false;
if (wait_for) {
auto wait_time = std::chrono::milliseconds(100);
timed_out = m_cond.wait_for(lock, wait_time) == std::cv_status::timeout;
} else {
auto wait_time =
std::chrono::system_clock::now() + std::chrono::milliseconds(100);
timed_out =
m_cond.wait_until(lock, wait_time) == std::cv_status::timeout;
}
EXPECT_TRUE(lock.owns_lock())
<< "The condition variable should have reacquired the lock.";
done = true;
};
// First, test without timing out.
timed_out = true;
StartThreads(wait_until);
NotifyAllTest();
EXPECT_FALSE(timed_out) << "The watcher should not have timed out.";
TearDown();
// Next, test and time out.
timed_out = false;
StartThreads(wait_until);
ShortSleep(110);
EXPECT_TRUE(m_done1) << "watcher1 should have timed out.";
EXPECT_TRUE(m_done2) << "watcher2 should have timed out.";
EXPECT_TRUE(timed_out) << "The watcher should have timed out.";
}
// For use with tests that have a timeout and a predicate.
void WaitTimePredicateTest(bool wait_for) {
// The condition_variable return value from the wait_for or wait_until
// function should in the case of having a predicate, by a boolean. If the
// predicate is true, then the return value will always be true. If the
// condition times out and, at the time of the timeout, the predicate is
// false, the return value will be false.
std::atomic<bool> retval{true};
auto predicate = [this]() -> bool { return m_pred_var; };
auto wait_until = [this, &retval, predicate,
wait_for](std::atomic<bool>& done) {
priority_lock lock(m_mutex);
done = false;
if (wait_for) {
auto wait_time = std::chrono::milliseconds(100);
retval = m_cond.wait_for(lock, wait_time, predicate);
} else {
auto wait_time =
std::chrono::system_clock::now() + std::chrono::milliseconds(100);
retval = m_cond.wait_until(lock, wait_time, predicate);
}
EXPECT_TRUE(lock.owns_lock())
<< "The condition variable should have reacquired the lock.";
done = true;
};
// Test without timing out and with the predicate set to true.
retval = true;
m_pred_var = true;
StartThreads(wait_until);
NotifyAllTest();
EXPECT_TRUE(retval) << "The watcher should not have timed out.";
TearDown();
// Test with timing out and with the predicate set to true.
retval = false;
m_pred_var = false;
StartThreads(wait_until);
ShortSleep(110);
EXPECT_TRUE(m_done1) << "watcher1 should have finished.";
EXPECT_TRUE(m_done2) << "watcher2 should have finished.";
EXPECT_FALSE(retval) << "The watcher should have timed out.";
TearDown();
// Test without timing out and run the PredicateTest().
retval = false;
StartThreads(wait_until);
PredicateTest();
EXPECT_TRUE(retval) << "The return value should have been true.";
TearDown();
// Test with timing out and the predicate set to true while we are waiting
// for the condition variable to time out.
retval = true;
StartThreads(wait_until);
ShortSleep();
m_pred_var = true;
ShortSleep(110);
EXPECT_TRUE(retval) << "The return value should have been true.";
}
virtual void TearDown() {
// If a thread has not completed, then continuing will cause the tests to
// hang forever and could cause issues. If we don't call detach, then
// std::terminate is called and all threads are terminated.
// Detaching is non-optimal, but should allow the rest of the tests to run
// before anything drastic occurs.
if (m_done1)
m_watcher1.join();
else
m_watcher1.detach();
if (m_done2)
m_watcher2.join();
else
m_watcher2.detach();
}
};
TEST_F(ConditionVariableTest, NotifyAll) {
auto wait = [this](std::atomic<bool>& done) {
priority_lock lock(m_mutex);
done = false;
m_cond.wait(lock);
EXPECT_TRUE(lock.owns_lock())
<< "The condition variable should have reacquired the lock.";
done = true;
};
StartThreads(wait);
NotifyAllTest();
}
TEST_F(ConditionVariableTest, NotifyOne) {
auto wait = [this](std::atomic<bool>& done) {
priority_lock lock(m_mutex);
done = false;
m_cond.wait(lock);
EXPECT_TRUE(lock.owns_lock())
<< "The condition variable should have reacquired the lock.";
done = true;
};
StartThreads(wait);
NotifyOne();
// Wait briefly to let things settle.
ShortSleep();
EXPECT_TRUE(m_done1 ^ m_done2) << "Only one thread should've been notified.";
NotifyOne();
ShortSleep();
EXPECT_TRUE(m_done2 && m_done2) << "Both threads should've been notified.";
}
TEST_F(ConditionVariableTest, WaitWithPredicate) {
auto predicate = [this]() -> bool { return m_pred_var; };
auto wait_predicate = [this, predicate](std::atomic<bool>& done) {
priority_lock lock(m_mutex);
done = false;
m_cond.wait(lock, predicate);
EXPECT_TRUE(lock.owns_lock())
<< "The condition variable should have reacquired the lock.";
done = true;
};
StartThreads(wait_predicate);
PredicateTest();
}
TEST_F(ConditionVariableTest, WaitUntil) { WaitTimeTest(false); }
TEST_F(ConditionVariableTest, WaitUntilWithPredicate) {
WaitTimePredicateTest(false);
}
TEST_F(ConditionVariableTest, WaitFor) { WaitTimeTest(true); }
TEST_F(ConditionVariableTest, WaitForWithPredicate) {
WaitTimePredicateTest(true);
}
TEST_F(ConditionVariableTest, NativeHandle) {
auto wait = [this](std::atomic<bool>& done) {
priority_lock lock(m_mutex);
done = false;
m_cond.wait(lock);
EXPECT_TRUE(lock.owns_lock())
<< "The condition variable should have reacquired the lock.";
done = true;
};
StartThreads(wait);
pthread_cond_t* native_handle = m_cond.native_handle();
pthread_cond_broadcast(native_handle);
ShortSleep();
EXPECT_TRUE(m_done1) << "watcher1 failed to be notified.";
EXPECT_TRUE(m_done2) << "watcher2 failed to be notified.";
}
} // namespace testing
} // namespace wpilib

View File

@@ -0,0 +1,176 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "Counter.h" // NOLINT(build/include_order)
#include "Jaguar.h"
#include "Talon.h"
#include "TestBench.h"
#include "Timer.h"
#include "Victor.h"
#include "gtest/gtest.h"
using namespace frc;
static const double kMotorDelay = 2.5;
static const double kMaxPeriod = 2.0;
class CounterTest : public testing::Test {
protected:
Counter* m_talonCounter;
Counter* m_victorCounter;
Counter* m_jaguarCounter;
Talon* m_talon;
Victor* m_victor;
Jaguar* m_jaguar;
void SetUp() override {
m_talonCounter = new Counter(TestBench::kTalonEncoderChannelA);
m_victorCounter = new Counter(TestBench::kVictorEncoderChannelA);
m_jaguarCounter = new Counter(TestBench::kJaguarEncoderChannelA);
m_victor = new Victor(TestBench::kVictorChannel);
m_talon = new Talon(TestBench::kTalonChannel);
m_jaguar = new Jaguar(TestBench::kJaguarChannel);
}
void TearDown() override {
delete m_talonCounter;
delete m_victorCounter;
delete m_jaguarCounter;
delete m_victor;
delete m_talon;
delete m_jaguar;
}
void Reset() {
m_talonCounter->Reset();
m_victorCounter->Reset();
m_jaguarCounter->Reset();
m_talon->Set(0.0);
m_victor->Set(0.0);
m_jaguar->Set(0.0);
}
};
/**
* Tests the counter by moving the motor and determining if the
* counter is counting.
*/
TEST_F(CounterTest, CountTalon) {
Reset();
/* Run the motor forward and determine if the counter is counting. */
m_talon->Set(1.0);
Wait(0.5);
EXPECT_NE(0.0, m_talonCounter->Get()) << "The counter did not count (talon)";
/* Set the motor to 0 and determine if the counter resets to 0. */
m_talon->Set(0.0);
Wait(0.5);
m_talonCounter->Reset();
EXPECT_FLOAT_EQ(0.0, m_talonCounter->Get())
<< "The counter did not restart to 0 (talon)";
}
TEST_F(CounterTest, CountVictor) {
Reset();
/* Run the motor forward and determine if the counter is counting. */
m_victor->Set(1.0);
Wait(0.5);
EXPECT_NE(0.0, m_victorCounter->Get())
<< "The counter did not count (victor)";
/* Set the motor to 0 and determine if the counter resets to 0. */
m_victor->Set(0.0);
Wait(0.5);
m_victorCounter->Reset();
EXPECT_FLOAT_EQ(0.0, m_victorCounter->Get())
<< "The counter did not restart to 0 (jaguar)";
}
TEST_F(CounterTest, CountJaguar) {
Reset();
/* Run the motor forward and determine if the counter is counting. */
m_jaguar->Set(1.0);
Wait(0.5);
EXPECT_NE(0.0, m_jaguarCounter->Get())
<< "The counter did not count (jaguar)";
/* Set the motor to 0 and determine if the counter resets to 0. */
m_jaguar->Set(0.0);
Wait(0.5);
m_jaguarCounter->Reset();
EXPECT_FLOAT_EQ(0.0, m_jaguarCounter->Get())
<< "The counter did not restart to 0 (jaguar)";
}
/**
* Tests the GetStopped and SetMaxPeriod methods by setting the Max Period and
* getting the value after a period of time.
*/
TEST_F(CounterTest, TalonGetStopped) {
Reset();
/* Set the Max Period of the counter and run the motor */
m_talonCounter->SetMaxPeriod(kMaxPeriod);
m_talon->Set(1.0);
Wait(0.5);
EXPECT_FALSE(m_talonCounter->GetStopped()) << "The counter did not count.";
/* Stop the motor and wait until the Max Period is exceeded */
m_talon->Set(0.0);
Wait(kMotorDelay);
EXPECT_TRUE(m_talonCounter->GetStopped())
<< "The counter did not stop counting.";
}
TEST_F(CounterTest, VictorGetStopped) {
Reset();
/* Set the Max Period of the counter and run the motor */
m_victorCounter->SetMaxPeriod(kMaxPeriod);
m_victor->Set(1.0);
Wait(0.5);
EXPECT_FALSE(m_victorCounter->GetStopped()) << "The counter did not count.";
/* Stop the motor and wait until the Max Period is exceeded */
m_victor->Set(0.0);
Wait(kMotorDelay);
EXPECT_TRUE(m_victorCounter->GetStopped())
<< "The counter did not stop counting.";
}
TEST_F(CounterTest, JaguarGetStopped) {
Reset();
/* Set the Max Period of the counter and run the motor */
m_jaguarCounter->SetMaxPeriod(kMaxPeriod);
m_jaguar->Set(1.0);
Wait(0.5);
EXPECT_FALSE(m_jaguarCounter->GetStopped()) << "The counter did not count.";
/* Stop the motor and wait until the Max Period is exceeded */
m_jaguar->Set(0.0);
Wait(kMotorDelay);
EXPECT_TRUE(m_jaguarCounter->GetStopped())
<< "The counter did not stop counting.";
}

View File

@@ -0,0 +1,187 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "DigitalInput.h" // NOLINT(build/include_order)
#include "DigitalOutput.h" // NOLINT(build/include_order)
#include "Counter.h"
#include "InterruptableSensorBase.h"
#include "TestBench.h"
#include "Timer.h"
#include "gtest/gtest.h"
using namespace frc;
static const double kCounterTime = 0.001;
static const double kDelayTime = 0.1;
static const double kSynchronousInterruptTime = 2.0;
static const double kSynchronousInterruptTimeTolerance = 0.01;
/**
* A fixture with a digital input and a digital output physically wired
* together.
*/
class DIOLoopTest : public testing::Test {
protected:
DigitalInput* m_input;
DigitalOutput* m_output;
void SetUp() override {
m_input = new DigitalInput(TestBench::kLoop1InputChannel);
m_output = new DigitalOutput(TestBench::kLoop1OutputChannel);
}
void TearDown() override {
delete m_input;
delete m_output;
}
void Reset() { m_output->Set(false); }
};
/**
* Test the DigitalInput and DigitalOutput classes by setting the output and
* reading the input.
*/
TEST_F(DIOLoopTest, Loop) {
Reset();
m_output->Set(false);
Wait(kDelayTime);
EXPECT_FALSE(m_input->Get()) << "The digital output was turned off, but "
<< "the digital input is on.";
m_output->Set(true);
Wait(kDelayTime);
EXPECT_TRUE(m_input->Get()) << "The digital output was turned on, but "
<< "the digital input is off.";
}
/**
* Tests to see if the DIO PWM functionality works.
*/
TEST_F(DIOLoopTest, DIOPWM) {
Reset();
m_output->Set(false);
Wait(kDelayTime);
EXPECT_FALSE(m_input->Get()) << "The digital output was turned off, but "
<< "the digital input is on.";
// Set frequency to 2.0 Hz
m_output->SetPWMRate(2.0);
// Enable PWM, but leave it off
m_output->EnablePWM(0.0);
Wait(0.5);
m_output->UpdateDutyCycle(0.5);
m_input->RequestInterrupts();
m_input->SetUpSourceEdge(false, true);
InterruptableSensorBase::WaitResult result =
m_input->WaitForInterrupt(3.0, true);
Wait(0.5);
bool firstCycle = m_input->Get();
Wait(0.5);
bool secondCycle = m_input->Get();
Wait(0.5);
bool thirdCycle = m_input->Get();
Wait(0.5);
bool forthCycle = m_input->Get();
Wait(0.5);
bool fifthCycle = m_input->Get();
Wait(0.5);
bool sixthCycle = m_input->Get();
Wait(0.5);
bool seventhCycle = m_input->Get();
m_output->DisablePWM();
Wait(0.5);
bool firstAfterStop = m_input->Get();
Wait(0.5);
bool secondAfterStop = m_input->Get();
EXPECT_EQ(InterruptableSensorBase::WaitResult::kFallingEdge, result)
<< "WaitForInterrupt was not falling.";
EXPECT_FALSE(firstCycle) << "Input not low after first delay";
EXPECT_TRUE(secondCycle) << "Input not high after second delay";
EXPECT_FALSE(thirdCycle) << "Input not low after third delay";
EXPECT_TRUE(forthCycle) << "Input not high after forth delay";
EXPECT_FALSE(fifthCycle) << "Input not low after fifth delay";
EXPECT_TRUE(sixthCycle) << "Input not high after sixth delay";
EXPECT_FALSE(seventhCycle) << "Input not low after seventh delay";
EXPECT_FALSE(firstAfterStop) << "Input not low after stopping first read";
EXPECT_FALSE(secondAfterStop) << "Input not low after stopping second read";
}
/**
* Test a fake "counter" that uses the DIO loop as an input to make sure the
* Counter class works
*/
TEST_F(DIOLoopTest, FakeCounter) {
Reset();
Counter counter(m_input);
EXPECT_EQ(0, counter.Get()) << "Counter did not initialize to 0.";
/* Count 100 ticks. The counter value should be 100 after this loop. */
for (int32_t i = 0; i < 100; i++) {
m_output->Set(true);
Wait(kCounterTime);
m_output->Set(false);
Wait(kCounterTime);
}
EXPECT_EQ(100, counter.Get()) << "Counter did not count up to 100.";
}
static void InterruptHandler(uint32_t interruptAssertedMask, void* param) {
*reinterpret_cast<int32_t*>(param) = 12345;
}
TEST_F(DIOLoopTest, AsynchronousInterruptWorks) {
int32_t param = 0;
// Given an interrupt handler that sets an int32_t to 12345
m_input->RequestInterrupts(InterruptHandler, &param);
m_input->EnableInterrupts();
// If the voltage rises
m_output->Set(false);
m_output->Set(true);
m_input->CancelInterrupts();
// Then the int32_t should be 12345
Wait(kDelayTime);
EXPECT_EQ(12345, param) << "The interrupt did not run.";
}
static void* InterruptTriggerer(void* data) {
DigitalOutput* output = static_cast<DigitalOutput*>(data);
output->Set(false);
Wait(kSynchronousInterruptTime);
output->Set(true);
return nullptr;
}
TEST_F(DIOLoopTest, SynchronousInterruptWorks) {
// Given a synchronous interrupt
m_input->RequestInterrupts();
// If we have another thread trigger the interrupt in a few seconds
pthread_t interruptTriggererLoop;
pthread_create(&interruptTriggererLoop, nullptr, InterruptTriggerer,
m_output);
// Then this thread should pause and resume after that number of seconds
Timer timer;
timer.Start();
m_input->WaitForInterrupt(kSynchronousInterruptTime + 1.0);
EXPECT_NEAR(kSynchronousInterruptTime, timer.Get(),
kSynchronousInterruptTimeTolerance);
}

View File

@@ -0,0 +1,63 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2015-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "DigitalGlitchFilter.h" // NOLINT(build/include_order)
#include "Counter.h"
#include "DigitalInput.h"
#include "Encoder.h"
#include "gtest/gtest.h"
using namespace frc;
/**
* Tests that configuring inputs to be filtered succeeds.
*
* This test actually tests everything except that the actual FPGA
* implementation works as intended. We configure the FPGA and then query it to
* make sure that the acutal configuration matches.
*/
TEST(DigitalGlitchFilterTest, BasicTest) {
DigitalInput input1(1);
DigitalInput input2(2);
DigitalInput input3(3);
DigitalInput input4(4);
Encoder encoder5(5, 6);
Counter counter7(7);
// Check that we can make a single filter and set the period.
DigitalGlitchFilter filter1;
filter1.Add(&input1);
filter1.SetPeriodNanoSeconds(4200);
// Check that we can make a second filter with 2 inputs.
DigitalGlitchFilter filter2;
filter2.Add(&input2);
filter2.Add(&input3);
filter2.SetPeriodNanoSeconds(97100);
// Check that we can make a third filter with an input, an encoder, and a
// counter.
DigitalGlitchFilter filter3;
filter3.Add(&input4);
filter3.Add(&encoder5);
filter3.Add(&counter7);
filter3.SetPeriodNanoSeconds(167800);
// Verify that the period was properly set for all 3 filters.
EXPECT_EQ(4200u, filter1.GetPeriodNanoSeconds());
EXPECT_EQ(97100u, filter2.GetPeriodNanoSeconds());
EXPECT_EQ(167800u, filter3.GetPeriodNanoSeconds());
// Clean up.
filter1.Remove(&input1);
filter2.Remove(&input2);
filter2.Remove(&input3);
filter3.Remove(&input4);
filter3.Remove(&encoder5);
filter3.Remove(&counter7);
}

View File

@@ -0,0 +1,35 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "DriverStation.h" // NOLINT(build/include_order)
#include "TestBench.h"
#include "Utility.h"
#include "gtest/gtest.h"
using namespace frc;
constexpr double TIMER_TOLERANCE = 0.2;
constexpr int64_t TIMER_RUNTIME = 1000000; // 1 second
class DriverStationTest : public testing::Test {};
/**
* Test if the WaitForData function works
*/
TEST_F(DriverStationTest, WaitForData) {
uint64_t initialTime = GetFPGATime();
for (int i = 0; i < 50; i++) {
DriverStation::GetInstance().WaitForData();
}
uint64_t finalTime = GetFPGATime();
EXPECT_NEAR(TIMER_RUNTIME, finalTime - initialTime,
TIMER_TOLERANCE * TIMER_RUNTIME);
}

View File

@@ -0,0 +1,161 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "Encoder.h" // NOLINT(build/include_order)
#include "AnalogOutput.h"
#include "AnalogTrigger.h"
#include "DigitalOutput.h"
#include "TestBench.h"
#include "Timer.h"
#include "gtest/gtest.h"
using namespace frc;
static const double kDelayTime = 0.001;
class FakeEncoderTest : public testing::Test {
protected:
DigitalOutput* m_outputA;
DigitalOutput* m_outputB;
AnalogOutput* m_indexOutput;
Encoder* m_encoder;
AnalogTrigger* m_indexAnalogTrigger;
std::shared_ptr<AnalogTriggerOutput> m_indexAnalogTriggerOutput;
void SetUp() override {
m_outputA = new DigitalOutput(TestBench::kLoop2OutputChannel);
m_outputB = new DigitalOutput(TestBench::kLoop1OutputChannel);
m_indexOutput = new AnalogOutput(TestBench::kAnalogOutputChannel);
m_outputA->Set(false);
m_outputB->Set(false);
m_encoder = new Encoder(TestBench::kLoop1InputChannel,
TestBench::kLoop2InputChannel);
m_indexAnalogTrigger =
new AnalogTrigger(TestBench::kFakeAnalogOutputChannel);
m_indexAnalogTrigger->SetLimitsVoltage(2.0, 3.0);
m_indexAnalogTriggerOutput =
m_indexAnalogTrigger->CreateOutput(AnalogTriggerType::kState);
}
void TearDown() override {
delete m_outputA;
delete m_outputB;
delete m_indexOutput;
delete m_encoder;
delete m_indexAnalogTrigger;
}
/**
* Output pulses to the encoder's input channels to simulate a change of 100
* ticks
*/
void Simulate100QuadratureTicks() {
for (int32_t i = 0; i < 100; i++) {
m_outputA->Set(true);
Wait(kDelayTime);
m_outputB->Set(true);
Wait(kDelayTime);
m_outputA->Set(false);
Wait(kDelayTime);
m_outputB->Set(false);
Wait(kDelayTime);
}
}
void SetIndexHigh() {
m_indexOutput->SetVoltage(5.0);
Wait(kDelayTime);
}
void SetIndexLow() {
m_indexOutput->SetVoltage(0.0);
Wait(kDelayTime);
}
};
/**
* Test the encoder by reseting it to 0 and reading the value.
*/
TEST_F(FakeEncoderTest, TestDefaultState) {
EXPECT_FLOAT_EQ(0.0, m_encoder->Get()) << "The encoder did not start at 0.";
}
/**
* Test the encoder by setting the digital outputs and reading the value.
*/
TEST_F(FakeEncoderTest, TestCountUp) {
m_encoder->Reset();
Simulate100QuadratureTicks();
EXPECT_FLOAT_EQ(100.0, m_encoder->Get()) << "Encoder did not count to 100.";
}
/**
* Test that the encoder can stay reset while the index source is high
*/
TEST_F(FakeEncoderTest, TestResetWhileHigh) {
m_encoder->SetIndexSource(*m_indexAnalogTriggerOutput,
Encoder::IndexingType::kResetWhileHigh);
SetIndexLow();
Simulate100QuadratureTicks();
SetIndexHigh();
EXPECT_EQ(0, m_encoder->Get());
Simulate100QuadratureTicks();
EXPECT_EQ(0, m_encoder->Get());
}
/**
* Test that the encoder can reset when the index source goes from low to high
*/
TEST_F(FakeEncoderTest, TestResetOnRisingEdge) {
m_encoder->SetIndexSource(*m_indexAnalogTriggerOutput,
Encoder::IndexingType::kResetOnRisingEdge);
SetIndexLow();
Simulate100QuadratureTicks();
SetIndexHigh();
EXPECT_EQ(0, m_encoder->Get());
Simulate100QuadratureTicks();
EXPECT_EQ(100, m_encoder->Get());
}
/**
* Test that the encoder can stay reset while the index source is low
*/
TEST_F(FakeEncoderTest, TestResetWhileLow) {
m_encoder->SetIndexSource(*m_indexAnalogTriggerOutput,
Encoder::IndexingType::kResetWhileLow);
SetIndexHigh();
Simulate100QuadratureTicks();
SetIndexLow();
EXPECT_EQ(0, m_encoder->Get());
Simulate100QuadratureTicks();
EXPECT_EQ(0, m_encoder->Get());
}
/**
* Test that the encoder can reset when the index source goes from high to low
*/
TEST_F(FakeEncoderTest, TestResetOnFallingEdge) {
m_encoder->SetIndexSource(*m_indexAnalogTriggerOutput,
Encoder::IndexingType::kResetOnFallingEdge);
SetIndexHigh();
Simulate100QuadratureTicks();
SetIndexLow();
EXPECT_EQ(0, m_encoder->Get());
Simulate100QuadratureTicks();
EXPECT_EQ(100, m_encoder->Get());
}

View File

@@ -0,0 +1,129 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2015-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "Filters/LinearDigitalFilter.h" // NOLINT(build/include_order)
#include <cmath>
#include <functional>
#include <memory>
#include <random>
#include <thread>
#include "Base.h"
#include "TestBench.h"
#include "gtest/gtest.h"
using namespace frc;
enum FilterNoiseTestType { TEST_SINGLE_POLE_IIR, TEST_MOVAVG };
std::ostream& operator<<(std::ostream& os, const FilterNoiseTestType& type) {
switch (type) {
case TEST_SINGLE_POLE_IIR:
os << "LinearDigitalFilter SinglePoleIIR";
break;
case TEST_MOVAVG:
os << "LinearDigitalFilter MovingAverage";
break;
}
return os;
}
constexpr double kStdDev = 10.0;
/**
* Adds Gaussian white noise to a function returning data. The noise will have
* the standard deviation provided in the constructor.
*/
class NoiseGenerator : public PIDSource {
public:
NoiseGenerator(double (*dataFunc)(double), double stdDev)
: m_distr(0.0, stdDev) {
m_dataFunc = dataFunc;
}
void SetPIDSourceType(PIDSourceType pidSource) override {}
double Get() { return m_dataFunc(m_count) + m_noise; }
double PIDGet() override {
m_noise = m_distr(m_gen);
m_count += TestBench::kFilterStep;
return m_dataFunc(m_count) + m_noise;
}
void Reset() { m_count = -TestBench::kFilterStep; }
private:
std::function<double(double)> m_dataFunc;
double m_noise = 0.0;
// Make sure first call to PIDGet() uses m_count == 0
double m_count = -TestBench::kFilterStep;
std::random_device m_rd;
std::mt19937 m_gen{m_rd()};
std::normal_distribution<double> m_distr;
};
/**
* A fixture that includes a noise generator wrapped in a filter
*/
class FilterNoiseTest : public testing::TestWithParam<FilterNoiseTestType> {
protected:
std::unique_ptr<PIDSource> m_filter;
std::shared_ptr<NoiseGenerator> m_noise;
static double GetData(double t) { return 100.0 * std::sin(2.0 * M_PI * t); }
void SetUp() override {
m_noise = std::make_shared<NoiseGenerator>(GetData, kStdDev);
switch (GetParam()) {
case TEST_SINGLE_POLE_IIR: {
m_filter = std::make_unique<LinearDigitalFilter>(
LinearDigitalFilter::SinglePoleIIR(
m_noise, TestBench::kSinglePoleIIRTimeConstant,
TestBench::kFilterStep));
break;
}
case TEST_MOVAVG: {
m_filter = std::make_unique<LinearDigitalFilter>(
LinearDigitalFilter::MovingAverage(m_noise,
TestBench::kMovAvgTaps));
break;
}
}
}
};
/**
* Test if the filter reduces the noise produced by a signal generator
*/
TEST_P(FilterNoiseTest, NoiseReduce) {
double theoryData = 0.0;
double noiseGenError = 0.0;
double filterError = 0.0;
m_noise->Reset();
for (double t = 0; t < TestBench::kFilterTime; t += TestBench::kFilterStep) {
theoryData = GetData(t);
filterError += std::abs(m_filter->PIDGet() - theoryData);
noiseGenError += std::abs(m_noise->Get() - theoryData);
}
RecordProperty("FilterError", filterError);
// The filter should have produced values closer to the theory
EXPECT_GT(noiseGenError, filterError)
<< "Filter should have reduced noise accumulation but failed";
}
INSTANTIATE_TEST_CASE_P(Test, FilterNoiseTest,
testing::Values(TEST_SINGLE_POLE_IIR, TEST_MOVAVG));

View File

@@ -0,0 +1,125 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2015-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "Filters/LinearDigitalFilter.h" // NOLINT(build/include_order)
#include <cmath>
#include <functional>
#include <memory>
#include <random>
#include <thread>
#include "Base.h"
#include "TestBench.h"
#include "gtest/gtest.h"
using namespace frc;
enum FilterOutputTestType { TEST_SINGLE_POLE_IIR, TEST_HIGH_PASS, TEST_MOVAVG };
std::ostream& operator<<(std::ostream& os, const FilterOutputTestType& type) {
switch (type) {
case TEST_SINGLE_POLE_IIR:
os << "LinearDigitalFilter SinglePoleIIR";
break;
case TEST_HIGH_PASS:
os << "LinearDigitalFilter HighPass";
break;
case TEST_MOVAVG:
os << "LinearDigitalFilter MovingAverage";
break;
}
return os;
}
class DataWrapper : public PIDSource {
public:
explicit DataWrapper(double (*dataFunc)(double)) { m_dataFunc = dataFunc; }
virtual void SetPIDSourceType(PIDSourceType pidSource) {}
virtual double PIDGet() {
m_count += TestBench::kFilterStep;
return m_dataFunc(m_count);
}
void Reset() { m_count = -TestBench::kFilterStep; }
private:
std::function<double(double)> m_dataFunc;
// Make sure first call to PIDGet() uses m_count == 0
double m_count = -TestBench::kFilterStep;
};
/**
* A fixture that includes a consistent data source wrapped in a filter
*/
class FilterOutputTest : public testing::TestWithParam<FilterOutputTestType> {
protected:
std::unique_ptr<PIDSource> m_filter;
std::shared_ptr<DataWrapper> m_data;
double m_expectedOutput = 0.0;
static double GetData(double t) {
return 100.0 * std::sin(2.0 * M_PI * t) + 20.0 * std::cos(50.0 * M_PI * t);
}
void SetUp() override {
m_data = std::make_shared<DataWrapper>(GetData);
switch (GetParam()) {
case TEST_SINGLE_POLE_IIR: {
m_filter = std::make_unique<LinearDigitalFilter>(
LinearDigitalFilter::SinglePoleIIR(
m_data, TestBench::kSinglePoleIIRTimeConstant,
TestBench::kFilterStep));
m_expectedOutput = TestBench::kSinglePoleIIRExpectedOutput;
break;
}
case TEST_HIGH_PASS: {
m_filter =
std::make_unique<LinearDigitalFilter>(LinearDigitalFilter::HighPass(
m_data, TestBench::kHighPassTimeConstant,
TestBench::kFilterStep));
m_expectedOutput = TestBench::kHighPassExpectedOutput;
break;
}
case TEST_MOVAVG: {
m_filter = std::make_unique<LinearDigitalFilter>(
LinearDigitalFilter::MovingAverage(m_data, TestBench::kMovAvgTaps));
m_expectedOutput = TestBench::kMovAvgExpectedOutput;
break;
}
}
}
};
/**
* Test if the linear digital filters produce consistent output
*/
TEST_P(FilterOutputTest, FilterOutput) {
m_data->Reset();
double filterOutput = 0.0;
for (double t = 0.0; t < TestBench::kFilterTime;
t += TestBench::kFilterStep) {
filterOutput = m_filter->PIDGet();
}
RecordProperty("FilterOutput", filterOutput);
EXPECT_FLOAT_EQ(m_expectedOutput, filterOutput)
<< "Filter output didn't match expected value";
}
INSTANTIATE_TEST_CASE_P(Test, FilterOutputTest,
testing::Values(TEST_SINGLE_POLE_IIR, TEST_HIGH_PASS,
TEST_MOVAVG));

View File

@@ -0,0 +1,188 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "Encoder.h"
#include "Jaguar.h"
#include "PIDController.h"
#include "Talon.h"
#include "TestBench.h"
#include "Timer.h"
#include "Victor.h"
#include "gtest/gtest.h"
using namespace frc;
enum MotorEncoderTestType { TEST_VICTOR, TEST_JAGUAR, TEST_TALON };
std::ostream& operator<<(std::ostream& os, MotorEncoderTestType const& type) {
switch (type) {
case TEST_VICTOR:
os << "Victor";
break;
case TEST_JAGUAR:
os << "Jaguar";
break;
case TEST_TALON:
os << "Talon";
break;
}
return os;
}
static constexpr double kMotorTime = 0.5;
/**
* A fixture that includes a PWM speed controller and an encoder connected to
* the same motor.
*/
class MotorEncoderTest : public testing::TestWithParam<MotorEncoderTestType> {
protected:
SpeedController* m_speedController;
Encoder* m_encoder;
void SetUp() override {
switch (GetParam()) {
case TEST_VICTOR:
m_speedController = new Victor(TestBench::kVictorChannel);
m_encoder = new Encoder(TestBench::kVictorEncoderChannelA,
TestBench::kVictorEncoderChannelB);
break;
case TEST_JAGUAR:
m_speedController = new Jaguar(TestBench::kJaguarChannel);
m_encoder = new Encoder(TestBench::kJaguarEncoderChannelA,
TestBench::kJaguarEncoderChannelB);
break;
case TEST_TALON:
m_speedController = new Talon(TestBench::kTalonChannel);
m_encoder = new Encoder(TestBench::kTalonEncoderChannelA,
TestBench::kTalonEncoderChannelB);
break;
}
}
void TearDown() override {
delete m_speedController;
delete m_encoder;
}
void Reset() {
m_speedController->Set(0.0);
m_encoder->Reset();
}
};
/**
* Test if the encoder value increments after the motor drives forward
*/
TEST_P(MotorEncoderTest, Increment) {
Reset();
/* Drive the speed controller briefly to move the encoder */
m_speedController->Set(0.2f);
Wait(kMotorTime);
m_speedController->Set(0.0);
/* The encoder should be positive now */
EXPECT_GT(m_encoder->Get(), 0)
<< "Encoder should have incremented after the motor moved";
}
/**
* Test if the encoder value decrements after the motor drives backwards
*/
TEST_P(MotorEncoderTest, Decrement) {
Reset();
/* Drive the speed controller briefly to move the encoder */
m_speedController->Set(-0.2);
Wait(kMotorTime);
m_speedController->Set(0.0);
/* The encoder should be positive now */
EXPECT_LT(m_encoder->Get(), 0.0)
<< "Encoder should have decremented after the motor moved";
}
/**
* Test if motor speeds are clamped to [-1,1]
*/
TEST_P(MotorEncoderTest, ClampSpeed) {
Reset();
m_speedController->Set(2.0);
Wait(kMotorTime);
EXPECT_FLOAT_EQ(1.0, m_speedController->Get());
m_speedController->Set(-2.0);
Wait(kMotorTime);
EXPECT_FLOAT_EQ(-1.0, m_speedController->Get());
}
/**
* Test if position PID loop works
*/
TEST_P(MotorEncoderTest, PositionPIDController) {
Reset();
double goal = 1000;
m_encoder->SetPIDSourceType(PIDSourceType::kDisplacement);
PIDController pid(0.001, 0.0005, 0.0, m_encoder, m_speedController);
pid.SetAbsoluteTolerance(50.0);
pid.SetOutputRange(-0.2, 0.2);
pid.SetSetpoint(goal);
/* 10 seconds should be plenty time to get to the setpoint */
pid.Enable();
Wait(10.0);
pid.Disable();
RecordProperty("PIDError", pid.GetError());
EXPECT_TRUE(pid.OnTarget())
<< "PID loop did not converge within 10 seconds. Goal was: " << goal
<< " Error was: " << pid.GetError();
}
/**
* Test if velocity PID loop works
*/
TEST_P(MotorEncoderTest, VelocityPIDController) {
Reset();
m_encoder->SetPIDSourceType(PIDSourceType::kRate);
PIDController pid(1e-5, 0.0, 3e-5, 8e-5, m_encoder, m_speedController);
pid.SetAbsoluteTolerance(200.0);
pid.SetToleranceBuffer(50);
pid.SetOutputRange(-0.3, 0.3);
pid.SetSetpoint(600);
/* 10 seconds should be plenty time to get to the setpoint */
pid.Enable();
Wait(10.0);
pid.Disable();
RecordProperty("PIDError", pid.GetError());
EXPECT_TRUE(pid.OnTarget())
<< "PID loop did not converge within 10 seconds. Goal was: " << 600
<< " Error was: " << pid.GetError();
}
/**
* Test resetting encoders
*/
TEST_P(MotorEncoderTest, Reset) {
Reset();
EXPECT_EQ(0, m_encoder->Get()) << "Encoder did not reset to 0";
}
INSTANTIATE_TEST_CASE_P(Test, MotorEncoderTest,
testing::Values(TEST_VICTOR, TEST_JAGUAR, TEST_TALON));

View File

@@ -0,0 +1,156 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "Encoder.h"
#include "Jaguar.h"
#include "Talon.h"
#include "TestBench.h"
#include "Timer.h"
#include "Victor.h"
#include "gtest/gtest.h"
using namespace frc;
enum MotorInvertingTestType { TEST_VICTOR, TEST_JAGUAR, TEST_TALON };
static const double motorSpeed = 0.15;
static const double delayTime = 0.5;
std::ostream& operator<<(std::ostream& os, MotorInvertingTestType const& type) {
switch (type) {
case TEST_VICTOR:
os << "Victor";
break;
case TEST_JAGUAR:
os << "Jaguar";
break;
case TEST_TALON:
os << "Talon";
break;
}
return os;
}
class MotorInvertingTest
: public testing::TestWithParam<MotorInvertingTestType> {
protected:
SpeedController* m_speedController;
Encoder* m_encoder;
void SetUp() override {
switch (GetParam()) {
case TEST_VICTOR:
m_speedController = new Victor(TestBench::kVictorChannel);
m_encoder = new Encoder(TestBench::kVictorEncoderChannelA,
TestBench::kVictorEncoderChannelB);
break;
case TEST_JAGUAR:
m_speedController = new Jaguar(TestBench::kJaguarChannel);
m_encoder = new Encoder(TestBench::kJaguarEncoderChannelA,
TestBench::kJaguarEncoderChannelB);
break;
case TEST_TALON:
m_speedController = new Talon(TestBench::kTalonChannel);
m_encoder = new Encoder(TestBench::kTalonEncoderChannelA,
TestBench::kTalonEncoderChannelB);
break;
}
}
void TearDown() override {
delete m_speedController;
delete m_encoder;
}
void Reset() {
m_speedController->SetInverted(false);
m_speedController->Set(0.0);
m_encoder->Reset();
}
};
TEST_P(MotorInvertingTest, InvertingPositive) {
Reset();
m_speedController->Set(motorSpeed);
Wait(delayTime);
bool initDirection = m_encoder->GetDirection();
m_speedController->SetInverted(true);
m_speedController->Set(motorSpeed);
Wait(delayTime);
EXPECT_TRUE(m_encoder->GetDirection() != initDirection)
<< "Inverting with Positive value does not change direction";
Reset();
}
TEST_P(MotorInvertingTest, InvertingNegative) {
Reset();
m_speedController->SetInverted(false);
m_speedController->Set(-motorSpeed);
Wait(delayTime);
bool initDirection = m_encoder->GetDirection();
m_speedController->SetInverted(true);
m_speedController->Set(-motorSpeed);
Wait(delayTime);
EXPECT_TRUE(m_encoder->GetDirection() != initDirection)
<< "Inverting with Negative value does not change direction";
Reset();
}
TEST_P(MotorInvertingTest, InvertingSwitchingPosToNeg) {
Reset();
m_speedController->SetInverted(false);
m_speedController->Set(motorSpeed);
Wait(delayTime);
bool initDirection = m_encoder->GetDirection();
m_speedController->SetInverted(true);
m_speedController->Set(-motorSpeed);
Wait(delayTime);
EXPECT_TRUE(m_encoder->GetDirection() == initDirection)
<< "Inverting with Switching value does change direction";
Reset();
}
TEST_P(MotorInvertingTest, InvertingSwitchingNegToPos) {
Reset();
m_speedController->SetInverted(false);
m_speedController->Set(-motorSpeed);
Wait(delayTime);
bool initDirection = m_encoder->GetDirection();
m_speedController->SetInverted(true);
m_speedController->Set(motorSpeed);
Wait(delayTime);
EXPECT_TRUE(m_encoder->GetDirection() == initDirection)
<< "Inverting with Switching value does change direction";
Reset();
}
INSTANTIATE_TEST_CASE_P(Test, MotorInvertingTest,
testing::Values(TEST_VICTOR, TEST_JAGUAR, TEST_TALON));

View File

@@ -0,0 +1,270 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2016-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "HAL/cpp/priority_mutex.h" // NOLINT(build/include_order)
#include <atomic>
#include <condition_variable>
#include <thread>
#include "TestBench.h"
#include "gtest/gtest.h"
namespace wpilib {
namespace testing {
using std::chrono::system_clock;
// Threading primitive used to notify many threads that a condition is now true.
// The condition can not be cleared.
class Notification {
public:
// Efficiently waits until the Notification has been notified once.
void Wait() {
std::unique_lock<hal::priority_mutex> lock(m_mutex);
while (!m_set) {
m_condition.wait(lock);
}
}
// Sets the condition to true, and wakes all waiting threads.
void Notify() {
std::lock_guard<hal::priority_mutex> lock(m_mutex);
m_set = true;
m_condition.notify_all();
}
private:
// hal::priority_mutex used for the notification and to protect the bit.
hal::priority_mutex m_mutex;
// Condition for threads to sleep on.
std::condition_variable_any m_condition;
// Bool which is true when the notification has been notified.
bool m_set = false;
};
void SetProcessorAffinity(int32_t core_id) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(core_id, &cpuset);
pthread_t current_thread = pthread_self();
ASSERT_EQ(pthread_setaffinity_np(current_thread, sizeof(cpu_set_t), &cpuset),
0);
}
void SetThreadRealtimePriorityOrDie(int32_t priority) {
struct sched_param param;
// Set realtime priority for this thread
param.sched_priority = priority + sched_get_priority_min(SCHED_RR);
ASSERT_EQ(pthread_setschedparam(pthread_self(), SCHED_RR, &param), 0)
<< ": Failed to set scheduler priority.";
}
// This thread holds the mutex and spins until signaled to release it and stop.
template <typename MutexType>
class LowPriorityThread {
public:
explicit LowPriorityThread(MutexType* mutex)
: m_mutex(mutex), m_hold_mutex(1), m_success(0) {}
void operator()() {
SetProcessorAffinity(0);
SetThreadRealtimePriorityOrDie(20);
m_mutex->lock();
m_started.Notify();
while (m_hold_mutex.test_and_set()) {
}
m_mutex->unlock();
m_success.store(1);
}
void WaitForStartup() { m_started.Wait(); }
void release_mutex() { m_hold_mutex.clear(); }
bool success() { return m_success.load(); }
private:
// hal::priority_mutex to grab and release.
MutexType* m_mutex;
Notification m_started;
// Atomic type used to signal when the thread should unlock the mutex.
// Using a mutex to protect something could cause other issues, and a delay
// between setting and reading isn't a problem as long as the set is atomic.
std::atomic_flag m_hold_mutex;
std::atomic<int> m_success;
};
// This thread spins forever until signaled to stop.
class BusyWaitingThread {
public:
BusyWaitingThread() : m_run(1), m_success(0) {}
void operator()() {
SetProcessorAffinity(0);
SetThreadRealtimePriorityOrDie(21);
system_clock::time_point start_time = system_clock::now();
m_started.Notify();
while (m_run.test_and_set()) {
// Have the busy waiting thread time out after a while. If it times out,
// the test failed.
if (system_clock::now() - start_time > std::chrono::milliseconds(50)) {
return;
}
}
m_success.store(1);
}
void quit() { m_run.clear(); }
void WaitForStartup() { m_started.Wait(); }
bool success() { return m_success.load(); }
private:
// Use an atomic type to signal if the thread should be running or not. A
// mutex could affect the scheduler, which isn't worth it. A delay between
// setting and reading the new value is fine.
std::atomic_flag m_run;
Notification m_started;
std::atomic<int> m_success;
};
// This thread starts up, grabs the mutex, and then exits.
template <typename MutexType>
class HighPriorityThread {
public:
explicit HighPriorityThread(MutexType* mutex) : m_mutex(mutex) {}
void operator()() {
SetProcessorAffinity(0);
SetThreadRealtimePriorityOrDie(22);
m_started.Notify();
m_mutex->lock();
m_success.store(1);
}
void WaitForStartup() { m_started.Wait(); }
bool success() { return m_success.load(); }
private:
Notification m_started;
MutexType* m_mutex;
std::atomic<int> m_success{0};
};
// Class to test a MutexType to see if it solves the priority inheritance
// problem.
//
// To run the test, we need 3 threads, and then 1 thread to kick the test off.
// The threads must all run on the same core, otherwise they wouldn't starve
// eachother. The threads and their roles are as follows:
//
// Low priority thread:
// Holds a lock that the high priority thread needs, and releases it upon
// request.
// Medium priority thread:
// Hogs the processor so that the low priority thread will never run if it's
// priority doesn't get bumped.
// High priority thread:
// Starts up and then goes to grab the lock that the low priority thread has.
//
// Control thread:
// Sets the deadlock up so that it will happen 100% of the time by making sure
// that each thread in order gets to the point that it needs to be at to cause
// the deadlock.
template <typename MutexType>
class InversionTestRunner {
public:
void operator()() {
// This thread must run at the highest priority or it can't coordinate the
// inversion. This means that it can't busy wait or everything could
// starve.
SetThreadRealtimePriorityOrDie(23);
MutexType m;
// Start the lowest priority thread and wait until it holds the lock.
LowPriorityThread<MutexType> low(&m);
std::thread low_thread(std::ref(low));
low.WaitForStartup();
// Start the busy waiting thread and let it get to the loop.
BusyWaitingThread busy;
std::thread busy_thread(std::ref(busy));
busy.WaitForStartup();
// Start the high priority thread and let it become blocked on the lock.
HighPriorityThread<MutexType> high(&m);
std::thread high_thread(std::ref(high));
high.WaitForStartup();
// Startup and locking the mutex in the high priority thread aren't atomic,
// but pretty close. Wait a bit to let it happen.
std::this_thread::sleep_for(std::chrono::milliseconds(10));
// Release the mutex in the low priority thread. If done right, everything
// should finish now.
low.release_mutex();
// Wait for everything to finish and compute success.
high_thread.join();
busy.quit();
busy_thread.join();
low_thread.join();
m_success = low.success() && busy.success() && high.success();
}
bool success() { return m_success; }
private:
bool m_success = false;
};
// TODO: Fix roborio permissions to run as root.
// Priority inversion test.
TEST(MutexTest, DISABLED_PriorityInversionTest) {
InversionTestRunner<hal::priority_mutex> runner;
std::thread runner_thread(std::ref(runner));
runner_thread.join();
EXPECT_TRUE(runner.success());
}
// Verify that the non-priority inversion mutex doesn't pass the test.
TEST(MutexTest, DISABLED_StdMutexPriorityInversionTest) {
InversionTestRunner<std::mutex> runner;
std::thread runner_thread(std::ref(runner));
runner_thread.join();
EXPECT_FALSE(runner.success());
}
// Smoke test to make sure that mutexes lock and unlock.
TEST(MutexTest, TryLock) {
hal::priority_mutex m;
m.lock();
EXPECT_FALSE(m.try_lock());
m.unlock();
EXPECT_TRUE(m.try_lock());
}
// Priority inversion test.
TEST(MutexTest, DISABLED_ReentrantPriorityInversionTest) {
InversionTestRunner<hal::priority_recursive_mutex> runner;
std::thread runner_thread(std::ref(runner));
runner_thread.join();
EXPECT_TRUE(runner.success());
}
// Smoke test to make sure that mutexes lock and unlock.
TEST(MutexTest, ReentrantTryLock) {
hal::priority_recursive_mutex m;
m.lock();
EXPECT_TRUE(m.try_lock());
m.unlock();
EXPECT_TRUE(m.try_lock());
}
} // namespace testing
} // namespace wpilib

View File

@@ -0,0 +1,42 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "Notifier.h" // NOLINT(build/include_order)
#include "TestBench.h"
#include "Timer.h"
#include "gtest/gtest.h"
#include "llvm/raw_ostream.h"
using namespace frc;
unsigned notifierCounter;
void notifierHandler(void*) { notifierCounter++; }
/**
* Test if the Wait function works
*/
TEST(NotifierTest, DISABLED_TestTimerNotifications) {
llvm::outs() << "NotifierTest...\n";
notifierCounter = 0;
llvm::outs() << "notifier(notifierHandler, nullptr)...\n";
Notifier notifier(notifierHandler, nullptr);
llvm::outs() << "Start Periodic...\n";
notifier.StartPeriodic(1.0);
llvm::outs() << "Wait...\n";
Wait(10.5);
llvm::outs() << "...Wait\n";
EXPECT_EQ(10u, notifierCounter) << "Received " << notifierCounter
<< " notifications in 10.5 seconds";
llvm::outs() << "Received " << notifierCounter
<< " notifications in 10.5 seconds";
llvm::outs() << "...NotifierTest\n";
}

View File

@@ -0,0 +1,159 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "AnalogInput.h"
#include "Compressor.h"
#include "DigitalInput.h"
#include "DigitalOutput.h"
#include "DoubleSolenoid.h"
#include "Solenoid.h"
#include "TestBench.h"
#include "Timer.h"
#include "gtest/gtest.h"
using namespace frc;
/* The PCM switches the compressor up to a couple seconds after the pressure
switch changes. */
static const double kCompressorDelayTime = 3.0;
/* Solenoids should change much more quickly */
static const double kSolenoidDelayTime = 0.5;
/* The voltage divider on the test bench should bring the compressor output
to around these values. */
static const double kCompressorOnVoltage = 5.00;
static const double kCompressorOffVoltage = 1.68;
class PCMTest : public testing::Test {
protected:
Compressor* m_compressor;
DigitalOutput* m_fakePressureSwitch;
AnalogInput* m_fakeCompressor;
DoubleSolenoid* m_doubleSolenoid;
DigitalInput *m_fakeSolenoid1, *m_fakeSolenoid2;
void SetUp() override {
m_compressor = new Compressor();
m_fakePressureSwitch =
new DigitalOutput(TestBench::kFakePressureSwitchChannel);
m_fakeCompressor = new AnalogInput(TestBench::kFakeCompressorChannel);
m_fakeSolenoid1 = new DigitalInput(TestBench::kFakeSolenoid1Channel);
m_fakeSolenoid2 = new DigitalInput(TestBench::kFakeSolenoid2Channel);
}
void TearDown() override {
delete m_compressor;
delete m_fakePressureSwitch;
delete m_fakeCompressor;
delete m_fakeSolenoid1;
delete m_fakeSolenoid2;
}
void Reset() {
m_compressor->Stop();
m_fakePressureSwitch->Set(false);
}
};
/**
* Test if the compressor turns on and off when the pressure switch is toggled
*/
TEST_F(PCMTest, PressureSwitch) {
Reset();
m_compressor->SetClosedLoopControl(true);
// Turn on the compressor
m_fakePressureSwitch->Set(true);
Wait(kCompressorDelayTime);
EXPECT_NEAR(kCompressorOnVoltage, m_fakeCompressor->GetVoltage(), 0.5)
<< "Compressor did not turn on when the pressure switch turned on.";
// Turn off the compressor
m_fakePressureSwitch->Set(false);
Wait(kCompressorDelayTime);
EXPECT_NEAR(kCompressorOffVoltage, m_fakeCompressor->GetVoltage(), 0.5)
<< "Compressor did not turn off when the pressure switch turned off.";
}
/**
* Test if the correct solenoids turn on and off when they should
*/
TEST_F(PCMTest, Solenoid) {
Reset();
Solenoid solenoid1(TestBench::kSolenoidChannel1);
Solenoid solenoid2(TestBench::kSolenoidChannel2);
// Turn both solenoids off
solenoid1.Set(false);
solenoid2.Set(false);
Wait(kSolenoidDelayTime);
EXPECT_TRUE(m_fakeSolenoid1->Get()) << "Solenoid #1 did not turn off";
EXPECT_TRUE(m_fakeSolenoid2->Get()) << "Solenoid #2 did not turn off";
EXPECT_FALSE(solenoid1.Get()) << "Solenoid #1 did not read off";
EXPECT_FALSE(solenoid2.Get()) << "Solenoid #2 did not read off";
// Turn one solenoid on and one off
solenoid1.Set(true);
solenoid2.Set(false);
Wait(kSolenoidDelayTime);
EXPECT_FALSE(m_fakeSolenoid1->Get()) << "Solenoid #1 did not turn on";
EXPECT_TRUE(m_fakeSolenoid2->Get()) << "Solenoid #2 did not turn off";
EXPECT_TRUE(solenoid1.Get()) << "Solenoid #1 did not read on";
EXPECT_FALSE(solenoid2.Get()) << "Solenoid #2 did not read off";
// Turn one solenoid on and one off
solenoid1.Set(false);
solenoid2.Set(true);
Wait(kSolenoidDelayTime);
EXPECT_TRUE(m_fakeSolenoid1->Get()) << "Solenoid #1 did not turn off";
EXPECT_FALSE(m_fakeSolenoid2->Get()) << "Solenoid #2 did not turn on";
EXPECT_FALSE(solenoid1.Get()) << "Solenoid #1 did not read off";
EXPECT_TRUE(solenoid2.Get()) << "Solenoid #2 did not read on";
// Turn both on
solenoid1.Set(true);
solenoid2.Set(true);
Wait(kSolenoidDelayTime);
EXPECT_FALSE(m_fakeSolenoid1->Get()) << "Solenoid #1 did not turn on";
EXPECT_FALSE(m_fakeSolenoid2->Get()) << "Solenoid #2 did not turn on";
EXPECT_TRUE(solenoid1.Get()) << "Solenoid #1 did not read on";
EXPECT_TRUE(solenoid2.Get()) << "Solenoid #2 did not read on";
}
/**
* Test if the correct solenoids turn on and off when they should when used
* with the DoubleSolenoid class.
*/
TEST_F(PCMTest, DoubleSolenoid) {
DoubleSolenoid solenoid(TestBench::kSolenoidChannel1,
TestBench::kSolenoidChannel2);
solenoid.Set(DoubleSolenoid::kOff);
Wait(kSolenoidDelayTime);
EXPECT_TRUE(m_fakeSolenoid1->Get()) << "Solenoid #1 did not turn off";
EXPECT_TRUE(m_fakeSolenoid2->Get()) << "Solenoid #2 did not turn off";
EXPECT_TRUE(solenoid.Get() == DoubleSolenoid::kOff)
<< "Solenoid does not read off";
solenoid.Set(DoubleSolenoid::kForward);
Wait(kSolenoidDelayTime);
EXPECT_FALSE(m_fakeSolenoid1->Get()) << "Solenoid #1 did not turn on";
EXPECT_TRUE(m_fakeSolenoid2->Get()) << "Solenoid #2 did not turn off";
EXPECT_TRUE(solenoid.Get() == DoubleSolenoid::kForward)
<< "Solenoid does not read forward";
solenoid.Set(DoubleSolenoid::kReverse);
Wait(kSolenoidDelayTime);
EXPECT_TRUE(m_fakeSolenoid1->Get()) << "Solenoid #1 did not turn off";
EXPECT_FALSE(m_fakeSolenoid2->Get()) << "Solenoid #2 did not turn on";
EXPECT_TRUE(solenoid.Get() == DoubleSolenoid::kReverse)
<< "Solenoid does not read reverse";
}

View File

@@ -0,0 +1,107 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "PIDController.h"
#include "PIDOutput.h"
#include "PIDSource.h"
#include "TestBench.h"
#include "Timer.h"
#include "gtest/gtest.h"
using namespace frc;
class PIDToleranceTest : public testing::Test {
protected:
const double setpoint = 50.0;
const double range = 200;
const double tolerance = 10.0;
class fakeInput : public PIDSource {
public:
double val = 0;
void SetPIDSourceType(PIDSourceType pidSource) {}
PIDSourceType GetPIDSourceType() { return PIDSourceType::kDisplacement; }
double PIDGet() { return val; }
};
class fakeOutput : public PIDOutput {
void PIDWrite(double output) {}
};
fakeInput inp;
fakeOutput out;
PIDController* pid;
void SetUp() override {
pid = new PIDController(0.5, 0.0, 0.0, &inp, &out);
pid->SetInputRange(-range / 2, range / 2);
}
void TearDown() override { delete pid; }
void Reset() { inp.val = 0; }
};
TEST_F(PIDToleranceTest, Absolute) {
Reset();
pid->SetAbsoluteTolerance(tolerance);
pid->SetSetpoint(setpoint);
pid->Enable();
EXPECT_FALSE(pid->OnTarget())
<< "Error was in tolerance when it should not have been. Error was "
<< pid->GetAvgError();
inp.val = setpoint + tolerance / 2;
Wait(1.0);
EXPECT_TRUE(pid->OnTarget())
<< "Error was not in tolerance when it should have been. Error was "
<< pid->GetAvgError();
inp.val = setpoint + 10 * tolerance;
Wait(1.0);
EXPECT_FALSE(pid->OnTarget())
<< "Error was in tolerance when it should not have been. Error was "
<< pid->GetAvgError();
}
TEST_F(PIDToleranceTest, Percent) {
Reset();
pid->SetPercentTolerance(tolerance);
pid->SetSetpoint(setpoint);
pid->Enable();
EXPECT_FALSE(pid->OnTarget())
<< "Error was in tolerance when it should not have been. Error was "
<< pid->GetAvgError();
inp.val = setpoint +
(tolerance) / 200 *
range; // half of percent tolerance away from setpoint
Wait(1.0);
EXPECT_TRUE(pid->OnTarget())
<< "Error was not in tolerance when it should have been. Error was "
<< pid->GetAvgError();
inp.val =
setpoint +
(tolerance) / 50 * range; // double percent tolerance away from setPoint
Wait(1.0);
EXPECT_FALSE(pid->OnTarget())
<< "Error was in tolerance when it should not have been. Error was "
<< pid->GetAvgError();
}

View File

@@ -0,0 +1,60 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "PowerDistributionPanel.h" // NOLINT(build/include_order)
#include "Jaguar.h"
#include "Talon.h"
#include "TestBench.h"
#include "Timer.h"
#include "Victor.h"
#include "gtest/gtest.h"
using namespace frc;
static const double kMotorTime = 0.25;
class PowerDistributionPanelTest : public testing::Test {
protected:
PowerDistributionPanel* m_pdp;
Talon* m_talon;
Victor* m_victor;
Jaguar* m_jaguar;
void SetUp() override {
m_pdp = new PowerDistributionPanel();
m_talon = new Talon(TestBench::kTalonChannel);
m_victor = new Victor(TestBench::kVictorChannel);
m_jaguar = new Jaguar(TestBench::kJaguarChannel);
}
void TearDown() override {
delete m_pdp;
delete m_talon;
delete m_victor;
delete m_jaguar;
}
};
/**
* Test if the current changes when the motor is driven using a talon
*/
TEST_F(PowerDistributionPanelTest, CheckCurrentTalon) {
Wait(kMotorTime);
/* The Current should be 0 */
EXPECT_FLOAT_EQ(0, m_pdp->GetCurrent(TestBench::kTalonPDPChannel))
<< "The Talon current was non-zero";
/* Set the motor to full forward */
m_talon->Set(1.0);
Wait(kMotorTime);
/* The current should now be positive */
ASSERT_GT(m_pdp->GetCurrent(TestBench::kTalonPDPChannel), 0)
<< "The Talon current was not positive";
}

View File

@@ -0,0 +1,102 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "Preferences.h" // NOLINT(build/include_order)
#include <cstdio>
#include <fstream>
#include "Timer.h"
#include "gtest/gtest.h"
#include "ntcore.h"
using namespace frc;
static const char* kFileName = "networktables.ini";
static const double kSaveTime = 1.2;
/**
* If we write a new networktables.ini with some sample values, test that
* we get those same values back using the Preference class.
*/
TEST(PreferencesTest, ReadPreferencesFromFile) {
NetworkTable::Shutdown();
std::remove(kFileName);
std::ofstream preferencesFile(kFileName);
preferencesFile << "[NetworkTables Storage 3.0]" << std::endl;
preferencesFile
<< "string \"/Preferences/testFileGetString\"=\"Hello, preferences file\""
<< std::endl;
preferencesFile << "double \"/Preferences/testFileGetInt\"=1" << std::endl;
preferencesFile << "double \"/Preferences/testFileGetDouble\"=0.5"
<< std::endl;
preferencesFile << "double \"/Preferences/testFileGetFloat\"=0.25"
<< std::endl;
preferencesFile << "boolean \"/Preferences/testFileGetBoolean\"=true"
<< std::endl;
preferencesFile
<< "double \"/Preferences/testFileGetLong\"=1000000000000000000"
<< std::endl;
preferencesFile.close();
NetworkTable::Initialize();
Preferences* preferences = Preferences::GetInstance();
EXPECT_EQ("Hello, preferences file",
preferences->GetString("testFileGetString"));
EXPECT_EQ(1, preferences->GetInt("testFileGetInt"));
EXPECT_FLOAT_EQ(0.5, preferences->GetDouble("testFileGetDouble"));
EXPECT_FLOAT_EQ(0.25f, preferences->GetFloat("testFileGetFloat"));
EXPECT_TRUE(preferences->GetBoolean("testFileGetBoolean"));
EXPECT_EQ(1000000000000000000ll, preferences->GetLong("testFileGetLong"));
}
/**
* If we set some values using the Preferences class, test that they show up
* in networktables.ini
*/
TEST(PreferencesTest, WritePreferencesToFile) {
NetworkTable::Shutdown();
NetworkTable::GlobalDeleteAll();
// persistent keys don't get deleted normally, so make remaining keys
// non-persistent and delete them too
for (const auto& info : nt::GetEntryInfo("", 0)) {
nt::SetEntryFlags(info.name, 0);
}
NetworkTable::GlobalDeleteAll();
std::remove(kFileName);
NetworkTable::Initialize();
Preferences* preferences = Preferences::GetInstance();
preferences->PutString("testFilePutString", "Hello, preferences file");
preferences->PutInt("testFilePutInt", 1);
preferences->PutDouble("testFilePutDouble", 0.5);
preferences->PutFloat("testFilePutFloat", 0.25f);
preferences->PutBoolean("testFilePutBoolean", true);
preferences->PutLong("testFilePutLong", 1000000000000000000ll);
Wait(kSaveTime);
static char const* kExpectedFileContents[] = {
"[NetworkTables Storage 3.0]",
"boolean \"/Preferences/testFilePutBoolean\"=true",
"double \"/Preferences/testFilePutDouble\"=0.5",
"double \"/Preferences/testFilePutFloat\"=0.25",
"double \"/Preferences/testFilePutInt\"=1",
"double \"/Preferences/testFilePutLong\"=1e+18",
"string \"/Preferences/testFilePutString\"=\"Hello, preferences file\""};
std::ifstream preferencesFile(kFileName);
for (auto& kExpectedFileContent : kExpectedFileContents) {
ASSERT_FALSE(preferencesFile.eof())
<< "Preferences file prematurely reached EOF";
std::string line;
std::getline(preferencesFile, line);
ASSERT_EQ(kExpectedFileContent, line)
<< "A line in networktables.ini was not correct";
}
}

View File

@@ -0,0 +1,93 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "Relay.h" // NOLINT(build/include_order)
#include "DigitalInput.h"
#include "TestBench.h"
#include "Timer.h"
#include "gtest/gtest.h"
using namespace frc;
static const double kDelayTime = 0.01;
class RelayTest : public testing::Test {
protected:
Relay* m_relay;
DigitalInput* m_forward;
DigitalInput* m_reverse;
void SetUp() override {
m_relay = new Relay(TestBench::kRelayChannel);
m_forward = new DigitalInput(TestBench::kFakeRelayForward);
m_reverse = new DigitalInput(TestBench::kFakeRelayReverse);
}
void TearDown() override {
delete m_relay;
delete m_forward;
delete m_reverse;
}
void Reset() { m_relay->Set(Relay::kOff); }
};
/**
* Test the relay by setting it forward, reverse, off, and on.
*/
TEST_F(RelayTest, Relay) {
Reset();
// set the relay to forward
m_relay->Set(Relay::kForward);
Wait(kDelayTime);
EXPECT_TRUE(m_forward->Get()) << "Relay did not set forward";
EXPECT_FALSE(m_reverse->Get()) << "Relay did not set forward";
EXPECT_EQ(m_relay->Get(), Relay::kForward);
// set the relay to reverse
m_relay->Set(Relay::kReverse);
Wait(kDelayTime);
EXPECT_TRUE(m_reverse->Get()) << "Relay did not set reverse";
EXPECT_FALSE(m_forward->Get()) << "Relay did not set reverse";
EXPECT_EQ(m_relay->Get(), Relay::kReverse);
// set the relay to off
m_relay->Set(Relay::kOff);
Wait(kDelayTime);
EXPECT_FALSE(m_forward->Get()) << "Relay did not set off";
EXPECT_FALSE(m_reverse->Get()) << "Relay did not set off";
EXPECT_EQ(m_relay->Get(), Relay::kOff);
// set the relay to on
m_relay->Set(Relay::kOn);
Wait(kDelayTime);
EXPECT_TRUE(m_forward->Get()) << "Relay did not set on";
EXPECT_TRUE(m_reverse->Get()) << "Relay did not set on";
EXPECT_EQ(m_relay->Get(), Relay::kOn);
// test forward direction
delete m_relay;
m_relay = new Relay(TestBench::kRelayChannel, Relay::kForwardOnly);
m_relay->Set(Relay::kOn);
Wait(kDelayTime);
EXPECT_TRUE(m_forward->Get()) << "Relay did not set forward";
EXPECT_FALSE(m_reverse->Get()) << "Relay did not set forward";
EXPECT_EQ(m_relay->Get(), Relay::kOn);
// test reverse direction
delete m_relay;
m_relay = new Relay(TestBench::kRelayChannel, Relay::kReverseOnly);
m_relay->Set(Relay::kOn);
Wait(kDelayTime);
EXPECT_FALSE(m_forward->Get()) << "Relay did not set reverse";
EXPECT_TRUE(m_reverse->Get()) << "Relay did not set reverse";
EXPECT_EQ(m_relay->Get(), Relay::kOn);
}

View File

@@ -0,0 +1,52 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include <cstdlib>
#include "DriverStation.h"
#include "HAL/HAL.h"
#include "LiveWindow/LiveWindow.h"
#include "Timer.h"
#include "gtest/gtest.h"
#include "llvm/raw_ostream.h"
using namespace frc;
class TestEnvironment : public testing::Environment {
bool m_alreadySetUp = false;
public:
void SetUp() override {
/* Only set up once. This allows gtest_repeat to be used to
automatically repeat tests. */
if (m_alreadySetUp) return;
m_alreadySetUp = true;
if (!HAL_Initialize(500, 0)) {
llvm::errs() << "FATAL ERROR: HAL could not be initialized\n";
std::exit(-1);
}
/* This sets up the network communications library to enable the driver
station. After starting network coms, it will loop until the driver
station returns that the robot is enabled, to ensure that tests
will be able to run on the hardware. */
HAL_ObserveUserProgramStarting();
LiveWindow::GetInstance()->SetEnabled(false);
llvm::outs() << "Waiting for enable\n";
while (!DriverStation::GetInstance().IsEnabled()) {
Wait(0.1);
}
}
void TearDown() override {}
};
testing::Environment* const environment =
testing::AddGlobalTestEnvironment(new TestEnvironment);

View File

@@ -0,0 +1,172 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include <cmath>
#include "ADXL345_SPI.h"
#include "AnalogGyro.h"
#include "Servo.h"
#include "TestBench.h"
#include "Timer.h"
#include "gtest/gtest.h"
using namespace frc;
static constexpr double kServoResetTime = 2.0;
static constexpr double kTestAngle = 90.0;
static constexpr double kTiltSetpoint0 = 0.22;
static constexpr double kTiltSetpoint45 = 0.45;
static constexpr double kTiltSetpoint90 = 0.68;
static constexpr double kTiltTime = 1.0;
static constexpr double kAccelerometerTolerance = 0.2;
static constexpr double kSensitivity = 0.013;
/**
* A fixture for the camera with two servos and a gyro
*/
class TiltPanCameraTest : public testing::Test {
protected:
static AnalogGyro* m_gyro;
Servo *m_tilt, *m_pan;
Accelerometer* m_spiAccel;
static void SetUpTestCase() {
// The gyro object blocks for 5 seconds in the constructor, so only
// construct it once for the whole test case
m_gyro = new AnalogGyro(TestBench::kCameraGyroChannel);
m_gyro->SetSensitivity(kSensitivity);
}
static void TearDownTestCase() { delete m_gyro; }
void SetUp() override {
m_tilt = new Servo(TestBench::kCameraTiltChannel);
m_pan = new Servo(TestBench::kCameraPanChannel);
m_spiAccel = new ADXL345_SPI(SPI::kOnboardCS1);
m_tilt->Set(kTiltSetpoint45);
m_pan->SetAngle(0.0);
Wait(kServoResetTime);
m_gyro->Reset();
}
void DefaultGyroAngle();
void GyroAngle();
void GyroCalibratedParameters();
void TearDown() override {
delete m_tilt;
delete m_pan;
delete m_spiAccel;
}
};
AnalogGyro* TiltPanCameraTest::m_gyro = nullptr;
/**
* Test if the gyro angle defaults to 0 immediately after being reset.
*/
void TiltPanCameraTest::DefaultGyroAngle() {
EXPECT_NEAR(0.0, m_gyro->GetAngle(), 1.0);
}
/**
* Test if the servo turns 90 degrees and the gyroscope measures this angle
* Note servo on TestBench is not the same type of servo that servo class
* was designed for so setAngle is significantly off. This has been calibrated
* for the servo on the rig.
*/
void TiltPanCameraTest::GyroAngle() {
// Make sure that the gyro doesn't get jerked when the servo goes to zero.
m_pan->SetAngle(0.0);
Wait(0.5);
m_gyro->Reset();
for (int32_t i = 0; i < 600; i++) {
m_pan->Set(i / 1000.0);
Wait(0.001);
}
double gyroAngle = m_gyro->GetAngle();
EXPECT_NEAR(gyroAngle, kTestAngle, 10.0)
<< "Gyro measured " << gyroAngle << " degrees, servo should have turned "
<< kTestAngle << " degrees";
}
/**
* Gets calibrated parameters from previously calibrated gyro, allocates a new
* gyro with the given parameters for center and offset, and re-runs tests on
* the new gyro.
*/
void TiltPanCameraTest::GyroCalibratedParameters() {
uint32_t cCenter = m_gyro->GetCenter();
double cOffset = m_gyro->GetOffset();
delete m_gyro;
m_gyro = new AnalogGyro(TestBench::kCameraGyroChannel, cCenter, cOffset);
m_gyro->SetSensitivity(kSensitivity);
// Default gyro angle test
// Accumulator needs a small amount of time to reset before being tested
m_gyro->Reset();
Wait(0.001);
EXPECT_NEAR(0.0, m_gyro->GetAngle(), 1.0);
// Gyro angle test
// Make sure that the gyro doesn't get jerked when the servo goes to zero.
m_pan->SetAngle(0.0);
Wait(0.5);
m_gyro->Reset();
for (int32_t i = 0; i < 600; i++) {
m_pan->Set(i / 1000.0);
Wait(0.001);
}
double gyroAngle = m_gyro->GetAngle();
EXPECT_NEAR(gyroAngle, kTestAngle, 10.0)
<< "Gyro measured " << gyroAngle << " degrees, servo should have turned "
<< kTestAngle << " degrees";
}
/**
* Run all gyro tests in one function to make sure they are run in order.
*/
TEST_F(TiltPanCameraTest, TestAllGyroTests) {
DefaultGyroAngle();
GyroAngle();
GyroCalibratedParameters();
}
/**
* Test if the accelerometer measures gravity along the correct axes when the
* camera rotates
*/
TEST_F(TiltPanCameraTest, SPIAccelerometer) {
m_tilt->Set(kTiltSetpoint0);
Wait(kTiltTime);
EXPECT_NEAR(-1.0, m_spiAccel->GetX(), kAccelerometerTolerance);
EXPECT_NEAR(0.0, m_spiAccel->GetY(), kAccelerometerTolerance);
EXPECT_NEAR(0.0, m_spiAccel->GetZ(), kAccelerometerTolerance);
m_tilt->Set(kTiltSetpoint45);
Wait(kTiltTime);
EXPECT_NEAR(-std::sqrt(0.5), m_spiAccel->GetX(), kAccelerometerTolerance);
EXPECT_NEAR(0.0, m_spiAccel->GetY(), kAccelerometerTolerance);
EXPECT_NEAR(std::sqrt(0.5), m_spiAccel->GetZ(), kAccelerometerTolerance);
m_tilt->Set(kTiltSetpoint90);
Wait(kTiltTime);
EXPECT_NEAR(0.0, m_spiAccel->GetX(), kAccelerometerTolerance);
EXPECT_NEAR(0.0, m_spiAccel->GetY(), kAccelerometerTolerance);
EXPECT_NEAR(1.0, m_spiAccel->GetZ(), kAccelerometerTolerance);
}

View File

@@ -0,0 +1,41 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "Timer.h" // NOLINT(build/include_order)
#include "TestBench.h"
#include "gtest/gtest.h"
using namespace frc;
static const double kWaitTime = 0.5;
class TimerTest : public testing::Test {
protected:
Timer* m_timer;
void SetUp() override { m_timer = new Timer; }
void TearDown() override { delete m_timer; }
void Reset() { m_timer->Reset(); }
};
/**
* Test if the Wait function works
*/
TEST_F(TimerTest, Wait) {
Reset();
double initialTime = m_timer->GetFPGATimestamp();
Wait(kWaitTime);
double finalTime = m_timer->GetFPGATimestamp();
EXPECT_NEAR(kWaitTime, finalTime - initialTime, 0.001);
}

View File

@@ -0,0 +1,444 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "Commands/CommandGroup.h"
#include "Commands/Scheduler.h"
#include "Commands/Subsystem.h"
#include "DriverStation.h"
#include "RobotState.h"
#include "Timer.h"
#include "command/MockCommand.h"
#include "gtest/gtest.h"
using namespace frc;
class CommandTest : public testing::Test {
protected:
void SetUp() override {
RobotState::SetImplementation(DriverStation::GetInstance());
Scheduler::GetInstance()->SetEnabled(true);
}
/**
* Tears Down the Scheduler at the end of each test.
* Must be called at the end of each test inside each test in order to prevent
* them being deallocated
* when they leave the scope of the test (causing a segfault).
* This can not be done within the virtual void Teardown() method because it
* is called outside of the
* scope of the test.
*/
void TeardownScheduler() { Scheduler::GetInstance()->ResetAll(); }
void AssertCommandState(MockCommand& command, int32_t initialize,
int32_t execute, int32_t isFinished, int32_t end,
int32_t interrupted) {
EXPECT_EQ(initialize, command.GetInitializeCount());
EXPECT_EQ(execute, command.GetExecuteCount());
EXPECT_EQ(isFinished, command.GetIsFinishedCount());
EXPECT_EQ(end, command.GetEndCount());
EXPECT_EQ(interrupted, command.GetInterruptedCount());
}
};
class ASubsystem : public Subsystem {
private:
Command* m_command = nullptr;
public:
explicit ASubsystem(const std::string& name) : Subsystem(name) {}
void InitDefaultCommand() override {
if (m_command != nullptr) {
SetDefaultCommand(m_command);
}
}
void Init(Command* command) { m_command = command; }
};
// CommandParallelGroupTest ported from CommandParallelGroupTest.java
TEST_F(CommandTest, ParallelCommands) {
MockCommand command1;
MockCommand command2;
CommandGroup commandGroup;
commandGroup.AddParallel(&command1);
commandGroup.AddParallel(&command2);
AssertCommandState(command1, 0, 0, 0, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
commandGroup.Start();
AssertCommandState(command1, 0, 0, 0, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 0, 0, 0, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 1, 1, 0, 0);
AssertCommandState(command2, 1, 1, 1, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 2, 2, 0, 0);
AssertCommandState(command2, 1, 2, 2, 0, 0);
command1.SetHasFinished(true);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 3, 3, 1, 0);
AssertCommandState(command2, 1, 3, 3, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 3, 3, 1, 0);
AssertCommandState(command2, 1, 4, 4, 0, 0);
command2.SetHasFinished(true);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 3, 3, 1, 0);
AssertCommandState(command2, 1, 5, 5, 1, 0);
TeardownScheduler();
}
// END CommandParallelGroupTest
// CommandScheduleTest ported from CommandScheduleTest.java
TEST_F(CommandTest, RunAndTerminate) {
MockCommand command;
command.Start();
AssertCommandState(command, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command, 1, 1, 1, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command, 1, 2, 2, 0, 0);
command.SetHasFinished(true);
AssertCommandState(command, 1, 2, 2, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command, 1, 3, 3, 1, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command, 1, 3, 3, 1, 0);
TeardownScheduler();
}
TEST_F(CommandTest, RunAndCancel) {
MockCommand command;
command.Start();
AssertCommandState(command, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command, 1, 1, 1, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command, 1, 2, 2, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command, 1, 3, 3, 0, 0);
command.Cancel();
AssertCommandState(command, 1, 3, 3, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command, 1, 3, 3, 0, 1);
Scheduler::GetInstance()->Run();
AssertCommandState(command, 1, 3, 3, 0, 1);
TeardownScheduler();
}
// END CommandScheduleTest
// CommandSequentialGroupTest ported from CommandSequentialGroupTest.java
TEST_F(CommandTest, ThreeCommandOnSubSystem) {
ASubsystem subsystem("Three Command Test Subsystem");
MockCommand command1;
command1.Requires(&subsystem);
MockCommand command2;
command2.Requires(&subsystem);
MockCommand command3;
command3.Requires(&subsystem);
CommandGroup commandGroup;
commandGroup.AddSequential(&command1, 1.0);
commandGroup.AddSequential(&command2, 2.0);
commandGroup.AddSequential(&command3);
AssertCommandState(command1, 0, 0, 0, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
AssertCommandState(command3, 0, 0, 0, 0, 0);
commandGroup.Start();
AssertCommandState(command1, 0, 0, 0, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
AssertCommandState(command3, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 0, 0, 0, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
AssertCommandState(command3, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 1, 1, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
AssertCommandState(command3, 0, 0, 0, 0, 0);
Wait(1); // command 1 timeout
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 1, 1, 0, 1);
AssertCommandState(command2, 1, 1, 1, 0, 0);
AssertCommandState(command3, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 1, 1, 0, 1);
AssertCommandState(command2, 1, 2, 2, 0, 0);
AssertCommandState(command3, 0, 0, 0, 0, 0);
Wait(2); // command 2 timeout
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 1, 1, 0, 1);
AssertCommandState(command2, 1, 2, 2, 0, 1);
AssertCommandState(command3, 1, 1, 1, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 1, 1, 0, 1);
AssertCommandState(command2, 1, 2, 2, 0, 1);
AssertCommandState(command3, 1, 2, 2, 0, 0);
command3.SetHasFinished(true);
AssertCommandState(command1, 1, 1, 1, 0, 1);
AssertCommandState(command2, 1, 2, 2, 0, 1);
AssertCommandState(command3, 1, 2, 2, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 1, 1, 0, 1);
AssertCommandState(command2, 1, 2, 2, 0, 1);
AssertCommandState(command3, 1, 3, 3, 1, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 1, 1, 0, 1);
AssertCommandState(command2, 1, 2, 2, 0, 1);
AssertCommandState(command3, 1, 3, 3, 1, 0);
TeardownScheduler();
}
// END CommandSequentialGroupTest
// CommandSequentialGroupTest ported from CommandSequentialGroupTest.java
TEST_F(CommandTest, OneCommandSupersedingAnotherBecauseOfDependencies) {
auto subsystem = new ASubsystem("Command Superseding Test Subsystem");
MockCommand command1;
command1.Requires(subsystem);
MockCommand command2;
command2.Requires(subsystem);
AssertCommandState(command1, 0, 0, 0, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
command1.Start();
AssertCommandState(command1, 0, 0, 0, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 0, 0, 0, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 1, 1, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 2, 2, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 3, 3, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
command2.Start();
AssertCommandState(command1, 1, 3, 3, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 4, 4, 0, 1);
AssertCommandState(command2, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 4, 4, 0, 1);
AssertCommandState(command2, 1, 1, 1, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 4, 4, 0, 1);
AssertCommandState(command2, 1, 2, 2, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 4, 4, 0, 1);
AssertCommandState(command2, 1, 3, 3, 0, 0);
TeardownScheduler();
}
TEST_F(CommandTest,
OneCommandFailingSupersedingBecauseFirstCanNotBeInterrupted) {
ASubsystem subsystem("Command Superseding Test Subsystem");
MockCommand command1;
command1.Requires(&subsystem);
command1.SetInterruptible(false);
MockCommand command2;
command2.Requires(&subsystem);
AssertCommandState(command1, 0, 0, 0, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
command1.Start();
AssertCommandState(command1, 0, 0, 0, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 0, 0, 0, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 1, 1, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 2, 2, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 3, 3, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
command2.Start();
AssertCommandState(command1, 1, 3, 3, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command1, 1, 4, 4, 0, 0);
AssertCommandState(command2, 0, 0, 0, 0, 0);
TeardownScheduler();
}
// END CommandSequentialGroupTest
class ModifiedMockCommand : public MockCommand {
public:
ModifiedMockCommand() : MockCommand() { SetTimeout(2.0); }
bool IsFinished() override {
return MockCommand::IsFinished() || IsTimedOut();
}
};
TEST_F(CommandTest, TwoSecondTimeout) {
ASubsystem subsystem("Two Second Timeout Test Subsystem");
ModifiedMockCommand command;
command.Requires(&subsystem);
command.Start();
AssertCommandState(command, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command, 1, 1, 1, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command, 1, 2, 2, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command, 1, 3, 3, 0, 0);
Wait(2);
Scheduler::GetInstance()->Run();
AssertCommandState(command, 1, 4, 4, 1, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(command, 1, 4, 4, 1, 0);
TeardownScheduler();
}
TEST_F(CommandTest, DefaultCommandWhereTheInteruptingCommandEndsItself) {
ASubsystem subsystem("Default Command Test Subsystem");
MockCommand defaultCommand;
defaultCommand.Requires(&subsystem);
MockCommand anotherCommand;
anotherCommand.Requires(&subsystem);
AssertCommandState(defaultCommand, 0, 0, 0, 0, 0);
subsystem.Init(&defaultCommand);
AssertCommandState(defaultCommand, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(defaultCommand, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(defaultCommand, 1, 1, 1, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(defaultCommand, 1, 2, 2, 0, 0);
anotherCommand.Start();
AssertCommandState(defaultCommand, 1, 2, 2, 0, 0);
AssertCommandState(anotherCommand, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(defaultCommand, 1, 3, 3, 0, 1);
AssertCommandState(anotherCommand, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(defaultCommand, 1, 3, 3, 0, 1);
AssertCommandState(anotherCommand, 1, 1, 1, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(defaultCommand, 1, 3, 3, 0, 1);
AssertCommandState(anotherCommand, 1, 2, 2, 0, 0);
anotherCommand.SetHasFinished(true);
AssertCommandState(defaultCommand, 1, 3, 3, 0, 1);
AssertCommandState(anotherCommand, 1, 2, 2, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(defaultCommand, 1, 3, 3, 0, 1);
AssertCommandState(anotherCommand, 1, 3, 3, 1, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(defaultCommand, 2, 4, 4, 0, 1);
AssertCommandState(anotherCommand, 1, 3, 3, 1, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(defaultCommand, 2, 5, 5, 0, 1);
AssertCommandState(anotherCommand, 1, 3, 3, 1, 0);
TeardownScheduler();
}
TEST_F(CommandTest, DefaultCommandsInterruptingCommandCanceled) {
ASubsystem subsystem("Default Command Test Subsystem");
MockCommand defaultCommand;
defaultCommand.Requires(&subsystem);
MockCommand anotherCommand;
anotherCommand.Requires(&subsystem);
AssertCommandState(defaultCommand, 0, 0, 0, 0, 0);
subsystem.Init(&defaultCommand);
subsystem.InitDefaultCommand();
AssertCommandState(defaultCommand, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(defaultCommand, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(defaultCommand, 1, 1, 1, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(defaultCommand, 1, 2, 2, 0, 0);
anotherCommand.Start();
AssertCommandState(defaultCommand, 1, 2, 2, 0, 0);
AssertCommandState(anotherCommand, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(defaultCommand, 1, 3, 3, 0, 1);
AssertCommandState(anotherCommand, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(defaultCommand, 1, 3, 3, 0, 1);
AssertCommandState(anotherCommand, 1, 1, 1, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(defaultCommand, 1, 3, 3, 0, 1);
AssertCommandState(anotherCommand, 1, 2, 2, 0, 0);
anotherCommand.Cancel();
AssertCommandState(defaultCommand, 1, 3, 3, 0, 1);
AssertCommandState(anotherCommand, 1, 2, 2, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(defaultCommand, 1, 3, 3, 0, 1);
AssertCommandState(anotherCommand, 1, 2, 2, 0, 1);
Scheduler::GetInstance()->Run();
AssertCommandState(defaultCommand, 2, 4, 4, 0, 1);
AssertCommandState(anotherCommand, 1, 2, 2, 0, 1);
Scheduler::GetInstance()->Run();
AssertCommandState(defaultCommand, 2, 5, 5, 0, 1);
AssertCommandState(anotherCommand, 1, 2, 2, 0, 1);
TeardownScheduler();
}

View File

@@ -0,0 +1,100 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2017. 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. */
/*----------------------------------------------------------------------------*/
#include <DriverStation.h>
#include <RobotState.h>
#include "Commands/ConditionalCommand.h"
#include "Commands/Scheduler.h"
#include "command/MockCommand.h"
#include "command/MockConditionalCommand.h"
#include "gtest/gtest.h"
using namespace frc;
class ConditionalCommandTest : public testing::Test {
public:
MockConditionalCommand* m_command;
MockCommand* m_onTrue;
MockCommand* m_onFalse;
protected:
void SetUp() override {
RobotState::SetImplementation(DriverStation::GetInstance());
Scheduler::GetInstance()->SetEnabled(true);
m_onTrue = new MockCommand();
m_onFalse = new MockCommand();
m_command = new MockConditionalCommand(m_onTrue, m_onFalse);
}
void TearDown() override { delete m_command; }
/**
* Tears Down the Scheduler at the end of each test.
*
* Must be called at the end of each test inside each test in order to prevent
* them being deallocated when they leave the scope of the test (causing a
* segfault). This cannot be done within the virtual void Teardown() method
* because it is called outside of the scope of the test.
*/
void TeardownScheduler() { Scheduler::GetInstance()->ResetAll(); }
void AssertCommandState(MockCommand& command, int32_t initialize,
int32_t execute, int32_t isFinished, int32_t end,
int32_t interrupted) {
EXPECT_EQ(initialize, command.GetInitializeCount());
EXPECT_EQ(execute, command.GetExecuteCount());
EXPECT_EQ(isFinished, command.GetIsFinishedCount());
EXPECT_EQ(end, command.GetEndCount());
EXPECT_EQ(interrupted, command.GetInterruptedCount());
}
};
TEST_F(ConditionalCommandTest, OnTrueTest) {
m_command->SetCondition(true);
Scheduler::GetInstance()->AddCommand(m_command);
AssertCommandState(*m_onTrue, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run(); // init command and select m_onTrue
AssertCommandState(*m_onTrue, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run(); // init m_onTrue
AssertCommandState(*m_onTrue, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(*m_onTrue, 1, 1, 2, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(*m_onTrue, 1, 2, 4, 0, 0);
EXPECT_TRUE(m_onTrue->GetInitializeCount() > 0)
<< "Did not initialize the true command\n";
EXPECT_TRUE(m_onFalse->GetInitializeCount() == 0)
<< "Initialized the false command\n";
TeardownScheduler();
}
TEST_F(ConditionalCommandTest, OnFalseTest) {
m_command->SetCondition(false);
Scheduler::GetInstance()->AddCommand(m_command);
AssertCommandState(*m_onFalse, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run(); // init command and select m_onTrue
AssertCommandState(*m_onFalse, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run(); // init m_onTrue
AssertCommandState(*m_onFalse, 0, 0, 0, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(*m_onFalse, 1, 1, 2, 0, 0);
Scheduler::GetInstance()->Run();
AssertCommandState(*m_onFalse, 1, 2, 4, 0, 0);
EXPECT_TRUE(m_onFalse->GetInitializeCount() > 0)
<< "Did not initialize the false command";
EXPECT_TRUE(m_onTrue->GetInitializeCount() == 0)
<< "Initialized the true command";
TeardownScheduler();
}

View File

@@ -0,0 +1,38 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2014-2017. 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. */
/*----------------------------------------------------------------------------*/
#include "command/MockCommand.h"
using namespace frc;
MockCommand::MockCommand() {
m_initializeCount = 0;
m_executeCount = 0;
m_isFinishedCount = 0;
m_hasFinished = false;
m_endCount = 0;
m_interruptedCount = 0;
}
bool MockCommand::HasInitialized() { return GetInitializeCount() > 0; }
bool MockCommand::HasEnd() { return GetEndCount() > 0; }
bool MockCommand::HasInterrupted() { return GetInterruptedCount() > 0; }
void MockCommand::Initialize() { ++m_initializeCount; }
void MockCommand::Execute() { ++m_executeCount; }
bool MockCommand::IsFinished() {
++m_isFinishedCount;
return IsHasFinished();
}
void MockCommand::End() { ++m_endCount; }
void MockCommand::Interrupted() { ++m_interruptedCount; }

View File

@@ -0,0 +1,20 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2017. 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. */
/*----------------------------------------------------------------------------*/
#include "command/MockConditionalCommand.h"
using namespace frc;
MockConditionalCommand::MockConditionalCommand(MockCommand* onTrue,
MockCommand* onFalse)
: ConditionalCommand(onTrue, onFalse) {}
void MockConditionalCommand::SetCondition(bool condition) {
m_condition = condition;
}
bool MockConditionalCommand::Condition() { return m_condition; }