/*
* Copyright 2008-2013 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.batch.core.step.tasklet;
import java.io.File;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobInterruptedException;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.scope.context.StepContext;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.util.Assert;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.when;
/**
* Tests for {@link SystemCommandTasklet}.
*/
public class SystemCommandTaskletIntegrationTests {
private static final Log log = LogFactory.getLog(SystemCommandTaskletIntegrationTests.class);
private SystemCommandTasklet tasklet;
private StepExecution stepExecution = new StepExecution("systemCommandStep", new JobExecution(new JobInstance(1L,
"systemCommandJob"), 1L, new JobParameters(), "configurationName"));
@Mock
private JobExplorer jobExplorer;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
initializeTasklet();
tasklet.afterPropertiesSet();
tasklet.beforeStep(stepExecution);
}
private void initializeTasklet() {
tasklet = new SystemCommandTasklet();
tasklet.setEnvironmentParams(null); // inherit from parent process
tasklet.setWorkingDirectory(null); // inherit from parent process
tasklet.setSystemProcessExitCodeMapper(new TestExitCodeMapper());
tasklet.setTimeout(5000); // long enough timeout
tasklet.setTerminationCheckInterval(500);
tasklet.setCommand("invalid command, change value for successful execution");
tasklet.setInterruptOnCancel(true);
tasklet.setTaskExecutor(new SimpleAsyncTaskExecutor());
}
/*
* Regular usage scenario - successful execution of system command.
*/
@Test
public void testExecute() throws Exception {
String command = getJavaCommand() + " --version";
tasklet.setCommand(command);
tasklet.afterPropertiesSet();
log.info("Executing command: " + command);
RepeatStatus exitStatus = tasklet.execute(stepExecution.createStepContribution(), null);
assertEquals(RepeatStatus.FINISHED, exitStatus);
}
/*
* Failed execution scenario - error exit code returned by system command.
*/
@Test
public void testExecuteFailure() throws Exception {
String command = getJavaCommand() + " org.springframework.batch.sample.tasklet.UnknownClass";
tasklet.setCommand(command);
tasklet.setTimeout(200L);
tasklet.afterPropertiesSet();
log.info("Executing command: " + command);
try {
StepContribution contribution = stepExecution.createStepContribution();
RepeatStatus exitStatus = tasklet.execute(contribution, null);
assertEquals(RepeatStatus.FINISHED, exitStatus);
assertEquals(ExitStatus.FAILED, contribution.getExitStatus());
}
catch (RuntimeException e) {
// on some platforms the system call does not return
assertEquals("Execution of system command did not finish within the timeout", e.getMessage());
}
}
/*
* The attempt to execute the system command results in exception
*/
@Test(expected = java.util.concurrent.ExecutionException.class)
public void testExecuteException() throws Exception {
String command = "non-sense-that-should-cause-exception-when-attempted-to-execute";
tasklet.setCommand(command);
tasklet.afterPropertiesSet();
tasklet.execute(null, null);
}
/*
* Failed execution scenario - execution time exceeds timeout.
*/
@Test
public void testExecuteTimeout() throws Exception {
String command = System.getProperty("os.name").toLowerCase().contains("win") ?
"ping 1.1.1.1 -n 1 -w 3000" :
"sleep 3";
tasklet.setCommand(command);
tasklet.setTimeout(10);
tasklet.afterPropertiesSet();
log.info("Executing command: " + command);
try {
tasklet.execute(null, null);
fail();
}
catch (SystemCommandException e) {
assertTrue(e.getMessage().contains("did not finish within the timeout"));
}
}
/*
* Job interrupted scenario.
*/
@Test
public void testInterruption() throws Exception {
String command = System.getProperty("os.name").toLowerCase().contains("win") ?
"ping 1.1.1.1 -n 1 -w 5000" :
"sleep 5";
tasklet.setCommand(command);
tasklet.setTerminationCheckInterval(10);
tasklet.afterPropertiesSet();
stepExecution.setTerminateOnly();
try {
tasklet.execute(null, null);
fail();
}
catch (JobInterruptedException e) {
System.out.println(e.getMessage());
assertTrue(e.getMessage().contains("Job interrupted while executing system command"));
assertTrue(e.getMessage().contains(command));
}
}
/*
* Command property value is required to be set.
*/
@Test
public void testCommandNotSet() throws Exception {
tasklet.setCommand(null);
try {
tasklet.afterPropertiesSet();
fail();
}
catch (IllegalArgumentException e) {
// expected
}
tasklet.setCommand("");
try {
tasklet.afterPropertiesSet();
fail();
}
catch (IllegalArgumentException e) {
// expected
}
}
/*
* Timeout must be set to non-zero value.
*/
@Test
public void testTimeoutNotSet() throws Exception {
tasklet.setCommand("not-empty placeholder");
tasklet.setTimeout(0);
try {
tasklet.afterPropertiesSet();
fail();
}
catch (IllegalArgumentException e) {
// expected
}
}
/*
* Working directory property must point to an existing location and it must
* be a directory
*/
@Test
public void testWorkingDirectory() throws Exception {
File notExistingFile = new File("not-existing-path");
Assert.state(!notExistingFile.exists(), "not-existing-path does actually exist");
try {
tasklet.setWorkingDirectory(notExistingFile.getCanonicalPath());
fail();
}
catch (IllegalArgumentException e) {
// expected
}
File notDirectory = File.createTempFile(this.getClass().getName(), null);
Assert.state(notDirectory.exists(), "The file does not exist");
Assert.state(!notDirectory.isDirectory(), "The file is actually a directory");
try {
tasklet.setWorkingDirectory(notDirectory.getCanonicalPath());
fail();
}
catch (IllegalArgumentException e) {
// expected
}
File directory = notDirectory.getParentFile();
Assert.state(directory.exists(), "The directory does not exist");
Assert.state(directory.isDirectory(), "The directory is not a directory");
// no error expected now
tasklet.setWorkingDirectory(directory.getCanonicalPath());
}
/*
* test stopping a tasklet
*/
@Test
public void testStopped() throws Exception {
initializeTasklet();
tasklet.setJobExplorer(jobExplorer);
tasklet.afterPropertiesSet();
tasklet.beforeStep(stepExecution);
JobExecution stoppedJobExecution = new JobExecution(stepExecution.getJobExecution());
stoppedJobExecution.setStatus(BatchStatus.STOPPING);
when(jobExplorer.getJobExecution(1L)).thenReturn(stepExecution.getJobExecution(), stepExecution.getJobExecution(), stoppedJobExecution);
String command = System.getProperty("os.name").toLowerCase().contains("win") ?
"ping 1.1.1.1 -n 1 -w 5000" :
"sleep 15";
tasklet.setCommand(command);
tasklet.setTerminationCheckInterval(10);
tasklet.afterPropertiesSet();
StepContribution contribution = stepExecution.createStepContribution();
StepContext stepContext = new StepContext(stepExecution);
ChunkContext chunkContext = new ChunkContext(stepContext);
tasklet.execute(contribution, chunkContext);
assertEquals(contribution.getExitStatus().getExitCode(),ExitStatus.STOPPED.getExitCode());
}
private String getJavaCommand() {
String javaHome = System.getProperty("java.home");
String fileSeparator = System.getProperty("file.separator");
StringBuilder command = new StringBuilder();
command.append(javaHome);
command.append(fileSeparator);
command.append("bin");
command.append(fileSeparator);
command.append("java");
if(System.getProperty("os.name").toLowerCase().indexOf("win") >= 0) {
command.append(".exe");
}
return command.toString();
}
/**
* Exit code mapper containing mapping logic expected by the tests. 0 means
* finished successfully, other value means failure.
*/
private static class TestExitCodeMapper implements SystemProcessExitCodeMapper {
@Override
public ExitStatus getExitStatus(int exitCode) {
if (exitCode == 0) {
return ExitStatus.COMPLETED;
}
else {
return ExitStatus.FAILED;
}
}
}
}