github.com/onflow/flow-go@v0.33.17/consensus/hotstuff/cruisectl/transition_time_test.go (about) 1 package cruisectl 2 3 import ( 4 "strings" 5 "testing" 6 "time" 7 8 "github.com/stretchr/testify/assert" 9 "pgregory.net/rapid" 10 ) 11 12 // TestParseTransition_Valid tests that valid transition configurations have 13 // consistent parsing and formatting behaviour. 14 func TestParseTransition_Valid(t *testing.T) { 15 cases := []struct { 16 transition EpochTransitionTime 17 str string 18 }{{ 19 transition: EpochTransitionTime{time.Sunday, 0, 0}, 20 str: "sunday@00:00", 21 }, { 22 transition: EpochTransitionTime{time.Wednesday, 8, 1}, 23 str: "wednesday@08:01", 24 }, { 25 transition: EpochTransitionTime{time.Monday, 23, 59}, 26 str: "monday@23:59", 27 }, { 28 transition: EpochTransitionTime{time.Friday, 12, 21}, 29 str: "FrIdAy@12:21", 30 }} 31 32 for _, c := range cases { 33 t.Run(c.str, func(t *testing.T) { 34 // 1 - the computed string representation should match the string fixture 35 assert.Equal(t, strings.ToLower(c.str), c.transition.String()) 36 // 2 - the parsed transition should match the transition fixture 37 parsed, err := ParseTransition(c.str) 38 assert.NoError(t, err) 39 assert.Equal(t, c.transition, *parsed) 40 }) 41 } 42 } 43 44 // TestParseTransition_Invalid tests that a selection of invalid transition strings 45 // fail validation and return an error. 46 func TestParseTransition_Invalid(t *testing.T) { 47 cases := []string{ 48 // invalid WD part 49 "sundy@12:00", 50 "tue@12:00", 51 "@12:00", 52 // invalid HH part 53 "wednesday@24:00", 54 "wednesday@1:00", 55 "wednesday@:00", 56 "wednesday@012:00", 57 // invalid MM part 58 "wednesday@12:60", 59 "wednesday@12:1", 60 "wednesday@12:", 61 "wednesday@12:030", 62 // otherwise invalid 63 "", 64 "@:", 65 "monday@@12:00", 66 "monday@09:00am", 67 "monday@09:00PM", 68 "monday12:00", 69 "monday12::00", 70 "wednesday@1200", 71 } 72 73 for _, transitionStr := range cases { 74 t.Run(transitionStr, func(t *testing.T) { 75 _, err := ParseTransition(transitionStr) 76 assert.Error(t, err) 77 }) 78 } 79 } 80 81 // drawTransitionTime draws a random EpochTransitionTime. 82 func drawTransitionTime(t *rapid.T) EpochTransitionTime { 83 day := time.Weekday(rapid.IntRange(0, 6).Draw(t, "wd")) 84 hour := rapid.Uint8Range(0, 23).Draw(t, "h") 85 minute := rapid.Uint8Range(0, 59).Draw(t, "m") 86 return EpochTransitionTime{day, hour, minute} 87 } 88 89 // TestInferTargetEndTime_Fixture is a single human-readable fixture test, 90 // in addition to the property-based rapid tests. 91 func TestInferTargetEndTime_Fixture(t *testing.T) { 92 // The target time is around midday Wednesday 93 // |S|M|T|W|T|F|S| 94 // | * | 95 ett := EpochTransitionTime{day: time.Wednesday, hour: 13, minute: 24} 96 // The current time is mid-morning on Friday. We are about 28% through the epoch in time terms 97 // |S|M|T|W|T|F|S| 98 // | * | 99 // Friday, November 20, 2020 11:44 100 curTime := time.Date(2020, 11, 20, 11, 44, 0, 0, time.UTC) 101 // We are 18% through the epoch in view terms - we are quite behind schedule 102 epochFractionComplete := .18 103 // We should still be able to infer the target switchover time: 104 // Wednesday, November 25, 2020 13:24 105 expectedTarget := time.Date(2020, 11, 25, 13, 24, 0, 0, time.UTC) 106 target := ett.inferTargetEndTime(curTime, epochFractionComplete) 107 assert.Equal(t, expectedTarget, target) 108 } 109 110 // TestInferTargetEndTime tests that we can infer "the most reasonable" target time. 111 func TestInferTargetEndTime_Rapid(t *testing.T) { 112 rapid.Check(t, func(t *rapid.T) { 113 ett := drawTransitionTime(t) 114 curTime := time.Unix(rapid.Int64().Draw(t, "ref_unix"), 0).UTC() 115 epochFractionComplete := rapid.Float64Range(0, 1).Draw(t, "pct_complete") 116 epochFractionRemaining := 1.0 - epochFractionComplete 117 118 target := ett.inferTargetEndTime(curTime, epochFractionComplete) 119 computedEndTime := curTime.Add(time.Duration(float64(epochLength) * epochFractionRemaining)) 120 // selected target must be the nearest to the computed end time 121 delta := computedEndTime.Sub(target).Abs() 122 assert.LessOrEqual(t, delta.Hours(), float64(24*7)/2) 123 // nearest date must be a target time 124 assert.Equal(t, ett.day, target.Weekday()) 125 assert.Equal(t, int(ett.hour), target.Hour()) 126 assert.Equal(t, int(ett.minute), target.Minute()) 127 }) 128 } 129 130 // TestFindNearestTargetTime tests finding the nearest target time to a reference time. 131 func TestFindNearestTargetTime(t *testing.T) { 132 rapid.Check(t, func(t *rapid.T) { 133 ett := drawTransitionTime(t) 134 ref := time.Unix(rapid.Int64().Draw(t, "ref_unix"), 0).UTC() 135 136 nearest := ett.findNearestTargetTime(ref) 137 distance := nearest.Sub(ref).Abs() 138 // nearest date must be at most 1/2 a week away 139 assert.LessOrEqual(t, distance.Hours(), float64(24*7)/2) 140 // nearest date must be a target time 141 assert.Equal(t, ett.day, nearest.Weekday()) 142 assert.Equal(t, int(ett.hour), nearest.Hour()) 143 assert.Equal(t, int(ett.minute), nearest.Minute()) 144 }) 145 }