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