| // Copyright 2025 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include "pw_bytes/span.h" |
| |
| #include "pw_compilation_testing/negative_compilation.h" |
| #include "pw_unit_test/framework.h" |
| |
| // TODO: https://pwbug.dev/395945006 - DRY these macros with |
| // pw_numeric/checked_arithmetic_test.cc |
| // pw_span/cast_test.cc |
| |
| // Emits a TEST() for a given templated function with the given type. |
| // |
| // suite - The name of the test suite (the first argument to TEST()). |
| // test_name - The full name of the test. |
| // func - The templated function called by the test. |
| // type - The type passed to the function template. |
| #define TEST_FUNC_FOR_TYPE(suite, test_name, func, type) \ |
| TEST(suite, test_name) { func<type>(); } |
| |
| // Emits a TEST() for a templated function named by the concatention of the |
| // test suite and test name, invoked with the given type. |
| // |
| // suite - The name of the test suite (the first argument to TEST()) |
| // name - The base name of the test. |
| // The full name of the test includes _suffix. |
| // The called function is the concatenation of suite and name. |
| // suffix - Appened to name (with an underscore) to generate the test name. |
| // type - The template type of the called function. |
| #define TEST_FOR_TYPE(suite, name, suffix, type) \ |
| TEST_FUNC_FOR_TYPE(suite, name##_##suffix, suite##name, type) |
| |
| // Emits a TEST_FOR_TYPE() for all common <cstdint> types. |
| #define TEST_FOR_STDINT_TYPES(suite, name) \ |
| TEST_FOR_TYPE(suite, name, u8, uint8_t) \ |
| TEST_FOR_TYPE(suite, name, i8, int8_t) \ |
| TEST_FOR_TYPE(suite, name, u16, uint16_t) \ |
| TEST_FOR_TYPE(suite, name, i16, int16_t) \ |
| TEST_FOR_TYPE(suite, name, u32, uint32_t) \ |
| TEST_FOR_TYPE(suite, name, i32, int32_t) \ |
| TEST_FOR_TYPE(suite, name, u64, uint64_t) \ |
| TEST_FOR_TYPE(suite, name, i64, int64_t) |
| |
| #define TEST_FOR_ALL_INT_TYPES(suite, name) \ |
| TEST_FOR_TYPE(suite, name, char, char) \ |
| TEST_FOR_TYPE(suite, name, uchar, unsigned char) \ |
| TEST_FOR_STDINT_TYPES(suite, name) |
| |
| #define EXPECT_SEQ_EQ(seq1, seq2) \ |
| EXPECT_TRUE(std::equal(seq1.begin(), seq1.end(), seq2.begin(), seq2.end())) |
| |
| namespace { |
| |
| constexpr auto kSomeValue = 0xDEADBEEF2B84F00D; |
| |
| template <class T> |
| void ObjectAsBytesWorksInt() { |
| const T val = static_cast<T>(kSomeValue); |
| |
| // N.B. We use an `auto` variable, rather than pw::ConstByteSpan, which |
| // always has a dynamic extent. |
| auto val_bytes = pw::ObjectAsBytes(val); |
| static_assert(std::is_const_v<typename decltype(val_bytes)::element_type>); |
| static_assert(val_bytes.extent == sizeof(T)); // Ensure static extent |
| |
| std::array<std::byte, sizeof(val)> buf; |
| std::memcpy(&buf, &val, sizeof(val)); |
| |
| EXPECT_SEQ_EQ(val_bytes, buf); |
| } |
| |
| TEST_FOR_ALL_INT_TYPES(ObjectAsBytes, WorksInt) |
| |
| template <class T> |
| void ObjectAsWritableBytesWorksInt() { |
| const T src = static_cast<T>(kSomeValue); |
| pw::ConstByteSpan src_bytes = pw::ObjectAsBytes(src); |
| |
| T dst{}; |
| |
| // N.B. We use an `auto` variable, rather than pw::ByteSpan, which |
| // always has a dynamic extent. |
| auto dst_bytes = pw::ObjectAsWritableBytes(dst); |
| static_assert(!std::is_const_v<typename decltype(dst_bytes)::element_type>); |
| static_assert(dst_bytes.extent == sizeof(T)); // Ensure static extent |
| |
| std::copy(src_bytes.begin(), src_bytes.end(), dst_bytes.begin()); |
| |
| EXPECT_EQ(src, dst); |
| } |
| |
| TEST_FOR_ALL_INT_TYPES(ObjectAsWritableBytes, WorksInt) |
| |
| struct Serializable { |
| char c; |
| unsigned char uc; |
| uint8_t u8; |
| int8_t i8; |
| uint16_t u16; |
| int16_t i16; |
| uint32_t u32; |
| int32_t i32; |
| uint64_t u64; |
| int64_t i64; |
| |
| // In C++20: |
| // bool operator==(const Serializable& other) const = default; |
| }; |
| static_assert(sizeof(Serializable) == 1 + 1 + 1 + 1 + 2 + 2 + 4 + 4 + 8 + 8); |
| |
| constexpr bool operator==(const Serializable& lhs, const Serializable& rhs) { |
| // clang-format off |
| return (lhs.c == rhs.c) |
| && (lhs.uc == rhs.uc) |
| && (lhs.u8 == rhs.u8) |
| && (lhs.i8 == rhs.i8) |
| && (lhs.u16 == rhs.u16) |
| && (lhs.i16 == rhs.i16) |
| && (lhs.u32 == rhs.u32) |
| && (lhs.i32 == rhs.i32) |
| && (lhs.u64 == rhs.u64) |
| && (lhs.i64 == rhs.i64); |
| // clang-format on |
| } |
| |
| constexpr Serializable kSerializableInit = { |
| .c = 'A', |
| .uc = 'Z', |
| .u8 = 243, |
| .i8 = -17, |
| .u16 = 43512, |
| .i16 = -31337, |
| .u32 = 8675309, |
| .i32 = -2870104, |
| .u64 = 3141592653589793, |
| .i64 = -2718281828459045, |
| }; |
| |
| TEST(ObjectAsBytes, WorksWithSerializable) { |
| const Serializable val = kSerializableInit; |
| |
| std::array<std::byte, sizeof(val)> buf; |
| std::memcpy(&buf, &val, sizeof(val)); |
| |
| EXPECT_SEQ_EQ(pw::ObjectAsBytes(val), buf); |
| } |
| |
| TEST(ObjectAsWritableBytes, WorksWithSerializable) { |
| Serializable src = kSerializableInit; |
| pw::ConstByteSpan src_bytes = pw::ObjectAsBytes(src); |
| |
| Serializable dst{}; |
| pw::ByteSpan dst_bytes = pw::ObjectAsWritableBytes(dst); |
| |
| std::copy(src_bytes.begin(), src_bytes.end(), dst_bytes.begin()); |
| |
| EXPECT_EQ(src, dst); |
| } |
| |
| struct NotTriviallyCopyable { |
| uint64_t u64 = 0; |
| |
| NotTriviallyCopyable() = default; |
| |
| // The explicit copy-constructor and copy-assignment operators make this type |
| // not trivially copyable. |
| NotTriviallyCopyable(const NotTriviallyCopyable& other) { *this = other; } |
| NotTriviallyCopyable& operator=(const NotTriviallyCopyable& other) { |
| u64 = other.u64; |
| return *this; |
| } |
| }; |
| |
| static_assert(!std::is_trivially_copyable_v<NotTriviallyCopyable>); |
| |
| #if PW_NC_TEST(ObjectAsBytesFailsWithNotTriviallyCopyable) |
| PW_NC_EXPECT( |
| "cannot treat object as bytes: " |
| "copying bytes may result in an invalid object"); |
| pw::ConstByteSpan FailsWithNotTriviallyCopyable( |
| const NotTriviallyCopyable& obj) { |
| return pw::ObjectAsBytes(obj); |
| } |
| #elif PW_NC_TEST(ObjectAsWritableBytesFailsWithNotTriviallyCopyable) |
| PW_NC_EXPECT( |
| "cannot treat object as bytes: " |
| "copying bytes may result in an invalid object"); |
| pw::ByteSpan FailsWithNotTriviallyCopyable(NotTriviallyCopyable& obj) { |
| return pw::ObjectAsWritableBytes(obj); |
| } |
| #endif // PW_NC_TEST |
| |
| struct NonUniqueRepresentation { |
| // For alignment, 7 padding bytes follow this field, making this type have |
| // multiple possible represantations for the same value. |
| uint8_t u8; |
| |
| uint64_t u64; |
| }; |
| |
| static_assert( |
| !std::has_unique_object_representations_v<NonUniqueRepresentation>); |
| |
| #if PW_NC_TEST(ObjectAsBytesFailsWithNonUniqueRepresentation) |
| PW_NC_EXPECT( |
| "cannot treat object as bytes: " |
| "type includes indeterminate bytes which may leak information " |
| "or result in incorrect object hashing"); |
| pw::ConstByteSpan FailsWithNonUniqueRepresentation( |
| const NonUniqueRepresentation& obj) { |
| return pw::ObjectAsBytes(obj); |
| } |
| #elif PW_NC_TEST(ObjectAsWritableBytesFailsWithNonUniqueRepresentation) |
| PW_NC_EXPECT( |
| "cannot treat object as bytes: " |
| "type includes indeterminate bytes which may leak information " |
| "or result in incorrect object hashing"); |
| pw::ByteSpan FailsWithNonUniqueRepresentation(NonUniqueRepresentation& obj) { |
| return pw::ObjectAsWritableBytes(obj); |
| } |
| #endif // PW_NC_TEST |
| |
| } // namespace |