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
8typedef 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
21static 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
102bool 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
119bool 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.
149bool 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
192bool 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
203bool 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
212BEGIN_TEST_CASE(pretty_tests)
213RUN_TEST(format_size_fixed_test)
214RUN_TEST(format_size_short_buf_truncates)
215RUN_TEST(format_size_bad_unit_short_buf_truncates)
216RUN_TEST(format_size_empty_str_succeeds)
217RUN_TEST(format_size_empty_null_str_succeeds)
218END_TEST_CASE(pretty_tests)
219
220int main(int argc, char** argv) {
221    return unittest_run_all_tests(argc, argv) ? 0 : -1;
222}
223