/******************************************************************************* * Copyright (c) 2015 IBM Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ package com.ibm.ws.lars.testutils; import static org.junit.Assert.fail; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.util.List; /** * Launches a program, providing options for checking its output and return code. * <p> * Note there is no timeout and the output from the program is stored in a buffer so it is not * suitable for testing programs with a very large output or which may hang. */ public class TestProcess { private static final String NOT_YET_RUN = "Cannot check output before program has been run"; private final ProcessBuilder processBuilder; private Process process; private String systemInput; private StringBuilder outputBuilder; private StringBuilder errorBuilder; private Integer returnCode; /** * Set up the TestProcess * * @param commandLine the command line (program followed by arguments) to execute */ public TestProcess(List<String> commandLine) { processBuilder = new ProcessBuilder(commandLine); returnCode = null; } /** * Set up the TestProcess * * @param commandLine the command line (program followed by arguments) to execute * @param systemInput the system input to be read during execution of the process */ public TestProcess(List<String> commandLine, String systemInput) { this(commandLine); this.systemInput = systemInput; } /** * Run the program passed in the constructor and wait for it to finish * * @throws IOException if any exceptions are thrown when starting or reading from the process */ public void run() throws IOException { process = processBuilder.start(); outputBuilder = new StringBuilder(); errorBuilder = new StringBuilder(); if (systemInput != null) { OutputStreamWriter osw = new OutputStreamWriter(process.getOutputStream()); osw.write(systemInput); osw.flush(); } ProcessStreamReader outReader = new ProcessStreamReader(process.getInputStream(), outputBuilder); new Thread(outReader).start(); ProcessStreamReader errReader = new ProcessStreamReader(process.getErrorStream(), errorBuilder); new Thread(errReader).start(); while (true) { try { returnCode = process.waitFor(); break; } catch (InterruptedException e) { } } if (outReader.getException() != null) { throw outReader.getException(); } if (errReader.getException() != null) { throw errReader.getException(); } } /** * Assert that the program wrote the given string to stdout * <p> * This can only be called after calling <code>run</code> * * @param string the string to look for */ public void assertOutputContains(String string) { if (returnCode == null) { fail(NOT_YET_RUN); } if (outputBuilder.indexOf(string) == -1) { fail("Program output did not contain " + string + "\n" + "Actual output:\n" + outputBuilder.toString()); } } /** * Assert that the program wrote the given string to stderr * <p> * This can only be called after calling <code>run</code> * * @param string the string to look for */ public void assertErrorContains(String string) { if (returnCode == null) { fail(NOT_YET_RUN); } if (errorBuilder.indexOf(string) == -1) { fail("Program error output did not contain " + string + "\n" + "Actual output:\n" + errorBuilder.toString()); } } public String getOutput() { if (returnCode == null) { fail(NOT_YET_RUN); } return outputBuilder.toString(); } /** * Assert that the program exited with the given return code * <p> * This can only be called after calling <code>run</code> * * @param returnCode the return code */ public void assertReturnCode(int returnCode) { if (this.returnCode == null) { fail("Cannot check return code before program has been run"); } if (this.returnCode.intValue() != returnCode) { fail("Incorrect return code, expected <" + returnCode + "> but was <" + this.returnCode + ">\n" + "Program output:\n" + outputBuilder.toString() + "\n" + "Program error output:\n" + errorBuilder.toString() + "\n"); } } /** * Handles reading from an input stream into a string builder */ private class ProcessStreamReader implements Runnable { private final Reader in; private final StringBuilder out; private IOException ex; public ProcessStreamReader(InputStream in, StringBuilder out) { this.in = new BufferedReader(new InputStreamReader(in)); this.out = out; ex = null; } /** {@inheritDoc} */ @Override public void run() { try { char[] buffer = new char[1024]; int length; while ((length = in.read(buffer)) != -1) { out.append(buffer, 0, length); } } catch (IOException e) { this.ex = e; } } public IOException getException() { return ex; } } }