go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/gerrit/mirror_test.go (about) 1 // Copyright 2021 The LUCI Authors. 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 gerrit 16 17 import ( 18 "context" 19 "fmt" 20 "sort" 21 "testing" 22 23 "google.golang.org/grpc" 24 25 "go.chromium.org/luci/common/errors" 26 27 . "github.com/smartystreets/goconvey/convey" 28 29 . "go.chromium.org/luci/common/testing/assertions" 30 ) 31 32 func TestMirrorIterator(t *testing.T) { 33 t.Parallel() 34 35 Convey("MirrorIterator and its factory work", t, func() { 36 ctx := context.Background() 37 const baseHost = "a.example.com" 38 Convey("No mirrors", func() { 39 it := newMirrorIterator(ctx) 40 So(it.Empty(), ShouldBeFalse) 41 So(it.next()(baseHost), ShouldResemble, baseHost) 42 So(it.Empty(), ShouldBeTrue) 43 So(it.next()(baseHost), ShouldResemble, baseHost) 44 So(it.Empty(), ShouldBeTrue) 45 So(it.next()(baseHost), ShouldResemble, baseHost) 46 }) 47 Convey("One mirrors", func() { 48 it := newMirrorIterator(ctx, "m1-") 49 So(it.Empty(), ShouldBeFalse) 50 So(it.next()(baseHost), ShouldResemble, baseHost) 51 So(it.Empty(), ShouldBeFalse) 52 So(it.next()(baseHost), ShouldResemble, "m1-"+baseHost) 53 So(it.Empty(), ShouldBeTrue) 54 So(it.next()(baseHost), ShouldResemble, baseHost) 55 }) 56 Convey("Shuffles mirrors", func() { 57 prefixes := make([]string, 10) 58 expectedHosts := make([]string, len(prefixes)+1) 59 expectedHosts[0] = baseHost 60 for i := range prefixes { 61 // use "m" prefix such that its lexicographically after baseHost itself. 62 p := fmt.Sprintf("m%d-", i) 63 prefixes[i] = p 64 expectedHosts[i+1] = p + baseHost 65 } 66 iterate := func() []string { 67 var actual []string 68 it := newMirrorIterator(ctx, prefixes...) 69 for !it.Empty() { 70 actual = append(actual, it.next()(baseHost)) 71 } 72 return actual 73 } 74 act1 := iterate() 75 So(act1, ShouldNotResemble, expectedHosts) 76 act2 := iterate() 77 So(act2, ShouldNotResemble, expectedHosts) 78 So(act1, ShouldNotResemble, act2) 79 80 sort.Strings(act1) 81 So(act1, ShouldResemble, expectedHosts) 82 sort.Strings(act2) 83 So(act2, ShouldResemble, expectedHosts) 84 }) 85 Convey("RetryIfStale works", func() { 86 it := &MirrorIterator{"", "m1", "m2"} 87 88 Convey("stops when mirrors are exhausted", func() { 89 tried := 0 90 err := it.RetryIfStale(func(grpc.CallOption) error { 91 tried += 1 92 return ErrStaleData 93 }) 94 So(err, ShouldEqual, ErrStaleData) 95 So(tried, ShouldEqual, 3) 96 }) 97 98 Convey("respects returned value, unwrapping if needed", func() { 99 tried := 0 100 err := it.RetryIfStale(func(grpc.CallOption) error { 101 tried += 1 102 if tried == 1 { 103 return errors.Annotate(ErrStaleData, "try #%d", tried).Err() 104 } 105 return errors.New("something else") 106 }) 107 So(err, ShouldErrLike, "something else") 108 So(tried, ShouldEqual, 2) 109 So((*it)[0], ShouldResemble, "m2") 110 }) 111 112 Convey("calls at least once even if empty", func() { 113 it.Next() 114 it.Next() 115 it.Next() 116 So(it.Empty(), ShouldBeTrue) 117 called := false 118 err := it.RetryIfStale(func(grpc.CallOption) error { 119 called = true 120 return nil 121 }) 122 So(err, ShouldBeNil) 123 So(called, ShouldBeTrue) 124 }) 125 }) 126 }) 127 }