// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package structs import ( "fmt" "testing" "github.com/hashicorp/nomad/helper/pointer" "github.com/stretchr/testify/require" ) func TestAttribute_Validate(t *testing.T) { cases := []struct { Input *Attribute Fail bool }{ { Input: &Attribute{ Bool: pointer.Of(true), }, }, { Input: &Attribute{ String: pointer.Of("foo"), }, }, { Input: &Attribute{ Int: pointer.Of(int64(123)), }, }, { Input: &Attribute{ Float: pointer.Of(float64(123.2)), }, }, { Input: &Attribute{ Bool: pointer.Of(true), Unit: "MB", }, Fail: true, }, { Input: &Attribute{ String: pointer.Of("foo"), Unit: "MB", }, Fail: true, }, { Input: &Attribute{ Int: pointer.Of(int64(123)), Unit: "lolNO", }, Fail: true, }, { Input: &Attribute{ Float: pointer.Of(float64(123.2)), Unit: "lolNO", }, Fail: true, }, { Input: &Attribute{ Int: pointer.Of(int64(123)), Float: pointer.Of(float64(123.2)), Unit: "mW", }, Fail: true, }, } for _, c := range cases { t.Run(c.Input.GoString(), func(t *testing.T) { if err := c.Input.Validate(); err != nil && !c.Fail { require.NoError(t, err) } }) } } type compareTestCase struct { A *Attribute B *Attribute Expected int NotComparable bool } func TestAttribute_Compare_Bool(t *testing.T) { cases := []*compareTestCase{ { A: &Attribute{ Bool: pointer.Of(true), }, B: &Attribute{ Bool: pointer.Of(true), }, Expected: 0, }, { A: &Attribute{ Bool: pointer.Of(true), }, B: &Attribute{ Bool: pointer.Of(false), }, Expected: 1, }, { A: &Attribute{ Bool: pointer.Of(true), }, B: &Attribute{ String: pointer.Of("foo"), }, NotComparable: true, }, { A: &Attribute{ Bool: pointer.Of(true), }, B: &Attribute{ Int: pointer.Of(int64(123)), }, NotComparable: true, }, { A: &Attribute{ Bool: pointer.Of(true), }, B: &Attribute{ Float: pointer.Of(float64(123.2)), }, NotComparable: true, }, } testComparison(t, cases) } func TestAttribute_Compare_String(t *testing.T) { cases := []*compareTestCase{ { A: &Attribute{ String: pointer.Of("a"), }, B: &Attribute{ String: pointer.Of("b"), }, Expected: -1, }, { A: &Attribute{ String: pointer.Of("hello"), }, B: &Attribute{ String: pointer.Of("hello"), }, Expected: 0, }, { A: &Attribute{ String: pointer.Of("b"), }, B: &Attribute{ String: pointer.Of("a"), }, Expected: 1, }, { A: &Attribute{ String: pointer.Of("hello"), }, B: &Attribute{ Bool: pointer.Of(true), }, NotComparable: true, }, { A: &Attribute{ String: pointer.Of("hello"), }, B: &Attribute{ Int: pointer.Of(int64(123)), }, NotComparable: true, }, { A: &Attribute{ String: pointer.Of("hello"), }, B: &Attribute{ Float: pointer.Of(float64(123.2)), }, NotComparable: true, }, } testComparison(t, cases) } func TestAttribute_Compare_Float(t *testing.T) { cases := []*compareTestCase{ { A: &Attribute{ Float: pointer.Of(float64(101.5)), }, B: &Attribute{ Float: pointer.Of(float64(100001.5)), }, Expected: -1, }, { A: &Attribute{ Float: pointer.Of(float64(100001.5)), }, B: &Attribute{ Float: pointer.Of(float64(100001.5)), }, Expected: 0, }, { A: &Attribute{ Float: pointer.Of(float64(999999999.5)), }, B: &Attribute{ Float: pointer.Of(float64(101.5)), }, Expected: 1, }, { A: &Attribute{ Float: pointer.Of(float64(101.5)), }, B: &Attribute{ Bool: pointer.Of(true), }, NotComparable: true, }, { A: &Attribute{ Float: pointer.Of(float64(101.5)), }, B: &Attribute{ String: pointer.Of("hello"), }, NotComparable: true, }, } testComparison(t, cases) } func TestAttribute_Compare_Int(t *testing.T) { cases := []*compareTestCase{ { A: &Attribute{ Int: pointer.Of(int64(3)), }, B: &Attribute{ Int: pointer.Of(int64(10)), }, Expected: -1, }, { A: &Attribute{ Int: pointer.Of(int64(10)), }, B: &Attribute{ Int: pointer.Of(int64(10)), }, Expected: 0, }, { A: &Attribute{ Int: pointer.Of(int64(100)), }, B: &Attribute{ Int: pointer.Of(int64(10)), }, Expected: 1, }, { A: &Attribute{ Int: pointer.Of(int64(10)), }, B: &Attribute{ Bool: pointer.Of(true), }, NotComparable: true, }, { A: &Attribute{ Int: pointer.Of(int64(10)), }, B: &Attribute{ String: pointer.Of("hello"), }, NotComparable: true, }, } testComparison(t, cases) } func TestAttribute_Compare_Int_With_Units(t *testing.T) { cases := []*compareTestCase{ { A: &Attribute{ Int: pointer.Of(int64(3)), Unit: "MB", }, B: &Attribute{ Int: pointer.Of(int64(10)), Unit: "MB", }, Expected: -1, }, { A: &Attribute{ Int: pointer.Of(int64(10)), Unit: "MB", }, B: &Attribute{ Int: pointer.Of(int64(10)), Unit: "MB", }, Expected: 0, }, { A: &Attribute{ Int: pointer.Of(int64(100)), Unit: "MB", }, B: &Attribute{ Int: pointer.Of(int64(10)), Unit: "MB", }, Expected: 1, }, { A: &Attribute{ Int: pointer.Of(int64(3)), Unit: "GB", }, B: &Attribute{ Int: pointer.Of(int64(3)), Unit: "MB", }, Expected: 1, }, { A: &Attribute{ Int: pointer.Of(int64(1)), Unit: "GiB", }, B: &Attribute{ Int: pointer.Of(int64(1024)), Unit: "MiB", }, Expected: 0, }, { A: &Attribute{ Int: pointer.Of(int64(1)), Unit: "GiB", }, B: &Attribute{ Int: pointer.Of(int64(1025)), Unit: "MiB", }, Expected: -1, }, { A: &Attribute{ Int: pointer.Of(int64(1000)), Unit: "mW", }, B: &Attribute{ Int: pointer.Of(int64(1)), Unit: "W", }, Expected: 0, }, } testComparison(t, cases) } func TestAttribute_Compare_Float_With_Units(t *testing.T) { cases := []*compareTestCase{ { A: &Attribute{ Float: pointer.Of(float64(3.0)), Unit: "MB", }, B: &Attribute{ Float: pointer.Of(float64(10.0)), Unit: "MB", }, Expected: -1, }, { A: &Attribute{ Float: pointer.Of(float64(10.0)), Unit: "MB", }, B: &Attribute{ Float: pointer.Of(float64(10.0)), Unit: "MB", }, Expected: 0, }, { A: &Attribute{ Float: pointer.Of(float64(100.0)), Unit: "MB", }, B: &Attribute{ Float: pointer.Of(float64(10.0)), Unit: "MB", }, Expected: 1, }, { A: &Attribute{ Float: pointer.Of(float64(3.0)), Unit: "GB", }, B: &Attribute{ Float: pointer.Of(float64(3.0)), Unit: "MB", }, Expected: 1, }, { A: &Attribute{ Float: pointer.Of(float64(1.0)), Unit: "GiB", }, B: &Attribute{ Float: pointer.Of(float64(1024.0)), Unit: "MiB", }, Expected: 0, }, { A: &Attribute{ Float: pointer.Of(float64(1.0)), Unit: "GiB", }, B: &Attribute{ Float: pointer.Of(float64(1025.0)), Unit: "MiB", }, Expected: -1, }, { A: &Attribute{ Float: pointer.Of(float64(1000.0)), Unit: "mW", }, B: &Attribute{ Float: pointer.Of(float64(1.0)), Unit: "W", }, Expected: 0, }, { A: &Attribute{ Float: pointer.Of(float64(1.5)), Unit: "GiB", }, B: &Attribute{ Float: pointer.Of(float64(1400.0)), Unit: "MiB", }, Expected: 1, }, } testComparison(t, cases) } func TestAttribute_Compare_IntToFloat(t *testing.T) { cases := []*compareTestCase{ { A: &Attribute{ Int: pointer.Of(int64(3)), }, B: &Attribute{ Float: pointer.Of(float64(10.0)), }, Expected: -1, }, { A: &Attribute{ Int: pointer.Of(int64(10)), }, B: &Attribute{ Float: pointer.Of(float64(10.0)), }, Expected: 0, }, { A: &Attribute{ Int: pointer.Of(int64(10)), }, B: &Attribute{ Float: pointer.Of(float64(10.1)), }, Expected: -1, }, { A: &Attribute{ Int: pointer.Of(int64(100)), }, B: &Attribute{ Float: pointer.Of(float64(10.0)), }, Expected: 1, }, { A: &Attribute{ Int: pointer.Of(int64(100)), }, B: &Attribute{ Float: pointer.Of(float64(100.00001)), }, Expected: -1, }, } testComparison(t, cases) } func testComparison(t *testing.T, cases []*compareTestCase) { for _, c := range cases { t.Run(fmt.Sprintf("%#v vs %#v", c.A, c.B), func(t *testing.T) { v, ok := c.A.Compare(c.B) if !ok && !c.NotComparable { t.Fatal("should be comparable") } else if ok { require.Equal(t, c.Expected, v) } }) } } func TestAttribute_ParseAndValidate(t *testing.T) { cases := []struct { Input string Expected *Attribute }{ { Input: "true", Expected: &Attribute{ Bool: pointer.Of(true), }, }, { Input: "false", Expected: &Attribute{ Bool: pointer.Of(false), }, }, { Input: "1", Expected: &Attribute{ Int: pointer.Of(int64(1)), }, }, { Input: "100", Expected: &Attribute{ Int: pointer.Of(int64(100)), }, }, { Input: "-100", Expected: &Attribute{ Int: pointer.Of(int64(-100)), }, }, { Input: "-1.0", Expected: &Attribute{ Float: pointer.Of(float64(-1.0)), }, }, { Input: "-100.25", Expected: &Attribute{ Float: pointer.Of(float64(-100.25)), }, }, { Input: "1.01", Expected: &Attribute{ Float: pointer.Of(float64(1.01)), }, }, { Input: "100.25", Expected: &Attribute{ Float: pointer.Of(float64(100.25)), }, }, { Input: "foobar", Expected: &Attribute{ String: pointer.Of("foobar"), }, }, { Input: "foo123bar", Expected: &Attribute{ String: pointer.Of("foo123bar"), }, }, { Input: "100MB", Expected: &Attribute{ Int: pointer.Of(int64(100)), Unit: "MB", }, }, { Input: "-100MHz", Expected: &Attribute{ Int: pointer.Of(int64(-100)), Unit: "MHz", }, }, { Input: "-1.0MB/s", Expected: &Attribute{ Float: pointer.Of(float64(-1.0)), Unit: "MB/s", }, }, { Input: "-100.25GiB/s", Expected: &Attribute{ Float: pointer.Of(float64(-100.25)), Unit: "GiB/s", }, }, { Input: "1.01TB", Expected: &Attribute{ Float: pointer.Of(float64(1.01)), Unit: "TB", }, }, { Input: "100.25mW", Expected: &Attribute{ Float: pointer.Of(float64(100.25)), Unit: "mW", }, }, } for _, c := range cases { t.Run(c.Input, func(t *testing.T) { a := ParseAttribute(c.Input) require.Equal(t, c.Expected, a) require.NoError(t, a.Validate()) }) } } func BenchmarkParse(b *testing.B) { cases := []string{ "true", "false", "100", "-100", "-1.0", "-100.25", "1.01", "100.25", "foobar", "foo123bar", "100MB", "-100MHz", "-1.0MB/s", "-100.25GiB/s", "1.01TB", "100.25mW", } for n := 0; n < b.N; n++ { for _, c := range cases { ParseAttribute(c) } } }