package hudson.distTest;
import hudson.Launcher;
import hudson.Extension;
import hudson.FilePath;
import hudson.FilePath.FileCallable;
import hudson.util.FormValidation;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.AbstractProject;
import hudson.model.Computer;
import hudson.model.Executor;
import hudson.model.FreeStyleProject;
import hudson.model.Hudson;
import hudson.model.Label;
import hudson.model.Node;
import hudson.model.Queue;
import hudson.model.Queue.Executable;
import hudson.remoting.Callable;
import hudson.remoting.Future;
import hudson.remoting.VirtualChannel;
import hudson.slaves.SlaveComputer;
import hudson.tasks.Builder;
import hudson.tasks.BuildStepDescriptor;
import java.io.File;
import java.io.FileOutputStream;
import java.net.URISyntaxException;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.QueryParameter;
import javax.servlet.ServletException;
import java.io.IOException;
import java.io.Serializable;
import java.net.URL;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Target;
import org.apache.tools.ant.taskdefs.Javac;
import org.apache.tools.ant.taskdefs.optional.junit.FormatterElement;
import org.apache.tools.ant.taskdefs.optional.junit.JUnitTask;
import org.apache.tools.ant.taskdefs.optional.junit.JUnitTest;
import org.apache.tools.ant.types.Path;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
/**
* Core class for Distributed Testing Plugin. This class take test source code,
* compile it and run test always on one node.
*
* @author Miroslav Novak
*/
public class DistTestingBuilder extends Builder implements Serializable {
private DistLocations[] distLocations = new DistLocations[0];
private LibLocations[] libLocations = new LibLocations[0];
private final boolean waitForNodes;
private final boolean compileTests;
private final String testDir;
// root, dist, tests, results
private final Map<String, Map<String, String>> mapNodeWorkspacePaths = new HashMap<String, Map<String, String>>();
public BuildListener listener = null;
/**
* Constructor for this build.
*
* @param distLocations locations of distribution directories and jar files
* @param libLocations locations of libraries - jar of directory
* @param testDir where resides directory with compiled tests
* @param waitForNodes whether build should wait for busy executors on online nodes
* @param compileTests whether compile tests sources - compiles all the jave source classes in workspace
*/
@DataBoundConstructor
public DistTestingBuilder(DistLocations[] distLocations, LibLocations[] libLocations,
String testDir, boolean waitForNodes, boolean compileTests) {
this.distLocations = distLocations;
this.libLocations = libLocations;
this.waitForNodes = waitForNodes;
this.testDir = testDir;
this.compileTests = compileTests;
Hudson h = Hudson.getInstance();
}
/**
* Method that actually run the whole build step and controll it
*
* @param build
* @param launcher
* @param listener
* @return
*/
@Override
public boolean perform(AbstractBuild build, Launcher launcher, final BuildListener listener) {
this.listener = listener;
LinkedList<Node> nodesList = new LinkedList<Node>();
for (Node n : build.getProject().getAssignedLabel().getNodes()) {
nodesList.add(n);
}
return entrySynchronizedSectionForNode(nodesList, build, launcher);
}
/**
* This perform2 method is run in the synchronized section because all nodes
* in label are shared resources we need to run test.
*
* @param build build representing this build
* @param launcher launcher for this build
* @return
*/
public boolean perform2(AbstractBuild build, Launcher launcher) {
try {
Label label = build.getProject().getAssignedLabel();
if (label == null) {
throw new Exception("Set label in the \"Tie this project to a node\" section +"
+ " in the project configaration.");
}
for (Node node : label.getNodes()) {
if (node.toComputer() instanceof Hudson.MasterComputer) {
throw new Exception("Label " + label + " cannot contain the \"master\" computer.");
}
}
if (label.getTotalConfiguredExecutors() < 2) {
throw new Exception("Number of executors in label "
+ label + " must be more than one. Distributed Testing was cancelled.");
}
// wait for freeing executors if some are busy
// don't wait for offline nodes
// (this executor is not idle (so +1))
waitForNodes(label);
// if there are no executors on which is possible to run tests then trow exception
if (label.getIdleExecutors() < 1 && !waitForNodes) {
throw new Exception("There are no idle/free executors in label : " + label
+ " on which could be possible to run tests. There must be at least 2 free exeutors. "
+ "This build is cancelled. Check \"Wait for nodes which are busy\" box "
+ "if you want to wait for freeing nodes. It is possible that the nodes "
+ "in this label are offline too.");
}
// sends a project to all executors which are idle - after it it's
// execution they're not able to accept any tasks
Computer c = null;
DistForkTask t = null;
Hudson h = Hudson.getInstance();
for (Node n : label.getNodes()) {
c = n.toComputer();
if (c.isOnline()) {
for (Executor e : c.getExecutors()) {
if (e.isIdle()) {
t = createTask(n.getSelfLabel());
// run and wait for the completion
Queue q = h.getQueue();
Future<Executable> f = (Future<Executable>) q.schedule(t, 0).getFuture();
try {
f.get();
} catch (CancellationException ex) {
// don't care about cancelation
} catch (InterruptedException ex) {
// if the command itself is aborted, cancel the execution
// don't care this too
}
}
}
}
}
listener.getLogger().println();
listener.getLogger().println("Lists all nodes in label " + label + " :");
for (Node node : label.getNodes()) {
listener.getLogger().println(node.getNodeName());
}
copyDists(build);
// copy the necessary libraries needed to run and compile tests - creates lib directory
listener.getLogger().print("Copying libraries: ");
copyLibraries(build.getWorkspace().child("lib"));
listener.getLogger().println("finished");
if (isCompileTests()) {
listener.getLogger().print("Compiling sources on slave " + build.getBuiltOnStr() + " :");
compileTests(build);
listener.getLogger().println("finished");
}
// LOAD ALL TEST TO THE QUEUE - String class name (example: "helloworld.Hello")
LinkedList<String> tests = findTestsInProjectWorkspace(build.getWorkspace().child(testDir));
listener.getLogger().println("Print all tests files:");
for (Iterator<String> it = tests.iterator(); it.hasNext();) {
listener.getLogger().println(it.next());
}
// MAKE A MAP (NODENAME -> (DIR -> DIRPATH)) FOR EACH NODE
// master is not there even if he is in the label
fillMapNodeWorkSpace(build);
// create directory for test results because ant is not able to do so
build.getWorkspace().child("results").mkdirs();
// find all executors which are online
Map<Executor, Future<Boolean>> mapExecutorFuture = getAllUseableExetors(label);
// list them
listener.getLogger().println("Lists all useable executors: ");
for (Executor e : mapExecutorFuture.keySet()) {
listener.getLogger().println(e.toString() + "is idle: " + e.isIdle());
}
// run tests on them
String testClassName = null;
boolean noFreeExecutors = true;
Future<Boolean> future = null;
FilePath testFilePathOnNode = null;
while (!tests.isEmpty()) {
for (Executor e : mapExecutorFuture.keySet()) {
// if executor is idle then run a test on it
if (e.getOwner().isOnline() && e.isIdle() && (testClassName = tests.pollFirst()) != null) {
testFilePathOnNode = createFilePathOnNodeForTest(e.getOwner().getNode(), testClassName);
mapExecutorFuture.put(e, runTestOnNode(e.getOwner().getNode(), testClassName, testFilePathOnNode));
}
}
// no free executors then try to find (if no tests end)
while (noFreeExecutors && (!tests.isEmpty())) {
for (Executor e : mapExecutorFuture.keySet()) {
try {
if ((future = mapExecutorFuture.get(e)) != null) {
future.get(100, TimeUnit.MILLISECONDS);
mapExecutorFuture.put(e, null);
noFreeExecutors = false;
}
} catch (TimeoutException ex) {
// when test not finished due to this timeout just go on
}
}
}
}
// it can happen that some tests wait in the task queue
// for the given executor - in this case we have to wait for them
listener.getLogger().println("Waiting for last tests results");
for (Executor e : mapExecutorFuture.keySet()) {
if ((future = mapExecutorFuture.get(e)) != null) {
future.get();
}
}
listener.getLogger().println("Copy test results back to the master's artifact directory");
copyResults(build);
} catch (Throwable ex) {
ex.printStackTrace(listener.getLogger());
} finally {
//print paths in mapNodeWorkspacePaths
for (Node n : build.getProject().getAssignedLabel().getNodes()) {
listener.getLogger().println(n.getNodeName());
}
for (String nodeName : mapNodeWorkspacePaths.keySet()) {
listener.getLogger().println("Paths for node " + nodeName);
Map<String, String> paths = mapNodeWorkspacePaths.get(nodeName);
for (String dir : paths.keySet()) {
listener.getLogger().print("Dir: " + dir + " path: " + paths.get(dir));
listener.getLogger().println();
}
}
}
return true;
}
/**
* Run test on the given node - runs ant programatically on this node
*
* @param node node where to run this test
* @param testClassName name of the class f.e. "helloworld.Hello"
* @param testFilePath path on node where to find test file class
* @return future whether the test finished
* @throws IOException
* @throws InterruptedException
*/
public Future<Boolean> runTestOnNode(Node node, final String testClassName, FilePath testFilePath) throws IOException, InterruptedException {
final String nodeName = node.getNodeName();
Future<Boolean> result = null;
listener.getLogger().println("Run test " + testClassName + " on node " + nodeName);
result = testFilePath.actAsync(new FileCallable<Boolean>() {
public Boolean invoke(File file, VirtualChannel channel) throws IOException, InterruptedException {
Project project = null;
try {
File baseDir = new File(mapNodeWorkspacePaths.get(nodeName).get("root"));
File resultsDir = new File(baseDir, "results");
// listener.getLogger().println("BaseDir is " + baseDir.getAbsolutePath());
project = new Project();
// DefaultLogger consoleLogger = new DefaultLogger();
// consoleLogger.setErrorPrintStream(System.err);
// consoleLogger.setOutputPrintStream(System.out);
// consoleLogger.setMessageOutputLevel(Project.MSG_DEBUG);
// project.addBuildListener(consoleLogger);
project.init();
project.setBaseDir(baseDir);
JUnitTest test = new JUnitTest(testClassName, true, true, false);
test.setOutfile("Test-" + testClassName);
test.setTodir(resultsDir);
FormatterElement fe = new FormatterElement();
FormatterElement.TypeAttribute ta = new FormatterElement.TypeAttribute();
ta.setValue("xml");
fe.setType(ta);
test.addFormatter(fe);
JUnitTask junit = null;
try {
junit = new JUnitTask();
} catch (Exception ex) {
ex.printStackTrace(listener.getLogger());
}
junit.addTest(test);
junit.setProject(project);
junit.init();
Path p = junit.createClasspath();
p.add(p.systemClasspath);
File fileTest = new File(mapNodeWorkspacePaths.get(nodeName).get("tests"));
File fileDist = new File(mapNodeWorkspacePaths.get(nodeName).get("dist"));
if (!fileDist.exists()) {
throw new IOException("The Directory or the jar file " + fileDist.getAbsolutePath()
+ " doesn't exist on the slave.");
}
p.createPathElement().setLocation(fileTest);
p.createPathElement().setLocation(fileDist);
for (File d : fileDist.listFiles()) {
if (d.isFile()) {
p.createPathElement().setLocation(d);
}
}
File fileLib = new File(mapNodeWorkspacePaths.get(nodeName).get("lib"));
p.createPathElement().setLocation(fileLib);
for (File l : fileLib.listFiles()) {
if (l.isFile()) {
p.createPathElement().setLocation(l);
}
}
Target target = new Target();
target.setName("test");
target.addTask(junit);
project.addTarget("test", target);
project.executeTarget("test");
} finally {
project = null;
System.gc();
}
return true;
}
});
return result;
}
// overrided for better type safety.
// if your plugin doesn't really define any property on Descriptor,
// you don't have to do this.
@Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl) super.getDescriptor();
}
/**
* Gets the FilePath(for jar) on library where the class resides. It is used
* when we need to copy some class to the lib directory in the project workspace.
*
* @param class name f.e. "helloword.Hello"
* @return FilePath to the library where this class resides
*/
private FilePath getFilePathOnMasterForClass(String className) {
FilePath f = null;
try {
Class cls = this.getClass().getClassLoader().loadClass(className);
ProtectionDomain pDomain = cls.getProtectionDomain();
CodeSource cSource = pDomain.getCodeSource();
URL url = cSource.getLocation(); // file:/c:/almanac14/examples/
f = new FilePath(new File(url.toURI()));
} catch (URISyntaxException ex) {
ex.printStackTrace(listener.getLogger());
} catch (ClassNotFoundException ex) {
ex.printStackTrace(listener.getLogger());
}
return f;
}
/**
* @return the waitForNodes
*/
public boolean isWaitForNodes() {
return waitForNodes;
}
/**
* Copies test results from "results" directory to the artifact directory and zip it
* into "test_results.zip" file.
*
* @param build
* @throws IOException
* @throws InterruptedException
*/
private void copyResults(AbstractBuild build) throws IOException, InterruptedException {
final String buildOnNodeName = build.getBuiltOnStr();
final String artifactDir = build.getArtifactsDir().getAbsolutePath();
FilePath workspace = build.getWorkspace();
workspace.act(new FileCallable<Void>() {
public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
File zip = new File(f, "test_results.zip");
FileOutputStream out =
new FileOutputStream(zip);
new FilePath(new File(mapNodeWorkspacePaths.get(buildOnNodeName).get("results"))).zip(out, "*.xml");
new FilePath(zip).copyTo(new FilePath(channel, artifactDir).child(zip.getName()));
return null;
}
});
}
/**
* Copies all the libraries needed for test compilation and running test to the "lib"
* directory in the project workspace.
*
* @param toWhere where resides the project's lib directory
* @return true if all is ok
* @throws IOException
* @throws InterruptedException
* @throws Exception
*/
private boolean copyLibraries(FilePath toWhere) throws IOException, InterruptedException, Exception {
if (!toWhere.exists()) {
toWhere.mkdirs();
}
FilePath junit = getFilePathOnMasterForClass("org.junit.runner.JUnitCore");
FilePath antJunit = getFilePathOnMasterForClass("org.apache.tools.ant.taskdefs.optional.junit.JUnitTask");
FilePath ant = getFilePathOnMasterForClass("org.apache.tools.ant.Project");
junit.copyTo(toWhere.child(junit.getName()));
antJunit.copyTo(toWhere.child(antJunit.getName()));
ant.copyTo(toWhere.child(ant.getName()));
// if libdir was set then try to find and copy libraries
if (getLibLocations() != null) {
for (LibLocations lib : getLibLocations()) {
String normalizeLibDir = normalizeRelPathForNode(Hudson.MasterComputer.currentComputer().getNode(), lib.getLibDir());
File libDirFile = new File(normalizeLibDir);
if (libDirFile.exists()) {
if (libDirFile.isDirectory()) {
//copyrec
new FilePath(libDirFile).copyRecursiveTo(toWhere);
} else {
//copy file
new FilePath(libDirFile).copyTo(toWhere.child(libDirFile.getName()));
}
}
}
}
return true;
}
/**
* Search in the directory with tests in the project's workspace and put all
* the classes with "test" string in the name (case insensitive) to the queue.
* @param dirWithTest diretory with compiled test classes
* @return list of test's classes names
* @throws IOException
* @throws Exception
*/
private LinkedList<String> findTestsInProjectWorkspace(FilePath dirWithTest) throws IOException, Exception {
TestFilePathVisitor filePathVisitor = new TestFilePathVisitor();
// scanner which go through the directory with tests and pass to TestVisitor
FilePathDirScanner filePathDirScanner = new FilePathDirScanner();
filePathDirScanner.scan(dirWithTest, filePathVisitor);
if (filePathVisitor.getListOfTests().size() <= 0) {
throw new Exception("Could not find any test classes in the directory: " + dirWithTest);
}
return filePathVisitor.getListOfTests();
}
/**
* Creates file path for the given test file on the node.
*
* @param node node for which this file path is
* @param testClassName name of the test class
* @return filepath on the test on the node
*/
private FilePath createFilePathOnNodeForTest(Node node, String testClassName) {
StringTokenizer st = new StringTokenizer(testClassName, ".");
FilePath testFilePathOnNode =
new FilePath(node.getChannel(),
mapNodeWorkspacePaths.get(node.getNodeName()).get("tests"));
for (int j = 0; j < st.countTokens() - 1; j++) {
testFilePathOnNode = testFilePathOnNode.child(st.nextToken());
}
return testFilePathOnNode.child(st.nextToken().concat(".class"));
}
/**
* @return the testDir
*/
public String getTestDir() {
return testDir;
}
/**
* Find all executors in the specified label which are on online nodes.
*
* @param label Label assigned to this project
* @return list of Executors
*/
private Map<Executor, Future<Boolean>> getAllUseableExetors(Label label) {
Map<Executor, Future<Boolean>> map = new HashMap<Executor, Future<Boolean>>();
for (Node node : label.getNodes()) {
if (node.toComputer().isOnline()) {
for (Executor e : node.toComputer().getExecutors()) {
map.put(e, null);
}
}
}
return map;
}
/**
* Fills the map (nodeName -> (directory -> path)) which is used on slaves to
* find files and directories in their NFS workspace.
*
* @param build this build
* @throws IOException
* @throws Exception
*/
private void fillMapNodeWorkSpace(AbstractBuild build) throws IOException, Exception {
Label label = build.getProject().getAssignedLabel();
HashMap<String, String> paths = null;
FilePath projectWorkspaceOnNode = null;
// name of the workspace on node
String workspace = "workspace";
for (Node node : label.getNodes()) {
// we don't want to use master even if he is in the label
if (node.toComputer() instanceof SlaveComputer && node.toComputer().isOnline()) {
paths = new HashMap<String, String>();
projectWorkspaceOnNode = new FilePath(node.getRootPath(), workspace).child(build.getProject().getName());
paths.put("root", projectWorkspaceOnNode.getRemote());
paths.put("lib", projectWorkspaceOnNode.child("lib").getRemote());
if (testDir == null || "".equals(testDir)) {
paths.put("tests", projectWorkspaceOnNode.child("tests").getRemote());
} else {
paths.put("tests", projectWorkspaceOnNode.child(normalizeRelPathForNode(node, testDir)).getRemote());
}
paths.put("results", projectWorkspaceOnNode.child("results").getRemote());
paths.put("dist", projectWorkspaceOnNode.child("dist").getRemote());
mapNodeWorkspacePaths.put(node.getNodeName(), paths);
}
}
}
/**
* Return file path on the master file system to the specified directory or file.
* @param distDir Absolute or relative (to the project workspace) path to dist. dir.
* @return file path to the dist. directory or file or null when not found
* @throws IOException
* @throws InterruptedException
*/
private FilePath getDistDirFilePath(String distDir) throws IOException, InterruptedException {
FilePath distDirFilePath = new FilePath(new File(distDir));
if (!distDirFilePath.exists()) {
return null;
}
return distDirFilePath;
}
/**
* When user set "compile tests" checkbox then all sources in the workspace
* will be compiled. All necessary libraries must be present in the "lib" directory.
* Compiles test classes are saved in the specified "directory with tests" directory.
*
* @param build build of this project
* @throws IOException
* @throws InterruptedException
*/
private void compileTests(AbstractBuild build) throws IOException, InterruptedException {
// get workspace filapath on the built-on slave and compile
build.getWorkspace().act(new FileCallable<Boolean>() {
public Boolean invoke(File f, VirtualChannel channel) {
Project project = null;
try {
// create dir for compiled tests
File testsDir = null;
if (testDir == null || "".equals(testDir)) {
testsDir = new File(f, "tests");
} else {
testsDir = new File(f, testDir.replace("/", System.getProperty("file.separator")));
}
if (!testsDir.exists()) {
testsDir.mkdir();
}
project = new Project();
project.setBaseDir(f);
Target compTestTarget = new Target();
compTestTarget.setName("compile-tests");
Javac javacTask = new Javac();
javacTask.setClasspath(Path.systemClasspath);
// load libraries
Path libs = javacTask.createClasspath();
File libDirFile = new File(f, "lib");
libs.createPathElement().setLocation(libDirFile);
for (File l : libDirFile.listFiles()) {
if (l.isFile()) {
libs.createPathElement().setLocation(l);
}
}
// load dists
Path dists = javacTask.createClasspath();
File distsDirFile = new File(f, "dist");
dists.createPathElement().setLocation(distsDirFile);
for (File d : distsDirFile.listFiles()) {
if (d.isFile()) {
dists.createPathElement().setLocation(d);
}
}
Path srcPath = new Path(project);
srcPath.setLocation(f);
javacTask.setSrcdir(srcPath);
javacTask.setDestdir(testsDir);
javacTask.setProject(project);
compTestTarget.addTask(javacTask);
project.addTarget("compile-tests", compTestTarget);
project.executeTarget("compile-tests");
} finally {
project = null;
System.gc();
}
return true;
}
});
}
/**
* @return the compileTests
*/
public boolean isCompileTests() {
return compileTests;
}
/**
* When user set relative file path - we don't know if on the slave is the same
* file system. So we need to check it and correct it if necessary.
*
* @param node node where for which we want to normalize it
* @param path rel. path
* @return normalized path
* @throws IOException
* @throws Exception
*/
private String normalizeRelPathForNode(Node node, String path) throws IOException, Exception {
String separator = getFileSeparatorForNode(node);
return path.replace("/", separator);
}
/**
* Gets file separator of the given node. Taken from System.getProperty("file.separator")
* on the node
* @param node which node
* @return separotor "/" or "\\"
* @throws IOException
* @throws Exception
*/
private String getFileSeparatorForNode(Node node) throws IOException, Exception {
return node.getChannel().call(new Callable<String, Exception>() {
public String call() throws Exception {
return System.getProperty("file.separator");
}
});
}
/**
* @return the distLocations
*/
public DistLocations[] getDistLocations() {
return distLocations;
}
/**
* @return the libLocations
*/
public LibLocations[] getLibLocations() {
return libLocations;
}
/**
* Copies dist. directories or files to the "dist" directory in the project workspace.
*
* @param build build
* @return true if all went well
* @throws IOException
* @throws InterruptedException
* @throws Exception
*/
private boolean copyDists(AbstractBuild build) throws IOException, InterruptedException, Exception {
for (DistLocations dist : getDistLocations()) {
if (dist.getDistDir() != null && !"".equals(dist.getDistDir())) {
FilePath distDirFilePath = getDistDirFilePath(dist.getDistDir());
if (distDirFilePath == null) {
if (!new FilePath(build.getWorkspace(),
normalizeRelPathForNode(build.getBuiltOn(), dist.getDistDir())).exists()) {
listener.getLogger().println(dist.getDistDir() + " :wasn't recognized as a valid "
+ "absolute path on master or a relative path in the project workspace on +"
+ "slave " + build.getBuiltOnStr());
}
//don't copy
continue;
}
listener.getLogger().print("Copying the distribution directory from "
+ distDirFilePath.getRemote() + " to slave " + build.getBuiltOn()
+ " to " + build.getWorkspace().getRemote() + ": ");
distDirFilePath.copyRecursiveTo(build.getWorkspace().child("dist"));
listener.getLogger().println("finished");
}
}
return true;
}
/**
* Locks executors of the nodes. Enter synchronized section controlled by the monitor
* of the node. After it no jobs can be executed on those nodes exept mine.
*
* @param nodesList list of nodes which should be locked
* @param build build
* @param launcher launcher of this build
* @return
*/
private boolean entrySynchronizedSectionForNode(LinkedList<Node> nodesList,
AbstractBuild build, Launcher launcher) {
synchronized (nodesList.poll().toComputer()) {
if (nodesList.isEmpty()) {
return perform2(build, launcher);
} else {
return entrySynchronizedSectionForNode(nodesList, build, launcher);
}
}
}
/**
* If wait for nodes/executors was checked then wait for all executors
* on all nodes which are online and busy.
* @param label Label which was set for this project/build
* @throws InterruptedException
*/
private void waitForNodes(Label label) throws InterruptedException {
while (waitForNodes && (label.getIdleExecutors() + 1 < label.getTotalExecutors())) {
listener.getLogger().println("Waiting for all other slaves in label " + label);
Thread.sleep(1000);
}
}
/**
* Used to create a new distFork task for locking executor.
*
* @param l label of the node to which this task will be sent
* @return new task
*/
private DistForkTask createTask(Label l) {
final int[] exitCode = new int[]{-1};
String name = "Lock For This Executor (on label " + l.getName()
+ ". During and after this job no one can access this executor until it's released.";
long duration = 100;
DistForkTask t = new DistForkTask(l, name, duration, new Runnable() {
public void run() {
try {
Computer c = Computer.currentComputer();
Node n = c.getNode();
FilePath workDir = n.getRootPath().createTempDir("distfork", null);
try {
Launcher launcher = n.createLauncher(listener);
exitCode[0] = launcher.launch().cmds(new String[]{""}).stdout(listener).join();
} finally {
workDir.deleteRecursive();
}
} catch (InterruptedException e) {
exitCode[0] = -1;
} catch (Exception e) {
exitCode[0] = -1;
}
}
});
return t;
}
/**
* Descriptor for {@link DistTestBuilder}. Used as a singleton.
* The class is marked as public so that it can be accessed from views.
*
*/
@Extension // this marker indicates Hudson that this is an implementation of an extension point.
public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
/**
* Performs on-the-fly validation of the form field 'name'.
*
* @param value
* This parameter receives the value that the user has typed.
* @return
* Indicates the outcome of the validation. This is sent to the browser.
*/
public FormValidation doCheckDistDir(@QueryParameter String value) throws IOException, ServletException, InterruptedException {
if (value.length() == 0) {
return FormValidation.warning("If you don't set the distribution path "
+ "then tests will search for the compiled classes in the standard classpath of the slave");
}
return FormValidation.ok();
}
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
return true;
}
/**
* This human readable name is used in the configuration screen.
*/
public String getDisplayName() {
return "Distibuted Testing";
}
@Override
public boolean configure(StaplerRequest req, JSONObject o) throws FormException {
// to persist global configuration information,
// set that to properties and call save().
save();
return super.configure(req, o);
}
}
@ExportedBean
public static final class DistLocations implements Serializable {
@Exported
public final String distDir;
@DataBoundConstructor
public DistLocations(String distDir) {
this.distDir = distDir;
}
public String getDistDir() {
return distDir;
}
}
@ExportedBean
public static final class LibLocations implements Serializable {
@Exported
public final String libDir;
@DataBoundConstructor
public LibLocations(String libDir) {
this.libDir = libDir;
}
public String getLibDir() {
return libDir;
}
}
}