Skip to content

Commit e8ff51f

Browse files
coeuvrecopybara-github
authored andcommitted
Make bazel coverage work with minimal mode
This PR solves the problem in a different way that bazelbuild#16475 tries to solve: 1. bazelbuild#16812 allows skyframe read metadata from ActionFS. 2. Use `ActionFileSystem` to check existence of coverage data. 3. Fire event `CoverageReport` in the action after the coverage report is generated and listen to it in `ToplevelArtifactsDownloader` to download the report. Closes bazelbuild#16556. PiperOrigin-RevId: 502854552 Change-Id: I2796baaa962857831ff161423be6dffa6eb73e5c
1 parent 454f11d commit e8ff51f

File tree

8 files changed

+187
-27
lines changed

8 files changed

+187
-27
lines changed

src/main/java/com/google/devtools/build/lib/analysis/BUILD

+10
Original file line numberDiff line numberDiff line change
@@ -2518,6 +2518,16 @@ java_library(
25182518
],
25192519
)
25202520

2521+
java_library(
2522+
name = "test/coverage_report",
2523+
srcs = ["test/CoverageReport.java"],
2524+
deps = [
2525+
"//src/main/java/com/google/devtools/build/lib/events",
2526+
"//src/main/java/com/google/devtools/build/lib/vfs",
2527+
"//third_party:guava",
2528+
],
2529+
)
2530+
25212531
java_library(
25222532
name = "test/coverage_configuration",
25232533
srcs = ["test/CoverageConfiguration.java"],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2023 The Bazel Authors. 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+
package com.google.devtools.build.lib.analysis.test;
15+
16+
import com.google.common.collect.ImmutableList;
17+
import com.google.devtools.build.lib.events.ExtendedEventHandler;
18+
import com.google.devtools.build.lib.vfs.Path;
19+
20+
/** This event is used to notify about a successfully generated coverage report. */
21+
public final class CoverageReport implements ExtendedEventHandler.Postable {
22+
private final ImmutableList<Path> files;
23+
24+
public CoverageReport(ImmutableList<Path> files) {
25+
this.files = files;
26+
}
27+
28+
public ImmutableList<Path> getFiles() {
29+
return files;
30+
}
31+
}

src/main/java/com/google/devtools/build/lib/bazel/coverage/BUILD

+2
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ java_library(
2525
"//src/main/java/com/google/devtools/build/lib/analysis:blaze_directories",
2626
"//src/main/java/com/google/devtools/build/lib/analysis:configured_target",
2727
"//src/main/java/com/google/devtools/build/lib/analysis:incompatible_platform_provider",
28+
"//src/main/java/com/google/devtools/build/lib/analysis:test/coverage_report",
2829
"//src/main/java/com/google/devtools/build/lib/analysis:test/coverage_report_action_factory",
2930
"//src/main/java/com/google/devtools/build/lib/collect/nestedset",
3031
"//src/main/java/com/google/devtools/build/lib/concurrent",
3132
"//src/main/java/com/google/devtools/build/lib/events",
3233
"//src/main/java/com/google/devtools/build/lib/exec:spawn_strategy_resolver",
3334
"//src/main/java/com/google/devtools/build/lib/util",
35+
"//src/main/java/com/google/devtools/build/lib/vfs",
3436
"//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
3537
"//src/main/java/com/google/devtools/common/options",
3638
"//third_party:auto_value",

src/main/java/com/google/devtools/build/lib/bazel/coverage/CoverageReportActionBuilder.java

+11
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
package com.google.devtools.build.lib.bazel.coverage;
1616

17+
import static com.google.common.collect.ImmutableList.toImmutableList;
18+
1719
import com.google.common.base.Joiner;
1820
import com.google.common.collect.ImmutableList;
1921
import com.google.common.collect.ImmutableMap;
@@ -30,6 +32,7 @@
3032
import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
3133
import com.google.devtools.build.lib.actions.ArtifactFactory;
3234
import com.google.devtools.build.lib.actions.ArtifactOwner;
35+
import com.google.devtools.build.lib.actions.ArtifactPathResolver;
3336
import com.google.devtools.build.lib.actions.ArtifactRoot;
3437
import com.google.devtools.build.lib.actions.BaseSpawn;
3538
import com.google.devtools.build.lib.actions.ExecException;
@@ -45,6 +48,7 @@
4548
import com.google.devtools.build.lib.analysis.RunfilesSupport;
4649
import com.google.devtools.build.lib.analysis.actions.Compression;
4750
import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
51+
import com.google.devtools.build.lib.analysis.test.CoverageReport;
4852
import com.google.devtools.build.lib.analysis.test.CoverageReportActionFactory.CoverageReportActionsWrapper;
4953
import com.google.devtools.build.lib.analysis.test.TestProvider;
5054
import com.google.devtools.build.lib.analysis.test.TestProvider.TestParams;
@@ -57,6 +61,7 @@
5761
import com.google.devtools.build.lib.events.EventHandler;
5862
import com.google.devtools.build.lib.exec.SpawnStrategyResolver;
5963
import com.google.devtools.build.lib.util.Fingerprint;
64+
import com.google.devtools.build.lib.vfs.Path;
6065
import com.google.devtools.build.lib.vfs.PathFragment;
6166
import java.util.ArrayList;
6267
import java.util.Collection;
@@ -146,6 +151,12 @@ public ActionResult execute(ActionExecutionContext actionExecutionContext)
146151
.getContext(SpawnStrategyResolver.class)
147152
.exec(spawn, actionExecutionContext);
148153
actionExecutionContext.getEventHandler().handle(Event.info(locationMessage));
154+
ArtifactPathResolver pathResolver = actionExecutionContext.getPathResolver();
155+
ImmutableList<Path> files =
156+
getOutputs().stream()
157+
.map(artifact -> pathResolver.convertPath(artifact.getPath()))
158+
.collect(toImmutableList());
159+
actionExecutionContext.getEventHandler().post(new CoverageReport(files));
149160
return ActionResult.create(spawnResults);
150161
} catch (ExecException e) {
151162
throw ActionExecutionException.fromExecException(e, this);

src/main/java/com/google/devtools/build/lib/exec/StandaloneTestStrategy.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -771,7 +771,10 @@ private TestAttemptResult runTestAttempt(
771771

772772
Verify.verify(
773773
!(testAction.isCoverageMode() && testAction.getSplitCoveragePostProcessing())
774-
|| testAction.getCoverageData().getPath().exists());
774+
|| actionExecutionContext
775+
.getPathResolver()
776+
.convertPath(testAction.getCoverageData().getPath())
777+
.exists());
775778
Verify.verifyNotNull(spawnResults);
776779
Verify.verifyNotNull(testResultDataBuilder);
777780

src/main/java/com/google/devtools/build/lib/remote/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ java_library(
203203
"//src/main/java/com/google/devtools/build/lib/analysis:analysis_cluster",
204204
"//src/main/java/com/google/devtools/build/lib/analysis:configured_target",
205205
"//src/main/java/com/google/devtools/build/lib/analysis:configured_target_value",
206+
"//src/main/java/com/google/devtools/build/lib/analysis:test/coverage_report",
206207
"//src/main/java/com/google/devtools/build/lib/packages",
207208
"//src/main/java/com/google/devtools/build/lib/remote/util",
208209
"//src/main/java/com/google/devtools/build/lib/skyframe:action_execution_value",

src/main/java/com/google/devtools/build/lib/remote/ToplevelArtifactsDownloader.java

+44-26
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import com.google.devtools.build.lib.analysis.TargetCompleteEvent;
3636
import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper.ArtifactsInOutputGroup;
3737
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
38+
import com.google.devtools.build.lib.analysis.test.CoverageReport;
3839
import com.google.devtools.build.lib.analysis.test.TestAttempt;
3940
import com.google.devtools.build.lib.remote.AbstractActionInputPrefetcher.Priority;
4041
import com.google.devtools.build.lib.remote.util.StaticMetadataProvider;
@@ -56,7 +57,8 @@ private enum CommandMode {
5657
UNKNOWN,
5758
BUILD,
5859
TEST,
59-
RUN;
60+
RUN,
61+
COVERAGE;
6062
}
6163

6264
private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
@@ -83,6 +85,9 @@ public ToplevelArtifactsDownloader(
8385
case "run":
8486
this.commandMode = CommandMode.RUN;
8587
break;
88+
case "coverage":
89+
this.commandMode = CommandMode.COVERAGE;
90+
break;
8691
default:
8792
this.commandMode = CommandMode.UNKNOWN;
8893
}
@@ -104,36 +109,48 @@ public interface PathToMetadataConverter {
104109
FileArtifactValue getMetadata(Path path);
105110
}
106111

112+
private void downloadTestOutput(Path path) {
113+
// Since the event is fired within action execution, the skyframe doesn't know the outputs of
114+
// test actions yet, so we can't get their metadata through skyframe. However, the fileSystem
115+
// of the path is an ActionFileSystem, we use it to get the metadata for this file.
116+
//
117+
// If the test hit action cache, the filesystem is local filesystem because the actual test
118+
// action didn't get the chance to execute. In this case the metadata is null which is fine
119+
// because test outputs are already downloaded (otherwise it cannot hit the action cache).
120+
FileArtifactValue metadata = pathToMetadataConverter.getMetadata(path);
121+
if (metadata != null) {
122+
ListenableFuture<Void> future =
123+
actionInputPrefetcher.downloadFileAsync(path.asFragment(), metadata, Priority.LOW);
124+
addCallback(
125+
future,
126+
new FutureCallback<Void>() {
127+
@Override
128+
public void onSuccess(Void unused) {}
129+
130+
@Override
131+
public void onFailure(Throwable throwable) {
132+
logger.atWarning().withCause(throwable).log(
133+
"Failed to download test output %s.", path);
134+
}
135+
},
136+
directExecutor());
137+
}
138+
}
139+
107140
@Subscribe
108141
@AllowConcurrentEvents
109142
public void onTestAttempt(TestAttempt event) {
110143
for (Pair<String, Path> pair : event.getFiles()) {
111144
Path path = checkNotNull(pair.getSecond());
112-
// Since the event is fired within action execution, the skyframe doesn't know the outputs of
113-
// test actions yet, so we can't get their metadata through skyframe. However, the fileSystem
114-
// of the path is an ActionFileSystem, we use it to get the metadata for this file.
115-
//
116-
// If the test hit action cache, the filesystem is local filesystem because the actual test
117-
// action didn't get the chance to execute. In this case the metadata is null which is fine
118-
// because test outputs are already downloaded (otherwise it cannot hit the action cache).
119-
FileArtifactValue metadata = pathToMetadataConverter.getMetadata(path);
120-
if (metadata != null) {
121-
ListenableFuture<Void> future =
122-
actionInputPrefetcher.downloadFileAsync(path.asFragment(), metadata, Priority.LOW);
123-
addCallback(
124-
future,
125-
new FutureCallback<Void>() {
126-
@Override
127-
public void onSuccess(Void unused) {}
145+
downloadTestOutput(path);
146+
}
147+
}
128148

129-
@Override
130-
public void onFailure(Throwable throwable) {
131-
logger.atWarning().withCause(throwable).log(
132-
"Failed to download test output %s.", path);
133-
}
134-
},
135-
directExecutor());
136-
}
149+
@Subscribe
150+
@AllowConcurrentEvents
151+
public void onCoverageReport(CoverageReport event) {
152+
for (var file : event.getFiles()) {
153+
downloadTestOutput(file);
137154
}
138155
}
139156

@@ -172,8 +189,9 @@ private boolean shouldDownloadToplevelOutputs(ConfiguredTargetKey configuredTarg
172189
case RUN:
173190
// Always download outputs of toplevel targets in RUN mode
174191
return true;
192+
case COVERAGE:
175193
case TEST:
176-
// Do not download test binary in test mode.
194+
// Do not download test binary in test/coverage mode.
177195
try {
178196
var configuredTargetValue =
179197
(ConfiguredTargetValue) memoizingEvaluator.getExistingValue(configuredTargetKey);

src/test/shell/bazel/remote/build_without_the_bytes_test.sh

+84
Original file line numberDiff line numberDiff line change
@@ -1583,4 +1583,88 @@ EOF
15831583
expect_log "bin-message"
15841584
}
15851585

1586+
function test_java_rbe_coverage_produces_report() {
1587+
mkdir -p java/factorial
1588+
1589+
JAVA_TOOLS_ZIP="released"
1590+
COVERAGE_GENERATOR_DIR="released"
1591+
1592+
cd java/factorial
1593+
1594+
cat > BUILD <<'EOF'
1595+
java_library(
1596+
name = "fact",
1597+
srcs = ["Factorial.java"],
1598+
)
1599+
java_test(
1600+
name = "fact-test",
1601+
size = "small",
1602+
srcs = ["FactorialTest.java"],
1603+
test_class = "factorial.FactorialTest",
1604+
deps = [
1605+
":fact",
1606+
],
1607+
)
1608+
EOF
1609+
1610+
cat > Factorial.java <<'EOF'
1611+
package factorial;
1612+
public class Factorial {
1613+
public static int factorial(int x) {
1614+
return x <= 0 ? 1 : x * factorial(x-1);
1615+
}
1616+
}
1617+
EOF
1618+
1619+
cat > FactorialTest.java <<'EOF'
1620+
package factorial;
1621+
import static org.junit.Assert.*;
1622+
import org.junit.Test;
1623+
public class FactorialTest {
1624+
@Test
1625+
public void testFactorialOfZeroIsOne() throws Exception {
1626+
assertEquals(Factorial.factorial(3),6);
1627+
}
1628+
}
1629+
EOF
1630+
cd ../..
1631+
1632+
cat $(rlocation io_bazel/src/test/shell/bazel/testdata/jdk_http_archives) >> WORKSPACE
1633+
1634+
bazel coverage \
1635+
--test_output=all \
1636+
--experimental_fetch_all_coverage_outputs \
1637+
--experimental_split_coverage_postprocessing \
1638+
--remote_download_minimal \
1639+
--combined_report=lcov \
1640+
--spawn_strategy=remote \
1641+
--remote_executor=grpc://localhost:${worker_port} \
1642+
--instrumentation_filter=//java/factorial \
1643+
//java/factorial:fact-test &> $TEST_log || fail "Shouldn't fail"
1644+
1645+
# Test binary shouldn't be downloaded
1646+
[[ ! -e "bazel-bin/java/factorial/libfact.jar" ]] || fail "bazel-bin/java/factorial/libfact.jar shouldn't exist!"
1647+
1648+
local expected_result="SF:java/factorial/Factorial.java
1649+
FN:2,factorial/Factorial::<init> ()V
1650+
FN:4,factorial/Factorial::factorial (I)I
1651+
FNDA:0,factorial/Factorial::<init> ()V
1652+
FNDA:1,factorial/Factorial::factorial (I)I
1653+
FNF:2
1654+
FNH:1
1655+
BRDA:4,0,0,1
1656+
BRDA:4,0,1,1
1657+
BRF:2
1658+
BRH:2
1659+
DA:2,0
1660+
DA:4,1
1661+
LH:1
1662+
LF:2
1663+
end_of_record"
1664+
cat bazel-testlogs/java/factorial/fact-test/coverage.dat > $TEST_log
1665+
expect_log "$expected_result"
1666+
cat bazel-out/_coverage/_coverage_report.dat > $TEST_log
1667+
expect_log "$expected_result"
1668+
}
1669+
15861670
run_suite "Build without the Bytes tests"

0 commit comments

Comments
 (0)