github.com/letsencrypt/trillian@v1.1.2-0.20180615153820-ae375a99d36a/util/election/runner_test.go (about) 1 // Copyright 2017 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package election_test 16 17 import ( 18 "context" 19 "errors" 20 "math" 21 "sync" 22 "testing" 23 "time" 24 25 "github.com/google/trillian/util" 26 "github.com/google/trillian/util/election" 27 "github.com/google/trillian/util/election/stub" 28 ) 29 30 func TestShouldResign(t *testing.T) { 31 startTime := time.Date(1970, 9, 19, 12, 00, 00, 00, time.UTC) 32 var tests = []struct { 33 hold time.Duration 34 odds int 35 wantHold time.Duration 36 wantProb float64 37 }{ 38 {hold: 12 * time.Second, odds: 10, wantHold: 12 * time.Second, wantProb: 0.1}, 39 {hold: time.Second, odds: 10, wantHold: 10 * time.Second, wantProb: 0.1}, 40 {hold: 10 * time.Second, odds: 0, wantHold: 10 * time.Second, wantProb: 1.0}, 41 {hold: 10 * time.Second, odds: 1, wantHold: 10 * time.Second, wantProb: 1.0}, 42 {hold: 10 * time.Second, odds: -1, wantHold: 10 * time.Second, wantProb: 1.0}, 43 } 44 for _, test := range tests { 45 fakeTimeSource := util.FakeTimeSource{} 46 cfg := election.RunnerConfig{ 47 MasterHoldInterval: test.hold, 48 ResignOdds: test.odds, 49 TimeSource: &fakeTimeSource, 50 } 51 er := election.NewRunner(6962, &cfg, nil, nil, nil) 52 53 holdChecks := int64(10) 54 holdNanos := test.wantHold.Nanoseconds() / holdChecks 55 timeslot: 56 for i := int64(-1); i < holdChecks; i++ { 57 gapNanos := time.Duration(i * holdNanos) 58 fakeTimeSource.Set(startTime.Add(gapNanos)) 59 for j := 0; j < 100; j++ { 60 if er.ShouldResign(startTime) { 61 t.Errorf("shouldResign(hold=%v,odds=%v @ start+%v)=true; want false", test.hold, test.odds, gapNanos) 62 continue timeslot 63 } 64 } 65 } 66 67 fakeTimeSource.Set(startTime.Add(test.wantHold).Add(time.Nanosecond)) 68 iterations := 10000 69 count := 0 70 for i := 0; i < iterations; i++ { 71 if er.ShouldResign(startTime) { 72 count++ 73 } 74 } 75 got := float64(count) / float64(iterations) 76 deltaFraction := math.Abs(got-test.wantProb) / test.wantProb 77 if deltaFraction > 0.05 { 78 t.Errorf("P(shouldResign(hold=%v,odds=%v))=%f; want ~%f", test.hold, test.odds, got, test.wantProb) 79 } 80 } 81 } 82 83 // TODO(pavelkalinnikov): Reduce flakiness risk in this test by making fewer 84 // time assumptions. 85 func TestElectionRunnerRun(t *testing.T) { 86 fakeTimeSource := util.NewFakeTimeSource(time.Date(2016, 6, 28, 13, 40, 12, 45, time.UTC)) 87 cfg := election.RunnerConfig{TimeSource: fakeTimeSource} 88 var tests = []struct { 89 desc string 90 election *stub.MasterElection 91 lostMaster bool 92 wantMaster bool 93 }{ 94 // Basic cases 95 { 96 desc: "IsNotMaster", 97 election: stub.NewMasterElection(false, nil), 98 }, 99 { 100 desc: "IsMaster", 101 election: stub.NewMasterElection(true, nil), 102 wantMaster: true, 103 }, 104 { 105 desc: "LostMaster", 106 election: stub.NewMasterElection(true, nil), 107 lostMaster: true, 108 wantMaster: false, 109 }, 110 // Error cases 111 { 112 desc: "ErrorOnStart", 113 election: stub.NewMasterElection(false, 114 &stub.Errors{Start: errors.New("on start")}), 115 }, 116 { 117 desc: "ErrorOnWait", 118 election: stub.NewMasterElection(false, 119 &stub.Errors{Wait: errors.New("on wait")}), 120 }, 121 { 122 desc: "ErrorOnIsMaster", 123 election: stub.NewMasterElection(true, 124 &stub.Errors{IsMaster: errors.New("on IsMaster")}), 125 wantMaster: true, 126 }, 127 { 128 desc: "ErrorOnResign", 129 election: stub.NewMasterElection(true, 130 &stub.Errors{Resign: errors.New("resignation failure")}), 131 wantMaster: true, 132 }, 133 } 134 const logID = int64(6962) 135 for _, test := range tests { 136 t.Run(test.desc, func(t *testing.T) { 137 var wg sync.WaitGroup 138 ctx, cancel := context.WithCancel(context.Background()) 139 140 startTime := time.Now() 141 fakeTimeSource := util.NewFakeTimeSource(startTime) 142 143 el := test.election 144 tracker := election.NewMasterTracker([]int64{logID}, nil) 145 er := election.NewRunner(logID, &cfg, tracker, nil, el) 146 resignations := make(chan election.Resignation, 100) 147 wg.Add(1) 148 go func() { 149 defer wg.Done() 150 er.Run(ctx, resignations) 151 }() 152 time.Sleep((3 * election.MinPreElectionPause) / 2) 153 if test.lostMaster { 154 el.Update(false, nil) 155 } 156 157 // Advance fake time so that shouldResign() triggers too. 158 fakeTimeSource.Set(startTime.Add(24 * 60 * time.Hour)) 159 time.Sleep(election.MinMasterCheckInterval) 160 for len(resignations) > 0 { 161 r := <-resignations 162 r.Execute(ctx) 163 } 164 165 cancel() 166 wg.Wait() 167 held := tracker.Held() 168 if got := (len(held) > 0 && held[0] == logID); got != test.wantMaster { 169 t.Errorf("held=%v => master=%v; want %v", held, got, test.wantMaster) 170 } 171 }) 172 } 173 }