/* * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code 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 * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.test.failurehandler.action; import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachineDescriptor; import jdk.test.failurehandler.value.InvalidValueException; import jdk.test.failurehandler.value.Value; import jdk.test.failurehandler.value.ValueHandler; import jdk.test.failurehandler.HtmlSection; import jdk.test.failurehandler.Stopwatch; import jdk.test.failurehandler.Utils; import java.io.BufferedReader; import java.io.CharArrayReader; import java.io.CharArrayWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.Reader; import java.io.Writer; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Properties; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.TimeUnit; public class ActionHelper { private final Path workDir; @Value(name = "execSuffix") private String executableSuffix = ""; private Path[] paths; private final PatternAction getChildren; public ActionHelper(Path workDir, String prefix, Properties properties, Path... jdks) throws InvalidValueException { this.workDir = workDir.toAbsolutePath(); getChildren = new PatternAction(null, Utils.prependPrefix(prefix, "getChildren"), properties); ValueHandler.apply(this, properties, prefix); String[] pathStrings = System.getenv("PATH").split(File.pathSeparator); paths = new Path[pathStrings.length]; for (int i = 0; i < paths.length; ++i) { paths[i] = Paths.get(pathStrings[i]); } addJdks(jdks); } public List getChildren(HtmlSection section, long pid) { String pidStr = "" + pid; ProcessBuilder pb = getChildren.prepareProcess(section, this, pidStr); PrintWriter log = getChildren.getSection(section).getWriter(); CharArrayWriter writer = new CharArrayWriter(); ExitCode code = run(log, writer, pb, getChildren.getParameters()); Reader output = new CharArrayReader(writer.toCharArray()); if (!ExitCode.OK.equals(code)) { log.println("WARNING: get children pids action failed"); try { Utils.copyStream(output, log); } catch (IOException e) { e.printStackTrace(log); } return Collections.emptyList(); } List result = new ArrayList<>(); try { try (BufferedReader reader = new BufferedReader(output)) { String line; while ((line = reader.readLine()) != null) { String value = line.trim(); if (value.isEmpty()) { // ignore empty lines continue; } try { result.add(Long.valueOf(value)); } catch (NumberFormatException e) { log.printf("WARNING: can't parse child pid %s : %s%n", line, e.getMessage()); e.printStackTrace(log); } } } } catch (IOException e) { e.printStackTrace(log); } return result; } public ProcessBuilder prepareProcess(PrintWriter log, String app, String... args) { File appBin = findApp(app); if (appBin == null) { log.printf("ERROR: can't find %s in %s.%n", app, Arrays.toString(paths)); return null; } List command = new ArrayList<>(args.length + 1); command.add(appBin.toString()); Collections.addAll(command, args); return new ProcessBuilder() .command(command) .directory(workDir.toFile()); } private File findApp(String app) { String name = app + executableSuffix; for (Path pathElem : paths) { File result = pathElem.resolve(name).toFile(); if (result.exists()) { return result; } } return null; } private void addJdks(Path[] jdkPaths) { if (jdkPaths != null && jdkPaths.length != 0) { Path[] result = new Path[jdkPaths.length + paths.length]; for (int i = 0; i < jdkPaths.length; ++i) { result[i] = jdkPaths[i].resolve("bin"); } System.arraycopy(paths, 0, result, jdkPaths.length, paths.length); paths = result; } } private ExitCode run(PrintWriter log, Writer out, ProcessBuilder pb, ActionParameters params) { char[] lineChars = new char[40]; Arrays.fill(lineChars, '-'); String line = new String(lineChars); Stopwatch stopwatch = new Stopwatch(); stopwatch.start(); log.printf("%s%n[%tF % 0) { WATCHDOG.schedule(this, timeout); } } } private void exec(HtmlSection section, ProcessBuilder process, ActionParameters params) { if (process == null) { return; } PrintWriter sectionWriter = section.getWriter(); if (params.repeat > 1) { for (int i = 0, n = params.repeat; i < n; ++i) { HtmlSection iteration = section.createChildren( String.format("iteration_%d", i)); PrintWriter writer = iteration.getWriter(); ExitCode exitCode = run(writer, writer, process, params); if (params.stopOnError && !ExitCode.OK.equals(exitCode)) { sectionWriter.printf( "ERROR: non zero exit code[%d] -- break.", exitCode.value); break; } // sleep, if this is not the last iteration if (i < n - 1) { try { Thread.sleep(params.pause); } catch (InterruptedException e) { sectionWriter.printf( "WARNING: interrupted while sleeping between invocations"); e.printStackTrace(sectionWriter); } } } } else { run(section.getWriter(), section.getWriter(), process, params); } } /** * Special values for prepareProcess exit code. * *

Can we clash with normal codes? * On Linux, only [0..255] are returned. * On Windows, prepareProcess exit codes are stored in unsigned int. * On MacOSX no limits (except it should fit C int type) * are defined in the exit() man pages. */ private static class ExitCode { /** Process exits gracefully */ public static final ExitCode OK = new ExitCode(0); /** Error launching prepareProcess */ public static final ExitCode LAUNCH_ERROR = new ExitCode(-1); /** Application prepareProcess has been killed by watchdog due to timeout */ public static final ExitCode TIMED_OUT = new ExitCode(-2); /** Application prepareProcess has never been started due to program logic */ public static final ExitCode NEVER_STARTED = new ExitCode(-3); public final int value; private ExitCode(int value) { this.value = value; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ExitCode exitCode = (ExitCode) o; return value == exitCode.value; } @Override public int hashCode() { return value; } } }