blob: f4404feaa3d3d45804cb78018fbfc4735e9b7f77 [file] [log] [blame]
/*
* 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.junit;
import com.facebook.buck.test.selectors.TestDescription;
import com.facebook.buck.test.selectors.TestSelectorList;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
/**
* Base class for both the JUnit and TestNG runners.
*/
public abstract class BaseRunner {
protected static final String FILTER_DESCRIPTION = "TestSelectorList-filter";
protected static final String ENCODING = "UTF-8";
protected File outputDirectory;
protected List<String> testClassNames;
protected long defaultTestTimeoutMillis;
protected TestSelectorList testSelectorList;
protected boolean isDryRun;
protected Set<TestDescription> seenDescriptions = new HashSet<>();
public abstract void run() throws Throwable;
/**
* The test result file is written as XML to avoid introducing a dependency on JSON (see class
* overview).
*/
protected void writeResult(String testClassName, List<TestResult> results)
throws IOException, ParserConfigurationException, TransformerException {
// XML writer logic taken from:
// http://www.genedavis.com/library/xml/java_dom_xml_creation.jsp
DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = docBuilder.newDocument();
doc.setXmlVersion("1.1");
Element root = doc.createElement("testcase");
root.setAttribute("name", testClassName);
doc.appendChild(root);
for (TestResult result : results) {
Element test = doc.createElement("test");
// name attribute
test.setAttribute("name", result.testMethodName);
// success attribute
boolean isSuccess = result.isSuccess();
test.setAttribute("success", Boolean.toString(isSuccess));
// type attribute
test.setAttribute("type", result.type.toString());
// time attribute
long runTime = result.runTime;
test.setAttribute("time", String.valueOf(runTime));
// Include failure details, if appropriate.
Throwable failure = result.failure;
if (failure != null) {
String message = failure.getMessage();
test.setAttribute("message", message);
String stacktrace = stackTraceToString(failure);
test.setAttribute("stacktrace", stacktrace);
}
// stdout, if non-empty.
if (result.stdOut != null) {
Element stdOutEl = doc.createElement("stdout");
stdOutEl.appendChild(doc.createTextNode(result.stdOut));
test.appendChild(stdOutEl);
}
// stderr, if non-empty.
if (result.stdErr != null) {
Element stdErrEl = doc.createElement("stderr");
stdErrEl.appendChild(doc.createTextNode(result.stdErr));
test.appendChild(stdErrEl);
}
root.appendChild(test);
}
// Create an XML transformer that pretty-prints with a 2-space indent.
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer trans = transformerFactory.newTransformer();
trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
trans.setOutputProperty(OutputKeys.INDENT, "yes");
trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
// Write the result to a file.
String testSelectorSuffix = "";
if (!testSelectorList.isEmpty()) {
testSelectorSuffix += ".test_selectors";
}
if (isDryRun) {
testSelectorSuffix += ".dry_run";
}
File outputFile = new File(outputDirectory, testClassName + testSelectorSuffix + ".xml");
OutputStream output = new BufferedOutputStream(new FileOutputStream(outputFile));
StreamResult streamResult = new StreamResult(output);
DOMSource source = new DOMSource(doc);
trans.transform(source, streamResult);
output.close();
}
private String stackTraceToString(Throwable exc) {
StringWriter writer = new StringWriter();
exc.printStackTrace(new PrintWriter(writer, /* autoFlush */true));
return writer.toString();
}
/**
* Expected arguments are:
* <ul>
* <li>(string) output directory
* <li>(long) default timeout in milliseconds (0 for no timeout)
* <li>(string) newline separated list of test selectors
* <li>(string...) fully-qualified names of test classes
* </ul>
*/
protected void parseArgs(String... args) throws Throwable {
// Verify the arguments.
if (args.length == 0) {
System.err.println("Must specify an output directory.");
System.exit(1);
} else if (args.length == 1) {
System.err.println("Must specify an output directory and a default timeout.");
System.exit(1);
} else if (args.length == 2) {
System.err.println("Must specify some test selectors (or empty string for no selectors).");
System.exit(1);
} else if (args.length == 3) {
System.err.println("Must specify at least one test.");
System.exit(1);
}
// The first argument should specify the output directory.
File outputDirectory = new File(args[0]);
if (!outputDirectory.exists()) {
System.err.printf("The output directory did not exist: %s\n", outputDirectory);
System.exit(1);
}
long defaultTestTimeoutMillis = Long.parseLong(args[1]);
TestSelectorList testSelectorList = TestSelectorList.empty();
if (!args[2].isEmpty()) {
List<String> rawSelectors = Arrays.asList(args[2].split("\n"));
testSelectorList = TestSelectorList.builder()
.addRawSelectors(rawSelectors)
.build();
}
boolean isDryRun = !args[3].isEmpty();
// Each subsequent argument should be a class name to run.
List<String> testClassNames = Arrays.asList(args).subList(4, args.length);
this.outputDirectory = outputDirectory;
this.defaultTestTimeoutMillis = defaultTestTimeoutMillis;
this.isDryRun = isDryRun;
this.testClassNames = testClassNames;
this.testSelectorList = testSelectorList;
}
protected void runAndExit() throws Throwable {
// Run the tests.
try {
run();
} catch (Throwable e){
e.printStackTrace();
} finally {
// Explicitly exit to force the test runner to complete even if tests have sloppily left
// behind non-daemon threads that would have otherwise forced the process to wait and
// eventually timeout.
//
// Separately, we're using a successful exit code regardless of test outcome since JUnitRunner
// is designed to execute all tests and produce a report of success or failure. We've done
// that successfully if we've gotten here.
System.exit(0);
}
}
}