blob: 3f0916139be89453ba6421b97204f33a617a366b [file] [log] [blame]
// 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