blob: 95b6264ba6036d37014f2917108f4c654fd3da84 [file] [log] [blame]
/*
* 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 org.apache.hadoop.maven.plugin.cmakebuilder;
import java.util.Locale;
import org.apache.hadoop.maven.plugin.util.Exec;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.concurrent.TimeUnit;
import java.util.LinkedList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
/**
* Goal which runs a native unit test.
*/
@Mojo(name="cmake-test", defaultPhase = LifecyclePhase.TEST)
public class TestMojo extends AbstractMojo {
/**
* A value for -Dtest= that runs all native tests.
*/
private final static String ALL_NATIVE = "allNative";
/**
* Location of the binary to run.
*/
@Parameter(required=true)
private File binary;
/**
* Name of this test.
*
* Defaults to the basename of the binary. So if your binary is /foo/bar/baz,
* this will default to 'baz.'
*/
@Parameter
private String testName;
/**
* Environment variables to pass to the binary.
*/
@Parameter
private Map<String, String> env;
/**
* Arguments to pass to the binary.
*/
@Parameter
private List<String> args = new LinkedList<String>();
/**
* Number of seconds to wait before declaring the test failed.
*
*/
@Parameter(defaultValue="600")
private int timeout;
/**
* The working directory to use.
*/
@Parameter
private File workingDirectory;
/**
* Path to results directory.
*/
@Parameter(defaultValue="native-results")
private File results;
/**
* A list of preconditions which must be true for this test to be run.
*/
@Parameter
private Map<String, String> preconditions = new HashMap<String, String>();
/**
* If true, pass over the test without an error if the binary is missing.
*/
@Parameter(defaultValue="false")
private boolean skipIfMissing;
/**
* What result to expect from the test
*
* Can be either "success", "failure", or "any".
*/
@Parameter(defaultValue="success")
private String expectedResult;
/**
* The Maven Session Object.
*/
@Parameter(defaultValue="${session}", readonly=true, required=true)
private MavenSession session;
// TODO: support Windows
private static void validatePlatform() throws MojoExecutionException {
if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH)
.startsWith("windows")) {
throw new MojoExecutionException("CMakeBuilder does not yet support " +
"the Windows platform.");
}
}
/**
* The test thread waits for the process to terminate.
*
* Since Process#waitFor doesn't take a timeout argument, we simulate one by
* interrupting this thread after a certain amount of time has elapsed.
*/
private static class TestThread extends Thread {
private Process proc;
private int retCode = -1;
public TestThread(Process proc) {
this.proc = proc;
}
public void run() {
try {
retCode = proc.waitFor();
} catch (InterruptedException e) {
retCode = -1;
}
}
public int retCode() {
return retCode;
}
}
/**
* Write to the status file.
*
* The status file will contain a string describing the exit status of the
* test. It will be SUCCESS if the test returned success (return code 0), a
* numerical code if it returned a non-zero status, or IN_PROGRESS or
* TIMED_OUT.
*/
private void writeStatusFile(String status) throws IOException {
FileOutputStream fos = new FileOutputStream(new File(results,
testName + ".pstatus"));
BufferedWriter out = null;
try {
out = new BufferedWriter(new OutputStreamWriter(fos, "UTF8"));
out.write(status + "\n");
} finally {
if (out != null) {
out.close();
} else {
fos.close();
}
}
}
private static boolean isTruthy(String str) {
if (str == null)
return false;
if (str.equalsIgnoreCase(""))
return false;
if (str.equalsIgnoreCase("false"))
return false;
if (str.equalsIgnoreCase("no"))
return false;
if (str.equalsIgnoreCase("off"))
return false;
if (str.equalsIgnoreCase("disable"))
return false;
return true;
}
final static private String VALID_PRECONDITION_TYPES_STR =
"Valid precondition types are \"and\", \"andNot\"";
/**
* Validate the parameters that the user has passed.
* @throws MojoExecutionException
*/
private void validateParameters() throws MojoExecutionException {
if (!(expectedResult.equals("success") ||
expectedResult.equals("failure") ||
expectedResult.equals("any"))) {
throw new MojoExecutionException("expectedResult must be either " +
"success, failure, or any");
}
}
private boolean shouldRunTest() throws MojoExecutionException {
// Were we told to skip all tests?
String skipTests = session.
getSystemProperties().getProperty("skipTests");
if (isTruthy(skipTests)) {
getLog().info("skipTests is in effect for test " + testName);
return false;
}
// Does the binary exist? If not, we shouldn't try to run it.
if (!binary.exists()) {
if (skipIfMissing) {
getLog().info("Skipping missing test " + testName);
return false;
} else {
throw new MojoExecutionException("Test " + binary +
" was not built! (File does not exist.)");
}
}
// If there is an explicit list of tests to run, it should include this
// test.
String testProp = session.
getSystemProperties().getProperty("test");
if (testProp != null) {
String testPropArr[] = testProp.split(",");
boolean found = false;
for (String test : testPropArr) {
if (test.equals(ALL_NATIVE)) {
found = true;
break;
}
if (test.equals(testName)) {
found = true;
break;
}
}
if (!found) {
getLog().debug("did not find test '" + testName + "' in "
+ "list " + testProp);
return false;
}
}
// Are all the preconditions satistfied?
if (preconditions != null) {
int idx = 1;
for (Map.Entry<String, String> entry : preconditions.entrySet()) {
String key = entry.getKey();
String val = entry.getValue();
if (key == null) {
throw new MojoExecutionException("NULL is not a valid " +
"precondition type. " + VALID_PRECONDITION_TYPES_STR);
}
if (key.equals("and")) {
if (!isTruthy(val)) {
getLog().info("Skipping test " + testName +
" because precondition number " + idx + " was not met.");
return false;
}
} else if (key.equals("andNot")) {
if (isTruthy(val)) {
getLog().info("Skipping test " + testName +
" because negative precondition number " + idx +
" was met.");
return false;
}
} else {
throw new MojoExecutionException(key + " is not a valid " +
"precondition type. " + VALID_PRECONDITION_TYPES_STR);
}
idx++;
}
}
// OK, we should run this.
return true;
}
public void execute() throws MojoExecutionException {
if (testName == null) {
testName = binary.getName();
}
validatePlatform();
validateParameters();
if (!shouldRunTest()) {
return;
}
if (!results.isDirectory()) {
if (!results.mkdirs()) {
throw new MojoExecutionException("Failed to create " +
"output directory '" + results + "'!");
}
}
List<String> cmd = new LinkedList<String>();
cmd.add(binary.getAbsolutePath());
getLog().info("-------------------------------------------------------");
getLog().info(" C M A K E B U I L D E R T E S T");
getLog().info("-------------------------------------------------------");
StringBuilder bld = new StringBuilder();
bld.append(testName).append(": running ");
bld.append(binary.getAbsolutePath());
for (String entry : args) {
cmd.add(entry);
bld.append(" ").append(entry);
}
getLog().info(bld.toString());
ProcessBuilder pb = new ProcessBuilder(cmd);
Exec.addEnvironment(pb, env);
if (workingDirectory != null) {
pb.directory(workingDirectory);
}
pb.redirectError(new File(results, testName + ".stderr"));
pb.redirectOutput(new File(results, testName + ".stdout"));
getLog().info("with extra environment variables " + Exec.envToString(env));
Process proc = null;
TestThread testThread = null;
int retCode = -1;
String status = "IN_PROGRESS";
try {
writeStatusFile(status);
} catch (IOException e) {
throw new MojoExecutionException("Error writing the status file", e);
}
long start = System.nanoTime();
try {
proc = pb.start();
testThread = new TestThread(proc);
testThread.start();
testThread.join(timeout * 1000);
if (!testThread.isAlive()) {
retCode = testThread.retCode();
testThread = null;
proc = null;
}
} catch (IOException e) {
throw new MojoExecutionException("IOException while executing the test " +
testName, e);
} catch (InterruptedException e) {
throw new MojoExecutionException("Interrupted while executing " +
"the test " + testName, e);
} finally {
if (testThread != null) {
// If the test thread didn't exit yet, that means the timeout expired.
testThread.interrupt();
try {
testThread.join();
} catch (InterruptedException e) {
getLog().error("Interrupted while waiting for testThread", e);
}
status = "TIMED OUT";
} else if (retCode == 0) {
status = "SUCCESS";
} else {
status = "ERROR CODE " + String.valueOf(retCode);
}
try {
writeStatusFile(status);
} catch (Exception e) {
getLog().error("failed to write status file!", e);
}
if (proc != null) {
proc.destroy();
}
}
long end = System.nanoTime();
getLog().info("STATUS: " + status + " after " +
TimeUnit.MILLISECONDS.convert(end - start, TimeUnit.NANOSECONDS) +
" millisecond(s).");
getLog().info("-------------------------------------------------------");
if (status.equals("TIMED_OUT")) {
if (expectedResult.equals("success")) {
throw new MojoExecutionException("Test " + binary +
" timed out after " + timeout + " seconds!");
}
} else if (!status.equals("SUCCESS")) {
if (expectedResult.equals("success")) {
throw new MojoExecutionException("Test " + binary +
" returned " + status);
}
} else if (expectedResult.equals("failure")) {
throw new MojoExecutionException("Test " + binary +
" succeeded, but we expected failure!");
}
}
}