1// Copyright 2020 Google Inc. All rights reserved. 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 15package bazel 16 17import ( 18 "encoding/json" 19 "fmt" 20 "reflect" 21 "sort" 22 "testing" 23 24 analysis_v2_proto "prebuilts/bazel/common/proto/analysis_v2" 25 26 "github.com/google/blueprint/metrics" 27 "google.golang.org/protobuf/proto" 28) 29 30func TestAqueryMultiArchGenrule(t *testing.T) { 31 // This input string is retrieved from a real build of bionic-related genrules. 32 const inputString = ` 33{ 34 "Artifacts": [ 35 { "Id": 1, "path_fragment_id": 1 }, 36 { "Id": 2, "path_fragment_id": 6 }, 37 { "Id": 3, "path_fragment_id": 8 }, 38 { "Id": 4, "path_fragment_id": 12 }, 39 { "Id": 5, "path_fragment_id": 19 }, 40 { "Id": 6, "path_fragment_id": 20 }, 41 { "Id": 7, "path_fragment_id": 21 }], 42 "Actions": [{ 43 "target_id": 1, 44 "action_key": "ab53f6ecbdc2ee8cb8812613b63205464f1f5083f6dca87081a0a398c0f1ecf7", 45 "Mnemonic": "Genrule", 46 "configuration_id": 1, 47 "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py arm ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-arm.S"], 48 "environment_variables": [{ 49 "Key": "PATH", 50 "Value": "/bin:/usr/bin:/usr/local/bin" 51 }], 52 "input_dep_set_ids": [1], 53 "output_ids": [4], 54 "primary_output_id": 4 55 }, { 56 "target_id": 2, 57 "action_key": "9f4309ce165dac458498cb92811c18b0b7919782cc37b82a42d2141b8cc90826", 58 "Mnemonic": "Genrule", 59 "configuration_id": 1, 60 "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py x86 ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-x86.S"], 61 "environment_variables": [{ 62 "Key": "PATH", 63 "Value": "/bin:/usr/bin:/usr/local/bin" 64 }], 65 "input_dep_set_ids": [2], 66 "output_ids": [5], 67 "primary_output_id": 5 68 }, { 69 "target_id": 3, 70 "action_key": "50d6c586103ebeed3a218195540bcc30d329464eae36377eb82f8ce7c36ac342", 71 "Mnemonic": "Genrule", 72 "configuration_id": 1, 73 "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py x86_64 ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-x86_64.S"], 74 "environment_variables": [{ 75 "Key": "PATH", 76 "Value": "/bin:/usr/bin:/usr/local/bin" 77 }], 78 "input_dep_set_ids": [3], 79 "output_ids": [6], 80 "primary_output_id": 6 81 }, { 82 "target_id": 4, 83 "action_key": "f30cbe442f5216f4223cf16a39112cad4ec56f31f49290d85cff587e48647ffa", 84 "Mnemonic": "Genrule", 85 "configuration_id": 1, 86 "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py arm64 ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-arm64.S"], 87 "environment_variables": [{ 88 "Key": "PATH", 89 "Value": "/bin:/usr/bin:/usr/local/bin" 90 }], 91 "input_dep_set_ids": [4], 92 "output_ids": [7], 93 "primary_output_id": 7 94 }], 95 "Targets": [ 96 { "Id": 1, "Label": "@sourceroot//bionic/libc:syscalls-arm", "rule_class_id": 1 }, 97 { "Id": 2, "Label": "@sourceroot//bionic/libc:syscalls-x86", "rule_class_id": 1 }, 98 { "Id": 3, "Label": "@sourceroot//bionic/libc:syscalls-x86_64", "rule_class_id": 1 }, 99 { "Id": 4, "Label": "@sourceroot//bionic/libc:syscalls-arm64", "rule_class_id": 1 }], 100 "dep_set_of_files": [ 101 { "Id": 1, "direct_artifact_ids": [1, 2, 3] }, 102 { "Id": 2, "direct_artifact_ids": [1, 2, 3] }, 103 { "Id": 3, "direct_artifact_ids": [1, 2, 3] }, 104 { "Id": 4, "direct_artifact_ids": [1, 2, 3] }], 105 "Configuration": [{ 106 "Id": 1, 107 "Mnemonic": "k8-fastbuild", 108 "platform_name": "k8", 109 "Checksum": "485c362832c178e367d972177f68e69e0981e51e67ef1c160944473db53fe046" 110 }], 111 "rule_classes": [{ "Id": 1, "Name": "genrule"}], 112 "path_fragments": [ 113 { "Id": 5, "Label": ".." }, 114 { "Id": 4, "Label": "sourceroot", "parent_id": 5 }, 115 { "Id": 3, "Label": "bionic", "parent_id": 4 }, 116 { "Id": 2, "Label": "libc", "parent_id": 3 }, 117 { "Id": 1, "Label": "SYSCALLS.TXT", "parent_id": 2 }, 118 { "Id": 7, "Label": "tools", "parent_id": 2 }, 119 { "Id": 6, "Label": "gensyscalls.py", "parent_id": 7 }, 120 { "Id": 11, "Label": "bazel_tools", "parent_id": 5 }, 121 { "Id": 10, "Label": "tools", "parent_id": 11 }, 122 { "Id": 9, "Label": "genrule", "parent_id": 10 }, 123 { "Id": 8, "Label": "genrule-setup.sh", "parent_id": 9 }, 124 { "Id": 18, "Label": "bazel-out" }, 125 { "Id": 17, "Label": "sourceroot", "parent_id": 18 }, 126 { "Id": 16, "Label": "k8-fastbuild", "parent_id": 17 }, 127 { "Id": 15, "Label": "bin", "parent_id": 16 }, 128 { "Id": 14, "Label": "bionic", "parent_id": 15 }, 129 { "Id": 13, "Label": "libc", "parent_id": 14 }, 130 { "Id": 12, "Label": "syscalls-arm.S", "parent_id": 13 }, 131 { "Id": 19, "Label": "syscalls-x86.S", "parent_id": 13 }, 132 { "Id": 20, "Label": "syscalls-x86_64.S", "parent_id": 13 }, 133 { "Id": 21, "Label": "syscalls-arm64.S", "parent_id": 13 }] 134} 135` 136 data, err := JsonToActionGraphContainer(inputString) 137 if err != nil { 138 t.Error(err) 139 return 140 } 141 actualbuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{}) 142 var expectedBuildStatements []*BuildStatement 143 for _, arch := range []string{"arm", "arm64", "x86", "x86_64"} { 144 expectedBuildStatements = append(expectedBuildStatements, 145 &BuildStatement{ 146 Command: fmt.Sprintf( 147 "/bin/bash -c 'source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py %s ../sourceroot/bionic/libc/SYSCALLS.TXT > bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-%s.S'", 148 arch, arch), 149 OutputPaths: []string{ 150 fmt.Sprintf("bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-%s.S", arch), 151 }, 152 Env: []*analysis_v2_proto.KeyValuePair{ 153 {Key: "PATH", Value: "/bin:/usr/bin:/usr/local/bin"}, 154 }, 155 Mnemonic: "Genrule", 156 }) 157 } 158 assertBuildStatements(t, expectedBuildStatements, actualbuildStatements) 159 160 expectedFlattenedInputs := []string{ 161 "../sourceroot/bionic/libc/SYSCALLS.TXT", 162 "../sourceroot/bionic/libc/tools/gensyscalls.py", 163 } 164 // In this example, each depset should have the same expected inputs. 165 for _, actualDepset := range actualDepsets { 166 actualFlattenedInputs := flattenDepsets([]string{actualDepset.ContentHash}, actualDepsets) 167 if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) { 168 t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs) 169 } 170 } 171} 172 173func TestInvalidOutputId(t *testing.T) { 174 const inputString = ` 175{ 176 "artifacts": [ 177 { "id": 1, "path_fragment_id": 1 }, 178 { "id": 2, "path_fragment_id": 2 }], 179 "actions": [{ 180 "target_id": 1, 181 "action_key": "action_x", 182 "mnemonic": "X", 183 "arguments": ["touch", "foo"], 184 "input_dep_set_ids": [1], 185 "output_ids": [3], 186 "primary_output_id": 3 187 }], 188 "dep_set_of_files": [ 189 { "id": 1, "direct_artifact_ids": [1, 2] }], 190 "path_fragments": [ 191 { "id": 1, "label": "one" }, 192 { "id": 2, "label": "two" }] 193}` 194 195 data, err := JsonToActionGraphContainer(inputString) 196 if err != nil { 197 t.Error(err) 198 return 199 } 200 _, _, err = AqueryBuildStatements(data, &metrics.EventHandler{}) 201 assertError(t, err, "undefined outputId 3: [X] []") 202} 203 204func TestInvalidInputDepsetIdFromAction(t *testing.T) { 205 const inputString = ` 206{ 207 "artifacts": [ 208 { "id": 1, "path_fragment_id": 1 }, 209 { "id": 2, "path_fragment_id": 2 }], 210 "actions": [{ 211 "target_id": 1, 212 "action_key": "action_x", 213 "mnemonic": "X", 214 "arguments": ["touch", "foo"], 215 "input_dep_set_ids": [2], 216 "output_ids": [1], 217 "primary_output_id": 1 218 }], 219 "targets": [{ 220 "id": 1, 221 "label": "target_x" 222 }], 223 "dep_set_of_files": [ 224 { "id": 1, "direct_artifact_ids": [1, 2] }], 225 "path_fragments": [ 226 { "id": 1, "label": "one" }, 227 { "id": 2, "label": "two" }] 228}` 229 230 data, err := JsonToActionGraphContainer(inputString) 231 if err != nil { 232 t.Error(err) 233 return 234 } 235 _, _, err = AqueryBuildStatements(data, &metrics.EventHandler{}) 236 assertError(t, err, "undefined (not even empty) input depsetId 2: [X] [target_x]") 237} 238 239func TestInvalidInputDepsetIdFromDepset(t *testing.T) { 240 const inputString = ` 241{ 242 "artifacts": [ 243 { "id": 1, "path_fragment_id": 1 }, 244 { "id": 2, "path_fragment_id": 2 }], 245 "actions": [{ 246 "target_id": 1, 247 "action_key": "x", 248 "mnemonic": "x", 249 "arguments": ["touch", "foo"], 250 "input_dep_set_ids": [1], 251 "output_ids": [1], 252 "primary_output_id": 1 253 }], 254 "dep_set_of_files": [ 255 { "id": 1, "direct_artifact_ids": [1, 2], "transitive_dep_set_ids": [42] }], 256 "path_fragments": [ 257 { "id": 1, "label": "one"}, 258 { "id": 2, "label": "two" }] 259}` 260 261 data, err := JsonToActionGraphContainer(inputString) 262 if err != nil { 263 t.Error(err) 264 return 265 } 266 _, _, err = AqueryBuildStatements(data, &metrics.EventHandler{}) 267 assertError(t, err, "undefined input depsetId 42 (referenced by depsetId 1)") 268} 269 270func TestInvalidInputArtifactId(t *testing.T) { 271 const inputString = ` 272{ 273 "artifacts": [ 274 { "id": 1, "path_fragment_id": 1 }, 275 { "id": 2, "path_fragment_id": 2 }], 276 "actions": [{ 277 "target_id": 1, 278 "action_key": "x", 279 "mnemonic": "x", 280 "arguments": ["touch", "foo"], 281 "input_dep_set_ids": [1], 282 "output_ids": [1], 283 "primary_output_id": 1 284 }], 285 "dep_set_of_files": [ 286 { "id": 1, "direct_artifact_ids": [1, 3] }], 287 "path_fragments": [ 288 { "id": 1, "label": "one" }, 289 { "id": 2, "label": "two" }] 290}` 291 292 data, err := JsonToActionGraphContainer(inputString) 293 if err != nil { 294 t.Error(err) 295 return 296 } 297 _, _, err = AqueryBuildStatements(data, &metrics.EventHandler{}) 298 assertError(t, err, "undefined input artifactId 3") 299} 300 301func TestInvalidPathFragmentId(t *testing.T) { 302 const inputString = ` 303{ 304 "artifacts": [ 305 { "id": 1, "path_fragment_id": 1 }, 306 { "id": 2, "path_fragment_id": 2 }], 307 "actions": [{ 308 "target_id": 1, 309 "action_key": "x", 310 "mnemonic": "x", 311 "arguments": ["touch", "foo"], 312 "input_dep_set_ids": [1], 313 "output_ids": [1], 314 "primary_output_id": 1 315 }], 316 "dep_set_of_files": [ 317 { "id": 1, "direct_artifact_ids": [1, 2] }], 318 "path_fragments": [ 319 { "id": 1, "label": "one" }, 320 { "id": 2, "label": "two", "parent_id": 3 }] 321}` 322 323 data, err := JsonToActionGraphContainer(inputString) 324 if err != nil { 325 t.Error(err) 326 return 327 } 328 _, _, err = AqueryBuildStatements(data, &metrics.EventHandler{}) 329 assertError(t, err, "undefined path fragment id 3") 330} 331 332func TestDepfiles(t *testing.T) { 333 const inputString = ` 334{ 335 "artifacts": [ 336 { "id": 1, "path_fragment_id": 1 }, 337 { "id": 2, "path_fragment_id": 2 }, 338 { "id": 3, "path_fragment_id": 3 }], 339 "actions": [{ 340 "target_Id": 1, 341 "action_Key": "x", 342 "mnemonic": "x", 343 "arguments": ["touch", "foo"], 344 "input_dep_set_ids": [1], 345 "output_ids": [2, 3], 346 "primary_output_id": 2 347 }], 348 "dep_set_of_files": [ 349 { "id": 1, "direct_Artifact_Ids": [1, 2, 3] }], 350 "path_fragments": [ 351 { "id": 1, "label": "one" }, 352 { "id": 2, "label": "two" }, 353 { "id": 3, "label": "two.d" }] 354}` 355 356 data, err := JsonToActionGraphContainer(inputString) 357 if err != nil { 358 t.Error(err) 359 return 360 } 361 actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{}) 362 if err != nil { 363 t.Errorf("Unexpected error %q", err) 364 return 365 } 366 if expected := 1; len(actual) != expected { 367 t.Fatalf("Expected %d build statements, got %d", expected, len(actual)) 368 return 369 } 370 371 bs := actual[0] 372 expectedDepfile := "two.d" 373 if bs.Depfile == nil { 374 t.Errorf("Expected depfile %q, but there was none found", expectedDepfile) 375 } else if *bs.Depfile != expectedDepfile { 376 t.Errorf("Expected depfile %q, but got %q", expectedDepfile, *bs.Depfile) 377 } 378} 379 380func TestMultipleDepfiles(t *testing.T) { 381 const inputString = ` 382{ 383 "artifacts": [ 384 { "id": 1, "path_fragment_id": 1 }, 385 { "id": 2, "path_fragment_id": 2 }, 386 { "id": 3, "path_fragment_id": 3 }, 387 { "id": 4, "path_fragment_id": 4 }], 388 "actions": [{ 389 "target_id": 1, 390 "action_key": "action_x", 391 "mnemonic": "X", 392 "arguments": ["touch", "foo"], 393 "input_dep_set_ids": [1], 394 "output_ids": [2,3,4], 395 "primary_output_id": 2 396 }], 397 "dep_set_of_files": [{ 398 "id": 1, 399 "direct_artifact_ids": [1, 2, 3, 4] 400 }], 401 "path_fragments": [ 402 { "id": 1, "label": "one" }, 403 { "id": 2, "label": "two" }, 404 { "id": 3, "label": "two.d" }, 405 { "id": 4, "label": "other.d" }] 406}` 407 408 data, err := JsonToActionGraphContainer(inputString) 409 if err != nil { 410 t.Error(err) 411 return 412 } 413 _, _, err = AqueryBuildStatements(data, &metrics.EventHandler{}) 414 assertError(t, err, `found multiple potential depfiles "two.d", "other.d": [X] []`) 415} 416 417func TestTransitiveInputDepsets(t *testing.T) { 418 // The input aquery for this test comes from a proof-of-concept starlark rule which registers 419 // a single action with many inputs given via a deep depset. 420 const inputString = ` 421{ 422 "artifacts": [ 423 { "id": 1, "path_fragment_id": 1 }, 424 { "id": 2, "path_fragment_id": 7 }, 425 { "id": 3, "path_fragment_id": 8 }, 426 { "id": 4, "path_fragment_id": 9 }, 427 { "id": 5, "path_fragment_id": 10 }, 428 { "id": 6, "path_fragment_id": 11 }, 429 { "id": 7, "path_fragment_id": 12 }, 430 { "id": 8, "path_fragment_id": 13 }, 431 { "id": 9, "path_fragment_id": 14 }, 432 { "id": 10, "path_fragment_id": 15 }, 433 { "id": 11, "path_fragment_id": 16 }, 434 { "id": 12, "path_fragment_id": 17 }, 435 { "id": 13, "path_fragment_id": 18 }, 436 { "id": 14, "path_fragment_id": 19 }, 437 { "id": 15, "path_fragment_id": 20 }, 438 { "id": 16, "path_fragment_id": 21 }, 439 { "id": 17, "path_fragment_id": 22 }, 440 { "id": 18, "path_fragment_id": 23 }, 441 { "id": 19, "path_fragment_id": 24 }, 442 { "id": 20, "path_fragment_id": 25 }, 443 { "id": 21, "path_fragment_id": 26 }], 444 "actions": [{ 445 "target_id": 1, 446 "action_key": "3b826d17fadbbbcd8313e456b90ec47c078c438088891dd45b4adbcd8889dc50", 447 "mnemonic": "Action", 448 "configuration_id": 1, 449 "arguments": ["/bin/bash", "-c", "touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out"], 450 "input_dep_set_ids": [1], 451 "output_ids": [21], 452 "primary_output_id": 21 453 }], 454 "dep_set_of_files": [ 455 { "id": 3, "direct_artifact_ids": [1, 2, 3, 4, 5] }, 456 { "id": 4, "direct_artifact_ids": [6, 7, 8, 9, 10] }, 457 { "id": 2, "transitive_dep_set_ids": [3, 4], "direct_artifact_ids": [11, 12, 13, 14, 15] }, 458 { "id": 5, "direct_artifact_ids": [16, 17, 18, 19] }, 459 { "id": 1, "transitive_dep_set_ids": [2, 5], "direct_artifact_ids": [20] }], 460 "path_fragments": [ 461 { "id": 6, "label": "bazel-out" }, 462 { "id": 5, "label": "sourceroot", "parent_id": 6 }, 463 { "id": 4, "label": "k8-fastbuild", "parent_id": 5 }, 464 { "id": 3, "label": "bin", "parent_id": 4 }, 465 { "id": 2, "label": "testpkg", "parent_id": 3 }, 466 { "id": 1, "label": "test_1", "parent_id": 2 }, 467 { "id": 7, "label": "test_2", "parent_id": 2 }, 468 { "id": 8, "label": "test_3", "parent_id": 2 }, 469 { "id": 9, "label": "test_4", "parent_id": 2 }, 470 { "id": 10, "label": "test_5", "parent_id": 2 }, 471 { "id": 11, "label": "test_6", "parent_id": 2 }, 472 { "id": 12, "label": "test_7", "parent_id": 2 }, 473 { "id": 13, "label": "test_8", "parent_id": 2 }, 474 { "id": 14, "label": "test_9", "parent_id": 2 }, 475 { "id": 15, "label": "test_10", "parent_id": 2 }, 476 { "id": 16, "label": "test_11", "parent_id": 2 }, 477 { "id": 17, "label": "test_12", "parent_id": 2 }, 478 { "id": 18, "label": "test_13", "parent_id": 2 }, 479 { "id": 19, "label": "test_14", "parent_id": 2 }, 480 { "id": 20, "label": "test_15", "parent_id": 2 }, 481 { "id": 21, "label": "test_16", "parent_id": 2 }, 482 { "id": 22, "label": "test_17", "parent_id": 2 }, 483 { "id": 23, "label": "test_18", "parent_id": 2 }, 484 { "id": 24, "label": "test_19", "parent_id": 2 }, 485 { "id": 25, "label": "test_root", "parent_id": 2 }, 486 { "id": 26,"label": "test_out", "parent_id": 2 }] 487}` 488 489 data, err := JsonToActionGraphContainer(inputString) 490 if err != nil { 491 t.Error(err) 492 return 493 } 494 actualbuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{}) 495 496 expectedBuildStatements := []*BuildStatement{ 497 &BuildStatement{ 498 Command: "/bin/bash -c 'touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out'", 499 OutputPaths: []string{"bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out"}, 500 Mnemonic: "Action", 501 SymlinkPaths: []string{}, 502 }, 503 } 504 assertBuildStatements(t, expectedBuildStatements, actualbuildStatements) 505 506 // Inputs for the action are test_{i} from 1 to 20, and test_root. These inputs 507 // are given via a deep depset, but the depset is flattened when returned as a 508 // BuildStatement slice. 509 var expectedFlattenedInputs []string 510 for i := 1; i < 20; i++ { 511 expectedFlattenedInputs = append(expectedFlattenedInputs, fmt.Sprintf("bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_%d", i)) 512 } 513 expectedFlattenedInputs = append(expectedFlattenedInputs, "bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_root") 514 515 actualDepsetHashes := actualbuildStatements[0].InputDepsetHashes 516 actualFlattenedInputs := flattenDepsets(actualDepsetHashes, actualDepsets) 517 if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) { 518 t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs) 519 } 520} 521 522func TestSymlinkTree(t *testing.T) { 523 const inputString = ` 524{ 525 "artifacts": [ 526 { "id": 1, "path_fragment_id": 1 }, 527 { "id": 2, "path_fragment_id": 2 }], 528 "actions": [{ 529 "target_id": 1, 530 "action_key": "x", 531 "mnemonic": "SymlinkTree", 532 "configuration_id": 1, 533 "input_dep_set_ids": [1], 534 "output_ids": [2], 535 "primary_output_id": 2, 536 "execution_platform": "//build/bazel/platforms:linux_x86_64" 537 }], 538 "path_fragments": [ 539 { "id": 1, "label": "foo.manifest" }, 540 { "id": 2, "label": "foo.runfiles/MANIFEST" }], 541 "dep_set_of_files": [ 542 { "id": 1, "direct_artifact_ids": [1] }] 543} 544` 545 data, err := JsonToActionGraphContainer(inputString) 546 if err != nil { 547 t.Error(err) 548 return 549 } 550 actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{}) 551 if err != nil { 552 t.Errorf("Unexpected error %q", err) 553 return 554 } 555 assertBuildStatements(t, []*BuildStatement{ 556 &BuildStatement{ 557 Command: "", 558 OutputPaths: []string{"foo.runfiles/MANIFEST"}, 559 Mnemonic: "SymlinkTree", 560 InputPaths: []string{"foo.manifest"}, 561 SymlinkPaths: []string{}, 562 }, 563 }, actual) 564} 565 566func TestBazelToolsRemovalFromInputDepsets(t *testing.T) { 567 const inputString = `{ 568 "artifacts": [ 569 { "id": 1, "path_fragment_id": 10 }, 570 { "id": 2, "path_fragment_id": 20 }, 571 { "id": 3, "path_fragment_id": 30 }, 572 { "id": 4, "path_fragment_id": 40 }], 573 "dep_set_of_files": [{ 574 "id": 1111, 575 "direct_artifact_ids": [3 , 4] 576 }, { 577 "id": 2222, 578 "direct_artifact_ids": [3] 579 }], 580 "actions": [{ 581 "target_id": 100, 582 "action_key": "x", 583 "input_dep_set_ids": [1111, 2222], 584 "mnemonic": "x", 585 "arguments": ["bogus", "command"], 586 "output_ids": [2], 587 "primary_output_id": 1 588 }], 589 "path_fragments": [ 590 { "id": 10, "label": "input" }, 591 { "id": 20, "label": "output" }, 592 { "id": 30, "label": "dep1", "parent_id": 50 }, 593 { "id": 40, "label": "dep2", "parent_id": 60 }, 594 { "id": 50, "label": "bazel_tools", "parent_id": 60 }, 595 { "id": 60, "label": ".."} 596 ] 597}` 598 /* depsets 599 1111 2222 600 / \ | 601 ../dep2 ../bazel_tools/dep1 602 */ 603 data, err := JsonToActionGraphContainer(inputString) 604 if err != nil { 605 t.Error(err) 606 return 607 } 608 actualBuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{}) 609 if len(actualDepsets) != 1 { 610 t.Errorf("expected 1 depset but found %#v", actualDepsets) 611 return 612 } 613 dep2Found := false 614 for _, dep := range flattenDepsets([]string{actualDepsets[0].ContentHash}, actualDepsets) { 615 if dep == "../bazel_tools/dep1" { 616 t.Errorf("dependency %s expected to be removed but still exists", dep) 617 } else if dep == "../dep2" { 618 dep2Found = true 619 } 620 } 621 if !dep2Found { 622 t.Errorf("dependency ../dep2 expected but not found") 623 } 624 625 expectedBuildStatement := &BuildStatement{ 626 Command: "bogus command", 627 OutputPaths: []string{"output"}, 628 Mnemonic: "x", 629 SymlinkPaths: []string{}, 630 } 631 buildStatementFound := false 632 for _, actualBuildStatement := range actualBuildStatements { 633 if buildStatementEquals(actualBuildStatement, expectedBuildStatement) == "" { 634 buildStatementFound = true 635 break 636 } 637 } 638 if !buildStatementFound { 639 t.Errorf("expected but missing %#v in %#v", expectedBuildStatement, actualBuildStatements) 640 return 641 } 642} 643 644func TestBazelToolsRemovalFromTargets(t *testing.T) { 645 const inputString = `{ 646 "artifacts": [{ "id": 1, "path_fragment_id": 10 }], 647 "targets": [ 648 { "id": 100, "label": "targetX" }, 649 { "id": 200, "label": "@bazel_tools//tool_y" } 650], 651 "actions": [{ 652 "target_id": 100, 653 "action_key": "actionX", 654 "arguments": ["bogus", "command"], 655 "mnemonic" : "x", 656 "output_ids": [1] 657 }, { 658 "target_id": 200, 659 "action_key": "y" 660 }], 661 "path_fragments": [{ "id": 10, "label": "outputX"}] 662}` 663 data, err := JsonToActionGraphContainer(inputString) 664 if err != nil { 665 t.Error(err) 666 return 667 } 668 actualBuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{}) 669 if len(actualDepsets) != 0 { 670 t.Errorf("expected 0 depset but found %#v", actualDepsets) 671 return 672 } 673 expectedBuildStatement := &BuildStatement{ 674 Command: "bogus command", 675 OutputPaths: []string{"outputX"}, 676 Mnemonic: "x", 677 SymlinkPaths: []string{}, 678 } 679 buildStatementFound := false 680 for _, actualBuildStatement := range actualBuildStatements { 681 if buildStatementEquals(actualBuildStatement, expectedBuildStatement) == "" { 682 buildStatementFound = true 683 break 684 } 685 } 686 if !buildStatementFound { 687 t.Errorf("expected but missing %#v in %#v build statements", expectedBuildStatement, len(actualBuildStatements)) 688 return 689 } 690} 691 692func TestBazelToolsRemovalFromTransitiveInputDepsets(t *testing.T) { 693 const inputString = `{ 694 "artifacts": [ 695 { "id": 1, "path_fragment_id": 10 }, 696 { "id": 2, "path_fragment_id": 20 }, 697 { "id": 3, "path_fragment_id": 30 }], 698 "dep_set_of_files": [{ 699 "id": 1111, 700 "transitive_dep_set_ids": [2222] 701 }, { 702 "id": 2222, 703 "direct_artifact_ids": [3] 704 }, { 705 "id": 3333, 706 "direct_artifact_ids": [3] 707 }, { 708 "id": 4444, 709 "transitive_dep_set_ids": [3333] 710 }], 711 "actions": [{ 712 "target_id": 100, 713 "action_key": "x", 714 "input_dep_set_ids": [1111, 4444], 715 "mnemonic": "x", 716 "arguments": ["bogus", "command"], 717 "output_ids": [2], 718 "primary_output_id": 1 719 }], 720 "path_fragments": [ 721 { "id": 10, "label": "input" }, 722 { "id": 20, "label": "output" }, 723 { "id": 30, "label": "dep", "parent_id": 50 }, 724 { "id": 50, "label": "bazel_tools", "parent_id": 60 }, 725 { "id": 60, "label": ".."} 726 ] 727}` 728 /* depsets 729 1111 4444 730 || || 731 2222 3333 732 | | 733 ../bazel_tools/dep 734 Note: in dep_set_of_files: 735 1111 appears BEFORE its dependency,2222 while 736 4444 appears AFTER its dependency 3333 737 and this test shows that that order doesn't affect empty depset pruning 738 */ 739 data, err := JsonToActionGraphContainer(inputString) 740 if err != nil { 741 t.Error(err) 742 return 743 } 744 actualBuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{}) 745 if len(actualDepsets) != 0 { 746 t.Errorf("expected 0 depsets but found %#v", actualDepsets) 747 return 748 } 749 750 expectedBuildStatement := &BuildStatement{ 751 Command: "bogus command", 752 OutputPaths: []string{"output"}, 753 Mnemonic: "x", 754 } 755 buildStatementFound := false 756 for _, actualBuildStatement := range actualBuildStatements { 757 if buildStatementEquals(actualBuildStatement, expectedBuildStatement) == "" { 758 buildStatementFound = true 759 break 760 } 761 } 762 if !buildStatementFound { 763 t.Errorf("expected but missing %#v in %#v", expectedBuildStatement, actualBuildStatements) 764 return 765 } 766} 767 768func TestMiddlemenAction(t *testing.T) { 769 const inputString = ` 770{ 771 "artifacts": [ 772 { "id": 1, "path_fragment_id": 1 }, 773 { "id": 2, "path_fragment_id": 2 }, 774 { "id": 3, "path_fragment_id": 3 }, 775 { "id": 4, "path_fragment_id": 4 }, 776 { "id": 5, "path_fragment_id": 5 }, 777 { "id": 6, "path_fragment_id": 6 }], 778 "path_fragments": [ 779 { "id": 1, "label": "middleinput_one" }, 780 { "id": 2, "label": "middleinput_two" }, 781 { "id": 3, "label": "middleman_artifact" }, 782 { "id": 4, "label": "maininput_one" }, 783 { "id": 5, "label": "maininput_two" }, 784 { "id": 6, "label": "output" }], 785 "dep_set_of_files": [ 786 { "id": 1, "direct_artifact_ids": [1, 2] }, 787 { "id": 2, "direct_artifact_ids": [3, 4, 5] }], 788 "actions": [{ 789 "target_id": 1, 790 "action_key": "x", 791 "mnemonic": "Middleman", 792 "arguments": ["touch", "foo"], 793 "input_dep_set_ids": [1], 794 "output_ids": [3], 795 "primary_output_id": 3 796 }, { 797 "target_id": 2, 798 "action_key": "y", 799 "mnemonic": "Main action", 800 "arguments": ["touch", "foo"], 801 "input_dep_set_ids": [2], 802 "output_ids": [6], 803 "primary_output_id": 6 804 }] 805}` 806 data, err := JsonToActionGraphContainer(inputString) 807 if err != nil { 808 t.Error(err) 809 return 810 } 811 actualBuildStatements, actualDepsets, err := AqueryBuildStatements(data, &metrics.EventHandler{}) 812 if err != nil { 813 t.Errorf("Unexpected error %q", err) 814 return 815 } 816 if expected := 2; len(actualBuildStatements) != expected { 817 t.Fatalf("Expected %d build statements, got %d %#v", expected, len(actualBuildStatements), actualBuildStatements) 818 return 819 } 820 821 expectedDepsetFiles := [][]string{ 822 {"middleinput_one", "middleinput_two", "maininput_one", "maininput_two"}, 823 {"middleinput_one", "middleinput_two"}, 824 } 825 assertFlattenedDepsets(t, actualDepsets, expectedDepsetFiles) 826 827 bs := actualBuildStatements[0] 828 if len(bs.InputPaths) > 0 { 829 t.Errorf("Expected main action raw inputs to be empty, but got %q", bs.InputPaths) 830 } 831 832 expectedOutputs := []string{"output"} 833 if !reflect.DeepEqual(bs.OutputPaths, expectedOutputs) { 834 t.Errorf("Expected main action outputs %q, but got %q", expectedOutputs, bs.OutputPaths) 835 } 836 837 expectedFlattenedInputs := []string{"middleinput_one", "middleinput_two", "maininput_one", "maininput_two"} 838 actualFlattenedInputs := flattenDepsets(bs.InputDepsetHashes, actualDepsets) 839 840 if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) { 841 t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs) 842 } 843 844 bs = actualBuildStatements[1] 845 if bs != nil { 846 t.Errorf("Expected nil action for skipped") 847 } 848} 849 850// Returns the contents of given depsets in concatenated post order. 851func flattenDepsets(depsetHashesToFlatten []string, allDepsets []AqueryDepset) []string { 852 depsetsByHash := map[string]AqueryDepset{} 853 for _, depset := range allDepsets { 854 depsetsByHash[depset.ContentHash] = depset 855 } 856 var result []string 857 for _, depsetId := range depsetHashesToFlatten { 858 result = append(result, flattenDepset(depsetId, depsetsByHash)...) 859 } 860 return result 861} 862 863// Returns the contents of a given depset in post order. 864func flattenDepset(depsetHashToFlatten string, allDepsets map[string]AqueryDepset) []string { 865 depset := allDepsets[depsetHashToFlatten] 866 var result []string 867 for _, depsetId := range depset.TransitiveDepSetHashes { 868 result = append(result, flattenDepset(depsetId, allDepsets)...) 869 } 870 result = append(result, depset.DirectArtifacts...) 871 return result 872} 873 874func assertFlattenedDepsets(t *testing.T, actualDepsets []AqueryDepset, expectedDepsetFiles [][]string) { 875 t.Helper() 876 if len(actualDepsets) != len(expectedDepsetFiles) { 877 t.Errorf("Expected %d depsets, but got %d depsets", len(expectedDepsetFiles), len(actualDepsets)) 878 } 879 for i, actualDepset := range actualDepsets { 880 actualFlattenedInputs := flattenDepsets([]string{actualDepset.ContentHash}, actualDepsets) 881 if !reflect.DeepEqual(actualFlattenedInputs, expectedDepsetFiles[i]) { 882 t.Errorf("Expected depset files: %v, but got %v", expectedDepsetFiles[i], actualFlattenedInputs) 883 } 884 } 885} 886 887func TestSimpleSymlink(t *testing.T) { 888 const inputString = ` 889{ 890 "artifacts": [ 891 { "id": 1, "path_fragment_id": 3 }, 892 { "id": 2, "path_fragment_id": 5 }], 893 "actions": [{ 894 "target_id": 1, 895 "action_key": "x", 896 "mnemonic": "Symlink", 897 "input_dep_set_ids": [1], 898 "output_ids": [2], 899 "primary_output_id": 2 900 }], 901 "dep_set_of_files": [ 902 { "id": 1, "direct_artifact_ids": [1] }], 903 "path_fragments": [ 904 { "id": 1, "label": "one" }, 905 { "id": 2, "label": "file_subdir", "parent_id": 1 }, 906 { "id": 3, "label": "file", "parent_id": 2 }, 907 { "id": 4, "label": "symlink_subdir", "parent_id": 1 }, 908 { "id": 5, "label": "symlink", "parent_id": 4 }] 909}` 910 data, err := JsonToActionGraphContainer(inputString) 911 if err != nil { 912 t.Error(err) 913 return 914 } 915 actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{}) 916 917 if err != nil { 918 t.Errorf("Unexpected error %q", err) 919 return 920 } 921 922 expectedBuildStatements := []*BuildStatement{ 923 &BuildStatement{ 924 Command: "mkdir -p one/symlink_subdir && " + 925 "rm -f one/symlink_subdir/symlink && " + 926 "ln -sf $PWD/one/file_subdir/file one/symlink_subdir/symlink", 927 InputPaths: []string{"one/file_subdir/file"}, 928 OutputPaths: []string{"one/symlink_subdir/symlink"}, 929 SymlinkPaths: []string{"one/symlink_subdir/symlink"}, 930 Mnemonic: "Symlink", 931 }, 932 } 933 assertBuildStatements(t, actual, expectedBuildStatements) 934} 935 936func TestSymlinkQuotesPaths(t *testing.T) { 937 const inputString = ` 938{ 939 "artifacts": [ 940 { "id": 1, "path_fragment_id": 3 }, 941 { "id": 2, "path_fragment_id": 5 }], 942 "actions": [{ 943 "target_id": 1, 944 "action_key": "x", 945 "mnemonic": "SolibSymlink", 946 "input_dep_set_ids": [1], 947 "output_ids": [2], 948 "primary_output_id": 2 949 }], 950 "dep_set_of_files": [ 951 { "id": 1, "direct_artifact_ids": [1] }], 952 "path_fragments": [ 953 { "id": 1, "label": "one" }, 954 { "id": 2, "label": "file subdir", "parent_id": 1 }, 955 { "id": 3, "label": "file", "parent_id": 2 }, 956 { "id": 4, "label": "symlink subdir", "parent_id": 1 }, 957 { "id": 5, "label": "symlink", "parent_id": 4 }] 958}` 959 960 data, err := JsonToActionGraphContainer(inputString) 961 if err != nil { 962 t.Error(err) 963 return 964 } 965 actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{}) 966 if err != nil { 967 t.Errorf("Unexpected error %q", err) 968 return 969 } 970 971 expectedBuildStatements := []*BuildStatement{ 972 &BuildStatement{ 973 Command: "mkdir -p 'one/symlink subdir' && " + 974 "rm -f 'one/symlink subdir/symlink' && " + 975 "ln -sf $PWD/'one/file subdir/file' 'one/symlink subdir/symlink'", 976 InputPaths: []string{"one/file subdir/file"}, 977 OutputPaths: []string{"one/symlink subdir/symlink"}, 978 SymlinkPaths: []string{"one/symlink subdir/symlink"}, 979 Mnemonic: "SolibSymlink", 980 }, 981 } 982 assertBuildStatements(t, expectedBuildStatements, actual) 983} 984 985func TestSymlinkMultipleInputs(t *testing.T) { 986 const inputString = ` 987{ 988 "artifacts": [ 989 { "id": 1, "path_fragment_id": 1 }, 990 { "id": 2, "path_fragment_id": 2 }, 991 { "id": 3, "path_fragment_id": 3 }], 992 "actions": [{ 993 "target_id": 1, 994 "action_key": "action_x", 995 "mnemonic": "Symlink", 996 "input_dep_set_ids": [1], 997 "output_ids": [3], 998 "primary_output_id": 3 999 }], 1000 "dep_set_of_files": [{ "id": 1, "direct_artifact_ids": [1,2] }], 1001 "path_fragments": [ 1002 { "id": 1, "label": "file" }, 1003 { "id": 2, "label": "other_file" }, 1004 { "id": 3, "label": "symlink" }] 1005}` 1006 1007 data, err := JsonToActionGraphContainer(inputString) 1008 if err != nil { 1009 t.Error(err) 1010 return 1011 } 1012 _, _, err = AqueryBuildStatements(data, &metrics.EventHandler{}) 1013 assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file" "other_file"], output ["symlink"]: [Symlink] []`) 1014} 1015 1016func TestSymlinkMultipleOutputs(t *testing.T) { 1017 const inputString = ` 1018{ 1019 "artifacts": [ 1020 { "id": 1, "path_fragment_id": 1 }, 1021 { "id": 3, "path_fragment_id": 3 }], 1022 "actions": [{ 1023 "target_id": 1, 1024 "action_key": "x", 1025 "mnemonic": "Symlink", 1026 "input_dep_set_ids": [1], 1027 "output_ids": [2,3], 1028 "primary_output_id": 2 1029 }], 1030 "dep_set_of_files": [ 1031 { "id": 1, "direct_artifact_ids": [1] }], 1032 "path_fragments": [ 1033 { "id": 1, "label": "file" }, 1034 { "id": 2, "label": "symlink" }, 1035 { "id": 3, "label": "other_symlink" }] 1036}` 1037 1038 data, err := JsonToActionGraphContainer(inputString) 1039 if err != nil { 1040 t.Error(err) 1041 return 1042 } 1043 _, _, err = AqueryBuildStatements(data, &metrics.EventHandler{}) 1044 assertError(t, err, "undefined outputId 2: [Symlink] []") 1045} 1046 1047func TestTemplateExpandActionSubstitutions(t *testing.T) { 1048 const inputString = ` 1049{ 1050 "artifacts": [{ 1051 "id": 1, 1052 "path_fragment_id": 1 1053 }], 1054 "actions": [{ 1055 "target_id": 1, 1056 "action_key": "x", 1057 "mnemonic": "TemplateExpand", 1058 "configuration_id": 1, 1059 "output_ids": [1], 1060 "primary_output_id": 1, 1061 "execution_platform": "//build/bazel/platforms:linux_x86_64", 1062 "template_content": "Test template substitutions: %token1%, %python_binary%", 1063 "substitutions": [ 1064 { "key": "%token1%", "value": "abcd" }, 1065 { "key": "%python_binary%", "value": "python3" }] 1066 }], 1067 "path_fragments": [ 1068 { "id": 1, "label": "template_file" }] 1069}` 1070 1071 data, err := JsonToActionGraphContainer(inputString) 1072 if err != nil { 1073 t.Error(err) 1074 return 1075 } 1076 actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{}) 1077 if err != nil { 1078 t.Errorf("Unexpected error %q", err) 1079 return 1080 } 1081 1082 expectedBuildStatements := []*BuildStatement{ 1083 &BuildStatement{ 1084 Command: "/bin/bash -c 'echo \"Test template substitutions: abcd, python3\" | sed \"s/\\\\\\\\n/\\\\n/g\" > template_file && " + 1085 "chmod a+x template_file'", 1086 OutputPaths: []string{"template_file"}, 1087 Mnemonic: "TemplateExpand", 1088 SymlinkPaths: []string{}, 1089 }, 1090 } 1091 assertBuildStatements(t, expectedBuildStatements, actual) 1092} 1093 1094func TestTemplateExpandActionNoOutput(t *testing.T) { 1095 const inputString = ` 1096{ 1097 "artifacts": [ 1098 { "id": 1, "path_fragment_id": 1 }], 1099 "actions": [{ 1100 "target_id": 1, 1101 "action_key": "x", 1102 "mnemonic": "TemplateExpand", 1103 "configuration_id": 1, 1104 "primary_output_id": 1, 1105 "execution_platform": "//build/bazel/platforms:linux_x86_64", 1106 "templateContent": "Test template substitutions: %token1%, %python_binary%", 1107 "substitutions": [ 1108 { "key": "%token1%", "value": "abcd" }, 1109 { "key": "%python_binary%", "value": "python3" }] 1110 }], 1111 "path_fragments": [ 1112 { "id": 1, "label": "template_file" }] 1113}` 1114 1115 data, err := JsonToActionGraphContainer(inputString) 1116 if err != nil { 1117 t.Error(err) 1118 return 1119 } 1120 _, _, err = AqueryBuildStatements(data, &metrics.EventHandler{}) 1121 assertError(t, err, `Expect 1 output to template expand action, got: output []: [TemplateExpand] []`) 1122} 1123 1124func TestFileWrite(t *testing.T) { 1125 const inputString = ` 1126{ 1127 "artifacts": [ 1128 { "id": 1, "path_fragment_id": 1 }], 1129 "actions": [{ 1130 "target_id": 1, 1131 "action_key": "x", 1132 "mnemonic": "FileWrite", 1133 "configuration_id": 1, 1134 "output_ids": [1], 1135 "primary_output_id": 1, 1136 "execution_platform": "//build/bazel/platforms:linux_x86_64", 1137 "file_contents": "file data\n" 1138 }], 1139 "path_fragments": [ 1140 { "id": 1, "label": "foo.manifest" }] 1141} 1142` 1143 data, err := JsonToActionGraphContainer(inputString) 1144 if err != nil { 1145 t.Error(err) 1146 return 1147 } 1148 actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{}) 1149 if err != nil { 1150 t.Errorf("Unexpected error %q", err) 1151 return 1152 } 1153 assertBuildStatements(t, []*BuildStatement{ 1154 &BuildStatement{ 1155 OutputPaths: []string{"foo.manifest"}, 1156 Mnemonic: "FileWrite", 1157 FileContents: "file data\n", 1158 SymlinkPaths: []string{}, 1159 }, 1160 }, actual) 1161} 1162 1163func TestSourceSymlinkManifest(t *testing.T) { 1164 const inputString = ` 1165{ 1166 "artifacts": [ 1167 { "id": 1, "path_fragment_id": 1 }], 1168 "actions": [{ 1169 "target_id": 1, 1170 "action_key": "x", 1171 "mnemonic": "SourceSymlinkManifest", 1172 "configuration_id": 1, 1173 "output_ids": [1], 1174 "primary_output_id": 1, 1175 "execution_platform": "//build/bazel/platforms:linux_x86_64", 1176 "file_contents": "symlink target\n" 1177 }], 1178 "path_fragments": [ 1179 { "id": 1, "label": "foo.manifest" }] 1180} 1181` 1182 data, err := JsonToActionGraphContainer(inputString) 1183 if err != nil { 1184 t.Error(err) 1185 return 1186 } 1187 actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{}) 1188 if err != nil { 1189 t.Errorf("Unexpected error %q", err) 1190 return 1191 } 1192 assertBuildStatements(t, []*BuildStatement{ 1193 &BuildStatement{ 1194 OutputPaths: []string{"foo.manifest"}, 1195 Mnemonic: "SourceSymlinkManifest", 1196 SymlinkPaths: []string{}, 1197 }, 1198 }, actual) 1199} 1200 1201func TestUnresolvedSymlink(t *testing.T) { 1202 const inputString = ` 1203{ 1204 "artifacts": [ 1205 { "id": 1, "path_fragment_id": 1 } 1206 ], 1207 "actions": [{ 1208 "target_id": 1, 1209 "action_key": "x", 1210 "mnemonic": "UnresolvedSymlink", 1211 "configuration_id": 1, 1212 "output_ids": [1], 1213 "primary_output_id": 1, 1214 "execution_platform": "//build/bazel/platforms:linux_x86_64", 1215 "unresolved_symlink_target": "symlink/target" 1216 }], 1217 "path_fragments": [ 1218 { "id": 1, "label": "path/to/symlink" } 1219 ] 1220} 1221` 1222 data, err := JsonToActionGraphContainer(inputString) 1223 if err != nil { 1224 t.Error(err) 1225 return 1226 } 1227 actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{}) 1228 if err != nil { 1229 t.Errorf("Unexpected error %q", err) 1230 return 1231 } 1232 assertBuildStatements(t, []*BuildStatement{{ 1233 Command: "mkdir -p path/to && rm -f path/to/symlink && ln -sf symlink/target path/to/symlink", 1234 OutputPaths: []string{"path/to/symlink"}, 1235 Mnemonic: "UnresolvedSymlink", 1236 SymlinkPaths: []string{"path/to/symlink"}, 1237 }}, actual) 1238} 1239 1240func TestUnresolvedSymlinkBazelSandwich(t *testing.T) { 1241 const inputString = ` 1242{ 1243 "artifacts": [ 1244 { "id": 1, "path_fragment_id": 1 } 1245 ], 1246 "actions": [{ 1247 "target_id": 1, 1248 "action_key": "x", 1249 "mnemonic": "UnresolvedSymlink", 1250 "configuration_id": 1, 1251 "output_ids": [1], 1252 "primary_output_id": 1, 1253 "execution_platform": "//build/bazel/platforms:linux_x86_64", 1254 "unresolved_symlink_target": "bazel_sandwich:{\"target\":\"target/product/emulator_x86_64/system\"}" 1255 }], 1256 "path_fragments": [ 1257 { "id": 1, "label": "path/to/symlink" } 1258 ] 1259} 1260` 1261 data, err := JsonToActionGraphContainer(inputString) 1262 if err != nil { 1263 t.Error(err) 1264 return 1265 } 1266 actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{}) 1267 if err != nil { 1268 t.Errorf("Unexpected error %q", err) 1269 return 1270 } 1271 assertBuildStatements(t, []*BuildStatement{{ 1272 Command: "mkdir -p path/to && rm -f path/to/symlink && ln -sf {DOTDOTS_TO_OUTPUT_ROOT}../../target/product/emulator_x86_64/system path/to/symlink", 1273 OutputPaths: []string{"path/to/symlink"}, 1274 Mnemonic: "UnresolvedSymlink", 1275 SymlinkPaths: []string{"path/to/symlink"}, 1276 ImplicitDeps: []string{"target/product/emulator_x86_64/system"}, 1277 }}, actual) 1278} 1279 1280func TestUnresolvedSymlinkBazelSandwichWithAlternativeDeps(t *testing.T) { 1281 const inputString = ` 1282{ 1283 "artifacts": [ 1284 { "id": 1, "path_fragment_id": 1 } 1285 ], 1286 "actions": [{ 1287 "target_id": 1, 1288 "action_key": "x", 1289 "mnemonic": "UnresolvedSymlink", 1290 "configuration_id": 1, 1291 "output_ids": [1], 1292 "primary_output_id": 1, 1293 "execution_platform": "//build/bazel/platforms:linux_x86_64", 1294 "unresolved_symlink_target": "bazel_sandwich:{\"depend_on_target\":false,\"implicit_deps\":[\"target/product/emulator_x86_64/obj/PACKAGING/systemimage_intermediates/staging_dir.stamp\"],\"target\":\"target/product/emulator_x86_64/system\"}" 1295 }], 1296 "path_fragments": [ 1297 { "id": 1, "label": "path/to/symlink" } 1298 ] 1299} 1300` 1301 data, err := JsonToActionGraphContainer(inputString) 1302 if err != nil { 1303 t.Error(err) 1304 return 1305 } 1306 actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{}) 1307 if err != nil { 1308 t.Errorf("Unexpected error %q", err) 1309 return 1310 } 1311 assertBuildStatements(t, []*BuildStatement{{ 1312 Command: "mkdir -p path/to && rm -f path/to/symlink && ln -sf {DOTDOTS_TO_OUTPUT_ROOT}../../target/product/emulator_x86_64/system path/to/symlink", 1313 OutputPaths: []string{"path/to/symlink"}, 1314 Mnemonic: "UnresolvedSymlink", 1315 SymlinkPaths: []string{"path/to/symlink"}, 1316 // Note that the target of the symlink, target/product/emulator_x86_64/system, is not listed here 1317 ImplicitDeps: []string{"target/product/emulator_x86_64/obj/PACKAGING/systemimage_intermediates/staging_dir.stamp"}, 1318 }}, actual) 1319} 1320 1321func assertError(t *testing.T, err error, expected string) { 1322 t.Helper() 1323 if err == nil { 1324 t.Errorf("expected error '%s', but got no error", expected) 1325 } else if err.Error() != expected { 1326 t.Errorf("expected error:\n\t'%s', but got:\n\t'%s'", expected, err.Error()) 1327 } 1328} 1329 1330// Asserts that the given actual build statements match the given expected build statements. 1331// Build statement equivalence is determined using buildStatementEquals. 1332func assertBuildStatements(t *testing.T, expected []*BuildStatement, actual []*BuildStatement) { 1333 t.Helper() 1334 if len(expected) != len(actual) { 1335 t.Errorf("expected %d build statements, but got %d,\n expected: %#v,\n actual: %#v", 1336 len(expected), len(actual), expected, actual) 1337 return 1338 } 1339 type compareFn = func(i int, j int) bool 1340 byCommand := func(slice []*BuildStatement) compareFn { 1341 return func(i int, j int) bool { 1342 if slice[i] == nil { 1343 return false 1344 } else if slice[j] == nil { 1345 return false 1346 } 1347 return slice[i].Command < slice[j].Command 1348 } 1349 } 1350 sort.SliceStable(expected, byCommand(expected)) 1351 sort.SliceStable(actual, byCommand(actual)) 1352 for i, actualStatement := range actual { 1353 expectedStatement := expected[i] 1354 if differingField := buildStatementEquals(actualStatement, expectedStatement); differingField != "" { 1355 t.Errorf("%s differs\nunexpected build statement %#v.\nexpected: %#v", 1356 differingField, actualStatement, expectedStatement) 1357 return 1358 } 1359 } 1360} 1361 1362func buildStatementEquals(first *BuildStatement, second *BuildStatement) string { 1363 if (first == nil) != (second == nil) { 1364 return "Nil" 1365 } 1366 if first.Mnemonic != second.Mnemonic { 1367 return "Mnemonic" 1368 } 1369 if first.Command != second.Command { 1370 return "Command" 1371 } 1372 // Ordering is significant for environment variables. 1373 if !reflect.DeepEqual(first.Env, second.Env) { 1374 return "Env" 1375 } 1376 // Ordering is irrelevant for input and output paths, so compare sets. 1377 if !reflect.DeepEqual(sortedStrings(first.InputPaths), sortedStrings(second.InputPaths)) { 1378 return "InputPaths" 1379 } 1380 if !reflect.DeepEqual(sortedStrings(first.OutputPaths), sortedStrings(second.OutputPaths)) { 1381 return "OutputPaths" 1382 } 1383 if !reflect.DeepEqual(sortedStrings(first.SymlinkPaths), sortedStrings(second.SymlinkPaths)) { 1384 return "SymlinkPaths" 1385 } 1386 if !reflect.DeepEqual(sortedStrings(first.ImplicitDeps), sortedStrings(second.ImplicitDeps)) { 1387 return "ImplicitDeps" 1388 } 1389 if first.Depfile != second.Depfile { 1390 return "Depfile" 1391 } 1392 return "" 1393} 1394 1395func sortedStrings(stringSlice []string) []string { 1396 sorted := make([]string, len(stringSlice)) 1397 copy(sorted, stringSlice) 1398 sort.Strings(sorted) 1399 return sorted 1400} 1401 1402// Transform the json format to ActionGraphContainer 1403func JsonToActionGraphContainer(inputString string) ([]byte, error) { 1404 var aqueryProtoResult analysis_v2_proto.ActionGraphContainer 1405 err := json.Unmarshal([]byte(inputString), &aqueryProtoResult) 1406 if err != nil { 1407 return []byte(""), err 1408 } 1409 data, _ := proto.Marshal(&aqueryProtoResult) 1410 return data, err 1411} 1412