1331 lines
36 KiB
Go
1331 lines
36 KiB
Go
|
/*
|
||
|
Copyright 2014 The Kubernetes Authors.
|
||
|
|
||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
you may not use this file except in compliance with the License.
|
||
|
You may obtain a copy of the License at
|
||
|
|
||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
||
|
Unless required by applicable law or agreed to in writing, software
|
||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
See the License for the specific language governing permissions and
|
||
|
limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package resource
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"math/rand"
|
||
|
"strings"
|
||
|
"testing"
|
||
|
"unicode"
|
||
|
|
||
|
fuzz "github.com/google/gofuzz"
|
||
|
"github.com/spf13/pflag"
|
||
|
|
||
|
inf "gopkg.in/inf.v0"
|
||
|
)
|
||
|
|
||
|
var useInfDec bool
|
||
|
|
||
|
func amount(i int64, exponent int) infDecAmount {
|
||
|
// See the below test-- scale is the negative of an exponent.
|
||
|
return infDecAmount{inf.NewDec(i, inf.Scale(-exponent))}
|
||
|
}
|
||
|
|
||
|
func dec(i int64, exponent int) infDecAmount {
|
||
|
// See the below test-- scale is the negative of an exponent.
|
||
|
return infDecAmount{inf.NewDec(i, inf.Scale(-exponent))}
|
||
|
}
|
||
|
|
||
|
func decQuantity(i int64, exponent int, format Format) Quantity {
|
||
|
return Quantity{d: dec(i, exponent), Format: format}
|
||
|
}
|
||
|
|
||
|
func intQuantity(i int64, exponent Scale, format Format) Quantity {
|
||
|
return Quantity{i: int64Amount{value: i, scale: exponent}, Format: format}
|
||
|
}
|
||
|
|
||
|
func TestDec(t *testing.T) {
|
||
|
table := []struct {
|
||
|
got infDecAmount
|
||
|
expect string
|
||
|
}{
|
||
|
{dec(1, 0), "1"},
|
||
|
{dec(1, 1), "10"},
|
||
|
{dec(5, 2), "500"},
|
||
|
{dec(8, 3), "8000"},
|
||
|
{dec(2, 0), "2"},
|
||
|
{dec(1, -1), "0.1"},
|
||
|
{dec(3, -2), "0.03"},
|
||
|
{dec(4, -3), "0.004"},
|
||
|
}
|
||
|
|
||
|
for _, item := range table {
|
||
|
if e, a := item.expect, item.got.Dec.String(); e != a {
|
||
|
t.Errorf("expected %v, got %v", e, a)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TestQuantityParseZero ensures that when a 0 quantity is passed, its string value is 0
|
||
|
func TestQuantityParseZero(t *testing.T) {
|
||
|
zero := MustParse("0")
|
||
|
if expected, actual := "0", zero.String(); expected != actual {
|
||
|
t.Errorf("Expected %v, actual %v", expected, actual)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TestQuantityAddZeroPreservesSuffix verifies that a suffix is preserved
|
||
|
// independent of the order of operations when adding a zero and non-zero val
|
||
|
func TestQuantityAddZeroPreservesSuffix(t *testing.T) {
|
||
|
testValues := []string{"100m", "1Gi"}
|
||
|
zero := MustParse("0")
|
||
|
for _, testValue := range testValues {
|
||
|
value := MustParse(testValue)
|
||
|
v1 := *value.Copy()
|
||
|
// ensure non-zero + zero = non-zero (suffix preserved)
|
||
|
v1.Add(zero)
|
||
|
// ensure zero + non-zero = non-zero (suffix preserved)
|
||
|
v2 := *zero.Copy()
|
||
|
v2.Add(value)
|
||
|
|
||
|
if v1.String() != testValue {
|
||
|
t.Errorf("Expected %v, actual %v", testValue, v1.String())
|
||
|
continue
|
||
|
}
|
||
|
if v2.String() != testValue {
|
||
|
t.Errorf("Expected %v, actual %v", testValue, v2.String())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TestQuantitySubZeroPreservesSuffix verifies that a suffix is preserved
|
||
|
// independent of the order of operations when subtracting a zero and non-zero val
|
||
|
func TestQuantitySubZeroPreservesSuffix(t *testing.T) {
|
||
|
testValues := []string{"100m", "1Gi"}
|
||
|
zero := MustParse("0")
|
||
|
for _, testValue := range testValues {
|
||
|
value := MustParse(testValue)
|
||
|
v1 := *value.Copy()
|
||
|
// ensure non-zero - zero = non-zero (suffix preserved)
|
||
|
v1.Sub(zero)
|
||
|
// ensure we preserved the input value
|
||
|
if v1.String() != testValue {
|
||
|
t.Errorf("Expected %v, actual %v", testValue, v1.String())
|
||
|
}
|
||
|
|
||
|
// ensure zero - non-zero = -non-zero (suffix preserved)
|
||
|
v2 := *zero.Copy()
|
||
|
v2.Sub(value)
|
||
|
negVal := *value.Copy()
|
||
|
negVal.Neg()
|
||
|
if v2.String() != negVal.String() {
|
||
|
t.Errorf("Expected %v, actual %v", negVal.String(), v2.String())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Verifies that you get 0 as canonical value if internal value is 0, and not 0<suffix>
|
||
|
func TestQuantityCanocicalizeZero(t *testing.T) {
|
||
|
val := MustParse("1000m")
|
||
|
val.i.Sub(int64Amount{value: 1})
|
||
|
zero := Quantity{i: val.i, Format: DecimalSI}
|
||
|
if expected, actual := "0", zero.String(); expected != actual {
|
||
|
t.Errorf("Expected %v, actual %v", expected, actual)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestQuantityCmp(t *testing.T) {
|
||
|
table := []struct {
|
||
|
x string
|
||
|
y string
|
||
|
expect int
|
||
|
}{
|
||
|
{"0", "0", 0},
|
||
|
{"100m", "50m", 1},
|
||
|
{"50m", "100m", -1},
|
||
|
{"10000T", "100Gi", 1},
|
||
|
}
|
||
|
for _, testCase := range table {
|
||
|
q1 := MustParse(testCase.x)
|
||
|
q2 := MustParse(testCase.y)
|
||
|
if result := q1.Cmp(q2); result != testCase.expect {
|
||
|
t.Errorf("X: %v, Y: %v, Expected: %v, Actual: %v", testCase.x, testCase.y, testCase.expect, result)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
nils := []struct {
|
||
|
x *inf.Dec
|
||
|
y *inf.Dec
|
||
|
expect int
|
||
|
}{
|
||
|
{dec(0, 0).Dec, dec(0, 0).Dec, 0},
|
||
|
{nil, dec(0, 0).Dec, 0},
|
||
|
{dec(0, 0).Dec, nil, 0},
|
||
|
{nil, nil, 0},
|
||
|
{nil, dec(10, 0).Dec, -1},
|
||
|
{nil, dec(-10, 0).Dec, 1},
|
||
|
{dec(10, 0).Dec, nil, 1},
|
||
|
{dec(-10, 0).Dec, nil, -1},
|
||
|
}
|
||
|
for _, nilCase := range nils {
|
||
|
q1 := Quantity{d: infDecAmount{nilCase.x}, Format: DecimalSI}
|
||
|
q2 := Quantity{d: infDecAmount{nilCase.y}, Format: DecimalSI}
|
||
|
if result := q1.Cmp(q2); result != nilCase.expect {
|
||
|
t.Errorf("X: %v, Y: %v, Expected: %v, Actual: %v", nilCase.x, nilCase.y, nilCase.expect, result)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestParseQuantityString(t *testing.T) {
|
||
|
table := []struct {
|
||
|
input string
|
||
|
positive bool
|
||
|
value string
|
||
|
num, denom, suffix string
|
||
|
}{
|
||
|
{"0.025Ti", true, "0.025", "0", "025", "Ti"},
|
||
|
{"1.025Ti", true, "1.025", "1", "025", "Ti"},
|
||
|
{"-1.025Ti", false, "-1.025", "1", "025", "Ti"},
|
||
|
{".", true, ".", "0", "", ""},
|
||
|
{"-.", false, "-.", "0", "", ""},
|
||
|
{"1E-3", true, "1", "1", "", "E-3"},
|
||
|
}
|
||
|
for _, test := range table {
|
||
|
positive, value, num, denom, suffix, err := parseQuantityString(test.input)
|
||
|
if err != nil {
|
||
|
t.Errorf("%s: error: %v", test.input, err)
|
||
|
continue
|
||
|
}
|
||
|
if positive != test.positive || value != test.value || num != test.num || denom != test.denom || suffix != test.suffix {
|
||
|
t.Errorf("%s: unmatched: %t %q %q %q %q", test.input, positive, value, num, denom, suffix)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestQuantityParse(t *testing.T) {
|
||
|
if _, err := ParseQuantity(""); err == nil {
|
||
|
t.Errorf("expected empty string to return error")
|
||
|
}
|
||
|
|
||
|
table := []struct {
|
||
|
input string
|
||
|
expect Quantity
|
||
|
}{
|
||
|
{"0", decQuantity(0, 0, DecimalSI)},
|
||
|
{"0n", decQuantity(0, 0, DecimalSI)},
|
||
|
{"0u", decQuantity(0, 0, DecimalSI)},
|
||
|
{"0m", decQuantity(0, 0, DecimalSI)},
|
||
|
{"0Ki", decQuantity(0, 0, BinarySI)},
|
||
|
{"0k", decQuantity(0, 0, DecimalSI)},
|
||
|
{"0Mi", decQuantity(0, 0, BinarySI)},
|
||
|
{"0M", decQuantity(0, 0, DecimalSI)},
|
||
|
{"0Gi", decQuantity(0, 0, BinarySI)},
|
||
|
{"0G", decQuantity(0, 0, DecimalSI)},
|
||
|
{"0Ti", decQuantity(0, 0, BinarySI)},
|
||
|
{"0T", decQuantity(0, 0, DecimalSI)},
|
||
|
|
||
|
// Quantity less numbers are allowed
|
||
|
{"1", decQuantity(1, 0, DecimalSI)},
|
||
|
|
||
|
// Binary suffixes
|
||
|
{"1Ki", decQuantity(1024, 0, BinarySI)},
|
||
|
{"8Ki", decQuantity(8*1024, 0, BinarySI)},
|
||
|
{"7Mi", decQuantity(7*1024*1024, 0, BinarySI)},
|
||
|
{"6Gi", decQuantity(6*1024*1024*1024, 0, BinarySI)},
|
||
|
{"5Ti", decQuantity(5*1024*1024*1024*1024, 0, BinarySI)},
|
||
|
{"4Pi", decQuantity(4*1024*1024*1024*1024*1024, 0, BinarySI)},
|
||
|
{"3Ei", decQuantity(3*1024*1024*1024*1024*1024*1024, 0, BinarySI)},
|
||
|
|
||
|
{"10Ti", decQuantity(10*1024*1024*1024*1024, 0, BinarySI)},
|
||
|
{"100Ti", decQuantity(100*1024*1024*1024*1024, 0, BinarySI)},
|
||
|
|
||
|
// Decimal suffixes
|
||
|
{"5n", decQuantity(5, -9, DecimalSI)},
|
||
|
{"4u", decQuantity(4, -6, DecimalSI)},
|
||
|
{"3m", decQuantity(3, -3, DecimalSI)},
|
||
|
{"9", decQuantity(9, 0, DecimalSI)},
|
||
|
{"8k", decQuantity(8, 3, DecimalSI)},
|
||
|
{"50k", decQuantity(5, 4, DecimalSI)},
|
||
|
{"7M", decQuantity(7, 6, DecimalSI)},
|
||
|
{"6G", decQuantity(6, 9, DecimalSI)},
|
||
|
{"5T", decQuantity(5, 12, DecimalSI)},
|
||
|
{"40T", decQuantity(4, 13, DecimalSI)},
|
||
|
{"300T", decQuantity(3, 14, DecimalSI)},
|
||
|
{"2P", decQuantity(2, 15, DecimalSI)},
|
||
|
{"1E", decQuantity(1, 18, DecimalSI)},
|
||
|
|
||
|
// Decimal exponents
|
||
|
{"1E-3", decQuantity(1, -3, DecimalExponent)},
|
||
|
{"1e3", decQuantity(1, 3, DecimalExponent)},
|
||
|
{"1E6", decQuantity(1, 6, DecimalExponent)},
|
||
|
{"1e9", decQuantity(1, 9, DecimalExponent)},
|
||
|
{"1E12", decQuantity(1, 12, DecimalExponent)},
|
||
|
{"1e15", decQuantity(1, 15, DecimalExponent)},
|
||
|
{"1E18", decQuantity(1, 18, DecimalExponent)},
|
||
|
|
||
|
// Nonstandard but still parsable
|
||
|
{"1e14", decQuantity(1, 14, DecimalExponent)},
|
||
|
{"1e13", decQuantity(1, 13, DecimalExponent)},
|
||
|
{"1e3", decQuantity(1, 3, DecimalExponent)},
|
||
|
{"100.035k", decQuantity(100035, 0, DecimalSI)},
|
||
|
|
||
|
// Things that look like floating point
|
||
|
{"0.001", decQuantity(1, -3, DecimalSI)},
|
||
|
{"0.0005k", decQuantity(5, -1, DecimalSI)},
|
||
|
{"0.005", decQuantity(5, -3, DecimalSI)},
|
||
|
{"0.05", decQuantity(5, -2, DecimalSI)},
|
||
|
{"0.5", decQuantity(5, -1, DecimalSI)},
|
||
|
{"0.00050k", decQuantity(5, -1, DecimalSI)},
|
||
|
{"0.00500", decQuantity(5, -3, DecimalSI)},
|
||
|
{"0.05000", decQuantity(5, -2, DecimalSI)},
|
||
|
{"0.50000", decQuantity(5, -1, DecimalSI)},
|
||
|
{"0.5e0", decQuantity(5, -1, DecimalExponent)},
|
||
|
{"0.5e-1", decQuantity(5, -2, DecimalExponent)},
|
||
|
{"0.5e-2", decQuantity(5, -3, DecimalExponent)},
|
||
|
{"0.5e0", decQuantity(5, -1, DecimalExponent)},
|
||
|
{"10.035M", decQuantity(10035, 3, DecimalSI)},
|
||
|
|
||
|
{"1.2e3", decQuantity(12, 2, DecimalExponent)},
|
||
|
{"1.3E+6", decQuantity(13, 5, DecimalExponent)},
|
||
|
{"1.40e9", decQuantity(14, 8, DecimalExponent)},
|
||
|
{"1.53E12", decQuantity(153, 10, DecimalExponent)},
|
||
|
{"1.6e15", decQuantity(16, 14, DecimalExponent)},
|
||
|
{"1.7E18", decQuantity(17, 17, DecimalExponent)},
|
||
|
|
||
|
{"9.01", decQuantity(901, -2, DecimalSI)},
|
||
|
{"8.1k", decQuantity(81, 2, DecimalSI)},
|
||
|
{"7.123456M", decQuantity(7123456, 0, DecimalSI)},
|
||
|
{"6.987654321G", decQuantity(6987654321, 0, DecimalSI)},
|
||
|
{"5.444T", decQuantity(5444, 9, DecimalSI)},
|
||
|
{"40.1T", decQuantity(401, 11, DecimalSI)},
|
||
|
{"300.2T", decQuantity(3002, 11, DecimalSI)},
|
||
|
{"2.5P", decQuantity(25, 14, DecimalSI)},
|
||
|
{"1.01E", decQuantity(101, 16, DecimalSI)},
|
||
|
|
||
|
// Things that saturate/round
|
||
|
{"3.001n", decQuantity(4, -9, DecimalSI)},
|
||
|
{"1.1E-9", decQuantity(2, -9, DecimalExponent)},
|
||
|
{"0.0000000001", decQuantity(1, -9, DecimalSI)},
|
||
|
{"0.0000000005", decQuantity(1, -9, DecimalSI)},
|
||
|
{"0.00000000050", decQuantity(1, -9, DecimalSI)},
|
||
|
{"0.5e-9", decQuantity(1, -9, DecimalExponent)},
|
||
|
{"0.9n", decQuantity(1, -9, DecimalSI)},
|
||
|
{"0.00000012345", decQuantity(124, -9, DecimalSI)},
|
||
|
{"0.00000012354", decQuantity(124, -9, DecimalSI)},
|
||
|
{"9Ei", Quantity{d: maxAllowed, Format: BinarySI}},
|
||
|
{"9223372036854775807Ki", Quantity{d: maxAllowed, Format: BinarySI}},
|
||
|
{"12E", decQuantity(12, 18, DecimalSI)},
|
||
|
|
||
|
// We'll accept fractional binary stuff, too.
|
||
|
{"100.035Ki", decQuantity(10243584, -2, BinarySI)},
|
||
|
{"0.5Mi", decQuantity(.5*1024*1024, 0, BinarySI)},
|
||
|
{"0.05Gi", decQuantity(536870912, -1, BinarySI)},
|
||
|
{"0.025Ti", decQuantity(274877906944, -1, BinarySI)},
|
||
|
|
||
|
// Things written by trolls
|
||
|
{"0.000000000001Ki", decQuantity(2, -9, DecimalSI)}, // rounds up, changes format
|
||
|
{".001", decQuantity(1, -3, DecimalSI)},
|
||
|
{".0001k", decQuantity(100, -3, DecimalSI)},
|
||
|
{"1.", decQuantity(1, 0, DecimalSI)},
|
||
|
{"1.G", decQuantity(1, 9, DecimalSI)},
|
||
|
}
|
||
|
|
||
|
for _, asDec := range []bool{false, true} {
|
||
|
for _, item := range table {
|
||
|
got, err := ParseQuantity(item.input)
|
||
|
if err != nil {
|
||
|
t.Errorf("%v: unexpected error: %v", item.input, err)
|
||
|
continue
|
||
|
}
|
||
|
if asDec {
|
||
|
got.AsDec()
|
||
|
}
|
||
|
|
||
|
if e, a := item.expect, got; e.Cmp(a) != 0 {
|
||
|
t.Errorf("%v: expected %v, got %v", item.input, e.String(), a.String())
|
||
|
}
|
||
|
if e, a := item.expect.Format, got.Format; e != a {
|
||
|
t.Errorf("%v: expected %#v, got %#v", item.input, e, a)
|
||
|
}
|
||
|
|
||
|
if asDec {
|
||
|
if i, ok := got.AsInt64(); i != 0 || ok {
|
||
|
t.Errorf("%v: expected inf.Dec to return false for AsInt64: %d", item.input, i)
|
||
|
}
|
||
|
continue
|
||
|
}
|
||
|
i, ok := item.expect.AsInt64()
|
||
|
if !ok {
|
||
|
continue
|
||
|
}
|
||
|
j, ok := got.AsInt64()
|
||
|
if !ok {
|
||
|
if got.d.Dec == nil && got.i.scale >= 0 {
|
||
|
t.Errorf("%v: is an int64Amount, but can't return AsInt64: %v", item.input, got)
|
||
|
}
|
||
|
continue
|
||
|
}
|
||
|
if i != j {
|
||
|
t.Errorf("%v: expected equivalent representation as int64: %d %d", item.input, i, j)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, item := range table {
|
||
|
got, err := ParseQuantity(item.input)
|
||
|
if err != nil {
|
||
|
t.Errorf("%v: unexpected error: %v", item.input, err)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if asDec {
|
||
|
got.AsDec()
|
||
|
}
|
||
|
|
||
|
// verify that we can decompose the input and get the same result by building up from the base.
|
||
|
positive, _, num, denom, suffix, err := parseQuantityString(item.input)
|
||
|
if err != nil {
|
||
|
t.Errorf("%v: unexpected error: %v", item.input, err)
|
||
|
continue
|
||
|
}
|
||
|
if got.Sign() >= 0 && !positive || got.Sign() < 0 && positive {
|
||
|
t.Errorf("%v: positive was incorrect: %t", item.input, positive)
|
||
|
continue
|
||
|
}
|
||
|
var value string
|
||
|
if !positive {
|
||
|
value = "-"
|
||
|
}
|
||
|
value += num
|
||
|
if len(denom) > 0 {
|
||
|
value += "." + denom
|
||
|
}
|
||
|
value += suffix
|
||
|
if len(value) == 0 {
|
||
|
t.Errorf("%v: did not parse correctly, %q %q %q", item.input, num, denom, suffix)
|
||
|
}
|
||
|
expected, err := ParseQuantity(value)
|
||
|
if err != nil {
|
||
|
t.Errorf("%v: unexpected error for %s: %v", item.input, value, err)
|
||
|
continue
|
||
|
}
|
||
|
if expected.Cmp(got) != 0 {
|
||
|
t.Errorf("%v: not the same as %s", item.input, value)
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Try the negative version of everything
|
||
|
desired := &inf.Dec{}
|
||
|
expect := Quantity{d: infDecAmount{Dec: desired}}
|
||
|
for _, item := range table {
|
||
|
got, err := ParseQuantity("-" + strings.TrimLeftFunc(item.input, unicode.IsSpace))
|
||
|
if err != nil {
|
||
|
t.Errorf("-%v: unexpected error: %v", item.input, err)
|
||
|
continue
|
||
|
}
|
||
|
if asDec {
|
||
|
got.AsDec()
|
||
|
}
|
||
|
|
||
|
expected := item.expect
|
||
|
desired.Neg(expected.AsDec())
|
||
|
|
||
|
if e, a := expect, got; e.Cmp(a) != 0 {
|
||
|
t.Errorf("%v: expected %s, got %s", item.input, e.String(), a.String())
|
||
|
}
|
||
|
if e, a := expected.Format, got.Format; e != a {
|
||
|
t.Errorf("%v: expected %#v, got %#v", item.input, e, a)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Try everything with an explicit +
|
||
|
for _, item := range table {
|
||
|
got, err := ParseQuantity("+" + strings.TrimLeftFunc(item.input, unicode.IsSpace))
|
||
|
if err != nil {
|
||
|
t.Errorf("-%v: unexpected error: %v", item.input, err)
|
||
|
continue
|
||
|
}
|
||
|
if asDec {
|
||
|
got.AsDec()
|
||
|
}
|
||
|
|
||
|
if e, a := item.expect, got; e.Cmp(a) != 0 {
|
||
|
t.Errorf("%v(%t): expected %s, got %s", item.input, asDec, e.String(), a.String())
|
||
|
}
|
||
|
if e, a := item.expect.Format, got.Format; e != a {
|
||
|
t.Errorf("%v: expected %#v, got %#v", item.input, e, a)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
invalid := []string{
|
||
|
"1.1.M",
|
||
|
"1+1.0M",
|
||
|
"0.1mi",
|
||
|
"0.1am",
|
||
|
"aoeu",
|
||
|
".5i",
|
||
|
"1i",
|
||
|
"-3.01i",
|
||
|
"-3.01e-",
|
||
|
|
||
|
// trailing whitespace is forbidden
|
||
|
" 1",
|
||
|
"1 ",
|
||
|
}
|
||
|
for _, item := range invalid {
|
||
|
_, err := ParseQuantity(item)
|
||
|
if err == nil {
|
||
|
t.Errorf("%v parsed unexpectedly", item)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestQuantityRoundUp(t *testing.T) {
|
||
|
table := []struct {
|
||
|
in string
|
||
|
scale Scale
|
||
|
expect Quantity
|
||
|
ok bool
|
||
|
}{
|
||
|
{"9.01", -3, decQuantity(901, -2, DecimalSI), true},
|
||
|
{"9.01", -2, decQuantity(901, -2, DecimalSI), true},
|
||
|
{"9.01", -1, decQuantity(91, -1, DecimalSI), false},
|
||
|
{"9.01", 0, decQuantity(10, 0, DecimalSI), false},
|
||
|
{"9.01", 1, decQuantity(10, 0, DecimalSI), false},
|
||
|
{"9.01", 2, decQuantity(100, 0, DecimalSI), false},
|
||
|
|
||
|
{"-9.01", -3, decQuantity(-901, -2, DecimalSI), true},
|
||
|
{"-9.01", -2, decQuantity(-901, -2, DecimalSI), true},
|
||
|
{"-9.01", -1, decQuantity(-91, -1, DecimalSI), false},
|
||
|
{"-9.01", 0, decQuantity(-10, 0, DecimalSI), false},
|
||
|
{"-9.01", 1, decQuantity(-10, 0, DecimalSI), false},
|
||
|
{"-9.01", 2, decQuantity(-100, 0, DecimalSI), false},
|
||
|
}
|
||
|
|
||
|
for _, asDec := range []bool{false, true} {
|
||
|
for _, item := range table {
|
||
|
got, err := ParseQuantity(item.in)
|
||
|
if err != nil {
|
||
|
t.Fatalf("unexpected error: %v", err)
|
||
|
}
|
||
|
expect := *item.expect.Copy()
|
||
|
if asDec {
|
||
|
got.AsDec()
|
||
|
}
|
||
|
if ok := got.RoundUp(item.scale); ok != item.ok {
|
||
|
t.Errorf("%s(%d,%t): unexpected ok: %t", item.in, item.scale, asDec, ok)
|
||
|
}
|
||
|
if got.Cmp(expect) != 0 {
|
||
|
t.Errorf("%s(%d,%t): unexpected round: %s vs %s", item.in, item.scale, asDec, got.String(), expect.String())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestQuantityCmpInt64AndDec(t *testing.T) {
|
||
|
table := []struct {
|
||
|
a, b Quantity
|
||
|
cmp int
|
||
|
}{
|
||
|
{intQuantity(901, -2, DecimalSI), intQuantity(901, -2, DecimalSI), 0},
|
||
|
{intQuantity(90, -1, DecimalSI), intQuantity(901, -2, DecimalSI), -1},
|
||
|
{intQuantity(901, -2, DecimalSI), intQuantity(900, -2, DecimalSI), 1},
|
||
|
{intQuantity(0, 0, DecimalSI), intQuantity(0, 0, DecimalSI), 0},
|
||
|
{intQuantity(0, 1, DecimalSI), intQuantity(0, -1, DecimalSI), 0},
|
||
|
{intQuantity(0, -1, DecimalSI), intQuantity(0, 1, DecimalSI), 0},
|
||
|
{intQuantity(800, -3, DecimalSI), intQuantity(1, 0, DecimalSI), -1},
|
||
|
{intQuantity(800, -3, DecimalSI), intQuantity(79, -2, DecimalSI), 1},
|
||
|
|
||
|
{intQuantity(mostPositive, 0, DecimalSI), intQuantity(1, -1, DecimalSI), 1},
|
||
|
{intQuantity(mostPositive, 1, DecimalSI), intQuantity(1, 0, DecimalSI), 1},
|
||
|
{intQuantity(mostPositive, 1, DecimalSI), intQuantity(1, 1, DecimalSI), 1},
|
||
|
{intQuantity(mostPositive, 1, DecimalSI), intQuantity(0, 1, DecimalSI), 1},
|
||
|
{intQuantity(mostPositive, -16, DecimalSI), intQuantity(1, 3, DecimalSI), -1},
|
||
|
|
||
|
{intQuantity(mostNegative, 0, DecimalSI), intQuantity(0, 0, DecimalSI), -1},
|
||
|
{intQuantity(mostNegative, -18, DecimalSI), intQuantity(-1, 0, DecimalSI), -1},
|
||
|
{intQuantity(mostNegative, -19, DecimalSI), intQuantity(-1, 0, DecimalSI), 1},
|
||
|
|
||
|
{intQuantity(1*1000000*1000000*1000000, -17, DecimalSI), intQuantity(1, 1, DecimalSI), 0},
|
||
|
{intQuantity(1*1000000*1000000*1000000, -17, DecimalSI), intQuantity(-10, 0, DecimalSI), 1},
|
||
|
{intQuantity(-1*1000000*1000000*1000000, -17, DecimalSI), intQuantity(-10, 0, DecimalSI), 0},
|
||
|
{intQuantity(1*1000000*1000000*1000000, -17, DecimalSI), intQuantity(1, 0, DecimalSI), 1},
|
||
|
|
||
|
{intQuantity(1*1000000*1000000*1000000+1, -17, DecimalSI), intQuantity(1, 1, DecimalSI), 1},
|
||
|
{intQuantity(1*1000000*1000000*1000000-1, -17, DecimalSI), intQuantity(1, 1, DecimalSI), -1},
|
||
|
}
|
||
|
|
||
|
for _, item := range table {
|
||
|
if cmp := item.a.Cmp(item.b); cmp != item.cmp {
|
||
|
t.Errorf("%#v: unexpected Cmp: %d", item, cmp)
|
||
|
}
|
||
|
if cmp := item.b.Cmp(item.a); cmp != -item.cmp {
|
||
|
t.Errorf("%#v: unexpected inverted Cmp: %d", item, cmp)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, item := range table {
|
||
|
a, b := *item.a.Copy(), *item.b.Copy()
|
||
|
a.AsDec()
|
||
|
if cmp := a.Cmp(b); cmp != item.cmp {
|
||
|
t.Errorf("%#v: unexpected Cmp: %d", item, cmp)
|
||
|
}
|
||
|
if cmp := b.Cmp(a); cmp != -item.cmp {
|
||
|
t.Errorf("%#v: unexpected inverted Cmp: %d", item, cmp)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, item := range table {
|
||
|
a, b := *item.a.Copy(), *item.b.Copy()
|
||
|
b.AsDec()
|
||
|
if cmp := a.Cmp(b); cmp != item.cmp {
|
||
|
t.Errorf("%#v: unexpected Cmp: %d", item, cmp)
|
||
|
}
|
||
|
if cmp := b.Cmp(a); cmp != -item.cmp {
|
||
|
t.Errorf("%#v: unexpected inverted Cmp: %d", item, cmp)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, item := range table {
|
||
|
a, b := *item.a.Copy(), *item.b.Copy()
|
||
|
a.AsDec()
|
||
|
b.AsDec()
|
||
|
if cmp := a.Cmp(b); cmp != item.cmp {
|
||
|
t.Errorf("%#v: unexpected Cmp: %d", item, cmp)
|
||
|
}
|
||
|
if cmp := b.Cmp(a); cmp != -item.cmp {
|
||
|
t.Errorf("%#v: unexpected inverted Cmp: %d", item, cmp)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestQuantityNeg(t *testing.T) {
|
||
|
table := []struct {
|
||
|
a Quantity
|
||
|
out string
|
||
|
}{
|
||
|
{intQuantity(901, -2, DecimalSI), "-9010m"},
|
||
|
{decQuantity(901, -2, DecimalSI), "-9010m"},
|
||
|
}
|
||
|
|
||
|
for i, item := range table {
|
||
|
out := *item.a.Copy()
|
||
|
out.Neg()
|
||
|
if out.Cmp(item.a) == 0 {
|
||
|
t.Errorf("%d: negating an item should not mutate the source: %s", i, out.String())
|
||
|
}
|
||
|
if out.String() != item.out {
|
||
|
t.Errorf("%d: negating did not equal exact value: %s", i, out.String())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestQuantityString(t *testing.T) {
|
||
|
table := []struct {
|
||
|
in Quantity
|
||
|
expect string
|
||
|
alternate string
|
||
|
}{
|
||
|
{decQuantity(1024*1024*1024, 0, BinarySI), "1Gi", "1024Mi"},
|
||
|
{decQuantity(300*1024*1024, 0, BinarySI), "300Mi", "307200Ki"},
|
||
|
{decQuantity(6*1024, 0, BinarySI), "6Ki", ""},
|
||
|
{decQuantity(1001*1024*1024*1024, 0, BinarySI), "1001Gi", "1025024Mi"},
|
||
|
{decQuantity(1024*1024*1024*1024, 0, BinarySI), "1Ti", "1024Gi"},
|
||
|
{decQuantity(5, 0, BinarySI), "5", "5000m"},
|
||
|
{decQuantity(500, -3, BinarySI), "500m", "0.5"},
|
||
|
{decQuantity(1, 9, DecimalSI), "1G", "1000M"},
|
||
|
{decQuantity(1000, 6, DecimalSI), "1G", "0.001T"},
|
||
|
{decQuantity(1000000, 3, DecimalSI), "1G", ""},
|
||
|
{decQuantity(1000000000, 0, DecimalSI), "1G", ""},
|
||
|
{decQuantity(1, -3, DecimalSI), "1m", "1000u"},
|
||
|
{decQuantity(80, -3, DecimalSI), "80m", ""},
|
||
|
{decQuantity(1080, -3, DecimalSI), "1080m", "1.08"},
|
||
|
{decQuantity(108, -2, DecimalSI), "1080m", "1080000000n"},
|
||
|
{decQuantity(10800, -4, DecimalSI), "1080m", ""},
|
||
|
{decQuantity(300, 6, DecimalSI), "300M", ""},
|
||
|
{decQuantity(1, 12, DecimalSI), "1T", ""},
|
||
|
{decQuantity(1234567, 6, DecimalSI), "1234567M", ""},
|
||
|
{decQuantity(1234567, -3, BinarySI), "1234567m", ""},
|
||
|
{decQuantity(3, 3, DecimalSI), "3k", ""},
|
||
|
{decQuantity(1025, 0, BinarySI), "1025", ""},
|
||
|
{decQuantity(0, 0, DecimalSI), "0", ""},
|
||
|
{decQuantity(0, 0, BinarySI), "0", ""},
|
||
|
{decQuantity(1, 9, DecimalExponent), "1e9", ".001e12"},
|
||
|
{decQuantity(1, -3, DecimalExponent), "1e-3", "0.001e0"},
|
||
|
{decQuantity(1, -9, DecimalExponent), "1e-9", "1000e-12"},
|
||
|
{decQuantity(80, -3, DecimalExponent), "80e-3", ""},
|
||
|
{decQuantity(300, 6, DecimalExponent), "300e6", ""},
|
||
|
{decQuantity(1, 12, DecimalExponent), "1e12", ""},
|
||
|
{decQuantity(1, 3, DecimalExponent), "1e3", ""},
|
||
|
{decQuantity(3, 3, DecimalExponent), "3e3", ""},
|
||
|
{decQuantity(3, 3, DecimalSI), "3k", ""},
|
||
|
{decQuantity(0, 0, DecimalExponent), "0", "00"},
|
||
|
{decQuantity(1, -9, DecimalSI), "1n", ""},
|
||
|
{decQuantity(80, -9, DecimalSI), "80n", ""},
|
||
|
{decQuantity(1080, -9, DecimalSI), "1080n", ""},
|
||
|
{decQuantity(108, -8, DecimalSI), "1080n", ""},
|
||
|
{decQuantity(10800, -10, DecimalSI), "1080n", ""},
|
||
|
{decQuantity(1, -6, DecimalSI), "1u", ""},
|
||
|
{decQuantity(80, -6, DecimalSI), "80u", ""},
|
||
|
{decQuantity(1080, -6, DecimalSI), "1080u", ""},
|
||
|
}
|
||
|
for _, item := range table {
|
||
|
got := item.in.String()
|
||
|
if e, a := item.expect, got; e != a {
|
||
|
t.Errorf("%#v: expected %v, got %v", item.in, e, a)
|
||
|
}
|
||
|
q, err := ParseQuantity(item.expect)
|
||
|
if err != nil {
|
||
|
t.Errorf("%#v: unexpected error: %v", item.expect, err)
|
||
|
}
|
||
|
if len(q.s) == 0 || q.s != item.expect {
|
||
|
t.Errorf("%#v: did not copy canonical string on parse: %s", item.expect, q.s)
|
||
|
}
|
||
|
if len(item.alternate) == 0 {
|
||
|
continue
|
||
|
}
|
||
|
q, err = ParseQuantity(item.alternate)
|
||
|
if err != nil {
|
||
|
t.Errorf("%#v: unexpected error: %v", item.expect, err)
|
||
|
continue
|
||
|
}
|
||
|
if len(q.s) != 0 {
|
||
|
t.Errorf("%#v: unexpected nested string: %v", item.expect, q.s)
|
||
|
}
|
||
|
if q.String() != item.expect {
|
||
|
t.Errorf("%#v: unexpected alternate canonical: %v", item.expect, q.String())
|
||
|
}
|
||
|
if len(q.s) == 0 || q.s != item.expect {
|
||
|
t.Errorf("%#v: did not set canonical string on ToString: %s", item.expect, q.s)
|
||
|
}
|
||
|
}
|
||
|
desired := &inf.Dec{} // Avoid modifying the values in the table.
|
||
|
for _, item := range table {
|
||
|
if item.in.Cmp(Quantity{}) == 0 {
|
||
|
// Don't expect it to print "-0" ever
|
||
|
continue
|
||
|
}
|
||
|
q := item.in
|
||
|
q.d = infDecAmount{desired.Neg(q.AsDec())}
|
||
|
if e, a := "-"+item.expect, q.String(); e != a {
|
||
|
t.Errorf("%#v: expected %v, got %v", item.in, e, a)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestQuantityParseEmit(t *testing.T) {
|
||
|
table := []struct {
|
||
|
in string
|
||
|
expect string
|
||
|
}{
|
||
|
{"1Ki", "1Ki"},
|
||
|
{"1Mi", "1Mi"},
|
||
|
{"1Gi", "1Gi"},
|
||
|
{"1024Mi", "1Gi"},
|
||
|
{"1000M", "1G"},
|
||
|
{".001Ki", "1024m"},
|
||
|
{".000001Ki", "1024u"},
|
||
|
{".000000001Ki", "1024n"},
|
||
|
{".000000000001Ki", "2n"},
|
||
|
}
|
||
|
|
||
|
for _, item := range table {
|
||
|
q, err := ParseQuantity(item.in)
|
||
|
if err != nil {
|
||
|
t.Errorf("Couldn't parse %v", item.in)
|
||
|
continue
|
||
|
}
|
||
|
if e, a := item.expect, q.String(); e != a {
|
||
|
t.Errorf("%#v: expected %v, got %v", item.in, e, a)
|
||
|
}
|
||
|
}
|
||
|
for _, item := range table {
|
||
|
q, err := ParseQuantity("-" + item.in)
|
||
|
if err != nil {
|
||
|
t.Errorf("Couldn't parse %v", item.in)
|
||
|
continue
|
||
|
}
|
||
|
if q.Cmp(Quantity{}) == 0 {
|
||
|
continue
|
||
|
}
|
||
|
if e, a := "-"+item.expect, q.String(); e != a {
|
||
|
t.Errorf("%#v: expected %v, got %v (%#v)", item.in, e, a, q.i)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var fuzzer = fuzz.New().Funcs(
|
||
|
func(q *Quantity, c fuzz.Continue) {
|
||
|
q.i = Zero
|
||
|
if c.RandBool() {
|
||
|
q.Format = BinarySI
|
||
|
if c.RandBool() {
|
||
|
dec := &inf.Dec{}
|
||
|
q.d = infDecAmount{Dec: dec}
|
||
|
dec.SetScale(0)
|
||
|
dec.SetUnscaled(c.Int63())
|
||
|
return
|
||
|
}
|
||
|
// Be sure to test cases like 1Mi
|
||
|
dec := &inf.Dec{}
|
||
|
q.d = infDecAmount{Dec: dec}
|
||
|
dec.SetScale(0)
|
||
|
dec.SetUnscaled(c.Int63n(1024) << uint(10*c.Intn(5)))
|
||
|
return
|
||
|
}
|
||
|
if c.RandBool() {
|
||
|
q.Format = DecimalSI
|
||
|
} else {
|
||
|
q.Format = DecimalExponent
|
||
|
}
|
||
|
if c.RandBool() {
|
||
|
dec := &inf.Dec{}
|
||
|
q.d = infDecAmount{Dec: dec}
|
||
|
dec.SetScale(inf.Scale(c.Intn(4)))
|
||
|
dec.SetUnscaled(c.Int63())
|
||
|
return
|
||
|
}
|
||
|
// Be sure to test cases like 1M
|
||
|
dec := &inf.Dec{}
|
||
|
q.d = infDecAmount{Dec: dec}
|
||
|
dec.SetScale(inf.Scale(3 - c.Intn(15)))
|
||
|
dec.SetUnscaled(c.Int63n(1000))
|
||
|
},
|
||
|
)
|
||
|
|
||
|
func TestJSON(t *testing.T) {
|
||
|
for i := 0; i < 500; i++ {
|
||
|
q := &Quantity{}
|
||
|
fuzzer.Fuzz(q)
|
||
|
b, err := json.Marshal(q)
|
||
|
if err != nil {
|
||
|
t.Errorf("error encoding %v: %v", q, err)
|
||
|
continue
|
||
|
}
|
||
|
q2 := &Quantity{}
|
||
|
err = json.Unmarshal(b, q2)
|
||
|
if err != nil {
|
||
|
t.Logf("%d: %s", i, string(b))
|
||
|
t.Errorf("%v: error decoding %v: %v", q, string(b), err)
|
||
|
}
|
||
|
if q2.Cmp(*q) != 0 {
|
||
|
t.Errorf("Expected equal: %v, %v (json was '%v')", q, q2, string(b))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestJSONWhitespace(t *testing.T) {
|
||
|
q := Quantity{}
|
||
|
testCases := []struct {
|
||
|
in string
|
||
|
expect string
|
||
|
}{
|
||
|
{`" 1"`, "1"},
|
||
|
{`"1 "`, "1"},
|
||
|
{`1`, "1"},
|
||
|
{` 1`, "1"},
|
||
|
{`1 `, "1"},
|
||
|
{`10`, "10"},
|
||
|
{`-1`, "-1"},
|
||
|
{` -1`, "-1"},
|
||
|
}
|
||
|
for _, test := range testCases {
|
||
|
if err := json.Unmarshal([]byte(test.in), &q); err != nil {
|
||
|
t.Errorf("%q: %v", test.in, err)
|
||
|
}
|
||
|
if q.String() != test.expect {
|
||
|
t.Errorf("unexpected string: %q", q.String())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestMilliNewSet(t *testing.T) {
|
||
|
table := []struct {
|
||
|
value int64
|
||
|
format Format
|
||
|
expect string
|
||
|
exact bool
|
||
|
}{
|
||
|
{1, DecimalSI, "1m", true},
|
||
|
{1000, DecimalSI, "1", true},
|
||
|
{1234000, DecimalSI, "1234", true},
|
||
|
{1024, BinarySI, "1024m", false}, // Format changes
|
||
|
{1000000, "invalidFormatDefaultsToExponent", "1e3", true},
|
||
|
{1024 * 1024, BinarySI, "1048576m", false}, // Format changes
|
||
|
}
|
||
|
|
||
|
for _, item := range table {
|
||
|
q := NewMilliQuantity(item.value, item.format)
|
||
|
if e, a := item.expect, q.String(); e != a {
|
||
|
t.Errorf("Expected %v, got %v; %#v", e, a, q)
|
||
|
}
|
||
|
if !item.exact {
|
||
|
continue
|
||
|
}
|
||
|
q2, err := ParseQuantity(q.String())
|
||
|
if err != nil {
|
||
|
t.Errorf("Round trip failed on %v", q)
|
||
|
}
|
||
|
if e, a := item.value, q2.MilliValue(); e != a {
|
||
|
t.Errorf("Expected %v, got %v", e, a)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, item := range table {
|
||
|
q := NewQuantity(0, item.format)
|
||
|
q.SetMilli(item.value)
|
||
|
if e, a := item.expect, q.String(); e != a {
|
||
|
t.Errorf("Set: Expected %v, got %v; %#v", e, a, q)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestNewSet(t *testing.T) {
|
||
|
table := []struct {
|
||
|
value int64
|
||
|
format Format
|
||
|
expect string
|
||
|
}{
|
||
|
{1, DecimalSI, "1"},
|
||
|
{1000, DecimalSI, "1k"},
|
||
|
{1234000, DecimalSI, "1234k"},
|
||
|
{1024, BinarySI, "1Ki"},
|
||
|
{1000000, "invalidFormatDefaultsToExponent", "1e6"},
|
||
|
{1024 * 1024, BinarySI, "1Mi"},
|
||
|
}
|
||
|
|
||
|
for _, asDec := range []bool{false, true} {
|
||
|
for _, item := range table {
|
||
|
q := NewQuantity(item.value, item.format)
|
||
|
if asDec {
|
||
|
q.ToDec()
|
||
|
}
|
||
|
if e, a := item.expect, q.String(); e != a {
|
||
|
t.Errorf("Expected %v, got %v; %#v", e, a, q)
|
||
|
}
|
||
|
q2, err := ParseQuantity(q.String())
|
||
|
if err != nil {
|
||
|
t.Errorf("Round trip failed on %v", q)
|
||
|
}
|
||
|
if e, a := item.value, q2.Value(); e != a {
|
||
|
t.Errorf("Expected %v, got %v", e, a)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, item := range table {
|
||
|
q := NewQuantity(0, item.format)
|
||
|
q.Set(item.value)
|
||
|
if asDec {
|
||
|
q.ToDec()
|
||
|
}
|
||
|
if e, a := item.expect, q.String(); e != a {
|
||
|
t.Errorf("Set: Expected %v, got %v; %#v", e, a, q)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestNewScaledSet(t *testing.T) {
|
||
|
table := []struct {
|
||
|
value int64
|
||
|
scale Scale
|
||
|
expect string
|
||
|
}{
|
||
|
{1, Nano, "1n"},
|
||
|
{1000, Nano, "1u"},
|
||
|
{1, Micro, "1u"},
|
||
|
{1000, Micro, "1m"},
|
||
|
{1, Milli, "1m"},
|
||
|
{1000, Milli, "1"},
|
||
|
{1, 0, "1"},
|
||
|
{0, Nano, "0"},
|
||
|
{0, Micro, "0"},
|
||
|
{0, Milli, "0"},
|
||
|
{0, 0, "0"},
|
||
|
}
|
||
|
|
||
|
for _, item := range table {
|
||
|
q := NewScaledQuantity(item.value, item.scale)
|
||
|
if e, a := item.expect, q.String(); e != a {
|
||
|
t.Errorf("Expected %v, got %v; %#v", e, a, q)
|
||
|
}
|
||
|
q2, err := ParseQuantity(q.String())
|
||
|
if err != nil {
|
||
|
t.Errorf("Round trip failed on %v", q)
|
||
|
}
|
||
|
if e, a := item.value, q2.ScaledValue(item.scale); e != a {
|
||
|
t.Errorf("Expected %v, got %v", e, a)
|
||
|
}
|
||
|
q3 := NewQuantity(0, DecimalSI)
|
||
|
q3.SetScaled(item.value, item.scale)
|
||
|
if q.Cmp(*q3) != 0 {
|
||
|
t.Errorf("Expected %v and %v to be equal", q, q3)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestScaledValue(t *testing.T) {
|
||
|
table := []struct {
|
||
|
fromScale Scale
|
||
|
toScale Scale
|
||
|
expected int64
|
||
|
}{
|
||
|
{Nano, Nano, 1},
|
||
|
{Nano, Micro, 1},
|
||
|
{Nano, Milli, 1},
|
||
|
{Nano, 0, 1},
|
||
|
{Micro, Nano, 1000},
|
||
|
{Micro, Micro, 1},
|
||
|
{Micro, Milli, 1},
|
||
|
{Micro, 0, 1},
|
||
|
{Milli, Nano, 1000 * 1000},
|
||
|
{Milli, Micro, 1000},
|
||
|
{Milli, Milli, 1},
|
||
|
{Milli, 0, 1},
|
||
|
{0, Nano, 1000 * 1000 * 1000},
|
||
|
{0, Micro, 1000 * 1000},
|
||
|
{0, Milli, 1000},
|
||
|
{0, 0, 1},
|
||
|
}
|
||
|
|
||
|
for _, item := range table {
|
||
|
q := NewScaledQuantity(1, item.fromScale)
|
||
|
if e, a := item.expected, q.ScaledValue(item.toScale); e != a {
|
||
|
t.Errorf("%v to %v: Expected %v, got %v", item.fromScale, item.toScale, e, a)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestUninitializedNoCrash(t *testing.T) {
|
||
|
var q Quantity
|
||
|
|
||
|
q.Value()
|
||
|
q.MilliValue()
|
||
|
q.Copy()
|
||
|
_ = q.String()
|
||
|
q.MarshalJSON()
|
||
|
}
|
||
|
|
||
|
func TestCopy(t *testing.T) {
|
||
|
q := NewQuantity(5, DecimalSI)
|
||
|
c := q.Copy()
|
||
|
c.Set(6)
|
||
|
if q.Value() == 6 {
|
||
|
t.Errorf("Copy didn't")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestQFlagSet(t *testing.T) {
|
||
|
qf := qFlag{&Quantity{}}
|
||
|
qf.Set("1Ki")
|
||
|
if e, a := "1Ki", qf.String(); e != a {
|
||
|
t.Errorf("Unexpected result %v != %v", e, a)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestQFlagIsPFlag(t *testing.T) {
|
||
|
var pfv pflag.Value = qFlag{}
|
||
|
if e, a := "quantity", pfv.Type(); e != a {
|
||
|
t.Errorf("Unexpected result %v != %v", e, a)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestSub(t *testing.T) {
|
||
|
tests := []struct {
|
||
|
a Quantity
|
||
|
b Quantity
|
||
|
expected Quantity
|
||
|
}{
|
||
|
{decQuantity(10, 0, DecimalSI), decQuantity(1, 1, DecimalSI), decQuantity(0, 0, DecimalSI)},
|
||
|
{decQuantity(10, 0, DecimalSI), decQuantity(1, 0, BinarySI), decQuantity(9, 0, DecimalSI)},
|
||
|
{decQuantity(10, 0, BinarySI), decQuantity(1, 0, DecimalSI), decQuantity(9, 0, BinarySI)},
|
||
|
{Quantity{Format: DecimalSI}, decQuantity(50, 0, DecimalSI), decQuantity(-50, 0, DecimalSI)},
|
||
|
{decQuantity(50, 0, DecimalSI), Quantity{Format: DecimalSI}, decQuantity(50, 0, DecimalSI)},
|
||
|
{Quantity{Format: DecimalSI}, Quantity{Format: DecimalSI}, decQuantity(0, 0, DecimalSI)},
|
||
|
}
|
||
|
|
||
|
for i, test := range tests {
|
||
|
test.a.Sub(test.b)
|
||
|
if test.a.Cmp(test.expected) != 0 {
|
||
|
t.Errorf("[%d] Expected %q, got %q", i, test.expected.String(), test.a.String())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestNeg(t *testing.T) {
|
||
|
tests := []struct {
|
||
|
a Quantity
|
||
|
b Quantity
|
||
|
expected Quantity
|
||
|
}{
|
||
|
{a: intQuantity(0, 0, DecimalSI), expected: intQuantity(0, 0, DecimalSI)},
|
||
|
{a: Quantity{}, expected: Quantity{}},
|
||
|
{a: intQuantity(10, 0, BinarySI), expected: intQuantity(-10, 0, BinarySI)},
|
||
|
{a: intQuantity(-10, 0, BinarySI), expected: intQuantity(10, 0, BinarySI)},
|
||
|
{a: decQuantity(0, 0, DecimalSI), expected: intQuantity(0, 0, DecimalSI)},
|
||
|
{a: decQuantity(10, 0, BinarySI), expected: intQuantity(-10, 0, BinarySI)},
|
||
|
{a: decQuantity(-10, 0, BinarySI), expected: intQuantity(10, 0, BinarySI)},
|
||
|
}
|
||
|
|
||
|
for i, test := range tests {
|
||
|
a := test.a.Copy()
|
||
|
a.Neg()
|
||
|
// ensure value is same
|
||
|
if a.Cmp(test.expected) != 0 {
|
||
|
t.Errorf("[%d] Expected %q, got %q", i, test.expected.String(), a.String())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestAdd(t *testing.T) {
|
||
|
tests := []struct {
|
||
|
a Quantity
|
||
|
b Quantity
|
||
|
expected Quantity
|
||
|
}{
|
||
|
{decQuantity(10, 0, DecimalSI), decQuantity(1, 1, DecimalSI), decQuantity(20, 0, DecimalSI)},
|
||
|
{decQuantity(10, 0, DecimalSI), decQuantity(1, 0, BinarySI), decQuantity(11, 0, DecimalSI)},
|
||
|
{decQuantity(10, 0, BinarySI), decQuantity(1, 0, DecimalSI), decQuantity(11, 0, BinarySI)},
|
||
|
{Quantity{Format: DecimalSI}, decQuantity(50, 0, DecimalSI), decQuantity(50, 0, DecimalSI)},
|
||
|
{decQuantity(50, 0, DecimalSI), Quantity{Format: DecimalSI}, decQuantity(50, 0, DecimalSI)},
|
||
|
{Quantity{Format: DecimalSI}, Quantity{Format: DecimalSI}, decQuantity(0, 0, DecimalSI)},
|
||
|
}
|
||
|
|
||
|
for i, test := range tests {
|
||
|
test.a.Add(test.b)
|
||
|
if test.a.Cmp(test.expected) != 0 {
|
||
|
t.Errorf("[%d] Expected %q, got %q", i, test.expected.String(), test.a.String())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestAddSubRoundTrip(t *testing.T) {
|
||
|
for k := -10; k <= 10; k++ {
|
||
|
q := Quantity{Format: DecimalSI}
|
||
|
var order []int64
|
||
|
for i := 0; i < 100; i++ {
|
||
|
j := rand.Int63()
|
||
|
order = append(order, j)
|
||
|
q.Add(*NewScaledQuantity(j, Scale(k)))
|
||
|
}
|
||
|
for _, j := range order {
|
||
|
q.Sub(*NewScaledQuantity(j, Scale(k)))
|
||
|
}
|
||
|
if !q.IsZero() {
|
||
|
t.Errorf("addition and subtraction did not cancel: %s", &q)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestAddSubRoundTripAcrossScales(t *testing.T) {
|
||
|
q := Quantity{Format: DecimalSI}
|
||
|
var order []int64
|
||
|
for i := 0; i < 100; i++ {
|
||
|
j := rand.Int63()
|
||
|
order = append(order, j)
|
||
|
q.Add(*NewScaledQuantity(j, Scale(j%20-10)))
|
||
|
}
|
||
|
for _, j := range order {
|
||
|
q.Sub(*NewScaledQuantity(j, Scale(j%20-10)))
|
||
|
}
|
||
|
if !q.IsZero() {
|
||
|
t.Errorf("addition and subtraction did not cancel: %s", &q)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestNegateRoundTrip(t *testing.T) {
|
||
|
for _, asDec := range []bool{false, true} {
|
||
|
for k := -10; k <= 10; k++ {
|
||
|
for i := 0; i < 100; i++ {
|
||
|
j := rand.Int63()
|
||
|
q := *NewScaledQuantity(j, Scale(k))
|
||
|
if asDec {
|
||
|
q.AsDec()
|
||
|
}
|
||
|
|
||
|
b := q.Copy()
|
||
|
b.Neg()
|
||
|
b.Neg()
|
||
|
if b.Cmp(q) != 0 {
|
||
|
t.Errorf("double negation did not cancel: %s", &q)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
func benchmarkQuantities() []Quantity {
|
||
|
return []Quantity{
|
||
|
intQuantity(1024*1024*1024, 0, BinarySI),
|
||
|
intQuantity(1024*1024*1024*1024, 0, BinarySI),
|
||
|
intQuantity(1000000, 3, DecimalSI),
|
||
|
intQuantity(1000000000, 0, DecimalSI),
|
||
|
intQuantity(1, -3, DecimalSI),
|
||
|
intQuantity(80, -3, DecimalSI),
|
||
|
intQuantity(1080, -3, DecimalSI),
|
||
|
intQuantity(0, 0, BinarySI),
|
||
|
intQuantity(1, 9, DecimalExponent),
|
||
|
intQuantity(1, -9, DecimalSI),
|
||
|
intQuantity(1000000, 10, DecimalSI),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func BenchmarkQuantityString(b *testing.B) {
|
||
|
values := benchmarkQuantities()
|
||
|
b.ResetTimer()
|
||
|
var s string
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
q := values[i%len(values)]
|
||
|
q.s = ""
|
||
|
s = q.String()
|
||
|
}
|
||
|
b.StopTimer()
|
||
|
if len(s) == 0 {
|
||
|
b.Fatal(s)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func BenchmarkQuantityStringPrecalc(b *testing.B) {
|
||
|
values := benchmarkQuantities()
|
||
|
for i := range values {
|
||
|
_ = values[i].String()
|
||
|
}
|
||
|
b.ResetTimer()
|
||
|
var s string
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
q := values[i%len(values)]
|
||
|
s = q.String()
|
||
|
}
|
||
|
b.StopTimer()
|
||
|
if len(s) == 0 {
|
||
|
b.Fatal(s)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func BenchmarkQuantityStringBinarySI(b *testing.B) {
|
||
|
values := benchmarkQuantities()
|
||
|
for i := range values {
|
||
|
values[i].Format = BinarySI
|
||
|
}
|
||
|
b.ResetTimer()
|
||
|
var s string
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
q := values[i%len(values)]
|
||
|
q.s = ""
|
||
|
s = q.String()
|
||
|
}
|
||
|
b.StopTimer()
|
||
|
if len(s) == 0 {
|
||
|
b.Fatal(s)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func BenchmarkQuantityMarshalJSON(b *testing.B) {
|
||
|
values := benchmarkQuantities()
|
||
|
b.ResetTimer()
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
q := values[i%len(values)]
|
||
|
q.s = ""
|
||
|
if _, err := q.MarshalJSON(); err != nil {
|
||
|
b.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
b.StopTimer()
|
||
|
}
|
||
|
|
||
|
func BenchmarkQuantityUnmarshalJSON(b *testing.B) {
|
||
|
values := benchmarkQuantities()
|
||
|
var json [][]byte
|
||
|
for _, v := range values {
|
||
|
data, _ := v.MarshalJSON()
|
||
|
json = append(json, data)
|
||
|
}
|
||
|
|
||
|
b.ResetTimer()
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
var q Quantity
|
||
|
if err := q.UnmarshalJSON(json[i%len(values)]); err != nil {
|
||
|
b.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
b.StopTimer()
|
||
|
}
|
||
|
|
||
|
func BenchmarkParseQuantity(b *testing.B) {
|
||
|
values := benchmarkQuantities()
|
||
|
var strings []string
|
||
|
for _, v := range values {
|
||
|
strings = append(strings, v.String())
|
||
|
}
|
||
|
b.ResetTimer()
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
if _, err := ParseQuantity(strings[i%len(values)]); err != nil {
|
||
|
b.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
b.StopTimer()
|
||
|
}
|
||
|
|
||
|
func BenchmarkCanonicalize(b *testing.B) {
|
||
|
values := benchmarkQuantities()
|
||
|
b.ResetTimer()
|
||
|
buffer := make([]byte, 0, 100)
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
s, _ := values[i%len(values)].CanonicalizeBytes(buffer)
|
||
|
if len(s) == 0 {
|
||
|
b.Fatal(s)
|
||
|
}
|
||
|
}
|
||
|
b.StopTimer()
|
||
|
}
|
||
|
|
||
|
func BenchmarkQuantityRoundUp(b *testing.B) {
|
||
|
values := benchmarkQuantities()
|
||
|
b.ResetTimer()
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
q := values[i%len(values)]
|
||
|
copied := q
|
||
|
copied.RoundUp(-3)
|
||
|
}
|
||
|
b.StopTimer()
|
||
|
}
|
||
|
|
||
|
func BenchmarkQuantityCopy(b *testing.B) {
|
||
|
values := benchmarkQuantities()
|
||
|
b.ResetTimer()
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
values[i%len(values)].Copy()
|
||
|
}
|
||
|
b.StopTimer()
|
||
|
}
|
||
|
|
||
|
func BenchmarkQuantityAdd(b *testing.B) {
|
||
|
values := benchmarkQuantities()
|
||
|
base := &Quantity{}
|
||
|
b.ResetTimer()
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
q := values[i%len(values)]
|
||
|
base.d.Dec = nil
|
||
|
base.i = int64Amount{value: 100}
|
||
|
base.Add(q)
|
||
|
}
|
||
|
b.StopTimer()
|
||
|
}
|
||
|
|
||
|
func BenchmarkQuantityCmp(b *testing.B) {
|
||
|
values := benchmarkQuantities()
|
||
|
b.ResetTimer()
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
q := values[i%len(values)]
|
||
|
if q.Cmp(q) != 0 {
|
||
|
b.Fatal(q)
|
||
|
}
|
||
|
}
|
||
|
b.StopTimer()
|
||
|
}
|