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  }