diff --git a/src/main/java/com/google/devtools/build/lib/remote/Scrubber.java b/src/main/java/com/google/devtools/build/lib/remote/Scrubber.java index 0b88edaddf34f8..b7ebe8826e5e92 100644 --- a/src/main/java/com/google/devtools/build/lib/remote/Scrubber.java +++ b/src/main/java/com/google/devtools/build/lib/remote/Scrubber.java @@ -137,7 +137,8 @@ private String emptyToAll(String s) { private boolean matches(Spawn spawn) { String mnemonic = spawn.getMnemonic(); ActionOwner actionOwner = spawn.getResourceOwner().getOwner(); - String label = actionOwner.getLabel().getCanonicalForm(); + String label = + actionOwner.getLabel() != null ? actionOwner.getLabel().getCanonicalForm() : ""; String kind = actionOwner.getTargetKind(); boolean isForTool = actionOwner.isBuildConfigurationForTool(); diff --git a/src/test/java/com/google/devtools/build/lib/exec/util/FakeOwner.java b/src/test/java/com/google/devtools/build/lib/exec/util/FakeOwner.java index 2f52090552d434..81acd9b0bac709 100644 --- a/src/test/java/com/google/devtools/build/lib/exec/util/FakeOwner.java +++ b/src/test/java/com/google/devtools/build/lib/exec/util/FakeOwner.java @@ -41,7 +41,7 @@ public class FakeOwner implements ActionExecutionMetadata { private final String mnemonic; private final String progressMessage; - private final String ownerLabel; + @Nullable private final String ownerLabel; private final String ownerRuleKind; @Nullable private final Artifact primaryOutput; @Nullable private final PlatformInfo platform; @@ -59,7 +59,7 @@ public class FakeOwner implements ActionExecutionMetadata { boolean isBuiltForToolConfiguration) { this.mnemonic = mnemonic; this.progressMessage = progressMessage; - this.ownerLabel = checkNotNull(ownerLabel); + this.ownerLabel = ownerLabel; this.ownerRuleKind = checkNotNull(ownerRuleKind); this.primaryOutput = primaryOutput; this.platform = platform; @@ -86,8 +86,9 @@ public FakeOwner(String mnemonic, String progressMessage, String ownerLabel) { @Override public ActionOwner getOwner() { + Label parsedLabel = ownerLabel != null ? Label.parseCanonicalUnchecked(ownerLabel) : null; return ActionOwner.createDummy( - Label.parseCanonicalUnchecked(ownerLabel), + parsedLabel, new Location("dummy-file", 0, 0), ownerRuleKind, mnemonic, diff --git a/src/test/java/com/google/devtools/build/lib/exec/util/SpawnBuilder.java b/src/test/java/com/google/devtools/build/lib/exec/util/SpawnBuilder.java index ba77a5744509c8..ca83bcf7f814cb 100644 --- a/src/test/java/com/google/devtools/build/lib/exec/util/SpawnBuilder.java +++ b/src/test/java/com/google/devtools/build/lib/exec/util/SpawnBuilder.java @@ -43,7 +43,7 @@ public final class SpawnBuilder { private String mnemonic = "Mnemonic"; private String progressMessage = "progress message"; - private String ownerLabel = "//dummy:label"; + @Nullable private String ownerLabel = "//dummy:label"; private String ownerRuleKind = "dummy-target-kind"; @Nullable private Artifact ownerPrimaryOutput; @Nullable private PlatformInfo platform; @@ -114,6 +114,13 @@ public SpawnBuilder withOwnerLabel(String ownerLabel) { return this; } + /** Sets the owner to have no label, simulating synthetic actions (e.g. coverage aggregation). */ + @CanIgnoreReturnValue + public SpawnBuilder withNullOwnerLabel() { + this.ownerLabel = null; + return this; + } + @CanIgnoreReturnValue public SpawnBuilder withOwnerRuleKind(String ownerRuleKind) { this.ownerRuleKind = checkNotNull(ownerRuleKind); diff --git a/src/test/java/com/google/devtools/build/lib/remote/ScrubberTest.java b/src/test/java/com/google/devtools/build/lib/remote/ScrubberTest.java index 383f3e31886a11..81653fd70e1a96 100644 --- a/src/test/java/com/google/devtools/build/lib/remote/ScrubberTest.java +++ b/src/test/java/com/google/devtools/build/lib/remote/ScrubberTest.java @@ -447,6 +447,35 @@ public void lastRuleWins() { assertThat(spawnScrubber.transformArgument("foospam")).isEqualTo("fooeggs"); } + @Test + public void nullOwnerLabelDoesNotThrow() { + // Synthetic Bazel actions (e.g. CoverageReportKeySingleton) have a null label on their + // ActionOwner. The scrubber must not throw NPE and should treat the label as empty. + var scrubber = + new Scrubber( + Config.newBuilder() + .addRules( + Config.Rule.newBuilder() + .setMatcher(Config.Matcher.newBuilder().setMnemonic("CoverageReport"))) + .build()); + + assertThat(scrubber.forSpawn(createSpawnWithNullLabel("CoverageReport"))).isNotNull(); + } + + @Test + public void nullOwnerLabelDoesNotMatchLabelFilter() { + // When the action owner has no label, it should not match a specific label pattern. + var scrubber = + new Scrubber( + Config.newBuilder() + .addRules( + Config.Rule.newBuilder() + .setMatcher(Config.Matcher.newBuilder().setLabel("//foo:bar"))) + .build()); + + assertThat(scrubber.forSpawn(createSpawnWithNullLabel("CoverageReport"))).isNull(); + } + private static Spawn createSpawn() { return createSpawn("//foo:bar", "Foo"); } @@ -464,4 +493,12 @@ private static Spawn createSpawn( .setBuiltForToolConfiguration(forTool) .build(); } + + /** + * Creates a spawn whose action owner has no label, simulating synthetic Bazel actions such as + * {@code CoverageReportKeySingleton}. + */ + private static Spawn createSpawnWithNullLabel(String mnemonic) { + return new SpawnBuilder("cmd").withNullOwnerLabel().withMnemonic(mnemonic).build(); + } }