Path: blob/master/test/functional/cmdline_options_tester/src/Test.java
6004 views
/*******************************************************************************1* Copyright (c) 2004, 2021 IBM Corp. and others2*3* This program and the accompanying materials are made available under4* the terms of the Eclipse Public License 2.0 which accompanies this5* distribution and is available at https://www.eclipse.org/legal/epl-2.0/6* or the Apache License, Version 2.0 which accompanies this distribution and7* is available at https://www.apache.org/licenses/LICENSE-2.0.8*9* This Source Code may also be made available under the following10* Secondary Licenses when the conditions for such availability set11* forth in the Eclipse Public License, v. 2.0 are satisfied: GNU12* General Public License, version 2 with the GNU Classpath13* Exception [1] and GNU General Public License, version 2 with the14* OpenJDK Assembly Exception [2].15*16* [1] https://www.gnu.org/software/classpath/license.html17* [2] http://openjdk.java.net/legal/assembly-exception.html18*19* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception20*******************************************************************************/2122import java.io.BufferedReader;23import java.io.File;24import java.io.IOException;25import java.io.InputStream;26import java.io.InputStreamReader;27import java.io.PrintStream;28import java.io.PrintWriter;29import java.io.Writer;30import java.lang.reflect.Field;31import java.lang.reflect.Method;32import java.util.ArrayList;33import java.util.Collections;34import java.util.List;35import java.util.concurrent.TimeUnit;3637@SuppressWarnings("nls")38class Test {39private final String _id;40private String _command;41private final String _timeoutString;42final boolean _debugCmdOnTimeout;43private int _outputLimit;4445final List<TestCondition> _testConditions;46boolean[] _matched;4748private final StringBuilder _standardOutput;49private final StringBuilder _errorOutput;5051boolean _timedOut;5253private String _commandExecutable;54private List<String> _commandArgs;55private List<String> _commandInputLines;56private static final long _JAVACORE_TIMEOUT_MILLIS = 5 * 60 * 1000; // 5 minute timeout57private static final long SHORT_TIMEOUT_MILLIS = 30 * 1000; // 30 second timeout5859/**60* The number of core files to capture may be specified via61* -Dcmdline.corecount=N62* The default is 2.63*/64static final String CORE_COUNT_PROPERTY = "cmdline.corecount";65static final int CORE_COUNT = Integer.getInteger(CORE_COUNT_PROPERTY, 2).intValue();6667/**68* The time in milliseconds between capturing core files (if CORE_COUNT > 1)69* may be specified via70* -Dcmdline.coreintervalms=N71* The default is one minute.72*/73static final long CORE_SPACING_MILLIS = Long.getLong("cmdline.coreintervalms", 60 * 1000).longValue();7475static String archName = System.getProperty("os.arch");76static boolean isRiscv = archName.toLowerCase().contains("riscv");77static boolean isJava8 = System.getProperty("java.specification.version").equals("1.8");7879/**80* Create a new test case with the given id.81* @param id82*/83Test(String id, String timeout, boolean debugCmdOnTimeout) {84super();85_id = id;86_timeoutString = timeout;87_debugCmdOnTimeout = debugCmdOnTimeout;88_outputLimit = -1;89_testConditions = new ArrayList<>();90_matched = null;91_standardOutput = new StringBuilder();92_errorOutput = new StringBuilder();93_timedOut = false;94}9596void setOutputLimit(int outputLimit) {97_outputLimit = outputLimit;98}99100/**101* Set the command particular to this test case102*/103void setCommand(String command) {104_command = command;105}106107void setSplitCommand(String command, List<String> args, List<String> inputLines) {108_commandExecutable = command;109_commandArgs = args;110_commandInputLines = inputLines;111}112113/**114* Get the name of this test case115*/116public String getId() {117return TestSuite.evaluateVariables(_id);118}119120/**121* Adds another test condition to this test case122*/123void addTestCondition(TestCondition tc) {124_testConditions.add(tc);125}126127/**128* Does the required variable substitutions and runs the test case.129*130* @param executable - The executable command string131* @param defaultTimeout - The default timeout for this test suite. This will be overridden by132* the instance variable _timeout if it is something >= 0.133* @param variables - The variables to do substitutions of134* @return true if the testcase passed, false if it failed135*/136public boolean run(long defaultTimeout) {137// clear any previously captured output138destroy();139140_matched = new boolean[_testConditions.size()];141long timer;142Process proc = null;143_timedOut = false;144try {145/**146* Set up a built-in variable "Q" to represent a double-quotes(").147* The command comes with $Q$ initially set up by tester to quote the classpath with white spaces.148* $Q$ in the command string will be replaced with a double-quotes(") by the parser of149* test framework before passing it over to Tokenizer for further processing.150* @see Tokenizer151*/152TestSuite.putVariable("Q", "\"");153String exeToDebug = null;154String fullCommand = null;155File userDir = new File(System.getProperty("user.dir"));156Stopwatch testTimer = new Stopwatch().start();157if (null != _command) {158fullCommand = TestSuite.evaluateVariables(_command);159System.out.println("Running command: " + fullCommand);160161/**162* According to the test framework, a command string is passed over to exec(String command,...) of Runtime for test execution.163* However, the method is unable to recognize a command string if white spaces occur in the classpath of the command string.164* To solve the issue, exec(String command,...) is replaced with exec(String[] cmdarray,...), in which case it requires that165* a command string be replaced with a command array with all arguments split up in the array.166* Meanwhile, a path of .jar file with white spaces should be treated as a single argument in the command array.167* Thus, a new class called Tokenizer is created to address the issue of classpath when splitting up a command string.168* NOTE: The reason why StreamTokenizer was discarded is that it wrongly interpreted escape characters (e.g. \b, \n, \t)169* in the classpath into a single character rather than two characters.170* @see Tokenizer171*/172String[] cmdArray = Tokenizer.tokenize(fullCommand);173exeToDebug = cmdArray[0];174proc = Runtime.getRuntime().exec(cmdArray, TestSuite.getEnvironmentVariableList(), userDir);175} else {176// Use a buffer to build the command line from the _commandExecutable and the _commandArgs177StringBuilder buffer = new StringBuilder(TestSuite.evaluateVariables(_commandExecutable));178for (String arg : _commandArgs) {179buffer.append(' ');180buffer.append(TestSuite.evaluateVariables(arg));181}182// Get the fullCommand string from the buffer183fullCommand = buffer.toString();184System.out.println("Running command: " + fullCommand);185String[] cmdArray = Tokenizer.tokenize(fullCommand);186exeToDebug = cmdArray[0];187// now start the program188proc = Runtime.getRuntime().exec(cmdArray, null, userDir);189}190testTimer.stop();191timer = testTimer.getTimeSpent();192System.out.println("Time spent starting: " + timer + " milliseconds");193StreamMatcher stdout = new StreamMatcher(proc.getInputStream(), _standardOutput, " [OUT] ");194StreamMatcher stderr = new StreamMatcher(proc.getErrorStream(), _errorOutput, " [ERR] ");195stdout.start();196stderr.start();197198// Feed our commands to stdin of the process. We do this *after* starting199// the threads that consume stdout/stderr to avoid deadlock.200try (PrintStream stdin = new PrintStream(proc.getOutputStream())) {201if (_commandInputLines != null && _command == null) {202// fix up the command inputs and feed them in (with newlines between)203for (String arg : _commandInputLines) {204String inputLine = TestSuite.evaluateVariables(arg);205stdin.println(inputLine);206}207}208} // closes input so the underlying program will get EOF209210long _timeout = defaultTimeout;211try {212_timeout = 1000 * Integer.parseInt(TestSuite.evaluateVariables(_timeoutString).trim());213} catch (Exception e) {214/* Expected exception, failing to parse _timeout makes timeout have the value of defaultTimeout */215}216217new ProcessKiller(proc, _timeout, exeToDebug).start();218219// Wait for the ProcessKiller. Timeout should never occur here when ProcessKiller is working.220long killerTimeout = _JAVACORE_TIMEOUT_MILLIS;221if (isLinux()) {222if (CORE_COUNT > 0) {223// Wait for the core files to be produced.224killerTimeout += (_JAVACORE_TIMEOUT_MILLIS * CORE_COUNT) + (CORE_SPACING_MILLIS * (CORE_COUNT - 1));225}226}227stdout.join(_timeout + killerTimeout);228// Don't wait long for stderr after waiting for stdout.229stderr.join(SHORT_TIMEOUT_MILLIS);230231if (stdout.isAlive()) {232TestSuite.printErrorMessage("stdout timed out");233}234if (stderr.isAlive()) {235TestSuite.printErrorMessage("stderr timed out");236}237238// Don't wait long for the process to timeout after waiting on stdout/stderr.239proc.waitFor(SHORT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);240if (proc.isAlive()) {241TestSuite.printErrorMessage("destroy test process after timeout");242proc.destroy();243}244245testTimer.stop();246timer = testTimer.getTimeSpent();247System.out.println("Time spent executing: " + timer + " milliseconds");248} catch (Exception e) {249if (proc != null) {250proc.destroy();251}252253TestSuite.printErrorMessage("Error during test case: " + _id);254TestSuite.printStackTrace(e);255}256257if (proc != null) {258Integer retval = Integer.valueOf(proc.exitValue());259for (int i = 0; i < _testConditions.size(); i++) {260TestCondition tc = _testConditions.get(i);261if (tc instanceof ReturnValue) {262_matched[i] = ((ReturnValue) tc).match(retval);263}264}265}266267if (proc != null && _timedOut) {268return false;269}270271for (int i = 0; i < _testConditions.size(); i++) {272int badType = _matched[i] ? TestCondition.FAILURE : TestCondition.REQUIRED;273274if (_testConditions.get(i).getType() == badType) {275return false;276}277}278279for (int i = 0; i < _testConditions.size(); i++) {280if (_matched[i] && _testConditions.get(i).getType() == TestCondition.SUCCESS) {281return true;282}283}284285return false;286}287288/**289* Prints the stdout and stderr output from the test case, as well as the reason the test case290* failed.291*/292public void dumpVerboseOutput(boolean result) {293final String standardOutputString;294synchronized (_standardOutput) {295standardOutputString = _standardOutput.toString();296// Clean up, if this function is called multiple times it will only output the new output297_standardOutput.setLength(0);298}299final String errorOutputString;300synchronized (_errorOutput) {301errorOutputString = _errorOutput.toString();302_errorOutput.setLength(0);303}304if (_outputLimit == -1) {305System.out.println("Output from test:");306System.out.print(standardOutputString);307System.out.print(errorOutputString);308} else {309String outputStr = standardOutputString + errorOutputString;310String[] lines = outputStr.split("\n");311List<Integer> matchIndexes = new ArrayList<>();312/* If test case passed, then all the success and required conditions are met */313if (result) {314for (int i = 0; i < _testConditions.size(); i++) {315TestCondition tc = _testConditions.get(i);316if (!(tc instanceof Output)) {317continue;318}319switch (tc.getType()) {320case TestCondition.REQUIRED:321case TestCondition.SUCCESS:322Output output = (Output) tc;323for (int j = 0; j < lines.length; j++) {324if (output.match(lines[j])) {325/* Log the line number where first match is found */326matchIndexes.add(Integer.valueOf(j));327break;328}329}330break;331default:332break;333}334}335} else {336/* If test case failed, then at least one of the following happened.337*338* 1. One of the required condition is not found.339* 2. None of the success conditions are found.340* 3. At least one failure condition is found.341*342* In the first two cases, just print the beginning and end of the output343* depending on user specified outputLimit option.344* For every occurrence of the third case, print the output where failure condition is found.345*/346for (int i = 0; i < _testConditions.size(); i++) {347TestCondition tc = _testConditions.get(i);348if (!(tc instanceof Output)) {349continue;350}351if (_matched[i] || (tc.getType() == TestCondition.FAILURE)) {352Output output = (Output) tc;353for (int j = 0; j < lines.length; j++) {354if (output.match(outputStr)) {355/* Log the line number where first match is found */356matchIndexes.add(Integer.valueOf(j));357break;358}359}360}361}362363/* No failure conditions is found, either case 1 or 2 above happened364* In this case, just print the beginning and end of the output.365*/366if (0 == matchIndexes.size()) {367matchIndexes.add(Integer.valueOf(0)); /* First line */368matchIndexes.add(Integer.valueOf(lines.length - 1)); /* Last line */369}370}371372Collections.sort(matchIndexes);373374int lineLimitPerCond = _outputLimit / matchIndexes.size();375int end = 0;376int lastPrintedLine = 0;377for (int i = 0; i < matchIndexes.size(); i++) {378Integer currentIndex = matchIndexes.get(i);379int start = currentIndex.intValue() - (lineLimitPerCond / 2);380start = start <= lastPrintedLine ? ((lastPrintedLine == 0) ? 0 : lastPrintedLine + 1) : start;381end = Math.min(start + lineLimitPerCond, lines.length) - 1;382383if ((start > lastPrintedLine) && (lastPrintedLine != lines.length - 1)) {384System.out.println();385System.out.println("\t..........................................................");386System.out.println("\t ............ " + (start - lastPrintedLine) + " lines of output is removed ............");387System.out.println("\t..........................................................");388System.out.println();389}390391lastPrintedLine = end;392for (int j = start; j <= end; j++) {393System.out.println(lines[j]);394}395}396397if (end < lines.length - 1) {398System.out.println();399System.out.println("\t...........................................................");400System.out.println("\t ............ " + (lines.length - 1 - end) + " lines of output is removed ............");401System.out.println("\t...........................................................");402System.out.println();403}404}405406if (!_timedOut) {407for (int i = 0; i < _testConditions.size(); i++) {408TestCondition tc = _testConditions.get(i);409StringBuilder sb = new StringBuilder(">> ");410if (tc.getType() == TestCondition.FAILURE) {411sb.append("Failure");412} else if (tc.getType() == TestCondition.REQUIRED) {413sb.append("Required");414} else if (tc.getType() == TestCondition.SUCCESS) {415sb.append("Success");416} else {417sb.append("<Unknown type>");418}419sb.append(" condition was ");420sb.append(_matched[i] ? "" : "not ");421sb.append("found: [");422sb.append(tc.toString());423sb.append("]");424System.out.println(sb.toString());425}426}427}428429public static boolean isLinux() {430String osName = System.getProperty("os.name", "<unknown>");431432return osName.toLowerCase().indexOf("linux") >= 0;433}434435public static int getPID(Process process) throws Exception {436if (isRiscv) {437return 0;438}439if (isJava8) {440Class<?> cl = process.getClass();441if (!cl.getName().equals("java.lang.UNIXProcess")) {442return 0;443}444Field field = cl.getDeclaredField("pid");445field.setAccessible(true);446Object pidObject = field.get(process);447return ((Integer) pidObject).intValue();448} else {449// if not Java 8 then it must be Java 11 or later450Method pidMethod = Process.class.getMethod("pid");451Long pid = (Long)pidMethod.invoke(process);452return pid.intValue();453}454}455456private final class StreamMatcher extends Thread {457private final BufferedReader _br;458private final StringBuilder _caughtOutput;459private final String _prefix;460461StreamMatcher(InputStream in, StringBuilder caughtOutput, String prefix) {462super();463_br = new BufferedReader(new InputStreamReader(in));464_caughtOutput = caughtOutput;465_prefix = prefix;466}467468@Override469public void run() {470int numO = 0;471for (TestCondition tc : _testConditions) {472if (tc instanceof Output) {473numO++;474}475}476final int[] outputIndices = new int[numO];477numO = 0;478for (int i = 0; i < _testConditions.size(); i++) {479if (_testConditions.get(i) instanceof Output) {480outputIndices[numO++] = i;481}482}483try {484String textRead;485while ((textRead = _br.readLine()) != null) {486synchronized (_caughtOutput) {487_caughtOutput.append(_prefix).append(textRead).append('\n');488}489for (int index : outputIndices) {490synchronized (_matched) {491if (_matched[index]) {492continue;493}494Output output = (Output) _testConditions.get(index);495if (!output.isJavaUtilPattern()) {496_matched[index] |= output.match(textRead);497}498}499}500}501_br.close();502503/*If Java.Util.Pattern is used for regex's need to process the whole output file*/504String fulloutputstr;505synchronized (_caughtOutput) {506fulloutputstr = _caughtOutput.toString();507}508for (int index : outputIndices) {509synchronized (_matched) {510if (_matched[index]) {511continue;512}513Output output = (Output) _testConditions.get(index);514if (output.isJavaUtilPattern()) {515_matched[index] |= output.match(fulloutputstr);516}517}518}519} catch (IOException e) {520// This exception is normal, it happens when the ProcessKiller destroys a process521} catch (Exception e) {522TestSuite.printStackTrace(e);523}524}525}526527/* Used by process killer to read in stdout & stderr from exec'd processes. */528private static final class StreamReader extends Thread {529private final BufferedReader _br;530private final StringBuilder _caughtOutput;531private final String _prefix;532private final boolean _printASAP;533534StreamReader(InputStream in, String prefix, boolean printASAP) {535super();536_br = new BufferedReader(new InputStreamReader(in));537_caughtOutput = new StringBuilder();538_prefix = prefix;539_printASAP = printASAP;540start();541}542543@Override544public void run() {545try {546String textRead;547while ((textRead = _br.readLine()) != null) {548if (_printASAP) {549System.out.println(_prefix + textRead);550} else {551_caughtOutput.append(_prefix).append(textRead).append('\n');552}553}554_br.close();555} catch (IOException e) {556// This exception is normal, it happens when the ProcessKiller destroys a process.557} catch (Exception e) {558e.printStackTrace();559}560}561562public String getString() {563return _caughtOutput.toString();564}565}566567private final class ProcessKiller extends Thread {568private final Process _proc;569private final long _procTimeout;570private final String _exeToDebug;571572public ProcessKiller(Process proc, long timeout, String exeToDebug) {573super();574_proc = proc;575_procTimeout = timeout;576_exeToDebug = exeToDebug;577setDaemon(true);578}579580@Override581public synchronized void run() {582final long endTime = System.currentTimeMillis() + _procTimeout;583while (_proc.isAlive()) {584long currTimeout = endTime - System.currentTimeMillis();585if (currTimeout > 0) {586try {587_proc.waitFor(currTimeout, TimeUnit.MILLISECONDS);588} catch (InterruptedException e) {589// ignore590}591} else {592_timedOut = true;593TestSuite.printErrorMessage("ProcessKiller detected a timeout after " + _procTimeout + " milliseconds!");594if (_debugCmdOnTimeout) {595captureCoreForProcess();596}597killTimedOutProcess();598// Dump content from stderr and stdout as soon as possible after a timeout.599dumpVerboseOutput(false);600break;601}602}603}604605private synchronized void captureCoreForProcess() {606if (CORE_COUNT <= 0) {607System.out.printf("INFO: Not capturing core files because %s=%d.\n",608CORE_COUNT_PROPERTY, Integer.valueOf(CORE_COUNT));609return;610}611612try {613int pid = getPID(_proc);614615if ((0 == pid) && !isRiscv) {616System.out.print("INFO: getPID() has failed. ");617System.out.println("'Debug on timeout' is currently only supported on Linux.");618return;619}620621// Make sure we are on linux, otherwise there is no gdb.622if (!isLinux()) {623System.out.print("INFO: The current OS is '" + System.getProperty("os.name") + "'. ");624System.out.println("'Debug on timeout' is currently only supported on Linux.");625return;626}627628// The path to gdb is hard coded because using '/home/j9build/bin/gdb' from the path doesn't always work.629// For example on j9s10z1.torolab.ibm.com it caused problems capturing useful debug information.630String gdbExe = "/usr/bin/gdb";631File gdbExeFile = new File(gdbExe);632633if (false == gdbExeFile.exists()) {634System.out.println("INFO: Cannot find '" + gdbExe + "' using 'gdb' from the path.");635gdbExe = "gdb";636}637638File commandFile = File.createTempFile("debugger", ".txt");639640commandFile.deleteOnExit();641642int count = 1;643// Capture all but the last core, without terminating the process.644for (int i = CORE_COUNT; i > 1; --i) {645captureCoreForProcess(gdbExe, pid, count++, commandFile, false);646System.out.println("INFO: Sleep for " + CORE_SPACING_MILLIS + " millis before next capture.");647Thread.sleep(CORE_SPACING_MILLIS);648}649650// Capture the final core and then terminate the process.651captureCoreForProcess(gdbExe, pid, count, commandFile, true);652} catch (Exception e) {653e.printStackTrace();654}655}656657private void captureCoreForProcess(String gdbexe, int pid, int count,658File commandFile, boolean terminate)659throws InterruptedException, IOException {660// For gdb the commands must be streamed to the debugger661// using a file. Using STDIN will not work because commands662// received before the debugger is fully started are ignored.663try (Writer writer = new PrintWriter(commandFile)) {664writer.write("info shared\n");665writer.write("info registers\n");666writer.write("info thread\n");667writer.write("thread apply all where full\n");668// Must specify a different name, otherwise the cores are overwritten.669writer.write("generate-core-file core." + pid + "." + count + "\n");670671if (!terminate) {672writer.write("detach inferior\n");673}674675writer.write("quit\n");676}677678// Setup the command.679String[] gdbCmd = new String[] {680gdbexe,681"-batch",682"-x",683commandFile.getCanonicalFile().toString(),684_exeToDebug,685String.valueOf(pid)686};687688StringBuilder debugCmd = new StringBuilder("executing");689690for (String arg : gdbCmd) {691debugCmd.append(' ').append(arg);692}693694TestSuite.printErrorMessage(debugCmd.toString());695696Process proc = Runtime.getRuntime().exec(gdbCmd);697698proc.getOutputStream().close();699StreamReader stdout = new StreamReader(proc.getInputStream(), "GDB OUT ", true);700StreamReader stderr = new StreamReader(proc.getErrorStream(), "GDB ERR ", false);701702/*703* Wait for a few minutes for gdb to grab the core on a busy system.704*/705stdout.join(_JAVACORE_TIMEOUT_MILLIS);706// Don't wait long for stderr after waiting for stdout.707stderr.join(SHORT_TIMEOUT_MILLIS);708709/* Call destroy to ensure the process is really dead. At710* this point stdout&err are closed, or _JAVACORE_TIMEOUT_MILLIS711* has expired. Calling destroy has no effect if the process712* has exited cleanly.713*/714proc.destroy();715// Don't wait forever if something went wrong.716proc.waitFor(SHORT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);717718try {719int rc = proc.exitValue();720if (rc != 0) {721System.out.println("INFO: Running '" + gdbexe + "' failed with rc = " + rc);722// Print the error stream only if gdb failed.723System.out.println(stderr.getString());724}725} catch (IllegalThreadStateException e) {726System.out.println("INFO: Running '" + gdbexe + "' failed to complete.");727// Print the error stream only if gdb failed.728System.out.println(stderr.getString());729}730}731732private synchronized void killTimedOutProcess() {733// If we can send a -QUIT signal to the process, send one734try {735int pid = getPID(_proc);736if (0 != pid) {737TestSuite.printErrorMessage("executing kill -ABRT " + pid);738Process proc = Runtime.getRuntime().exec("kill -ABRT " + pid);739// waiting for kill740proc.waitFor();741TestSuite.printErrorMessage("kill -ABRT signal sent");742743// Waiting for the process to quit744long startTime = System.currentTimeMillis();745while ((System.currentTimeMillis() - startTime) < _JAVACORE_TIMEOUT_MILLIS) {746try {747_proc.exitValue();748} catch (IllegalThreadStateException e) {749// process not done yet750Thread.sleep(100);751continue;752}753TestSuite.printErrorMessage("ABRT completed");754return;755}756TestSuite.printErrorMessage("ABRT timed out");757/* When ABRT times out, try to kill the process with kill -9 to make sure it doesn't stop the rest */758TestSuite.printErrorMessage("executing kill -9 " + pid);759Process procKill9 = Runtime.getRuntime().exec("kill -9 " + pid);760procKill9.waitFor();761TestSuite.printErrorMessage("kill -9 signal sent");762}763if (_proc.isAlive()) {764TestSuite.printErrorMessage("ProcessKiller destroy test process after timeout");765_proc.destroy();766}767} catch (IOException e) {768// FIXME769} catch (Exception e) {770// FIXME771}772}773}774775public void destroy() {776synchronized (_standardOutput) {777_standardOutput.setLength(0);778}779synchronized (_errorOutput) {780_errorOutput.setLength(0);781}782}783}784785786