go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/buildbucket/cli/print_test.go (about) 1 // Copyright 2019 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 cli 16 17 import ( 18 "bytes" 19 "regexp" 20 "strings" 21 "testing" 22 "time" 23 24 "github.com/golang/protobuf/proto" 25 "github.com/mgutz/ansi" 26 "google.golang.org/protobuf/encoding/protojson" 27 28 "go.chromium.org/luci/common/clock/testclock" 29 30 pb "go.chromium.org/luci/buildbucket/proto" 31 32 . "github.com/smartystreets/goconvey/convey" 33 ) 34 35 var buildJSON = ` 36 { 37 "id": "8917899588926498064", 38 "builder": { 39 "project": "chromium", 40 "bucket": "try", 41 "builder": "linux-rel" 42 }, 43 "number": 1, 44 "createdBy": "user:5071639625-1lppvbtck1morgivc6sq4dul7klu27sd@developer.gserviceaccount.com", 45 "createTime": "2019-03-26T18:33:47.958975Z", 46 "startTime": "2019-03-26T18:33:52.447816Z", 47 "endTime": "2019-03-26T18:37:13.892433Z", 48 "updateTime": "2019-03-26T18:37:14.629154Z", 49 "status": "SUCCESS", 50 "summaryMarkdown": "it was ok", 51 "canary": true, 52 "input": { 53 "experimental": true, 54 "experiments": ["luci.buildbucket.canary_software", "luci.non_production"], 55 "properties": { 56 "$recipe_engine/cq": { 57 "dry_run": true 58 }, 59 "$recipe_engine/runtime": { 60 "is_experimental": false 61 }, 62 "blamelist": [ 63 "hinoka@chromium.org" 64 ], 65 "buildername": "Luci-go Mac Tester", 66 "category": "cq" 67 }, 68 "gitilesCommit": { 69 "host": "chromium.googlesource.com", 70 "project": "infra/luci/luci-go", 71 "ref": "refs/heads/x", 72 "id": "deadbeef" 73 }, 74 "gerritChanges": [ 75 { 76 "host": "chromium-review.googlesource.com", 77 "project": "infra/luci/luci-go", 78 "change": "1539021", 79 "patchset": "1" 80 } 81 ] 82 }, 83 "output": { 84 "properties": { 85 "$recipe_engine/cq": { 86 "dry_run": true 87 }, 88 "bot_id": "build234-m9" 89 } 90 }, 91 "steps": [ 92 { 93 "name": "first", 94 "startTime": "2019-03-26T18:34:01.896053Z", 95 "endTime": "2019-03-26T18:34:01.954986Z", 96 "status": "SUCCESS", 97 "logs": [ 98 { 99 "name": "stdout", 100 "viewUrl": "https://logs.example.com/first/stdout" 101 }, 102 { 103 "name": "stderr", 104 "viewUrl": "https://logs.example.com/first/stderr" 105 } 106 ], 107 "summaryMarkdown": "first summary markdown\nsummary second line" 108 }, 109 { 110 "name": "second", 111 "startTime": "2019-03-26T18:34:01.896053Z", 112 "endTime": "2019-03-26T18:34:01.954986Z", 113 "status": "SUCCESS", 114 "logs": [ 115 { 116 "name": "stdout", 117 "viewUrl": "https://logs.example.com/second/stdout" 118 }, 119 { 120 "name": "stderr", 121 "viewUrl": "https://logs.example.com/second/stderr" 122 } 123 ] 124 } 125 ], 126 "infra": { 127 "buildbucket": { 128 "serviceConfigRevision": "80aefcf1d2f6c25894a968312eca86ba0f80737c" 129 }, 130 "swarming": { 131 "hostname": "chromium-swarm.appspot.com", 132 "taskId": "43d4192e94d9ff10", 133 "taskServiceAccount": "infra-try-builder@chops-service-accounts.iam.gserviceaccount.com" 134 }, 135 "logdog": { 136 "hostname": "logs.chromium.org", 137 "project": "infra", 138 "prefix": "buildbucket/cr-buildbucket.appspot.com/8917899588926498064" 139 } 140 }, 141 "tags": [ 142 { 143 "key": "buildset", 144 "value": "patch/gerrit/chromium-review.googlesource.com/1539021/1" 145 }, 146 { 147 "key": "cq_experimental", 148 "value": "false" 149 }, 150 { 151 "key": "user_agent", 152 "value": "cq" 153 } 154 ] 155 }` 156 157 const expectedBuildPrintedTemplate = `<white+b><white+u><green+h>http://ci.chromium.org/b/8917899588926498064<reset><white+b><green+h> SUCCESS 'chromium/try/linux-rel/1'<reset> 158 <white+b>Summary<reset>: it was ok 159 <white+b>Experimental<reset> <white+b>Canary<reset> 160 <white+b>Created<reset> on 2019-03-26 at 18:33:47, <white+b>waited<reset> 4.5s, <white+b>started<reset> at 18:33:52, <white+b>ran<reset> for 3m21s, <white+b>ended<reset> at 18:37:13 161 <white+b>By<reset>: user:5071639625-1lppvbtck1morgivc6sq4dul7klu27sd@developer.gserviceaccount.com 162 <white+b>Commit<reset>: <white+u>https://crrev.com/deadbeef<reset> on refs/heads/x 163 <white+b>CL<reset>: <white+u>https://crrev.com/c/1539021/1<reset> 164 <white+b>Tag<reset>: buildset:patch/gerrit/chromium-review.googlesource.com/1539021/1 165 <white+b>Tag<reset>: cq_experimental:false 166 <white+b>Tag<reset>: user_agent:cq 167 <white+b>Input properties<reset>: { 168 "$recipe_engine/cq": { 169 "dry_run": true 170 }, 171 "$recipe_engine/runtime": { 172 "is_experimental": false 173 }, 174 "blamelist": [ 175 "hinoka@chromium.org" 176 ], 177 "buildername": "Luci-go Mac Tester", 178 "category": "cq" 179 } 180 <white+b>Output properties<reset>: { 181 "$recipe_engine/cq": { 182 "dry_run": true 183 }, 184 "bot_id": "build234-m9" 185 } 186 <white+b>Experiments<reset>: 187 luci.buildbucket.canary_software 188 luci.non_production 189 <green+h>Step "first" SUCCESS 59ms Logs: "stdout", "stderr"<reset> 190 first summary markdown 191 summary second line 192 <green+h>Step "second" SUCCESS 59ms Logs: "stdout", "stderr"<reset> 193 ` 194 195 func ansifyTemplate(template string) string { 196 return regexp.MustCompile("<[^>]+>").ReplaceAllStringFunc( 197 template, 198 func(style string) string { 199 style = strings.TrimPrefix(style, "<") 200 style = strings.TrimSuffix(style, ">") 201 return ansi.ColorCode(style) 202 }) 203 } 204 205 func TestPrint(t *testing.T) { 206 Convey("Print", t, func() { 207 buf := &bytes.Buffer{} 208 p := newPrinter(buf, false, func() time.Time { 209 return testclock.TestRecentTimeUTC 210 }) 211 212 Convey("Build", func() { 213 build := &pb.Build{} 214 So(protojson.Unmarshal([]byte(buildJSON), build), ShouldBeNil) 215 216 expectedBuildPrinted := ansifyTemplate(expectedBuildPrintedTemplate) 217 p.Build(build) 218 219 So(p.Err, ShouldBeNil) 220 So(buf.String(), ShouldEqual, expectedBuildPrinted) 221 }) 222 223 Convey("Partial build", func() { 224 // Only Id, Status and Builder are available 225 build := &pb.Build{ 226 Id: 8917899588926498064, 227 Status: pb.Status_STARTED, 228 Builder: &pb.BuilderID{ 229 Project: "chromium", 230 Bucket: "try", 231 Builder: "linux-rel", 232 }, 233 } 234 expectedBuildPrinted := ansifyTemplate("<white+b><white+u><yellow+h>http://ci.chromium.org/b/8917899588926498064<reset><white+b><yellow+h> STARTED 'chromium/try/linux-rel'<reset>\n") 235 p.Build(build) 236 237 So(p.Err, ShouldBeNil) 238 So(buf.String(), ShouldEqual, expectedBuildPrinted) 239 }) 240 241 Convey("Build error when any of required fields is missing", func() { 242 validBuild := &pb.Build{ 243 Id: 8917899588926498064, 244 Status: pb.Status_STARTED, 245 Builder: &pb.BuilderID{ 246 Project: "chromium", 247 Bucket: "try", 248 Builder: "linux-rel", 249 }, 250 } 251 // Make sure Build can be printed 252 p.Build(validBuild) 253 So(p.Err, ShouldBeNil) 254 255 buildWithoutID := proto.Clone(validBuild).(*pb.Build) 256 buildWithoutID.Id = 0 257 So(func() { p.Build(buildWithoutID) }, ShouldPanic) 258 259 buildWithoutStatus := proto.Clone(validBuild).(*pb.Build) 260 buildWithoutStatus.Status = pb.Status_STATUS_UNSPECIFIED 261 So(func() { p.Build(buildWithoutStatus) }, ShouldPanic) 262 263 buildWithoutBuilder := proto.Clone(validBuild).(*pb.Build) 264 buildWithoutBuilder.Builder = nil 265 So(func() { p.Build(buildWithoutBuilder) }, ShouldPanic) 266 }) 267 268 Convey("Chromium commit", func() { 269 p.commit(&pb.GitilesCommit{ 270 Host: "chromium.googlesource.com", 271 Project: "chromium/src", 272 Ref: "refs/heads/x", 273 Id: "deadbeef", 274 }) 275 276 So(p.Err, ShouldBeNil) 277 So(buf.String(), ShouldEqual, ansi.Color("https://crrev.com/deadbeef", "white+u")+" on refs/heads/x") 278 }) 279 280 Convey("Chromium commit without id", func() { 281 p.commit(&pb.GitilesCommit{ 282 Host: "chromium.googlesource.com", 283 Project: "infra/luci/luci-go", 284 Ref: "refs/heads/x", 285 }) 286 287 So(p.Err, ShouldBeNil) 288 So(buf.String(), ShouldEqual, ansi.Color("https://chromium.googlesource.com/infra/luci/luci-go/+/refs/heads/x", "white+u")) 289 }) 290 291 Convey("Arbitrary commit", func() { 292 p.commit(&pb.GitilesCommit{ 293 Host: "fuchsia.googlesource.com", 294 Project: "infra", 295 Ref: "refs/heads/x", 296 Id: "deadbeef", 297 }) 298 299 So(p.Err, ShouldBeNil) 300 So(buf.String(), ShouldEqual, ansi.Color("https://fuchsia.googlesource.com/infra/+/deadbeef", "white+u")+" on refs/heads/x") 301 }) 302 303 Convey("Arbitrary commit without id", func() { 304 p.commit(&pb.GitilesCommit{ 305 Host: "fuchsia.googlesource.com", 306 Project: "infra", 307 Ref: "refs/heads/x", 308 }) 309 310 So(p.Err, ShouldBeNil) 311 So(buf.String(), ShouldEqual, ansi.Color("https://fuchsia.googlesource.com/infra/+/refs/heads/x", "white+u")) 312 }) 313 314 Convey("Chromium CL", func() { 315 p.change(&pb.GerritChange{ 316 Host: "chromium-review.googlesource.com", 317 Project: "infra/luci/luci-go", 318 Change: 123, 319 Patchset: 4, 320 }) 321 322 So(p.Err, ShouldBeNil) 323 So(buf.String(), ShouldEqual, ansi.Color("https://crrev.com/c/123/4", "white+u")) 324 }) 325 326 Convey("Chrome CL", func() { 327 p.change(&pb.GerritChange{ 328 Host: "chrome-internal-review.googlesource.com", 329 Project: "secret", 330 Change: 123, 331 Patchset: 4, 332 }) 333 334 So(p.Err, ShouldBeNil) 335 So(buf.String(), ShouldEqual, ansi.Color("https://crrev.com/i/123/4", "white+u")) 336 }) 337 }) 338 }