Kill process started by org.apache.commons.executor

1.3k views Asked by At

I started execution of a testng.xml through apache commons executor libarary by following code:

DefaultExecuteResultHandler resultHandler;
        ExecuteWatchdog watchdog;
        final Executor executor;

        resultHandler = new DefaultExecuteResultHandler();
        watchdog = new ExecuteWatchdog(-1L);
        executor = new DefaultExecutor();
        executor.setStreamHandler(new PumpStreamHandler(new LogOutputStream() {

        @Override
        protected void processLine(final String line, @SuppressWarnings("unused") int level) {
           Display.getDefault().syncExec(new Runnable() {

                public void run() {

                    if (line.toLowerCase().indexOf("error") > -1) {

                        textArea.append(line+"\n");
                    } else if (line.toLowerCase().indexOf("warn") > -1) {
                        textArea.append(line+"\n");
                    } else {
                        textArea.append(line+"\n");


                    }
                }
            });
        }
    }));

    executor.setExitValue(1);
    executor.setWatchdog(watchdog);
    executor.execute(cl, resultHandler);

But I want to give a stop button to stop this process. I tried :

executor.getWatchdog().destroyProcess();

But this destroys only the watchdog . However the testng.xml I started keeps on running in the background. I want to destroy the process that I gave in the command . Is this possible?

1

There are 1 answers

0
Krishnan Mahadevan On

The reason behind why your solution is not killing the JVM that was spawned is perhaps because you are invoking cmd.exe and from within that is where you are probably spawning the JVM. So when you invoke destroyProcess() I believe its cmd.exe that is getting killed but not java.exe.

You should try changing your command line to something like below:

java -cp D:\MyProject\Utilities*;D:\MyProject\bin org.testng.TestNG D:\MyProject\testng.xml

Here's a solution that does not make use of Apache commons executor for doing this, but manages everything within a single JVM and also provides for a way to retrieve the output and error output from TestNG.

This solution makes use of the TestNG APIs.

The main test runner, that uses TestNG to run tests, in a different thread looks like below

import org.testng.TestNG;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class SimpleTestRunner {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        System.err.println("***Main Thread running in Thread [" + Thread.currentThread().getId() + "]***");
        ExecutorService service = Executors.newCachedThreadPool();
        WorkerThread thread = new WorkerThread(SampleTestClass.class);
        List<Future<ExecutionResults>> allResults = service.invokeAll(Collections.singletonList(thread));
        service.shutdown();
        ExecutionResults result = allResults.get(0).get();
        System.err.println("******Printing the TestNG output******");
        System.err.println(result);
        System.err.println("**************************************");
    }

    public static class WorkerThread implements Callable<ExecutionResults> {
        private Class<?>[] classes;

        WorkerThread(Class<?>... classes) {
            this.classes = classes;
        }

        @Override
        public ExecutionResults call() throws Exception {
            System.err.println("***Worker Thread running in Thread [" + Thread.currentThread().getId() + "]***");
            TestNG testNG = new TestNG();
            ExecutionResults results;
            testNG.setVerbose(2);
            ConsoleCapturer capturer = new ConsoleCapturer();
            testNG.setTestClasses(classes);
            try {
                capturer.start();
                testNG.run();
            } finally {
                ConsoleCapturer.CapturedData data = capturer.stop();
                results = new ExecutionResults(data, testNG.getStatus());
            }
            return results;
        }
    }

    public static class ExecutionResults {
        private ConsoleCapturer.CapturedData data;
        private int status;

        public ExecutionResults(ConsoleCapturer.CapturedData data, int status) {
            this.data = data;
            this.status = status;
        }

        public ConsoleCapturer.CapturedData getData() {
            return data;
        }

        public int getStatus() {
            return status;
        }

        @Override
        public String toString() {
            return "ExecutionResults{" +
                    "data=" + getData() +
                    ", status=" + getStatus() +
                    '}';
        }
    }
}

The utility class that re-directs all output and error contents to a thread, so that they can be redirected to anywhere, looks like below :

This class is mostly borrowed code from the solution Redirect console output to string in java with some improvisations.

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;

/**
 * This class is an improvisation of the solution provided in https://stackoverflow.com/a/30665299/679824
 */
public class ConsoleCapturer {
    private ByteArrayOutputStream baosOutput, baosError;
    private PrintStream previousOut, previousError;
    private boolean capturing;

    public void start() {
        if (capturing) {
            return;
        }

        capturing = true;
        previousOut = System.out;
        previousError = System.err;
        baosOutput = new ByteArrayOutputStream();
        baosError = new ByteArrayOutputStream();

        System.setOut(new PrintStream(new OutputStreamCombiner(previousOut, baosOutput)));
        System.setErr(new PrintStream(new OutputStreamCombiner(previousError, baosError)));
    }

    public CapturedData stop() {

        if (!capturing) {
            return new CapturedData();
        }

        System.setOut(previousOut);
        System.setErr(previousError);

        String output = baosOutput.toString();
        String error = baosError.toString();

        try {
            baosOutput.close();
            baosError.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        baosOutput = null;
        previousOut = null;
        capturing = false;

        return new CapturedData(output, error);
    }

    private static class OutputStreamCombiner extends OutputStream {
        private OutputStream[] outputStreams;

        OutputStreamCombiner(OutputStream... outputStreams) {
            this.outputStreams = outputStreams;
        }

        public void write(int b) throws IOException {
            for (OutputStream os : outputStreams) {
                os.write(b);
            }
        }

        public void flush() throws IOException {
            for (OutputStream os : outputStreams) {
                os.flush();
            }
        }

        public void close() throws IOException {
            for (OutputStream os : outputStreams) {
                os.close();
            }
        }
    }

    public static class CapturedData {
        private String output;
        private String error;

        CapturedData() {
            this("", "");
        }

        public CapturedData(String output, String error) {
            this.output = output;
            this.error = error;
        }

        public String getError() {
            return error;
        }

        public String getOutput() {
            return output;
        }

        @Override
        public String toString() {
            return "CapturedData{" +
                    "output='" + getOutput() + '\'' +
                    ", error='" + getError() + '\'' +
                    '}';
        }
    }
}

The test class used looks like below

import org.testng.annotations.Test;

public class SampleTestClass {

    @Test
    public void testMethod() {
        System.err.println("This goes into the error console");
        System.out.println("This goes into the console");
    }
}

The output looks like below

***Main Thread running in Thread [1]***
***Worker Thread running in Thread [11]***
This goes into the console
This goes into the error console
PASSED: testMethod

===============================================
    Command line test
    Tests run: 1, Failures: 0, Skips: 0
===============================================


===============================================
Command line suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

******Printing the TestNG output******
ExecutionResults{data=CapturedData{output='This goes into the console
PASSED: testMethod

===============================================
    Command line test
    Tests run: 1, Failures: 0, Skips: 0
===============================================


===============================================
Command line suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

', error='This goes into the error console
'}, status=0}
**************************************

Process finished with exit code 0