// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package nomad import ( "bytes" "sync" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/hashicorp/go-msgpack/codec" "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/nomad/structs" ) func TestTimeTable(t *testing.T) { ci.Parallel(t) tt := NewTimeTable(time.Second, time.Minute) index := tt.NearestIndex(time.Now()) if index != 0 { t.Fatalf("bad: %v", index) } when := tt.NearestTime(1000) if !when.IsZero() { t.Fatalf("bad: %v", when) } // Witness some data start := time.Now() plusOne := start.Add(time.Minute) plusTwo := start.Add(2 * time.Minute) plusFive := start.Add(5 * time.Minute) plusThirty := start.Add(30 * time.Minute) plusHour := start.Add(60 * time.Minute) plusHourHalf := start.Add(90 * time.Minute) tt.Witness(2, start) tt.Witness(2, start) tt.Witness(10, plusOne) tt.Witness(10, plusOne) tt.Witness(20, plusTwo) tt.Witness(20, plusTwo) tt.Witness(30, plusFive) tt.Witness(30, plusFive) tt.Witness(40, plusThirty) tt.Witness(40, plusThirty) tt.Witness(50, plusHour) tt.Witness(50, plusHour) type tcase struct { when time.Time expectIndex uint64 index uint64 expectWhen time.Time } cases := []tcase{ // Exact match {start, 2, 2, start}, {plusOne, 10, 10, plusOne}, {plusHour, 50, 50, plusHour}, // Before the newest entry {plusHourHalf, 50, 51, plusHour}, // After the oldest entry {time.Time{}, 0, 1, time.Time{}}, // Mid range {start.Add(3 * time.Minute), 20, 25, plusTwo}, } for _, tc := range cases { index := tt.NearestIndex(tc.when) if index != tc.expectIndex { t.Fatalf("bad: %v %v", index, tc.expectIndex) } when := tt.NearestTime(tc.index) if when != tc.expectWhen { t.Fatalf("bad: for %d %v %v", tc.index, when, tc.expectWhen) } } } func TestTimeTable_SerializeDeserialize(t *testing.T) { ci.Parallel(t) tt := NewTimeTable(time.Second, time.Minute) // Witness some data start := time.Now() plusOne := start.Add(time.Minute) plusTwo := start.Add(2 * time.Minute) plusFive := start.Add(5 * time.Minute) plusThirty := start.Add(30 * time.Minute) plusHour := start.Add(60 * time.Minute) tt.Witness(2, start) tt.Witness(10, plusOne) tt.Witness(20, plusTwo) tt.Witness(30, plusFive) tt.Witness(40, plusThirty) tt.Witness(50, plusHour) var buf bytes.Buffer enc := codec.NewEncoder(&buf, structs.MsgpackHandle) err := tt.Serialize(enc) if err != nil { t.Fatalf("err: %v", err) } dec := codec.NewDecoder(&buf, structs.MsgpackHandle) tt2 := NewTimeTable(time.Second, time.Minute) err = tt2.Deserialize(dec) if err != nil { t.Fatalf("err: %v", err) } o := cmp.AllowUnexported(TimeTable{}) o2 := cmpopts.IgnoreTypes(sync.RWMutex{}) if !cmp.Equal(tt.table, tt2.table, o, o2) { t.Fatalf("bad: %s", cmp.Diff(tt, tt2, o, o2)) } } func TestTimeTable_Overflow(t *testing.T) { ci.Parallel(t) tt := NewTimeTable(time.Second, 3*time.Second) // Witness some data start := time.Now() plusOne := start.Add(time.Second) plusTwo := start.Add(2 * time.Second) plusThree := start.Add(3 * time.Second) tt.Witness(10, start) tt.Witness(20, plusOne) tt.Witness(30, plusTwo) tt.Witness(40, plusThree) if len(tt.table) != 3 { t.Fatalf("bad") } index := tt.NearestIndex(start) if index != 0 { t.Fatalf("bad: %v %v", index, 0) } when := tt.NearestTime(15) if !when.IsZero() { t.Fatalf("bad: %v", when) } }