package com.sandwich.koan.path;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import com.sandwich.koan.Koan;
import com.sandwich.koan.KoanClassLoader;
import com.sandwich.koan.KoanIncompleteException;
import com.sandwich.koan.TestUtils;
import com.sandwich.koan.TestUtils.ArgRunner;
import com.sandwich.koan.constant.ArgumentType;
import com.sandwich.koan.path.PathToEnlightenment.Path;
import com.sandwich.koan.path.xmltransformation.FakeXmlToPathTransformer;
import com.sandwich.koan.path.xmltransformation.KoanElementAttributes;
import com.sandwich.koan.runner.RunKoans;
import com.sandwich.koan.ui.ConsolePresenter;
import com.sandwich.koan.ui.SuitePresenter;
import com.sandwich.koan.util.ApplicationUtils;
import com.sandwich.koan.util.ApplicationUtils.SuitePresenterFactory;
import com.sandwich.util.io.KoanSuiteCompilationListener;
import com.sandwich.util.io.classloader.DynamicClassLoader;
import com.sandwich.util.io.directories.DirectoryManager;
import com.sandwich.util.io.directories.ProductionExecutedFromTestsDirectories;
import com.sandwich.util.io.directories.UnitTestDirectories;
public abstract class CommandLineTestCase {
private PrintStream out;
private PrintStream err;
private ByteArrayOutputStream outBytes;
private ByteArrayOutputStream errBytes;
@BeforeClass
public static void deleteFileHashesAfterJVMExit(){
new File(DirectoryManager.getDataFile()).deleteOnExit();
}
@Before
public void setUp() throws Exception {
DirectoryManager.setDirectorySet(new UnitTestDirectories());
resetClassLoader();
out = System.out;
err = System.err;
TestUtils.setValue("behavior", new RunKoans(), ArgumentType.RUN_KOANS);
PathToEnlightenment.xmlToPathTransformer = new FakeXmlToPathTransformer();
PathToEnlightenment.theWay = PathToEnlightenment.createPath();
clearSystemOutputs();
stubPresenter(new ConsolePresenter());
}
@After
public void tearDown() throws Exception {
DirectoryManager.setDirectorySet(new ProductionExecutedFromTestsDirectories());
setRealPath();
if(out != null){
System.setOut(out);
}
if(err != null){
System.setErr(err);
}
resetClassLoader();
}
protected void setRealPath(){
PathToEnlightenment.xmlToPathTransformer = null;
PathToEnlightenment.theWay = PathToEnlightenment.createPath();
}
protected void stubPresenter(final SuitePresenter presenter)
throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Field f = ApplicationUtils.class.getDeclaredField("suitePresenterFactory");
boolean isAccessible = f.isAccessible();
try {
f.setAccessible(true);
f.set(ApplicationUtils.class, new SuitePresenterFactory(){
@Override public SuitePresenter create(){
return presenter;
}
});
} finally {
f.setAccessible(isAccessible);
}
}
protected Path stubAllKoans(String packageName, List<String> path){
Path oldKoans = PathToEnlightenment.getPathToEnlightenment();
Map<String, Map<String, KoanElementAttributes>> tempSuitesAndMethods =
new LinkedHashMap<String, Map<String, KoanElementAttributes>>();
DynamicClassLoader loader = KoanClassLoader.getInstance();
for(String suite : path){
Map<String, KoanElementAttributes> methodsByName = new LinkedHashMap<String, KoanElementAttributes>();
KoanSuiteCompilationListener listener = new KoanSuiteCompilationListener();
for(Method m : loader.loadClass(suite, listener).getMethods()){
if(m.getAnnotation(Koan.class) != null){
methodsByName.put(m.getName(), new KoanElementAttributes(m.getName(), "", m.getDeclaringClass().getName()));
}
}
tempSuitesAndMethods.put(suite, methodsByName);
}
Map<String, Map<String, Map<String, KoanElementAttributes>>> stubbedPath =
new LinkedHashMap<String, Map<String, Map<String, KoanElementAttributes>>>();
stubbedPath.put(packageName, tempSuitesAndMethods);
PathToEnlightenment.theWay = new Path(null,stubbedPath);
return oldKoans;
}
public Path stubAllKoans(List<?> path){
List<String> classes = new ArrayList<String>();
for(Object o : path){
String className;
if(o instanceof Class<?>){
className = ((Class<?>)o).getName();
}else{
className = o.getClass().getName();
}
classes.add(className);
}
return stubAllKoans("Test", classes);
}
public void clearSystemOutputs(){
outBytes = new ByteArrayOutputStream();
errBytes = new ByteArrayOutputStream();
System.setOut(new PrintStream(outBytes));
System.setErr(new PrintStream(errBytes));
}
public void assertSystemOutEquals(String expectation){
assertEquals(expectation, outBytes);
}
public void assertSystemErrEquals(String expectation){
assertEquals(expectation, errBytes);
}
private void assertEquals(String expectation, ByteArrayOutputStream bytes){
expectation = expectation == null ? "" : expectation;
String bytesString = String.valueOf(bytes);
if(!expectation.equals(bytesString)){
throw new KoanIncompleteException("expected: <"+expectation+"> but found: <"+bytesString+">");
}
}
public void assertSystemOutContains(String expectation){
assertContains(true, expectation, outBytes);
}
public void assertSystemOutDoesntContain(String expectation){
assertContains(false, expectation, outBytes);
}
public void assertSystemErrContains(String expectation){
assertContains(true, expectation, errBytes);
}
public void assertSystemErrDoesntContain(String expectation){
assertContains(false, expectation, errBytes);
}
private void assertContains(boolean assertContains, String expectation, ByteArrayOutputStream bytes) {
String consoleOutput = String.valueOf(bytes.toString());
boolean containsTheSubstring = consoleOutput.contains(expectation);
if(assertContains && !containsTheSubstring || !assertContains && containsTheSubstring){
throw new KoanIncompleteException(new StringBuilder(
"<").append(
expectation).append(
"> ").append(
(assertContains ? "wasn't" : "was")).append(
" found in: " ).append(
"<").append(
consoleOutput).append(
">").toString());
}
}
public void assertSystemOutLineEquals(final int lineNumber, final String lineText){
assertLineEquals(lineNumber, lineText, false, outBytes);
}
public void assertSystemErrLineEquals(final int lineNumber, final String lineText){
assertLineEquals(lineNumber, lineText, false, errBytes);
}
public void assertLineEquals(final int lineNumber, final String lineText, final boolean trimLinesString, ByteArrayOutputStream bytes) {
final int[] onLine = new int[]{0};
final boolean[] found = new boolean[]{false};
String bytesString = String.valueOf(bytes);
TestUtils.forEachLine(bytesString, new ArgRunner<String>(){
public void run(String s){
if(onLine[0] == lineNumber){
if(trimLinesString){
s = s.trim();
}
Assert.assertEquals(lineText, s);
found[0] = true;
}
onLine[0]++;
}
});
if(!found[0]){
throw new KoanIncompleteException(lineText+" was expected, but not found in: "+bytesString);
}
}
public void resetClassLoader() {
Constructor<KoanClassLoader> constructor = null;
Field fileMonitorField = null;
boolean consWasAccessible = false;
boolean monitorWasAccessible = false;
try {
constructor = KoanClassLoader.class.getDeclaredConstructor();
consWasAccessible = constructor.isAccessible();
constructor.setAccessible(true);
fileMonitorField = KoanClassLoader.class.getDeclaredField("instance");
monitorWasAccessible = fileMonitorField.isAccessible();
fileMonitorField.setAccessible(true);
fileMonitorField.set(KoanClassLoader.class, constructor.newInstance());
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} finally {
if(constructor != null){
constructor.setAccessible(consWasAccessible);
}
if(fileMonitorField != null){
fileMonitorField.setAccessible(monitorWasAccessible);
}
}
}
}