| // Copyright 2014 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.runtime; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.collect.ImmutableList.toImmutableList; |
| |
| import com.google.common.base.MoreObjects; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ComparisonChain; |
| import com.google.common.collect.ImmutableList; |
| import com.google.devtools.build.lib.analysis.AliasProvider; |
| import com.google.devtools.build.lib.analysis.ConfiguredTarget; |
| import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue; |
| import com.google.devtools.build.lib.analysis.test.TestProvider; |
| import com.google.devtools.build.lib.analysis.test.TestProvider.TestParams; |
| 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.BuildEventIdUtil; |
| import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos; |
| import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId; |
| 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.cmdline.Label; |
| import com.google.devtools.build.lib.util.DetailedExitCode; |
| import com.google.devtools.build.lib.util.DetailedExitCode.DetailedExitCodeComparator; |
| import com.google.devtools.build.lib.util.io.AnsiTerminalPrinter.Mode; |
| 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.FailedTestCasesStatus; |
| import com.google.devtools.build.lib.view.test.TestStatus.TestCase; |
| import com.google.devtools.build.lib.view.test.TestStatus.TestCase.Status; |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; |
| import com.google.protobuf.util.Durations; |
| import com.google.protobuf.util.Timestamps; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TreeMap; |
| import java.util.stream.Stream; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Test summary entry. Stores summary information for a single test rule. Also used to sort summary |
| * output by status. |
| * |
| * <p>Invariant: All TestSummary mutations should be performed through the Builder. No direct |
| * TestSummary methods (except the constructor) may mutate the object. |
| */ |
| public class TestSummary implements Comparable<TestSummary>, BuildEventWithOrderConstraint { |
| /** |
| * Builder class responsible for creating and altering TestSummary objects. |
| */ |
| public static class Builder { |
| private TestSummary summary; |
| private boolean built; |
| |
| private Builder(ConfiguredTarget target) { |
| summary = new TestSummary(target); |
| built = false; |
| } |
| |
| void mergeFrom(TestSummary existingSummary) { |
| // Yuck, manually fill in fields. |
| for (int i = 0; i < existingSummary.shardRunStatuses.size(); i++) { |
| summary.shardRunStatuses.get(i).addAll(existingSummary.shardRunStatuses.get(i)); |
| } |
| summary.firstStartTimeMillis = existingSummary.firstStartTimeMillis; |
| summary.lastStopTimeMillis = existingSummary.lastStopTimeMillis; |
| summary.totalRunDurationMillis = existingSummary.totalRunDurationMillis; |
| setConfiguration(existingSummary.configuration); |
| setStatus(existingSummary.status); |
| addCoverageFiles(existingSummary.coverageFiles); |
| addPassedLogs(existingSummary.passedLogs); |
| addFailedLogs(existingSummary.failedLogs); |
| summary.totalTestCases += existingSummary.totalTestCases; |
| summary.totalUnknownTestCases += existingSummary.totalUnknownTestCases; |
| |
| if (existingSummary.failedTestCasesStatus != null) { |
| addFailedTestCases( |
| existingSummary.getFailedTestCases(), existingSummary.getFailedTestCasesStatus()); |
| } |
| |
| addTestTimes(existingSummary.testTimes); |
| addWarnings(existingSummary.warnings); |
| setActionRan(existingSummary.actionRan); |
| setNumCached(existingSummary.numCached); |
| setRanRemotely(existingSummary.ranRemotely); |
| setWasUnreportedWrongSize(existingSummary.wasUnreportedWrongSize); |
| mergeSystemFailure(existingSummary.getSystemFailure()); |
| } |
| |
| // Implements copy on write logic, allowing reuse of the same builder. |
| private void checkMutation() { |
| // If mutating the builder after an object was built, create another copy. |
| if (built) { |
| built = false; |
| TestSummary lastSummary = summary; |
| summary = new TestSummary(lastSummary.target); |
| mergeFrom(lastSummary); |
| } |
| } |
| |
| // This used to return a reference to the value on success. |
| // However, since it can alter the summary member, inlining it in an |
| // assignment to a property of summary was unsafe. |
| private void checkMutation(Object value) { |
| checkNotNull(value); |
| checkMutation(); |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder setConfiguration(BuildConfigurationValue configuration) { |
| checkMutation(configuration); |
| summary.configuration = checkNotNull(configuration, summary); |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder setStatus(BlazeTestStatus status) { |
| checkMutation(status); |
| summary.status = status; |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder setSkipped(boolean skipped) { |
| checkMutation(skipped); |
| summary.skipped = skipped; |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder addCoverageFiles(List<Path> coverageFiles) { |
| checkMutation(coverageFiles); |
| summary.coverageFiles.addAll(coverageFiles); |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder addPassedLogs(List<Path> passedLogs) { |
| checkMutation(passedLogs); |
| summary.passedLogs.addAll(passedLogs); |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder addPassedLog(Path passedLog) { |
| checkMutation(passedLog); |
| summary.passedLogs.add(passedLog); |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder addFailedLogs(List<Path> failedLogs) { |
| checkMutation(failedLogs); |
| summary.failedLogs.addAll(failedLogs); |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder addFailedLog(Path failedLog) { |
| checkMutation(failedLog); |
| summary.failedLogs.add(failedLog); |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder collectTestCases(@Nullable TestCase testCase) { |
| // Maintain the invariant: failedTestCases + totalUnknownTestCases <= totalTestCases |
| if (testCase == null) { |
| // If we don't have test case information, count each test as one case with unknown status. |
| summary.failedTestCasesStatus = FailedTestCasesStatus.NOT_AVAILABLE; |
| summary.totalTestCases++; |
| summary.totalUnknownTestCases++; |
| } else { |
| summary.failedTestCasesStatus = FailedTestCasesStatus.FULL; |
| summary.totalTestCases += traverseTestCases(testCase); |
| } |
| return this; |
| } |
| |
| private int traverseTestCases(TestCase testCase) { |
| if (testCase.getChildCount() > 0) { |
| // This is a non-leaf result. Traverse its children, but do not add its |
| // name to the output list. It should not contain any 'failure' or |
| // 'error' tags, but we want to be lax here, because the syntax of the |
| // test.xml file is also lax. |
| // don't count container of test cases as test |
| int res = 0; |
| for (TestCase child : testCase.getChildList()) { |
| res += traverseTestCases(child); |
| } |
| return res; |
| } else if (testCase.getType() != TestCase.Type.TEST_CASE) { |
| return 0; |
| } |
| |
| // This is a leaf result. |
| if (!testCase.getRun()) { |
| // Don't count test cases that were not run. |
| return 0; |
| } |
| if (testCase.getStatus() != TestCase.Status.PASSED) { |
| this.summary.failedTestCases.add(testCase); |
| } |
| |
| if (testCase.getStatus() == Status.PASSED) { |
| this.summary.passedTestCases.add(testCase); |
| } |
| |
| return 1; |
| } |
| |
| public Builder addPassedTestCases(List<TestCase> testCases) { |
| checkMutation(testCases); |
| summary.passedTestCases.addAll(testCases); |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder addFailedTestCases(List<TestCase> testCases, FailedTestCasesStatus status) { |
| checkMutation(status); |
| checkMutation(testCases); |
| |
| if (summary.failedTestCasesStatus == null) { |
| summary.failedTestCasesStatus = status; |
| } else if (summary.failedTestCasesStatus != status) { |
| summary.failedTestCasesStatus = FailedTestCasesStatus.PARTIAL; |
| } |
| |
| if (testCases.isEmpty()) { |
| return this; |
| } |
| |
| // union of summary.failedTestCases, testCases |
| Map<String, TestCase> allCases = new TreeMap<>(); |
| if (summary.failedTestCases != null) { |
| for (TestCase detail : summary.failedTestCases) { |
| allCases.put(detail.getClassName() + "." + detail.getName(), detail); |
| } |
| } |
| for (TestCase detail : testCases) { |
| allCases.put(detail.getClassName() + "." + detail.getName(), detail); |
| } |
| |
| summary.failedTestCases = new ArrayList<>(allCases.values()); |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder addTestTimes(List<Long> testTimes) { |
| checkMutation(testTimes); |
| summary.testTimes.addAll(testTimes); |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder mergeTiming(long startTimeMillis, long runDurationMillis) { |
| checkMutation(); |
| summary.firstStartTimeMillis = Math.min(summary.firstStartTimeMillis, startTimeMillis); |
| summary.lastStopTimeMillis = |
| Math.max(summary.lastStopTimeMillis, startTimeMillis + runDurationMillis); |
| summary.totalRunDurationMillis += runDurationMillis; |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder addWarnings(List<String> warnings) { |
| checkMutation(warnings); |
| summary.warnings.addAll(warnings); |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder setActionRan(boolean actionRan) { |
| checkMutation(); |
| summary.actionRan = actionRan; |
| return this; |
| } |
| |
| /** |
| * Set the number of results cached, locally or remotely. |
| * |
| * @param numCached number of results cached locally or remotely |
| * @return this Builder |
| */ |
| @CanIgnoreReturnValue |
| public Builder setNumCached(int numCached) { |
| checkMutation(); |
| summary.numCached = numCached; |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder setNumLocalActionCached(int numLocalActionCached) { |
| checkMutation(); |
| summary.numLocalActionCached = numLocalActionCached; |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder setRanRemotely(boolean ranRemotely) { |
| checkMutation(); |
| summary.ranRemotely = ranRemotely; |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder setWasUnreportedWrongSize(boolean wasUnreportedWrongSize) { |
| checkMutation(); |
| summary.wasUnreportedWrongSize = wasUnreportedWrongSize; |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder mergeSystemFailure(@Nullable DetailedExitCode systemFailure) { |
| checkMutation(); |
| summary.systemFailure = |
| DetailedExitCodeComparator.chooseMoreImportantWithFirstIfTie( |
| summary.systemFailure, systemFailure); |
| return this; |
| } |
| |
| /** |
| * Records a new result for the given shard of the test. |
| * |
| * @return an immutable view of the statuses associated with the shard, with the new element. |
| */ |
| public ImmutableList<BlazeTestStatus> addShardStatus(int shardNumber, BlazeTestStatus status) { |
| List<BlazeTestStatus> statuses = summary.shardRunStatuses.get(shardNumber); |
| statuses.add(status); |
| return ImmutableList.copyOf(statuses); |
| } |
| |
| /** Records new attempts for the given shard of the target. */ |
| @CanIgnoreReturnValue |
| public Builder addShardAttempts(int shardNumber, int newAtttempts) { |
| checkMutation(); |
| summary.shardAttempts[shardNumber] += newAtttempts; |
| return this; |
| } |
| |
| /** |
| * Returns the created TestSummary object. |
| * Any actions following a build() will create another copy of the same values. |
| * Since no mutators are provided directly by TestSummary, a copy will not |
| * be produced if two builds are invoked in a row without calling a setter. |
| */ |
| public TestSummary build() { |
| peek(); |
| if (!built) { |
| makeSummaryImmutable(); |
| // else: it is already immutable. |
| } |
| Preconditions.checkState(built, "Built flag was not set"); |
| return summary; |
| } |
| |
| /** |
| * Within-package, it is possible to read directly from an |
| * incompletely-built TestSummary. Used to pass Builders around directly. |
| */ |
| TestSummary peek() { |
| checkNotNull(summary.target, "Target cannot be null"); |
| checkNotNull(summary.status, "Status cannot be null"); |
| return summary; |
| } |
| |
| private void makeSummaryImmutable() { |
| // Once finalized, the list types are immutable. |
| summary.passedLogs = Collections.unmodifiableList(summary.passedLogs); |
| summary.failedLogs = Collections.unmodifiableList(summary.failedLogs); |
| summary.warnings = Collections.unmodifiableList(summary.warnings); |
| summary.coverageFiles = Collections.unmodifiableList(summary.coverageFiles); |
| summary.testTimes = Collections.unmodifiableList(summary.testTimes); |
| |
| built = true; |
| } |
| } |
| |
| private final ConfiguredTarget target; |
| // Currently only populated if --runs_per_test_detects_flakes is enabled. |
| private final ImmutableList<ArrayList<BlazeTestStatus>> shardRunStatuses; |
| |
| private BuildConfigurationValue configuration; |
| private BlazeTestStatus status; |
| private boolean skipped; |
| private final int[] shardAttempts; |
| private int numCached; |
| private int numLocalActionCached; |
| private boolean actionRan; |
| private boolean ranRemotely; |
| private boolean wasUnreportedWrongSize; |
| private List<TestCase> failedTestCases = new ArrayList<>(); |
| private final List<TestCase> passedTestCases = new ArrayList<>(); |
| private List<Path> passedLogs = new ArrayList<>(); |
| private List<Path> failedLogs = new ArrayList<>(); |
| private List<String> warnings = new ArrayList<>(); |
| private List<Path> coverageFiles = new ArrayList<>(); |
| private List<Long> testTimes = new ArrayList<>(); |
| private long totalRunDurationMillis; |
| private long firstStartTimeMillis = Long.MAX_VALUE; |
| private long lastStopTimeMillis = Long.MIN_VALUE; |
| private FailedTestCasesStatus failedTestCasesStatus = null; |
| private int totalTestCases; |
| private int totalUnknownTestCases; |
| @Nullable private DetailedExitCode systemFailure; |
| |
| // Don't allow public instantiation; go through the Builder. |
| private TestSummary(ConfiguredTarget target) { |
| this.target = target; |
| TestParams testParams = getTestParams(); |
| int sz = Math.max(testParams.getShards(), 1); |
| shardAttempts = new int[sz]; |
| shardRunStatuses = createAndInitialize(testParams.runsDetectsFlakes() ? sz : 0); |
| } |
| |
| private static ImmutableList<ArrayList<BlazeTestStatus>> createAndInitialize(int sz) { |
| return Stream.generate(() -> new ArrayList<BlazeTestStatus>(1)) |
| .limit(sz) |
| .collect(toImmutableList()); |
| } |
| |
| /** Creates a new Builder allowing construction of a new TestSummary object. */ |
| public static Builder newBuilder(ConfiguredTarget target) { |
| return new Builder(target); |
| } |
| |
| public Label getLabel() { |
| return AliasProvider.getDependencyLabel(target); |
| } |
| |
| public ConfiguredTarget getTarget() { |
| return target; |
| } |
| |
| public BuildConfigurationValue getConfiguration() { |
| return configuration; |
| } |
| |
| public BlazeTestStatus getStatus() { |
| return status; |
| } |
| |
| public boolean isSkipped() { |
| return skipped; |
| } |
| |
| /** |
| * Whether or not any results associated with this test were cached locally or remotely. |
| * |
| * @return true if any results were cached, false if not |
| */ |
| public boolean isCached() { |
| return numCached > 0; |
| } |
| |
| public boolean isLocalActionCached() { |
| return numLocalActionCached > 0; |
| } |
| |
| public int numLocalActionCached() { |
| return numLocalActionCached; |
| } |
| |
| /** |
| * @return number of results that were cached locally or remotely |
| */ |
| public int numCached() { |
| return numCached; |
| } |
| |
| private int numUncached() { |
| return totalRuns() - numCached; |
| } |
| |
| /** |
| * Whether or not any action was taken for this test, that is there was some result that was |
| * <em>not cached</em>. |
| * |
| * @return true if some action was taken for this test, false if not |
| */ |
| public boolean actionRan() { |
| return actionRan; |
| } |
| |
| public boolean ranRemotely() { |
| return ranRemotely; |
| } |
| |
| public boolean wasUnreportedWrongSize() { |
| return wasUnreportedWrongSize; |
| } |
| |
| public int getTotalTestCases() { |
| return totalTestCases; |
| } |
| |
| public int getUnkownTestCases() { |
| return totalUnknownTestCases; |
| } |
| |
| public List<TestCase> getFailedTestCases() { |
| return failedTestCases; |
| } |
| |
| public List<TestCase> getPassedTestCases() { |
| return passedTestCases; |
| } |
| |
| public List<Path> getCoverageFiles() { |
| return coverageFiles; |
| } |
| |
| public List<Path> getPassedLogs() { |
| return passedLogs; |
| } |
| |
| public List<Path> getFailedLogs() { |
| return failedLogs; |
| } |
| |
| public FailedTestCasesStatus getFailedTestCasesStatus() { |
| return failedTestCasesStatus; |
| } |
| |
| @Nullable |
| public DetailedExitCode getSystemFailure() { |
| return systemFailure; |
| } |
| |
| /** |
| * Returns an immutable view of the warnings associated with this test. |
| */ |
| public List<String> getWarnings() { |
| return Collections.unmodifiableList(warnings); |
| } |
| |
| private static int getSortKey(BlazeTestStatus status) { |
| return status == BlazeTestStatus.PASSED ? -1 : status.getNumber(); |
| } |
| |
| @Override |
| public int compareTo(TestSummary that) { |
| return ComparisonChain.start() |
| .compareTrueFirst(this.isCached(), that.isCached()) |
| .compare(this.numUncached(), that.numUncached()) |
| .compare(getSortKey(this.status), getSortKey(that.status)) |
| .compare(this.getLabel(), that.getLabel()) |
| .compare( |
| this.getTarget().getConfigurationChecksum(), |
| that.getTarget().getConfigurationChecksum()) |
| .compare(this.getTotalTestCases(), that.getTotalTestCases()) |
| .result(); |
| } |
| |
| @Override |
| public String toString() { |
| return MoreObjects.toStringHelper(this) |
| .add("target", this.getTarget()) |
| .add("status", status) |
| .add("numCached", numCached) |
| .add("numLocalActionCached", numLocalActionCached) |
| .add("actionRan", actionRan) |
| .add("ranRemotely", ranRemotely) |
| .toString(); |
| } |
| |
| public List<Long> getTestTimes() { |
| // The return result is unmodifiable (UnmodifiableList instance) |
| return testTimes; |
| } |
| |
| public int getNumAttempts() { |
| return Arrays.stream(this.shardAttempts).max().getAsInt(); |
| } |
| |
| public int getNumCached() { |
| return numCached; |
| } |
| |
| public int totalRuns() { |
| return testTimes.size(); |
| } |
| |
| public long getTotalRunDurationMillis() { |
| return totalRunDurationMillis; |
| } |
| |
| public long getFirstStartTimeMillis() { |
| return firstStartTimeMillis; |
| } |
| |
| public long getLastStopTimeMillis() { |
| return lastStopTimeMillis; |
| } |
| |
| Mode getStatusMode() { |
| if (skipped) { |
| return Mode.WARNING; |
| } |
| return status == BlazeTestStatus.PASSED |
| ? Mode.INFO |
| : (status == BlazeTestStatus.FLAKY ? Mode.WARNING : Mode.ERROR); |
| } |
| |
| @Override |
| public BuildEventId getEventId() { |
| return BuildEventIdUtil.testSummary( |
| AliasProvider.getDependencyLabel(target), |
| BuildEventIdUtil.configurationId(target.getConfigurationChecksum())); |
| } |
| |
| @Override |
| public Collection<BuildEventId> getChildrenEvents() { |
| return ImmutableList.of(); |
| } |
| |
| @Override |
| public Collection<BuildEventId> postedAfter() { |
| return ImmutableList.of( |
| BuildEventIdUtil.targetCompleted( |
| AliasProvider.getDependencyLabel(target), |
| BuildEventIdUtil.configurationId(target.getConfigurationChecksum()))); |
| } |
| |
| @Override |
| public ImmutableList<LocalFile> referencedLocalFiles() { |
| ImmutableList.Builder<LocalFile> localFiles = ImmutableList.builder(); |
| // TODO(b/199940216): Can we populate metadata for these files? |
| for (Path path : getFailedLogs()) { |
| localFiles.add( |
| new LocalFile(path, LocalFileType.FAILED_TEST_OUTPUT, /* artifactMetadata= */ null)); |
| } |
| for (Path path : getPassedLogs()) { |
| localFiles.add( |
| new LocalFile(path, LocalFileType.SUCCESSFUL_TEST_OUTPUT, /* artifactMetadata= */ null)); |
| } |
| return localFiles.build(); |
| } |
| |
| @Override |
| public BuildEventStreamProtos.BuildEvent asStreamProto(BuildEventContext converters) { |
| PathConverter pathConverter = converters.pathConverter(); |
| TestParams testParams = getTestParams(); |
| BuildEventStreamProtos.TestSummary.Builder summaryBuilder = |
| BuildEventStreamProtos.TestSummary.newBuilder() |
| .setOverallStatus(BuildEventStreamerUtils.bepStatus(status)) |
| .setTotalNumCached(getNumCached()) |
| .setTotalRunCount(totalRuns()) |
| .setAttemptCount(getNumAttempts()) |
| .setRunCount(testParams.getRuns()) |
| .setShardCount(testParams.getShards()) |
| .setFirstStartTime(Timestamps.fromMillis(firstStartTimeMillis)) |
| .setFirstStartTimeMillis(firstStartTimeMillis) |
| .setLastStopTime(Timestamps.fromMillis(lastStopTimeMillis)) |
| .setLastStopTimeMillis(lastStopTimeMillis) |
| .setTotalRunDuration(Durations.fromMillis(totalRunDurationMillis)) |
| .setTotalRunDurationMillis(totalRunDurationMillis); |
| for (Path path : getFailedLogs()) { |
| String uri = pathConverter.apply(path); |
| if (uri != null) { |
| summaryBuilder.addFailed(BuildEventStreamProtos.File.newBuilder().setUri(uri).build()); |
| } |
| } |
| for (Path path : getPassedLogs()) { |
| String uri = pathConverter.apply(path); |
| if (uri != null) { |
| summaryBuilder.addPassed(BuildEventStreamProtos.File.newBuilder().setUri(uri).build()); |
| } |
| } |
| return GenericBuildEvent.protoChaining(this).setTestSummary(summaryBuilder.build()).build(); |
| } |
| |
| private TestParams getTestParams() { |
| return checkNotNull(target.getProvider(TestProvider.class).getTestParams(), target); |
| } |
| } |