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