Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1337,6 +1337,75 @@ class E2ETest {
assertThat(noKeepGoingExit).isEqualTo(1)
}

@Test
fun testUndeclaredWorkspaceReadIsNotImpacted_reproducerForIssue401() {
// Reproducer for https://github.com/Tinder/bazel-diff/issues/401
//
// Feature request: "always-affected" hashing for targets that perform undeclared
// workspace reads (non-hermetic targets). bazel-diff derives a target's hash purely
// from its DECLARED graph -- srcs, deps, and attributes. A target that reads files it
// does not declare (e.g. a `buildifier_test` that scans every `.bzl` in the repo
// without listing them in `srcs`) therefore gets a STABLE hash even when one of those
// undeclared files changes, so `get-impacted-targets` skips it -- even though the test
// would fail if actually run.
//
// The `always_affected_external` workspace has two genrules:
// //:scanner -- tagged `external`, its action reads `scanned_data.txt` but does NOT
// declare it as a src (the undeclared / non-hermetic read).
// //:hermetic -- a normal target that declares `declared_src.txt` as a src.
//
// Between the two checkouts we edit BOTH files. The hermetic target is correctly
// reported as impacted; the `external`-tagged scanner is NOT -- pinning the current
// behaviour that motivates the feature request. If an `--alwaysAffectedTags`
// (or similar) feature lands, the scanner should start appearing in the impacted set
// and this assertion will flag that the behaviour changed.
val workspaceA = copyTestWorkspace("always_affected_external")
val workspaceB = copyTestWorkspace("always_affected_external")

// Change the UNDECLARED read consumed by //:scanner and the DECLARED src of //:hermetic.
File(workspaceB, "scanned_data.txt").writeText("v2\n")
File(workspaceB, "declared_src.txt").writeText("goodbye\n")

val outputDir = temp.newFolder()
val from = File(outputDir, "starting_hashes.json")
val to = File(outputDir, "final_hashes.json")
val impactedTargetsOutput = File(outputDir, "impacted_targets.txt")

val cli = CommandLine(BazelDiff())

assertThat(
cli.execute(
"generate-hashes", "-w", workspaceA.absolutePath, "-b", "bazel", from.absolutePath))
.isEqualTo(0)
assertThat(
cli.execute(
"generate-hashes", "-w", workspaceB.absolutePath, "-b", "bazel", to.absolutePath))
.isEqualTo(0)
assertThat(
cli.execute(
"get-impacted-targets",
"-w",
workspaceB.absolutePath,
"-b",
"bazel",
"-sh",
from.absolutePath,
"-fh",
to.absolutePath,
"-o",
impactedTargetsOutput.absolutePath))
.isEqualTo(0)

val impacted = impactedTargetsOutput.readLines().filter { it.isNotBlank() }.toSet()

// The hermetic target declared its changed source, so it is correctly impacted.
assertThat(impacted.contains("//:hermetic")).isEqualTo(true)

// The `external`-tagged scanner read a file it never declared. Its hash is unchanged,
// so bazel-diff does NOT report it -- this is exactly the gap #401 asks to close.
assertThat(impacted.contains("//:scanner")).isEqualTo(false)
}

/**
* Returns the Bazel version triple by running `bazel version`, or null if it cannot be
* determined.
Expand Down
36 changes: 36 additions & 0 deletions cli/src/test/resources/workspaces/always_affected_external/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Reproducer workspace for https://github.com/Tinder/bazel-diff/issues/401
#
# The issue: a target that reads files it does NOT declare as inputs (a
# non-hermetic / "undeclared workspace read") gets a stable hash from
# bazel-diff, because hashing is derived purely from the declared graph
# (srcs / deps / attrs). The canonical real-world example is a
# `buildifier_test` that scans every `.bzl` in the repo without declaring
# any of them: edit only a `.bzl` file and the target's hash is unchanged,
# so `get-impacted-targets` skips it -- even though the test would fail if
# actually run.
#
# `scanner` stands in for that buildifier-style target. It is tagged
# `external` (the tag the feature request proposes keying "always-affected"
# behaviour off of) and its action reads `scanned_data.txt`, but that file
# is NOT in `srcs` -- it is an undeclared read.

genrule(
name = "scanner",
srcs = [],
outs = ["scanner_result.txt"],
# Reads scanned_data.txt at action time without declaring it as an input.
# This mirrors a linter/buildifier target scanning the workspace.
cmd = "cat $$(dirname $(location scanner_result.txt))/../../../scanned_data.txt > $@ 2>/dev/null || echo scanned > $@",
tags = ["external"],
visibility = ["//visibility:public"],
)

# A normal, hermetic target for contrast: it declares its source, so editing
# `declared_src.txt` DOES change its hash and it IS reported as impacted.
genrule(
name = "hermetic",
srcs = ["declared_src.txt"],
outs = ["hermetic_result.txt"],
cmd = "cp $< $@",
visibility = ["//visibility:public"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module(
name = "always_affected_external",
version = "0.0.0",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v1
Loading