package fitnesse.slim;

import java.io.*;

import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

public class StackTraceEnricherTest {
  private static final String JUNIT_JAR_PATTERN = "[junit";
  private static final String RT_JAR = "rt.jar";
  private static final String NON_EXISTING_FILE = "/this/should/not/exist/at/all/sadfbas";

  private Throwable exception;
  private String javaVersion;
  private StackTraceEnricher enricher;

  @Before
  public void setUp() {
    exception = createIoException();
    javaVersion = getJavaVersion();
    enricher = new StackTraceEnricher();
  }

  private Throwable createIoException() {
    Throwable exception = null;
    try {
      new FileInputStream(new File(NON_EXISTING_FILE));
      fail("Managed to find a file that shouldn't exist " + NON_EXISTING_FILE);
    } catch (IOException ioe) {
      if (ioe.getStackTrace() == null || ioe.getStackTrace().length == 0) {
        ioe.fillInStackTrace();
      }
      exception = ioe;
    }
    return exception;
  }

  private String getJavaVersion() {
    String version;
    String fullVersion = System.getProperty("java.runtime.version", "Failed to read Java RT version");
    if (fullVersion.contains("-")) {
      version = fullVersion.substring(0, fullVersion.indexOf('-'));
    } else {
      version = fullVersion;
    }
    return version;
  }

  private StackTraceElement getJavaLangStackTraceElement(Throwable exception) {
    StackTraceElement javaLangElement = null;
    for (StackTraceElement element : exception.getStackTrace()) {
      if (element.getClassName().startsWith("java.lang")) {
        javaLangElement = element;
        break;
      }
    }
    if (javaLangElement == null) {
      fail("Unable to find a java.lang class in the stack trace.");
    }
    return javaLangElement;
  }

  @Test
  public void shouldWriteEnrichedStackTraceToWriter() throws Exception {
    StringWriter writer = new StringWriter();
    enricher.printStackTrace(exception, writer);
    assertTrue("JUnit jar " + JUNIT_JAR_PATTERN + " not found in stack trace written to writer",
        writer.toString().contains(JUNIT_JAR_PATTERN));
  }

  @Test
  public void shouldWriteEnrichedStackTraceToOutputStream() throws Exception {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    enricher.printStackTrace(exception, outputStream);
    String output = new String(outputStream.toByteArray());
    assertTrue("JUnit jar " + JUNIT_JAR_PATTERN + " not found in stack trace written to output stream",
        output.contains(JUNIT_JAR_PATTERN));
  }

  @Test
  public void shouldReturnEnrichedStackTraceAsString() {
    assertTrue("JUnit jar " + JUNIT_JAR_PATTERN + " not found in stack trace as String",
        enricher.getStackTraceAsString(exception).contains(JUNIT_JAR_PATTERN));
  }

  @Test
  public void shouldParseRtJar() {
    assertTrue("Java RT jar not properly determined.", enricher.getStackTraceAsString(exception).contains(RT_JAR));
  }

  @Test
  public void shouldAddVersionWhenAvailable() {
    assertTrue("Version not added for rt.jar", enricher.getStackTraceAsString(exception).contains(RT_JAR + ":" +
        javaVersion));
  }

  @Test
  public void shouldGetVersionForClassInJarWithVersion() {
    assertEquals("Version not retrieved for java.lang.reflect.Method",
        enricher.getVersion(java.lang.reflect.Method.class), javaVersion);
  }

  @Test
  public void shouldGetVersionForStackTraceElementInJarWithVersion() {
    StackTraceElement javaLangElement = getJavaLangStackTraceElement(exception);
    assertEquals("Version not retrieved for " + javaLangElement.getClassName(), enricher.getVersion(javaLangElement),
        javaVersion);
  }

  @Test
  public void shouldGetLocationForClassInJarWithVersion() {
    assertEquals("Location not retrieved for java.lang.reflect.Method",
        enricher.getLocation(java.lang.reflect.Method.class), RT_JAR);
  }

  @Test
  public void shouldGetLocationForStackTraceElementInJarWithVersion() {
    StackTraceElement javaLangElement = getJavaLangStackTraceElement(exception);
    assertEquals("Version not retrieved for " + javaLangElement.getClassName(), enricher.getLocation(javaLangElement),
        RT_JAR);
  }

  @Test
  public void shouldParseDirectories() {
    String classPath = System.getProperty("java.class.path");
    classPath = classPath.replace('\\', '/');
    String testLocation = enricher.getLocation(this.getClass());
    if (!testLocation.contains(".jar")) {
      testLocation = testLocation.replace('\\', '/');
      if (testLocation.startsWith("file:/")) {
        testLocation = testLocation.substring("file:/".length());
      }
      if (testLocation.endsWith("/")) {
        testLocation = testLocation.substring(0, testLocation.length() - 1);
      }
      assertTrue("Location of unit test (" + testLocation + ") not found on the classpath.",
          classPath.contains(testLocation));
    } else {
      // TODO Find a way to test this if the unit test is executed from a jar.
      System.err.println("Unit test executed from jar file, no stable method available for testing the parsing of " +
          "directory locations.");
    }
  }

  @Test
  public void shouldWriteToSystemErrByDefault() {
    PrintStream originalErrStream = System.err;
    try {
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      System.setErr(new PrintStream(out));
      enricher.printStackTrace(exception);
      String output = new String(out.toByteArray());
      assertTrue("JUnit jar " + JUNIT_JAR_PATTERN + " not found in stack trace written to default System.err stream",
          output.contains(JUNIT_JAR_PATTERN));
    } finally {
      System.setErr(originalErrStream);
    }
  }
}

@@ -3352,38 +3352,36 @@
 n() %7B%0A    assert
-Equals
+True
 (%22Version not re
@@ -3478,18 +3478,26 @@
 d.class)
-, 
+.contains(
 javaVers
@@ -3492,32 +3492,33 @@
 ins(javaVersion)
+)
 ;%0A  %7D%0A%0A  @Test%0A 
@@ -3670,38 +3670,36 @@
 ion);%0A    assert
-Equals
+True
 (%22Version not re
@@ -3782,26 +3782,26 @@
 Element)
-,%0A        
+.contains(
 javaVers
@@ -3800,24 +3800,25 @@
 javaVersion)
+)
 ;%0A  %7D%0A%0A  @Te

Make StackTraceEnricher tests a little more robust.