1 // Copyright 2019 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_string/to_string.h"
16
17 #include <array>
18 #include <cinttypes>
19 #include <cmath>
20 #include <cstring>
21 #include <string>
22
23 #include "pw_result/result.h"
24 #include "pw_span/span.h"
25 #include "pw_status/status.h"
26 #include "pw_string/internal/config.h"
27 #include "pw_string/type_to_string.h"
28 #include "pw_unit_test/framework.h"
29
30 namespace pw {
31
32 struct CustomType {
33 unsigned a;
34 unsigned b;
35
36 static constexpr const char* kToString = "This is a CustomType";
37
CustomTypepw::CustomType38 CustomType() : a(0), b(0) {}
39
40 // Non-copyable to verify that ToString doesn't copy it.
41 CustomType(const CustomType&) = delete;
42 CustomType& operator=(const CustomType&) = delete;
43 };
44
ToString(const CustomType &,span<char> buffer)45 StatusWithSize ToString(const CustomType&, span<char> buffer) {
46 int result =
47 std::snprintf(buffer.data(), buffer.size(), CustomType::kToString);
48 if (result < 0) {
49 return StatusWithSize::Unknown();
50 }
51 if (static_cast<size_t>(result) < buffer.size()) {
52 return StatusWithSize(result);
53 }
54 return StatusWithSize::ResourceExhausted(buffer.empty() ? 0u
55 : buffer.size() - 1);
56 }
57
58 namespace {
59
60 char buffer[128] = {};
61 char expected[128] = {};
62
TEST(ToString,Bool)63 TEST(ToString, Bool) {
64 const volatile bool b = true;
65 EXPECT_EQ(4u, ToString(b, buffer).size());
66 EXPECT_STREQ("true", buffer);
67 EXPECT_EQ(5u, ToString(false, buffer).size());
68 EXPECT_STREQ("false", buffer);
69 }
70
TEST(ToString,Char)71 TEST(ToString, Char) {
72 EXPECT_EQ(1u, ToString('%', buffer).size());
73 EXPECT_STREQ("%", buffer);
74 }
75
76 template <typename T>
77 constexpr T kInteger = 127;
78
TEST(ToString,Integer_AllTypesAreSupported)79 TEST(ToString, Integer_AllTypesAreSupported) {
80 EXPECT_EQ(3u, ToString(kInteger<unsigned char>, buffer).size());
81 EXPECT_STREQ("127", buffer);
82 EXPECT_EQ(3u, ToString(kInteger<signed char>, buffer).size());
83 EXPECT_STREQ("127", buffer);
84 EXPECT_EQ(3u, ToString(kInteger<unsigned short>, buffer).size());
85 EXPECT_STREQ("127", buffer);
86 EXPECT_EQ(3u, ToString(kInteger<signed short>, buffer).size());
87 EXPECT_STREQ("127", buffer);
88 EXPECT_EQ(3u, ToString(kInteger<unsigned int>, buffer).size());
89 EXPECT_STREQ("127", buffer);
90 EXPECT_EQ(3u, ToString(kInteger<signed int>, buffer).size());
91 EXPECT_STREQ("127", buffer);
92 EXPECT_EQ(3u, ToString(kInteger<unsigned long>, buffer).size());
93 EXPECT_STREQ("127", buffer);
94 EXPECT_EQ(3u, ToString(kInteger<signed long>, buffer).size());
95 EXPECT_STREQ("127", buffer);
96 EXPECT_EQ(3u, ToString(kInteger<unsigned long long>, buffer).size());
97 EXPECT_STREQ("127", buffer);
98 EXPECT_EQ(3u, ToString(kInteger<signed long long>, buffer).size());
99 EXPECT_STREQ("127", buffer);
100 }
101
TEST(ToString,ScopedEnum)102 TEST(ToString, ScopedEnum) {
103 enum class MyEnum : short { kLuckyNumber = 8 };
104
105 auto result = ToString(MyEnum::kLuckyNumber, buffer);
106 EXPECT_EQ(1u, result.size());
107 EXPECT_EQ(OkStatus(), result.status());
108 EXPECT_STREQ("8", buffer);
109 }
110
TEST(ToString,Integer_EmptyBuffer_WritesNothing)111 TEST(ToString, Integer_EmptyBuffer_WritesNothing) {
112 auto result = ToString(-1234, span(buffer, 0));
113 EXPECT_EQ(0u, result.size());
114 EXPECT_EQ(Status::ResourceExhausted(), result.status());
115 }
116
TEST(ToString,Integer_BufferTooSmall_WritesNullTerminator)117 TEST(ToString, Integer_BufferTooSmall_WritesNullTerminator) {
118 auto result = ToString(-1234, span(buffer, 5));
119 EXPECT_EQ(0u, result.size());
120 EXPECT_FALSE(result.ok());
121 EXPECT_STREQ("", buffer);
122 }
123
TEST(ToString,Float)124 TEST(ToString, Float) {
125 if (string::internal::config::kEnableDecimalFloatExpansion) {
126 EXPECT_EQ(5u, ToString(0.0f, buffer).size());
127 EXPECT_STREQ("0.000", buffer);
128 EXPECT_EQ(6u, ToString(33.444f, buffer).size());
129 EXPECT_STREQ("33.444", buffer);
130 EXPECT_EQ(3u, ToString(INFINITY, buffer).size());
131 EXPECT_STREQ("inf", buffer);
132 EXPECT_EQ(3u, ToString(NAN, buffer).size());
133 EXPECT_STREQ("nan", buffer);
134 } else {
135 EXPECT_EQ(1u, ToString(0.0f, buffer).size());
136 EXPECT_STREQ("0", buffer);
137 EXPECT_EQ(3u, ToString(INFINITY, buffer).size());
138 EXPECT_STREQ("inf", buffer);
139 EXPECT_EQ(4u, ToString(-NAN, buffer).size());
140 EXPECT_STREQ("-NaN", buffer);
141 }
142 }
143
TEST(ToString,Pointer_NonNull_WritesValue)144 TEST(ToString, Pointer_NonNull_WritesValue) {
145 CustomType custom;
146 const size_t length =
147 static_cast<size_t>(std::snprintf(expected,
148 sizeof(expected),
149 "%" PRIxPTR,
150 reinterpret_cast<intptr_t>(&custom)));
151
152 EXPECT_EQ(length, ToString(&custom, buffer).size());
153 EXPECT_STREQ(expected, buffer);
154 EXPECT_EQ(length, ToString(static_cast<void*>(&custom), buffer).size());
155 EXPECT_STREQ(expected, buffer);
156 EXPECT_EQ(1u, ToString(reinterpret_cast<int*>(4), buffer).size());
157 EXPECT_STREQ("4", buffer);
158 }
159
TEST(ToString,Pointer_Nullptr_WritesNull)160 TEST(ToString, Pointer_Nullptr_WritesNull) {
161 EXPECT_EQ(string::kNullPointerString.size(),
162 ToString(nullptr, buffer).size());
163 EXPECT_EQ(string::kNullPointerString, buffer);
164 }
165
TEST(ToString,Pointer_NullValuedPointer_WritesNull)166 TEST(ToString, Pointer_NullValuedPointer_WritesNull) {
167 EXPECT_EQ(string::kNullPointerString.size(),
168 ToString(static_cast<const CustomType*>(nullptr), buffer).size());
169 EXPECT_EQ(string::kNullPointerString, buffer);
170 }
171
TEST(ToString,Pointer_NullValuedCString_WritesNull)172 TEST(ToString, Pointer_NullValuedCString_WritesNull) {
173 EXPECT_EQ(string::kNullPointerString.size(),
174 ToString(static_cast<char*>(nullptr), buffer).size());
175 EXPECT_EQ(string::kNullPointerString, buffer);
176
177 EXPECT_EQ(string::kNullPointerString.size(),
178 ToString(static_cast<const char*>(nullptr), buffer).size());
179 EXPECT_EQ(string::kNullPointerString, buffer);
180 }
181
TEST(ToString,String_Literal)182 TEST(ToString, String_Literal) {
183 EXPECT_EQ(0u, ToString("", buffer).size());
184 EXPECT_STREQ("", buffer);
185 EXPECT_EQ(5u, ToString("hello", buffer).size());
186 EXPECT_STREQ("hello", buffer);
187 }
188
TEST(ToString,String_Pointer)189 TEST(ToString, String_Pointer) {
190 EXPECT_EQ(0u, ToString(static_cast<const char*>(""), buffer).size());
191 EXPECT_STREQ("", buffer);
192 EXPECT_EQ(5u, ToString(static_cast<const char*>("hello"), buffer).size());
193 EXPECT_STREQ("hello", buffer);
194 }
195
TEST(ToString,String_MutableBuffer)196 TEST(ToString, String_MutableBuffer) {
197 char chars[] = {'C', 'o', 'o', 'l', '\0'};
198 EXPECT_EQ(sizeof(chars) - 1, ToString(chars, buffer).size());
199 EXPECT_STREQ("Cool", buffer);
200 }
201
TEST(ToString,String_MutablePointer)202 TEST(ToString, String_MutablePointer) {
203 char chars[] = {'b', 'o', 'o', 'l', '\0'};
204 EXPECT_EQ(sizeof(chars) - 1,
205 ToString(static_cast<char*>(chars), buffer).size());
206 EXPECT_STREQ("bool", buffer);
207 }
208
TEST(ToString,Object)209 TEST(ToString, Object) {
210 CustomType custom;
211 EXPECT_EQ(std::strlen(CustomType::kToString),
212 ToString(custom, buffer).size());
213 EXPECT_STREQ(CustomType::kToString, buffer);
214 }
215
216 enum Foo : uint8_t {
217 BAR = 32,
218 BAZ = 100,
219 };
220
TEST(ToString,Enum)221 TEST(ToString, Enum) {
222 EXPECT_EQ(2u, ToString(Foo::BAR, buffer).size());
223 EXPECT_STREQ("32", buffer);
224
225 EXPECT_EQ(3u, ToString(Foo::BAZ, buffer).size());
226 EXPECT_STREQ("100", buffer);
227 }
228
TEST(ToString,Status)229 TEST(ToString, Status) {
230 EXPECT_EQ(2u, ToString(Status(), buffer).size());
231 EXPECT_STREQ(Status().str(), buffer);
232 }
233
TEST(ToString,StatusCode)234 TEST(ToString, StatusCode) {
235 EXPECT_EQ(sizeof("UNAVAILABLE") - 1,
236 ToString(Status::Unavailable(), buffer).size());
237 EXPECT_STREQ("UNAVAILABLE", buffer);
238 }
239
TEST(ToString,StdArrayAsBuffer)240 TEST(ToString, StdArrayAsBuffer) {
241 std::array<char, 128> test_buffer;
242 EXPECT_EQ(5u, ToString(false, test_buffer).size());
243 EXPECT_STREQ("false", test_buffer.data());
244 EXPECT_EQ(2u, ToString("Hi", test_buffer).size());
245 EXPECT_STREQ("Hi", test_buffer.data());
246 EXPECT_EQ(string::kNullPointerString.size(),
247 ToString(static_cast<void*>(nullptr), test_buffer).size());
248 EXPECT_EQ(string::kNullPointerString, test_buffer.data());
249 }
250
TEST(ToString,StringView)251 TEST(ToString, StringView) {
252 std::string_view view = "cool";
253 EXPECT_EQ(4u, ToString(view, buffer).size());
254 EXPECT_STREQ("cool", buffer);
255 }
256
TEST(ToString,StringView_TooSmall_Truncates)257 TEST(ToString, StringView_TooSmall_Truncates) {
258 std::string_view view = "kale!";
259 EXPECT_EQ(3u, ToString(view, span(buffer, 4)).size());
260 EXPECT_STREQ("kal", buffer);
261 }
262
TEST(ToString,StringView_EmptyBuffer_WritesNothing)263 TEST(ToString, StringView_EmptyBuffer_WritesNothing) {
264 constexpr char kOriginal[] = {'@', '#', '$', '%'};
265 char test_buffer[sizeof(kOriginal)];
266 std::memcpy(test_buffer, kOriginal, sizeof(kOriginal));
267
268 EXPECT_EQ(0u,
269 ToString(std::string_view("Hello!"), span(test_buffer, 0)).size());
270 ASSERT_EQ(0, std::memcmp(kOriginal, test_buffer, sizeof(kOriginal)));
271 }
272
TEST(ToString,StdString)273 TEST(ToString, StdString) {
274 EXPECT_EQ(5u, ToString(std::string("Whoa!"), buffer).size());
275 EXPECT_STREQ("Whoa!", buffer);
276
277 EXPECT_EQ(0u, ToString(std::string(), buffer).size());
278 EXPECT_STREQ("", buffer);
279 }
280
TEST(ToString,StdNullopt)281 TEST(ToString, StdNullopt) {
282 EXPECT_EQ(12u, ToString(std::nullopt, buffer).size());
283 EXPECT_STREQ("std::nullopt", buffer);
284 }
285
TEST(ToString,StdOptionalWithoutValue)286 TEST(ToString, StdOptionalWithoutValue) {
287 std::optional<uint16_t> v = std::nullopt;
288 EXPECT_EQ(12u, ToString(v, buffer).size());
289 EXPECT_STREQ("std::nullopt", buffer);
290 }
291
TEST(ToString,StdOptionalWithValue)292 TEST(ToString, StdOptionalWithValue) {
293 std::optional<uint16_t> v = 5;
294 EXPECT_EQ(1u, ToString(v, buffer).size());
295 EXPECT_STREQ("5", buffer);
296 }
297
TEST(ToString,ResultWithNonOkStatus)298 TEST(ToString, ResultWithNonOkStatus) {
299 Result<int> v = Status::ResourceExhausted();
300 EXPECT_EQ(18u, ToString(v, buffer).size());
301 EXPECT_STREQ("RESOURCE_EXHAUSTED", buffer);
302 }
303
TEST(ToString,ResultWithValue)304 TEST(ToString, ResultWithValue) {
305 Result<int> v = 27;
306 EXPECT_EQ(6u, ToString(v, buffer).size());
307 EXPECT_STREQ("Ok(27)", buffer);
308 }
309
TEST(ToString,EmptyArrayUsesIterableFormat)310 TEST(ToString, EmptyArrayUsesIterableFormat) {
311 std::array<int, 0> v = {};
312 EXPECT_EQ(2u, ToString(v, buffer).size());
313 EXPECT_STREQ("[]", buffer);
314 }
315
TEST(ToString,ArrayUsesIterableFormat)316 TEST(ToString, ArrayUsesIterableFormat) {
317 std::array<int, 3> v = {1, 2, 3};
318 EXPECT_EQ(9u, ToString(v, buffer).size());
319 EXPECT_STREQ("[1, 2, 3]", buffer);
320 }
321
TEST(ToString,SpanUsesIterableFormat)322 TEST(ToString, SpanUsesIterableFormat) {
323 std::array<int, 3> arr = {1, 2, 3};
324 span v(arr);
325 EXPECT_EQ(9u, ToString(v, buffer).size());
326 EXPECT_STREQ("[1, 2, 3]", buffer);
327 }
328
TEST(ToString,ArrayOfStringsUsesIterableFormat)329 TEST(ToString, ArrayOfStringsUsesIterableFormat) {
330 std::array<const char*, 3> v({"foo", "bar", "baz"});
331 EXPECT_EQ(15u, ToString(v, buffer).size());
332 EXPECT_STREQ("[foo, bar, baz]", buffer);
333 }
334
TEST(ToString,TimeVerySmallEndsInNsOrUnrepresentable)335 TEST(ToString, TimeVerySmallEndsInNsOrUnrepresentable) {
336 size_t size =
337 ToString(std::chrono::system_clock::time_point::min(), buffer).size();
338 ASSERT_GE(size, 3u);
339 if (std::strcmp("ns", &buffer[size - 2]) != 0) {
340 EXPECT_STREQ("An unrepresentably long time ago (>292 years).", buffer);
341 }
342 }
343
TEST(ToString,TimeVeryBigEndsInNsOrUnrepresentable)344 TEST(ToString, TimeVeryBigEndsInNsOrUnrepresentable) {
345 size_t size =
346 ToString(std::chrono::system_clock::time_point::max(), buffer).size();
347 ASSERT_GE(size, 3u);
348 if (std::strcmp("ns", &buffer[size - 2]) != 0) {
349 EXPECT_STREQ("An unrepresentably long time in the future (>292 years).",
350 buffer);
351 }
352 }
353
TEST(ToString,TimeJustRight)354 TEST(ToString, TimeJustRight) {
355 EXPECT_TRUE(ToString(std::chrono::system_clock::time_point(), buffer).ok());
356 EXPECT_STREQ("0ns", buffer);
357 }
358
359 int64_t sensor_clock_tick_count;
360
361 struct SensorClock {
362 using rep = int64_t;
363 using period = std::ratio<1, 32768>;
364 using duration = std::chrono::duration<rep, period>;
365 using time_point = std::chrono::time_point<SensorClock>;
nowpw::__anon3f2f52730111::SensorClock366 static time_point now() noexcept {
367 return time_point(duration(sensor_clock_tick_count));
368 }
369 };
370
TEST(ToString,TimeSensorClockZero)371 TEST(ToString, TimeSensorClockZero) {
372 sensor_clock_tick_count = 0;
373 EXPECT_TRUE(ToString(SensorClock::now(), buffer).ok());
374 EXPECT_STREQ("0ns", buffer);
375 }
376
TEST(ToString,TimeSensorClockMax)377 TEST(ToString, TimeSensorClockMax) {
378 sensor_clock_tick_count = std::numeric_limits<int64_t>::max();
379 EXPECT_TRUE(ToString(SensorClock::now(), buffer).ok());
380 EXPECT_STREQ("An unrepresentably long time in the future (>292 years).",
381 buffer);
382 }
383
TEST(ToString,TimeSensorClockMin)384 TEST(ToString, TimeSensorClockMin) {
385 sensor_clock_tick_count = std::numeric_limits<int64_t>::min();
386 EXPECT_TRUE(ToString(SensorClock::now(), buffer).ok());
387 EXPECT_STREQ("An unrepresentably long time ago (>292 years).", buffer);
388 }
389
TEST(ToString,DurationEndsInNs)390 TEST(ToString, DurationEndsInNs) {
391 EXPECT_TRUE(ToString(std::chrono::nanoseconds::zero(), buffer).ok());
392 EXPECT_STREQ("0ns", buffer);
393 }
394
395 } // namespace
396 } // namespace pw
397