1 // Copyright 2025 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_span/cast.h"
16
17 #include <array>
18 #include <cstdint>
19
20 #include "pw_span/span.h"
21 #include "pw_unit_test/framework.h"
22
23 // TODO: https://pwbug.dev/395945006 - DRY these macros with
24 // pw_numeric/checked_arithmetic_test.cc
25
26 // Emits a TEST() for a given templated function with the given type.
27 //
28 // suite - The name of the test suite (the first argument to TEST()).
29 // test_name - The full name of the test.
30 // func - The templated function called by the test.
31 // type - The type passed to the function template.
32 #define TEST_FUNC_FOR_TYPE(suite, test_name, func, type) \
33 TEST(suite, test_name) { func<type>(); }
34
35 // Emits a TEST() for a templated function named by the concatention of the
36 // test suite and test name, invoked with the given type.
37 //
38 // suite - The name of the test suite (the first argument to TEST())
39 // name - The base name of the test.
40 // The full name of the test includes _suffix.
41 // The called function is the concatenation of suite and name.
42 // suffix - Appened to name (with an underscore) to generate the test name.
43 // type - The template type of the called function.
44 #define TEST_FOR_TYPE(suite, name, suffix, type) \
45 TEST_FUNC_FOR_TYPE(suite, name##_##suffix, suite##name, type)
46
47 // Emits a TEST_FOR_TYPE() for all common <cstdint> types.
48 #define TEST_FOR_STDINT_TYPES(suite, name) \
49 TEST_FOR_TYPE(suite, name, u8, uint8_t) \
50 TEST_FOR_TYPE(suite, name, i8, int8_t) \
51 TEST_FOR_TYPE(suite, name, u16, uint16_t) \
52 TEST_FOR_TYPE(suite, name, i16, int16_t) \
53 TEST_FOR_TYPE(suite, name, u32, uint32_t) \
54 TEST_FOR_TYPE(suite, name, i32, int32_t) \
55 TEST_FOR_TYPE(suite, name, u64, uint64_t) \
56 TEST_FOR_TYPE(suite, name, i64, int64_t)
57
58 #define STATIC_ASSERT_EXTENT(spanvar, n) \
59 static_assert(decltype(spanvar)::extent == n)
60
61 namespace {
62
63 template <class T>
SpanCastRoundTrip()64 void SpanCastRoundTrip() {
65 constexpr size_t kNum = 4;
66 std::array<T, kNum> t_array;
67
68 pw::span<T> t_span = pw::span(t_array);
69
70 pw::ByteSpan bytes = pw::as_writable_bytes(t_span);
71 EXPECT_EQ(bytes.size(), t_span.size_bytes());
72 // The expectation above is actually outside the scope of this test,
73 // but we do it to ensure proper setup for the test below.
74 // Now verify the UUT span_cast<T>.
75
76 pw::span<T> t_span2 = pw::span_cast<T>(bytes);
77
78 EXPECT_EQ(t_span.data(), t_span2.data());
79 EXPECT_EQ(t_span.size(), t_span2.size());
80 }
81
82 template <class T>
SpanCastRoundTripConst()83 void SpanCastRoundTripConst() {
84 constexpr size_t kNum = 4;
85 std::array<T, kNum> t_array;
86
87 pw::span<T> t_span = pw::span(t_array);
88
89 pw::ConstByteSpan bytes = pw::as_bytes(t_span);
90 EXPECT_EQ(bytes.size(), t_span.size_bytes());
91 // The expectation above is actually outside the scope of this test,
92 // but we do it to ensure proper setup for the test below.
93 // Now verify the UUT span_cast<T>.
94
95 // N.B. We use span_cast<T> but the result is span<const T> due to the const
96 // type of the span argument.
97 pw::span<const T> t_span2 = pw::span_cast<T>(bytes);
98
99 // ...but it's okay if you want to include const in the template arg.
100 [[maybe_unused]] pw::span<const T> t_span3 = pw::span_cast<const T>(bytes);
101
102 EXPECT_EQ(t_span.data(), t_span2.data());
103 EXPECT_EQ(t_span.size(), t_span2.size());
104 }
105
106 template <class T>
SpanCastRoundTripStaticExtent()107 void SpanCastRoundTripStaticExtent() {
108 constexpr size_t kNumElem = 4;
109 constexpr size_t kNumBytes = kNumElem * sizeof(T);
110
111 std::array<T, kNumElem> t_array;
112
113 // N.B. We use `auto` variables throughout this test, rather than pw::span<T>
114 // or pw::ByteSpan, which are always of a dynamic extent!
115
116 auto t_span = pw::span(t_array);
117 STATIC_ASSERT_EXTENT(t_span, kNumElem); // Ensure static extent
118
119 auto byte_span = pw::as_writable_bytes(t_span);
120 STATIC_ASSERT_EXTENT(byte_span, kNumBytes); // Ensure static extent
121
122 // Everything above is actually out of scope for this test...
123 // Now verify the UUT span_cast<T>...
124
125 auto t_span2 = pw::span_cast<T>(byte_span);
126 STATIC_ASSERT_EXTENT(t_span2, kNumElem); // Ensure proper static extent
127 }
128
129 template <class T>
SpanCastRoundTripStaticExtentConst()130 void SpanCastRoundTripStaticExtentConst() {
131 constexpr size_t kNumElem = 4;
132 constexpr size_t kNumBytes = kNumElem * sizeof(T);
133
134 std::array<T, kNumElem> t_array;
135
136 // N.B. We use `auto` variables throughout this test, rather than pw::span<T>
137 // or pw::ConstByteSpan, which are always of a dynamic extent!
138
139 auto t_span = pw::span(t_array);
140 STATIC_ASSERT_EXTENT(t_span, kNumElem); // Ensure static extent
141
142 auto byte_span = pw::as_bytes(t_span);
143 STATIC_ASSERT_EXTENT(byte_span, kNumBytes); // Ensure static extent
144
145 // The static_asert_extent checks above are actually out of scope for this
146 // test, but we check them to ensure the proper setup for the test below.
147 // Now verify the UUT span_cast<T>.
148
149 auto t_span2 = pw::span_cast<const T>(byte_span);
150 STATIC_ASSERT_EXTENT(t_span2, kNumElem); // Ensure proper static extent
151 }
152
153 struct MixedBag {
154 uint8_t u8;
155 uint16_t u16;
156 uint32_t u32;
157 uint64_t u64;
158 float f;
159 double d;
160 };
161
162 #define TEST_FOR_ALL_TYPES(suite, name) \
163 TEST_FOR_TYPE(suite, name, u8, uint8_t) \
164 TEST_FOR_TYPE(suite, name, i8, int8_t) \
165 TEST_FOR_TYPE(suite, name, char, char) \
166 TEST_FOR_TYPE(suite, name, uchar, unsigned char)
167 // TEST_FOR_STDINT_TYPES(suite, name)
168 // TEST_FOR_TYPE(suite, name, MixedBag, MixedBag)
169
170 TEST_FOR_ALL_TYPES(SpanCast, RoundTrip)
171 TEST_FOR_ALL_TYPES(SpanCast, RoundTripConst)
172 TEST_FOR_ALL_TYPES(SpanCast, RoundTripStaticExtent)
173 TEST_FOR_ALL_TYPES(SpanCast, RoundTripStaticExtentConst)
174
175 } // namespace
176
177 // An example test for the docs, which includes the #includes.
178 // DOCSTAG[start-pw_span-cast-example]
179 #include "pw_bytes/span.h"
180 #include "pw_span/cast.h"
181
182 void SDK_ReadData(uint8_t* data, size_t size);
183 void SDK_WriteData(const uint8_t* data, size_t size);
184
Write(pw::ConstByteSpan buffer)185 void Write(pw::ConstByteSpan buffer) {
186 auto data = pw::span_cast<const uint8_t>(buffer);
187 SDK_WriteData(data.data(), data.size());
188 }
189
Read(pw::ByteSpan buffer)190 void Read(pw::ByteSpan buffer) {
191 auto data = pw::span_cast<uint8_t>(buffer);
192 SDK_ReadData(data.data(), data.size());
193 }
194 // DOCSTAG[end-pw_span-cast-example]
195
TEST(SpanCast,Examples)196 TEST(SpanCast, Examples) {
197 std::array<std::byte, 4> data{};
198 Read(data);
199 Write(data);
200 }
201
SDK_ReadData(uint8_t *,size_t)202 void SDK_ReadData(uint8_t*, size_t) {}
SDK_WriteData(const uint8_t *,size_t)203 void SDK_WriteData(const uint8_t*, size_t) {}
204