github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/api/resource/quantity_test.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package resource 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "math" 23 "math/rand" 24 "os" 25 "strings" 26 "testing" 27 "unicode" 28 29 fuzz "github.com/google/gofuzz" 30 "github.com/spf13/pflag" 31 32 inf "gopkg.in/inf.v0" 33 ) 34 35 func dec(i int64, exponent int) infDecAmount { 36 // See the below test-- scale is the negative of an exponent. 37 return infDecAmount{inf.NewDec(i, inf.Scale(-exponent))} 38 } 39 40 func decQuantity(i int64, exponent int, format Format) Quantity { 41 return Quantity{d: dec(i, exponent), Format: format} 42 } 43 44 func intQuantity(i int64, exponent Scale, format Format) Quantity { 45 return Quantity{i: int64Amount{value: i, scale: exponent}, Format: format} 46 } 47 48 func TestDec(t *testing.T) { 49 table := []struct { 50 got infDecAmount 51 expect string 52 }{ 53 {dec(1, 0), "1"}, 54 {dec(1, 1), "10"}, 55 {dec(5, 2), "500"}, 56 {dec(8, 3), "8000"}, 57 {dec(2, 0), "2"}, 58 {dec(1, -1), "0.1"}, 59 {dec(3, -2), "0.03"}, 60 {dec(4, -3), "0.004"}, 61 } 62 63 for _, item := range table { 64 if e, a := item.expect, item.got.Dec.String(); e != a { 65 t.Errorf("expected %v, got %v", e, a) 66 } 67 } 68 } 69 70 // TestQuantityParseZero ensures that when a 0 quantity is passed, its string value is 0 71 func TestQuantityParseZero(t *testing.T) { 72 zero := MustParse("0") 73 if expected, actual := "0", zero.String(); expected != actual { 74 t.Errorf("Expected %v, actual %v", expected, actual) 75 } 76 } 77 78 // TestQuantityParseNonNumericPanic ensures that when a non-numeric string is parsed 79 // it panics 80 func TestQuantityParseNonNumericPanic(t *testing.T) { 81 defer func() { 82 if r := recover(); r == nil { 83 t.Errorf("MustParse did not panic") 84 } 85 }() 86 _ = MustParse("Non-Numeric") 87 } 88 89 // TestQuantityAddZeroPreservesSuffix verifies that a suffix is preserved 90 // independent of the order of operations when adding a zero and non-zero val 91 func TestQuantityAddZeroPreservesSuffix(t *testing.T) { 92 testValues := []string{"100m", "1Gi"} 93 zero := MustParse("0") 94 for _, testValue := range testValues { 95 value := MustParse(testValue) 96 v1 := value.DeepCopy() 97 // ensure non-zero + zero = non-zero (suffix preserved) 98 v1.Add(zero) 99 // ensure zero + non-zero = non-zero (suffix preserved) 100 v2 := zero.DeepCopy() 101 v2.Add(value) 102 103 if v1.String() != testValue { 104 t.Errorf("Expected %v, actual %v", testValue, v1.String()) 105 continue 106 } 107 if v2.String() != testValue { 108 t.Errorf("Expected %v, actual %v", testValue, v2.String()) 109 } 110 } 111 } 112 113 // TestQuantitySubZeroPreservesSuffix verifies that a suffix is preserved 114 // independent of the order of operations when subtracting a zero and non-zero val 115 func TestQuantitySubZeroPreservesSuffix(t *testing.T) { 116 testValues := []string{"100m", "1Gi"} 117 zero := MustParse("0") 118 for _, testValue := range testValues { 119 value := MustParse(testValue) 120 v1 := value.DeepCopy() 121 // ensure non-zero - zero = non-zero (suffix preserved) 122 v1.Sub(zero) 123 // ensure we preserved the input value 124 if v1.String() != testValue { 125 t.Errorf("Expected %v, actual %v", testValue, v1.String()) 126 } 127 128 // ensure zero - non-zero = -non-zero (suffix preserved) 129 v2 := zero.DeepCopy() 130 v2.Sub(value) 131 negVal := value.DeepCopy() 132 negVal.Neg() 133 if v2.String() != negVal.String() { 134 t.Errorf("Expected %v, actual %v", negVal.String(), v2.String()) 135 } 136 } 137 } 138 139 // TestQuantityCanocicalizeZero verifies that you get 0 as canonical value if internal value is 0, and not 0<suffix> 140 func TestQuantityCanocicalizeZero(t *testing.T) { 141 val := MustParse("1000m") 142 val.i.Sub(int64Amount{value: 1}) 143 zero := Quantity{i: val.i, Format: DecimalSI} 144 if expected, actual := "0", zero.String(); expected != actual { 145 t.Errorf("Expected %v, actual %v", expected, actual) 146 } 147 } 148 149 func TestQuantityCmp(t *testing.T) { 150 // Test when d is nil 151 table := []struct { 152 x string 153 y string 154 expect int 155 }{ 156 {"0", "0", 0}, 157 {"100m", "50m", 1}, 158 {"50m", "100m", -1}, 159 {"10000T", "100Gi", 1}, 160 } 161 for _, testCase := range table { 162 q1 := MustParse(testCase.x) 163 q2 := MustParse(testCase.y) 164 if result := q1.Cmp(q2); result != testCase.expect { 165 t.Errorf("X: %v, Y: %v, Expected: %v, Actual: %v", testCase.x, testCase.y, testCase.expect, result) 166 } 167 } 168 // Test when i is {0,0} 169 table2 := []struct { 170 x *inf.Dec 171 y *inf.Dec 172 expect int 173 }{ 174 {dec(0, 0).Dec, dec(0, 0).Dec, 0}, 175 {nil, dec(0, 0).Dec, 0}, 176 {dec(0, 0).Dec, nil, 0}, 177 {nil, nil, 0}, 178 {nil, dec(10, 0).Dec, -1}, 179 {nil, dec(-10, 0).Dec, 1}, 180 {dec(10, 0).Dec, nil, 1}, 181 {dec(-10, 0).Dec, nil, -1}, 182 } 183 for _, testCase := range table2 { 184 q1 := Quantity{d: infDecAmount{testCase.x}, Format: DecimalSI} 185 q2 := Quantity{d: infDecAmount{testCase.y}, Format: DecimalSI} 186 if result := q1.Cmp(q2); result != testCase.expect { 187 t.Errorf("X: %v, Y: %v, Expected: %v, Actual: %v", testCase.x, testCase.y, testCase.expect, result) 188 } 189 } 190 } 191 192 func TestParseQuantityString(t *testing.T) { 193 table := []struct { 194 input string 195 positive bool 196 value string 197 num, denom, suffix string 198 }{ 199 {"0.025Ti", true, "0.025", "0", "025", "Ti"}, 200 {"1.025Ti", true, "1.025", "1", "025", "Ti"}, 201 {"-1.025Ti", false, "-1.025", "1", "025", "Ti"}, 202 {".", true, ".", "0", "", ""}, 203 {"-.", false, "-.", "0", "", ""}, 204 {"1E-3", true, "1", "1", "", "E-3"}, 205 } 206 for _, test := range table { 207 positive, value, num, denom, suffix, err := parseQuantityString(test.input) 208 if err != nil { 209 t.Errorf("%s: error: %v", test.input, err) 210 continue 211 } 212 if positive != test.positive || value != test.value || num != test.num || denom != test.denom || suffix != test.suffix { 213 t.Errorf("%s: unmatched: %t %q %q %q %q", test.input, positive, value, num, denom, suffix) 214 } 215 } 216 } 217 218 func TestQuantityParse(t *testing.T) { 219 if _, err := ParseQuantity(""); err == nil { 220 t.Errorf("expected empty string to return error") 221 } 222 223 table := []struct { 224 input string 225 expect Quantity 226 }{ 227 {"0", decQuantity(0, 0, DecimalSI)}, 228 {"0n", decQuantity(0, 0, DecimalSI)}, 229 {"0u", decQuantity(0, 0, DecimalSI)}, 230 {"0m", decQuantity(0, 0, DecimalSI)}, 231 {"0Ki", decQuantity(0, 0, BinarySI)}, 232 {"0k", decQuantity(0, 0, DecimalSI)}, 233 {"0Mi", decQuantity(0, 0, BinarySI)}, 234 {"0M", decQuantity(0, 0, DecimalSI)}, 235 {"0Gi", decQuantity(0, 0, BinarySI)}, 236 {"0G", decQuantity(0, 0, DecimalSI)}, 237 {"0Ti", decQuantity(0, 0, BinarySI)}, 238 {"0T", decQuantity(0, 0, DecimalSI)}, 239 240 // Quantity less numbers are allowed 241 {"1", decQuantity(1, 0, DecimalSI)}, 242 243 // Binary suffixes 244 {"1Ki", decQuantity(1024, 0, BinarySI)}, 245 {"8Ki", decQuantity(8*1024, 0, BinarySI)}, 246 {"7Mi", decQuantity(7*1024*1024, 0, BinarySI)}, 247 {"6Gi", decQuantity(6*1024*1024*1024, 0, BinarySI)}, 248 {"5Ti", decQuantity(5*1024*1024*1024*1024, 0, BinarySI)}, 249 {"4Pi", decQuantity(4*1024*1024*1024*1024*1024, 0, BinarySI)}, 250 {"3Ei", decQuantity(3*1024*1024*1024*1024*1024*1024, 0, BinarySI)}, 251 252 {"10Ti", decQuantity(10*1024*1024*1024*1024, 0, BinarySI)}, 253 {"100Ti", decQuantity(100*1024*1024*1024*1024, 0, BinarySI)}, 254 255 // Decimal suffixes 256 {"5n", decQuantity(5, -9, DecimalSI)}, 257 {"4u", decQuantity(4, -6, DecimalSI)}, 258 {"3m", decQuantity(3, -3, DecimalSI)}, 259 {"9", decQuantity(9, 0, DecimalSI)}, 260 {"8k", decQuantity(8, 3, DecimalSI)}, 261 {"50k", decQuantity(5, 4, DecimalSI)}, 262 {"7M", decQuantity(7, 6, DecimalSI)}, 263 {"6G", decQuantity(6, 9, DecimalSI)}, 264 {"5T", decQuantity(5, 12, DecimalSI)}, 265 {"40T", decQuantity(4, 13, DecimalSI)}, 266 {"300T", decQuantity(3, 14, DecimalSI)}, 267 {"2P", decQuantity(2, 15, DecimalSI)}, 268 {"1E", decQuantity(1, 18, DecimalSI)}, 269 270 // Decimal exponents 271 {"1E-3", decQuantity(1, -3, DecimalExponent)}, 272 {"1e3", decQuantity(1, 3, DecimalExponent)}, 273 {"1E6", decQuantity(1, 6, DecimalExponent)}, 274 {"1e9", decQuantity(1, 9, DecimalExponent)}, 275 {"1E12", decQuantity(1, 12, DecimalExponent)}, 276 {"1e15", decQuantity(1, 15, DecimalExponent)}, 277 {"1E18", decQuantity(1, 18, DecimalExponent)}, 278 279 // Nonstandard but still parsable 280 {"1e14", decQuantity(1, 14, DecimalExponent)}, 281 {"1e13", decQuantity(1, 13, DecimalExponent)}, 282 {"1e3", decQuantity(1, 3, DecimalExponent)}, 283 {"100.035k", decQuantity(100035, 0, DecimalSI)}, 284 285 // Things that look like floating point 286 {"0.001", decQuantity(1, -3, DecimalSI)}, 287 {"0.0005k", decQuantity(5, -1, DecimalSI)}, 288 {"0.005", decQuantity(5, -3, DecimalSI)}, 289 {"0.05", decQuantity(5, -2, DecimalSI)}, 290 {"0.5", decQuantity(5, -1, DecimalSI)}, 291 {"0.00050k", decQuantity(5, -1, DecimalSI)}, 292 {"0.00500", decQuantity(5, -3, DecimalSI)}, 293 {"0.05000", decQuantity(5, -2, DecimalSI)}, 294 {"0.50000", decQuantity(5, -1, DecimalSI)}, 295 {"0.5e0", decQuantity(5, -1, DecimalExponent)}, 296 {"0.5e-1", decQuantity(5, -2, DecimalExponent)}, 297 {"0.5e-2", decQuantity(5, -3, DecimalExponent)}, 298 {"0.5e0", decQuantity(5, -1, DecimalExponent)}, 299 {"10.035M", decQuantity(10035, 3, DecimalSI)}, 300 301 {"1.2e3", decQuantity(12, 2, DecimalExponent)}, 302 {"1.3E+6", decQuantity(13, 5, DecimalExponent)}, 303 {"1.40e9", decQuantity(14, 8, DecimalExponent)}, 304 {"1.53E12", decQuantity(153, 10, DecimalExponent)}, 305 {"1.6e15", decQuantity(16, 14, DecimalExponent)}, 306 {"1.7E18", decQuantity(17, 17, DecimalExponent)}, 307 308 {"9.01", decQuantity(901, -2, DecimalSI)}, 309 {"8.1k", decQuantity(81, 2, DecimalSI)}, 310 {"7.123456M", decQuantity(7123456, 0, DecimalSI)}, 311 {"6.987654321G", decQuantity(6987654321, 0, DecimalSI)}, 312 {"5.444T", decQuantity(5444, 9, DecimalSI)}, 313 {"40.1T", decQuantity(401, 11, DecimalSI)}, 314 {"300.2T", decQuantity(3002, 11, DecimalSI)}, 315 {"2.5P", decQuantity(25, 14, DecimalSI)}, 316 {"1.01E", decQuantity(101, 16, DecimalSI)}, 317 318 // Things that saturate/round 319 {"3.001n", decQuantity(4, -9, DecimalSI)}, 320 {"1.1E-9", decQuantity(2, -9, DecimalExponent)}, 321 {"0.0000000001", decQuantity(1, -9, DecimalSI)}, 322 {"0.0000000005", decQuantity(1, -9, DecimalSI)}, 323 {"0.00000000050", decQuantity(1, -9, DecimalSI)}, 324 {"0.5e-9", decQuantity(1, -9, DecimalExponent)}, 325 {"0.9n", decQuantity(1, -9, DecimalSI)}, 326 {"0.00000012345", decQuantity(124, -9, DecimalSI)}, 327 {"0.00000012354", decQuantity(124, -9, DecimalSI)}, 328 {"9Ei", Quantity{d: maxAllowed, Format: BinarySI}}, 329 {"9223372036854775807Ki", Quantity{d: maxAllowed, Format: BinarySI}}, 330 {"12E", decQuantity(12, 18, DecimalSI)}, 331 332 // We'll accept fractional binary stuff, too. 333 {"100.035Ki", decQuantity(10243584, -2, BinarySI)}, 334 {"0.5Mi", decQuantity(.5*1024*1024, 0, BinarySI)}, 335 {"0.05Gi", decQuantity(536870912, -1, BinarySI)}, 336 {"0.025Ti", decQuantity(274877906944, -1, BinarySI)}, 337 338 // Things written by trolls 339 {"0.000000000001Ki", decQuantity(2, -9, DecimalSI)}, // rounds up, changes format 340 {".001", decQuantity(1, -3, DecimalSI)}, 341 {".0001k", decQuantity(100, -3, DecimalSI)}, 342 {"1.", decQuantity(1, 0, DecimalSI)}, 343 {"1.G", decQuantity(1, 9, DecimalSI)}, 344 } 345 346 for _, asDec := range []bool{false, true} { 347 for _, item := range table { 348 got, err := ParseQuantity(item.input) 349 if err != nil { 350 t.Errorf("%v: unexpected error: %v", item.input, err) 351 continue 352 } 353 if asDec { 354 got.AsDec() 355 } 356 357 if e, a := item.expect, got; e.Cmp(a) != 0 { 358 t.Errorf("%v: expected %v, got %v", item.input, e.String(), a.String()) 359 } 360 if e, a := item.expect.Format, got.Format; e != a { 361 t.Errorf("%v: expected %#v, got %#v", item.input, e, a) 362 } 363 364 if asDec { 365 if i, ok := got.AsInt64(); i != 0 || ok { 366 t.Errorf("%v: expected inf.Dec to return false for AsInt64: %d", item.input, i) 367 } 368 continue 369 } 370 i, ok := item.expect.AsInt64() 371 if !ok { 372 continue 373 } 374 j, ok := got.AsInt64() 375 if !ok { 376 if got.d.Dec == nil && got.i.scale >= 0 { 377 t.Errorf("%v: is an int64Amount, but can't return AsInt64: %v", item.input, got) 378 } 379 continue 380 } 381 if i != j { 382 t.Errorf("%v: expected equivalent representation as int64: %d %d", item.input, i, j) 383 } 384 } 385 386 for _, item := range table { 387 got, err := ParseQuantity(item.input) 388 if err != nil { 389 t.Errorf("%v: unexpected error: %v", item.input, err) 390 continue 391 } 392 393 if asDec { 394 got.AsDec() 395 } 396 397 for _, format := range []Format{DecimalSI, BinarySI, DecimalExponent} { 398 // ensure we are not simply checking pointer equality by creating a new inf.Dec 399 var copied inf.Dec 400 copied.Add(inf.NewDec(0, inf.Scale(0)), got.AsDec()) 401 q := NewDecimalQuantity(copied, format) 402 if c := q.Cmp(got); c != 0 { 403 t.Errorf("%v: round trip from decimal back to quantity is not comparable: %d: %#v vs %#v", item.input, c, got, q) 404 } 405 } 406 407 // verify that we can decompose the input and get the same result by building up from the base. 408 positive, _, num, denom, suffix, err := parseQuantityString(item.input) 409 if err != nil { 410 t.Errorf("%v: unexpected error: %v", item.input, err) 411 continue 412 } 413 if got.Sign() >= 0 && !positive || got.Sign() < 0 && positive { 414 t.Errorf("%v: positive was incorrect: %t", item.input, positive) 415 continue 416 } 417 var value string 418 if !positive { 419 value = "-" 420 } 421 value += num 422 if len(denom) > 0 { 423 value += "." + denom 424 } 425 value += suffix 426 if len(value) == 0 { 427 t.Errorf("%v: did not parse correctly, %q %q %q", item.input, num, denom, suffix) 428 } 429 expected, err := ParseQuantity(value) 430 if err != nil { 431 t.Errorf("%v: unexpected error for %s: %v", item.input, value, err) 432 continue 433 } 434 if expected.Cmp(got) != 0 { 435 t.Errorf("%v: not the same as %s", item.input, value) 436 continue 437 } 438 } 439 440 // Try the negative version of everything 441 desired := &inf.Dec{} 442 expect := Quantity{d: infDecAmount{Dec: desired}} 443 for _, item := range table { 444 got, err := ParseQuantity("-" + strings.TrimLeftFunc(item.input, unicode.IsSpace)) 445 if err != nil { 446 t.Errorf("-%v: unexpected error: %v", item.input, err) 447 continue 448 } 449 if asDec { 450 got.AsDec() 451 } 452 453 expected := item.expect 454 desired.Neg(expected.AsDec()) 455 456 if e, a := expect, got; e.Cmp(a) != 0 { 457 t.Errorf("%v: expected %s, got %s", item.input, e.String(), a.String()) 458 } 459 if e, a := expected.Format, got.Format; e != a { 460 t.Errorf("%v: expected %#v, got %#v", item.input, e, a) 461 } 462 } 463 464 // Try everything with an explicit + 465 for _, item := range table { 466 got, err := ParseQuantity("+" + strings.TrimLeftFunc(item.input, unicode.IsSpace)) 467 if err != nil { 468 t.Errorf("-%v: unexpected error: %v", item.input, err) 469 continue 470 } 471 if asDec { 472 got.AsDec() 473 } 474 475 if e, a := item.expect, got; e.Cmp(a) != 0 { 476 t.Errorf("%v(%t): expected %s, got %s", item.input, asDec, e.String(), a.String()) 477 } 478 if e, a := item.expect.Format, got.Format; e != a { 479 t.Errorf("%v: expected %#v, got %#v", item.input, e, a) 480 } 481 } 482 } 483 484 invalid := []string{ 485 "1.1.M", 486 "1+1.0M", 487 "0.1mi", 488 "0.1am", 489 "aoeu", 490 ".5i", 491 "1i", 492 "-3.01i", 493 "-3.01e-", 494 495 // trailing whitespace is forbidden 496 " 1", 497 "1 ", 498 } 499 for _, item := range invalid { 500 _, err := ParseQuantity(item) 501 if err == nil { 502 t.Errorf("%v parsed unexpectedly", item) 503 } 504 } 505 } 506 507 func TestQuantityRoundUp(t *testing.T) { 508 table := []struct { 509 in string 510 scale Scale 511 expect Quantity 512 ok bool 513 }{ 514 {"9.01", -3, decQuantity(901, -2, DecimalSI), true}, 515 {"9.01", -2, decQuantity(901, -2, DecimalSI), true}, 516 {"9.01", -1, decQuantity(91, -1, DecimalSI), false}, 517 {"9.01", 0, decQuantity(10, 0, DecimalSI), false}, 518 {"9.01", 1, decQuantity(10, 0, DecimalSI), false}, 519 {"9.01", 2, decQuantity(100, 0, DecimalSI), false}, 520 521 {"-9.01", -3, decQuantity(-901, -2, DecimalSI), true}, 522 {"-9.01", -2, decQuantity(-901, -2, DecimalSI), true}, 523 {"-9.01", -1, decQuantity(-91, -1, DecimalSI), false}, 524 {"-9.01", 0, decQuantity(-10, 0, DecimalSI), false}, 525 {"-9.01", 1, decQuantity(-10, 0, DecimalSI), false}, 526 {"-9.01", 2, decQuantity(-100, 0, DecimalSI), false}, 527 } 528 529 for _, asDec := range []bool{false, true} { 530 for _, item := range table { 531 got, err := ParseQuantity(item.in) 532 if err != nil { 533 t.Fatalf("unexpected error: %v", err) 534 } 535 expect := item.expect.DeepCopy() 536 if asDec { 537 got.AsDec() 538 } 539 if ok := got.RoundUp(item.scale); ok != item.ok { 540 t.Errorf("%s(%d,%t): unexpected ok: %t", item.in, item.scale, asDec, ok) 541 } 542 if got.Cmp(expect) != 0 { 543 t.Errorf("%s(%d,%t): unexpected round: %s vs %s", item.in, item.scale, asDec, got.String(), expect.String()) 544 } 545 } 546 } 547 } 548 549 func TestQuantityCmpInt64AndDec(t *testing.T) { 550 table := []struct { 551 a, b Quantity 552 cmp int 553 }{ 554 {intQuantity(901, -2, DecimalSI), intQuantity(901, -2, DecimalSI), 0}, 555 {intQuantity(90, -1, DecimalSI), intQuantity(901, -2, DecimalSI), -1}, 556 {intQuantity(901, -2, DecimalSI), intQuantity(900, -2, DecimalSI), 1}, 557 {intQuantity(0, 0, DecimalSI), intQuantity(0, 0, DecimalSI), 0}, 558 {intQuantity(0, 1, DecimalSI), intQuantity(0, -1, DecimalSI), 0}, 559 {intQuantity(0, -1, DecimalSI), intQuantity(0, 1, DecimalSI), 0}, 560 {intQuantity(800, -3, DecimalSI), intQuantity(1, 0, DecimalSI), -1}, 561 {intQuantity(800, -3, DecimalSI), intQuantity(79, -2, DecimalSI), 1}, 562 563 {intQuantity(mostPositive, 0, DecimalSI), intQuantity(1, -1, DecimalSI), 1}, 564 {intQuantity(mostPositive, 1, DecimalSI), intQuantity(1, 0, DecimalSI), 1}, 565 {intQuantity(mostPositive, 1, DecimalSI), intQuantity(1, 1, DecimalSI), 1}, 566 {intQuantity(mostPositive, 1, DecimalSI), intQuantity(0, 1, DecimalSI), 1}, 567 {intQuantity(mostPositive, -16, DecimalSI), intQuantity(1, 3, DecimalSI), -1}, 568 569 {intQuantity(mostNegative, 0, DecimalSI), intQuantity(0, 0, DecimalSI), -1}, 570 {intQuantity(mostNegative, -18, DecimalSI), intQuantity(-1, 0, DecimalSI), -1}, 571 {intQuantity(mostNegative, -19, DecimalSI), intQuantity(-1, 0, DecimalSI), 1}, 572 573 {intQuantity(1*1000000*1000000*1000000, -17, DecimalSI), intQuantity(1, 1, DecimalSI), 0}, 574 {intQuantity(1*1000000*1000000*1000000, -17, DecimalSI), intQuantity(-10, 0, DecimalSI), 1}, 575 {intQuantity(-1*1000000*1000000*1000000, -17, DecimalSI), intQuantity(-10, 0, DecimalSI), 0}, 576 {intQuantity(1*1000000*1000000*1000000, -17, DecimalSI), intQuantity(1, 0, DecimalSI), 1}, 577 578 {intQuantity(1*1000000*1000000*1000000+1, -17, DecimalSI), intQuantity(1, 1, DecimalSI), 1}, 579 {intQuantity(1*1000000*1000000*1000000-1, -17, DecimalSI), intQuantity(1, 1, DecimalSI), -1}, 580 } 581 582 for _, item := range table { 583 if cmp := item.a.Cmp(item.b); cmp != item.cmp { 584 t.Errorf("%#v: unexpected Cmp: %d", item, cmp) 585 } 586 if cmp := item.b.Cmp(item.a); cmp != -item.cmp { 587 t.Errorf("%#v: unexpected inverted Cmp: %d", item, cmp) 588 } 589 } 590 591 for _, item := range table { 592 a, b := item.a.DeepCopy(), item.b.DeepCopy() 593 a.AsDec() 594 if cmp := a.Cmp(b); cmp != item.cmp { 595 t.Errorf("%#v: unexpected Cmp: %d", item, cmp) 596 } 597 if cmp := b.Cmp(a); cmp != -item.cmp { 598 t.Errorf("%#v: unexpected inverted Cmp: %d", item, cmp) 599 } 600 } 601 602 for _, item := range table { 603 a, b := item.a.DeepCopy(), item.b.DeepCopy() 604 b.AsDec() 605 if cmp := a.Cmp(b); cmp != item.cmp { 606 t.Errorf("%#v: unexpected Cmp: %d", item, cmp) 607 } 608 if cmp := b.Cmp(a); cmp != -item.cmp { 609 t.Errorf("%#v: unexpected inverted Cmp: %d", item, cmp) 610 } 611 } 612 613 for _, item := range table { 614 a, b := item.a.DeepCopy(), item.b.DeepCopy() 615 a.AsDec() 616 b.AsDec() 617 if cmp := a.Cmp(b); cmp != item.cmp { 618 t.Errorf("%#v: unexpected Cmp: %d", item, cmp) 619 } 620 if cmp := b.Cmp(a); cmp != -item.cmp { 621 t.Errorf("%#v: unexpected inverted Cmp: %d", item, cmp) 622 } 623 } 624 } 625 626 func TestQuantityNeg(t *testing.T) { 627 table := []struct { 628 a Quantity 629 out string 630 }{ 631 {intQuantity(901, -2, DecimalSI), "-9010m"}, 632 {decQuantity(901, -2, DecimalSI), "-9010m"}, 633 } 634 635 for i, item := range table { 636 out := item.a.DeepCopy() 637 out.Neg() 638 if out.Cmp(item.a) == 0 { 639 t.Errorf("%d: negating an item should not mutate the source: %s", i, out.String()) 640 } 641 if out.String() != item.out { 642 t.Errorf("%d: negating did not equal exact value: %s", i, out.String()) 643 } 644 } 645 } 646 647 func TestQuantityString(t *testing.T) { 648 table := []struct { 649 in Quantity 650 expect string 651 alternate string 652 }{ 653 {decQuantity(1024*1024*1024, 0, BinarySI), "1Gi", "1024Mi"}, 654 {decQuantity(300*1024*1024, 0, BinarySI), "300Mi", "307200Ki"}, 655 {decQuantity(6*1024, 0, BinarySI), "6Ki", ""}, 656 {decQuantity(1001*1024*1024*1024, 0, BinarySI), "1001Gi", "1025024Mi"}, 657 {decQuantity(1024*1024*1024*1024, 0, BinarySI), "1Ti", "1024Gi"}, 658 {decQuantity(5, 0, BinarySI), "5", "5000m"}, 659 {decQuantity(500, -3, BinarySI), "500m", "0.5"}, 660 {decQuantity(1, 9, DecimalSI), "1G", "1000M"}, 661 {decQuantity(1000, 6, DecimalSI), "1G", "0.001T"}, 662 {decQuantity(1000000, 3, DecimalSI), "1G", ""}, 663 {decQuantity(1000000000, 0, DecimalSI), "1G", ""}, 664 {decQuantity(1, -3, DecimalSI), "1m", "1000u"}, 665 {decQuantity(80, -3, DecimalSI), "80m", ""}, 666 {decQuantity(1080, -3, DecimalSI), "1080m", "1.08"}, 667 {decQuantity(108, -2, DecimalSI), "1080m", "1080000000n"}, 668 {decQuantity(10800, -4, DecimalSI), "1080m", ""}, 669 {decQuantity(300, 6, DecimalSI), "300M", ""}, 670 {decQuantity(1, 12, DecimalSI), "1T", ""}, 671 {decQuantity(1234567, 6, DecimalSI), "1234567M", ""}, 672 {decQuantity(1234567, -3, BinarySI), "1234567m", ""}, 673 {decQuantity(3, 3, DecimalSI), "3k", ""}, 674 {decQuantity(1025, 0, BinarySI), "1025", ""}, 675 {decQuantity(0, 0, DecimalSI), "0", ""}, 676 {decQuantity(0, 0, BinarySI), "0", ""}, 677 {decQuantity(1, 9, DecimalExponent), "1e9", ".001e12"}, 678 {decQuantity(1, -3, DecimalExponent), "1e-3", "0.001e0"}, 679 {decQuantity(1, -9, DecimalExponent), "1e-9", "1000e-12"}, 680 {decQuantity(80, -3, DecimalExponent), "80e-3", ""}, 681 {decQuantity(300, 6, DecimalExponent), "300e6", ""}, 682 {decQuantity(1, 12, DecimalExponent), "1e12", ""}, 683 {decQuantity(1, 3, DecimalExponent), "1e3", ""}, 684 {decQuantity(3, 3, DecimalExponent), "3e3", ""}, 685 {decQuantity(3, 3, DecimalSI), "3k", ""}, 686 {decQuantity(0, 0, DecimalExponent), "0", "00"}, 687 {decQuantity(1, -9, DecimalSI), "1n", ""}, 688 {decQuantity(80, -9, DecimalSI), "80n", ""}, 689 {decQuantity(1080, -9, DecimalSI), "1080n", ""}, 690 {decQuantity(108, -8, DecimalSI), "1080n", ""}, 691 {decQuantity(10800, -10, DecimalSI), "1080n", ""}, 692 {decQuantity(1, -6, DecimalSI), "1u", ""}, 693 {decQuantity(80, -6, DecimalSI), "80u", ""}, 694 {decQuantity(1080, -6, DecimalSI), "1080u", ""}, 695 } 696 for _, item := range table { 697 got := item.in.String() 698 if e, a := item.expect, got; e != a { 699 t.Errorf("%#v: expected %v, got %v", item.in, e, a) 700 } 701 q, err := ParseQuantity(item.expect) 702 if err != nil { 703 t.Errorf("%#v: unexpected error: %v", item.expect, err) 704 } 705 if len(q.s) == 0 || q.s != item.expect { 706 t.Errorf("%#v: did not copy canonical string on parse: %s", item.expect, q.s) 707 } 708 if len(item.alternate) == 0 { 709 continue 710 } 711 q, err = ParseQuantity(item.alternate) 712 if err != nil { 713 t.Errorf("%#v: unexpected error: %v", item.expect, err) 714 continue 715 } 716 if len(q.s) != 0 { 717 t.Errorf("%#v: unexpected nested string: %v", item.expect, q.s) 718 } 719 if q.String() != item.expect { 720 t.Errorf("%#v: unexpected alternate canonical: %v", item.expect, q.String()) 721 } 722 if len(q.s) == 0 || q.s != item.expect { 723 t.Errorf("%#v: did not set canonical string on ToString: %s", item.expect, q.s) 724 } 725 } 726 desired := &inf.Dec{} // Avoid modifying the values in the table. 727 for _, item := range table { 728 if item.in.Cmp(Quantity{}) == 0 { 729 // Don't expect it to print "-0" ever 730 continue 731 } 732 q := item.in 733 q.d = infDecAmount{desired.Neg(q.AsDec())} 734 if e, a := "-"+item.expect, q.String(); e != a { 735 t.Errorf("%#v: expected %v, got %v", item.in, e, a) 736 } 737 } 738 } 739 740 func TestQuantityParseEmit(t *testing.T) { 741 table := []struct { 742 in string 743 expect string 744 }{ 745 {"1Ki", "1Ki"}, 746 {"1Mi", "1Mi"}, 747 {"1Gi", "1Gi"}, 748 {"1024Mi", "1Gi"}, 749 {"1000M", "1G"}, 750 {".001Ki", "1024m"}, 751 {".000001Ki", "1024u"}, 752 {".000000001Ki", "1024n"}, 753 {".000000000001Ki", "2n"}, 754 } 755 756 for _, item := range table { 757 q, err := ParseQuantity(item.in) 758 if err != nil { 759 t.Errorf("Couldn't parse %v", item.in) 760 continue 761 } 762 if e, a := item.expect, q.String(); e != a { 763 t.Errorf("%#v: expected %v, got %v", item.in, e, a) 764 } 765 } 766 for _, item := range table { 767 q, err := ParseQuantity("-" + item.in) 768 if err != nil { 769 t.Errorf("Couldn't parse %v", item.in) 770 continue 771 } 772 if q.Cmp(Quantity{}) == 0 { 773 continue 774 } 775 if e, a := "-"+item.expect, q.String(); e != a { 776 t.Errorf("%#v: expected %v, got %v (%#v)", item.in, e, a, q.i) 777 } 778 } 779 } 780 781 var fuzzer = fuzz.New().Funcs( 782 func(q *Quantity, c fuzz.Continue) { 783 q.i = Zero 784 if c.RandBool() { 785 q.Format = BinarySI 786 if c.RandBool() { 787 dec := &inf.Dec{} 788 q.d = infDecAmount{Dec: dec} 789 dec.SetScale(0) 790 dec.SetUnscaled(c.Int63()) 791 return 792 } 793 // Be sure to test cases like 1Mi 794 dec := &inf.Dec{} 795 q.d = infDecAmount{Dec: dec} 796 dec.SetScale(0) 797 dec.SetUnscaled(c.Int63n(1024) << uint(10*c.Intn(5))) 798 return 799 } 800 if c.RandBool() { 801 q.Format = DecimalSI 802 } else { 803 q.Format = DecimalExponent 804 } 805 if c.RandBool() { 806 dec := &inf.Dec{} 807 q.d = infDecAmount{Dec: dec} 808 dec.SetScale(inf.Scale(c.Intn(4))) 809 dec.SetUnscaled(c.Int63()) 810 return 811 } 812 // Be sure to test cases like 1M 813 dec := &inf.Dec{} 814 q.d = infDecAmount{Dec: dec} 815 dec.SetScale(inf.Scale(3 - c.Intn(15))) 816 dec.SetUnscaled(c.Int63n(1000)) 817 }, 818 ) 819 820 func TestQuantityDeepCopy(t *testing.T) { 821 // Test when d is nil 822 slice := []string{"0", "100m", "50m", "10000T"} 823 for _, testCase := range slice { 824 q := MustParse(testCase) 825 if result := q.DeepCopy(); result != q { 826 t.Errorf("Expected: %v, Actual: %v", q, result) 827 } 828 } 829 table := []*inf.Dec{ 830 dec(0, 0).Dec, 831 dec(10, 0).Dec, 832 dec(-10, 0).Dec, 833 } 834 // Test when i is {0,0} 835 for _, testCase := range table { 836 q := Quantity{d: infDecAmount{testCase}, Format: DecimalSI} 837 result := q.DeepCopy() 838 if q.d.Cmp(result.AsDec()) != 0 { 839 t.Errorf("Expected: %v, Actual: %v", q.String(), result.String()) 840 } 841 result = Quantity{d: infDecAmount{dec(2, 0).Dec}, Format: DecimalSI} 842 if q.d.Cmp(result.AsDec()) == 0 { 843 t.Errorf("Modifying result has affected q") 844 } 845 } 846 } 847 848 func TestJSON(t *testing.T) { 849 for i := 0; i < 500; i++ { 850 q := &Quantity{} 851 fuzzer.Fuzz(q) 852 b, err := json.Marshal(q) 853 if err != nil { 854 t.Errorf("error encoding %v: %v", q, err) 855 continue 856 } 857 q2 := &Quantity{} 858 err = json.Unmarshal(b, q2) 859 if err != nil { 860 t.Logf("%d: %s", i, string(b)) 861 t.Errorf("%v: error decoding %v: %v", q, string(b), err) 862 } 863 if q2.Cmp(*q) != 0 { 864 t.Errorf("Expected equal: %v, %v (json was '%v')", q, q2, string(b)) 865 } 866 } 867 } 868 869 func TestJSONWhitespace(t *testing.T) { 870 q := Quantity{} 871 testCases := []struct { 872 in string 873 expect string 874 }{ 875 {`" 1"`, "1"}, 876 {`"1 "`, "1"}, 877 {`1`, "1"}, 878 {` 1`, "1"}, 879 {`1 `, "1"}, 880 {`10`, "10"}, 881 {`-1`, "-1"}, 882 {` -1`, "-1"}, 883 } 884 for _, test := range testCases { 885 if err := json.Unmarshal([]byte(test.in), &q); err != nil { 886 t.Errorf("%q: %v", test.in, err) 887 } 888 if q.String() != test.expect { 889 t.Errorf("unexpected string: %q", q.String()) 890 } 891 } 892 } 893 894 func TestMilliNewSet(t *testing.T) { 895 table := []struct { 896 value int64 897 format Format 898 expect string 899 exact bool 900 }{ 901 {1, DecimalSI, "1m", true}, 902 {1000, DecimalSI, "1", true}, 903 {1234000, DecimalSI, "1234", true}, 904 {1024, BinarySI, "1024m", false}, // Format changes 905 {1000000, "invalidFormatDefaultsToExponent", "1e3", true}, 906 {1024 * 1024, BinarySI, "1048576m", false}, // Format changes 907 } 908 909 for _, item := range table { 910 q := NewMilliQuantity(item.value, item.format) 911 if e, a := item.expect, q.String(); e != a { 912 t.Errorf("Expected %v, got %v; %#v", e, a, q) 913 } 914 if !item.exact { 915 continue 916 } 917 q2, err := ParseQuantity(q.String()) 918 if err != nil { 919 t.Errorf("Round trip failed on %v", q) 920 } 921 if e, a := item.value, q2.MilliValue(); e != a { 922 t.Errorf("Expected %v, got %v", e, a) 923 } 924 } 925 926 for _, item := range table { 927 q := NewQuantity(0, item.format) 928 q.SetMilli(item.value) 929 if e, a := item.expect, q.String(); e != a { 930 t.Errorf("Set: Expected %v, got %v; %#v", e, a, q) 931 } 932 } 933 } 934 935 func TestNewSet(t *testing.T) { 936 table := []struct { 937 value int64 938 format Format 939 expect string 940 }{ 941 {1, DecimalSI, "1"}, 942 {1000, DecimalSI, "1k"}, 943 {1234000, DecimalSI, "1234k"}, 944 {1024, BinarySI, "1Ki"}, 945 {1000000, "invalidFormatDefaultsToExponent", "1e6"}, 946 {1024 * 1024, BinarySI, "1Mi"}, 947 } 948 949 for _, asDec := range []bool{false, true} { 950 for _, item := range table { 951 q := NewQuantity(item.value, item.format) 952 if asDec { 953 q.ToDec() 954 } 955 if e, a := item.expect, q.String(); e != a { 956 t.Errorf("Expected %v, got %v; %#v", e, a, q) 957 } 958 q2, err := ParseQuantity(q.String()) 959 if err != nil { 960 t.Errorf("Round trip failed on %v", q) 961 } 962 if e, a := item.value, q2.Value(); e != a { 963 t.Errorf("Expected %v, got %v", e, a) 964 } 965 } 966 967 for _, item := range table { 968 q := NewQuantity(0, item.format) 969 q.Set(item.value) 970 if asDec { 971 q.ToDec() 972 } 973 if e, a := item.expect, q.String(); e != a { 974 t.Errorf("Set: Expected %v, got %v; %#v", e, a, q) 975 } 976 } 977 } 978 } 979 980 func TestNewScaledSet(t *testing.T) { 981 table := []struct { 982 value int64 983 scale Scale 984 expect string 985 }{ 986 {1, Nano, "1n"}, 987 {1000, Nano, "1u"}, 988 {1, Micro, "1u"}, 989 {1000, Micro, "1m"}, 990 {1, Milli, "1m"}, 991 {1000, Milli, "1"}, 992 {1, 0, "1"}, 993 {0, Nano, "0"}, 994 {0, Micro, "0"}, 995 {0, Milli, "0"}, 996 {0, 0, "0"}, 997 } 998 999 for _, item := range table { 1000 q := NewScaledQuantity(item.value, item.scale) 1001 if e, a := item.expect, q.String(); e != a { 1002 t.Errorf("Expected %v, got %v; %#v", e, a, q) 1003 } 1004 q2, err := ParseQuantity(q.String()) 1005 if err != nil { 1006 t.Errorf("Round trip failed on %v", q) 1007 } 1008 if e, a := item.value, q2.ScaledValue(item.scale); e != a { 1009 t.Errorf("Expected %v, got %v", e, a) 1010 } 1011 q3 := NewQuantity(0, DecimalSI) 1012 q3.SetScaled(item.value, item.scale) 1013 if q.Cmp(*q3) != 0 { 1014 t.Errorf("Expected %v and %v to be equal", q, q3) 1015 } 1016 } 1017 } 1018 1019 func TestScaledValue(t *testing.T) { 1020 table := []struct { 1021 fromScale Scale 1022 toScale Scale 1023 expected int64 1024 }{ 1025 {Nano, Nano, 1}, 1026 {Nano, Micro, 1}, 1027 {Nano, Milli, 1}, 1028 {Nano, 0, 1}, 1029 {Micro, Nano, 1000}, 1030 {Micro, Micro, 1}, 1031 {Micro, Milli, 1}, 1032 {Micro, 0, 1}, 1033 {Milli, Nano, 1000 * 1000}, 1034 {Milli, Micro, 1000}, 1035 {Milli, Milli, 1}, 1036 {Milli, 0, 1}, 1037 {0, Nano, 1000 * 1000 * 1000}, 1038 {0, Micro, 1000 * 1000}, 1039 {0, Milli, 1000}, 1040 {0, 0, 1}, 1041 {2, -2, 100 * 100}, 1042 } 1043 1044 for _, item := range table { 1045 q := NewScaledQuantity(1, item.fromScale) 1046 if e, a := item.expected, q.ScaledValue(item.toScale); e != a { 1047 t.Errorf("%v to %v: Expected %v, got %v", item.fromScale, item.toScale, e, a) 1048 } 1049 } 1050 } 1051 1052 func TestUninitializedNoCrash(t *testing.T) { 1053 var q Quantity 1054 1055 q.Value() 1056 q.MilliValue() 1057 q.DeepCopy() 1058 _ = q.String() 1059 q.MarshalJSON() 1060 } 1061 1062 func TestDeepCopy(t *testing.T) { 1063 q := NewQuantity(5, DecimalSI) 1064 c := q.DeepCopy() 1065 c.Set(6) 1066 if q.Value() == 6 { 1067 t.Errorf("Copy didn't") 1068 } 1069 } 1070 1071 func TestSub(t *testing.T) { 1072 tests := []struct { 1073 a Quantity 1074 b Quantity 1075 expected Quantity 1076 }{ 1077 {decQuantity(10, 0, DecimalSI), decQuantity(1, 1, DecimalSI), decQuantity(0, 0, DecimalSI)}, 1078 {decQuantity(10, 0, DecimalSI), decQuantity(1, 0, BinarySI), decQuantity(9, 0, DecimalSI)}, 1079 {decQuantity(10, 0, BinarySI), decQuantity(1, 0, DecimalSI), decQuantity(9, 0, BinarySI)}, 1080 {Quantity{Format: DecimalSI}, decQuantity(50, 0, DecimalSI), decQuantity(-50, 0, DecimalSI)}, 1081 {decQuantity(50, 0, DecimalSI), Quantity{Format: DecimalSI}, decQuantity(50, 0, DecimalSI)}, 1082 {Quantity{Format: DecimalSI}, Quantity{Format: DecimalSI}, decQuantity(0, 0, DecimalSI)}, 1083 } 1084 1085 for i, test := range tests { 1086 test.a.Sub(test.b) 1087 if test.a.Cmp(test.expected) != 0 { 1088 t.Errorf("[%d] Expected %q, got %q", i, test.expected.String(), test.a.String()) 1089 } 1090 } 1091 } 1092 1093 func TestNeg(t *testing.T) { 1094 tests := []struct { 1095 a Quantity 1096 b Quantity 1097 expected Quantity 1098 }{ 1099 {a: intQuantity(0, 0, DecimalSI), expected: intQuantity(0, 0, DecimalSI)}, 1100 {a: Quantity{}, expected: Quantity{}}, 1101 {a: intQuantity(10, 0, BinarySI), expected: intQuantity(-10, 0, BinarySI)}, 1102 {a: intQuantity(-10, 0, BinarySI), expected: intQuantity(10, 0, BinarySI)}, 1103 {a: decQuantity(0, 0, DecimalSI), expected: intQuantity(0, 0, DecimalSI)}, 1104 {a: decQuantity(10, 0, BinarySI), expected: intQuantity(-10, 0, BinarySI)}, 1105 {a: decQuantity(-10, 0, BinarySI), expected: intQuantity(10, 0, BinarySI)}, 1106 } 1107 1108 for i, test := range tests { 1109 a := test.a.DeepCopy() 1110 a.Neg() 1111 // ensure value is same 1112 if a.Cmp(test.expected) != 0 { 1113 t.Errorf("[%d] Expected %q, got %q", i, test.expected.String(), a.String()) 1114 } 1115 } 1116 } 1117 1118 func TestAdd(t *testing.T) { 1119 tests := []struct { 1120 a Quantity 1121 b Quantity 1122 expected Quantity 1123 }{ 1124 {decQuantity(10, 0, DecimalSI), decQuantity(1, 1, DecimalSI), decQuantity(20, 0, DecimalSI)}, 1125 {decQuantity(10, 0, DecimalSI), decQuantity(1, 0, BinarySI), decQuantity(11, 0, DecimalSI)}, 1126 {decQuantity(10, 0, BinarySI), decQuantity(1, 0, DecimalSI), decQuantity(11, 0, BinarySI)}, 1127 {Quantity{Format: DecimalSI}, decQuantity(50, 0, DecimalSI), decQuantity(50, 0, DecimalSI)}, 1128 {decQuantity(50, 0, DecimalSI), Quantity{Format: DecimalSI}, decQuantity(50, 0, DecimalSI)}, 1129 {Quantity{Format: DecimalSI}, Quantity{Format: DecimalSI}, decQuantity(0, 0, DecimalSI)}, 1130 } 1131 1132 for i, test := range tests { 1133 test.a.Add(test.b) 1134 if test.a.Cmp(test.expected) != 0 { 1135 t.Errorf("[%d] Expected %q, got %q", i, test.expected.String(), test.a.String()) 1136 } 1137 } 1138 } 1139 1140 func TestAddSubRoundTrip(t *testing.T) { 1141 for k := -10; k <= 10; k++ { 1142 q := Quantity{Format: DecimalSI} 1143 var order []int64 1144 for i := 0; i < 100; i++ { 1145 j := rand.Int63() 1146 order = append(order, j) 1147 q.Add(*NewScaledQuantity(j, Scale(k))) 1148 } 1149 for _, j := range order { 1150 q.Sub(*NewScaledQuantity(j, Scale(k))) 1151 } 1152 if !q.IsZero() { 1153 t.Errorf("addition and subtraction did not cancel: %s", &q) 1154 } 1155 } 1156 } 1157 1158 func TestAddSubRoundTripAcrossScales(t *testing.T) { 1159 q := Quantity{Format: DecimalSI} 1160 var order []int64 1161 for i := 0; i < 100; i++ { 1162 j := rand.Int63() 1163 order = append(order, j) 1164 q.Add(*NewScaledQuantity(j, Scale(j%20-10))) 1165 } 1166 for _, j := range order { 1167 q.Sub(*NewScaledQuantity(j, Scale(j%20-10))) 1168 } 1169 if !q.IsZero() { 1170 t.Errorf("addition and subtraction did not cancel: %s", &q) 1171 } 1172 } 1173 1174 func TestNegateRoundTrip(t *testing.T) { 1175 for _, asDec := range []bool{false, true} { 1176 for k := -10; k <= 10; k++ { 1177 for i := 0; i < 100; i++ { 1178 j := rand.Int63() 1179 q := *NewScaledQuantity(j, Scale(k)) 1180 if asDec { 1181 q.AsDec() 1182 } 1183 1184 b := q.DeepCopy() 1185 b.Neg() 1186 b.Neg() 1187 if b.Cmp(q) != 0 { 1188 t.Errorf("double negation did not cancel: %s", &q) 1189 } 1190 } 1191 } 1192 } 1193 } 1194 1195 func TestQuantityAsApproximateFloat64(t *testing.T) { 1196 table := []struct { 1197 in Quantity 1198 out float64 1199 }{ 1200 {decQuantity(0, 0, DecimalSI), 0.0}, 1201 {decQuantity(0, 0, DecimalExponent), 0.0}, 1202 {decQuantity(0, 0, BinarySI), 0.0}, 1203 1204 {decQuantity(1, 0, DecimalSI), 1}, 1205 {decQuantity(1, 0, DecimalExponent), 1}, 1206 {decQuantity(1, 0, BinarySI), 1}, 1207 1208 // Binary suffixes 1209 {decQuantity(1024, 0, BinarySI), 1024}, 1210 {decQuantity(8*1024, 0, BinarySI), 8 * 1024}, 1211 {decQuantity(7*1024*1024, 0, BinarySI), 7 * 1024 * 1024}, 1212 {decQuantity(7*1024*1024, 1, BinarySI), (7 * 1024 * 1024) * 10}, 1213 {decQuantity(7*1024*1024, 4, BinarySI), (7 * 1024 * 1024) * 10000}, 1214 {decQuantity(7*1024*1024, 8, BinarySI), (7 * 1024 * 1024) * 100000000}, 1215 {decQuantity(7*1024*1024, -1, BinarySI), (7 * 1024 * 1024) * math.Pow10(-1)}, // '* Pow10' and '/ float(10)' do not round the same way 1216 {decQuantity(7*1024*1024, -8, BinarySI), (7 * 1024 * 1024) / float64(100000000)}, 1217 1218 {decQuantity(1024, 0, DecimalSI), 1024}, 1219 {decQuantity(8*1024, 0, DecimalSI), 8 * 1024}, 1220 {decQuantity(7*1024*1024, 0, DecimalSI), 7 * 1024 * 1024}, 1221 {decQuantity(7*1024*1024, 1, DecimalSI), (7 * 1024 * 1024) * 10}, 1222 {decQuantity(7*1024*1024, 4, DecimalSI), (7 * 1024 * 1024) * 10000}, 1223 {decQuantity(7*1024*1024, 8, DecimalSI), (7 * 1024 * 1024) * 100000000}, 1224 {decQuantity(7*1024*1024, -1, DecimalSI), (7 * 1024 * 1024) * math.Pow10(-1)}, // '* Pow10' and '/ float(10)' do not round the same way 1225 {decQuantity(7*1024*1024, -8, DecimalSI), (7 * 1024 * 1024) / float64(100000000)}, 1226 1227 {decQuantity(1024, 0, DecimalExponent), 1024}, 1228 {decQuantity(8*1024, 0, DecimalExponent), 8 * 1024}, 1229 {decQuantity(7*1024*1024, 0, DecimalExponent), 7 * 1024 * 1024}, 1230 {decQuantity(7*1024*1024, 1, DecimalExponent), (7 * 1024 * 1024) * 10}, 1231 {decQuantity(7*1024*1024, 4, DecimalExponent), (7 * 1024 * 1024) * 10000}, 1232 {decQuantity(7*1024*1024, 8, DecimalExponent), (7 * 1024 * 1024) * 100000000}, 1233 {decQuantity(7*1024*1024, -1, DecimalExponent), (7 * 1024 * 1024) * math.Pow10(-1)}, // '* Pow10' and '/ float(10)' do not round the same way 1234 {decQuantity(7*1024*1024, -8, DecimalExponent), (7 * 1024 * 1024) / float64(100000000)}, 1235 1236 // very large numbers 1237 {Quantity{d: maxAllowed, Format: DecimalSI}, math.MaxInt64}, 1238 {Quantity{d: maxAllowed, Format: BinarySI}, math.MaxInt64}, 1239 {decQuantity(12, 18, DecimalSI), 1.2e19}, 1240 1241 // infinities caused due to float64 overflow 1242 {decQuantity(12, 500, DecimalSI), math.Inf(0)}, 1243 {decQuantity(-12, 500, DecimalSI), math.Inf(-1)}, 1244 } 1245 1246 for _, item := range table { 1247 t.Run(fmt.Sprintf("%s %s", item.in.Format, item.in.String()), func(t *testing.T) { 1248 out := item.in.AsApproximateFloat64() 1249 if out != item.out { 1250 t.Fatalf("expected %v, got %v", item.out, out) 1251 } 1252 if item.in.d.Dec != nil { 1253 if i, ok := item.in.AsInt64(); ok { 1254 q := intQuantity(i, 0, item.in.Format) 1255 out := q.AsApproximateFloat64() 1256 if out != item.out { 1257 t.Fatalf("as int quantity: expected %v, got %v", item.out, out) 1258 } 1259 } 1260 } 1261 }) 1262 } 1263 } 1264 1265 func TestStringQuantityAsApproximateFloat64(t *testing.T) { 1266 table := []struct { 1267 in string 1268 out float64 1269 }{ 1270 {"2Ki", 2048}, 1271 {"1.1Ki", 1126.4e+0}, 1272 {"1Mi", 1.048576e+06}, 1273 {"2Gi", 2.147483648e+09}, 1274 } 1275 1276 for _, item := range table { 1277 t.Run(item.in, func(t *testing.T) { 1278 in, err := ParseQuantity(item.in) 1279 if err != nil { 1280 t.Fatal(err) 1281 } 1282 out := in.AsApproximateFloat64() 1283 if out != item.out { 1284 t.Fatalf("expected %v, got %v", item.out, out) 1285 } 1286 if in.d.Dec != nil { 1287 if i, ok := in.AsInt64(); ok { 1288 q := intQuantity(i, 0, in.Format) 1289 out := q.AsApproximateFloat64() 1290 if out != item.out { 1291 t.Fatalf("as int quantity: expected %v, got %v", item.out, out) 1292 } 1293 } 1294 } 1295 }) 1296 } 1297 } 1298 1299 func benchmarkQuantities() []Quantity { 1300 return []Quantity{ 1301 intQuantity(1024*1024*1024, 0, BinarySI), 1302 intQuantity(1024*1024*1024*1024, 0, BinarySI), 1303 intQuantity(1000000, 3, DecimalSI), 1304 intQuantity(1000000000, 0, DecimalSI), 1305 intQuantity(1, -3, DecimalSI), 1306 intQuantity(80, -3, DecimalSI), 1307 intQuantity(1080, -3, DecimalSI), 1308 intQuantity(0, 0, BinarySI), 1309 intQuantity(1, 9, DecimalExponent), 1310 intQuantity(1, -9, DecimalSI), 1311 intQuantity(1000000, 10, DecimalSI), 1312 } 1313 } 1314 1315 func BenchmarkQuantityString(b *testing.B) { 1316 values := benchmarkQuantities() 1317 b.ResetTimer() 1318 var s string 1319 for i := 0; i < b.N; i++ { 1320 q := values[i%len(values)] 1321 q.s = "" 1322 s = q.String() 1323 } 1324 b.StopTimer() 1325 if len(s) == 0 { 1326 b.Fatal(s) 1327 } 1328 } 1329 1330 func BenchmarkQuantityStringPrecalc(b *testing.B) { 1331 values := benchmarkQuantities() 1332 for i := range values { 1333 _ = values[i].String() 1334 } 1335 b.ResetTimer() 1336 var s string 1337 for i := 0; i < b.N; i++ { 1338 q := values[i%len(values)] 1339 s = q.String() 1340 } 1341 b.StopTimer() 1342 if len(s) == 0 { 1343 b.Fatal(s) 1344 } 1345 } 1346 1347 func BenchmarkQuantityStringBinarySI(b *testing.B) { 1348 values := benchmarkQuantities() 1349 for i := range values { 1350 values[i].Format = BinarySI 1351 } 1352 b.ResetTimer() 1353 var s string 1354 for i := 0; i < b.N; i++ { 1355 q := values[i%len(values)] 1356 q.s = "" 1357 s = q.String() 1358 } 1359 b.StopTimer() 1360 if len(s) == 0 { 1361 b.Fatal(s) 1362 } 1363 } 1364 1365 func BenchmarkQuantityMarshalJSON(b *testing.B) { 1366 values := benchmarkQuantities() 1367 b.ResetTimer() 1368 for i := 0; i < b.N; i++ { 1369 q := values[i%len(values)] 1370 q.s = "" 1371 if _, err := q.MarshalJSON(); err != nil { 1372 b.Fatal(err) 1373 } 1374 } 1375 b.StopTimer() 1376 } 1377 1378 func BenchmarkQuantityUnmarshalJSON(b *testing.B) { 1379 values := benchmarkQuantities() 1380 var json [][]byte 1381 for _, v := range values { 1382 data, _ := v.MarshalJSON() 1383 json = append(json, data) 1384 } 1385 1386 b.ResetTimer() 1387 for i := 0; i < b.N; i++ { 1388 var q Quantity 1389 if err := q.UnmarshalJSON(json[i%len(values)]); err != nil { 1390 b.Fatal(err) 1391 } 1392 } 1393 b.StopTimer() 1394 } 1395 1396 func BenchmarkParseQuantity(b *testing.B) { 1397 values := benchmarkQuantities() 1398 var strings []string 1399 for _, v := range values { 1400 strings = append(strings, v.String()) 1401 } 1402 b.ResetTimer() 1403 for i := 0; i < b.N; i++ { 1404 if _, err := ParseQuantity(strings[i%len(values)]); err != nil { 1405 b.Fatal(err) 1406 } 1407 } 1408 b.StopTimer() 1409 } 1410 1411 func BenchmarkCanonicalize(b *testing.B) { 1412 values := benchmarkQuantities() 1413 b.ResetTimer() 1414 buffer := make([]byte, 0, 100) 1415 for i := 0; i < b.N; i++ { 1416 s, _ := values[i%len(values)].CanonicalizeBytes(buffer) 1417 if len(s) == 0 { 1418 b.Fatal(s) 1419 } 1420 } 1421 b.StopTimer() 1422 } 1423 1424 func BenchmarkQuantityRoundUp(b *testing.B) { 1425 values := benchmarkQuantities() 1426 b.ResetTimer() 1427 for i := 0; i < b.N; i++ { 1428 q := values[i%len(values)] 1429 copied := q 1430 copied.RoundUp(-3) 1431 } 1432 b.StopTimer() 1433 } 1434 1435 func BenchmarkQuantityCopy(b *testing.B) { 1436 values := benchmarkQuantities() 1437 b.ResetTimer() 1438 for i := 0; i < b.N; i++ { 1439 values[i%len(values)].DeepCopy() 1440 } 1441 b.StopTimer() 1442 } 1443 1444 func BenchmarkQuantityAdd(b *testing.B) { 1445 values := benchmarkQuantities() 1446 base := &Quantity{} 1447 b.ResetTimer() 1448 for i := 0; i < b.N; i++ { 1449 q := values[i%len(values)] 1450 base.d.Dec = nil 1451 base.i = int64Amount{value: 100} 1452 base.Add(q) 1453 } 1454 b.StopTimer() 1455 } 1456 1457 func BenchmarkQuantityCmp(b *testing.B) { 1458 values := benchmarkQuantities() 1459 b.ResetTimer() 1460 for i := 0; i < b.N; i++ { 1461 q := values[i%len(values)] 1462 if q.Cmp(q) != 0 { 1463 b.Fatal(q) 1464 } 1465 } 1466 b.StopTimer() 1467 } 1468 1469 func BenchmarkQuantityAsApproximateFloat64(b *testing.B) { 1470 values := benchmarkQuantities() 1471 b.ResetTimer() 1472 for i := 0; i < b.N; i++ { 1473 q := values[i%len(values)] 1474 if q.AsApproximateFloat64() == -1 { 1475 b.Fatal(q) 1476 } 1477 } 1478 b.StopTimer() 1479 } 1480 1481 var _ pflag.Value = &QuantityValue{} 1482 1483 func TestQuantityValueSet(t *testing.T) { 1484 q := QuantityValue{} 1485 1486 if err := q.Set("invalid"); err == nil { 1487 1488 t.Error("'invalid' did not trigger a parse error") 1489 } 1490 1491 if err := q.Set("1Mi"); err != nil { 1492 t.Errorf("parsing 1Mi should have worked, got: %v", err) 1493 } 1494 if q.Value() != 1024*1024 { 1495 t.Errorf("quantity should have been set to 1Mi, got: %v", q) 1496 } 1497 1498 data, err := json.Marshal(q) 1499 if err != nil { 1500 t.Errorf("unexpected encoding error: %v", err) 1501 } 1502 expected := `"1Mi"` 1503 if string(data) != expected { 1504 t.Errorf("expected 1Mi value to be encoded as %q, got: %q", expected, string(data)) 1505 } 1506 } 1507 1508 func ExampleQuantityValue() { 1509 q := QuantityValue{ 1510 Quantity: MustParse("1Mi"), 1511 } 1512 fs := pflag.FlagSet{} 1513 fs.SetOutput(os.Stdout) 1514 fs.Var(&q, "mem", "sets amount of memory") 1515 fs.PrintDefaults() 1516 // Output: 1517 // --mem quantity sets amount of memory (default 1Mi) 1518 }