|
@@ -0,0 +1,383 @@
|
|
|
+/*
|
|
|
+ * 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 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;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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().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.
|
|
|
+ getExecutionProperties().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.
|
|
|
+ getExecutionProperties().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);
|
|
|
+ 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!");
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|