package hudson.plugins.downstream_ext;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.*;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Cause;
import hudson.model.ItemGroup;
import hudson.model.Result;
import hudson.model.TaskListener;
import hudson.plugins.downstream_ext.DownstreamTrigger.Strategy;
import hudson.scm.SCM;
import hudson.util.StreamTaskListener;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
@SuppressWarnings("unchecked")
public class AsynchPollingTest {
private AbstractProject upstream;
private AbstractProject downstream;
private AbstractBuild upstreamBuild;
@Before
public void setup() {
upstream = mock(AbstractProject.class);
ItemGroup parent = mock(ItemGroup.class);
when(parent.getUrl()).thenReturn("http://foo");
when(upstream.getParent()).thenReturn(parent);
upstreamBuild = mock(AbstractBuild.class);
when(upstreamBuild.getProject()).thenReturn(upstream);
when(upstreamBuild.getResult()).thenReturn(Result.SUCCESS);
this.downstream = createDownstreamProject();
}
private static AbstractProject createDownstreamProject() {
final AbstractProject downstream = mock(AbstractProject.class);
when(downstream.pollSCMChanges(Mockito.<TaskListener>any())).thenReturn(Boolean.TRUE);
SCM blockingScm = mock(SCM.class);
when(blockingScm.requiresWorkspaceForPolling()).thenReturn(Boolean.TRUE);
when(downstream.getScm()).thenReturn(blockingScm);
return downstream;
}
/**
* Tests that for projects with SCMs which 'requiresWorkspaceForPolling'
* polling is started on a different thread.
*/
@Test
public void testPollingIsAsynchronous() throws IOException, InterruptedException {
final CountDownLatch startLatch = new CountDownLatch(1);
final CountDownLatch endLatch = new CountDownLatch(1);
final Cause[] causeHolder = new Cause[1];
DownstreamDependency dependency = new DownstreamDependency(upstream, downstream,
new DownstreamTrigger("", Result.SUCCESS, true, Strategy.AND_HIGHER,
MatrixTrigger.BOTH)) {
@Override
Runnable getPoller(AbstractProject p, Cause cause,
List<Action> actions) {
causeHolder[0] = cause;
final Runnable run = super.getPoller(p, cause, actions);
return new Runnable() {
@Override
public void run() {
startLatch.countDown();
run.run();
endLatch.countDown();
}
};
}
};
Action action = mock(Action.class);
boolean triggerSynchronously = dependency.shouldTriggerBuild(upstreamBuild,
new StreamTaskListener(), Collections.singletonList(action));
assertFalse(triggerSynchronously);
if(!startLatch.await(60, TimeUnit.SECONDS)) {
fail("Time out waiting for start latch");
}
if(!endLatch.await(60, TimeUnit.SECONDS)) {
fail("Time out waiting for end latch");
}
verify(downstream).scheduleBuild(downstream.getQuietPeriod(),
causeHolder[0], action);
}
/**
* Tests that for asynchronous polling only one polling is done in parallel.
*/
@Test
public void testOnlyOneParallelPoll() throws IOException, InterruptedException {
final CountDownLatch startLatch1 = new CountDownLatch(1);
final CountDownLatch endLatch1 = new CountDownLatch(1);
DownstreamDependency dependency = new DownstreamDependency(upstream, downstream,
new DownstreamTrigger("", Result.SUCCESS, true, Strategy.AND_HIGHER,
MatrixTrigger.BOTH)) {
@Override
Runnable getPoller(AbstractProject p, Cause cause,
List<Action> actions) {
final Runnable run = super.getPoller(p, cause, actions);
return new Runnable() {
@Override
public void run() {
startLatch1.countDown();
run.run();
try {
endLatch1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
}
};
boolean triggerSynchronously = dependency.shouldTriggerBuild(upstreamBuild,
new StreamTaskListener(), Collections.<Action>emptyList());
assertFalse(triggerSynchronously);
// wait until 1st poller is definitely running
if (!startLatch1.await(60, TimeUnit.SECONDS)) {
fail("Time out waiting for start latch");
}
final CountDownLatch startLatch2 = new CountDownLatch(1);
DownstreamDependency dependency2 = new DownstreamDependency(upstream, downstream,
new DownstreamTrigger("", Result.SUCCESS, true, Strategy.AND_HIGHER,
MatrixTrigger.BOTH)) {
@Override
Runnable getPoller(AbstractProject p, Cause cause,
List<Action> actions) {
final Runnable run = super.getPoller(p, cause, actions);
return new Runnable() {
@Override
public void run() {
startLatch2.countDown();
run.run();
}
};
}
};
dependency2.shouldTriggerBuild(upstreamBuild,
new StreamTaskListener(), Collections.<Action>emptyList());
boolean noTimeout = startLatch2.await(2, TimeUnit.SECONDS);
// assert that we timeout waiting for poller2 to start
assertFalse(noTimeout);
// poll on a different downstream job can still happen:
final CountDownLatch startLatch3 = new CountDownLatch(1);
AbstractProject newDownstream = createDownstreamProject();
DownstreamDependency dependency3 = new DownstreamDependency(upstream, newDownstream,
new DownstreamTrigger("", Result.SUCCESS, true, Strategy.AND_HIGHER,
MatrixTrigger.BOTH)) {
@Override
Runnable getPoller(AbstractProject p, Cause cause,
List<Action> actions) {
final Runnable run = super.getPoller(p, cause, actions);
return new Runnable() {
@Override
public void run() {
startLatch3.countDown();
run.run();
}
};
}
};
dependency3.shouldTriggerBuild(upstreamBuild,
new StreamTaskListener(), Collections.<Action>emptyList());
noTimeout = startLatch3.await(60, TimeUnit.SECONDS);
assertTrue(noTimeout);
// when poller 1 finishes, poller2 can continue:
endLatch1.countDown();
noTimeout = startLatch2.await(60, TimeUnit.SECONDS);
assertTrue(noTimeout);
}
}