View Javadoc

1   package com.atlassian.sal.core.scheduling;
2   
3   import java.util.Collections;
4   import java.util.Date;
5   import java.util.Map;
6   
7   import com.atlassian.sal.api.scheduling.PluginJob;
8   import com.atlassian.scheduler.JobRunner;
9   import com.atlassian.scheduler.JobRunnerRequest;
10  import com.atlassian.scheduler.JobRunnerResponse;
11  import com.atlassian.scheduler.SchedulerService;
12  import com.atlassian.scheduler.SchedulerServiceException;
13  import com.atlassian.scheduler.config.JobConfig;
14  import com.atlassian.scheduler.config.JobId;
15  import com.atlassian.scheduler.config.RunMode;
16  import com.atlassian.scheduler.config.Schedule;
17  import com.atlassian.scheduler.status.RunOutcome;
18  
19  import org.junit.After;
20  import org.junit.Before;
21  import org.junit.Test;
22  import org.junit.runner.RunWith;
23  import org.mockito.ArgumentCaptor;
24  import org.mockito.Captor;
25  import org.mockito.Mock;
26  import org.mockito.runners.MockitoJUnitRunner;
27  
28  import static com.atlassian.sal.core.scheduling.DefaultPluginScheduler.JOB_RUNNER_KEY;
29  import static com.atlassian.sal.core.scheduling.DefaultPluginScheduler.toJobId;
30  import static com.atlassian.sal.core.util.Assert.notNull;
31  import static org.hamcrest.Matchers.containsString;
32  import static org.hamcrest.Matchers.is;
33  import static org.junit.Assert.assertThat;
34  import static org.junit.Assert.fail;
35  import static org.mockito.Matchers.eq;
36  import static org.mockito.Matchers.same;
37  import static org.mockito.Mockito.mock;
38  import static org.mockito.Mockito.verify;
39  import static org.mockito.Mockito.verifyZeroInteractions;
40  import static org.mockito.Mockito.when;
41  
42  @RunWith(MockitoJUnitRunner.class)
43  public class DefaultPluginSchedulerTest
44  {
45      // Used in case the tests are run multi-threaded.  The plugin job instances are created anew for each execution
46      // so there is no way to inject them with specific instances.
47      private static final ThreadLocal<JobChecker> CALLBACK = new ThreadLocal<JobChecker>()
48      {
49          @Override
50          protected JobChecker initialValue()
51          {
52              return mock(JobChecker.class);
53          }
54      };
55  
56      @Mock private SchedulerService schedulerService;
57      @Captor private ArgumentCaptor<JobRunner> runnerCaptor;
58  
59      private DefaultPluginScheduler pluginScheduler;
60  
61      @Before
62      public void setUp()
63      {
64          pluginScheduler = new DefaultPluginScheduler(schedulerService);
65          pluginScheduler.afterPropertiesSet();
66  
67          verify(schedulerService).registerJobRunner(eq(JOB_RUNNER_KEY), runnerCaptor.capture());
68      }
69  
70      @After
71      public void tearDown()
72      {
73          CALLBACK.remove();
74      }
75  
76  
77      @Test
78      public void testUnregisterOnDestroy()
79      {
80          pluginScheduler.destroy();
81  
82          verify(schedulerService).unregisterJobRunner(eq(JOB_RUNNER_KEY));
83      }
84  
85      @Test
86      public void testSchedule() throws SchedulerServiceException
87      {
88          final String jobName = "testjob";
89          final JobId jobId = toJobId(jobName);
90          final Map<String, Object> jobMap = Collections.<String, Object>singletonMap("a", "b");
91          final Date jobStartTime = new Date();
92          final int repeatInterval = 60000;
93  
94          // Part 1: The actual scheduling
95          pluginScheduler.scheduleJob(jobName, TestPluginJob.class, jobMap, jobStartTime, repeatInterval);
96  
97          verify(schedulerService).scheduleJob(jobId, JobConfig.forJobRunnerKey(JOB_RUNNER_KEY)
98                          .withRunMode(RunMode.RUN_LOCALLY)
99                          .withSchedule(Schedule.forInterval(repeatInterval, jobStartTime)) );
100 
101         // Part 2: Honouring execute requests
102         final JobRunnerRequest request = mock(JobRunnerRequest.class);
103         when(request.getJobId()).thenReturn(jobId);
104 
105         runnerCaptor.getValue().runJob(request);
106         verify(CALLBACK.get()).executedWith(same(jobMap));
107     }
108 
109     @Test
110     public void testUnschedule() throws Exception
111     {
112         final String jobName = "testjob";
113         final JobId jobId = toJobId(jobName);
114         final Map<String, Object> jobMap = Collections.<String, Object>singletonMap("a", "b");
115         final Date jobStartTime = new Date();
116         final int repeatInterval = 60000;
117 
118         pluginScheduler.scheduleJob(jobName, TestPluginJob.class, jobMap, jobStartTime, repeatInterval);
119 
120         // Part 1: The actual unscheduling
121         pluginScheduler.unscheduleJob(jobName);
122 
123         final JobRunnerRequest request = mock(JobRunnerRequest.class);
124         when(request.getJobId()).thenReturn(jobId);
125 
126         // Part 2: Correct handling of failed execute requests (race conditions)
127         final JobRunnerResponse response = notNull(runnerCaptor.getValue().runJob(request), "null response");
128         assertThat(response.getRunOutcome(), is(RunOutcome.ABORTED));
129         assertThat(response.getMessage(), is("Job descriptor not found"));
130         verifyZeroInteractions(CALLBACK.get());
131     }
132 
133     @Test
134     public void testUnscheduleInexisting()
135     {
136         final String jobName = "testjob";
137         final JobId jobId = toJobId(jobName);
138 
139         try
140         {
141             pluginScheduler.unscheduleJob(jobName);
142             fail("Expected an IllegalArgumentException");
143         }
144         catch (IllegalArgumentException iae)
145         {
146             assertThat(iae.getMessage(), containsString(jobName));
147         }
148 
149         // Just in case we're "wrong" and it really is registered.  The scheduler service doesn't mind this.
150         verify(schedulerService).unscheduleJob(jobId);
151     }
152 
153     static class TestPluginJob implements PluginJob
154     {
155         @Override
156         public void execute(Map<String, Object> jobDataMap)
157         {
158             CALLBACK.get().executedWith(jobDataMap);
159         }
160     }
161 
162     static interface JobChecker
163     {
164         void executedWith(Map<String,Object> jobDataMap);
165     }
166 }