go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/api/gitiles/refset_test.go (about) 1 // Copyright 2018 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 gitiles 16 17 import ( 18 "context" 19 "regexp" 20 "testing" 21 22 "github.com/golang/mock/gomock" 23 . "github.com/smartystreets/goconvey/convey" 24 "go.chromium.org/luci/common/errors" 25 "go.chromium.org/luci/common/proto" 26 "go.chromium.org/luci/common/proto/gitiles" 27 "go.chromium.org/luci/common/proto/gitiles/mock_gitiles" 28 "go.chromium.org/luci/config/validation" 29 ) 30 31 func TestRefSet(t *testing.T) { 32 t.Parallel() 33 34 Convey("RefSet", t, func() { 35 wr := NewRefSet([]string{ 36 `refs/heads/master`, 37 `regexp:refs/branch-heads/\d+\.\d+`, 38 `regexp:refs/missing/many.+`, 39 `refs/missing/exact`, 40 }) 41 42 Convey("explicit refs", func() { 43 So(wr.Has("refs/heads/master"), ShouldBeTrue) 44 So(wr.Has("refs/heads/foo"), ShouldBeFalse) 45 }) 46 47 Convey("regexp refs", func() { 48 So(wr.Has("refs/branch-heads/1.12"), ShouldBeTrue) 49 So(wr.Has("refs/branch-heads/1.12.123"), ShouldBeFalse) 50 }) 51 52 Convey("resolve ref tips", func() { 53 ctx := context.Background() 54 ctl := gomock.NewController(t) 55 defer ctl.Finish() 56 mockClient := mock_gitiles.NewMockGitilesClient(ctl) 57 58 mockClient.EXPECT().Refs(gomock.Any(), proto.MatcherEqual(&gitiles.RefsRequest{ 59 Project: "project", RefsPath: "refs/heads", 60 })).Return( 61 &gitiles.RefsResponse{Revisions: map[string]string{ 62 "refs/heads/master": "01234567", 63 "refs/heads/foobar": "89abcdef", 64 }}, nil, 65 ) 66 mockClient.EXPECT().Refs(gomock.Any(), proto.MatcherEqual(&gitiles.RefsRequest{ 67 Project: "project", RefsPath: "refs/missing", 68 })).Return(&gitiles.RefsResponse{}, nil) 69 70 Convey("normal", func() { 71 mockClient.EXPECT().Refs(gomock.Any(), proto.MatcherEqual(&gitiles.RefsRequest{ 72 Project: "project", RefsPath: "refs/branch-heads", 73 })).Return( 74 &gitiles.RefsResponse{Revisions: map[string]string{ 75 "refs/branch-heads/1.9": "cafedead", 76 "refs/branch-heads/1.10": "deadcafe", 77 "refs/branch-heads/1.11.123": "deadbeef", 78 }}, nil, 79 ) 80 81 refTips, missing, err := wr.Resolve(ctx, mockClient, "project") 82 So(err, ShouldBeNil) 83 So(refTips, ShouldResemble, map[string]string{ 84 "refs/heads/master": "01234567", 85 "refs/branch-heads/1.9": "cafedead", 86 "refs/branch-heads/1.10": "deadcafe", 87 }) 88 So(missing, ShouldResemble, []string{`refs/missing/exact`, `regexp:refs/missing/many.+`}) 89 }) 90 91 Convey("failed RPCs", func() { 92 mockClient.EXPECT().Refs(gomock.Any(), proto.MatcherEqual(&gitiles.RefsRequest{ 93 Project: "project", RefsPath: "refs/branch-heads", 94 })).Return( 95 nil, errors.New("foobar"), 96 ) 97 98 _, _, err := wr.Resolve(ctx, mockClient, "project") 99 So(err.Error(), ShouldContainSubstring, "foobar") 100 }) 101 }) 102 }) 103 104 Convey("ValidateRefSet", t, func() { 105 ctx := &validation.Context{Context: context.Background()} 106 107 Convey("plain refs", func() { 108 Convey("too few slashes", func() { 109 ValidateRefSet(ctx, []string{`refs/foo`}) 110 So(ctx.Finalize().Error(), ShouldContainSubstring, 111 `fewer than 2 slashes in ref "refs/foo"`) 112 }) 113 Convey("does not start with refs/", func() { 114 ValidateRefSet(ctx, []string{`foo/bar/baz`}) 115 So(ctx.Finalize().Error(), ShouldContainSubstring, 116 `ref must start with 'refs/' not "foo/bar/baz"`) 117 }) 118 Convey("valid", func() { 119 ValidateRefSet(ctx, []string{`refs/heads/master`}) 120 So(ctx.Finalize(), ShouldBeNil) 121 }) 122 }) 123 124 Convey("regexp refs", func() { 125 Convey("starts with ^ or ends with $", func() { 126 ValidateRefSet(ctx, []string{`regexp:^refs/branch-heads/\d+\.\d+$`}) 127 So(ctx.Finalize().Error(), ShouldContainSubstring, 128 `^ and $ qualifiers are added automatically, please remove them`) 129 }) 130 Convey("invalid regexp", func() { 131 ValidateRefSet(ctx, []string{`regexp:([{`}) 132 So(ctx.Finalize().Error(), ShouldContainSubstring, `invalid regexp`) 133 }) 134 Convey("matches single ref only is fine", func() { 135 ValidateRefSet(ctx, []string{`regexp:refs/h[e]ad(s)/m[a]ster`}) 136 So(ctx.Finalize(), ShouldBeNil) 137 }) 138 Convey("fewer than 2 slashes in literal prefix", func() { 139 ValidateRefSet(ctx, []string{`regexp:refs/branch[-_]heads/\d+\/\d+`}) 140 So(ctx.Finalize().Error(), ShouldContainSubstring, 141 `fewer than 2 slashes in literal prefix "refs/branch"`) 142 }) 143 Convey("does not start with refs/", func() { 144 ValidateRefSet(ctx, []string{`regexp:foo/branch-heads/\d+\/\d+`}) 145 So(ctx.Finalize().Error(), ShouldContainSubstring, 146 `literal prefix "foo/branch-heads/" must start with "refs/"`) 147 }) 148 Convey("non-trivial ref prefix is supported", func() { 149 ValidateRefSet(ctx, []string{`regexp:refs/foo\.bar/\d+`}) 150 So(ctx.Finalize(), ShouldBeNil) 151 }) 152 Convey("not-trivial literal prefix is supported", func() { 153 ValidateRefSet(ctx, []string{`regexp:refs/branch-heads/(6\.8|6\.9)\.\d+`}) 154 So(ctx.Finalize(), ShouldBeNil) 155 }) 156 Convey("valid", func() { 157 ValidateRefSet(ctx, []string{`regexp:refs/branch-heads/\d+\.\d+`}) 158 So(ctx.Finalize(), ShouldBeNil) 159 }) 160 }) 161 }) 162 163 Convey("smoke test of LiteralPrefix not working as expected", t, func() { 164 r := "refs/heads/\\d+\\.\\d+.\\d" 165 l1, _ := regexp.MustCompile(r).LiteralPrefix() 166 l2, _ := regexp.MustCompile("^" + r + "$").LiteralPrefix() 167 So(l1, ShouldResemble, "refs/heads/") 168 So(l2, ShouldResemble, "") // See https://github.com/golang/go/issues/30425 169 NewRefSet([]string{"regexp:" + r}) 170 }) 171 }