package org.junit.tests.running.methods;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;

import junit.framework.JUnit4TestAdapter;
import junit.framework.TestResult;
import org.junit.After;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;

public class TimeoutTest {
	
	static public class FailureWithTimeoutTest {
		@Test(timeout= 1000) public void failure() {
			fail();
		}
	}
	
	@Test public void failureWithTimeout() throws Exception {
		JUnitCore core= new JUnitCore();
		Result result= core.run(FailureWithTimeoutTest.class);
		assertEquals(1, result.getRunCount());
		assertEquals(1, result.getFailureCount());
		assertEquals(AssertionError.class, result.getFailures().get(0).getException().getClass());
	}

	static public class FailureWithTimeoutRunTimeExceptionTest {
		@Test(timeout= 1000) public void failure() {
			throw new NullPointerException();
		}
	}
	
	@Test public void failureWithTimeoutRunTimeException() throws Exception {
		JUnitCore core= new JUnitCore();
		Result result= core.run(FailureWithTimeoutRunTimeExceptionTest.class);
		assertEquals(1, result.getRunCount());
		assertEquals(1, result.getFailureCount());
		assertEquals(NullPointerException.class, result.getFailures().get(0).getException().getClass());
	}

	static public class SuccessWithTimeoutTest {
		@Test(timeout= 1000) public void success() {			
		}
	}
		
	@Test public void successWithTimeout() throws Exception {
		JUnitCore core= new JUnitCore();
		Result result= core.run(SuccessWithTimeoutTest.class);
		assertEquals(1, result.getRunCount());
		assertEquals(0, result.getFailureCount());
	}

	static public class TimeoutFailureTest {
		@Test(timeout= 100) public void success() throws InterruptedException {			
			Thread.sleep(40000);
		}
	}
	
	@Ignore("was breaking gump")
	@Test public void timeoutFailure() throws Exception {
		JUnitCore core= new JUnitCore();
		Result result= core.run(TimeoutFailureTest.class);
		assertEquals(1, result.getRunCount());
		assertEquals(1, result.getFailureCount());
		assertEquals(InterruptedException.class, result.getFailures().get(0).getException().getClass());
	}
	
	static public class InfiniteLoopTest {
		@Test(timeout= 100) public void failure() {
			infiniteLoop();
		}

		private void infiniteLoop() {
			for(;;)
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
				}
		}
	}
	
	@Test public void infiniteLoop() throws Exception {
		JUnitCore core= new JUnitCore();
		Result result= core.run(InfiniteLoopTest.class);
		assertEquals(1, result.getRunCount());
		assertEquals(1, result.getFailureCount());
		Throwable exception= result.getFailures().get(0).getException();
		assertTrue(exception.getMessage().contains("test timed out after 100 milliseconds"));
	}
	
	static public class ImpatientLoopTest {
		@Test(timeout= 1) public void failure() {
			infiniteLoop();
		}

		private void infiniteLoop() {
			for(;;);
		}
	}
	
	@Ignore("This breaks sporadically with time differences just slightly more than 200ms")
	@Test public void infiniteLoopRunsForApproximatelyLengthOfTimeout() throws Exception {
		// "prime the pump": running these beforehand makes the runtimes more predictable
		//                   (because of class loading?)
		JUnitCore.runClasses(InfiniteLoopTest.class, ImpatientLoopTest.class);
		long longTime= runAndTime(InfiniteLoopTest.class);
		long shortTime= runAndTime(ImpatientLoopTest.class);
		long difference= longTime - shortTime;
		assertTrue(String.format("Difference was %sms", difference), difference < 200);
	}

	private long runAndTime(Class<?> clazz) {
		JUnitCore core= new JUnitCore();
		long startTime= System.currentTimeMillis();
		core.run(clazz);
		long totalTime = System.currentTimeMillis() - startTime;
		return totalTime;
	}

	@Ignore("We would like this behavior to work but it may not be possible")
	@Test public void stalledThreadAppearsInStackTrace() throws Exception {
		JUnitCore core= new JUnitCore();
		Result result= core.run(InfiniteLoopTest.class);
		assertEquals(1, result.getRunCount());
		assertEquals(1, result.getFailureCount());
		Throwable exception= result.getFailures().get(0).getException();
		Writer buffer= new StringWriter();
		PrintWriter writer= new PrintWriter(buffer);
		exception.printStackTrace(writer);
		assertTrue(writer.toString().contains("infiniteLoop")); // Make sure we have the stalled frame on the stack somewhere
	}

	@Test public void compatibility() {
		TestResult result= new TestResult();
		new JUnit4TestAdapter(InfiniteLoopTest.class).run(result);
		assertEquals(1, result.errorCount());
	}
	
	public static class WillTimeOut {
		static boolean afterWasCalled= false;
		
		@Test(timeout=1) public void test() {
			for(;;);
		}
		
		@After public void after() {
			afterWasCalled= true;
		}
	}
	
	@Test public void makeSureAfterIsCalledAfterATimeout() {
		JUnitCore.runClasses(WillTimeOut.class);
		assertThat(WillTimeOut.afterWasCalled, is(true));
	}
}
