// Copyright 2017 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.exec; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.test.TestRunnerAction; import com.google.devtools.build.lib.buildeventstream.BuildEvent.LocalFile.LocalFileType; import com.google.devtools.build.lib.buildeventstream.BuildEventContext; import com.google.devtools.build.lib.buildeventstream.BuildEventId; import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos; import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.TestStatus; import com.google.devtools.build.lib.buildeventstream.BuildEventWithOrderConstraint; import com.google.devtools.build.lib.buildeventstream.GenericBuildEvent; import com.google.devtools.build.lib.buildeventstream.PathConverter; import com.google.devtools.build.lib.runtime.BuildEventStreamerUtils; import com.google.devtools.build.lib.util.Pair; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.view.test.TestStatus.BlazeTestStatus; import com.google.devtools.build.lib.view.test.TestStatus.TestResultData; import java.util.Collection; import java.util.List; /** This event is raised whenever an individual test attempt is completed. */ // TODO(ulfjack): This class should be in the same package as the TestResult class, and TestSummary // should live there, too. It's depended upon by TestRunnerAction / TestActionContext, which // suggests that it should live in the analysis.test package. public class TestAttempt implements BuildEventWithOrderConstraint { private final TestRunnerAction testAction; private final TestStatus status; private final String statusDetails; private final boolean cachedLocally; private final int attempt; private final boolean lastAttempt; private final Collection> files; private final List testWarnings; private final long durationMillis; private final long startTimeMillis; private final BuildEventStreamProtos.TestResult.ExecutionInfo executionInfo; /** * Construct the event given the test action and attempt number. * * @param cachedLocally True if the reported attempt is taken from the tool's local cache. * @param testAction The test that was run. * @param attempt The number of the attempt for this action. */ private TestAttempt( boolean cachedLocally, TestRunnerAction testAction, BuildEventStreamProtos.TestResult.ExecutionInfo executionInfo, int attempt, BlazeTestStatus status, String statusDetails, long startTimeMillis, long durationMillis, Collection> files, List testWarnings, boolean lastAttempt) { this.testAction = testAction; this.executionInfo = Preconditions.checkNotNull(executionInfo); this.attempt = attempt; this.status = BuildEventStreamerUtils.bepStatus(Preconditions.checkNotNull(status)); this.statusDetails = statusDetails; this.cachedLocally = cachedLocally; this.startTimeMillis = startTimeMillis; this.durationMillis = durationMillis; this.files = Preconditions.checkNotNull(files); this.testWarnings = Preconditions.checkNotNull(testWarnings); this.lastAttempt = lastAttempt; } /** * Creates a test attempt result instance for a test that was not locally cached; it may have been * locally executed, remotely executed, or remotely cached. */ public static TestAttempt forExecutedTestResult( TestRunnerAction testAction, TestResultData attemptData, int attempt, Collection> files, BuildEventStreamProtos.TestResult.ExecutionInfo executionInfo, boolean lastAttempt) { return new TestAttempt( false, testAction, executionInfo, attempt, attemptData.getStatus(), attemptData.getStatusDetails(), attemptData.getStartTimeMillisEpoch(), attemptData.getRunDurationMillis(), files, attemptData.getWarningList(), lastAttempt); } public static TestAttempt fromCachedTestResult( TestRunnerAction testAction, TestResultData attemptData, int attempt, Collection> files, BuildEventStreamProtos.TestResult.ExecutionInfo executionInfo, boolean lastAttempt) { return new TestAttempt( true, testAction, executionInfo, attempt, attemptData.getStatus(), attemptData.getStatusDetails(), attemptData.getStartTimeMillisEpoch(), attemptData.getRunDurationMillis(), files, attemptData.getWarningList(), lastAttempt); } @VisibleForTesting public Artifact getTestStatusArtifact() { return testAction.getCacheStatusArtifact(); } @VisibleForTesting public Collection> getFiles() { return files; } @VisibleForTesting public BuildEventStreamProtos.TestResult.ExecutionInfo getExecutionInfo() { return executionInfo; } @VisibleForTesting public TestStatus getStatus() { return status; } @VisibleForTesting public boolean isCachedLocally() { return cachedLocally; } @Override public BuildEventId getEventId() { return BuildEventId.testResult( testAction.getOwner().getLabel(), testAction.getRunNumber(), testAction.getShardNum(), attempt, testAction.getConfiguration().getEventId()); } @Override public Collection postedAfter() { return ImmutableList.of( BuildEventId.targetCompleted( testAction.getOwner().getLabel(), testAction.getConfiguration().getEventId())); } @Override public Collection getChildrenEvents() { if (lastAttempt) { return ImmutableList.of(); } else { return ImmutableList.of( BuildEventId.testResult( testAction.getOwner().getLabel(), testAction.getRunNumber(), testAction.getShardNum(), attempt + 1, testAction.getConfiguration().getEventId())); } } @Override public ImmutableList referencedLocalFiles() { LocalFileType localFileType = status == TestStatus.PASSED ? LocalFileType.SUCCESSFUL_TEST_OUTPUT : LocalFileType.FAILED_TEST_OUTPUT; ImmutableList.Builder localFiles = ImmutableList.builder(); for (Pair file : files) { localFiles.add(new LocalFile(file.getSecond(), localFileType)); } return localFiles.build(); } @Override public BuildEventStreamProtos.BuildEvent asStreamProto(BuildEventContext converters) { return GenericBuildEvent.protoChaining(this).setTestResult(asTestResult(converters)).build(); } @VisibleForTesting public BuildEventStreamProtos.TestResult asTestResult(BuildEventContext converters) { PathConverter pathConverter = converters.pathConverter(); BuildEventStreamProtos.TestResult.Builder builder = BuildEventStreamProtos.TestResult.newBuilder(); builder.setStatus(status); builder.setStatusDetails(statusDetails); builder.setExecutionInfo(executionInfo); builder.setCachedLocally(cachedLocally); builder.setTestAttemptStartMillisEpoch(startTimeMillis); builder.setTestAttemptDurationMillis(durationMillis); builder.addAllWarning(testWarnings); for (Pair file : files) { String uri = pathConverter.apply(file.getSecond()); if (uri != null) { builder.addTestActionOutput( BuildEventStreamProtos.File.newBuilder().setName(file.getFirst()).setUri(uri).build()); } } return builder.build(); } }