Squashed 'third_party/allwpilib_2017/' content from commit 35ac87d
Change-Id: I7bb6f5556c30d3f5a092e68de0be9c710c60c9f4
git-subtree-dir: third_party/allwpilib_2017
git-subtree-split: 35ac87d6ff8b7f061c4f18c9ea316e5dccd4888a
diff --git a/wpilibcIntegrationTests/src/AnalogLoopTest.cpp b/wpilibcIntegrationTests/src/AnalogLoopTest.cpp
new file mode 100644
index 0000000..7b0d18d
--- /dev/null
+++ b/wpilibcIntegrationTests/src/AnalogLoopTest.cpp
@@ -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, ¶m);
+ 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.";
+}
diff --git a/wpilibcIntegrationTests/src/AnalogPotentiometerTest.cpp b/wpilibcIntegrationTests/src/AnalogPotentiometerTest.cpp
new file mode 100644
index 0000000..16a733b
--- /dev/null
+++ b/wpilibcIntegrationTests/src/AnalogPotentiometerTest.cpp
@@ -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.";
+}
diff --git a/wpilibcIntegrationTests/src/BuiltInAccelerometerTest.cpp b/wpilibcIntegrationTests/src/BuiltInAccelerometerTest.cpp
new file mode 100644
index 0000000..abcac1b
--- /dev/null
+++ b/wpilibcIntegrationTests/src/BuiltInAccelerometerTest.cpp
@@ -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);
+}
diff --git a/wpilibcIntegrationTests/src/CircularBufferTest.cpp b/wpilibcIntegrationTests/src/CircularBufferTest.cpp
new file mode 100644
index 0000000..24b00a9
--- /dev/null
+++ b/wpilibcIntegrationTests/src/CircularBufferTest.cpp
@@ -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]);
+}
diff --git a/wpilibcIntegrationTests/src/ConditionVariableTest.cpp b/wpilibcIntegrationTests/src/ConditionVariableTest.cpp
new file mode 100644
index 0000000..e4e12aa
--- /dev/null
+++ b/wpilibcIntegrationTests/src/ConditionVariableTest.cpp
@@ -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<priority_mutex> priority_lock;
+
+ // Condition variable to test.
+ priority_condition_variable m_cond;
+
+ // Mutex to pass to condition variable when waiting.
+ 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
diff --git a/wpilibcIntegrationTests/src/CounterTest.cpp b/wpilibcIntegrationTests/src/CounterTest.cpp
new file mode 100644
index 0000000..71b94bf
--- /dev/null
+++ b/wpilibcIntegrationTests/src/CounterTest.cpp
@@ -0,0 +1,152 @@
+/*----------------------------------------------------------------------------*/
+/* 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.";
+}
diff --git a/wpilibcIntegrationTests/src/DIOLoopTest.cpp b/wpilibcIntegrationTests/src/DIOLoopTest.cpp
new file mode 100644
index 0000000..2678a52
--- /dev/null
+++ b/wpilibcIntegrationTests/src/DIOLoopTest.cpp
@@ -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, ¶m);
+ 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);
+}
diff --git a/wpilibcIntegrationTests/src/DigitalGlitchFilterTest.cpp b/wpilibcIntegrationTests/src/DigitalGlitchFilterTest.cpp
new file mode 100644
index 0000000..701aa55
--- /dev/null
+++ b/wpilibcIntegrationTests/src/DigitalGlitchFilterTest.cpp
@@ -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);
+}
diff --git a/wpilibcIntegrationTests/src/DriverStationTest.cpp b/wpilibcIntegrationTests/src/DriverStationTest.cpp
new file mode 100644
index 0000000..7cea63e
--- /dev/null
+++ b/wpilibcIntegrationTests/src/DriverStationTest.cpp
@@ -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);
+}
diff --git a/wpilibcIntegrationTests/src/FakeEncoderTest.cpp b/wpilibcIntegrationTests/src/FakeEncoderTest.cpp
new file mode 100644
index 0000000..89610a9
--- /dev/null
+++ b/wpilibcIntegrationTests/src/FakeEncoderTest.cpp
@@ -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());
+}
diff --git a/wpilibcIntegrationTests/src/FilterNoiseTest.cpp b/wpilibcIntegrationTests/src/FilterNoiseTest.cpp
new file mode 100644
index 0000000..f99c544
--- /dev/null
+++ b/wpilibcIntegrationTests/src/FilterNoiseTest.cpp
@@ -0,0 +1,131 @@
+/*----------------------------------------------------------------------------*/
+/* 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;
+}
+
+using std::chrono::system_clock;
+
+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));
diff --git a/wpilibcIntegrationTests/src/FilterOutputTest.cpp b/wpilibcIntegrationTests/src/FilterOutputTest.cpp
new file mode 100644
index 0000000..e863518
--- /dev/null
+++ b/wpilibcIntegrationTests/src/FilterOutputTest.cpp
@@ -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));
diff --git a/wpilibcIntegrationTests/src/MotorEncoderTest.cpp b/wpilibcIntegrationTests/src/MotorEncoderTest.cpp
new file mode 100644
index 0000000..cdb9334
--- /dev/null
+++ b/wpilibcIntegrationTests/src/MotorEncoderTest.cpp
@@ -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));
diff --git a/wpilibcIntegrationTests/src/MotorInvertingTest.cpp b/wpilibcIntegrationTests/src/MotorInvertingTest.cpp
new file mode 100644
index 0000000..c7015c8
--- /dev/null
+++ b/wpilibcIntegrationTests/src/MotorInvertingTest.cpp
@@ -0,0 +1,126 @@
+/*----------------------------------------------------------------------------*/
+/* 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));
diff --git a/wpilibcIntegrationTests/src/MutexTest.cpp b/wpilibcIntegrationTests/src/MutexTest.cpp
new file mode 100644
index 0000000..8658388
--- /dev/null
+++ b/wpilibcIntegrationTests/src/MutexTest.cpp
@@ -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<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<priority_mutex> lock(m_mutex);
+ m_set = true;
+ m_condition.notify_all();
+ }
+
+ private:
+ // priority_mutex used for the notification and to protect the bit.
+ 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, ¶m), 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:
+ // 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<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) {
+ 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<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) {
+ priority_recursive_mutex m;
+ m.lock();
+ EXPECT_TRUE(m.try_lock());
+ m.unlock();
+ EXPECT_TRUE(m.try_lock());
+}
+
+} // namespace testing
+} // namespace wpilib
diff --git a/wpilibcIntegrationTests/src/NotifierTest.cpp b/wpilibcIntegrationTests/src/NotifierTest.cpp
new file mode 100644
index 0000000..448ebb6
--- /dev/null
+++ b/wpilibcIntegrationTests/src/NotifierTest.cpp
@@ -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 "Notifier.h" // NOLINT(build/include_order)
+
+#include "TestBench.h"
+#include "Timer.h"
+#include "gtest/gtest.h"
+
+using namespace frc;
+
+unsigned notifierCounter;
+
+void notifierHandler(void*) { notifierCounter++; }
+
+/**
+ * Test if the Wait function works
+ */
+TEST(NotifierTest, DISABLED_TestTimerNotifications) {
+ std::cout << "NotifierTest..." << std::endl;
+ notifierCounter = 0;
+ std::cout << "notifier(notifierHandler, nullptr)..." << std::endl;
+ Notifier notifier(notifierHandler, nullptr);
+ std::cout << "Start Periodic..." << std::endl;
+ notifier.StartPeriodic(1.0);
+
+ std::cout << "Wait..." << std::endl;
+ Wait(10.5);
+ std::cout << "...Wait" << std::endl;
+
+ EXPECT_EQ(10u, notifierCounter) << "Received " << notifierCounter
+ << " notifications in 10.5 seconds";
+ std::cout << "Received " << notifierCounter
+ << " notifications in 10.5 seconds";
+
+ std::cout << "...NotifierTest" << std::endl;
+}
diff --git a/wpilibcIntegrationTests/src/PCMTest.cpp b/wpilibcIntegrationTests/src/PCMTest.cpp
new file mode 100644
index 0000000..1423e6e
--- /dev/null
+++ b/wpilibcIntegrationTests/src/PCMTest.cpp
@@ -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";
+}
diff --git a/wpilibcIntegrationTests/src/PIDToleranceTest.cpp b/wpilibcIntegrationTests/src/PIDToleranceTest.cpp
new file mode 100644
index 0000000..cf3849b
--- /dev/null
+++ b/wpilibcIntegrationTests/src/PIDToleranceTest.cpp
@@ -0,0 +1,85 @@
+/*----------------------------------------------------------------------------*/
+/* 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();
+}
diff --git a/wpilibcIntegrationTests/src/PowerDistributionPanelTest.cpp b/wpilibcIntegrationTests/src/PowerDistributionPanelTest.cpp
new file mode 100644
index 0000000..8bedcf5
--- /dev/null
+++ b/wpilibcIntegrationTests/src/PowerDistributionPanelTest.cpp
@@ -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";
+}
diff --git a/wpilibcIntegrationTests/src/PreferencesTest.cpp b/wpilibcIntegrationTests/src/PreferencesTest.cpp
new file mode 100644
index 0000000..60262fb
--- /dev/null
+++ b/wpilibcIntegrationTests/src/PreferencesTest.cpp
@@ -0,0 +1,103 @@
+/*----------------------------------------------------------------------------*/
+/* 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);
+ preferences->Save();
+
+ 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";
+ }
+}
diff --git a/wpilibcIntegrationTests/src/RelayTest.cpp b/wpilibcIntegrationTests/src/RelayTest.cpp
new file mode 100644
index 0000000..81a7c14
--- /dev/null
+++ b/wpilibcIntegrationTests/src/RelayTest.cpp
@@ -0,0 +1,68 @@
+/*----------------------------------------------------------------------------*/
+/* 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";
+
+ // 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";
+
+ // 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";
+
+ // 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";
+}
diff --git a/wpilibcIntegrationTests/src/TestEnvironment.cpp b/wpilibcIntegrationTests/src/TestEnvironment.cpp
new file mode 100644
index 0000000..a59f0a8
--- /dev/null
+++ b/wpilibcIntegrationTests/src/TestEnvironment.cpp
@@ -0,0 +1,51 @@
+/*----------------------------------------------------------------------------*/
+/* 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"
+
+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(0)) {
+ std::cerr << "FATAL ERROR: HAL could not be initialized" << std::endl;
+ 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);
+
+ std::cout << "Waiting for enable" << std::endl;
+
+ while (!DriverStation::GetInstance().IsEnabled()) {
+ Wait(0.1);
+ }
+ }
+
+ void TearDown() override {}
+};
+
+testing::Environment* const environment =
+ testing::AddGlobalTestEnvironment(new TestEnvironment);
diff --git a/wpilibcIntegrationTests/src/TiltPanCameraTest.cpp b/wpilibcIntegrationTests/src/TiltPanCameraTest.cpp
new file mode 100644
index 0000000..0761205
--- /dev/null
+++ b/wpilibcIntegrationTests/src/TiltPanCameraTest.cpp
@@ -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);
+}
diff --git a/wpilibcIntegrationTests/src/TimerTest.cpp b/wpilibcIntegrationTests/src/TimerTest.cpp
new file mode 100644
index 0000000..a328a06
--- /dev/null
+++ b/wpilibcIntegrationTests/src/TimerTest.cpp
@@ -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);
+}
diff --git a/wpilibcIntegrationTests/src/VisionTest.cpp b/wpilibcIntegrationTests/src/VisionTest.cpp
new file mode 100644
index 0000000..d9ba821
--- /dev/null
+++ b/wpilibcIntegrationTests/src/VisionTest.cpp
@@ -0,0 +1,27 @@
+/*----------------------------------------------------------------------------*/
+/* 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 "vision/VisionRunner.h"
+
+using namespace frc;
+
+class VisionTester : public VisionPipeline {
+ public:
+ virtual ~VisionTester() = default;
+ void Process(cv::Mat& mat) override {}
+ void TestThing() {}
+};
+
+void TestVisionInitialization() {
+ cs::CvSource source;
+ VisionTester tester;
+ VisionRunner<VisionTester> runner(source, &tester,
+ [](VisionTester& t) { t.TestThing(); });
+
+ runner.RunOnce();
+ runner.RunForever();
+}
diff --git a/wpilibcIntegrationTests/src/command/CommandTest.cpp b/wpilibcIntegrationTests/src/command/CommandTest.cpp
new file mode 100644
index 0000000..351f3ba
--- /dev/null
+++ b/wpilibcIntegrationTests/src/command/CommandTest.cpp
@@ -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();
+}
diff --git a/wpilibcIntegrationTests/src/command/ConditionalCommandTest.cpp b/wpilibcIntegrationTests/src/command/ConditionalCommandTest.cpp
new file mode 100644
index 0000000..271131d
--- /dev/null
+++ b/wpilibcIntegrationTests/src/command/ConditionalCommandTest.cpp
@@ -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();
+}
diff --git a/wpilibcIntegrationTests/src/command/MockCommand.cpp b/wpilibcIntegrationTests/src/command/MockCommand.cpp
new file mode 100644
index 0000000..19c0c64
--- /dev/null
+++ b/wpilibcIntegrationTests/src/command/MockCommand.cpp
@@ -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; }
diff --git a/wpilibcIntegrationTests/src/command/MockConditionalCommand.cpp b/wpilibcIntegrationTests/src/command/MockConditionalCommand.cpp
new file mode 100644
index 0000000..b23b815
--- /dev/null
+++ b/wpilibcIntegrationTests/src/command/MockConditionalCommand.cpp
@@ -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; }