/* * 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.rules.BuildRule; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.ExternalTestRunnerRule; import com.facebook.buck.rules.ExternalTestRunnerTestSpec; import com.facebook.buck.rules.ForwardingBuildTargetSourcePath; import com.facebook.buck.rules.HasRuntimeDeps; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.Tool; import com.facebook.buck.step.ExecutionContext; import com.facebook.buck.test.TestResultSummary; import com.facebook.buck.test.TestRunningOptions; import com.facebook.buck.test.result.type.ResultType; import com.facebook.buck.util.XmlDomParser; import com.google.common.base.Charsets; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.base.Supplier; 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.io.BufferedReader; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Stack; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @SuppressWarnings("PMD.TestClassWithoutTestCases") public class CxxBoostTest extends CxxTest implements HasRuntimeDeps, ExternalTestRunnerRule { private static final Pattern SUITE_START = Pattern.compile("^Entering test suite \"(.*)\"$"); private static final Pattern SUITE_END = Pattern.compile("^Leaving test suite \"(.*)\"$"); private static final Pattern CASE_START = Pattern.compile("^Entering test case \"(.*)\"$"); private static final Pattern CASE_END = Pattern.compile("^Leaving test case \"(.*)\"(?:; testing time: (\\d+)ms)?$"); private static final Pattern ERROR = Pattern.compile("^.*\\(\\d+\\): error .*"); private final SourcePathRuleFinder ruleFinder; private final BuildRule binary; public CxxBoostTest( BuildRuleParams params, SourcePathRuleFinder ruleFinder, BuildRule binary, 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, executable, env, args, resources, additionalCoverageTargets, additionalDeps, labels, contacts, runTestSeparately, testRuleTimeoutMs); this.ruleFinder = ruleFinder; this.binary = binary; } @Override public SourcePath getSourcePathToOutput() { return new ForwardingBuildTargetSourcePath( getBuildTarget(), Preconditions.checkNotNull(binary.getSourcePathToOutput())); } @Override protected ImmutableList<String> getShellCommand(SourcePathResolver pathResolver, Path output) { return ImmutableList.<String>builder() .addAll(getExecutableCommand().getCommandPrefix(pathResolver)) .add("--log_format=hrf") .add("--log_level=test_suite") .add("--report_format=xml") .add("--report_level=detailed") .add("--result_code=no") .add("--report_sink=" + getProjectFilesystem().resolve(output)) .build(); } private void visitTestSuite( ImmutableList.Builder<TestResultSummary> builder, Map<String, String> messages, Map<String, List<String>> stdout, Map<String, Long> times, String prefix, Node testSuite) { NamedNodeMap attributes = testSuite.getAttributes(); String suiteName = attributes.getNamedItem("name").getNodeValue(); if (!prefix.isEmpty()) { suiteName = prefix + "." + suiteName; } NodeList testCases = testSuite.getChildNodes(); for (int index = 0; index < testCases.getLength(); index++) { Node testCase = testCases.item(index); if (!testCase.getNodeName().equals("TestCase")) { visitTestSuite(builder, messages, stdout, times, suiteName, testCase); continue; } NamedNodeMap attrs = testCase.getAttributes(); String caseName = attrs.getNamedItem("name").getNodeValue(); String test = String.format("%s.%s", suiteName, caseName); Long time = Optional.ofNullable(times.get(test)).orElse(0L); String resultString = attrs.getNamedItem("result").getNodeValue(); ResultType result = ResultType.SUCCESS; String output = ""; String message = ""; if (!"passed".equals(resultString)) { result = ResultType.FAILURE; message = messages.get(test); output = Joiner.on("\n").join(stdout.get(test)); } builder.add( new TestResultSummary( suiteName, caseName, result, time, message, /* stacktrace */ "", /* stdOut */ output, /* stdErr */ "")); } } @Override protected ImmutableList<TestResultSummary> parseResults(Path exitCode, Path output, Path results) throws Exception { ImmutableList.Builder<TestResultSummary> summariesBuilder = ImmutableList.builder(); // Process the test run output to grab the individual test stdout/stderr and // test run times. Map<String, String> messages = new HashMap<>(); Map<String, List<String>> stdout = new HashMap<>(); Map<String, Long> times = new HashMap<>(); try (BufferedReader reader = Files.newBufferedReader(output, Charsets.US_ASCII)) { Stack<String> testSuite = new Stack<>(); Optional<String> currentTest = Optional.empty(); String line; while ((line = reader.readLine()) != null) { Matcher matcher; if ((matcher = SUITE_START.matcher(line)).matches()) { String suite = matcher.group(1); testSuite.push(suite); } else if ((matcher = SUITE_END.matcher(line)).matches()) { String suite = matcher.group(1); Preconditions.checkState(testSuite.peek().equals(suite)); testSuite.pop(); } else if ((matcher = CASE_START.matcher(line)).matches()) { String test = Joiner.on(".").join(testSuite) + "." + matcher.group(1); currentTest = Optional.of(test); stdout.put(test, new ArrayList<>()); } else if ((matcher = CASE_END.matcher(line)).matches()) { String test = Joiner.on(".").join(testSuite) + "." + matcher.group(1); Preconditions.checkState(currentTest.isPresent() && currentTest.get().equals(test)); String time = matcher.group(2); times.put(test, time == null ? 0 : Long.valueOf(time)); currentTest = Optional.empty(); } else if (currentTest.isPresent()) { if (ERROR.matcher(line).matches()) { messages.put(currentTest.get(), line); } else { Preconditions.checkNotNull(stdout.get(currentTest.get())).add(line); } } } } // Parse the XML result file for the actual test result summaries. Document doc = XmlDomParser.parse(results); Node testResult = doc.getElementsByTagName("TestResult").item(0); Node testSuite = testResult.getFirstChild(); visitTestSuite(summariesBuilder, messages, stdout, times, "", testSuite); return summariesBuilder.build(); } // The C++ test rules just wrap a test binary produced by another rule, so make sure that's // always available to run the test. @Override public Stream<BuildTarget> getRuntimeDeps() { return Stream.concat( super.getRuntimeDeps(), getExecutableCommand().getDeps(ruleFinder).stream().map(BuildRule::getBuildTarget)); } @Override public ExternalTestRunnerTestSpec getExternalTestRunnerSpec( ExecutionContext executionContext, TestRunningOptions testRunningOptions, SourcePathResolver pathResolver) { return ExternalTestRunnerTestSpec.builder() .setTarget(getBuildTarget()) .setType("boost") .addAllCommand(getExecutableCommand().getCommandPrefix(pathResolver)) .addAllCommand(getArgs().get()) .putAllEnv(getEnv(pathResolver)) .addAllLabels(getLabels()) .addAllContacts(getContacts()) .addAllAdditionalCoverageTargets( pathResolver.getAllAbsolutePaths(getAdditionalCoverageTargets())) .build(); } }