// : com:bruceeckel:simpletest:Test.java
//Simple utility for testing program output. Intercepts
//System.out to print both to the console and a buffer.
//From 'Thinking in Java, 3rd ed.' (c) Bruce Eckel 2002
//www.BruceEckel.com. See copyright notice in CopyRight.txt.
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.regex.Pattern;
public class Alias2 {
private static Test monitor = new Test();
private int i;
public Alias2(int ii) { i = ii; }
public static void f(Alias2 reference) { reference.i++; }
public static void main(String[] args) {
Alias2 x = new Alias2(7);
System.out.println("x: " + x.i);
System.out.println("Calling f(x)");
f(x);
System.out.println("x: " + x.i);
monitor.expect(new String[] {
"x: 7",
"Calling f(x)",
"x: 8"
});
}
} ///:~
class Test {
// Bit-shifted so they can be added together:
public static final int EXACT = 1 << 0, // Lines must match exactly
AT_LEAST = 1 << 1, // Must be at least these lines
IGNORE_ORDER = 1 << 2, // Ignore line order
WAIT = 1 << 3; // Delay until all lines are output
private String className;
private TestStream testStream;
public Test() {
// Discover the name of the class this
// object was created within:
className = new Throwable().getStackTrace()[1].getClassName();
testStream = new TestStream(className);
}
public static List fileToList(String fname) {
ArrayList list = new ArrayList();
try {
BufferedReader in = new BufferedReader(new FileReader(fname));
try {
String line;
while ((line = in.readLine()) != null) {
if (fname.endsWith(".txt"))
list.add(line);
else
list.add(new TestExpression(line));
}
} finally {
in.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return list;
}
public static List arrayToList(Object[] array) {
List l = new ArrayList();
for (int i = 0; i < array.length; i++) {
if (array[i] instanceof TestExpression) {
TestExpression re = (TestExpression) array[i];
for (int j = 0; j < re.getNumber(); j++)
l.add(re);
} else {
l.add(new TestExpression(array[i].toString()));
}
}
return l;
}
public void expect(Object[] exp, int flags) {
if ((flags & WAIT) != 0)
while (testStream.numOfLines < exp.length) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
List output = fileToList(className + "Output.txt");
if ((flags & IGNORE_ORDER) == IGNORE_ORDER)
OutputVerifier.verifyIgnoreOrder(output, exp);
else if ((flags & AT_LEAST) == AT_LEAST)
OutputVerifier.verifyAtLeast(output, arrayToList(exp));
else
OutputVerifier.verify(output, arrayToList(exp));
// Clean up the output file - see c06:Detergent.java
testStream.openOutputFile();
}
public void expect(Object[] expected) {
expect(expected, EXACT);
}
public void expect(Object[] expectFirst, String fname, int flags) {
List expected = fileToList(fname);
for (int i = 0; i < expectFirst.length; i++)
expected.add(i, expectFirst[i]);
expect(expected.toArray(), flags);
}
public void expect(Object[] expectFirst, String fname) {
expect(expectFirst, fname, EXACT);
}
public void expect(String fname) {
expect(new Object[] {}, fname, EXACT);
}
} ///:~
class TestExpression implements Comparable {
private Pattern p;
private String expression;
private boolean isRegEx;
// Default to only one instance of this expression:
private int duplicates = 1;
public TestExpression(String s) {
this.expression = s;
if (expression.startsWith("%% ")) {
this.isRegEx = true;
expression = expression.substring(3);
this.p = Pattern.compile(expression);
}
}
// For duplicate instances:
public TestExpression(String s, int duplicates) {
this(s);
this.duplicates = duplicates;
}
public String toString() {
if (isRegEx)
return p.pattern();
return expression;
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (isRegEx)
return (compareTo(obj) == 0);
return expression.equals(obj.toString());
}
public int compareTo(Object obj) {
if ((isRegEx) && (p.matcher(obj.toString()).matches()))
return 0;
return expression.compareTo(obj.toString());
}
public int getNumber() {
return duplicates;
}
public String getExpression() {
return expression;
}
public boolean isRegEx() {
return isRegEx;
}
} ///:~
class TestStream extends PrintStream {
protected int numOfLines;
private PrintStream console = System.out, err = System.err, fout;
// To store lines sent to System.out or err
private InputStream stdin;
private String className;
public TestStream(String className) {
super(System.out, true); // Autoflush
System.setOut(this);
System.setErr(this);
stdin = System.in; // Save to restore in dispose()
// Replace the default version with one that
// automatically produces input on demand:
System.setIn(new BufferedInputStream(new InputStream() {
char[] input = ("test\n").toCharArray();
int index = 0;
public int read() {
return (int) input[index = (index + 1) % input.length];
}
}));
this.className = className;
openOutputFile();
}
// public PrintStream getConsole() { return console; }
public void dispose() {
System.setOut(console);
System.setErr(err);
System.setIn(stdin);
}
// This will write over an old Output.txt file:
public void openOutputFile() {
try {
fout = new PrintStream(new FileOutputStream(new File(className
+ "Output.txt")));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
// Override all possible print/println methods to send
// intercepted console output to both the console and
// the Output.txt file:
public void print(boolean x) {
console.print(x);
fout.print(x);
}
public void println(boolean x) {
numOfLines++;
console.println(x);
fout.println(x);
}
public void print(char x) {
console.print(x);
fout.print(x);
}
public void println(char x) {
numOfLines++;
console.println(x);
fout.println(x);
}
public void print(int x) {
console.print(x);
fout.print(x);
}
public void println(int x) {
numOfLines++;
console.println(x);
fout.println(x);
}
public void print(long x) {
console.print(x);
fout.print(x);
}
public void println(long x) {
numOfLines++;
console.println(x);
fout.println(x);
}
public void print(float x) {
console.print(x);
fout.print(x);
}
public void println(float x) {
numOfLines++;
console.println(x);
fout.println(x);
}
public void print(double x) {
console.print(x);
fout.print(x);
}
public void println(double x) {
numOfLines++;
console.println(x);
fout.println(x);
}
public void print(char[] x) {
console.print(x);
fout.print(x);
}
public void println(char[] x) {
numOfLines++;
console.println(x);
fout.println(x);
}
public void print(String x) {
console.print(x);
fout.print(x);
}
public void println(String x) {
numOfLines++;
console.println(x);
fout.println(x);
}
public void print(Object x) {
console.print(x);
fout.print(x);
}
public void println(Object x) {
numOfLines++;
console.println(x);
fout.println(x);
}
public void println() {
if (false)
console.print("println");
numOfLines++;
console.println();
fout.println();
}
public void write(byte[] buffer, int offset, int length) {
console.write(buffer, offset, length);
fout.write(buffer, offset, length);
}
public void write(int b) {
console.write(b);
fout.write(b);
}
} ///:~
class OutputVerifier {
private static void verifyLength(int output, int expected, int compare) {
if ((compare == Test.EXACT && expected != output)
|| (compare == Test.AT_LEAST && output < expected))
throw new NumOfLinesException(expected, output);
}
public static void verify(List output, List expected) {
verifyLength(output.size(), expected.size(), Test.EXACT);
if (!expected.equals(output)) {
//find the line of mismatch
ListIterator it1 = expected.listIterator();
ListIterator it2 = output.listIterator();
while (it1.hasNext() && it2.hasNext()
&& it1.next().equals(it2.next()))
;
throw new LineMismatchException(it1.nextIndex(), it1.previous()
.toString(), it2.previous().toString());
}
}
public static void verifyIgnoreOrder(List output, Object[] expected) {
verifyLength(expected.length, output.size(), Test.EXACT);
if (!(expected instanceof String[]))
throw new RuntimeException(
"IGNORE_ORDER only works with String objects");
String[] out = new String[output.size()];
Iterator it = output.iterator();
for (int i = 0; i < out.length; i++)
out[i] = it.next().toString();
Arrays.sort(out);
Arrays.sort(expected);
int i = 0;
if (!Arrays.equals(expected, out)) {
while (expected[i].equals(out[i])) {
i++;
}
throw new SimpleTestException(((String) out[i]).compareTo(expected[i]) < 0 ? "output: <" + out[i] + ">"
: "expected: <" + expected[i] + ">");
}
}
public static void verifyAtLeast(List output, List expected) {
verifyLength(output.size(), expected.size(), Test.AT_LEAST);
if (!output.containsAll(expected)) {
ListIterator it = expected.listIterator();
while (output.contains(it.next())) {
}
throw new SimpleTestException("expected: <"
+ it.previous().toString() + ">");
}
}
} ///:~
class SimpleTestException extends RuntimeException {
public SimpleTestException(String msg) {
super(msg);
}
} ///:~
class NumOfLinesException extends SimpleTestException {
public NumOfLinesException(int exp, int out) {
super("Number of lines of output and "
+ "expected output did not match.\n" + "expected: <" + exp
+ ">\n" + "output: <" + out + "> lines)");
}
} ///:~
class LineMismatchException extends SimpleTestException {
public LineMismatchException(int lineNum, String expected, String output) {
super("line " + lineNum + " of output did not match expected output\n"
+ "expected: <" + expected + ">\n" + "output: <" + output
+ ">");
}
} ///:~