go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/rpc/admin/analyze_multi_cls_test.go (about) 1 // Copyright 2023 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 admin 16 17 import ( 18 "testing" 19 20 "go.chromium.org/luci/cv/internal/changelist" 21 "go.chromium.org/luci/cv/internal/common" 22 "go.chromium.org/luci/cv/internal/run" 23 ) 24 25 func TestCLGraph(t *testing.T) { 26 t.Parallel() 27 28 t.Run("HasRootCL", func(t *testing.T) { 29 t.Run("2->1->0", func(t *testing.T) { 30 cls := generateRunCL(3) 31 setHardDepOn(cls[2], cls[1]) 32 setHardDepOn(cls[1], cls[0]) 33 if !makeCLGraph(cls).hasRootCL() { 34 t.Fail() 35 } 36 }) 37 38 t.Run("2->1->0->2", func(t *testing.T) { 39 cls := generateRunCL(3) 40 setHardDepOn(cls[2], cls[1]) 41 setHardDepOn(cls[1], cls[0]) 42 setSoftDepOn(cls[0], cls[2]) 43 if !makeCLGraph(cls).hasRootCL() { 44 t.Fail() 45 } 46 }) 47 48 t.Run("2->1->0->2", func(t *testing.T) { 49 cls := generateRunCL(3) 50 setSoftDepOn(cls[2], cls[1]) 51 setSoftDepOn(cls[1], cls[0]) 52 setSoftDepOn(cls[0], cls[2]) 53 if !makeCLGraph(cls).hasRootCL() { 54 t.Fail() 55 } 56 }) 57 58 t.Run("2->1->0;3->1;", func(t *testing.T) { 59 cls := generateRunCL(4) 60 setHardDepOn(cls[2], cls[1]) 61 setHardDepOn(cls[1], cls[0]) 62 setSoftDepOn(cls[3], cls[1]) 63 if makeCLGraph(cls).hasRootCL() { 64 t.Fail() 65 } 66 }) 67 t.Run("2->1->0->2;3->1;", func(t *testing.T) { 68 cls := generateRunCL(4) 69 setHardDepOn(cls[2], cls[1]) 70 setHardDepOn(cls[1], cls[0]) 71 setSoftDepOn(cls[0], cls[2]) 72 setSoftDepOn(cls[3], cls[1]) 73 if !makeCLGraph(cls).hasRootCL() { 74 t.Fail() 75 } 76 }) 77 }) 78 79 t.Run("DotGraph", func(t *testing.T) { 80 81 t.Run("2->1->0", func(t *testing.T) { 82 cls := generateRunCL(3) 83 setHardDepOn(cls[2], cls[1]) 84 setHardDepOn(cls[1], cls[0]) 85 dotGraph := makeCLGraph(cls).computeDotGraph() 86 expected := `digraph { 87 "gerrit/example.com/0" [href="https://example.com/c/0", target="_parent"] 88 "gerrit/example.com/1" [href="https://example.com/c/1", target="_parent"] 89 "gerrit/example.com/2" [href="https://example.com/c/2", target="_parent"] 90 91 "gerrit/example.com/1" -> "gerrit/example.com/0" 92 "gerrit/example.com/2" -> "gerrit/example.com/1" 93 }` 94 if dotGraph != expected { 95 t.Fatalf("expecting:\n%s\ngot:\n%s", expected, dotGraph) 96 } 97 }) 98 99 t.Run("2->1->0->2", func(t *testing.T) { 100 cls := generateRunCL(3) 101 setSoftDepOn(cls[2], cls[1]) 102 setSoftDepOn(cls[1], cls[0]) 103 setSoftDepOn(cls[0], cls[2]) 104 dotGraph := makeCLGraph(cls).computeDotGraph() 105 expected := `digraph { 106 "gerrit/example.com/0" [href="https://example.com/c/0", target="_parent"] 107 "gerrit/example.com/1" [href="https://example.com/c/1", target="_parent"] 108 "gerrit/example.com/2" [href="https://example.com/c/2", target="_parent"] 109 110 "gerrit/example.com/0" -> "gerrit/example.com/2"[style="dotted"] 111 "gerrit/example.com/1" -> "gerrit/example.com/0"[style="dotted"] 112 "gerrit/example.com/2" -> "gerrit/example.com/1"[style="dotted"] 113 }` 114 if dotGraph != expected { 115 t.Fatalf("expecting:\n%s\ngot:\n%s", expected, dotGraph) 116 } 117 }) 118 119 }) 120 121 } 122 123 // generate CL 0...n-1 124 func generateRunCL(n int) []*run.RunCL { 125 runCLs := make([]*run.RunCL, n) 126 for i := range runCLs { 127 runCLs[i] = &run.RunCL{ 128 ID: common.CLID(i), 129 ExternalID: changelist.MustGobID("example.com", int64(i)), 130 Detail: &changelist.Snapshot{ 131 Kind: &changelist.Snapshot_Gerrit{ 132 Gerrit: &changelist.Gerrit{ 133 Host: "example.com", 134 }, 135 }, 136 }, 137 } 138 } 139 return runCLs 140 } 141 142 func setHardDepOn(from, to *run.RunCL) { 143 _, change, err := to.ExternalID.ParseGobID() 144 if err != nil { 145 panic(err) 146 } 147 from.Detail.GetGerrit().GitDeps = append(from.Detail.GetGerrit().GitDeps, &changelist.GerritGitDep{ 148 Change: change, 149 Immediate: true, 150 }) 151 } 152 153 func setSoftDepOn(from, to *run.RunCL) { 154 host, change, err := to.ExternalID.ParseGobID() 155 if err != nil { 156 panic(err) 157 } 158 from.Detail.GetGerrit().SoftDeps = append(from.Detail.GetGerrit().SoftDeps, &changelist.GerritSoftDep{ 159 Host: host, 160 Change: change, 161 }) 162 }