Fix tests failing on windows

The command `ping 1.1.1.1 -n 1 -w 5000` sends only one packet to the
remote address and might finish before the configured timeout of 10ms
which makes some tests to fail. Moreover, pinging 1.1.1.1 requires the
host (which can be the CI build server) to have internet connection.
This command can also fail if there is no internet connection which
makes some tests (expecting the command to succeed) to fail too.

This commit uses the command `ping 127.0.0.1` which does not require
an internet connection and which will, by default [1], send 4 packets
and wait for a timeout of 4 seconds for each request. This should take
more time than the configured timeout of 10ms.

Resolves BATCH-2722

[1]: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/ping
This commit is contained in:
Mahmoud Ben Hassine
2019-01-18 11:03:24 +01:00
parent 951bfc970b
commit 1bfabeb33c

View File

@@ -1,322 +1,322 @@
/*
* 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;
}
}
}
}
/*
* Copyright 2008-2019 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 127.0.0.1" :
"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 127.0.0.1" :
"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 127.0.0.1 -n 5" :
"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;
}
}
}
}