mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 13:50:01 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			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()
 | |
| }
 |