go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/luciexe/host/buildmerge/build_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 buildmerge 16 17 import ( 18 "errors" 19 "strings" 20 "testing" 21 22 "github.com/golang/protobuf/ptypes" 23 "google.golang.org/protobuf/proto" 24 "google.golang.org/protobuf/types/known/structpb" 25 26 bbpb "go.chromium.org/luci/buildbucket/proto" 27 "go.chromium.org/luci/common/clock/testclock" 28 29 . "github.com/smartystreets/goconvey/convey" 30 31 . "go.chromium.org/luci/common/testing/assertions" 32 ) 33 34 func TestSetErrorOnBuild(t *testing.T) { 35 t.Parallel() 36 Convey(`setErrorOnBuild`, t, func() { 37 Convey(`basic`, func() { 38 build := &bbpb.Build{ 39 Output: &bbpb.Build_Output{}, 40 } 41 setErrorOnBuild(build, errors.New("hi")) 42 So(build, ShouldResembleProto, &bbpb.Build{ 43 SummaryMarkdown: "\n\nError in build protocol: hi", 44 Status: bbpb.Status_INFRA_FAILURE, 45 Output: &bbpb.Build_Output{ 46 Status: bbpb.Status_INFRA_FAILURE, 47 SummaryMarkdown: "\n\nError in build protocol: hi", 48 }, 49 }) 50 }) 51 52 Convey(`truncated message`, func() { 53 build := &bbpb.Build{ 54 SummaryMarkdown: strings.Repeat("16 test pattern\n", 256), 55 Output: &bbpb.Build_Output{}, 56 } 57 setErrorOnBuild(build, errors.New("hi")) 58 So(build.SummaryMarkdown, ShouldHaveLength, 4096) 59 So(build.SummaryMarkdown, ShouldEndWith, "...\n\nError in build protocol: hi") 60 So(build.Output.SummaryMarkdown, ShouldEqual, build.SummaryMarkdown) 61 build.SummaryMarkdown = "" 62 build.Output.SummaryMarkdown = "" 63 So(build, ShouldResembleProto, &bbpb.Build{ 64 Status: bbpb.Status_INFRA_FAILURE, 65 Output: &bbpb.Build_Output{ 66 Status: bbpb.Status_INFRA_FAILURE, 67 }, 68 }) 69 }) 70 }) 71 } 72 73 func TestProcessFinalBuild(t *testing.T) { 74 t.Parallel() 75 Convey(`processFinalBuild`, t, func() { 76 now, err := ptypes.TimestampProto(testclock.TestRecentTimeLocal) 77 So(err, ShouldBeNil) 78 79 Convey(`empty`, func() { 80 build := &bbpb.Build{ 81 Output: &bbpb.Build_Output{}, 82 } 83 processFinalBuild(now, build) 84 So(build, ShouldResembleProto, &bbpb.Build{ 85 SummaryMarkdown: ("\n\nError in build protocol: " + 86 "Expected a terminal build status, got STATUS_UNSPECIFIED, while top level status is STATUS_UNSPECIFIED."), 87 UpdateTime: now, 88 EndTime: now, 89 Status: bbpb.Status_INFRA_FAILURE, 90 Output: &bbpb.Build_Output{ 91 Status: bbpb.Status_INFRA_FAILURE, 92 SummaryMarkdown: ("\n\nError in build protocol: " + 93 "Expected a terminal build status, got STATUS_UNSPECIFIED, while top level status is STATUS_UNSPECIFIED."), 94 }, 95 }) 96 }) 97 98 Convey(`success`, func() { 99 build := &bbpb.Build{ 100 Status: bbpb.Status_SUCCESS, 101 Steps: []*bbpb.Step{ 102 {Status: bbpb.Status_SUCCESS}, 103 }, 104 Output: &bbpb.Build_Output{ 105 Status: bbpb.Status_SUCCESS, 106 }, 107 } 108 processFinalBuild(now, build) 109 So(build, ShouldResembleProto, &bbpb.Build{ 110 UpdateTime: now, 111 EndTime: now, 112 Status: bbpb.Status_SUCCESS, 113 Steps: []*bbpb.Step{ 114 {Status: bbpb.Status_SUCCESS, EndTime: now}, 115 }, 116 Output: &bbpb.Build_Output{ 117 Status: bbpb.Status_SUCCESS, 118 }, 119 }) 120 }) 121 122 Convey(`incomplete step`, func() { 123 build := &bbpb.Build{ 124 Status: bbpb.Status_SUCCESS, 125 Steps: []*bbpb.Step{ 126 {Status: bbpb.Status_SUCCESS}, 127 {SummaryMarkdown: "hi"}, 128 }, 129 Output: &bbpb.Build_Output{ 130 Status: bbpb.Status_SUCCESS, 131 }, 132 } 133 processFinalBuild(now, build) 134 So(build, ShouldResembleProto, &bbpb.Build{ 135 UpdateTime: now, 136 EndTime: now, 137 Status: bbpb.Status_SUCCESS, 138 Steps: []*bbpb.Step{ 139 {Status: bbpb.Status_SUCCESS, EndTime: now}, 140 { 141 Status: bbpb.Status_CANCELED, 142 EndTime: now, 143 SummaryMarkdown: "hi\nstep was never finalized; did the build crash?", 144 }, 145 }, 146 Output: &bbpb.Build_Output{ 147 Status: bbpb.Status_SUCCESS, 148 }, 149 }) 150 }) 151 }) 152 } 153 154 func TestUpdateStepFromBuild(t *testing.T) { 155 Convey(`updateStepFromBuild`, t, func() { 156 now, err := ptypes.TimestampProto(testclock.TestRecentTimeLocal) 157 So(err, ShouldBeNil) 158 159 Convey(`basic`, func() { 160 step := &bbpb.Step{ 161 Logs: []*bbpb.Log{{Name: "something"}}, 162 } 163 build := &bbpb.Build{ 164 SummaryMarkdown: "hi", 165 Status: bbpb.Status_FAILURE, 166 EndTime: now, 167 Output: &bbpb.Build_Output{ 168 Logs: []*bbpb.Log{{Name: "other"}}, 169 Status: bbpb.Status_FAILURE, 170 }, 171 } 172 updateStepFromBuild(step, build) 173 So(step, ShouldResembleProto, &bbpb.Step{ 174 SummaryMarkdown: "hi", 175 Status: bbpb.Status_FAILURE, 176 EndTime: now, 177 Logs: []*bbpb.Log{ 178 {Name: "something"}, 179 {Name: "other"}, 180 }, 181 }) 182 }) 183 Convey(`step status terminal`, func() { 184 step := &bbpb.Step{ 185 Status: bbpb.Status_INFRA_FAILURE, 186 SummaryMarkdown: "hi step", 187 EndTime: now, 188 Logs: []*bbpb.Log{{Name: "step something"}}, 189 } 190 build := &bbpb.Build{ 191 SummaryMarkdown: "hi sub build", 192 Status: bbpb.Status_FAILURE, 193 Output: &bbpb.Build_Output{ 194 Logs: []*bbpb.Log{{Name: "build other"}}, 195 Status: bbpb.Status_FAILURE, 196 }, 197 } 198 updateStepFromBuild(step, build) 199 So(step, ShouldResembleProto, &bbpb.Step{ 200 SummaryMarkdown: "hi step", 201 Status: bbpb.Status_INFRA_FAILURE, 202 EndTime: now, 203 Logs: []*bbpb.Log{ 204 {Name: "step something"}, 205 }, 206 }) 207 }) 208 }) 209 } 210 211 func TestUpdateBaseFromUserbuild(t *testing.T) { 212 Convey(`updateBaseFromUserBuild`, t, func() { 213 now, err := ptypes.TimestampProto(testclock.TestRecentTimeLocal) 214 So(err, ShouldBeNil) 215 216 Convey(`basic`, func() { 217 base := &bbpb.Build{ 218 Steps: []*bbpb.Step{{Name: "sup"}}, 219 } 220 build := &bbpb.Build{ 221 SummaryMarkdown: "hi", 222 Status: bbpb.Status_CANCELED, 223 StatusDetails: &bbpb.StatusDetails{ 224 Timeout: &bbpb.StatusDetails_Timeout{}, 225 }, 226 UpdateTime: now, 227 EndTime: now, 228 Tags: []*bbpb.StringPair{ 229 {Key: "hi", Value: "there"}, 230 }, 231 Output: &bbpb.Build_Output{ 232 Logs: []*bbpb.Log{{Name: "other"}}, 233 Status: bbpb.Status_CANCELED, 234 StatusDetails: &bbpb.StatusDetails{ 235 Timeout: &bbpb.StatusDetails_Timeout{}, 236 }, 237 }, 238 } 239 buildClone := proto.Clone(build).(*bbpb.Build) 240 buildClone.Steps = append(buildClone.Steps, &bbpb.Step{Name: "sup"}) 241 updateBaseFromUserBuild(base, build) 242 So(base, ShouldResembleProto, buildClone) 243 }) 244 245 Convey(`nil build`, func() { 246 base := &bbpb.Build{ 247 Steps: []*bbpb.Step{{Name: "sup"}}, 248 SummaryMarkdown: "hi", 249 } 250 baseClone := proto.Clone(base).(*bbpb.Build) 251 updateBaseFromUserBuild(base, nil) 252 So(base, ShouldResembleProto, baseClone) 253 }) 254 255 Convey(`output is merged`, func() { 256 base := &bbpb.Build{ 257 Output: &bbpb.Build_Output{ 258 Logs: []*bbpb.Log{ 259 {Name: "hello"}, 260 }, 261 }, 262 } 263 updateBaseFromUserBuild(base, &bbpb.Build{ 264 Output: &bbpb.Build_Output{ 265 Logs: []*bbpb.Log{ 266 {Name: "world"}, 267 }, 268 }, 269 }) 270 So(base, ShouldResembleProto, &bbpb.Build{ 271 Output: &bbpb.Build_Output{ 272 Logs: []*bbpb.Log{ 273 {Name: "hello"}, 274 {Name: "world"}, 275 }, 276 }, 277 }) 278 }) 279 }) 280 } 281 282 func TestUpdateBuildFromGlobalSubBuild(t *testing.T) { 283 Convey(`TestUpdateBuildFromGlobalSubBuild`, t, func() { 284 base := &bbpb.Build{} 285 sub := &bbpb.Build{} 286 287 Convey(`empty parent`, func() { 288 Convey(`empty child`, func() { 289 updateBuildFromGlobalSubBuild(base, sub) 290 So(base, ShouldResembleProto, &bbpb.Build{}) 291 }) 292 293 Convey(`properties`, func() { 294 s, err := structpb.NewStruct(map[string]any{ 295 "hello": "world", 296 "this": 100, 297 }) 298 So(err, ShouldBeNil) 299 sub.Output = &bbpb.Build_Output{Properties: s} 300 updateBuildFromGlobalSubBuild(base, sub) 301 m := base.Output.Properties.AsMap() 302 So(m, ShouldResemble, map[string]any{ 303 "hello": "world", 304 "this": 100.0, // because JSON semantics 305 }) 306 }) 307 }) 308 309 Convey(`populated parent`, func() { 310 s, err := structpb.NewStruct(map[string]any{ 311 "hello": "world", 312 "this": 100, 313 }) 314 So(err, ShouldBeNil) 315 base.Output = &bbpb.Build_Output{ 316 Properties: s, 317 } 318 319 Convey(`empty child`, func() { 320 updateBuildFromGlobalSubBuild(base, sub) 321 So(base, ShouldResembleProto, &bbpb.Build{ 322 Output: &bbpb.Build_Output{ 323 Properties: s, 324 }, 325 }) 326 }) 327 328 Convey(`properties`, func() { 329 sSub, err := structpb.NewStruct(map[string]any{ 330 "newkey": "yes", 331 "hello": "replacement", 332 }) 333 So(err, ShouldBeNil) 334 sub.Output = &bbpb.Build_Output{Properties: sSub} 335 updateBuildFromGlobalSubBuild(base, sub) 336 337 sNew, err := structpb.NewStruct(map[string]any{ 338 "hello": "replacement", 339 "this": 100, 340 "newkey": "yes", 341 }) 342 So(err, ShouldBeNil) 343 So(base, ShouldResembleProto, &bbpb.Build{ 344 Output: &bbpb.Build_Output{ 345 Properties: sNew, 346 }, 347 }) 348 }) 349 350 }) 351 }) 352 }