Path: blob/master/test/jdk/java/lang/ProcessHandle/TreeTest.java
41149 views
/*1* Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/222324import java.io.IOException;25import java.time.Duration;26import java.time.Instant;27import java.util.ArrayList;28import java.util.Arrays;29import java.util.List;30import java.util.Optional;31import java.util.Set;32import java.util.concurrent.ConcurrentHashMap;33import java.util.concurrent.CountDownLatch;34import java.util.concurrent.ExecutionException;35import java.util.concurrent.TimeUnit;36import java.util.stream.Collectors;37import java.util.stream.Stream;3839import jdk.test.lib.Utils;40import org.testng.Assert;41import org.testng.TestNG;42import org.testng.annotations.Test;4344/*45* @test46* @library /test/lib47* @modules java.base/jdk.internal.misc48* jdk.management49* @build jdk.test.lib.Utils50* jdk.test.lib.Asserts51* jdk.test.lib.JDKToolFinder52* jdk.test.lib.JDKToolLauncher53* jdk.test.lib.Platform54* jdk.test.lib.process.*55* @run testng/othervm TreeTest56* @summary Test counting and JavaChild.spawning and counting of Processes.57* @author Roger Riggs58*/59public class TreeTest extends ProcessUtil {60// Main can be used to run the tests from the command line with only testng.jar.61@SuppressWarnings("raw_types")62public static void main(String[] args) {63Class<?>[] testclass = {TreeTest.class};64TestNG testng = new TestNG();65testng.setTestClasses(testclass);66testng.run();67}6869/**70* Test counting and spawning and counting of Processes.71*/72@Test73public static void test1() {74final int MAXCHILDREN = 2;75List<JavaChild> spawned = new ArrayList<>();7677try {78ProcessHandle self = ProcessHandle.current();7980printf("self pid: %d%n", self.pid());81printDeep(self, "");8283for (int i = 0; i < MAXCHILDREN; i++) {84// spawn and wait for instructions85spawned.add(JavaChild.spawnJavaChild("pid", "stdin"));86}8788// Verify spawned Process is in list of children89final List<ProcessHandle> initialChildren = getChildren(self);90spawned.stream()91.map(Process::toHandle)92.filter(p -> !initialChildren.contains(p))93.forEach(p -> Assert.fail("Spawned process missing from children: " + p));9495// Send exit command to each spawned Process96spawned.forEach(p -> {97try {98p.sendAction("exit", "");99} catch (IOException ex) {100Assert.fail("IOException in sendAction", ex);101}102});103104// Wait for each Process to exit105spawned.forEach(p -> {106do {107try {108Assert.assertEquals(p.waitFor(), 0, "exit status incorrect");109break;110} catch (InterruptedException ex) {111continue; // Retry112}113} while (true);114});115116// Verify that ProcessHandle.isAlive sees each of them as not alive117for (Process p : spawned) {118ProcessHandle ph = p.toHandle();119Assert.assertFalse(ph.isAlive(),120"ProcessHandle.isAlive for exited process: " + ph);121}122123// Verify spawned processes are not visible as children124final List<ProcessHandle> afterChildren = getChildren(self);125spawned.stream()126.map(Process::toHandle)127.filter(p -> afterChildren.contains(p))128.forEach(p -> Assert.fail("Spawned process missing from children: " + p));129130} catch (IOException ioe) {131Assert.fail("unable to spawn process", ioe);132} finally {133// Cleanup any left over processes134spawned.stream()135.map(Process::toHandle)136.filter(ProcessHandle::isAlive)137.forEach(ph -> {138printDeep(ph, "test1 cleanup: ");139ph.destroyForcibly();140});141}142}143144/**145* Test counting and spawning and counting of Processes.146*/147@Test148public static void test2() {149try {150ConcurrentHashMap<ProcessHandle, ProcessHandle> processes = new ConcurrentHashMap<>();151152ProcessHandle self = ProcessHandle.current();153List<ProcessHandle> initialChildren = getChildren(self);154long count = initialChildren.size();155if (count > 0) {156initialChildren.forEach(p -> printDeep(p, "test2 initial unexpected: "));157}158159JavaChild p1 = JavaChild.spawnJavaChild("stdin");160ProcessHandle p1Handle = p1.toHandle();161printf(" p1 pid: %d%n", p1.pid());162163// Gather the PIDs from the output of the spawing process164p1.forEachOutputLine((s) -> {165String[] split = s.trim().split(" ");166if (split.length == 3 && split[1].equals("spawn")) {167Long child = Long.valueOf(split[2]);168Long parent = Long.valueOf(split[0].split(":")[0]);169processes.put(ProcessHandle.of(child).get(), ProcessHandle.of(parent).get());170}171});172173int spawnNew = 3;174p1.sendAction("spawn", spawnNew, "stdin");175176// Wait for direct children to be created and save the list177List<ProcessHandle> subprocesses = waitForAllChildren(p1Handle, spawnNew);178Optional<Instant> p1Start = p1Handle.info().startInstant();179for (ProcessHandle ph : subprocesses) {180Assert.assertTrue(ph.isAlive(), "Child should be alive: " + ph);181// Verify each child was started after the parent182ph.info().startInstant()183.ifPresent(childStart -> p1Start.ifPresent(parentStart -> {184Assert.assertFalse(childStart.isBefore(parentStart),185String.format("Child process started before parent: child: %s, parent: %s",186childStart, parentStart));187}));188}189190// Each child spawns two processes and waits for commands191int spawnNewSub = 2;192p1.sendAction("child", "spawn", spawnNewSub, "stdin");193194// Poll until all 9 child processes exist or the timeout is reached195int expected = 9;196long timeout = Utils.adjustTimeout(60L);197Instant endTimeout = Instant.now().plusSeconds(timeout);198do {199Thread.sleep(200L);200printf(" subprocess count: %d, waiting for %d%n", processes.size(), expected);201} while (processes.size() < expected &&202Instant.now().isBefore(endTimeout));203204if (processes.size() < expected) {205printf("WARNING: not all children have been started. Can't complete test.%n");206printf(" You can try to increase the timeout or%n");207printf(" you can try to use a faster VM (i.e. not a debug version).%n");208}209210// show the complete list of children (for debug)211List<ProcessHandle> descendants = getDescendants(p1Handle);212printf(" descendants: %s%n",213descendants.stream().map(p -> p.pid())214.collect(Collectors.toList()));215216// Verify that all spawned children show up in the descendants List217processes.forEach((p, parent) -> {218Assert.assertEquals(p.isAlive(), true, "Child should be alive: " + p);219Assert.assertTrue(descendants.contains(p), "Spawned child should be listed in descendants: " + p);220});221222// Closing JavaChild's InputStream will cause all children to exit223p1.getOutputStream().close();224225for (ProcessHandle p : descendants) {226try {227p.onExit().get(); // wait for the child to exit228} catch (ExecutionException e) {229Assert.fail("waiting for process to exit", e);230}231}232p1.waitFor(); // wait for spawned process to exit233234// Verify spawned processes are no longer alive235processes.forEach((ph, parent) -> Assert.assertFalse(ph.isAlive(),236"process should not be alive: " + ph));237} catch (IOException | InterruptedException t) {238t.printStackTrace();239throw new RuntimeException(t);240}241}242243/**244* Test destroy of processes.245* A JavaChild is started and it starts three children.246* Each one is then checked to be alive and listed by descendants247* and forcibly destroyed.248* After they exit they should no longer be listed by descendants.249*/250@Test251public static void test3() {252ConcurrentHashMap<ProcessHandle, ProcessHandle> processes = new ConcurrentHashMap<>();253254try {255ProcessHandle self = ProcessHandle.current();256257JavaChild p1 = JavaChild.spawnJavaChild("stdin");258ProcessHandle p1Handle = p1.toHandle();259printf(" p1: %s%n", p1.pid());260261int newChildren = 3;262CountDownLatch spawnCount = new CountDownLatch(newChildren);263// Spawn children and have them wait264p1.sendAction("spawn", newChildren, "stdin");265266// Gather the PIDs from the output of the spawning process267p1.forEachOutputLine((s) -> {268String[] split = s.trim().split(" ");269if (split.length == 3 && split[1].equals("spawn")) {270Long child = Long.valueOf(split[2]);271Long parent = Long.valueOf(split[0].split(":")[0]);272processes.put(ProcessHandle.of(child).get(), ProcessHandle.of(parent).get());273spawnCount.countDown();274}275});276277// Wait for all the subprocesses to be listed as started278Assert.assertTrue(spawnCount.await(Utils.adjustTimeout(30L), TimeUnit.SECONDS),279"Timeout waiting for processes to start");280281// Debugging; list descendants that are not expected in processes282List<ProcessHandle> descendants = ProcessUtil.getDescendants(p1Handle);283long count = descendants.stream()284.filter(ph -> !processes.containsKey(ph))285.count();286if (count > 0) {287descendants.stream()288.filter(ph -> !processes.containsKey(ph))289.forEach(ph1 -> ProcessUtil.printProcess(ph1, "Extra process: "));290ProcessUtil.logTaskList();291Assert.assertEquals(0, count, "Extra processes in descendants");292}293294// Verify that all spawned children are alive, show up in the descendants list295// then destroy them296processes.forEach((p, parent) -> {297Assert.assertEquals(p.isAlive(), true, "Child should be alive: " + p);298Assert.assertTrue(descendants.contains(p), "Spawned child should be listed in descendants: " + p);299p.destroyForcibly();300});301Assert.assertEquals(processes.size(), newChildren, "Wrong number of children");302303// Wait for each of the processes to exit304processes.forEach((p, parent) -> {305for (long retries = Utils.adjustTimeout(100L); retries > 0 ; retries--) {306if (!p.isAlive()) {307return; // not alive, go on to the next308}309// Wait a bit and retry310try {311Thread.sleep(100L);312} catch (InterruptedException ie) {313// try again314}315}316printf("Timeout waiting for exit of pid %s, parent: %s, info: %s%n",317p, parent, p.info());318Assert.fail("Process still alive: " + p);319});320p1.destroyForcibly();321p1.waitFor();322323// Verify that none of the spawned children are still listed by descendants324List<ProcessHandle> remaining = getDescendants(self);325Assert.assertFalse(remaining.remove(p1Handle), "Child p1 should have exited");326remaining = remaining.stream().filter(processes::containsKey).collect(Collectors.toList());327Assert.assertEquals(remaining.size(), 0, "Subprocess(es) should have exited: " + remaining);328329} catch (IOException ioe) {330Assert.fail("Spawn of subprocess failed", ioe);331} catch (InterruptedException inte) {332Assert.fail("InterruptedException", inte);333} finally {334processes.forEach((p, parent) -> {335if (p.isAlive()) {336ProcessUtil.printProcess(p);337p.destroyForcibly();338}339});340}341}342343/**344* Test (Not really a test) that dumps the list of all Processes.345*/346@Test347public static void test4() {348printf(" Parent Child Info%n");349Stream<ProcessHandle> s = ProcessHandle.allProcesses();350ProcessHandle[] processes = s.toArray(ProcessHandle[]::new);351int len = processes.length;352ProcessHandle[] parent = new ProcessHandle[len];353Set<ProcessHandle> processesSet =354Arrays.stream(processes).collect(Collectors.toSet());355Integer[] sortindex = new Integer[len];356for (int i = 0; i < len; i++) {357sortindex[i] = i;358}359for (int i = 0; i < len; i++) {360parent[sortindex[i]] = processes[sortindex[i]].parent().orElse(null);361}362Arrays.sort(sortindex, (i1, i2) -> {363int cmp = Long.compare((parent[i1] == null ? 0L : parent[i1].pid()),364(parent[i2] == null ? 0L : parent[i2].pid()));365if (cmp == 0) {366cmp = Long.compare((processes[i1] == null ? 0L : processes[i1].pid()),367(processes[i2] == null ? 0L : processes[i2].pid()));368}369return cmp;370});371boolean fail = false;372for (int i = 0; i < len; i++) {373ProcessHandle p = processes[sortindex[i]];374ProcessHandle p_parent = parent[sortindex[i]];375ProcessHandle.Info info = p.info();376String indent = " ";377if (p_parent != null) {378if (!processesSet.contains(p_parent)) {379fail = true;380indent = "*** ";381}382}383printf("%s %7s, %7s, %s%n", indent, p_parent, p, info);384}385Assert.assertFalse(fail, "Parents missing from all Processes");386387}388389/**390* A test for scale; launch a large number (14) of subprocesses.391*/392@Test393public static void test5() {394ConcurrentHashMap<ProcessHandle, ProcessHandle> processes = new ConcurrentHashMap<>();395396int factor = 2;397JavaChild p1 = null;398Instant start = Instant.now();399try {400p1 = JavaChild.spawnJavaChild("stdin");401ProcessHandle p1Handle = p1.toHandle();402403printf("Spawning %d x %d x %d processes, pid: %d%n",404factor, factor, factor, p1.pid());405406// Start the first tier of subprocesses407p1.sendAction("spawn", factor, "stdin");408409// Start the second tier of subprocesses410p1.sendAction("child", "spawn", factor, "stdin");411412// Start the third tier of subprocesses413p1.sendAction("child", "child", "spawn", factor, "stdin");414415int newChildren = factor * (1 + factor * (1 + factor));416CountDownLatch spawnCount = new CountDownLatch(newChildren);417418// Gather the PIDs from the output of the spawning process419p1.forEachOutputLine((s) -> {420String[] split = s.trim().split(" ");421if (split.length == 3 && split[1].equals("spawn")) {422Long child = Long.valueOf(split[2]);423Long parent = Long.valueOf(split[0].split(":")[0]);424processes.put(ProcessHandle.of(child).get(), ProcessHandle.of(parent).get());425spawnCount.countDown();426}427});428429// Wait for all the subprocesses to be listed as started430Assert.assertTrue(spawnCount.await(Utils.adjustTimeout(30L), TimeUnit.SECONDS),431"Timeout waiting for processes to start");432433// Debugging; list descendants that are not expected in processes434List<ProcessHandle> descendants = ProcessUtil.getDescendants(p1Handle);435long count = descendants.stream()436.filter(ph -> !processes.containsKey(ph))437.count();438if (count > 0) {439descendants.stream()440.filter(ph -> !processes.containsKey(ph))441.forEach(ph1 -> ProcessUtil.printProcess(ph1, "Extra process: "));442ProcessUtil.logTaskList();443Assert.assertEquals(0, count, "Extra processes in descendants");444}445446Assert.assertEquals(getChildren(p1Handle).size(),447factor, "expected direct children");448count = getDescendants(p1Handle).size();449long totalChildren = factor * factor * factor + factor * factor + factor;450Assert.assertTrue(count >= totalChildren,451"expected at least " + totalChildren + ", actual: " + count);452453List<ProcessHandle> subprocesses = getDescendants(p1Handle);454printf(" descendants: %s%n",455subprocesses.stream().map(p -> p.pid())456.collect(Collectors.toList()));457458p1.getOutputStream().close(); // Close stdin for the controlling p1459p1.waitFor();460} catch (InterruptedException | IOException ex) {461Assert.fail("Unexpected Exception", ex);462} finally {463printf("Duration: %s%n", Duration.between(start, Instant.now()));464if (p1 != null) {465p1.destroyForcibly();466}467processes.forEach((p, parent) -> {468if (p.isAlive()) {469ProcessUtil.printProcess(p, "Process Cleanup: ");470p.destroyForcibly();471}472});473}474}475476}477478479