View Javadoc

1   package com.atlassian.scheduler.core;
2   
3   import com.atlassian.scheduler.JobRunner;
4   import com.atlassian.scheduler.JobRunnerRequest;
5   import com.atlassian.scheduler.JobRunnerResponse;
6   import com.atlassian.scheduler.config.JobConfig;
7   import com.atlassian.scheduler.config.Schedule;
8   import com.atlassian.scheduler.core.impl.RunningJobImpl;
9   import com.atlassian.scheduler.core.status.SimpleJobDetails;
10  import com.atlassian.scheduler.core.status.UnusableJobDetails;
11  import com.atlassian.scheduler.status.JobDetails;
12  import org.junit.Test;
13  import org.mockito.ArgumentCaptor;
14  import org.mockito.InOrder;
15  
16  import java.util.Date;
17  
18  import static com.atlassian.scheduler.JobRunnerResponse.failed;
19  import static com.atlassian.scheduler.JobRunnerResponse.success;
20  import static com.atlassian.scheduler.config.RunMode.RUN_LOCALLY;
21  import static com.atlassian.scheduler.config.RunMode.RUN_ONCE_PER_CLUSTER;
22  import static com.atlassian.scheduler.core.Constants.JOB_ID;
23  import static com.atlassian.scheduler.core.Constants.KEY;
24  import static com.atlassian.scheduler.status.RunOutcome.ABORTED;
25  import static com.atlassian.scheduler.status.RunOutcome.FAILED;
26  import static com.atlassian.scheduler.status.RunOutcome.SUCCESS;
27  import static com.atlassian.scheduler.status.RunOutcome.UNAVAILABLE;
28  import static java.lang.Math.abs;
29  import static java.lang.System.currentTimeMillis;
30  import static org.hamcrest.Matchers.lessThan;
31  import static org.junit.Assert.assertNotNull;
32  import static org.junit.Assert.assertThat;
33  import static org.mockito.Matchers.any;
34  import static org.mockito.Matchers.eq;
35  import static org.mockito.Mockito.inOrder;
36  import static org.mockito.Mockito.mock;
37  import static org.mockito.Mockito.never;
38  import static org.mockito.Mockito.verify;
39  import static org.mockito.Mockito.when;
40  
41  /**
42   * @since v1.0
43   */
44  @SuppressWarnings({"ResultOfObjectAllocationIgnored", "ConstantConditions"})
45  public class JobLauncherTest {
46      private static final Date NOW = new Date();
47      private static final Schedule SCHEDULE = Schedule.forInterval(60000L, null);
48  
49      @Test(expected = IllegalArgumentException.class)
50      public void testSchedulerServiceNull() {
51          new JobLauncher(null, RUN_LOCALLY, new Date(), JOB_ID);
52      }
53  
54      @Test(expected = IllegalArgumentException.class)
55      public void testRunModeNull() {
56          new JobLauncher(mock(AbstractSchedulerService.class), null, new Date(), JOB_ID);
57      }
58  
59      @Test(expected = IllegalArgumentException.class)
60      public void testJobIdNull() {
61          new JobLauncher(mock(AbstractSchedulerService.class), RUN_LOCALLY, new Date(), null);
62      }
63  
64      @Test
65      public void testLaunchJobDetailsNull() {
66          final AbstractSchedulerService schedulerService = mock(AbstractSchedulerService.class);
67          final JobLauncher jobLauncher = new JobLauncher(schedulerService, RUN_LOCALLY, null, JOB_ID);
68          assertCloseToNow(jobLauncher.firedAt);
69  
70          jobLauncher.launch();
71  
72          verify(schedulerService).addRunDetails(JOB_ID, jobLauncher.firedAt, ABORTED, "No corresponding job details");
73      }
74  
75      @Test
76      public void testLaunchJobDetailsNotRunnable() {
77          final AbstractSchedulerService schedulerService = mock(AbstractSchedulerService.class);
78          final JobLauncher jobLauncher = new JobLauncher(schedulerService, RUN_LOCALLY, NOW, JOB_ID);
79          when(schedulerService.getJobDetails(JOB_ID)).thenReturn(unusable(null));
80  
81          jobLauncher.launch();
82  
83          verify(schedulerService).addRunDetails(JOB_ID, NOW, UNAVAILABLE, "Job runner key 'test.key' is not registered");
84      }
85  
86      @Test
87      public void testLaunchJobRunnerVanished() {
88          final AbstractSchedulerService schedulerService = mock(AbstractSchedulerService.class);
89          final JobLauncher jobLauncher = new JobLauncher(schedulerService, RUN_LOCALLY, NOW, JOB_ID);
90          when(schedulerService.getJobDetails(JOB_ID)).thenReturn(details());
91  
92          jobLauncher.launch();
93  
94          verify(schedulerService).addRunDetails(JOB_ID, NOW, UNAVAILABLE, "Job runner key 'test.key' is not registered");
95      }
96  
97      @Test
98      public void testLaunchRunModeInconsistency() {
99          final AbstractSchedulerService schedulerService = mock(AbstractSchedulerService.class);
100         final JobRunner jobRunner = mock(JobRunner.class);
101         final JobLauncher jobLauncher = new JobLauncher(schedulerService, RUN_ONCE_PER_CLUSTER, NOW, JOB_ID);
102         when(schedulerService.getJobDetails(JOB_ID)).thenReturn(details());
103         when(schedulerService.getJobRunner(KEY)).thenReturn(jobRunner);
104 
105         jobLauncher.launch();
106 
107         verify(schedulerService).addRunDetails(JOB_ID, NOW, ABORTED,
108                 "Inconsistent run mode: expected 'RUN_LOCALLY' got: 'RUN_ONCE_PER_CLUSTER'");
109     }
110 
111     @Test
112     public void testLaunchJobRunnerWhileAlreadyRunning() throws Exception {
113         final AbstractSchedulerService schedulerService = mock(AbstractSchedulerService.class);
114         final JobRunner jobRunner = mock(JobRunner.class);
115         final RunningJob existing = mock(RunningJob.class);
116         final JobLauncher jobLauncher = new JobLauncher(schedulerService, RUN_LOCALLY, NOW, JOB_ID);
117 
118         when(schedulerService.getJobDetails(JOB_ID)).thenReturn(details());
119         when(schedulerService.getJobRunner(KEY)).thenReturn(jobRunner);
120         when(schedulerService.enterJob(eq(JOB_ID), any(RunningJob.class))).thenReturn(existing);
121 
122         jobLauncher.launch();
123 
124         verify(schedulerService).addRunDetails(JOB_ID, NOW, ABORTED, "Already running");
125         verify(schedulerService).enterJob(eq(JOB_ID), any(RunningJob.class));
126         verify(schedulerService, never()).leaveJob(eq(JOB_ID), any(RunningJob.class));
127         verify(schedulerService, never()).unscheduleJob(JOB_ID);
128         verify(schedulerService, never()).preJob();
129         verify(schedulerService, never()).postJob();
130         verify(jobRunner, never()).runJob(new RunningJobImpl(NOW, JOB_ID, config()));
131     }
132 
133     @Test
134     public void testLaunchJobRunnerThatReturnsNull() throws Exception {
135         final AbstractSchedulerService schedulerService = mock(AbstractSchedulerService.class);
136         final JobRunner jobRunner = mock(JobRunner.class);
137         final JobLauncher jobLauncher = new JobLauncher(schedulerService, RUN_LOCALLY, NOW, JOB_ID);
138 
139         when(schedulerService.getJobDetails(JOB_ID)).thenReturn(details());
140         when(schedulerService.getJobRunner(KEY)).thenReturn(jobRunner);
141 
142         jobLauncher.launch();
143 
144         verify(schedulerService).addRunDetails(JOB_ID, NOW, SUCCESS, null);
145         verify(schedulerService, never()).unscheduleJob(JOB_ID);
146         assertJobLifeCycle(schedulerService, jobRunner, config());
147     }
148 
149     @Test
150     public void testLaunchJobRunnerThatReturnsInfo() throws Exception {
151         final AbstractSchedulerService schedulerService = mock(AbstractSchedulerService.class);
152         final JobRunner jobRunner = mock(JobRunner.class);
153         final JobLauncher jobLauncher = new JobLauncher(schedulerService, RUN_LOCALLY, NOW, JOB_ID);
154         final JobRunnerRequest request = new RunningJobImpl(NOW, JOB_ID, config());
155         final JobRunnerResponse response = success("Info");
156 
157         when(schedulerService.getJobDetails(JOB_ID)).thenReturn(details());
158         when(schedulerService.getJobRunner(KEY)).thenReturn(jobRunner);
159         when(jobRunner.runJob(request)).thenReturn(response);
160 
161         jobLauncher.launch();
162 
163         verify(schedulerService).addRunDetails(JOB_ID, NOW, SUCCESS, "Info");
164         verify(schedulerService, never()).unscheduleJob(JOB_ID);
165         assertJobLifeCycle(schedulerService, jobRunner, config());
166     }
167 
168 
169     @Test
170     public void testLaunchDeletesRunOnceJobOnSuccess() throws Exception {
171         final AbstractSchedulerService schedulerService = mock(AbstractSchedulerService.class);
172         final JobRunner jobRunner = mock(JobRunner.class);
173         final JobLauncher jobLauncher = new JobLauncher(schedulerService, RUN_LOCALLY, NOW, JOB_ID);
174         final JobDetails jobDetails = new SimpleJobDetails(JOB_ID, KEY, RUN_LOCALLY, Schedule.runOnce(null), null, null, null);
175         final JobConfig config = config(jobDetails);
176         final JobRunnerRequest request = new RunningJobImpl(NOW, JOB_ID, config);
177         final JobRunnerResponse response = success("Info");
178 
179         when(schedulerService.getJobDetails(JOB_ID)).thenReturn(jobDetails);
180         when(schedulerService.getJobRunner(KEY)).thenReturn(jobRunner);
181         when(jobRunner.runJob(request)).thenReturn(response);
182 
183         jobLauncher.launch();
184 
185         verify(schedulerService).addRunDetails(JOB_ID, NOW, SUCCESS, "Info");
186         verify(schedulerService).unscheduleJob(JOB_ID);
187         assertJobLifeCycle(schedulerService, jobRunner, config);
188     }
189 
190     @Test
191     public void testLaunchDeletesRunOnceJobOnFailure() throws Exception {
192         final AbstractSchedulerService schedulerService = mock(AbstractSchedulerService.class);
193         final JobRunner jobRunner = mock(JobRunner.class);
194         final JobLauncher jobLauncher = new JobLauncher(schedulerService, RUN_LOCALLY, NOW, JOB_ID);
195         final JobDetails jobDetails = new SimpleJobDetails(JOB_ID, KEY, RUN_LOCALLY, Schedule.runOnce(null), null, null, null);
196         final JobConfig config = config(jobDetails);
197         final JobRunnerRequest request = new RunningJobImpl(NOW, JOB_ID, config);
198         final IllegalArgumentException testEx = new IllegalArgumentException("Just testing!");
199 
200         when(schedulerService.getJobDetails(JOB_ID)).thenReturn(jobDetails);
201         when(schedulerService.getJobRunner(KEY)).thenReturn(jobRunner);
202         when(jobRunner.runJob(request)).thenThrow(testEx);
203 
204         jobLauncher.launch();
205 
206         verify(schedulerService).addRunDetails(JOB_ID, NOW, FAILED, failed(testEx).getMessage());
207         verify(schedulerService).unscheduleJob(JOB_ID);
208         assertJobLifeCycle(schedulerService, jobRunner, config);
209     }
210 
211     @Test
212     public void testLaunchDeletesRunOnceJobOnUnavailable() throws Exception {
213         final AbstractSchedulerService schedulerService = mock(AbstractSchedulerService.class);
214         final JobLauncher jobLauncher = new JobLauncher(schedulerService, RUN_LOCALLY, NOW, JOB_ID);
215         final JobDetails jobDetails = new UnusableJobDetails(JOB_ID, KEY, RUN_LOCALLY, Schedule.runOnce(null), null, null, null);
216 
217         when(schedulerService.getJobDetails(JOB_ID)).thenReturn(jobDetails);
218 
219         jobLauncher.launch();
220 
221         verify(schedulerService).addRunDetails(JOB_ID, NOW, UNAVAILABLE, "Job runner key 'test.key' is not registered");
222         verify(schedulerService).unscheduleJob(JOB_ID);
223     }
224 
225 
226     @Test
227     public void testLaunchJobRunnerDoesNotAttemptToDeleteWithoutJobDetails() throws Exception {
228         final AbstractSchedulerService schedulerService = mock(AbstractSchedulerService.class);
229         final JobLauncher jobLauncher = new JobLauncher(schedulerService, RUN_LOCALLY, NOW, JOB_ID);
230 
231         jobLauncher.launch();
232 
233         verify(schedulerService).addRunDetails(JOB_ID, NOW, ABORTED, "No corresponding job details");
234         verify(schedulerService, never()).unscheduleJob(JOB_ID);
235     }
236 
237     private static void assertCloseToNow(final Date date) {
238         assertNotNull("Expected a date close to now but got null", date);
239         final long delta = abs(currentTimeMillis() - date.getTime());
240         assertThat("Expected date to be close to now but the delta was too large", delta, lessThan(1000L));
241     }
242 
243     private static void assertJobLifeCycle(AbstractSchedulerService schedulerService, JobRunner jobRunner, JobConfig config) {
244         final InOrder inOrder = inOrder(schedulerService, jobRunner);
245         final ArgumentCaptor<RunningJob> jobCaptor = ArgumentCaptor.forClass(RunningJob.class);
246         inOrder.verify(schedulerService).enterJob(eq(JOB_ID), jobCaptor.capture());
247         inOrder.verify(schedulerService).preJob();
248         inOrder.verify(jobRunner).runJob(new RunningJobImpl(NOW, JOB_ID, config));
249         inOrder.verify(schedulerService).leaveJob(JOB_ID, jobCaptor.getValue());
250         inOrder.verify(schedulerService).postJob();
251     }
252 
253 
254     private static JobConfig config() {
255         return config(details());
256     }
257 
258     private static JobConfig config(JobDetails jobDetails) {
259         return JobConfig.forJobRunnerKey(jobDetails.getJobRunnerKey())
260                 .withRunMode(jobDetails.getRunMode())
261                 .withSchedule(jobDetails.getSchedule())
262                 .withParameters(jobDetails.getParameters());
263     }
264 
265     private static SimpleJobDetails details() {
266         return new SimpleJobDetails(JOB_ID, KEY, RUN_LOCALLY, SCHEDULE, null, null, null);
267     }
268 
269 
270     private static UnusableJobDetails unusable(String reason) {
271         return new UnusableJobDetails(JOB_ID, KEY, RUN_LOCALLY, SCHEDULE, null, null,
272                 (reason != null) ? new IllegalStateException("Bet you didn't see this coming!") : null);
273     }
274 }