package hudson.model; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.*; import hudson.Launcher; import hudson.remoting.VirtualChannel; import hudson.slaves.DumbSlave; import hudson.slaves.OfflineCause; import hudson.tasks.Builder; import hudson.util.OneShotEvent; import jenkins.model.CauseOfInterruption.UserInterruption; import jenkins.model.InterruptedBuildAction; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import java.io.IOException; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import org.jvnet.hudson.test.TestExtension; public class ExecutorTest { @Rule public JenkinsRule j = new JenkinsRule(); @Test @Issue("JENKINS-4756") public void whenAnExecutorDiesHardANewExecutorTakesItsPlace() throws Exception { j.jenkins.setNumExecutors(1); Computer c = j.jenkins.toComputer(); Executor e = getExecutorByNumber(c, 0); j.jenkins.getQueue().schedule(new QueueTest.TestTask(new AtomicInteger()) { @Override public Queue.Executable createExecutable() throws IOException { throw new IllegalStateException("oops"); } }, 0); while (e.isActive()) { Thread.sleep(10); } waitUntilExecutorSizeIs(c, 1); assertNotNull(getExecutorByNumber(c, 0)); } private void waitUntilExecutorSizeIs(Computer c, int executorCollectionSize) throws InterruptedException { int timeOut = 10; while (c.getExecutors().size() != executorCollectionSize) { Thread.sleep(10); if (timeOut-- == 0) fail("executor collection size was not " + executorCollectionSize); } } private Executor getExecutorByNumber(Computer c, int executorNumber) { for (Executor executor : c.getExecutors()) { if (executor.getNumber() == executorNumber) { return executor; } } return null; } /** * Makes sure that the cause of interruption is properly recorded. */ @Test public void abortCause() throws Exception { FreeStyleProject p = j.createFreeStyleProject(); Future<FreeStyleBuild> r = startBlockingBuild(p); User johnny = User.get("Johnny"); p.getLastBuild().getExecutor().interrupt(Result.FAILURE, new UserInterruption(johnny), // test the merge semantics new UserInterruption(johnny)); FreeStyleBuild b = r.get(); // make sure this information is recorded assertEquals(b.getResult(), Result.FAILURE); InterruptedBuildAction iba = b.getAction(InterruptedBuildAction.class); assertEquals(1,iba.getCauses().size()); assertEquals(((UserInterruption) iba.getCauses().get(0)).getUser(), johnny); // make sure it shows up in the log assertTrue(b.getLog().contains(johnny.getId())); } @Test public void disconnectCause() throws Exception { DumbSlave slave = j.createOnlineSlave(); FreeStyleProject p = j.createFreeStyleProject(); p.setAssignedNode(slave); Future<FreeStyleBuild> r = startBlockingBuild(p); User johnny = User.get("Johnny"); p.getLastBuild().getBuiltOn().toComputer().disconnect( new OfflineCause.UserCause(johnny, "Taking offline to break your build") ); FreeStyleBuild b = r.get(); String log = b.getLog(); assertEquals(b.getResult(), Result.FAILURE); assertThat(log, containsString("Finished: FAILURE")); assertThat(log, containsString("Build step 'BlockingBuilder' marked build as failure")); assertThat(log, containsString("Agent went offline during the build")); assertThat(log, containsString("Disconnected by Johnny : Taking offline to break your buil")); } /** * Start a project with an infinite build step * * @param project {@link FreeStyleProject} to start * @return A {@link Future} object represents the started build * @throws Exception if somethink wrong happened */ public static Future<FreeStyleBuild> startBlockingBuild(FreeStyleProject project) throws Exception { final OneShotEvent e = new OneShotEvent(); project.getBuildersList().add(new BlockingBuilder(e)); Future<FreeStyleBuild> r = project.scheduleBuild2(0); e.block(); // wait until we are safe to interrupt assertTrue(project.getLastBuild().isBuilding()); return r; } private static final class BlockingBuilder extends Builder { private final OneShotEvent e; private BlockingBuilder(OneShotEvent e) { this.e = e; } @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { VirtualChannel channel = launcher.getChannel(); Node node = build.getBuiltOn(); e.signal(); // we are safe to be interrupted for (;;) { // Keep using the channel channel.call(node.getClockDifferenceCallable()); Thread.sleep(100); } } @TestExtension("disconnectCause") public static class DescriptorImpl extends Descriptor<Builder> {} } }