/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.xd.shell.command;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.JobParameter;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.shell.core.CommandResult;
import org.springframework.xd.shell.util.Table;
import org.springframework.xd.shell.util.TableRow;
/**
* Test {@link JobCommands}.
*
* @author Glenn Renfro
* @author Gunnar Hillert
* @author Ilayaperumal Gopinathan
* @author Michael Minella
* @since 1.0
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class AbstractJobCommandsTests extends AbstractJobIntegrationTest {
private static final Logger logger = LoggerFactory.getLogger(AbstractJobCommandsTests.class);
@Test
public void testJobLifecycleForMyJob() throws InterruptedException {
JobParametersHolder.reset();
final JobParametersHolder jobParametersHolder = new JobParametersHolder();
String jobName = executeJobCreate(JOB_WITH_PARAMETERS_DESCRIPTOR);
logger.info("Created Job " + jobName);
checkForJobInList(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, true);
triggerJob(jobName);
assertTrue("Job did not complete within time alotted", jobParametersHolder.isDone());
CommandResult cr = getShell().executeCommand("job undeploy --name " + jobName);
checkForSuccess(cr);
checkUndeployedJobMessage(cr, jobName);
}
@Test
public void testJobCreateDuplicate() throws InterruptedException {
JobParametersHolder.reset();
final JobParametersHolder jobParametersHolder = new JobParametersHolder();
String jobName = executeJobCreate(JOB_WITH_PARAMETERS_DESCRIPTOR);
logger.info("Create job " + jobName);
triggerJob(jobName);
checkForJobInList(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, true);
assertTrue("Job did not complete within time alotted", jobParametersHolder.isDone());
CommandResult cr = createJob(jobName, "job");
checkForFail(cr);
checkBatchJobExistsErrorMessage(cr, jobName);
}
@Test
public void testStreamDestroyMissing() {
logger.info("Destroy a job that doesn't exist");
String jobName = generateJobName();
CommandResult cr = jobDestroy(jobName);
checkForFail(cr);
checkErrorMessages(cr, "There is no job definition named '" + jobName + "'");
}
@Test
public void testJobCreateDuplicateWithDeployFalse() {
logger.info("Create 2 myJobs with --deploy = false");
String jobName = generateJobName();
executeJobCreate(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, false);
checkForJobInList(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, false);
CommandResult cr = createJob(jobName, "job", "false");
checkForFail(cr);
checkDuplicateJobErrorMessage(cr, jobName);
checkForJobInList(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, false);
}
@Test
public void testJobDeployUndeployFlow() throws InterruptedException {
logger.info("Create batch job");
JobParametersHolder.reset();
final JobParametersHolder jobParametersHolder = new JobParametersHolder();
String jobName = generateJobName();
executeJobCreate(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, false);
checkForJobInList(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, false);
CommandResult cr = deployJob(jobName);
checkForSuccess(cr);
checkDeployedJobMessage(cr, jobName);
triggerJob(jobName);
assertTrue("Job did not complete within time alotted", jobParametersHolder.isDone());
checkForJobInList(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, true);
cr = undeployJob(jobName);
checkForSuccess(cr);
checkUndeployedJobMessage(cr, jobName);
cr = deployJob(jobName);
checkForSuccess(cr);
assertEquals("Deployed job '" + jobName + "'", cr.getResult());
checkForJobInList(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, true);
}
@Test
public void testInvalidJobDescriptor() throws InterruptedException {
JobParametersHolder.reset();
final JobParametersHolder jobParametersHolder = new JobParametersHolder();
CommandResult cr = getShell().executeCommand("job create --definition \"barsdaf\" --name " + generateJobName());
checkForFail(cr);
checkErrorMessages(cr, "Could not find module with name 'barsdaf'");
assertFalse("Job did not complete within time alotted", jobParametersHolder.isDone());
}
@Test
public void testMissingJobDescriptor() {
CommandResult cr = getShell().executeCommand("job create --name " + generateJobName());
checkForFail(cr);
}
@Test
public void testJobDeployWithParameters() throws InterruptedException {
logger.info("Create batch job with parameters");
JobParametersHolder.reset();
String jobName = generateJobName();
executeJobCreate(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, false);
checkForJobInList(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, false);
final JobParametersHolder jobParametersHolder = new JobParametersHolder();
CommandResult cr = deployJob(jobName);
checkForSuccess(cr);
checkDeployedJobMessage(cr, jobName);
triggerJobWithParams(jobName, "{\"param1\":\"spring rocks!\"}");
boolean done = jobParametersHolder.isDone();
assertTrue("The countdown latch expired and did not count down.", done);
int numberOfJobParameters = JobParametersHolder.getJobParameters().size();
assertTrue("Expecting 2 parameters but got " + numberOfJobParameters, numberOfJobParameters == 2);
assertNotNull(JobParametersHolder.getJobParameters().get("random"));
final JobParameter parameter1 = JobParametersHolder.getJobParameters().get("param1");
assertNotNull(parameter1);
assertEquals("spring rocks!", parameter1.getValue());
}
@Test
public void testJobDeployWithTypedParameters() throws InterruptedException, ParseException {
logger.info("Create batch job with typed parameters");
JobParametersHolder.reset();
String jobName = generateJobName();
executeJobCreate(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, false);
checkForJobInList(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, false);
final JobParametersHolder jobParametersHolder = new JobParametersHolder();
final CommandResult cr = deployJob(jobName);
checkForSuccess(cr);
checkDeployedJobMessage(cr, jobName);
triggerJobWithParams(jobName, "{\"-param1(long)\":\"12345\",\"param2(date)\":\"1990-10-03\"}");
boolean done = jobParametersHolder.isDone();
assertTrue("The countdown latch expired and did not count down.", done);
assertTrue("Expecting 3 parameters.", JobParametersHolder.getJobParameters().size() == 3);
assertNotNull(JobParametersHolder.getJobParameters().get("random"));
final JobParameter parameter1 = JobParametersHolder.getJobParameters().get("param1");
final JobParameter parameter2 = JobParametersHolder.getJobParameters().get("param2");
assertNotNull(parameter1);
assertNotNull(parameter2);
assertTrue("parameter1 should be a Long", parameter1.getValue() instanceof Long);
assertTrue("parameter2 should be a java.util.Date", parameter2.getValue() instanceof Date);
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
final Date expectedDate = dateFormat.parse("1990/10/03");
assertEquals("Was expecting the Long value 12345", Long.valueOf(12345), parameter1.getValue());
assertEquals("Should be the same dates", expectedDate, parameter2.getValue());
assertFalse("parameter1 should be non-identifying", parameter1.isIdentifying());
assertTrue("parameter2 should be identifying", parameter2.isIdentifying());
}
@Test
public void testLaunchJob() {
logger.info("Launch batch job");
String jobName = generateJobName();
executeJobCreate(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR);
checkForJobInList(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, true);
executeJobLaunch(jobName);
}
@Test
public void testNestedJobLaunch() {
logger.info("Launch nested job");
String jobName = generateJobName();
executeJobCreate(jobName, NESTED_JOB_DESCRIPTOR);
checkForJobInList(jobName, NESTED_JOB_DESCRIPTOR, true);
executeJobLaunch(jobName);
}
@Test
public void testInvalidNestedJobLaunch() {
logger.info("Launch nested job");
String jobName = generateJobName();
CommandResult result = executeCommandExpectingFailure("job create --definition \"" +
INVALID_NESTED_JOB_DESCRIPTOR + "\" --name " + jobName);
assertTrue(result.getException().getMessage().contains(
"Could not find module with name 'invalidJob' and type 'job'"));
}
@Test
public void testLaunchPartitionedJob() {
logger.info("Launch Partitioned batch job");
String jobName = generateJobName();
executeJobCreate(jobName, JOB_WITH_PARTITIONS_DESCRIPTOR);
checkForJobInList(jobName, JOB_WITH_PARTITIONS_DESCRIPTOR, true);
executeJobLaunch(jobName);
}
@Test
public void testLaunchNotDeployedJob() {
logger.info("Launch batch job that is not deployed");
String jobName = generateJobName();
executeJobCreate(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, false);
CommandResult result = executeCommandExpectingFailure("job launch --name " + jobName);
assertThat(result.getException().getMessage(),
containsString(String.format("The job named '%s' is not currently deployed", jobName)));
}
@Test
public void testLaunchJobWithParameters() throws InterruptedException, ParseException {
logger.info("Launch batch job with typed parameters");
String myJobParams = "{\"-param1(long)\":\"12345\",\"param2(date)\":\"1990-10-03\"}";
JobParametersHolder.reset();
final JobParametersHolder jobParametersHolder = new JobParametersHolder();
String jobName = generateJobName();
executeJobCreate(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR);
checkForJobInList(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, true);
executeJobLaunch(jobName, myJobParams);
boolean done = jobParametersHolder.isDone();
assertTrue("The countdown latch expired and did not count down.", done);
// Make sure the job parameters are set when passing through job launch command
assertTrue("Expecting 3 parameters, but got: " + JobParametersHolder.getJobParameters(),
JobParametersHolder.getJobParameters().size() == 3);
assertNotNull(JobParametersHolder.getJobParameters().get("random"));
final JobParameter parameter1 = JobParametersHolder.getJobParameters().get("param1");
final JobParameter parameter2 = JobParametersHolder.getJobParameters().get("param2");
assertNotNull(parameter1);
assertNotNull(parameter2);
assertTrue("parameter1 should be a Long", parameter1.getValue() instanceof Long);
assertTrue("parameter2 should be a java.util.Date", parameter2.getValue() instanceof Date);
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
final Date expectedDate = dateFormat.parse("1990/10/03");
assertEquals("Was expecting the Long value 12345", Long.valueOf(12345), parameter1.getValue());
assertEquals("Should be the same dates", expectedDate, parameter2.getValue());
assertFalse("parameter1 should be non-identifying", parameter1.isIdentifying());
assertTrue("parameter2 should be identifying", parameter2.isIdentifying());
}
@Test
public void testLaunchJobTwiceWhereMakeUniqueIsImplicitlyTrue() throws Exception {
logger.info("Launch batch job twice (makeUnique is implicitly true)");
String jobName = generateJobName();
// Batch 3.0 requires at least one parameter to reject duplicate executions of an instance
String myJobParams = "{\"-param(long)\":\"12345\"}";
JobParametersHolder.reset();
final JobParametersHolder jobParametersHolder = new JobParametersHolder();
executeJobCreate(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR);
checkForJobInList(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, true);
executeJobLaunch(jobName, myJobParams);
assertTrue("The countdown latch expired and did not count down.", jobParametersHolder.isDone());
executeJobLaunch(jobName, myJobParams);
}
@Test
public void testLaunchJobTwiceWhereMakeUniqueIsTrue() throws Exception {
logger.info("Launch batch job (makeUnique=true) twice");
String jobName = generateJobName();
// Batch 3.0 requires at least one parameter to reject duplicate executions of an instance
String myJobParams = "{\"-param(long)\":\"12345\"}";
JobParametersHolder.reset();
final JobParametersHolder jobParametersHolder = new JobParametersHolder();
executeJobCreate(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR + " --makeUnique=true");
checkForJobInList(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR + " --makeUnique=true", true);
executeJobLaunch(jobName, myJobParams);
assertTrue("The countdown latch expired and did not count down.", jobParametersHolder.isDone());
executeJobLaunch(jobName, myJobParams);
}
@Test
public void testLaunchJobTwiceWhereMakeUniqueIsFalse() throws Exception {
logger.info("Launch batch job (makeUnique=false) twice");
String jobName = generateJobName();
// Batch 3.0 requires at least one parameter to reject duplicate executions of an instance
String myJobParams = "{\"-param(long)\":\"12345\"}";
JobParametersHolder.reset();
final JobParametersHolder jobParametersHolder = new JobParametersHolder();
executeJobCreate(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR + " --makeUnique=false");
checkForJobInList(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR + " --makeUnique=false", true);
executeJobLaunch(jobName, myJobParams);
assertTrue("The countdown latch expired and did not count down.", jobParametersHolder.isDone());
final SynchronousQueue<Message<?>> rendezvous = new SynchronousQueue<Message<?>>();
MessageHandler handler = new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
rendezvous.add(message);
}
};
getErrorChannel().subscribe(handler);
executeCommand("job launch --name " + jobName + " --params " + myJobParams);
Message<?> error = rendezvous.poll(5, TimeUnit.SECONDS);
getErrorChannel().unsubscribe(handler);
assertNotNull("expected an error message", error);
Object payload = error.getPayload();
assertTrue("payload should be a MessagingException", payload instanceof MessagingException);
assertEquals(JobInstanceAlreadyCompleteException.class,
((MessagingException) payload).getCause().getClass());
}
public static class JobParametersHolder {
private static Map<String, JobParameter> jobParameters = new ConcurrentHashMap<String, JobParameter>();
private static CountDownLatch countDownLatch = new CountDownLatch(1);
public boolean isDone() throws InterruptedException {
return countDownLatch.await(10, TimeUnit.SECONDS);
}
public void countDown() throws InterruptedException {
countDownLatch.countDown();
}
public void addParameter(String parameterName, JobParameter jobParameter) {
jobParameters.put(parameterName, jobParameter);
}
protected static Map<String, JobParameter> getJobParameters() {
return jobParameters;
}
public static void reset() {
jobParameters.clear();
countDownLatch = new CountDownLatch(1);
}
}
@Test
public void testListJobExecutions() {
String jobName = generateJobName();
executeJobCreate(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR);
checkForJobInList(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, true);
executeJobLaunch(jobName);
listJobExecutions();
}
@Test
public void testDisplaySpecificJobExecution() {
String jobName = generateJobName();
executeJobCreate(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR);
checkForJobInList(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, true);
executeJobLaunch(jobName);
displayJobExecution(getJobExecutionId(jobName));
}
@Test
public void testDisplaySpecificJobExecutionWithDateParam() {
String jobName = generateJobName();
String jobDefinition = JOB_WITH_PARAMETERS_DESCRIPTOR + " --dateFormat='yyyy/dd/MM' --numberFormat='###;(###)'";
executeJobCreate(jobName, jobDefinition);
checkForJobInList(jobName, jobDefinition, true);
executeJobLaunch(jobName,
"{\"param1\":\"fixedDelayKenny\",\"param2(date)\":\"2013/28/12\",\"param3(long)\":\"(123)\"}");
String id = getJobExecutionId(jobName);
String displayed = displayJobExecution(id);
assertTrue("Date of job execution is not as expected. [" + displayed + "]",
displayed.matches("(?s).*param2 +Sat Dec 28 00:00:00 [A-Z]{3} 2013 +DATE.*"));
assertTrue("Long parameter of job execution is not as expected. [" + displayed + "]",
displayed.matches("(?s).*param3 +-123 +LONG.*"));
}
@Test
public void testListStepExecutionsForSpecificJobExecution() {
String jobName = generateJobName();
executeJobCreate(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR);
checkForJobInList(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, true);
executeJobLaunch(jobName);
final Table stepExecutions = listStepExecutions(getJobExecutionId(jobName));
assertNotNull(stepExecutions.getRows().get(0).getValue(1));
}
@Test
public void doStopJobExecution() throws Exception {
String jobName = generateJobName();
executeJobCreate(jobName, JOB_WITH_STEP_EXECUTIONS);
checkForJobInList(jobName, JOB_WITH_STEP_EXECUTIONS, true);
triggerJobWithDelay(jobName, "5");
Thread.sleep(5000);
String executionId = getJobExecutionId(jobName);
String executionStatus = getJobExecutionStatus(jobName);
long timeout = System.currentTimeMillis() + 20000;
while ((executionStatus.equals("STARTING") || executionStatus.equals("STARTED"))
&& System.currentTimeMillis() < timeout) {
executionStatus = getJobExecutionStatus(jobName);
executionId = getJobExecutionId(jobName);
}
// Stop the execution by the given executionId.
executeCommand("job execution stop " + executionId);
// sleep for stop() until the step2 is invoked.
Thread.sleep(3000);
Table table;
int n = 0;
do {
table = (Table) executeCommand("job execution list").getResult();
for (TableRow tr : table.getRows()) {
// Match by above executionId
if (tr.getValue(1).equals(executionId)) {
executionStatus = tr.getValue(5);
break;
}
}
if (!"STOPPED".equals(executionStatus)) {
Thread.sleep(100);
}
}
while (!"STOPPED".equals(executionStatus) && n++ < 100);
assertEquals("STOPPED", executionStatus);
}
@Test
public void doStopAllJobExecutions() throws Exception {
String jobName = generateJobName();
executeJobCreate(jobName, JOB_WITH_STEP_EXECUTIONS);
checkForJobInList(jobName, JOB_WITH_STEP_EXECUTIONS, true);
triggerJobWithDelay(jobName, "5");
Thread.sleep(5000);
String executionId = getJobExecutionId(jobName);
String executionStatus = getJobExecutionStatus(jobName);
assertTrue(executionStatus.equals("STARTING") || executionStatus.equals("STARTED"));
// Stop the execution by the given executionId.
executeCommand("job execution all stop --force");
// sleep for stop() until the step2 is invoked.
Thread.sleep(3000);
Table table = (Table) executeCommand("job execution list").getResult();
for (TableRow tr : table.getRows()) {
// Match by above executionId
if (tr.getValue(1).equals(executionId)) {
executionStatus = tr.getValue(5);
break;
}
}
assertTrue("Expected an executionStatus with Status 'STOPPING' or 'STOPPED' but got " + executionStatus,
executionStatus.equals("STOPPING") || executionStatus.equals("STOPPED"));
}
public void testStepExecutionProgress() {
String jobName = generateJobName();
executeJobCreate(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR);
checkForJobInList(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, true);
executeJobLaunch(jobName);
String jobExecutionId = getJobExecutionId(jobName);
final Table stepExecutions = listStepExecutions(jobExecutionId);
String stepExecutionId = stepExecutions.getRows().get(0).getValue(1);
final Table stepExecutionProgress = getStepExecutionProgress(jobExecutionId, stepExecutionId);
String id = stepExecutionProgress.getRows().get(0).getValue(1);
String percentageComplete = stepExecutionProgress.getRows().get(0).getValue(3);
String duration = stepExecutionProgress.getRows().get(0).getValue(4);
assertEquals(stepExecutionId, id);
assertNotNull(percentageComplete);
assertNotNull(duration);
}
@Test
public void testDisplayStepExecution() {
final String jobName = generateJobName();
executeJobCreate(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR);
checkForJobInList(jobName, JOB_WITH_PARAMETERS_DESCRIPTOR, true);
executeJobLaunch(jobName);
final String jobExecutionId = getJobExecutionId(jobName);
final Table stepExecutions = listStepExecutions(jobExecutionId);
String stepExecutionId = stepExecutions.getRows().get(0).getValue(1);
final Table stepExecution = getDisplayStepExecution(jobExecutionId, stepExecutionId);
final String stepExecutionIdFromTable = stepExecution.getRows().get(0).getValue(2);
final String jobExecutionIdFromTable = stepExecution.getRows().get(1).getValue(2);
final String stepNameFromTable = stepExecution.getRows().get(2).getValue(2);
// start time
final String duration = stepExecution.getRows().get(3).getValue(2);
assertEquals(stepExecutionId, stepExecutionIdFromTable);
assertEquals(jobExecutionId, jobExecutionIdFromTable);
assertNotEquals(stepNameFromTable, "N/A");
assertFalse(duration.isEmpty());
assertNotEquals(duration, "N/A");
}
@Test
public void testJavaConfigJob() {
final String jobName = generateJobName();
executeJobCreate(jobName, JAVA_CONFIG_JOB_DESCRIPTOR);
checkForJobInList(jobName, JAVA_CONFIG_JOB_DESCRIPTOR, true);
executeJobLaunch(jobName);
}
}