package bytesize_test import ( "math" "math/rand" "strings" "testing" "gitoa.ru/go-4devs/bytesize" ) var parseTests = []struct { in string want bytesize.Size }{ // simple {"0", 0}, {"5KiB", 5 * bytesize.Kibibyte}, {"30KiB", 30 * bytesize.Kibibyte}, {"1478KiB", 1478 * bytesize.Kibibyte}, // sign {"-5KiB", -5 * bytesize.Kibibyte}, {"+5KiB", 5 * bytesize.Kibibyte}, {"-0", 0}, {"+0", 0}, // decimal {"5.0KiB", 5 * bytesize.Kibibyte}, {"5.6KiB", 5*bytesize.Kibibyte + bytesize.Kibibyte*6/10}, {"5.KiB", 5 * bytesize.Kibibyte}, {".5KiB", bytesize.Kibibyte / 2}, {"1.0KiB", 1 * bytesize.Kibibyte}, {"1.00KiB", 1 * bytesize.Kibibyte}, {"1.004KiB", 1*bytesize.Kibibyte + 4*bytesize.Byte}, {"1.0040KiB", 1*bytesize.Kibibyte + 4*bytesize.Byte}, {"100.00100KiB", 100*bytesize.Kibibyte + 1*bytesize.Byte}, // different units {"10B", 10 * bytesize.Byte}, {"11KiB", 11 * bytesize.Kibibyte}, {"12MiB", 12 * bytesize.Mebibyte}, {"12GiB", 12 * bytesize.Gibibyte}, {"13TiB", 13 * bytesize.Tebibyte}, {"14PiB", 14 * bytesize.Pebibyte}, // composite durations {"3PiB30TiB", 3*bytesize.Pebibyte + 30*bytesize.Tebibyte}, {"10.5MiB4TiB", 4*bytesize.Tebibyte + 10*bytesize.Mebibyte + bytesize.Mebibyte/2}, {"-2TiB3.4MiB", -(2*bytesize.Tebibyte + 3*bytesize.Mebibyte + bytesize.Mebibyte*4/10)}, { "1PiB2TiB3GiB4MiB5KiB6B", 1*bytesize.Pebibyte + 2*bytesize.Tebibyte + 3*bytesize.Gibibyte + 4*bytesize.Mebibyte + 5*bytesize.Kibibyte + 6*bytesize.Byte, }, { "39PiB9TiB14.425GiB", 39*bytesize.Pebibyte + 9*bytesize.Tebibyte + 14*bytesize.Gibibyte + bytesize.Gibibyte*425/1000, }, // large value {"52763797000B", 52763797000 * bytesize.Byte}, // more than 9 digits after decimal point {"0.3333333333333333333GiB", bytesize.Gibibyte * 3333333333 / 1e10}, // 9007199254740993 = 1<<53+1 cannot be stored precisely in a float64 {"9007199254740993B", (1<<53 + 1) * bytesize.Byte}, // largest duration that can be represented by int64 in nanoseconds {"9223372036854775807B", (1<<63 - 1) * bytesize.Byte}, {"-9223372036854775808B", -1 << 63 * bytesize.Byte}, // largest negative value {"-9223372036854775808B", -1 << 63 * bytesize.Byte}, {"0.100000000000000000000MiB", bytesize.Mebibyte * 1 / 10}, // simple {"5GB", 5 * bytesize.Gigabyte}, {"30GB", 30 * bytesize.Gigabyte}, {"1478GB", 1478 * bytesize.Gigabyte}, // sign {"-5GB", -5 * bytesize.Gigabyte}, {"+5GB", 5 * bytesize.Gigabyte}, // decimal {"5.0GB", 5 * bytesize.Gigabyte}, {"5.6GB", 5*bytesize.Gigabyte + 600*bytesize.Megabyte}, {"5.GB", 5 * bytesize.Gigabyte}, {".5GB", 500 * bytesize.Megabyte}, {"1.0GB", 1 * bytesize.Gigabyte}, {"1.00GB", 1 * bytesize.Gigabyte}, {"1.004GB", 1*bytesize.Gigabyte + 4*bytesize.Megabyte}, {"1.0040GB", 1*bytesize.Gigabyte + 4*bytesize.Megabyte}, {"100.00100GB", 100*bytesize.Gigabyte + 1*bytesize.Megabyte}, // different units {"10B", 10 * bytesize.Byte}, {"11kB", 11 * bytesize.Kilobyte}, {"12MB", 12 * bytesize.Megabyte}, {"12GB", 12 * bytesize.Gigabyte}, {"13TB", 13 * bytesize.Terabyte}, {"14PB", 14 * bytesize.Petabyte}, // composite durations {"3GB30MB", 3*bytesize.Gigabyte + 30*bytesize.Megabyte}, {"10.5MB4GB", 4*bytesize.Gigabyte + 10*bytesize.Megabyte + 500*bytesize.Kilobyte}, {"-2TB3.4GB", -(2*bytesize.Terabyte + 3*bytesize.Gigabyte + 400*bytesize.Megabyte)}, { "1PB2TB3GB4MB5kB6B", 1*bytesize.Petabyte + 2*bytesize.Terabyte + 3*bytesize.Gigabyte + 4*bytesize.Megabyte + 5*bytesize.Kilobyte + 6*bytesize.Byte, }, {"39PB9TB14.425GB", 39*bytesize.Petabyte + 9*bytesize.Terabyte + 14*bytesize.Gigabyte + 425*bytesize.Megabyte}, {"9223372036854775.807kB", (1<<63 - 1) * bytesize.Byte}, {"9223372036GB854MB775kB807B", (1<<63 - 1) * bytesize.Byte}, {"-9223372036854775.808kB", -1 << 63 * bytesize.Byte}, {"-9223372036GB854MB775kB808B", -1 << 63 * bytesize.Byte}, // largest negative round trip value {"-9223372036GB854MB775.808kB", -1 << 63 * bytesize.Byte}, } func TestParse(t *testing.T) { t.Parallel() for _, tc := range parseTests { d, err := bytesize.Parse(tc.in) if err != nil || d != tc.want { t.Errorf("Parse(%q) = %v, %v, want %v, nil", tc.in, d, err, tc.want) } } } var parseErrorTests = []struct { in string expect string }{ // invalid {"", `""`}, {"3", `"3"`}, {"-", `"-"`}, {"kB", `"kB"`}, {".", `"."`}, {"-.", `"-."`}, {".MB", `".MB"`}, {"+.MB", `"+.MB"`}, {"1ExB", `"1ExB"`}, {"\x85\x85", `"\x85\x85"`}, {"\xffff", `"\xffff"`}, {"hello \xffff world", `"hello \xffff world"`}, {"\uFFFD", `"�"`}, // utf8.RuneError {"\uFFFD hello \uFFFD world", `"� hello � world"`}, // utf8.RuneError // overflow {"9223372036854775810B", `"9223372036854775810B"`}, {"9223372036854775808B", `"9223372036854775808B"`}, {"-9223372036854775809B", `"-9223372036854775809B"`}, {"9223372036854776kB", `"9223372036854776kB"`}, {"3000000PB", `"3000000PB"`}, {"9223372036854775.808KiB", `"9223372036854775.808KiB"`}, {"9223372036854MB775kB808B", `"9223372036854MB775kB808B"`}, } func TestParseErrors(t *testing.T) { t.Parallel() for _, tc := range parseErrorTests { _, err := bytesize.Parse(tc.in) if err == nil { t.Errorf("Parse(%q) = _, nil, want _, non-nil", tc.in) } else if !strings.Contains(err.Error(), tc.expect) { t.Errorf("Parse(%q) = _, %q, error does not contain %q", tc.in, err, tc.expect) } } } func TestParseRoundTrip(t *testing.T) { t.Parallel() max0 := bytesize.Size(math.MaxInt64) max1, err := bytesize.Parse(max0.String()) if err != nil || max0 != max1 { t.Errorf("round-trip failed: %d => %q => %d, %v", max0, max0.String(), max1, err) } min0 := bytesize.Size(math.MinInt64) min1, err := bytesize.Parse(min0.String()) if err != nil || min0 != min1 { t.Errorf("round-trip failed: %d => %q => %d, %v", min0, min0.String(), min1, err) } for range 100 { // Resolutions finer than milliseconds will result in // imprecise round-trips. d0 := bytesize.Size(rand.Int31()) * bytesize.Megabyte s := d0.String() d1, err := bytesize.Parse(s) if err != nil || d0 != d1 { t.Errorf("round-trip failed: %d => %q => %d, %v", d0, s, d1, err) } } }