/*
* JBoss, Home of Professional Open Source.
* Copyright 2015, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.test.integration.management.cli;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import static org.junit.Assert.fail;
/**
* Tool for creating CLI processes for testing
*
* @author Joe Wertz ewertz@redhat.com
*/
public class CliProcessWrapper extends CliProcessBuilder {
public CliProcessWrapper(){
cliProcessWrapper = this;
}
private Process cliProcess;
private volatile StringBuffer cliOutputBuffer = new StringBuffer();
private BufferedWriter bufferedWriter = null;
/**
* Clear current output buffer
*/
public void clearOutput(){
cliOutputBuffer = new StringBuffer();
}
/**
* Get existing output buffer.
* @return CLI Output
*/
public String getOutput(){
String output = cliOutputBuffer.toString();
if(output.contains("\u001B[0G\u001B[2K")){
return output.replace("\u001B[0G\u001B[2K", "");
}
return output;
}
/**
* Start a CLI process with the current options and
* wait for the output to show the startup has completed
*/
public void executeInteractive(){
executeInteractive(null);
}
/**
* Start a CLI process with the current options and
* wait for the output to show the startup has completed
*
* @param prompt - Expected prompt after startup
* @return Returns true if the expected prompt is found. False if the timeout is reached.
*/
public boolean executeInteractive(String prompt) {
if( cliProcess != null ){
throw new RuntimeException("Process Already Started");
}
cliProcess = createProcess();
new Thread(new CliResultsReader(cliProcess.getInputStream())).start();
new Thread(new CliResultsReader(cliProcess.getErrorStream())).start();
bufferedWriter = new BufferedWriter(new OutputStreamWriter(cliProcess.getOutputStream(), StandardCharsets.UTF_8));
return waitForPrompt(prompt);
}
/**
* Run a CLI process with the current options and
* wait for the process to end. If not ended within the timeout,
* the process is destroyed.
*
* @return All output from the process after completion.
*/
public String executeNonInteractive() throws IOException {
if( cliProcess != null ){
throw new RuntimeException("Process Already Started");
}
cliProcess = createProcess();
new Thread(new CliResultsReader(cliProcess.getInputStream())).start();
new Thread(new CliResultsReader(cliProcess.getErrorStream())).start();
waitForClose();
return cliOutputBuffer.toString();
}
/**
* Push string to CLI input and wait for results.
*
* @param string - String pushed to CLI input
*/
public void pushLineAndWaitForResults(String string) throws IOException {
pushLineAndWaitForResults(string, null);
}
/**
* Push string to CLI input and wait for results.
*
* @param string - String pushed to CLI input
* @param prompt - Expected prompt after line is processed
* @return Returns true if the expected prompt is found. False if the timeout is reached.
*/
public boolean pushLineAndWaitForResults(String string, String prompt) throws IOException {
pushToInput(string);
return waitForPrompt(prompt);
}
/**
* Push string to CLI input and wait for process to end
*
* @param string - String pushed to CLI input
* @return Whether the process closed within the timeout or was forced.
*/
public boolean pushLineAndWaitForClose(String string) throws IOException {
pushToInput(string);
return waitForClose();
}
/**
* Push ctrl-c to CLI input and wait for process to end.
* Separate from normal 'push' because no newLine can be pushed after the ctrl-c
*
* @return Whether the process closed within the timeout or was forced.
*/
public boolean ctrlCAndWaitForClose() throws IOException {
try {
if(cliProcess != null){
bufferedWriter.write('\u0003');
bufferedWriter.flush();
}
} catch (IOException e) {
fail("Failed to push ctrl-c char, '\\u0003', to CLI input: " + e.getLocalizedMessage());
}
return waitForClose();
}
/**
* Passthrough method to get the process exit value
*
* @return process exit value
*/
public int getProcessExitValue() {
return cliProcess.exitValue();
}
/**
* Passthrough method to get the process exit value
*
* @return process exit value
*/
public void destroyProcess() {
cliProcess.destroy();
}
/**
* Returns the last line in the current output buffer.
* Unless the buffer has been cleared, this should be an active prompt waiting for user input.
*
* @return Last line in the current output buffer
*/
public String getCurrentPrompt(){
if(cliOutputBuffer.toString().contains("\n")) {
return cliOutputBuffer.toString().substring(cliOutputBuffer.toString().lastIndexOf("\n")+1);
}else{
return cliOutputBuffer.toString();
}
}
private void pushToInput(String line) {
try {
if(cliProcess != null){
bufferedWriter.write(line);
bufferedWriter.newLine();
bufferedWriter.flush();
}
} catch (IOException e) {
fail("Failed to push command '" + line + "' to CLI input: " + e.getLocalizedMessage());
}
}
private int resultTimeout = 10000;
private int resultInterval = 100;
private boolean waitForPrompt(String prompt) {
boolean success = false;
boolean wait = true;
int waitingTime = 0;
while(wait) {
try {
Thread.sleep(resultInterval);
} catch (InterruptedException e) {
fail("Interrupted");
}
waitingTime += resultInterval;
// If the timeout is reached, return regardless.
if (waitingTime > resultTimeout) {
wait = false;
}
// If the expected prompt is not in the output, keep waiting
if (wait && outputHasPrompt(prompt)){
success = true;
wait = false;
}
}
return success;
}
private boolean waitForClose() throws IOException {
if( bufferedWriter != null ){
bufferedWriter.close();
}
boolean closed = false;
int waitingTime = 0;
boolean wait = true;
while(wait) {
try {
Thread.sleep(resultInterval);
} catch (InterruptedException e) {
fail("Interrupted");
}
waitingTime += resultInterval;
try{
cliProcess.exitValue();
wait = false;
closed = true;
} catch(IllegalThreadStateException e) {
// Windows check...
if(cliOutputBuffer.toString().endsWith("Press any key to continue . . . ")){
pushLineAndWaitForClose("l");
}
// cli still working
}
// If the timeout is reached, destroy the process and return false
if (waitingTime > resultTimeout) {
cliProcess.destroy();
wait = false;
}
}
return closed;
}
private boolean outputHasPrompt(String prompt){
if(cliOutputBuffer.toString().contains("\n")) {
if (prompt == null) {
return cliOutputBuffer.toString().substring(cliOutputBuffer.toString().lastIndexOf("\n")+1).matches(".*[\\[].*[\\]].*");
} else {
return cliOutputBuffer.toString().substring(cliOutputBuffer.toString().lastIndexOf("\n")).contains(prompt);
}
}else{
if (prompt == null) {
return cliOutputBuffer.toString().matches(".*[\\[].*[\\]].*");
} else {
return cliOutputBuffer.toString().contains(prompt);
}
}
}
private class CliResultsReader implements Runnable {
InputStream cliStream = null;
public CliResultsReader(InputStream inputStream){
cliStream = inputStream;
}
@Override
public void run() {
try(java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(cliStream, StandardCharsets.UTF_8))){
int intCharacter = 0;
Character character;
// While the next character is a valid character and isn't null
while ((intCharacter = br.read()) != -1 && (character = (char)intCharacter) != null) {
cliOutputBuffer.append(character);
}
} catch (IOException e) {
fail("Failed to read process output or error streams: " + e.getLocalizedMessage());
}
}
}
}