/*******************************************************************************
* Copyright (c) 2012 VMWare, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VMWare, Inc. - initial API and implementation
*******************************************************************************/
package org.grails.ide.eclipse.longrunning.test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.LinkedList;
import org.grails.ide.eclipse.longrunning.Console;
import org.grails.ide.eclipse.longrunning.ConsoleProvider;
import org.grails.ide.eclipse.longrunning.client.GrailsCommandExecution;
import org.grails.ide.eclipse.runtime.shared.longrunning.Pipe;
import junit.framework.Assert;
/**
* Used to replace the 'console UI' for testing, so we can control what input gets sent to
* a Grails command from the test.
* <p>
* The console is driven by a a list of "questions + answer" pairs. When the "question" is seen
* in the input, we put the answer on the output.
*/
public class TestConsoleProvider extends ConsoleProvider {
/**
* Thread that reads the output from a Grails command and answers questions it asked
* by putting an answer on the 'keyboard'.
*/
public class AnswerProvider extends Thread {
private PrintStream keyboard;
private BufferedReader grails;
char[] readBuffer = new char[2048];
StringBuilder currentLine = new StringBuilder();
public AnswerProvider(PrintStream keyboard, InputStream grails) {
this.keyboard = keyboard;
this.grails = new BufferedReader(new InputStreamReader(grails));
}
@Override
public void run() {
try {
boolean failedToAnswer = false;
while (!qas.isEmpty() && !failedToAnswer) {
QuestionAnswer q = qas.getFirst();
if (answerQuestion(q)) {
qas.removeFirst();
} else {
failedToAnswer = true;
}
}
} catch (IOException e) {
}
}
private boolean answerQuestion(QuestionAnswer q) throws IOException {
boolean answered = false;
String line;
do {
line = readSome();
} while (line!=null && !line.contains(q.question));
if (line!=null) {
keyboard.println(q.answer);
answered = true;
}
return answered;
}
/**
* Read some input and return the text read so far on the currentline.
* The guarantees this function should provide for it to be 'correct' is
* - should block until at least one character can be read or EOF
* - should stop as soon as the first eol is returned.
* - should return *before* an eol is returned if input is blocked.
*
* The current implementation is very simplistic as it only reads at most one
* character at a time. This meets all the 'correctness' requirements, but is very inefficient
* since it will cause the question answerer to check whether the input text after
* each character it reads. A more efficient implementation could read multiple
* chars as long as no newline is seen and the input is not blocked.
*/
private String readSome() throws IOException {
//TODO: we could try reading more than one character at a time, but it is harder to
// implement that correctly.
int c = grails.read();
boolean eol = c==-1 || c=='\r' || c=='\n';
if (c==-1 && "".equals(currentLine.toString())) {
return null; //EOF
}
try {
if (!eol) {
currentLine.append((char)c);
}
return currentLine.toString();
} finally {
if (eol) {
currentLine = new StringBuilder();
}
}
}
}
boolean used = false;
LinkedList<QuestionAnswer> qas = new LinkedList<QuestionAnswer>();
public TestConsoleProvider(QuestionAnswer... qas) {
this.qas = new LinkedList<QuestionAnswer>();
for (QuestionAnswer it : qas) {
this.qas.add(it);
}
}
public void assertAllQuestionsAnswered() {
if (qas.isEmpty()) return;
StringBuffer unansweredQuestions = new StringBuffer("Unanswered questions:\n");
for (QuestionAnswer q : qas) {
unansweredQuestions.append(q);
unansweredQuestions.append('\n');
}
Assert.fail(unansweredQuestions.toString());
}
@Override
public Console getConsole(String title, GrailsCommandExecution execution) {
Pipe grailsOut; // pipe that receives output from the grails command
Pipe grailsErr; // pipe that receives error output from the grails command
Pipe keyboard; // pipe that receives input from the keyboard
try {
grailsOut = new Pipe();
grailsErr = grailsOut;
keyboard = new Pipe();
} catch (IOException e) {
throw new Error(e);
}
LongRunningGrailsTest.assertFalse("This TestConsoleProvider can only make one test console", used);
used = true;
new AnswerProvider(keyboard.getOutputStream(), grailsOut.getInputStream()).start();
return Console.make(
keyboard.getInputStream(),
grailsOut.getOutputStream(),
grailsErr.getOutputStream()
);
}
}