Files
Joe Tsai e3c3ef18a5 util/unitconv: add new package (#20868)
Package unitconv implements string conversion functionality
for unit prefixes such as those from the SI and IEC standards.
It is catered towards printing of values for human consumption.

This exists to support open-sourcing JSON migration tooling.

Updates #20220
Updates tailscale/corp#791

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2026-10-16 18:02:33 -07:00

242 lines
7.7 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package unitconv
import (
"math"
"testing"
"time"
)
func TestFormatSI(t *testing.T) {
tests := []struct {
in float64
unit string
want string
}{
{-123456789123456789, "W", "-123PW"},
{-12345678912345678, "W", "-12.3PW"},
{-1234567891234567, "W", "-1.23PW"},
{-123456789123456, "W", "-123TW"},
{-12345678912345, "W", "-12.3TW"},
{-1234567891234, "W", "-1.23TW"},
{-123456789123, "W", "-123GW"},
{-12345678912, "W", "-12.3GW"},
{-1234567891, "W", "-1.23GW"},
{-123456789, "W", "-123MW"},
{-12345678, "W", "-12.3MW"},
{-1234567, "W", "-1.23MW"},
{-123456, "W", "-123kW"},
{-12345, "W", "-12.3kW"},
{-1234, "W", "-1.23kW"},
{-123, "W", "-123W"},
{-12, "W", "-12W"},
{-1, "W", "-1W"},
{0, "W", "0W"},
{+123, "W", "123W"},
{+123456, "W", "123kW"},
{+123456789, "W", "123MW"},
{+123456789123, "W", "123GW"},
{+123456789123456, "W", "123TW"},
{+123456789123456789, "W", "123PW"},
{-1000000000000000000, " B", "-1 EB"},
{-1000000000000000, " B", "-1 PB"},
{-1000000000000, " B", "-1 TB"},
{-1000000000, " B", "-1 GB"},
{-1000000, " B", "-1 MB"},
{-1000, " B", "-1 kB"},
{0, " B", "0 B"},
{+1000, " B", "1 kB"},
{+1000000, " B", "1 MB"},
{+1000000000, " B", "1 GB"},
{+1000000000000, " B", "1 TB"},
{+1000000000000000, " B", "1 PB"},
{+1000000000000000000, " B", "1 EB"},
{0.123456, "m", "123mm"},
{0.0123456, "m", "12.3mm"},
{0.00123456, "m", "1.23mm"},
{0.000123456, "m", "123µm"},
{0.0000123456, "m", "12.3µm"},
{0.00000123456, "m", "1.23µm"},
{0.000000123456, "m", "123nm"},
{0.0000000123456, "m", "12.3nm"},
{0.00000000123456, "m", "1.23nm"},
{0.000000000123456, "m", "123pm"},
{0.0000000000123456, "m", "12.3pm"},
{0.00000000000123456, "m", "1.23pm"},
{0.000000000000123456, "m", "123fm"},
{0.0000000000000123456, "m", "12.3fm"},
{0.00000000000000123456, "m", "1.23fm"},
{0.000000000000000123456, "m", "123am"},
{0.0000000000000000123456, "m", "12.3am"},
{math.Nextafter(0, 1), "W", "0.00yW"},
{math.Copysign(0, -1), "W", "-0W"},
{math.Nextafter(1e0, math.Inf(-1)), "W", "1000mW"},
{1e0, "W", "1W"},
{math.Nextafter(1e0, math.Inf(+1)), "W", "1.00W"},
{math.Nextafter(50e0, math.Inf(-1)), "W", "50.0W"},
{50e0, "W", "50W"},
{math.Nextafter(50e0, math.Inf(+1)), "W", "50.0W"},
{math.Nextafter(1e3, math.Inf(-1)), "W", "1000W"},
{1e3, "W", "1kW"},
{math.Nextafter(1e3, math.Inf(+1)), "W", "1.00kW"},
{math.Nextafter(1e6, math.Inf(-1)), "W", "1000kW"},
{1e6, "W", "1MW"},
{math.Nextafter(1e6, math.Inf(+1)), "W", "1.00MW"},
{math.Nextafter(1e9, math.Inf(-1)), "W", "1000MW"},
{1e9, "W", "1GW"},
{math.Nextafter(1e9, math.Inf(+1)), "W", "1.00GW"},
{math.Nextafter(500e9, math.Inf(-1)), "W", "500GW"},
{500e9, "W", "500GW"},
{math.Nextafter(500e9, math.Inf(+1)), "W", "500GW"},
{math.Nextafter(1e12, math.Inf(-1)), "W", "1000GW"},
{1e12, "W", "1TW"},
{math.Nextafter(1e12, math.Inf(+1)), "W", "1.00TW"},
{math.Nextafter(1e15, math.Inf(-1)), "W", "1000TW"},
{1e15, "W", "1PW"},
{math.Nextafter(1e15, math.Inf(+1)), "W", "1.00PW"},
{math.Nextafter(1e18, math.Inf(-1)), "W", "1000PW"},
{1e18, "W", "1EW"},
{math.Nextafter(1e18, math.Inf(+1)), "W", "1.00EW"},
{math.Nextafter(1e21, math.Inf(-1)), "W", "1000EW"},
{1e21, "W", "1ZW"},
{math.Nextafter(1e21, math.Inf(+1)), "W", "1.00ZW"},
{math.Nextafter(1e24, math.Inf(-1)), "W", "1000ZW"},
{1e24, "W", "1YW"},
{math.Nextafter(1e24, math.Inf(+1)), "W", "1.00YW"},
{math.Inf(-1), "W", "-Inf"},
{math.Inf(+1), "W", "+Inf"},
{math.NaN(), "W", "NaN"},
}
for _, tt := range tests {
got := FormatSI(tt.in, tt.unit)
if got != tt.want {
t.Errorf("FormatSI(%v, %q) = %q, want %q", tt.in, tt.unit, got, tt.want)
}
}
}
func TestFormatIEC(t *testing.T) {
tests := []struct {
in float64
unit string
want string
}{
{-123456789123456789, "B/s", "-109.6PiB/s"},
{-12345678912345678, "B/s", "-10.96PiB/s"},
{-1234567891234567, "B/s", "-1.096PiB/s"},
{-123456789123456, "B/s", "-112.2TiB/s"},
{-12345678912345, "B/s", "-11.22TiB/s"},
{-1234567891234, "B/s", "-1.122TiB/s"},
{-123456789123, "B/s", "-114.9GiB/s"},
{-12345678912, "B/s", "-11.49GiB/s"},
{-1234567891, "B/s", "-1.149GiB/s"},
{-123456789, "B/s", "-117.7MiB/s"},
{-12345678, "B/s", "-11.77MiB/s"},
{-1234567, "B/s", "-1.177MiB/s"},
{-123456, "B/s", "-120.5KiB/s"},
{-12345, "B/s", "-12.05KiB/s"},
{-1234, "B/s", "-1.205KiB/s"},
{-123, "B/s", "-123B/s"},
{-12, "B/s", "-12B/s"},
{-1, "B/s", "-1B/s"},
{0, "B/s", "0B/s"},
{+123, "B/s", "123B/s"},
{+123456, "B/s", "120.5KiB/s"},
{+123456789, "B/s", "117.7MiB/s"},
{+123456789123, "B/s", "114.9GiB/s"},
{+123456789123456, "B/s", "112.2TiB/s"},
{+123456789123456789, "B/s", "109.6PiB/s"},
{-1000000000000000000, " B", "-888.1 PiB"},
{-1000000000000000, " B", "-909.4 TiB"},
{-1000000000000, " B", "-931.3 GiB"},
{-1000000000, " B", "-953.6 MiB"},
{-1000000, " B", "-976.5 KiB"},
{-1000, " B", "-1000 B"},
{0, " B", "0 B"},
{+1000, " B", "1000 B"},
{+1000000, " B", "976.5 KiB"},
{+1000000000, " B", "953.6 MiB"},
{+1000000000000, " B", "931.3 GiB"},
{+1000000000000000, " B", "909.4 TiB"},
{+1000000000000000000, " B", "888.1 PiB"},
{math.Nextafter(0, 1), "B", "0.000B"},
{math.Copysign(0, -1), "B", "-0B"},
{math.Nextafter(1<<0, math.Inf(-1)), "B", "1.000B"},
{1 << 0, "B", "1B"},
{math.Nextafter(1<<0, math.Inf(+1)), "B", "1.000B"},
{math.Nextafter(1<<10, math.Inf(-1)), "B", "1024B"},
{1 << 10, "B", "1KiB"},
{math.Nextafter(1<<10, math.Inf(+1)), "B", "1.000KiB"},
{math.Nextafter(1<<20, math.Inf(-1)), "B", "1024KiB"},
{1 << 20, "B", "1MiB"},
{math.Nextafter(1<<20, math.Inf(+1)), "B", "1.000MiB"},
{math.Nextafter(1<<30, math.Inf(-1)), "B", "1024MiB"},
{1 << 30, "B", "1GiB"},
{math.Nextafter(1<<30, math.Inf(+1)), "B", "1.000GiB"},
{math.Nextafter(1<<40, math.Inf(-1)), "B", "1024GiB"},
{1 << 40, "B", "1TiB"},
{math.Nextafter(1<<40, math.Inf(+1)), "B", "1.000TiB"},
{math.Nextafter(1<<50, math.Inf(-1)), "B", "1024TiB"},
{1 << 50, "B", "1PiB"},
{math.Nextafter(1<<50, math.Inf(+1)), "B", "1.000PiB"},
{math.Nextafter(1<<60, math.Inf(-1)), "B", "1024PiB"},
{1 << 60, "B", "1EiB"},
{math.Nextafter(1<<60, math.Inf(+1)), "B", "1.000EiB"},
{math.Nextafter(1<<70, math.Inf(-1)), "B", "1024EiB"},
{1 << 70, "B", "1ZiB"},
{math.Nextafter(1<<70, math.Inf(+1)), "B", "1.000ZiB"},
{math.Nextafter(1<<80, math.Inf(-1)), "B", "1024ZiB"},
{1 << 80, "B", "1YiB"},
{math.Nextafter(1<<80, math.Inf(+1)), "B", "1.000YiB"},
{math.Inf(-1), "B", "-Inf"},
{math.Inf(+1), "B", "+Inf"},
{math.NaN(), "B", "NaN"},
}
for _, tt := range tests {
got := FormatIEC(tt.in, tt.unit)
if got != tt.want {
t.Errorf("FormatIEC(%v, %q) = %q, want %q", tt.in, tt.unit, got, tt.want)
}
}
}
func TestFormatDuration(t *testing.T) {
tests := []struct {
in time.Duration
want string
}{
{-123456789123456789, "-3y333d"},
{-123456789123456, "-1d10h"},
{-123456789123, "-2m3s"},
{-123456789, "-123ms"},
{-123456, "-123µs"},
{-123, "-123ns"},
{0, "0s"},
{123, "123ns"},
{123456, "123µs"},
{123456789, "123ms"},
{123456789123, "2m3s"},
{123456789123456, "1d10h"},
{123456789123456789, "3y333d"},
{math.MinInt64, "-292y98d"},
{math.MaxInt64, "292y98d"},
{time.Minute - 1, "1m0s"},
{time.Minute, "1m0s"},
{time.Hour - 1, "60m0s"},
{time.Hour, "1h0m"},
{24*time.Hour - 1, "24h0m"},
{24 * time.Hour, "1d0h"},
{365 * 24 * time.Hour, "365d0h"},
{365*24*time.Hour + 6*time.Hour - 1, "365d6h"},
{365*24*time.Hour + 6*time.Hour, "1y0d"},
}
for _, tt := range tests {
got := FormatDuration(tt.in)
if got != tt.want {
t.Errorf("FormatDuration(%d) = %q, want %q", tt.in, got, tt.want)
}
}
}