| // Copyright 2024 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package com.google.devtools.build.lib.buildtool; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import static org.junit.Assert.assertThrows; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.devtools.build.lib.actions.BuildFailedException; | |
| import com.google.devtools.build.lib.analysis.util.AnalysisMock; | |
| import com.google.devtools.build.lib.authandtls.credentialhelper.CredentialModule; | |
| import com.google.devtools.build.lib.buildeventservice.BazelBuildEventServiceModule; | |
| import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos; | |
| import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEvent; | |
| import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId.IdCase; | |
| import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.TestStatus; | |
| import com.google.devtools.build.lib.buildtool.util.BuildIntegrationTestCase; | |
| import com.google.devtools.build.lib.runtime.BlazeCommandDispatcher; | |
| import com.google.devtools.build.lib.runtime.BlazeRuntime; | |
| import com.google.devtools.build.lib.runtime.NoSpawnCacheModule; | |
| import com.google.devtools.build.lib.testutil.BlazeTestUtils; | |
| import com.google.devtools.build.lib.vfs.FileSystemUtils; | |
| import com.google.devtools.build.lib.vfs.Path; | |
| import java.io.File; | |
| import java.io.FileInputStream; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.util.List; | |
| import javax.annotation.Nullable; | |
| import org.junit.Before; | |
| import org.junit.Rule; | |
| import org.junit.Test; | |
| import org.junit.rules.TemporaryFolder; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** | |
| * Integration test verifying behavior of {@code | |
| * com.google.devtools.build.lib.runtime.TargetSummaryEvent} event. | |
| */ | |
| @RunWith(JUnit4.class) | |
| public class TargetSummaryEventTest extends BuildIntegrationTestCase { | |
| @Rule public final TemporaryFolder tmpFolder = new TemporaryFolder(); | |
| @Before | |
| public void stageEmbeddedTools() throws Exception { | |
| AnalysisMock.get().setupMockToolsRepository(mockToolsConfig); | |
| } | |
| @Override | |
| protected BlazeRuntime.Builder getRuntimeBuilder() throws Exception { | |
| return super.getRuntimeBuilder() | |
| .addBlazeModule(new NoSpawnCacheModule()) | |
| .addBlazeModule(new CredentialModule()) | |
| .addBlazeModule(new BazelBuildEventServiceModule()); | |
| } | |
| private void afterBuildCommand() throws Exception { | |
| runtimeWrapper.newCommand(); | |
| } | |
| @Test | |
| public void plainTarget_buildSuccess() throws Exception { | |
| write("foo/BUILD", "genrule(name = 'foobin', outs = ['out.txt'], cmd = 'echo -n Hello > $@')"); | |
| File bep = buildTargetAndCaptureBuildEventProtocol("//foo:foobin"); | |
| BuildEventStreamProtos.TargetSummary summary = findTargetSummaryEventInBuildEventStream(bep); | |
| assertThat(summary.getOverallBuildSuccess()).isTrue(); | |
| assertThat(summary.getOverallTestStatus()).isEqualTo(TestStatus.NO_STATUS); | |
| } | |
| @Test | |
| public void plainTarget_buildFails() throws Exception { | |
| write("foo/BUILD", "genrule(name = 'foobin', outs = ['out.txt'], cmd = 'false')"); | |
| File bep = buildFailingTargetAndCaptureBuildEventProtocol("//foo:foobin"); | |
| BuildEventStreamProtos.TargetSummary summary = findTargetSummaryEventInBuildEventStream(bep); | |
| assertThat(summary.getOverallBuildSuccess()).isFalse(); | |
| assertThat(summary.getOverallTestStatus()).isEqualTo(TestStatus.NO_STATUS); | |
| } | |
| @Test | |
| public void test_buildSucceeds_testSucceeds() throws Exception { | |
| write("foo/good_test.sh", "#!/bin/bash", "true").setExecutable(true); | |
| write("foo/BUILD", "sh_test(name = 'good_test', srcs = ['good_test.sh'])"); | |
| File bep = testTargetAndCaptureBuildEventProtocol("//foo:good_test"); | |
| BuildEventStreamProtos.TargetSummary summary = findTargetSummaryEventInBuildEventStream(bep); | |
| assertThat(summary.getOverallBuildSuccess()).isTrue(); | |
| assertThat(summary.getOverallTestStatus()).isEqualTo(TestStatus.PASSED); | |
| } | |
| @Test | |
| public void test_buildSucceeds_testFails() throws Exception { | |
| write("foo/bad_test.sh", "#!/bin/bash", "false").setExecutable(true); | |
| write("foo/BUILD", "sh_test(name = 'bad_test', srcs = ['bad_test.sh'])"); | |
| File bep = testTargetAndCaptureBuildEventProtocol("//foo:bad_test"); | |
| BuildEventStreamProtos.TargetSummary summary = findTargetSummaryEventInBuildEventStream(bep); | |
| assertThat(summary.getOverallBuildSuccess()).isTrue(); | |
| assertThat(summary.getOverallTestStatus()).isEqualTo(TestStatus.FAILED); | |
| } | |
| @Test | |
| public void test_buildSucceeds_testRuntimeFailsToBuild() throws Exception { | |
| write("foo/good_test.sh", "#!/bin/bash", "true").setExecutable(true); | |
| write("foo/BUILD", "sh_test(name = 'good_test', srcs = ['good_test.sh'])"); | |
| // Hack: the path to the tools/test/BUILD file is prefixed in the Bazel tests. | |
| String pathToToolsTestBuildPrefix = AnalysisMock.get().isThisBazel() ? "embedded_tools/" : ""; | |
| Path toolsTestBuildPath = | |
| mockToolsConfig.getPath(pathToToolsTestBuildPrefix + "tools/test/BUILD"); | |
| // Delete the test-setup.sh file and introduce a broken genrule to create test-setup.sh. | |
| mockToolsConfig.getPath(pathToToolsTestBuildPrefix + "tools/test/test-setup.sh").delete(); | |
| String bogusTestSetupGenrule = | |
| """ | |
| genrule( | |
| name = 'bogus-make-test-setup', | |
| outs = ['test-setup.sh'], | |
| cmd = 'false', | |
| ) | |
| """; | |
| FileSystemUtils.appendIsoLatin1(toolsTestBuildPath, bogusTestSetupGenrule); | |
| File bep = testTargetAndCaptureBuildEventProtocol("//foo:good_test"); | |
| BuildEventStreamProtos.TargetSummary summary = findTargetSummaryEventInBuildEventStream(bep); | |
| assertThat(summary.getOverallBuildSuccess()).isTrue(); | |
| assertThat(summary.getOverallTestStatus()).isEqualTo(TestStatus.FAILED_TO_BUILD); | |
| } | |
| private File buildTargetAndCaptureBuildEventProtocol(String target) throws Exception { | |
| File bep = tmpFolder.newFile(); | |
| // We use WAIT_FOR_UPLOAD_COMPLETE because it's the easiest way to force the BES module to | |
| // wait until the BEP binary file has been written. | |
| addOptions( | |
| "--keep_going", | |
| "--experimental_bep_target_summary", | |
| "--build_event_binary_file=" + bep.getAbsolutePath(), | |
| "--bes_upload_mode=WAIT_FOR_UPLOAD_COMPLETE"); | |
| buildTarget(target); | |
| // We need to wait for all events to be written to the file, which is done in #afterCommand() | |
| // if --bes_upload_mode=WAIT_FOR_UPLOAD_COMPLETE. | |
| afterBuildCommand(); | |
| return bep; | |
| } | |
| private File buildFailingTargetAndCaptureBuildEventProtocol(String target) throws Exception { | |
| File bep = tmpFolder.newFile(); | |
| // We use WAIT_FOR_UPLOAD_COMPLETE because it's the easiest way to force the BES module to | |
| // wait until the BEP binary file has been written. | |
| addOptions( | |
| "--keep_going", | |
| "--experimental_bep_target_summary", | |
| "--build_event_binary_file=" + bep.getAbsolutePath(), | |
| "--bes_upload_mode=WAIT_FOR_UPLOAD_COMPLETE"); | |
| assertThrows(BuildFailedException.class, () -> buildTarget(target)); | |
| // We need to wait for all events to be written to the file, which is done in #afterCommand() | |
| // if --bes_upload_mode=WAIT_FOR_UPLOAD_COMPLETE. | |
| afterBuildCommand(); | |
| return bep; | |
| } | |
| private File testTargetAndCaptureBuildEventProtocol(String target) throws Exception { | |
| File bep = tmpFolder.newFile(); | |
| BlazeCommandDispatcher dispatcher = new BlazeCommandDispatcher(getRuntime()); | |
| ImmutableList.Builder<String> args = ImmutableList.builder(); | |
| args.add("test", target); | |
| args.addAll(getDefaultBlazeTestArguments()); | |
| // We use WAIT_FOR_UPLOAD_COMPLETE because it's the easiest way to force the BES module to | |
| // wait until the BEP binary file has been written. | |
| args.add( | |
| "--keep_going", | |
| "--client_env=PATH=/bin:/usr/bin:/usr/sbin:/sbin", | |
| "--experimental_bep_target_summary", | |
| "--build_event_binary_file=" + bep.getAbsolutePath(), | |
| "--bes_upload_mode=WAIT_FOR_UPLOAD_COMPLETE"); | |
| dispatcher.exec(args.build(), /* clientDescription= */ "test", outErr); | |
| return bep; | |
| } | |
| protected List<String> getDefaultBlazeTestArguments() { | |
| return BlazeTestUtils.makeArgs("--default_visibility=public", "--test_output=all"); | |
| } | |
| private static ImmutableList<BuildEvent> parseBuildEventsFromBuildEventStream(File bep) | |
| throws IOException { | |
| ImmutableList.Builder<BuildEvent> buildEvents = ImmutableList.builder(); | |
| try (InputStream in = new FileInputStream(bep)) { | |
| BuildEvent ev; | |
| while ((ev = BuildEvent.parseDelimitedFrom(in)) != null) { | |
| buildEvents.add(ev); | |
| } | |
| } | |
| return buildEvents.build(); | |
| } | |
| @Nullable | |
| private static BuildEventStreamProtos.TargetSummary findTargetSummaryEventInBuildEventStream( | |
| File bep) throws IOException { | |
| for (BuildEvent buildEvent : parseBuildEventsFromBuildEventStream(bep)) { | |
| if (buildEvent.getId().getIdCase() == IdCase.TARGET_SUMMARY) { | |
| return buildEvent.getTargetSummary(); | |
| } | |
| } | |
| return null; | |
| } | |
| } |