go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/luciexe/legacy/annotee/step_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 annotee 16 17 import ( 18 "context" 19 "fmt" 20 "net/url" 21 "testing" 22 23 "google.golang.org/protobuf/types/known/structpb" 24 "google.golang.org/protobuf/types/known/timestamppb" 25 26 pb "go.chromium.org/luci/buildbucket/proto" 27 annotpb "go.chromium.org/luci/luciexe/legacy/annotee/proto" 28 29 . "github.com/smartystreets/goconvey/convey" 30 . "go.chromium.org/luci/common/testing/assertions" 31 ) 32 33 var annotationStep = &annotpb.Step{ 34 Substep: asSubSteps( 35 &annotpb.Step{ 36 Name: "running step", 37 Status: annotpb.Status_RUNNING, 38 }, 39 &annotpb.Step{ 40 Name: "successful step", 41 Status: annotpb.Status_SUCCESS, 42 Started: ×tamppb.Timestamp{Seconds: 1400000000}, 43 Ended: ×tamppb.Timestamp{Seconds: 1400001000}, 44 }, 45 &annotpb.Step{ 46 Name: "failed step", 47 Status: annotpb.Status_FAILURE, 48 Started: ×tamppb.Timestamp{Seconds: 1400000000}, 49 Ended: ×tamppb.Timestamp{Seconds: 1400001000}, 50 }, 51 &annotpb.Step{ 52 Name: "infra-failed step", 53 Status: annotpb.Status_FAILURE, 54 FailureDetails: &annotpb.FailureDetails{Type: annotpb.FailureDetails_EXCEPTION}, 55 Started: ×tamppb.Timestamp{Seconds: 1400000000}, 56 Ended: ×tamppb.Timestamp{Seconds: 1400001000}, 57 }, 58 &annotpb.Step{ 59 Name: "with failure details text", 60 Status: annotpb.Status_FAILURE, 61 FailureDetails: &annotpb.FailureDetails{Text: "failure_details_text"}, 62 Started: ×tamppb.Timestamp{Seconds: 1400000000}, 63 Ended: ×tamppb.Timestamp{Seconds: 1400001000}, 64 }, 65 &annotpb.Step{ 66 Name: "with text", 67 Text: []string{"text1", "text2"}, 68 }, 69 &annotpb.Step{ 70 Name: "with stdio", 71 StdoutStream: &annotpb.LogdogStream{Name: "steps/setup_build/0/stdout"}, 72 StderrStream: &annotpb.LogdogStream{Name: "steps/setup_build/0/stderr"}, 73 }, 74 &annotpb.Step{ 75 Name: "other links", 76 OtherLinks: []*annotpb.AnnotationLink{ 77 { 78 Label: "logdog link", 79 Value: &annotpb.AnnotationLink_LogdogStream{ 80 LogdogStream: &annotpb.LogdogStream{Name: "steps/setup_build/0/logs/run_recipe/0"}, 81 }, 82 }, 83 { 84 Label: "1", 85 Value: &annotpb.AnnotationLink_Url{ 86 Url: "https://example.com/1(foo)", 87 }, 88 }, 89 { 90 Label: "with-ampersand", 91 Value: &annotpb.AnnotationLink_Url{ 92 Url: "https://example.com?a=1×tamp=2", 93 }, 94 }, 95 }, 96 }, 97 &annotpb.Step{ 98 Name: "substeps", 99 // This time will be overridden by children. 100 Started: ×tamppb.Timestamp{Seconds: 1500000500}, 101 Ended: ×tamppb.Timestamp{Seconds: 1500000501}, 102 Substep: asSubSteps( 103 &annotpb.Step{ 104 Name: "child", 105 Status: annotpb.Status_SUCCESS, 106 Substep: asSubSteps( 107 &annotpb.Step{ 108 Name: "descendant0", 109 Status: annotpb.Status_FAILURE, 110 Started: ×tamppb.Timestamp{Seconds: 1500000000}, 111 Ended: ×tamppb.Timestamp{Seconds: 1500001000}, 112 }, 113 &annotpb.Step{ 114 Name: "descendant1", 115 Status: annotpb.Status_FAILURE, 116 FailureDetails: &annotpb.FailureDetails{Type: annotpb.FailureDetails_EXCEPTION}, 117 Started: ×tamppb.Timestamp{Seconds: 1500001000}, 118 Ended: ×tamppb.Timestamp{Seconds: 1500002000}, 119 }, 120 ), 121 }, 122 &annotpb.Step{ 123 Name: "child2", 124 Status: annotpb.Status_SUCCESS, 125 Started: ×tamppb.Timestamp{Seconds: 1500002000}, 126 Ended: ×tamppb.Timestamp{Seconds: 1500003000}, 127 }, 128 &annotpb.Step{ 129 Name: "child3_unfinished", 130 Status: annotpb.Status_RUNNING, 131 Started: ×tamppb.Timestamp{Seconds: 1500003000}, 132 }, 133 ), 134 }, 135 &annotpb.Step{ 136 Name: "started_parent", 137 Substep: asSubSteps( 138 &annotpb.Step{ 139 Name: "descendant", 140 Status: annotpb.Status_RUNNING, 141 Started: ×tamppb.Timestamp{Seconds: 1500000000}, 142 }, 143 ), 144 }, 145 &annotpb.Step{ 146 Name: "duplicate_log_name", 147 StdoutStream: &annotpb.LogdogStream{Name: "steps/duplicate_log_name/0/stdout"}, 148 StderrStream: &annotpb.LogdogStream{Name: "steps/duplicate_log_name/0/stderr"}, 149 OtherLinks: []*annotpb.AnnotationLink{ 150 { 151 Label: "stdout", 152 Value: &annotpb.AnnotationLink_LogdogStream{ 153 LogdogStream: &annotpb.LogdogStream{Name: "steps/duplicate_log_name/0/stdout"}, 154 }, 155 }, 156 }, 157 }, 158 &annotpb.Step{Name: "dup step name"}, 159 &annotpb.Step{Name: "dup step name"}, 160 &annotpb.Step{ 161 Name: "parent_prefix", 162 Substep: asSubSteps( 163 &annotpb.Step{ 164 Name: "parent_prefix.child", 165 Substep: asSubSteps( 166 &annotpb.Step{ 167 Name: "parent_prefix.child.grandchild", 168 }, 169 ), 170 }, 171 ), 172 }, 173 &annotpb.Step{ 174 Name: "start time is a bit greater than end time", 175 Status: annotpb.Status_SUCCESS, 176 Started: ×tamppb.Timestamp{Seconds: 1500000000, Nanos: 2}, 177 Ended: ×tamppb.Timestamp{Seconds: 1500000000, Nanos: 1}, 178 }, 179 ), 180 } 181 182 type calcURLFunc func(logName string) string 183 184 var expectedStepsFn = func(urlFunc, viewerURLFunc calcURLFunc) []*pb.Step { 185 return []*pb.Step{ 186 { 187 Name: "running step", 188 Status: pb.Status_SCHEDULED, 189 }, 190 { 191 Name: "successful step", 192 Status: pb.Status_SUCCESS, 193 StartTime: ×tamppb.Timestamp{Seconds: 1400000000}, 194 EndTime: ×tamppb.Timestamp{Seconds: 1400001000}, 195 }, 196 { 197 Name: "failed step", 198 Status: pb.Status_FAILURE, 199 StartTime: ×tamppb.Timestamp{Seconds: 1400000000}, 200 EndTime: ×tamppb.Timestamp{Seconds: 1400001000}, 201 }, 202 { 203 Name: "infra-failed step", 204 Status: pb.Status_INFRA_FAILURE, 205 StartTime: ×tamppb.Timestamp{Seconds: 1400000000}, 206 EndTime: ×tamppb.Timestamp{Seconds: 1400001000}, 207 }, 208 { 209 Name: "with failure details text", 210 Status: pb.Status_FAILURE, 211 SummaryMarkdown: "failure_details_text", 212 StartTime: ×tamppb.Timestamp{Seconds: 1400000000}, 213 EndTime: ×tamppb.Timestamp{Seconds: 1400001000}, 214 }, 215 { 216 Name: "with text", 217 Status: pb.Status_SCHEDULED, 218 SummaryMarkdown: "\n\n<div>text1 text2</div>\n\n", 219 }, 220 { 221 Name: "with stdio", 222 Status: pb.Status_SCHEDULED, 223 Logs: []*pb.Log{ 224 { 225 Name: "stdout", 226 Url: urlFunc("steps/setup_build/0/stdout"), 227 ViewUrl: viewerURLFunc("steps/setup_build/0/stdout"), 228 }, 229 { 230 Name: "stderr", 231 Url: urlFunc("steps/setup_build/0/stderr"), 232 ViewUrl: viewerURLFunc("steps/setup_build/0/stderr"), 233 }, 234 }, 235 }, 236 { 237 Name: "other links", 238 Status: pb.Status_SCHEDULED, 239 SummaryMarkdown: "* [1](https://example.com/1\\(foo\\))\n* [with-ampersand](https://example.com?a=1&timestamp=2)", 240 Logs: []*pb.Log{ 241 { 242 Name: "logdog link", 243 Url: urlFunc("steps/setup_build/0/logs/run_recipe/0"), 244 ViewUrl: viewerURLFunc("steps/setup_build/0/logs/run_recipe/0"), 245 }, 246 }, 247 }, 248 { 249 Name: "substeps", 250 Status: pb.Status_STARTED, 251 StartTime: ×tamppb.Timestamp{Seconds: 1500000000}, 252 }, 253 { 254 Name: "substeps|child", 255 Status: pb.Status_STARTED, 256 StartTime: ×tamppb.Timestamp{Seconds: 1500000000}, 257 }, 258 { 259 Name: "substeps|child|descendant0", 260 Status: pb.Status_FAILURE, 261 StartTime: ×tamppb.Timestamp{Seconds: 1500000000}, 262 EndTime: ×tamppb.Timestamp{Seconds: 1500001000}, 263 }, 264 { 265 Name: "substeps|child|descendant1", 266 Status: pb.Status_INFRA_FAILURE, 267 StartTime: ×tamppb.Timestamp{Seconds: 1500001000}, 268 EndTime: ×tamppb.Timestamp{Seconds: 1500002000}, 269 }, 270 { 271 Name: "substeps|child2", 272 Status: pb.Status_SUCCESS, 273 StartTime: ×tamppb.Timestamp{Seconds: 1500002000}, 274 EndTime: ×tamppb.Timestamp{Seconds: 1500003000}, 275 }, 276 { 277 Name: "substeps|child3_unfinished", 278 Status: pb.Status_STARTED, 279 StartTime: ×tamppb.Timestamp{Seconds: 1500003000}, 280 }, 281 { 282 Name: "started_parent", 283 Status: pb.Status_STARTED, 284 StartTime: ×tamppb.Timestamp{Seconds: 1500000000}, 285 }, 286 { 287 Name: "started_parent|descendant", 288 Status: pb.Status_STARTED, 289 StartTime: ×tamppb.Timestamp{Seconds: 1500000000}, 290 }, 291 { 292 Name: "duplicate_log_name", 293 Status: pb.Status_SCHEDULED, 294 Logs: []*pb.Log{ 295 { 296 Name: "stdout", 297 Url: urlFunc("steps/duplicate_log_name/0/stdout"), 298 ViewUrl: viewerURLFunc("steps/duplicate_log_name/0/stdout"), 299 }, 300 { 301 Name: "stderr", 302 Url: urlFunc("steps/duplicate_log_name/0/stderr"), 303 ViewUrl: viewerURLFunc("steps/duplicate_log_name/0/stderr"), 304 }, 305 }, 306 }, 307 { 308 Name: "dup step name", 309 Status: pb.Status_SCHEDULED, 310 }, 311 { 312 Name: "dup step name (2)", 313 Status: pb.Status_SCHEDULED, 314 }, 315 { 316 Name: "parent_prefix", 317 Status: pb.Status_SCHEDULED, 318 }, 319 { 320 Name: "parent_prefix|child", 321 Status: pb.Status_SCHEDULED, 322 }, 323 { 324 Name: "parent_prefix|child|grandchild", 325 Status: pb.Status_SCHEDULED, 326 }, 327 { 328 Name: "start time is a bit greater than end time", 329 Status: pb.Status_SUCCESS, 330 StartTime: ×tamppb.Timestamp{Seconds: 1500000000, Nanos: 1}, 331 EndTime: ×tamppb.Timestamp{Seconds: 1500000000, Nanos: 2}, 332 }, 333 } 334 335 } 336 337 func TestConvertBuildStep(t *testing.T) { 338 t.Parallel() 339 340 Convey("convert", t, func() { 341 Convey("with LogDog URL constructed", func() { 342 host := "logdog.example.com" 343 prefix := "project/prefix" 344 345 actual, err := ConvertBuildSteps(context.Background(), annotationStep.Substep, true, host, prefix) 346 So(err, ShouldBeNil) 347 expected := expectedStepsFn( 348 func(logName string) string { 349 return fmt.Sprintf("logdog://%s/%s/+/%s", host, prefix, logName) 350 }, 351 func(logName string) string { 352 return fmt.Sprintf("https://%s/v/?s=%s", host, url.QueryEscape(prefix+"/+/"+logName)) 353 }, 354 ) 355 So(actual, ShouldResembleProto, expected) 356 }) 357 Convey("without LogDog URL constructed", func() { 358 actual, err := ConvertBuildSteps(context.Background(), annotationStep.Substep, false, "", "") 359 So(err, ShouldBeNil) 360 expected := expectedStepsFn( 361 func(logName string) string { return logName }, 362 func(logName string) string { return "" }, 363 ) 364 So(actual, ShouldResembleProto, expected) 365 }) 366 }) 367 } 368 369 func TestConvertRootStep(t *testing.T) { 370 t.Parallel() 371 372 Convey("convert", t, func() { 373 rootStep := &annotpb.Step{ 374 Started: ×tamppb.Timestamp{Seconds: 1400000000}, 375 Ended: ×tamppb.Timestamp{Seconds: 1500000000}, 376 Status: annotpb.Status_SUCCESS, 377 Substep: asSubSteps( 378 &annotpb.Step{ 379 Name: "cool step", 380 Status: annotpb.Status_SUCCESS, 381 Started: ×tamppb.Timestamp{Seconds: 1400000000}, 382 Ended: ×tamppb.Timestamp{Seconds: 1500000000}, 383 Property: []*annotpb.Step_Property{ 384 { 385 Name: "string_prop", 386 Value: "\"baz\"", 387 }, 388 }, 389 }, 390 ), 391 StdoutStream: &annotpb.LogdogStream{Name: "build/stdout"}, 392 OtherLinks: []*annotpb.AnnotationLink{ 393 { 394 Label: "awesome_log", 395 Value: &annotpb.AnnotationLink_LogdogStream{ 396 LogdogStream: &annotpb.LogdogStream{Name: "build/awesome"}, 397 }, 398 }, 399 }, 400 Text: []string{ 401 "text one", 402 "text two", 403 }, 404 Property: []*annotpb.Step_Property{ 405 { 406 Name: "map_prop", 407 Value: "{\"foo\" : \"bar\"}", 408 }, 409 }, 410 } 411 412 expectedBuild := &pb.Build{ 413 StartTime: ×tamppb.Timestamp{Seconds: 1400000000}, 414 EndTime: ×tamppb.Timestamp{Seconds: 1500000000}, 415 Status: pb.Status_SUCCESS, 416 Steps: []*pb.Step{ 417 { 418 Name: "cool step", 419 Status: pb.Status_SUCCESS, 420 StartTime: ×tamppb.Timestamp{Seconds: 1400000000}, 421 EndTime: ×tamppb.Timestamp{Seconds: 1500000000}, 422 }, 423 }, 424 SummaryMarkdown: "\n\n<div>text one text two</div>\n\n", 425 Output: &pb.Build_Output{ 426 Logs: []*pb.Log{ 427 { 428 Name: "stdout", 429 Url: "build/stdout", 430 }, 431 { 432 Name: "awesome_log", 433 Url: "build/awesome", 434 }, 435 }, 436 Properties: &structpb.Struct{ 437 Fields: map[string]*structpb.Value{ 438 "map_prop": { 439 Kind: &structpb.Value_StructValue{ 440 StructValue: &structpb.Struct{ 441 Fields: map[string]*structpb.Value{ 442 "foo": { 443 Kind: &structpb.Value_StringValue{ 444 StringValue: "bar", 445 }, 446 }, 447 }, 448 }, 449 }, 450 }, 451 "string_prop": { 452 Kind: &structpb.Value_StringValue{ 453 StringValue: "baz", 454 }, 455 }, 456 }, 457 }, 458 SummaryMarkdown: "\n\n<div>text one text two</div>\n\n", 459 Status: pb.Status_SUCCESS, 460 }, 461 } 462 463 var test = func() { 464 actual, err := ConvertRootStep(context.Background(), rootStep) 465 So(err, ShouldBeNil) 466 So(actual, ShouldResembleProto, expectedBuild) 467 } 468 469 test() 470 471 Convey("infra failure build", func() { 472 rootStep.Status = annotpb.Status_FAILURE 473 rootStep.FailureDetails = &annotpb.FailureDetails{ 474 Type: annotpb.FailureDetails_INFRA, 475 Text: "bad infra failure", 476 } 477 478 expectedBuild.Status = pb.Status_INFRA_FAILURE 479 expectedBuild.SummaryMarkdown = "bad infra failure\n\n" + expectedBuild.SummaryMarkdown 480 expectedBuild.Output.Status = pb.Status_INFRA_FAILURE 481 expectedBuild.Output.SummaryMarkdown = expectedBuild.SummaryMarkdown 482 test() 483 }) 484 Convey("worst step status", func() { 485 rootStep.Substep[0].GetStep().Status = annotpb.Status_FAILURE 486 487 expectedBuild.Steps[0].Status = pb.Status_FAILURE 488 expectedBuild.Status = pb.Status_FAILURE 489 expectedBuild.Output.Status = pb.Status_FAILURE 490 test() 491 }) 492 Convey("use largest step end time", func() { 493 rootStep.Substep[0].GetStep().Ended = ×tamppb.Timestamp{Seconds: 1600000000} 494 495 expectedBuild.Steps[0].EndTime = ×tamppb.Timestamp{Seconds: 1600000000} 496 expectedBuild.EndTime = ×tamppb.Timestamp{Seconds: 1600000000} 497 test() 498 }) 499 500 }) 501 } 502 503 func asSubSteps(subSteps ...*annotpb.Step) []*annotpb.Step_Substep { 504 ret := make([]*annotpb.Step_Substep, len(subSteps)) 505 for i, subStep := range subSteps { 506 ret[i] = &annotpb.Step_Substep{ 507 Substep: &annotpb.Step_Substep_Step{ 508 Step: subStep, 509 }, 510 } 511 } 512 return ret 513 }