#include #include #include #include "bits.hpp" // ============================================================================= // Catch2 // ============================================================================= // // 1. TEST_CASE(name, tags) // The basic unit of testing in Catch2. Each TEST_CASE is an independent test // function. The first argument is a descriptive name (must be unique), and // the second is a string of tags in square brackets (e.g. "[set_bits]") // used to filter and group tests when running. // // 2. SECTION(name) // Sections allow multiple subtests within a single TEST_CASE. Each SECTION // runs the TEST_CASE from the top, so any setup code before the SECTION is // re-executed fresh for every section. This gives each section an isolated // starting state without needing separate TEST_CASEs or explicit teardown. // Sections can also be nested. // // 3. REQUIRE(expression) // The primary assertion macro. If the expression evaluates to false, the // test fails immediately and Catch2 reports the actual values of both sides // of the comparison (e.g. "0xF5 == 0xF0" on failure). There is also // CHECK(), which records a failure but continues executing the rest of the // test; REQUIRE() aborts the current test on failure. // // 4. TEMPLATE_TEST_CASE(name, tags, Type1, Type2, ...) // A parameterised test that is instantiated once for each type listed. // Inside the test body, the alias `TestType` refers to the current type. // This avoids duplicating identical logic for u8, u16, u32, // and u64. Catch2 automatically appends the type name to the test name // in the output so you can see which instantiation failed. // // 5. Tags (e.g. "[create_mask]", "[round-trip]") // Tags let you selectively run subsets of tests from the command line. // For example: // ./tests "[set_bits]" -- runs only tests tagged [set_bits] // ./tests "~[round-trip]" -- runs everything except [round-trip] // ./tests "[get_bits],[set_bits]" -- runs tests matching either tag // // ============================================================================= // --------------------------------------------------------------------------- // create_mask // --------------------------------------------------------------------------- TEMPLATE_TEST_CASE("create_mask produces correct masks", "[create_mask]", u8, u16, u32, u64) { SECTION("single bit mask at bit 0") { auto m = create_mask(0, 0); REQUIRE(m == TestType{0b1}); } SECTION("single bit mask at bit 3") { auto m = create_mask(3, 3); REQUIRE(m == TestType{0b1000}); } SECTION("mask spanning bits 0..7 gives 0xFF") { auto m = create_mask(0, 7); REQUIRE(m == TestType{0xFF}); } SECTION("mask spanning bits 4..7") { auto m = create_mask(4, 7); REQUIRE(m == TestType{0xF0}); } SECTION("full-width mask returns all ones") { constexpr u8 last = sizeof(TestType) * 8 - 1; auto m = create_mask(0, last); REQUIRE(m == static_cast(~TestType{0})); } } TEST_CASE("create_mask 32-bit specific cases", "[create_mask]") { REQUIRE(create_mask(0, 15) == 0x0000FFFF); REQUIRE(create_mask(0, 31) == 0xFFFFFFFF); REQUIRE(create_mask(16, 31) == 0xFFFF0000); } // --------------------------------------------------------------------------- // clear_bits // --------------------------------------------------------------------------- TEMPLATE_TEST_CASE("clear_bits zeroes the specified range", "[clear_bits]", u8, u16, u32, u64) { SECTION("clear all bits") { TestType val = static_cast(~TestType{0}); constexpr u8 last = sizeof(TestType) * 8 - 1; clear_bits(val, 0, last); REQUIRE(val == TestType{0}); } SECTION("clear lower nibble") { TestType val = static_cast(0xFF); clear_bits(val, 0, 3); REQUIRE(val == static_cast(0xF0)); } SECTION("clear upper nibble") { TestType val = static_cast(0xFF); clear_bits(val, 4, 7); REQUIRE(val == static_cast(0x0F)); } SECTION("clear single bit") { TestType val = static_cast(0xFF); clear_bits(val, 0, 0); REQUIRE(val == static_cast(0xFE)); } SECTION("clearing already-zero bits is a no-op") { TestType val = TestType{0}; clear_bits(val, 0, 3); REQUIRE(val == TestType{0}); } } // --------------------------------------------------------------------------- // set_bits // --------------------------------------------------------------------------- TEMPLATE_TEST_CASE("set_bits writes value into the specified range", "[set_bits]", u8, u16, u32, u64) { SECTION("set lower nibble on zero") { TestType val = TestType{0}; set_bits(val, u8{0}, u8{3}, static_cast(0xA)); REQUIRE(val == static_cast(0x0A)); } SECTION("set upper nibble on zero") { TestType val = TestType{0}; set_bits(val, u8{4}, u8{7}, static_cast(0xB)); REQUIRE(val == static_cast(0xB0)); } SECTION("set_bits replaces existing bits") { TestType val = static_cast(0xFF); set_bits(val, u8{0}, u8{3}, static_cast(0x5)); REQUIRE(val == static_cast(0xF5)); } SECTION("set single bit to 1") { TestType val = TestType{0}; set_bits(val, u8{3}, u8{3}, static_cast(1)); REQUIRE(val == static_cast(0x08)); } SECTION("set single bit to 0") { TestType val = static_cast(0xFF); set_bits(val, u8{3}, u8{3}, static_cast(0)); REQUIRE(val == static_cast(0xF7)); } SECTION("setting value 0 clears the range") { TestType val = static_cast(0xFF); set_bits(val, u8{0}, u8{7}, static_cast(0)); REQUIRE(val == TestType{0}); } } TEST_CASE("set_bits with different value type (U != T)", "[set_bits]") { u32 val = 0; constexpr u8 small_val = 0x3F; set_bits(val, u8{8}, u8{13}, small_val); REQUIRE(val == (u32{0x3F} << 8)); } TEST_CASE("set_bits preserves surrounding bits in 32-bit", "[set_bits]") { u32 val = 0xDEADBEEF; set_bits(val, u8{8}, u8{15}, u32{0x42}); REQUIRE(val == 0xDEAD42EF); } // --------------------------------------------------------------------------- // get_bits // --------------------------------------------------------------------------- TEMPLATE_TEST_CASE("get_bits extracts the specified range", "[get_bits]", u8, u16, u32, u64) { SECTION("get lower nibble") { TestType val = static_cast(0xAB); auto result = get_bits(val, u8{0}, u8{3}); REQUIRE(result == TestType{0xB}); } SECTION("get upper nibble") { TestType val = static_cast(0xAB); auto result = get_bits(val, u8{4}, u8{7}); REQUIRE(result == TestType{0xA}); } SECTION("get single bit that is set") { TestType val = static_cast(0x08); auto result = get_bits(val, u8{3}, u8{3}); REQUIRE(result == TestType{1}); } SECTION("get single bit that is clear") { TestType val = static_cast(0xF7); auto result = get_bits(val, u8{3}, u8{3}); REQUIRE(result == TestType{0}); } SECTION("get all bits") { TestType val = static_cast(~TestType{0}); constexpr u8 last = sizeof(TestType) * 8 - 1; auto result = get_bits(val, u8{0}, last); REQUIRE(result == val); } SECTION("get from zero returns zero") { TestType val = TestType{0}; auto result = get_bits(val, u8{0}, u8{7}); REQUIRE(result == TestType{0}); } } TEST_CASE("get_bits 32-bit specific extractions", "[get_bits]") { constexpr u32 val = 0xDEADBEEF; REQUIRE(get_bits(val, u8{0}, u8{7}) == 0xEF); REQUIRE(get_bits(val, u8{8}, u8{15}) == 0xBE); REQUIRE(get_bits(val, u8{16}, u8{23}) == 0xAD); REQUIRE(get_bits(val, u8{24}, u8{31}) == 0xDE); } // --------------------------------------------------------------------------- // Round-trip: set then get // --------------------------------------------------------------------------- TEST_CASE("set_bits then get_bits round-trips correctly", "[round-trip]") { u32 reg = 0; set_bits(reg, u8{4}, u8{11}, u32{0xAB}); REQUIRE(get_bits(reg, u8{4}, u8{11}) == 0xAB); REQUIRE(get_bits(reg, u8{0}, u8{3}) == 0x0); REQUIRE(get_bits(reg, u8{12}, u8{31}) == 0x0); } TEST_CASE("multiple set_bits on different ranges", "[round-trip]") { u32 reg = 0; set_bits(reg, u8{0}, u8{7}, u32{0x01}); set_bits(reg, u8{8}, u8{15}, u32{0x02}); set_bits(reg, u8{16}, u8{23}, u32{0x03}); set_bits(reg, u8{24}, u8{31}, u32{0x04}); REQUIRE(reg == 0x04030201); } TEST_CASE("64-bit round-trip", "[round-trip]") { u64 reg = 0; set_bits(reg, u8{32}, u8{63}, u64{0xCAFEBABE}); REQUIRE(get_bits(reg, u8{32}, u8{63}) == u64{0xCAFEBABE}); REQUIRE(get_bits(reg, u8{0}, u8{31}) == u64{0}); }