/* * Copyright 2014-present Facebook, Inc. * * 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.facebook.buck.cxx; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargets; import com.facebook.buck.rules.AbstractBuildRule; import com.facebook.buck.rules.AddToRuleKey; import com.facebook.buck.rules.BinaryBuildRule; import com.facebook.buck.rules.BuildContext; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.BuildableContext; import com.facebook.buck.rules.HasRuntimeDeps; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.TestRule; import com.facebook.buck.rules.Tool; import com.facebook.buck.step.ExecutionContext; import com.facebook.buck.step.Step; import com.facebook.buck.step.fs.MakeCleanDirectoryStep; import com.facebook.buck.step.fs.TouchStep; import com.facebook.buck.test.TestCaseSummary; import com.facebook.buck.test.TestResultSummary; import com.facebook.buck.test.TestResults; import com.facebook.buck.test.TestRunningOptions; import com.facebook.buck.util.MoreCollectors; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import java.nio.file.Path; import java.util.Optional; import java.util.concurrent.Callable; import java.util.stream.Stream; /** A no-op {@link BuildRule} which houses the logic to run and form the results for C/C++ tests. */ public abstract class CxxTest extends AbstractBuildRule implements TestRule, HasRuntimeDeps, BinaryBuildRule { @AddToRuleKey private final ImmutableMap<String, String> env; @AddToRuleKey private final Supplier<ImmutableList<String>> args; @AddToRuleKey private final Tool executable; @AddToRuleKey @SuppressWarnings("PMD.UnusedPrivateField") private final ImmutableSortedSet<? extends SourcePath> resources; private final ImmutableSet<SourcePath> additionalCoverageTargets; private final Supplier<ImmutableSortedSet<BuildRule>> additionalDeps; private final ImmutableSet<String> labels; private final ImmutableSet<String> contacts; private final boolean runTestSeparately; private final Optional<Long> testRuleTimeoutMs; public CxxTest( BuildRuleParams params, Tool executable, ImmutableMap<String, String> env, Supplier<ImmutableList<String>> args, ImmutableSortedSet<? extends SourcePath> resources, ImmutableSet<SourcePath> additionalCoverageTargets, Supplier<ImmutableSortedSet<BuildRule>> additionalDeps, ImmutableSet<String> labels, ImmutableSet<String> contacts, boolean runTestSeparately, Optional<Long> testRuleTimeoutMs) { super(params); this.executable = executable; this.env = env; this.args = Suppliers.memoize(args); this.resources = resources; this.additionalCoverageTargets = additionalCoverageTargets; this.additionalDeps = Suppliers.memoize(additionalDeps); this.labels = labels; this.contacts = contacts; this.runTestSeparately = runTestSeparately; this.testRuleTimeoutMs = testRuleTimeoutMs; } @Override public Tool getExecutableCommand() { return executable; } @Override public final ImmutableList<Step> getBuildSteps( BuildContext context, BuildableContext buildableContext) { return ImmutableList.of(); } /** @return the path to which the test commands output is written. */ @VisibleForTesting protected Path getPathToTestExitCode() { return getPathToTestOutputDirectory().resolve("exitCode"); } /** @return the path to which the test commands output is written. */ @VisibleForTesting protected Path getPathToTestOutput() { return getPathToTestOutputDirectory().resolve("output"); } /** @return the path to which the framework-specific test results are written. */ @VisibleForTesting protected Path getPathToTestResults() { return getPathToTestOutputDirectory().resolve("results"); } /** @return the shell command used to run the test. */ protected abstract ImmutableList<String> getShellCommand( SourcePathResolver pathResolver, Path output); @Override public ImmutableList<Step> runTests( ExecutionContext executionContext, TestRunningOptions options, SourcePathResolver pathResolver, TestReportingCallback testReportingCallback) { return new ImmutableList.Builder<Step>() .addAll(MakeCleanDirectoryStep.of(getProjectFilesystem(), getPathToTestOutputDirectory())) .add(new TouchStep(getProjectFilesystem(), getPathToTestResults())) .add( new CxxTestStep( getProjectFilesystem(), ImmutableList.<String>builder() .addAll(getShellCommand(pathResolver, getPathToTestResults())) .addAll(args.get()) .build(), getEnv(pathResolver), getPathToTestExitCode(), getPathToTestOutput(), testRuleTimeoutMs)) .build(); } protected abstract ImmutableList<TestResultSummary> parseResults( Path exitCode, Path output, Path results) throws Exception; @Override public Callable<TestResults> interpretTestResults( final ExecutionContext executionContext, boolean isUsingTestSelectors) { return () -> { return TestResults.of( getBuildTarget(), ImmutableList.of( new TestCaseSummary( getBuildTarget().getFullyQualifiedName(), parseResults( getPathToTestExitCode(), getPathToTestOutput(), getPathToTestResults()))), contacts, labels.stream().map(Object::toString).collect(MoreCollectors.toImmutableSet())); }; } @Override public ImmutableSet<String> getLabels() { return labels; } @Override public ImmutableSet<String> getContacts() { return contacts; } protected ImmutableSet<SourcePath> getAdditionalCoverageTargets() { return additionalCoverageTargets; } @Override public Path getPathToTestOutputDirectory() { return BuildTargets.getGenPath(getProjectFilesystem(), getBuildTarget(), "__test_%s_output__"); } @Override public boolean runTestSeparately() { return runTestSeparately; } @Override public boolean supportsStreamingTests() { return false; } @Override public Stream<BuildTarget> getRuntimeDeps() { return additionalDeps.get().stream().map(BuildRule::getBuildTarget); } protected ImmutableMap<String, String> getEnv(SourcePathResolver pathResolver) { return new ImmutableMap.Builder<String, String>() .putAll(executable.getEnvironment(pathResolver)) .putAll(env) .build(); } protected Supplier<ImmutableList<String>> getArgs() { return args; } }