Skip to content

Commit c00fec3

Browse files
test: add E2E reproducer for remote proto module version bumps (#372)
Adds testRemoteProtoVersionBumpImpactsConsumer plus a hermetic `proto_external_version_bump` fixture that validates bazel-diff invalidates main-repo targets when a remote dependency shipping .proto files and exposing java_proto_library targets is version bumped. The fixture wires up a `proto_dep` module (resolved via local_path_override, standing in for any remote/BCR module) that ships greeting.proto and exposes @proto_dep//:greeting_java_proto, plus a //:consumer java_library depending on it. The test simulates a 1.0.0 -> 2.0.0 release (bumps the module version and adds a proto field) and asserts that, with --fineGrainedHashExternalRepos @proto_dep, both //:consumer and @proto_dep//:greeting_java_proto are reported impacted. This locks in end-to-end tracking of remote proto targets: if fine-grained external hashing ever stops descending into proto / java_proto_library targets, //:consumer would silently stop being impacted and this test would fail. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent e52a352 commit c00fec3

11 files changed

Lines changed: 624 additions & 0 deletions

File tree

cli/src/test/kotlin/com/bazel_diff/e2e/E2ETest.kt

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1606,6 +1606,105 @@ class E2ETest {
16061606
.isEqualTo(true)
16071607
}
16081608

1609+
// ------------------------------------------------------------------------
1610+
// Reproducer: a *remote* proto module version bump must invalidate the
1611+
// main-repo targets that consume its generated Java protos.
1612+
// ------------------------------------------------------------------------
1613+
// Question this answers: if an external dependency that ships .proto files and
1614+
// exposes `java_proto_library` targets gets a version bump, do the targets that
1615+
// depend on those Java protos show up as impacted?
1616+
//
1617+
// The `proto_external_version_bump` workspace wires up exactly that shape:
1618+
// - proto_dep: a module (resolved via local_path_override so the test stays
1619+
// hermetic, but standing in for any remote/BCR module) that ships
1620+
// `greeting.proto` and exposes `@proto_dep//:greeting_java_proto`.
1621+
// - //:consumer: a main-repo java_library that depends on
1622+
// `@proto_dep//:greeting_java_proto`.
1623+
//
1624+
// We simulate a proto_dep 1.0.0 -> 2.0.0 release the way a real version bump
1625+
// looks: the module `version` is bumped (root + dep MODULE.bazel) AND the
1626+
// shipped proto gains a new field. With `--fineGrainedHashExternalRepos
1627+
// @proto_dep` the external repo's contents are hashed, so the proto change must
1628+
// propagate `@proto_dep//:greeting_proto` -> `@proto_dep//:greeting_java_proto`
1629+
// -> `//:consumer`.
1630+
//
1631+
// Verified by hand with the locally built CLI: the bump impacts //:consumer,
1632+
// @proto_dep//:greeting.proto, @proto_dep//:greeting_proto, and
1633+
// @proto_dep//:greeting_java_proto (the unchanged @proto_dep//:BUILD keeps its
1634+
// hash). This test locks that in: if fine-grained external hashing ever stops
1635+
// descending into proto / java_proto_library targets, //:consumer would
1636+
// silently stop being impacted and this test would fail.
1637+
@Test
1638+
fun testRemoteProtoVersionBumpImpactsConsumer() {
1639+
val workspaceA = copyTestWorkspace("proto_external_version_bump")
1640+
val workspaceB = copyTestWorkspace("proto_external_version_bump")
1641+
1642+
// Bump proto_dep 1.0.0 -> 2.0.0 in B (root + dep MODULE.bazel)...
1643+
val rootModuleB = File(workspaceB, "MODULE.bazel")
1644+
val rootOriginal = rootModuleB.readText()
1645+
val rootBumped =
1646+
rootOriginal.replace(
1647+
"bazel_dep(name = \"proto_dep\", version = \"1.0.0\")",
1648+
"bazel_dep(name = \"proto_dep\", version = \"2.0.0\")")
1649+
assertThat(rootBumped != rootOriginal).isEqualTo(true)
1650+
rootModuleB.writeText(rootBumped)
1651+
1652+
val depModuleB = File(workspaceB, "proto_dep/MODULE.bazel")
1653+
depModuleB.writeText(depModuleB.readText().replace("version = \"1.0.0\"", "version = \"2.0.0\""))
1654+
1655+
// ...and ship a new field in the proto, mirroring a real upstream release.
1656+
val protoB = File(workspaceB, "proto_dep/greeting.proto")
1657+
val protoOriginal = protoB.readText()
1658+
val protoMutated =
1659+
protoOriginal.replace(
1660+
"message Greeting {\n string message = 1;\n}",
1661+
"message Greeting {\n string message = 1;\n string locale = 2;\n}")
1662+
assertThat(protoMutated != protoOriginal).isEqualTo(true)
1663+
protoB.writeText(protoMutated)
1664+
1665+
val outputDir = temp.newFolder()
1666+
val from = File(outputDir, "starting_hashes.json")
1667+
val to = File(outputDir, "final_hashes.json")
1668+
val impactedTargetsOutput = File(outputDir, "impacted_targets.txt")
1669+
1670+
val cli = CommandLine(BazelDiff())
1671+
assertThat(
1672+
cli.execute(
1673+
"generate-hashes",
1674+
"-w", workspaceA.absolutePath,
1675+
"-b", "bazel",
1676+
"--fineGrainedHashExternalRepos", "@proto_dep",
1677+
from.absolutePath))
1678+
.isEqualTo(0)
1679+
assertThat(
1680+
cli.execute(
1681+
"generate-hashes",
1682+
"-w", workspaceB.absolutePath,
1683+
"-b", "bazel",
1684+
"--fineGrainedHashExternalRepos", "@proto_dep",
1685+
to.absolutePath))
1686+
.isEqualTo(0)
1687+
assertThat(
1688+
cli.execute(
1689+
"get-impacted-targets",
1690+
"-w", workspaceB.absolutePath,
1691+
"-b", "bazel",
1692+
"-sh", from.absolutePath,
1693+
"-fh", to.absolutePath,
1694+
"-o", impactedTargetsOutput.absolutePath))
1695+
.isEqualTo(0)
1696+
1697+
val impacted = impactedTargetsOutput.readLines().filter { it.isNotBlank() }.toSet()
1698+
val consumerImpacted = impacted.any { it == "//:consumer" || it == "@@//:consumer" }
1699+
val javaProtoImpacted = impacted.any { it.endsWith("proto_dep//:greeting_java_proto") }
1700+
assertThat(consumerImpacted)
1701+
.transform("//:consumer should be impacted by a proto_dep version bump; got: $impacted") { it }
1702+
.isEqualTo(true)
1703+
assertThat(javaProtoImpacted)
1704+
.transform("@proto_dep//:greeting_java_proto should be impacted by the proto change; got: $impacted") { it }
1705+
.isEqualTo(true)
1706+
}
1707+
16091708
// ------------------------------------------------------------------------
16101709
// Regression coverage for https://github.com/Tinder/bazel-diff/issues/228
16111710
// ------------------------------------------------------------------------
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
proto_dep
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
load("@rules_java//java:defs.bzl", "java_library")
2+
3+
# Consumes a java_proto_library that lives in the remote `proto_dep` module.
4+
# When proto_dep ships a new version whose .proto content changes, this target
5+
# must be reported as impacted.
6+
java_library(
7+
name = "consumer",
8+
srcs = ["Consumer.java"],
9+
visibility = ["//visibility:public"],
10+
deps = ["@proto_dep//:greeting_java_proto"],
11+
)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.example.consumer;
2+
3+
import com.example.greeting.Greeting;
4+
5+
/** Trivial consumer of the generated Java proto from the remote proto_dep module. */
6+
public final class Consumer {
7+
private Consumer() {}
8+
9+
public static String describe() {
10+
return Greeting.getDefaultInstance().toString();
11+
}
12+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module(
2+
name = "proto_external_version_bump_test",
3+
version = "0.0.0",
4+
)
5+
6+
bazel_dep(name = "rules_java", version = "9.3.0")
7+
bazel_dep(name = "rules_proto", version = "7.1.0")
8+
bazel_dep(name = "protobuf", version = "33.4")
9+
10+
# proto_dep stands in for a remote module that ships .proto files and exposes
11+
# java_proto_library targets. We resolve it via local_path_override so the test
12+
# is hermetic; a "version bump" is simulated by mutating proto_dep's contents
13+
# (see E2ETest.testProtoExternalVersionBumpImpactsConsumer).
14+
bazel_dep(name = "proto_dep", version = "1.0.0")
15+
local_path_override(
16+
module_name = "proto_dep",
17+
path = "proto_dep",
18+
)

0 commit comments

Comments
 (0)