// 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.analysis.test; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.actions.AbstractAction; import com.google.devtools.build.lib.actions.ActionExecutionContext; import com.google.devtools.build.lib.actions.ActionExecutionException; import com.google.devtools.build.lib.actions.ActionInput; import com.google.devtools.build.lib.actions.ActionInputHelper; import com.google.devtools.build.lib.actions.ActionKeyContext; import com.google.devtools.build.lib.actions.ActionOwner; import com.google.devtools.build.lib.actions.ActionResult; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.CommandLineExpansionException; import com.google.devtools.build.lib.actions.ExecException; import com.google.devtools.build.lib.actions.NotifyOnActionCacheHit; import com.google.devtools.build.lib.analysis.RunfilesSupplierImpl; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.RunUnder; import com.google.devtools.build.lib.buildeventstream.TestFileNameConstants; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.collect.ImmutableIterable; import com.google.devtools.build.lib.util.Fingerprint; import com.google.devtools.build.lib.util.LoggingUtil; import com.google.devtools.build.lib.util.Pair; import com.google.devtools.build.lib.vfs.FileSystem; import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.lib.view.test.TestStatus.TestResultData; import com.google.devtools.common.options.TriState; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.logging.Level; import javax.annotation.Nullable; /** * An Action representing a test with the associated environment (runfiles, * environment variables, test result, etc). It consumes test executable and * runfiles artifacts and produces test result and test status artifacts. */ // Not final so that we can mock it in tests. public class TestRunnerAction extends AbstractAction implements NotifyOnActionCacheHit { public static final PathFragment COVERAGE_TMP_ROOT = PathFragment.create("_coverage"); // Used for selecting subset of testcase / testmethods. private static final String TEST_BRIDGE_TEST_FILTER_ENV = "TESTBRIDGE_TEST_ONLY"; private static final String GUID = "cc41f9d0-47a6-11e7-8726-eb6ce83a8cc8"; private final Artifact testSetupScript; private final Artifact collectCoverageScript; private final BuildConfiguration configuration; private final TestConfiguration testConfiguration; private final Artifact testLog; private final Artifact cacheStatus; private final PathFragment testWarningsPath; private final PathFragment unusedRunfilesLogPath; private final PathFragment shExecutable; private final PathFragment splitLogsPath; private final PathFragment splitLogsDir; private final PathFragment undeclaredOutputsDir; private final PathFragment undeclaredOutputsZipPath; private final PathFragment undeclaredOutputsAnnotationsDir; private final PathFragment undeclaredOutputsManifestPath; private final PathFragment undeclaredOutputsAnnotationsPath; private final PathFragment xmlOutputPath; @Nullable private final PathFragment testShard; private final PathFragment testExitSafe; private final PathFragment testStderr; private final PathFragment testInfrastructureFailure; private final PathFragment baseDir; private final Artifact coverageData; private final TestTargetProperties testProperties; private final TestTargetExecutionSettings executionSettings; private final int shardNum; private final int runNumber; private final String workspaceName; private final boolean useTestRunner; // Mutable state related to test caching. Lazily initialized: null indicates unknown. private Boolean unconditionalExecution; /** Any extra environment variables (and values) added by the rule that created this action. */ private final ImmutableMap extraTestEnv; /** * The set of environment variables that are inherited from the client environment. These are * handled explicitly by the ActionCacheChecker and so don't have to be included in the cache key. */ private final ImmutableIterable requiredClientEnvVariables; private static ImmutableList list(Artifact... artifacts) { ImmutableList.Builder builder = ImmutableList.builder(); for (Artifact artifact : artifacts) { if (artifact != null) { builder.add(artifact); } } return builder.build(); } /** * Create new TestRunnerAction instance. Should not be called directly. Use {@link * TestActionBuilder} instead. * * @param shardNum The shard number. Must be 0 if totalShards == 0 (no sharding). Otherwise, must * be >= 0 and < totalShards. * @param runNumber test run number */ TestRunnerAction( ActionOwner owner, Iterable inputs, Artifact testSetupScript, // Must be in inputs @Nullable Artifact collectCoverageScript, // Must be in inputs, if not null Artifact testLog, Artifact cacheStatus, Artifact coverageArtifact, TestTargetProperties testProperties, Map extraTestEnv, TestTargetExecutionSettings executionSettings, int shardNum, int runNumber, BuildConfiguration configuration, String workspaceName, PathFragment shExecutable, boolean useTestRunner) { super( owner, inputs, // Note that this action only cares about the runfiles, not the mapping. new RunfilesSupplierImpl(PathFragment.create("runfiles"), executionSettings.getRunfiles()), list(testLog, cacheStatus, coverageArtifact)); Preconditions.checkState((collectCoverageScript == null) == (coverageArtifact == null)); this.testSetupScript = testSetupScript; this.collectCoverageScript = collectCoverageScript; this.configuration = Preconditions.checkNotNull(configuration); this.testConfiguration = Preconditions.checkNotNull(configuration.getFragment(TestConfiguration.class)); this.testLog = testLog; this.cacheStatus = cacheStatus; this.coverageData = coverageArtifact; this.shardNum = shardNum; this.runNumber = runNumber; this.testProperties = Preconditions.checkNotNull(testProperties); this.executionSettings = Preconditions.checkNotNull(executionSettings); this.baseDir = cacheStatus.getExecPath().getParentDirectory(); int totalShards = executionSettings.getTotalShards(); Preconditions.checkState((totalShards == 0 && shardNum == 0) || (totalShards > 0 && 0 <= shardNum && shardNum < totalShards)); this.testExitSafe = baseDir.getChild("test.exited_prematurely"); // testShard Path should be set only if sharding is enabled. this.testShard = totalShards > 1 ? baseDir.getChild("test.shard") : null; this.xmlOutputPath = baseDir.getChild("test.xml"); this.testWarningsPath = baseDir.getChild("test.warnings"); this.unusedRunfilesLogPath = baseDir.getChild("test.unused_runfiles_log"); this.testStderr = baseDir.getChild("test.err"); this.shExecutable = shExecutable; this.splitLogsDir = baseDir.getChild("test.raw_splitlogs"); // See note in {@link #getSplitLogsPath} on the choice of file name. this.splitLogsPath = splitLogsDir.getChild("test.splitlogs"); this.undeclaredOutputsDir = baseDir.getChild("test.outputs"); this.undeclaredOutputsZipPath = undeclaredOutputsDir.getChild("outputs.zip"); this.undeclaredOutputsAnnotationsDir = baseDir.getChild("test.outputs_manifest"); this.undeclaredOutputsManifestPath = undeclaredOutputsAnnotationsDir.getChild("MANIFEST"); this.undeclaredOutputsAnnotationsPath = undeclaredOutputsAnnotationsDir.getChild("ANNOTATIONS"); this.testInfrastructureFailure = baseDir.getChild("test.infrastructure_failure"); this.workspaceName = workspaceName; this.useTestRunner = useTestRunner; this.extraTestEnv = ImmutableMap.copyOf(extraTestEnv); this.requiredClientEnvVariables = ImmutableIterable.from(Iterables.concat( configuration.getActionEnvironment().getInheritedEnv(), configuration.getTestActionEnvironment().getInheritedEnv())); } public BuildConfiguration getConfiguration() { return configuration; } public final PathFragment getBaseDir() { return baseDir; } @Override public boolean showsOutputUnconditionally() { return true; } public List getSpawnOutputs() { final List outputs = new ArrayList<>(); outputs.add(ActionInputHelper.fromPath(getXmlOutputPath())); outputs.add(ActionInputHelper.fromPath(getExitSafeFile())); if (isSharded()) { outputs.add(ActionInputHelper.fromPath(getTestShard())); } outputs.add(ActionInputHelper.fromPath(getTestWarningsPath())); outputs.add(ActionInputHelper.fromPath(getSplitLogsPath())); outputs.add(ActionInputHelper.fromPath(getUnusedRunfilesLogPath())); outputs.add(ActionInputHelper.fromPath(getInfrastructureFailureFile())); outputs.add(ActionInputHelper.fromPath(getUndeclaredOutputsZipPath())); outputs.add(ActionInputHelper.fromPath(getUndeclaredOutputsManifestPath())); outputs.add(ActionInputHelper.fromPath(getUndeclaredOutputsAnnotationsPath())); if (isCoverageMode()) { outputs.add(getCoverageData()); } return outputs; } /** * Returns the list of mappings from file name constants to output files. This method checks the * file system for existence of these output files, so it must only be used after test execution. */ // TODO(ulfjack): Instead of going to local disk here, use SpawnResult (add list of files there). public ImmutableList> getTestOutputsMapping(Path execRoot) { ImmutableList.Builder> builder = ImmutableList.builder(); if (getTestLog().getPath().exists()) { builder.add(Pair.of(TestFileNameConstants.TEST_LOG, getTestLog().getPath())); } if (getCoverageData() != null && getCoverageData().getPath().exists()) { builder.add(Pair.of(TestFileNameConstants.TEST_COVERAGE, getCoverageData().getPath())); } if (execRoot != null) { ResolvedPaths resolvedPaths = resolve(execRoot); if (resolvedPaths.getXmlOutputPath().exists()) { builder.add(Pair.of(TestFileNameConstants.TEST_XML, resolvedPaths.getXmlOutputPath())); } if (resolvedPaths.getSplitLogsPath().exists()) { builder.add(Pair.of(TestFileNameConstants.SPLIT_LOGS, resolvedPaths.getSplitLogsPath())); } if (resolvedPaths.getTestWarningsPath().exists()) { builder.add(Pair.of(TestFileNameConstants.TEST_WARNINGS, resolvedPaths.getTestWarningsPath())); } if (resolvedPaths.getUndeclaredOutputsZipPath().exists()) { builder.add(Pair.of(TestFileNameConstants.UNDECLARED_OUTPUTS_ZIP, resolvedPaths.getUndeclaredOutputsZipPath())); } if (resolvedPaths.getUndeclaredOutputsManifestPath().exists()) { builder.add(Pair.of(TestFileNameConstants.UNDECLARED_OUTPUTS_MANIFEST, resolvedPaths.getUndeclaredOutputsManifestPath())); } if (resolvedPaths.getUndeclaredOutputsAnnotationsPath().exists()) { builder.add(Pair.of(TestFileNameConstants.UNDECLARED_OUTPUTS_ANNOTATIONS, resolvedPaths.getUndeclaredOutputsAnnotationsPath())); } if (resolvedPaths.getUnusedRunfilesLogPath().exists()) { builder.add(Pair.of(TestFileNameConstants.UNUSED_RUNFILES_LOG, resolvedPaths.getUnusedRunfilesLogPath())); } if (resolvedPaths.getInfrastructureFailureFile().exists()) { builder.add(Pair.of(TestFileNameConstants.TEST_INFRASTRUCTURE_FAILURE, resolvedPaths.getInfrastructureFailureFile())); } } return builder.build(); } @Override protected void computeKey(ActionKeyContext actionKeyContext, Fingerprint fp) throws CommandLineExpansionException { fp.addString(GUID); fp.addStrings(executionSettings.getArgs().arguments()); fp.addString( executionSettings.getTestFilter() == null ? "" : executionSettings.getTestFilter()); RunUnder runUnder = executionSettings.getRunUnder(); fp.addString(runUnder == null ? "" : runUnder.getValue()); fp.addStringMap(extraTestEnv); // TODO(ulfjack): It might be better for performance to hash the action and test envs in config, // and only add a hash here. configuration.getActionEnvironment().addTo(fp); configuration.getTestActionEnvironment().addTo(fp); // The 'requiredClientEnvVariables' are handled by Skyframe and don't need to be added here. fp.addString(testProperties.getSize().toString()); fp.addString(testProperties.getTimeout().toString()); fp.addStrings(testProperties.getTags()); fp.addInt(testProperties.isLocal() ? 1 : 0); fp.addInt(shardNum); fp.addInt(executionSettings.getTotalShards()); fp.addInt(runNumber); fp.addInt(testConfiguration.getRunsPerTestForLabel(getOwner().getLabel())); fp.addInt(configuration.isCodeCoverageEnabled() ? 1 : 0); } @Override public boolean executeUnconditionally() { // Note: isVolatile must return true if executeUnconditionally can ever return true // for this instance. if (unconditionalExecution == null) { unconditionalExecution = computeExecuteUnconditionallyFromTestStatus(); } return unconditionalExecution; } @Override public boolean isVolatile() { return true; } /** * Saves cache status to disk. */ public void saveCacheStatus(TestResultData data) throws IOException { try (OutputStream out = cacheStatus.getPath().getOutputStream()) { data.writeTo(out); } } /** * Returns the cache from disk, or null if the file doesn't exist or if there is an error. */ @Nullable private TestResultData readCacheStatus() { try (InputStream in = cacheStatus.getPath().getInputStream()) { return TestResultData.parseFrom(in); } catch (IOException expected) { return null; } } private boolean computeExecuteUnconditionallyFromTestStatus() { return !canBeCached( testConfiguration.cacheTestResults(), readCacheStatus(), testProperties.isExternal(), testConfiguration.getRunsPerTestForLabel(getOwner().getLabel())); } @VisibleForTesting static boolean canBeCached( TriState cacheTestResults, TestResultData prevStatus, boolean isExternal, int runsPerTest) { if (cacheTestResults == TriState.NO) { return false; } if (isExternal) { return false; } if (cacheTestResults == TriState.AUTO && (runsPerTest > 1)) { return false; } // Test will not be executed unconditionally - check whether test result exists and is // valid. If it is, method will return false and we will rely on the dependency checker // to make a decision about test execution. if (cacheTestResults == TriState.AUTO && prevStatus != null && !prevStatus.getTestPassed()) { return false; } // Rely on the dependency checker to determine if the test can be cached. Note that the status // is a declared output, so its non-existence also triggers a re-run. return true; } /** * Returns whether caching has been deemed safe by looking at the previous test run * (for local caching). If the previous run is not present, return "true" here, as * remote execution caching should be safe. */ public boolean shouldCacheResult() { return !executeUnconditionally(); } @Override public void actionCacheHit(ActionCachedContext executor) { unconditionalExecution = null; try { executor.getEventBus().post( executor.getContext(TestActionContext.class).newCachedTestResult( executor.getExecRoot(), this, readCacheStatus())); } catch (IOException e) { LoggingUtil.logToRemote(Level.WARNING, "Failed creating cached protocol buffer", e); } } @Override protected String getRawProgressMessage() { return "Testing " + getTestName(); } /** * Deletes all possible test outputs. * *

TestRunnerAction potentially can create many more non-declared outputs - xml output, * coverage data file and logs for failed attempts. All those outputs are uniquely identified by * the test log base name with arbitrary prefix and extension. */ @Override protected void deleteOutputs(FileSystem fileSystem, Path execRoot) throws IOException { super.deleteOutputs(fileSystem, execRoot); // We do not rely on globs, as it causes quadratic behavior in --runs_per_test and test // shard count. // We also need to remove *.(xml|data|shard|warnings|zip) files if they are present. execRoot.getRelative(xmlOutputPath).delete(); execRoot.getRelative(testWarningsPath).delete(); execRoot.getRelative(unusedRunfilesLogPath).delete(); // Note that splitLogsPath points to a file inside the splitLogsDir so // it's not necessary to delete it explicitly. FileSystemUtils.deleteTree(execRoot.getRelative(splitLogsDir)); FileSystemUtils.deleteTree(execRoot.getRelative(undeclaredOutputsDir)); FileSystemUtils.deleteTree(execRoot.getRelative(undeclaredOutputsAnnotationsDir)); execRoot.getRelative(testStderr).delete(); execRoot.getRelative(testExitSafe).delete(); if (testShard != null) { execRoot.getRelative(testShard).delete(); } execRoot.getRelative(testInfrastructureFailure).delete(); // Coverage files use "coverage" instead of "test". String coveragePrefix = "coverage"; // We cannot use coverageData artifact since it may be null. Generate coverage name instead. execRoot.getRelative(baseDir.getChild(coveragePrefix + ".dat")).delete(); // Delete files fetched from remote execution. execRoot.getRelative(baseDir.getChild("test.zip")).delete(); deleteTestAttemptsDirMaybe(execRoot.getRelative(baseDir), "test"); } private void deleteTestAttemptsDirMaybe(Path outputDir, String namePrefix) throws IOException { Path testAttemptsDir = outputDir.getChild(namePrefix + "_attempts"); if (testAttemptsDir.exists()) { // Normally we should have used deleteTree(testAttemptsDir). However, if test output is // in a FUSE filesystem implemented with the high-level API, there may be .fuse??????? // entries, which prevent removing the directory. As a workaround, code below will throw // IOException if it will fail to remove something inside testAttemptsDir, but will // silently suppress any exceptions when deleting testAttemptsDir itself. FileSystemUtils.deleteTreesBelow(testAttemptsDir); try { testAttemptsDir.delete(); } catch (IOException e) { // Do nothing. } } } public void setupEnvVariables(Map env, Duration timeout) { env.put("TEST_TARGET", Label.print(getOwner().getLabel())); env.put("TEST_SIZE", getTestProperties().getSize().toString()); env.put("TEST_TIMEOUT", Long.toString(timeout.getSeconds())); env.put("TEST_WORKSPACE", getRunfilesPrefix()); env.put( "TEST_BINARY", getExecutionSettings().getExecutable().getRootRelativePath().getCallablePathString()); // When we run test multiple times, set different TEST_RANDOM_SEED values for each run. // Don't override any previous setting. if (testConfiguration.getRunsPerTestForLabel(getOwner().getLabel()) > 1 && !env.containsKey("TEST_RANDOM_SEED")) { env.put("TEST_RANDOM_SEED", Integer.toString(getRunNumber() + 1)); } String testFilter = getExecutionSettings().getTestFilter(); if (testFilter != null) { env.put(TEST_BRIDGE_TEST_FILTER_ENV, testFilter); } env.put("TEST_WARNINGS_OUTPUT_FILE", getTestWarningsPath().getPathString()); env.put("TEST_UNUSED_RUNFILES_LOG_FILE", getUnusedRunfilesLogPath().getPathString()); env.put("TEST_LOGSPLITTER_OUTPUT_FILE", getSplitLogsPath().getPathString()); env.put("TEST_UNDECLARED_OUTPUTS_ZIP", getUndeclaredOutputsZipPath().getPathString()); env.put("TEST_UNDECLARED_OUTPUTS_DIR", getUndeclaredOutputsDir().getPathString()); env.put("TEST_UNDECLARED_OUTPUTS_MANIFEST", getUndeclaredOutputsManifestPath().getPathString()); env.put( "TEST_UNDECLARED_OUTPUTS_ANNOTATIONS", getUndeclaredOutputsAnnotationsPath().getPathString()); env.put( "TEST_UNDECLARED_OUTPUTS_ANNOTATIONS_DIR", getUndeclaredOutputsAnnotationsDir().getPathString()); env.put("TEST_PREMATURE_EXIT_FILE", getExitSafeFile().getPathString()); env.put("TEST_INFRASTRUCTURE_FAILURE_FILE", getInfrastructureFailureFile().getPathString()); if (isSharded()) { env.put("TEST_SHARD_INDEX", Integer.toString(getShardNum())); env.put("TEST_TOTAL_SHARDS", Integer.toString(getExecutionSettings().getTotalShards())); env.put("TEST_SHARD_STATUS_FILE", getTestShard().getPathString()); } env.put("XML_OUTPUT_FILE", getXmlOutputPath().getPathString()); if (isCoverageMode()) { // Instruct remote-runtest.sh/local-runtest.sh not to cd into the runfiles directory. // TODO(ulfjack): Find a way to avoid setting this variable. env.put("RUNTEST_PRESERVE_CWD", "1"); env.put("COVERAGE_MANIFEST", getCoverageManifest().getExecPathString()); env.put("COVERAGE_DIR", getCoverageDirectory().getPathString()); env.put("COVERAGE_OUTPUT_FILE", getCoverageData().getExecPathString()); // TODO(elenairina): Remove this after it reaches a blaze release. if (configuration.isExperimentalJavaCoverage()) { // This value ("released") tells lcov_merger whether it should use the old or the new // java coverage implementation. The meaning of "released" is that lcov_merger will receive // this value only after blaze containing this change will be released. env.put("NEW_JAVA_COVERAGE_IMPL", "released"); } else { // This value ("True") should have told lcov_merger whether it should use the old or the new // java coverage implementation. Due to several failed attempts at submitting the new // implementation, this value will be treated still as the old implementation. This // environment variable must be set to a value recognized by lcov_merger. env.put("NEW_JAVA_COVERAGE_IMPL", "True"); } } } /** * Gets the test name in a user-friendly format. * Will generally include the target name and run/shard numbers, if applicable. */ public String getTestName() { String suffix = getTestSuffix(); String label = Label.print(getOwner().getLabel()); return suffix.isEmpty() ? label : label + " " + suffix; } /** * Gets the test suffix in a user-friendly format, eg "(shard 1 of 7)". * Will include the target name and run/shard numbers, if applicable. */ public String getTestSuffix() { int totalShards = executionSettings.getTotalShards(); // Use a 1-based index for user friendliness. int runsPerTest = testConfiguration.getRunsPerTestForLabel(getOwner().getLabel()); if (totalShards > 1 && runsPerTest > 1) { return String.format("(shard %d of %d, run %d of %d)", shardNum + 1, totalShards, runNumber + 1, runsPerTest); } else if (totalShards > 1) { return String.format("(shard %d of %d)", shardNum + 1, totalShards); } else if (runsPerTest > 1) { return String.format("(run %d of %d)", runNumber + 1, runsPerTest); } else { return ""; } } public Artifact getTestLog() { return testLog; } /** * Returns all environment variables which must be set in order to run this test. */ public Map getExtraTestEnv() { return extraTestEnv; } @Override public Iterable getClientEnvironmentVariables() { return requiredClientEnvVariables; } public ResolvedPaths resolve(Path execRoot) { return new ResolvedPaths(execRoot); } public Artifact getCacheStatusArtifact() { return cacheStatus; } public PathFragment getTestWarningsPath() { return testWarningsPath; } public PathFragment getUnusedRunfilesLogPath() { return unusedRunfilesLogPath; } public PathFragment getSplitLogsPath() { return splitLogsPath; } public PathFragment getUndeclaredOutputsDir() { return undeclaredOutputsDir; } /** * @return path to the optional zip file of undeclared test outputs. */ public PathFragment getUndeclaredOutputsZipPath() { return undeclaredOutputsZipPath; } /** * @return path to the undeclared output manifest file. */ public PathFragment getUndeclaredOutputsManifestPath() { return undeclaredOutputsManifestPath; } public PathFragment getUndeclaredOutputsAnnotationsDir() { return undeclaredOutputsAnnotationsDir; } /** * @return path to the undeclared output annotations file. */ public PathFragment getUndeclaredOutputsAnnotationsPath() { return undeclaredOutputsAnnotationsPath; } public PathFragment getTestShard() { return testShard; } public PathFragment getExitSafeFile() { return testExitSafe; } public PathFragment getInfrastructureFailureFile() { return testInfrastructureFailure; } /** * @return path to the optionally created XML output file created by the test. */ public PathFragment getXmlOutputPath() { return xmlOutputPath; } /** * @return coverage data artifact or null if code coverage was not requested. */ @Nullable public Artifact getCoverageData() { return coverageData; } @Nullable public Artifact getCoverageManifest() { return getExecutionSettings().getInstrumentedFileManifest(); } /** Returns true if coverage data should be gathered. */ public boolean isCoverageMode() { return coverageData != null; } /** * Returns a directory to temporarily store coverage results for the given action relative to the * execution root. This directory is used to store all coverage results related to the test * execution with exception of the locally generated *.gcda files. Those are stored separately * using relative path within coverage directory. * *

The directory name for the given test runner action is constructed as: {@code * _coverage/target_path/test_log_name} where {@code test_log_name} is usually a target name but * potentially can include extra suffix, such as a shard number (if test execution was sharded). */ public PathFragment getCoverageDirectory() { return COVERAGE_TMP_ROOT.getRelative( FileSystemUtils.removeExtension(getTestLog().getRootRelativePath())); } public TestTargetProperties getTestProperties() { return testProperties; } public TestTargetExecutionSettings getExecutionSettings() { return executionSettings; } public boolean useTestRunner() { return useTestRunner; } public boolean isSharded() { return testShard != null; } /** * @return the shard number for this action. * If getTotalShards() > 0, must be >= 0 and < getTotalShards(). * Otherwise, must be 0. */ public int getShardNum() { return shardNum; } /** * @return run number. */ public int getRunNumber() { return runNumber; } /** * @return the workspace name. */ public String getRunfilesPrefix() { return workspaceName; } @Override public Artifact getPrimaryOutput() { return testLog; } @Override public ActionResult execute(ActionExecutionContext actionExecutionContext) throws ActionExecutionException, InterruptedException { TestActionContext context = actionExecutionContext.getContext(TestActionContext.class); try { return ActionResult.create(context.exec(this, actionExecutionContext)); } catch (ExecException e) { throw e.toActionExecutionException(this); } finally { unconditionalExecution = null; } } @Override public String getMnemonic() { return "TestRunner"; } @Override public ImmutableSet getMandatoryOutputs() { return getOutputs(); } public Artifact getTestSetupScript() { return testSetupScript; } @Nullable public Artifact getCollectCoverageScript() { return collectCoverageScript; } public PathFragment getShExecutable() { return shExecutable; } public ImmutableMap getLocalShellEnvironment() { return configuration.getLocalShellEnvironment(); } public boolean isEnableRunfiles() { return configuration.runfilesEnabled(); } /** * The same set of paths as the parent test action, resolved against a given exec root. */ public final class ResolvedPaths { private final Path execRoot; ResolvedPaths(Path execRoot) { this.execRoot = Preconditions.checkNotNull(execRoot); } private Path getPath(PathFragment relativePath) { return execRoot.getRelative(relativePath); } public final Path getBaseDir() { return getPath(baseDir); } /** * In rare cases, error messages will be printed to stderr instead of stdout. The test action is * responsible for appending anything in the stderr file to the real test.log. */ public Path getTestStderr() { return getPath(testStderr); } public Path getTestWarningsPath() { return getPath(testWarningsPath); } public Path getSplitLogsPath() { return getPath(splitLogsPath); } public Path getUnusedRunfilesLogPath() { return getPath(unusedRunfilesLogPath); } /** * @return path to the directory containing the split logs (raw and proto file). */ public Path getSplitLogsDir() { return getPath(splitLogsDir); } /** * @return path to the optional zip file of undeclared test outputs. */ public Path getUndeclaredOutputsZipPath() { return getPath(undeclaredOutputsZipPath); } /** * @return path to the directory to hold undeclared test outputs. */ public Path getUndeclaredOutputsDir() { return getPath(undeclaredOutputsDir); } /** * @return path to the directory to hold undeclared output annotations parts. */ public Path getUndeclaredOutputsAnnotationsDir() { return getPath(undeclaredOutputsAnnotationsDir); } /** * @return path to the undeclared output manifest file. */ public Path getUndeclaredOutputsManifestPath() { return getPath(undeclaredOutputsManifestPath); } /** * @return path to the undeclared output annotations file. */ public Path getUndeclaredOutputsAnnotationsPath() { return getPath(undeclaredOutputsAnnotationsPath); } @Nullable public Path getTestShard() { return testShard == null ? null : getPath(testShard); } public Path getExitSafeFile() { return getPath(testExitSafe); } public Path getInfrastructureFailureFile() { return getPath(testInfrastructureFailure); } /** * @return path to the optionally created XML output file created by the test. */ public Path getXmlOutputPath() { return getPath(xmlOutputPath); } } }