/* * Copyright 2012-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.shell; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargets; import com.facebook.buck.rules.AddToRuleKey; import com.facebook.buck.rules.BinaryBuildRule; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.CommandTool; import com.facebook.buck.rules.ExternalTestRunnerRule; import com.facebook.buck.rules.ExternalTestRunnerTestSpec; import com.facebook.buck.rules.HasRuntimeDeps; import com.facebook.buck.rules.NoopBuildRule; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.TestRule; import com.facebook.buck.rules.Tool; import com.facebook.buck.rules.args.Arg; import com.facebook.buck.rules.args.SourcePathArg; import com.facebook.buck.step.ExecutionContext; import com.facebook.buck.step.Step; import com.facebook.buck.step.fs.MakeCleanDirectoryStep; 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.facebook.buck.util.ObjectMappers; import com.google.common.annotations.VisibleForTesting; 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.Set; import java.util.concurrent.Callable; import java.util.stream.Stream; /** * Test whose correctness is determined by running a specified shell script. If running the shell * script returns a non-zero error code, the test is considered a failure. */ @SuppressWarnings("PMD.TestClassWithoutTestCases") public class ShTest extends NoopBuildRule implements TestRule, HasRuntimeDeps, ExternalTestRunnerRule, BinaryBuildRule { private final SourcePathRuleFinder ruleFinder; @AddToRuleKey private final SourcePath test; @AddToRuleKey private final ImmutableList<Arg> args; @AddToRuleKey private final ImmutableMap<String, Arg> env; @AddToRuleKey @SuppressWarnings("PMD.UnusedPrivateField") private final ImmutableSortedSet<? extends SourcePath> resources; private final Optional<Long> testRuleTimeoutMs; private final ImmutableSet<String> contacts; private final boolean runTestSeparately; private final ImmutableSet<String> labels; protected ShTest( BuildRuleParams params, SourcePathRuleFinder ruleFinder, SourcePath test, ImmutableList<Arg> args, ImmutableMap<String, Arg> env, ImmutableSortedSet<? extends SourcePath> resources, Optional<Long> testRuleTimeoutMs, boolean runTestSeparately, Set<String> labels, ImmutableSet<String> contacts) { super(params); this.ruleFinder = ruleFinder; this.test = test; this.args = args; this.env = env; this.resources = resources; this.testRuleTimeoutMs = testRuleTimeoutMs; this.runTestSeparately = runTestSeparately; this.labels = ImmutableSet.copyOf(labels); this.contacts = contacts; } @Override public ImmutableSet<String> getLabels() { return labels; } @Override public ImmutableSet<String> getContacts() { return contacts; } @Override public ImmutableList<Step> runTests( ExecutionContext executionContext, TestRunningOptions options, SourcePathResolver pathResolver, TestReportingCallback testReportingCallback) { return new ImmutableList.Builder<Step>() .addAll(MakeCleanDirectoryStep.of(getProjectFilesystem(), getPathToTestOutputDirectory())) .add( // Return a single command that runs an .sh file with no arguments. new RunShTestAndRecordResultStep( getProjectFilesystem(), pathResolver.getAbsolutePath(test), Arg.stringify(args, pathResolver), Arg.stringify(env, pathResolver), testRuleTimeoutMs, getBuildTarget().getFullyQualifiedName(), getPathToTestOutputResult())) .build(); } @Override public Path getPathToTestOutputDirectory() { return BuildTargets.getGenPath( getProjectFilesystem(), getBuildTarget(), "__java_test_%s_output__"); } @VisibleForTesting Path getPathToTestOutputResult() { return getPathToTestOutputDirectory().resolve("result.json"); } @Override public Callable<TestResults> interpretTestResults( final ExecutionContext context, boolean isUsingTestSelectors) { return () -> { Optional<String> resultsFileContents = getProjectFilesystem().readFileIfItExists(getPathToTestOutputResult()); TestResultSummary testResultSummary = ObjectMappers.readValue(resultsFileContents.get(), TestResultSummary.class); TestCaseSummary testCaseSummary = new TestCaseSummary( getBuildTarget().getFullyQualifiedName(), ImmutableList.of(testResultSummary)); return TestResults.of( getBuildTarget(), ImmutableList.of(testCaseSummary), contacts, labels.stream().map(Object::toString).collect(MoreCollectors.toImmutableSet())); }; } @Override public boolean runTestSeparately() { return runTestSeparately; } // A shell test has no real build dependencies. Instead interpret the dependencies as runtime // dependencies, as these are always components that the shell test needs available to run. @Override public Stream<BuildTarget> getRuntimeDeps() { return getBuildDeps().stream().map(BuildRule::getBuildTarget); } @Override public boolean supportsStreamingTests() { return false; } @Override public Tool getExecutableCommand() { CommandTool.Builder builder = new CommandTool.Builder() .addArg(SourcePathArg.of(test)) .addDeps(ruleFinder.filterBuildRuleInputs(resources)); for (Arg arg : args) { builder.addArg(arg); } for (ImmutableMap.Entry<String, Arg> envVar : env.entrySet()) { builder.addEnv(envVar.getKey(), envVar.getValue()); } return builder.build(); } @Override public ExternalTestRunnerTestSpec getExternalTestRunnerSpec( ExecutionContext executionContext, TestRunningOptions testRunningOptions, SourcePathResolver pathResolver) { return ExternalTestRunnerTestSpec.builder() .setTarget(getBuildTarget()) .setType("custom") .addCommand(pathResolver.getAbsolutePath(test).toString()) .addAllCommand(Arg.stringify(args, pathResolver)) .setEnv(Arg.stringify(env, pathResolver)) .addAllLabels(getLabels()) .addAllContacts(getContacts()) .build(); } @VisibleForTesting protected ImmutableList<Arg> getArgs() { return args; } @VisibleForTesting protected ImmutableMap<String, Arg> getEnv() { return env; } }