diff --git a/common/source/java/ch/systemsx/cisd/common/process/ProcessExecutionHelper.java b/common/source/java/ch/systemsx/cisd/common/process/ProcessExecutionHelper.java index 6e21b7784f5b57aa7193de23605282eea6b11478..dd4cfa72d30e220fd57c4d34c7645e5db80430c3 100644 --- a/common/source/java/ch/systemsx/cisd/common/process/ProcessExecutionHelper.java +++ b/common/source/java/ch/systemsx/cisd/common/process/ProcessExecutionHelper.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; +import java.util.Map; import org.apache.log4j.Logger; @@ -100,7 +101,7 @@ public final class ProcessExecutionHelper public static ProcessResult run(final List<String> cmd, final Logger operationLog, final Logger machineLog, boolean stopOnInterrupt) throws InterruptedExceptionUnchecked { - return new ProcessExecutor(cmd, ConcurrencyUtilities.NO_TIMEOUT, + return new ProcessExecutor(cmd, null, false, ConcurrencyUtilities.NO_TIMEOUT, ProcessIOStrategy.DEFAULT_IO_STRATEGY, operationLog, machineLog) .run(stopOnInterrupt); } @@ -177,7 +178,7 @@ public final class ProcessExecutionHelper final Logger machineLog, final long millisToWaitForCompletion, final boolean stopOnInterrupt) throws InterruptedExceptionUnchecked { - return new ProcessExecutor(cmd, millisToWaitForCompletion, + return new ProcessExecutor(cmd, null, false, millisToWaitForCompletion, ProcessIOStrategy.DEFAULT_IO_STRATEGY, operationLog, machineLog) .run(stopOnInterrupt); } @@ -204,8 +205,39 @@ public final class ProcessExecutionHelper final ProcessIOStrategy ioStrategy, final boolean stopOnInterrupt) throws InterruptedExceptionUnchecked { - return new ProcessExecutor(cmd, millisToWaitForCompletion, ioStrategy, operationLog, - machineLog).run(stopOnInterrupt); + return new ProcessExecutor(cmd, null, false, millisToWaitForCompletion, ioStrategy, + operationLog, machineLog).run(stopOnInterrupt); + } + + /** + * Runs an Operating System process, specified by <var>cmd</var>. + * + * @param cmd The command line to run. + * @param environment The environment of the process to start. + * @param replaceEnvironment If <code>true</code>, the environment will be cleared before + * addinng the keys from <var>environment</var>, if it is <code>false</code>, the + * keys in <var>environment</var> will be added without clearing the environment + * before. + * @param operationLog The {@link Logger} to use for all message on the higher level. + * @param machineLog The {@link Logger} to use for all message on the lower (machine) level. + * @param millisToWaitForCompletion The time to wait for the process to complete in milli + * seconds. If the process is not finished after that time, it will be terminated by + * a watch dog. + * @param ioStrategy The strategy to handle process I/O. + * @param stopOnInterrupt If <code>true</code>, an {@link InterruptedExceptionUnchecked} will be + * thrown if the thread got interrupted, otherwise, the + * {@link ProcessResult#isInterruped()} flag will be set. + * @return The process result. + * @throws InterruptedExceptionUnchecked If the thread got interrupted and + * <var>stopOnInterrupt</var> is <code>true</code>. + */ + public static ProcessResult run(final List<String> cmd, final Map<String, String> environment, + boolean replaceEnvironment, final Logger operationLog, final Logger machineLog, + final long millisToWaitForCompletion, final ProcessIOStrategy ioStrategy, + final boolean stopOnInterrupt) throws InterruptedExceptionUnchecked + { + return new ProcessExecutor(cmd, environment, replaceEnvironment, millisToWaitForCompletion, + ioStrategy, operationLog, machineLog).run(stopOnInterrupt); } /** @@ -249,6 +281,34 @@ public final class ProcessExecutionHelper false)); } + /** + * Runs an Operating System process, specified by <var>cmd</var>. + * + * @param cmd The command line to run. + * @param environment The environment of the process to start. + * @param replaceEnvironment If <code>true</code>, the environment will be cleared before + * addinng the keys from <var>environment</var>, if it is <code>false</code>, the + * keys in <var>environment</var> will be added without clearing the environment + * before. + * @param operationLog The {@link Logger} to use for all message on the higher level. + * @param machineLog The {@link Logger} to use for all message on the lower (machine) level. + * @param millisToWaitForCompletion The time to wait for the process to complete in + * milli-seconds. If the process is not finished after that time, it will be + * terminated by a watch dog. + * @param processIOStrategy The strategy of how to handle the process I/O. + * @return <code>true</code>, if the process did complete successfully, <code>false</code> + * otherwise. + * @throws InterruptedExceptionUnchecked If the thread got interrupted. + */ + public static boolean runAndLog(final List<String> cmd, final Map<String, String> environment, + boolean replaceEnvironment, final Logger operationLog, final Logger machineLog, + final long millisToWaitForCompletion, final ProcessIOStrategy processIOStrategy) + throws InterruptedExceptionUnchecked + { + return log(run(cmd, environment, replaceEnvironment, operationLog, machineLog, + millisToWaitForCompletion, processIOStrategy, false)); + } + /** * Runs an Operating System process, specified by <var>cmd</var>. * @@ -298,6 +358,8 @@ public final class ProcessExecutionHelper { return new ProcessExecutor( cmd, + null, + false, millisToWaitForCompletion, outputReadingStrategy == OutputReadingStrategy.NEVER ? ProcessIOStrategy.DISCARD_IO_STRATEGY : ProcessIOStrategy.DEFAULT_IO_STRATEGY, operationLog, machineLog) @@ -331,6 +393,8 @@ public final class ProcessExecutionHelper { return new ProcessExecutor( cmd, + null, + false, millisToWaitForCompletion, outputReadingStrategy == OutputReadingStrategy.NEVER ? ProcessIOStrategy.DISCARD_IO_STRATEGY : (binaryOutput ? ProcessIOStrategy.BINARY_IO_STRATEGY @@ -351,8 +415,32 @@ public final class ProcessExecutionHelper public static IProcessHandler runUnblocking(final List<String> cmd, Logger operationLog, final Logger machineLog, final ProcessIOStrategy processIOStrategy) { - return new ProcessExecutor(cmd, ConcurrencyUtilities.NO_TIMEOUT, processIOStrategy, - operationLog, machineLog).runUnblocking(); + return new ProcessExecutor(cmd, null, false, ConcurrencyUtilities.NO_TIMEOUT, + processIOStrategy, operationLog, machineLog).runUnblocking(); + } + + /** + * Runs an Operating System process, specified by <var>cmd</var>. Does not block waiting for a + * result. + * + * @param cmd The command line to run. + * @param environment The environment of the process to start. + * @param replaceEnvironment If <code>true</code>, the environment will be cleared before + * addinng the keys from <var>environment</var>, if it is <code>false</code>, the + * keys in <var>environment</var> will be added without clearing the environment + * before. + * @param operationLog The {@link Logger} to use for all message on the higher level. + * @param machineLog The {@link Logger} to use for all message on the lower (machine) level. + * @param processIOStrategy The strategy on how to deal with process I/O. + * @return The handler which allows to wait for the result or terminate the process. + */ + public static IProcessHandler runUnblocking(final List<String> cmd, + final Map<String, String> environment, final boolean replaceEnvironment, + Logger operationLog, final Logger machineLog, final ProcessIOStrategy processIOStrategy) + { + return new ProcessExecutor(cmd, environment, replaceEnvironment, + ConcurrencyUtilities.NO_TIMEOUT, processIOStrategy, operationLog, machineLog) + .runUnblocking(); } /** @@ -373,6 +461,8 @@ public final class ProcessExecutionHelper { return new ProcessExecutor( cmd, + null, + false, ConcurrencyUtilities.NO_TIMEOUT, outputReadingStrategy == OutputReadingStrategy.NEVER ? ProcessIOStrategy.DISCARD_IO_STRATEGY : ProcessIOStrategy.DEFAULT_IO_STRATEGY, operationLog, machineLog) diff --git a/common/source/java/ch/systemsx/cisd/common/process/ProcessExecutor.java b/common/source/java/ch/systemsx/cisd/common/process/ProcessExecutor.java index 129ef5b6011f8f6ed6fbc10d1fe3a040df46447a..1881aefc2157e5aec8b079e075b24b835a6c840c 100644 --- a/common/source/java/ch/systemsx/cisd/common/process/ProcessExecutor.java +++ b/common/source/java/ch/systemsx/cisd/common/process/ProcessExecutor.java @@ -25,6 +25,7 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; @@ -151,6 +152,11 @@ class ProcessExecutor /** Read-only! */ private final List<String> commandLine; + private final boolean replaceEnvironment; + + /** Read-only! */ + private final Map<String, String> environment; + private final long millisToWaitForCompletion; private final long millisToWaitForIOCompletion; @@ -273,6 +279,14 @@ class ProcessExecutor private final Process launch() throws IOException { final ProcessBuilder processBuilder = new ProcessBuilder(commandLine); + if (replaceEnvironment) + { + processBuilder.environment().clear(); + } + if (environment != null) + { + processBuilder.environment().putAll(environment); + } if (processIOStrategy.isMergeStderr()) { processBuilder.redirectErrorStream(true); @@ -371,6 +385,14 @@ class ProcessExecutor private final Process launch() throws IOException { final ProcessBuilder processBuilder = new ProcessBuilder(commandLine); + if (replaceEnvironment) + { + processBuilder.environment().clear(); + } + if (environment != null) + { + processBuilder.environment().putAll(environment); + } if (processIOStrategy.isMergeStderr()) { processBuilder.redirectErrorStream(true); @@ -566,7 +588,8 @@ class ProcessExecutor } } - ProcessExecutor(final List<String> commandLine, final long millisToWaitForCompletion, + ProcessExecutor(final List<String> commandLine, final Map<String, String> environment, + final boolean replaceEnvironment, final long millisToWaitForCompletion, final ProcessIOStrategy ioStrategy, final Logger operationLog, final Logger machineLog) { this.processNumber = processCounter.getAndIncrement(); @@ -582,10 +605,12 @@ class ProcessExecutor this.millisToWaitForCompletion = millisToWaitForCompletion; } this.millisToWaitForIOCompletion = - Math.round((millisToWaitForCompletion == ConcurrencyUtilities.NO_TIMEOUT) ? ConcurrencyUtilities.NO_TIMEOUT + Math.round((millisToWaitForCompletion == ConcurrencyUtilities.NO_TIMEOUT) ? ConcurrencyUtilities.NO_TIMEOUT : this.millisToWaitForCompletion * OUTPUT_READING_TIMEOUT_FRACTION); this.processIOStrategy = ioStrategy; this.commandLine = Collections.unmodifiableList(commandLine); + this.environment = environment; + this.replaceEnvironment = replaceEnvironment; this.processOutput = new ProcessOutput(); this.processWrapper = new AtomicReference<ProcessRecord>(); if (ioStrategy.isUseNoIOHandler() == false && ioStrategy.tryGetCustomIOHandler() == null) @@ -622,8 +647,8 @@ class ProcessExecutor } @Override - public ProcessResult getResult( - @SuppressWarnings("hiding") final long millisToWaitForCompletion) + public ProcessResult getResult(@SuppressWarnings("hiding") + final long millisToWaitForCompletion) { return getProcessResult(stopOnInterruption, runnerFuture, millisToWaitForCompletion); diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/process/ProcessExecutionHelperTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/process/ProcessExecutionHelperTest.java index 8a7942df33b06df71fbe85a57ee9936f44054106..3e90be0d00e821e67cdae521d2bb9f51eade5de8 100644 --- a/common/sourceTest/java/ch/systemsx/cisd/common/process/ProcessExecutionHelperTest.java +++ b/common/sourceTest/java/ch/systemsx/cisd/common/process/ProcessExecutionHelperTest.java @@ -23,6 +23,8 @@ import static org.testng.AssertJUnit.assertTrue; import java.io.File; import java.io.IOException; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicReference; @@ -148,7 +150,7 @@ public class ProcessExecutionHelperTest assertFalse(ok); } - @Test(groups = + @Test(groups = { "requires_unix", "flaky" }) public void testExecutionOKWithTimeOut() throws Exception { @@ -171,6 +173,55 @@ public class ProcessExecutionHelperTest assertTrue(ok); } + @Test(groups = + { "requires_unix", "flaky" }) + public void testExecutionEnvNoReplaceEnviron() throws Exception + { + final File dummyExec = createExecutable("printEnv.sh", "env"); + final Map<String, String> env = new HashMap<String, String>(); + env.put("bla", "blub"); + final ProcessResult result = + ProcessExecutionHelper.run(Arrays.asList(dummyExec.getAbsolutePath()), env, + false, operationLog, machineLog, WATCHDOG_WAIT_MILLIS, + ProcessIOStrategy.DEFAULT_IO_STRATEGY, true); + assertTrue(result.isOK()); + boolean found = false; + for (String ln : result.getOutput()) + { + if (ln.equals("bla=blub")) + { + found = true; + break; + } + } + assertTrue(found); + } + + @Test(groups = + { "requires_unix", "flaky" }) + public void testExecutionEnvReplaceEnviron() throws Exception + { + final File dummyExec = createExecutable("printEnv.sh", "env"); + final Map<String, String> env = new HashMap<String, String>(); + env.put("bla", "blub"); + final ProcessResult result = + ProcessExecutionHelper.run(Arrays.asList(dummyExec.getAbsolutePath()), env, + true, operationLog, machineLog, WATCHDOG_WAIT_MILLIS, + ProcessIOStrategy.DEFAULT_IO_STRATEGY, true); + assertTrue(result.isOK()); + assertEquals(2, result.getOutput().size()); + boolean found = false; + for (String ln : result.getOutput()) + { + if (ln.equals("bla=blub")) + { + found = true; + break; + } + } + assertTrue(found); + } + @Test(groups = { "requires_unix" }) public void testExecutionFailedWithTimeOut() throws Exception