1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <pretty/sizes.h>
6 #include <unittest/unittest.h>
7
8 typedef struct {
9 const size_t input;
10 const char unit;
11 const char* expected_output;
12 } format_size_test_case_t;
13
14 #define KILO (1024ULL)
15 #define MEGA (KILO * 1024)
16 #define GIGA (MEGA * 1024)
17 #define TERA (GIGA * 1024)
18 #define PETA (TERA * 1024)
19 #define EXA (PETA * 1024)
20
21 static const format_size_test_case_t format_size_test_cases[] = {
22 // Declare a test case that uses a unit of 0,
23 // picking a natural unit for the size.
24 #define TC0(i, o) \
25 { .input = i, .unit = 0, .expected_output = o }
26
27 // Whole multiples don't print decimals,
28 // and always round up to their largest unit.
29 TC0(0, "0B"),
30 TC0(1, "1B"),
31
32 // Favor the largest unit when it loses no precision
33 // (e.g., "1k" not "1024B").
34 // Larger values may still use a smaller unit
35 // (e.g., "1k" + 1 == "1025B") to preserve precision.
36 TC0(KILO - 1, "1023B"),
37 TC0(KILO, "1k"),
38 TC0(KILO + 1, "1025B"),
39 TC0(KILO * 9, "9k"),
40 TC0(KILO * 9 + 1, "9217B"),
41 TC0(KILO * 10, "10k"),
42
43 // Same demonstration for the next unit.
44 TC0(MEGA - KILO, "1023k"),
45 TC0(MEGA, "1M"),
46 TC0(MEGA + KILO, "1025k"),
47 TC0(MEGA * 9, "9M"),
48 TC0(MEGA * 9 + KILO, "9217k"),
49 TC0(MEGA * 10, "10M"),
50
51 // Sanity checks for remaining units.
52 TC0(MEGA, "1M"),
53 TC0(GIGA, "1G"),
54 TC0(TERA, "1T"),
55 TC0(PETA, "1P"),
56 TC0(EXA, "1E"),
57
58 // Non-whole multiples print decimals, and favor more whole digits
59 // (e.g., "1024.0k" not "1.0M") to retain precision.
60 TC0(MEGA - 1, "1024.0k"),
61 TC0(MEGA + MEGA / 3, "1365.3k"), // Only one decimal place is ever shown.
62 TC0(GIGA - 1, "1024.0M"),
63 TC0(TERA - 1, "1024.0G"),
64 TC0(PETA - 1, "1024.0T"),
65 TC0(EXA - 1, "1024.0P"),
66 TC0(UINT64_MAX, "16.0E"),
67
68 // Never show more than four whole digits,
69 // to make the values easier to eyeball.
70 TC0(9999, "9999B"),
71 TC0(10000, "9.8k"),
72 TC0(KILO * 9999, "9999k"),
73 TC0(KILO * 9999 + 1, "9999.0k"),
74 TC0(KILO * 10000, "9.8M"),
75
76 // Declare a test case fixed to the specified unit.
77 #define TCF(i, u, o) \
78 { .input = i, .unit = u, .expected_output = o }
79
80 // When fixed, we can see a lot more digits.
81 TCF(UINT64_MAX, 'B', "18446744073709551615B"),
82 TCF(UINT64_MAX, 'k', "18014398509481984.0k"),
83 TCF(UINT64_MAX, 'M', "17592186044416.0M"),
84 TCF(UINT64_MAX, 'G', "17179869184.0G"),
85 TCF(UINT64_MAX, 'T', "16777216.0T"),
86 TCF(UINT64_MAX, 'P', "16384.0P"),
87 TCF(UINT64_MAX, 'E', "16.0E"),
88
89 // Smaller than natural fixed unit.
90 TCF(GIGA, 'k', "1048576k"),
91
92 // Larger than natural fixed unit.
93 TCF(MEGA / 10, 'M', "0.1M"),
94
95 // Unknown units fall back to natural, but add a '?' prefix.
96 TCF(GIGA, 'q', "?1G"),
97 TCF(KILO, 'q', "?1k"),
98 TCF(GIGA + 1, '#', "?1.0G"),
99 TCF(KILO + 1, '#', "?1025B"),
100 };
101
format_size_fixed_test(void)102 bool format_size_fixed_test(void) {
103 BEGIN_TEST;
104 char str[MAX_FORMAT_SIZE_LEN];
105 char msg[128];
106 for (unsigned int i = 0; i < countof(format_size_test_cases); i++) {
107 const format_size_test_case_t* tc = format_size_test_cases + i;
108 memset(str, 0, sizeof(str));
109 char* ret = format_size_fixed(str, sizeof(str), tc->input, tc->unit);
110 snprintf(msg, sizeof(msg), "format_size_fixed(bytes=%zd, unit=%c)",
111 tc->input, tc->unit == 0 ? '0' : tc->unit);
112 EXPECT_STR_EQ(tc->expected_output, str, msg);
113 // Should always return the input pointer.
114 EXPECT_EQ(&(str[0]), ret, msg);
115 }
116 END_TEST;
117 }
118
format_size_short_buf_truncates(void)119 bool format_size_short_buf_truncates(void) {
120 BEGIN_TEST;
121 // Widest possible output: four whole digits + decimal.
122 static const size_t input = 1023 * 1024 + 1;
123 static const char expected_output[] = "1023.0k";
124
125 char buf[sizeof(expected_output) * 2];
126 char msg[128];
127 for (size_t str_size = 0; str_size <= sizeof(expected_output);
128 str_size++) {
129 memset(buf, 0x55, sizeof(buf));
130 char* ret = format_size(buf, str_size, input);
131
132 snprintf(msg, sizeof(msg),
133 "format_size(str_size=%zd, bytes=%zd)", str_size, input);
134 EXPECT_EQ(&(buf[0]), ret, msg);
135 if (str_size > 2) {
136 EXPECT_BYTES_EQ(
137 (uint8_t*)expected_output, (uint8_t*)buf, str_size - 1, msg);
138 }
139 if (str_size > 1) {
140 EXPECT_EQ(buf[str_size - 1], '\0', msg);
141 }
142 EXPECT_EQ(buf[str_size], 0x55, msg);
143 }
144 END_TEST;
145 }
146
147 // Tests the path where we add a prefix '?' to make sure we don't
148 // overrun the buffer or return a non-null-terminated result.
format_size_bad_unit_short_buf_truncates(void)149 bool format_size_bad_unit_short_buf_truncates(void) {
150 BEGIN_TEST;
151
152 char buf[MAX_FORMAT_SIZE_LEN];
153
154 // Size zero should not touch the buffer.
155 memset(buf, 0x55, sizeof(buf));
156 format_size_fixed(buf, 0, GIGA, 'q');
157 EXPECT_EQ(buf[0], 0x55, "");
158
159 // Size 1 should only null out the first byte.
160 memset(buf, 0x55, sizeof(buf));
161 format_size_fixed(buf, 1, GIGA, 'q');
162 EXPECT_EQ(buf[0], '\0', "");
163 EXPECT_EQ(buf[1], 0x55, "");
164
165 // Size 2 should just be the warning '?'.
166 memset(buf, 0x55, sizeof(buf));
167 format_size_fixed(buf, 2, GIGA, 'q');
168 EXPECT_EQ(buf[0], '?', "");
169 EXPECT_EQ(buf[1], '\0', "");
170 EXPECT_EQ(buf[2], 0x55, "");
171
172 // Then just the number without units.
173 memset(buf, 0x55, sizeof(buf));
174 format_size_fixed(buf, 3, GIGA, 'q');
175 EXPECT_EQ(buf[0], '?', "");
176 EXPECT_EQ(buf[1], '1', "");
177 EXPECT_EQ(buf[2], '\0', "");
178 EXPECT_EQ(buf[3], 0x55, "");
179
180 // Then the whole thing.
181 memset(buf, 0x55, sizeof(buf));
182 format_size_fixed(buf, 4, GIGA, 'q');
183 EXPECT_EQ(buf[0], '?', "");
184 EXPECT_EQ(buf[1], '1', "");
185 EXPECT_EQ(buf[2], 'G', "");
186 EXPECT_EQ(buf[3], '\0', "");
187 EXPECT_EQ(buf[4], 0x55, "");
188
189 END_TEST;
190 }
191
format_size_empty_str_succeeds(void)192 bool format_size_empty_str_succeeds(void) {
193 BEGIN_TEST;
194 static const size_t input = 1023 * 1024 + 1;
195
196 char c = 0x55;
197 char* ret = format_size(&c, 0, input);
198 EXPECT_EQ(&c, ret, "");
199 EXPECT_EQ(0x55, c, "");
200 END_TEST;
201 }
202
format_size_empty_null_str_succeeds(void)203 bool format_size_empty_null_str_succeeds(void) {
204 BEGIN_TEST;
205 static const size_t input = 1023 * 1024 + 1;
206
207 char* ret = format_size(NULL, 0, input);
208 EXPECT_EQ(NULL, ret, "");
209 END_TEST;
210 }
211
212 BEGIN_TEST_CASE(pretty_tests)
RUN_TEST(format_size_fixed_test)213 RUN_TEST(format_size_fixed_test)
214 RUN_TEST(format_size_short_buf_truncates)
215 RUN_TEST(format_size_bad_unit_short_buf_truncates)
216 RUN_TEST(format_size_empty_str_succeeds)
217 RUN_TEST(format_size_empty_null_str_succeeds)
218 END_TEST_CASE(pretty_tests)
219
220 int main(int argc, char** argv) {
221 return unittest_run_all_tests(argc, argv) ? 0 : -1;
222 }
223