diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b55de633..616e2c87 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -339,12 +339,14 @@ if(WITH_TESTS) ../tests/util/MathTest.cpp ../tests/util/TimeTest.cpp ../tests/util/SchedulerTest.cpp + ../tests/util/RandomTest.cpp + ../tests/util/JsonTest.cpp + ../tests/util/ThreadingTest.cpp # Protocol/teleop tests ../tests/kinematics/DiffWristKinematicsTest.cpp) target_link_libraries(tests ${rover_libs} - stub_world_interface ${OpenCV_LIBS}) include(CTest) include(Catch) diff --git a/tests/util/CoreTest.cpp b/tests/util/CoreTest.cpp index 2839ed99..db9f4ed9 100644 --- a/tests/util/CoreTest.cpp +++ b/tests/util/CoreTest.cpp @@ -4,6 +4,84 @@ using namespace util; +TEST_CASE("Test almostEqual", "[util][core]") { + SECTION("Numbers are approximately equal") { + REQUIRE(almostEqual(1.000000001, 1.000000002)); + REQUIRE(almostEqual(1.0, 1.0, 1e-5)); + } + + SECTION("Numbers are not approximately equal") { + REQUIRE_FALSE(almostEqual(1.0, 1.1)); + REQUIRE_FALSE(almostEqual(1.0, 1.0 + 1e-5, 1e-6)); + } +} + +TEST_CASE("Test Key Set", "[util][core]") { + SECTION("Extracting keys from a given map") { + std::unordered_map inputtedMap = { + {"key1", 1}, {"key2", 2}, {"key3", 3}}; + + auto keys = keySet(inputtedMap); + + std::unordered_set expectedSet = {"key1", "key2", "key3"}; + + REQUIRE(keys == expectedSet); + } + + SECTION("Empty map results in empty set") { + std::unordered_map emptyMap; + auto keys = keySet(emptyMap); + REQUIRE(keys.empty()); + } +} + +TEST_CASE("Test To String", "[util][core]") { + SECTION("Convert integers to strings") { + REQUIRE(to_string(69) == "69"); + REQUIRE(to_string(-1) == "-1"); + } + + SECTION("Convert floats to strings") { + REQUIRE(to_string(3.14).substr(0, 4) == "3.14"); + REQUIRE(to_string(1e-6) == "0.000001"); + } + + SECTION("Convert booleans to strings") { + REQUIRE(to_string(true) == "true"); + REQUIRE(to_string(false) == "false"); + } +} + +TEST_CASE("Test Freeze String", "[util][core]") { + SECTION("Converting std::string to frozen::string") { + std::string stdString = "Test"; + auto frozenString = freezeStr(stdString); + REQUIRE(frozenString == frozen::string("Test")); + } + + SECTION("Converting empty std::string results in empty frozen::string") { + std::string stdString; + auto frozenString = freezeStr(stdString); + REQUIRE(frozenString == frozen::string("")); + } +} + +TEST_CASE("Test Pair To Tuple", "[util][core]") { + SECTION("Converting pair of integers to tuple") { + std::pair pair = {0, 1}; + auto tuple = pairToTuple(pair); + REQUIRE(std::get<0>(tuple) == 0); + REQUIRE(std::get<1>(tuple) == 1); + } + + SECTION("Converting mixed pair of string and integer to tuple") { + std::pair pair = {"key0", 0}; + auto tuple = pairToTuple(pair); + REQUIRE(std::get<0>(tuple) == "key0"); + REQUIRE(std::get<1>(tuple) == 0); + } +} + TEST_CASE("Test RAIIHelper", "[util][core]") { SECTION("Test RAIIHelper executes") { bool called = false; diff --git a/tests/util/JsonTest.cpp b/tests/util/JsonTest.cpp new file mode 100644 index 00000000..400ecd89 --- /dev/null +++ b/tests/util/JsonTest.cpp @@ -0,0 +1,65 @@ +#include "../../src/utils/json.h" + +#include + +using namespace util; + +TEST_CASE("Test Has Key", "[util][json]") { + json j = {{"name", "huskyRobos"}, {"location", "University of Washington"}}; + + REQUIRE(hasKey(j, "name")); // has key of 'name' + REQUIRE_FALSE(hasKey(j, "numLosses")); // doesn't have key of 'numLosses' +} + +TEST_CASE("Test Validate Key (Given Individual Types)", "[util][json]") { + json j = {{"name", "huskyRobos"}, {"age", 11}, {"cool", true}}; + REQUIRE(validateKey(j, "name", val_t::string)); // has key of 'name' that is a string + REQUIRE_FALSE( + validateKey(j, "age", val_t::string)); // doesn't have key of 'age' that is a string + REQUIRE(validateKey(j, "cool", val_t::boolean)); // has key of 'cool' that is a boolean +} + +TEST_CASE("Test Validate Key (Given Set of Types)", "[util][json]") { + json j = {{"name", "huskyRobos"}, {"age", 11}, {"cool", true}}; + + std::unordered_set keyTypesAllowed = {val_t::string, val_t::number_integer}; + + REQUIRE(validateKey( + j, "name", keyTypesAllowed)); // has key of 'name' that is a type of the allowed types + REQUIRE(validateKey( + j, "age", keyTypesAllowed)); // has key of 'age' that is a type of the allowed types + REQUIRE_FALSE(validateKey( + j, "cool", + keyTypesAllowed)); // doesn't have key of 'cool' that is a type of the allowed types +} + +TEST_CASE("Test Validate One Of", "[util][json]") { + json j = {{"status", "active"}, {"subTeams", "software"}}; + + std::unordered_set statusValueTypesAllowed = {"active", "inactive"}; + std::unordered_set subTeamValueTypesAllowed = {"software", "electrical", + "mechanical"}; + + REQUIRE(validateOneOf(j, "status", + statusValueTypesAllowed)); // value of 'status' is a string in set of + // statusValueTypesAllowed + REQUIRE_FALSE(validateOneOf( + j, "status", {"pending", "closed"})); // value of 'status' is a string but not in set + // of statusValueTypesAllowed + REQUIRE(validateOneOf(j, "subTeams", + subTeamValueTypesAllowed)); // value of 'subTeams' is a string in set + // of subTeamValueTypesAllowed +} + +TEST_CASE("Test Validate Range", "[util][json]") { + json j = { + {"winLossRatio", 0.8375}, + {"avgGPA", 3.6}, + }; + + REQUIRE(validateRange(j, "winLossRatio", 0.0, + 1.0)); // 'winLossRatio' is in range from 0.0 to 1.0 + REQUIRE(validateRange(j, "avgGPA", 0.0, 4.0)); // 'avgGPA' is in range from 0.0 to 4.0 + REQUIRE_FALSE( + validateRange(j, "avgGPA", 0.0, 2.0)); // 'avgGPA' isnt in range of 0.0 to 2.0 +} \ No newline at end of file diff --git a/tests/util/RandomTest.cpp b/tests/util/RandomTest.cpp new file mode 100644 index 00000000..78bd1504 --- /dev/null +++ b/tests/util/RandomTest.cpp @@ -0,0 +1,21 @@ +#include "../../src/utils/random.h" + +#include + +using namespace util; + +TEST_CASE("Test Standard Normal Distribution w/ different thread_ids", "[util][random]") { + double sample0 = stdn(0); + double sample1 = stdn(1); + + // samples should be different + REQUIRE(sample0 != sample1); +} + +TEST_CASE("Test Get Normal Seed", "[util][random]") { + double seed1 = getNormalSeed(); + double seed2 = getNormalSeed(); + + // seeds should be the same + REQUIRE(seed1 == seed2); +} diff --git a/tests/util/ThreadingTest.cpp b/tests/util/ThreadingTest.cpp new file mode 100644 index 00000000..274df4be --- /dev/null +++ b/tests/util/ThreadingTest.cpp @@ -0,0 +1,101 @@ +#include "../../src/utils/threading.h" + +#include +#include + +#include + +using namespace util; + +using namespace std::chrono_literals; + +TEST_CASE("Test Wait", "[util][threading]") { + SECTION("Wait until latch unlocks when countdown reaches zero") { + latch l(3); + std::thread worker([&]() { + l.count_down(); + l.count_down(); + l.count_down(); + }); + + l.wait(); + + worker.join(); + + REQUIRE(true); // if thread isn't blocked, will be reached + } +} + +TEST_CASE("Test Wait For", "[util][threading]") { + SECTION("Wait for latch unlocks within timeout") { + latch l(1); + std::thread worker([&]() { + std::this_thread::sleep_for(50ms); + l.count_down(); + }); + + REQUIRE(l.wait_for(100ms)); + + worker.join(); + } + + SECTION("Wait for latch times out when not unlocked") { + latch l(1); + + REQUIRE_FALSE(l.wait_for(50ms)); + } +} + +TEST_CASE("Test Wait Until", "[util][threading]") { + SECTION("Wait until latch unlocks before timeout") { + latch l(1); + std::thread worker([&]() { + std::this_thread::sleep_for(50ms); + l.count_down(); + }); + + auto timeout = std::chrono::steady_clock::now() + 100ms; + REQUIRE(l.wait_until(timeout)); + + worker.join(); + } + + SECTION("Wait until latch times out") { + latch l(1); + auto timeout = std::chrono::steady_clock::now() + 50ms; + REQUIRE_FALSE(l.wait_until(timeout)); + } +} + +TEST_CASE("Test Count Down", "[util][threading]") { + SECTION("Latch unlocks after enough countdowns") { + latch l(3); + + REQUIRE_FALSE(l.wait_for(10ms)); + + l.count_down(); + l.count_down(); + REQUIRE_FALSE(l.wait_for(10ms)); + + l.count_down(); + REQUIRE(l.wait_for(10ms)); + } + + SECTION("Latch remains locked if not counted down enough") { + latch l(3); + + l.count_down(); + l.count_down(); + REQUIRE_FALSE(l.wait_for(10ms)); + } + + SECTION("Latch can count down by more than 1") { + latch l(3); + + l.count_down(2); + REQUIRE_FALSE(l.wait_for(10ms)); + + l.count_down(1); + REQUIRE(l.wait_for(10ms)); + } +} \ No newline at end of file