go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/tryjob/execute/reuse_common_test.go (about) 1 // Copyright 2022 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 execute 16 17 import ( 18 "fmt" 19 "testing" 20 21 "google.golang.org/protobuf/types/known/timestamppb" 22 23 recipe "go.chromium.org/luci/cv/api/recipe/v1" 24 "go.chromium.org/luci/cv/internal/changelist" 25 "go.chromium.org/luci/cv/internal/cvtesting" 26 "go.chromium.org/luci/cv/internal/run" 27 "go.chromium.org/luci/cv/internal/tryjob" 28 29 . "github.com/smartystreets/goconvey/convey" 30 ) 31 32 func TestCanReuse(t *testing.T) { 33 t.Parallel() 34 35 Convey("canReuseTryjob works", t, func() { 36 ct := cvtesting.Test{} 37 ctx, cancel := ct.SetUp(t) 38 defer cancel() 39 40 Convey("reuse allowed", func() { 41 Convey("empty mode allowlist", func() { 42 tj := &tryjob.Tryjob{ 43 Status: tryjob.Status_ENDED, 44 Result: &tryjob.Result{ 45 CreateTime: timestamppb.New(ct.Clock.Now().Add(-staleTryjobAge / 2)), 46 Status: tryjob.Result_SUCCEEDED, 47 }, 48 } 49 So(canReuseTryjob(ctx, tj, run.FullRun), ShouldEqual, reuseAllowed) 50 }) 51 52 Convey("explicitly allowed in mode allowlist", func() { 53 tj := &tryjob.Tryjob{ 54 Status: tryjob.Status_ENDED, 55 Result: &tryjob.Result{ 56 CreateTime: timestamppb.New(ct.Clock.Now().Add(-staleTryjobAge / 2)), 57 Status: tryjob.Result_SUCCEEDED, 58 Output: &recipe.Output{ 59 Reusability: &recipe.Output_Reusability{ 60 ModeAllowlist: []string{string(run.DryRun), string(run.FullRun)}, 61 }, 62 }, 63 }, 64 } 65 So(canReuseTryjob(ctx, tj, run.FullRun), ShouldEqual, reuseAllowed) 66 }) 67 }) 68 69 Convey("reuse maybe", func() { 70 Convey("triggered fresh tryjob", func() { 71 tj := &tryjob.Tryjob{ 72 Status: tryjob.Status_TRIGGERED, 73 Result: &tryjob.Result{ 74 CreateTime: timestamppb.New(ct.Clock.Now().Add(-staleTryjobAge / 2)), 75 }, 76 } 77 So(canReuseTryjob(ctx, tj, run.FullRun), ShouldEqual, reuseMaybe) 78 }) 79 80 Convey("pending tryjob", func() { 81 tj := &tryjob.Tryjob{ 82 Status: tryjob.Status_PENDING, 83 } 84 So(canReuseTryjob(ctx, tj, run.FullRun), ShouldEqual, reuseMaybe) 85 }) 86 }) 87 88 Convey("reuse denied", func() { 89 Convey("triggered stale tryjob", func() { 90 tj := &tryjob.Tryjob{ 91 Status: tryjob.Status_TRIGGERED, 92 Result: &tryjob.Result{ 93 CreateTime: timestamppb.New(ct.Clock.Now().Add(-staleTryjobAge * 2)), 94 }, 95 } 96 So(canReuseTryjob(ctx, tj, run.FullRun), ShouldEqual, reuseDenied) 97 }) 98 99 Convey("successfully ended tryjob but stale", func() { 100 tj := &tryjob.Tryjob{ 101 Status: tryjob.Status_ENDED, 102 Result: &tryjob.Result{ 103 CreateTime: timestamppb.New(ct.Clock.Now().Add(-staleTryjobAge * 2)), 104 Status: tryjob.Result_SUCCEEDED, 105 }, 106 } 107 So(canReuseTryjob(ctx, tj, run.FullRun), ShouldEqual, reuseDenied) 108 }) 109 110 Convey("failed tryjob", func() { 111 tj := &tryjob.Tryjob{ 112 Status: tryjob.Status_ENDED, 113 Result: &tryjob.Result{ 114 CreateTime: timestamppb.New(ct.Clock.Now().Add(-staleTryjobAge * 2)), 115 Status: tryjob.Result_FAILED_PERMANENTLY, 116 }, 117 } 118 So(canReuseTryjob(ctx, tj, run.FullRun), ShouldEqual, reuseDenied) 119 }) 120 121 Convey("not in the mode allowlist", func() { 122 tj := &tryjob.Tryjob{ 123 Status: tryjob.Status_ENDED, 124 Result: &tryjob.Result{ 125 CreateTime: timestamppb.New(ct.Clock.Now().Add(-staleTryjobAge * 2)), 126 Status: tryjob.Result_SUCCEEDED, 127 Output: &recipe.Output{ 128 Reusability: &recipe.Output_Reusability{ 129 ModeAllowlist: []string{string(run.DryRun)}, 130 }, 131 }, 132 }, 133 } 134 So(canReuseTryjob(ctx, tj, run.FullRun), ShouldEqual, reuseDenied) 135 }) 136 137 for _, st := range []tryjob.Status{tryjob.Status_CANCELLED, tryjob.Status_UNTRIGGERED} { 138 Convey(fmt.Sprintf("status is %s", st), func() { 139 tj := &tryjob.Tryjob{Status: st} 140 So(canReuseTryjob(ctx, tj, run.FullRun), ShouldEqual, reuseDenied) 141 }) 142 } 143 }) 144 }) 145 } 146 147 func TestComputeReuseKey(t *testing.T) { 148 t.Parallel() 149 150 Convey("computeReuseKey works", t, func() { 151 cls := []*run.RunCL{ 152 { 153 ID: 22222, 154 Detail: &changelist.Snapshot{ 155 MinEquivalentPatchset: 22, 156 }, 157 }, 158 { 159 ID: 11111, 160 Detail: &changelist.Snapshot{ 161 MinEquivalentPatchset: 11, 162 }, 163 }, 164 } 165 // Should yield the same result as 166 // > python3 -c 'import base64;from hashlib import sha256;print(base64.b64encode(sha256(b"\0".join(sorted(b"%d/%d"%(x[0], x[1]) for x in [[22222,22],[11111,11]]))).digest()))' 167 So(computeReuseKey(cls), ShouldEqual, "2Yh+hI8zJZFe8ac1TrrFjATWGjhiV9aXsKjNJIhzATk=") 168 }) 169 }