changeset 152:428032cdc6fc

Add base infrastructure for integration tests Reviewed-by: jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-May/023104.html
author Jie Kang <jkang@redhat.com>
date Tue, 16 May 2017 11:52:33 -0400
parents c8192ee541b9
children de6a3c5c2321
files pom.xml tests/integration-tests/pom.xml tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/service/jvm/gc/JvmGcServiceIntegrationTest.java tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/tests/integration/IntegrationTest.java tests/pom.xml tests/test-utils/pom.xml tests/test-utils/src/main/java/com/redhat/thermostat/gateway/tests/utils/MongodTestUtil.java tests/test-utils/src/main/java/com/redhat/thermostat/gateway/tests/utils/ProcessTestUtil.java
diffstat 8 files changed, 692 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/pom.xml	Thu May 11 02:35:17 2017 -0400
+++ b/pom.xml	Tue May 16 11:52:33 2017 -0400
@@ -53,10 +53,13 @@
         <module>distribution</module>
         <module>common</module>
         <module>services</module>
+
+        <module>tests</module>
     </modules>
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <java.version>1.7</java.version>
         <jersey.version>2.25</jersey.version>
         <jetty.version>9.2.19.v20160908</jetty.version>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/integration-tests/pom.xml	Tue May 16 11:52:33 2017 -0400
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright 2012-2017 Red Hat, Inc.
+
+ This file is part of Thermostat.
+
+ Thermostat is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2, or (at your
+ option) any later version.
+
+ Thermostat is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Thermostat; see the file COPYING.  If not see
+ <http://www.gnu.org/licenses/>.
+
+ Linking this code with other modules is making a combined work
+ based on this code.  Thus, the terms and conditions of the GNU
+ General Public License cover the whole combination.
+
+ As a special exception, the copyright holders of this code give
+ you permission to link this code with independent modules to
+ produce an executable, regardless of the license terms of these
+ independent modules, and to copy and distribute the resulting
+ executable under terms of your choice, provided that you also
+ meet, for each linked independent module, the terms and conditions
+ of the license of that module.  An independent module is a module
+ which is not derived from or based on this code.  If you modify
+ this code, you may extend this exception to your version of the
+ library, but you are not obligated to do so.  If you do not wish
+ to do so, delete this exception statement from your version.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.redhat.thermostat</groupId>
+        <artifactId>thermostat-web-gateway-tests</artifactId>
+        <version>1.99.12-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>thermostat-web-gateway-tests-integration</artifactId>
+
+    <packaging>jar</packaging>
+
+    <name>Thermostat Web Gateway Integration Tests</name>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-failsafe-plugin</artifactId>
+                <version>2.19.1</version>
+                <executions>
+                    <execution>
+                        <configuration>
+                            <redirectTestOutputToFile>true</redirectTestOutputToFile>
+                            <skip>${maven.itest.skip}</skip>
+                        </configuration>
+                        <goals>
+                            <goal>integration-test</goal>
+                            <goal>verify</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>3.0.2</version>
+                <configuration>
+                    <skipIfEmpty>true</skipIfEmpty>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <!-- Thermostat Web Gateway dependencies -->
+        <dependency>
+            <groupId>com.redhat.thermostat</groupId>
+            <artifactId>thermostat-web-gateway-distribution</artifactId>
+            <version>${project.version}</version>
+            <type>pom</type>
+        </dependency>
+
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>com.redhat.thermostat</groupId>
+            <artifactId>thermostat-web-gateway-tests-utils</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>${junit.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-client</artifactId>
+            <version>${jetty.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/service/jvm/gc/JvmGcServiceIntegrationTest.java	Tue May 16 11:52:33 2017 -0400
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+
+package com.redhat.thermostat.gateway.service.jvm.gc;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.http.HttpMethod;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.gateway.tests.integration.IntegrationTest;
+
+public class JvmGcServiceIntegrationTest extends IntegrationTest {
+
+    private final String collectionName = "jvm-gc";
+    private final String gcUrl = baseUrl + "/jvm-gc/0.0.2";
+
+    @Before
+    public void beforeIntegrationTest() {
+        mongodTestUtil.dropCollection(collectionName);
+    }
+
+    @Test
+    public void testGet() throws InterruptedException, TimeoutException, ExecutionException {
+        ContentResponse response = client.newRequest(gcUrl).method(HttpMethod.GET).send();
+        assertEquals(200, response.getStatus());
+        String expected = "{ \"response\" : [] }";
+        assertEquals(expected, response.getContentAsString());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/tests/integration/IntegrationTest.java	Tue May 16 11:52:33 2017 -0400
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.gateway.tests.integration;
+
+import static org.junit.Assert.fail;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+import com.redhat.thermostat.gateway.tests.utils.MongodTestUtil;
+import com.redhat.thermostat.gateway.tests.utils.ProcessTestUtil;
+
+public class IntegrationTest {
+    protected static HttpClient client;
+    protected static String baseUrl = "http://127.0.0.1:30000";
+
+    protected static final MongodTestUtil mongodTestUtil = new MongodTestUtil();
+    private static final Path distributionImage = Paths.get("../../distribution/target/image");
+    private static final String WEB_GATEWAY_SCRIPT = "thermostat-web-gateway.sh";
+
+    private static Process serverProcess;
+
+    @BeforeClass
+    public static void beforeClassIntegrationTest() throws Exception {
+        mongodTestUtil.startMongod();
+        setupMongoCredentials();
+
+        client = new HttpClient();
+        client.start();
+
+        startServer();
+    }
+
+    private static void setupMongoCredentials() throws IOException, InterruptedException {
+        Path mongoSetup = distributionImage.resolve("etc/mongo-dev-setup.js");
+
+        ProcessBuilder processBuilder = new ProcessBuilder().command("mongo", mongodTestUtil.listenAddress, mongoSetup.toAbsolutePath().toString()).inheritIO();
+        Process mongoProcess = processBuilder.start();
+        mongoProcess.waitFor();
+    }
+
+    private static void startServer() throws IOException, InterruptedException {
+        String command = distributionImage.resolve("bin").resolve(WEB_GATEWAY_SCRIPT).toAbsolutePath().toString();
+
+        ProcessBuilder processBuilder = new ProcessBuilder().command(command).inheritIO().redirectError(ProcessBuilder.Redirect.PIPE);
+
+        serverProcess = processBuilder.start();
+        final BufferedReader reader = new BufferedReader(new InputStreamReader(serverProcess.getErrorStream()));
+
+        Future<Boolean> f = Executors.newFixedThreadPool(1).submit(new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+                String line;
+                while ((line = reader.readLine()) != null && !line.contains("Server:main: Started")) {
+                    System.out.println(line);
+                }
+                return true;
+            }
+        });
+
+        try {
+            f.get(5000L, TimeUnit.MILLISECONDS);
+        } catch (ExecutionException | TimeoutException e) {
+            fail();
+        }
+    }
+
+
+
+    private static void stopServer() throws Exception {
+        ProcessTestUtil.killRecursively(serverProcess);
+    }
+
+    @AfterClass
+    public static void afterClassIntegrationTest() throws Exception {
+        mongodTestUtil.stopMongod();
+        client.stop();
+        stopServer();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/pom.xml	Tue May 16 11:52:33 2017 -0400
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright 2012-2017 Red Hat, Inc.
+
+ This file is part of Thermostat.
+
+ Thermostat is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2, or (at your
+ option) any later version.
+
+ Thermostat is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Thermostat; see the file COPYING.  If not see
+ <http://www.gnu.org/licenses/>.
+
+ Linking this code with other modules is making a combined work
+ based on this code.  Thus, the terms and conditions of the GNU
+ General Public License cover the whole combination.
+
+ As a special exception, the copyright holders of this code give
+ you permission to link this code with independent modules to
+ produce an executable, regardless of the license terms of these
+ independent modules, and to copy and distribute the resulting
+ executable under terms of your choice, provided that you also
+ meet, for each linked independent module, the terms and conditions
+ of the license of that module.  An independent module is a module
+ which is not derived from or based on this code.  If you modify
+ this code, you may extend this exception to your version of the
+ library, but you are not obligated to do so.  If you do not wish
+ to do so, delete this exception statement from your version.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.redhat.thermostat</groupId>
+        <artifactId>thermostat-web-gateway</artifactId>
+        <version>1.99.12-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>thermostat-web-gateway-tests</artifactId>
+    <packaging>pom</packaging>
+
+    <name>Thermostat Web Gateway Tests</name>
+
+    <modules>
+        <module>integration-tests</module>
+        <module>test-utils</module>
+    </modules>
+</project>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-utils/pom.xml	Tue May 16 11:52:33 2017 -0400
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright 2012-2017 Red Hat, Inc.
+
+ This file is part of Thermostat.
+
+ Thermostat is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2, or (at your
+ option) any later version.
+
+ Thermostat is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Thermostat; see the file COPYING.  If not see
+ <http://www.gnu.org/licenses/>.
+
+ Linking this code with other modules is making a combined work
+ based on this code.  Thus, the terms and conditions of the GNU
+ General Public License cover the whole combination.
+
+ As a special exception, the copyright holders of this code give
+ you permission to link this code with independent modules to
+ produce an executable, regardless of the license terms of these
+ independent modules, and to copy and distribute the resulting
+ executable under terms of your choice, provided that you also
+ meet, for each linked independent module, the terms and conditions
+ of the license of that module.  An independent module is a module
+ which is not derived from or based on this code.  If you modify
+ this code, you may extend this exception to your version of the
+ library, but you are not obligated to do so.  If you do not wish
+ to do so, delete this exception statement from your version.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>thermostat-web-gateway-tests</artifactId>
+        <groupId>com.redhat.thermostat</groupId>
+        <version>1.99.12-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>thermostat-web-gateway-tests-utils</artifactId>
+    <name>Thermostat Web Gateway Test Utilities</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <!-- Thermostat Web Gateway dependencies -->
+        <dependency>
+            <groupId>com.redhat.thermostat</groupId>
+            <artifactId>thermostat-web-gateway-common-mongodb</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <!-- MongoDB dependencies -->
+        <dependency>
+            <groupId>org.mongodb</groupId>
+            <artifactId>mongo-java-driver</artifactId>
+            <version>${mongo-java-driver.version}</version>
+        </dependency>
+    </dependencies>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-utils/src/main/java/com/redhat/thermostat/gateway/tests/utils/MongodTestUtil.java	Tue May 16 11:52:33 2017 -0400
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.gateway.tests.utils;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+import org.bson.Document;
+
+import com.mongodb.MongoClient;
+import com.mongodb.ServerAddress;
+
+public class MongodTestUtil {
+    private final String databaseName = "thermostat";
+    private final String host = "127.0.0.1";
+    private final int port = 27518;
+    public final String listenAddress = host + ":" + port;
+
+    private MongoClient mongoClient;
+    private Path tempDbDir;
+    private Path tempLogFile;
+    public Process process;
+
+
+
+    public void startMongod() throws IOException, InterruptedException {
+        tempDbDir = Files.createTempDirectory("tms-mongo");
+        tempDbDir.toFile().deleteOnExit();
+        Files.createDirectories(tempDbDir.resolve("data/db"));
+        tempLogFile = tempDbDir.resolve("mongod.log");
+        tempLogFile.toFile().deleteOnExit();
+
+        String[] command = {"mongod", "--dbpath", tempDbDir.resolve("data/db").toAbsolutePath().toString(), "--port", String.valueOf(port), "--fork", "--logpath", tempLogFile.toAbsolutePath().toString()};
+        ProcessBuilder builder = new ProcessBuilder(command);
+        process = builder.start();
+
+        mongoClient = new MongoClient(new ServerAddress(host, port));
+
+        waitForMongodStart();
+    }
+
+    public void stopMongod() throws IOException, InterruptedException {
+        try {
+            mongoClient.getDatabase("admin").runCommand(new Document("shutdown", 1));
+        } catch (Exception ignored) {
+        }
+        mongoClient.close();
+        mongoClient = null;
+        waitForMongodStop();
+        finish();
+    }
+
+    public void dropCollection(String collectionName) {
+        mongoClient.getDatabase(databaseName).getCollection(collectionName).drop();
+    }
+
+    private void finish() throws IOException {
+        Files.walkFileTree(tempDbDir, new SimpleFileVisitor<Path>() {
+            @Override
+            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                Files.delete(file);
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+                Files.delete(dir);
+                return FileVisitResult.CONTINUE;
+            }
+
+        });
+    }
+
+    private boolean waitForMongodStart() throws IOException, InterruptedException {
+        return waitFor("waiting for connections on port");
+    }
+
+    private boolean waitForMongodStop() throws IOException, InterruptedException {
+        return waitFor("dbexit:  rc: 0");
+    }
+
+    private boolean waitFor(String match) throws IOException, InterruptedException {
+        final String[] s = new String[]{""};
+
+        for (int i = 0; i < 100; i++) {
+            if (Files.exists(tempLogFile) && !s[0].contains(match)) {
+                s[0] = new String(Files.readAllBytes(tempLogFile));
+            } else if (s[0].contains(match)) {
+                return true;
+            }
+            Thread.sleep(100L);
+        }
+        return false;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-utils/src/main/java/com/redhat/thermostat/gateway/tests/utils/ProcessTestUtil.java	Tue May 16 11:52:33 2017 -0400
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.gateway.tests.utils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ProcessTestUtil {
+    /**
+     * Kill the process and all its children, recursively. Sends SIGTERM.
+     */
+    public static void killRecursively(Process process) throws Exception {
+        killRecursively(getPid(process));
+    }
+
+    private static void killRecursively(int pid) throws Exception {
+        List<Integer> childPids = findChildPids(pid);
+        for (Integer childPid : childPids) {
+            killRecursively(childPid);
+        }
+        killProcess(pid);
+    }
+
+    private static void killProcess(int processId) throws Exception {
+        System.err.println("Killing process with pid: " + processId);
+        Runtime.getRuntime().exec("kill " + processId).waitFor();
+
+        while (1 != Runtime.getRuntime().exec("kill -s 0 " + processId).waitFor()) {
+            // Repeatedly send kill signal until it fails meaning process doesn't exist
+            Thread.sleep(50l);
+        }
+    }
+
+    private static List<Integer> findChildPids(int processId) throws IOException {
+        String children = new String(readAll(Runtime.getRuntime().exec("ps --ppid " + processId + " -o pid=").getInputStream()));
+        String[] childPids = children.split("\n");
+        List<Integer> result = new ArrayList<>();
+        for (String childPid : childPids) {
+            String pidString = childPid.trim();
+            if (pidString.length() == 0) {
+                continue;
+            }
+            try {
+                result.add(Integer.parseInt(pidString));
+            } catch (NumberFormatException nfe) {
+                System.err.println(nfe);
+            }
+        }
+        return result;
+    }
+
+    private static int getPid(Process process) throws Exception {
+        final String UNIX_PROCESS_CLASS = "java.lang.UNIXProcess";
+        // JDK 9 renamed this class to ProcessImpl
+        final String PROCESS_IMPL_CLASS = "java.lang.ProcessImpl";
+        if (!(process.getClass().getName().equals(UNIX_PROCESS_CLASS) || process.getClass().getName().equals(PROCESS_IMPL_CLASS))) {
+            throw new IllegalArgumentException("can only kill " + UNIX_PROCESS_CLASS + " or " + PROCESS_IMPL_CLASS + "; input is a " + process.getClass());
+        }
+
+        Class<?> processClass = process.getClass();
+        Field pidField = processClass.getDeclaredField("pid");
+        pidField.setAccessible(true);
+        return (int) pidField.get(process);
+    }
+
+    private static byte[] readAll(InputStream in) throws IOException {
+        final int TEMPORARY_BUFFER_SIZE = 1024;
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        byte[] buffer = new byte[TEMPORARY_BUFFER_SIZE];
+        int read;
+        while ((read = in.read(buffer)) != -1) {
+            baos.write(buffer, 0, read);
+        }
+
+        return baos.toByteArray();
+    }
+}