
package org.apache.geode.internal.lang;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.*;

import java.lang.Thread.State;
import java.util.concurrent.atomic.AtomicBoolean;

import edu.umd.cs.mtc.MultithreadedTestCase;
import edu.umd.cs.mtc.TestFramework;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.lib.concurrent.Synchroniser;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;

import org.apache.geode.test.junit.categories.UnitTest;

/**
 * The ThreadUtilsJUnitTest class is a test suite of test cases for testing the contract and
 * functionality of the ThreadUtils class.
 * <p/>
 * 
 * @see org.apache.geode.internal.lang.ThreadUtils
 * @see org.jmock.Expectations
 * @see org.jmock.Mockery
 * @see org.junit.Assert
 * @see org.junit.Test
 * @since GemFire 7.0
 */
@Category(UnitTest.class)
public class ThreadUtilsJUnitTest {

  protected Mockery mockContext;

  @Before
  public void setUp() {
    mockContext = new Mockery() {
      {
        setImposteriser(ClassImposteriser.INSTANCE);
        setThreadingPolicy(new Synchroniser());
      }
    };
  }

  @After
  public void tearDown() {
    mockContext.assertIsSatisfied();
  }

  @Test
  public void testGetThreadNameWithNull() {
    assertNull(ThreadUtils.getThreadName(null));
  }

  @Test
  public void testGetThreadNameWithThread() {
    assertNotNull(ThreadUtils.getThreadName(Thread.currentThread()));
  }

  @Test
  public void testInterruptWithNullThread() {
    ThreadUtils.interrupt(null);
  }

  @Test
  public void testInterruptWithNonNullThread() {
    final Thread mockThread = mockContext.mock(Thread.class, "Interrupted Thread");

    mockContext.checking(new Expectations() {
      {
        oneOf(mockThread).interrupt();
      }
    });

    ThreadUtils.interrupt(mockThread);
  }

  @Test
  public void testIsAlive() {
    assertTrue(ThreadUtils.isAlive(Thread.currentThread()));
  }

  @Test
  public void testIsAliveWithNullThread() {
    assertFalse(ThreadUtils.isAlive(null));
  }

  @Test
  public void testIsAliveWithUnstartedThread() {
    final Thread thread = new Thread(new Runnable() {
      public void run() {}
    });
    assertFalse(ThreadUtils.isAlive(thread));
  }

  @Test
  public void testIsAliveWithStoppedThread() throws InterruptedException {
    final AtomicBoolean ran = new AtomicBoolean(false);

    final Thread thread = new Thread(new Runnable() {
      public void run() {
        ran.set(true);
      }
    });

    thread.start();
    thread.join(50);

    assertFalse(ThreadUtils.isAlive(thread));
    assertTrue(ran.get());
  }

  @Test
  public void testIsWaitingWithNullThread() {
    assertFalse(ThreadUtils.isWaiting(null));
  }

  @Test
  public void testIsWaitingWithRunningThread() {
    final Thread runningThread = mockContext.mock(Thread.class, "Running Thread");

    mockContext.checking(new Expectations() {
      {
        oneOf(runningThread).getState();
        will(returnValue(State.RUNNABLE));
      }
    });

    assertFalse(ThreadUtils.isWaiting(runningThread));
  }

  @Test
  public void testIsWaitingWithWaitingThread() {
    final Thread waitingThread = mockContext.mock(Thread.class, "Waiting Thread");

    mockContext.checking(new Expectations() {
      {
        one(waitingThread).getState();
        will(returnValue(State.WAITING));
      }
    });

    assertTrue(ThreadUtils.isWaiting(waitingThread));
  }

  @Test
  public void testSleep() {
    final long t0 = System.currentTimeMillis();
    final long sleepDuration = ThreadUtils.sleep(500);
    final long t1 = System.currentTimeMillis();

    assertTrue(t1 > t0);
    assertTrue(sleepDuration > 0);
  }

  @Ignore("This is really just testing Thread.sleep(long)")
  @Test
  public void testSleepWithInterrupt() throws Throwable {
    TestFramework.runOnce(new SleepInterruptedMultithreadedTestCase(10 * 1000));
  }

  protected static class SleepInterruptedMultithreadedTestCase extends MultithreadedTestCase {

    private final long sleepDuration;

    private volatile Thread sleeperThread;
    private volatile boolean sleeperWasInterrupted;
    private volatile long actualSleepDuration;

    public SleepInterruptedMultithreadedTestCase(final long sleepDuration) {
      assert sleepDuration > 0 : "The duration of sleep must be greater than equal to 0!";
      this.sleepDuration = sleepDuration;
    }

    public void thread1() {
      assertTick(0);
      sleeperThread = Thread.currentThread();
      sleeperThread.setName("Sleeper Thread");
      waitForTick(1);
      actualSleepDuration = ThreadUtils.sleep(sleepDuration);
      sleeperWasInterrupted = sleeperThread.isInterrupted();
      assertTick(2);
    }

    public void thread2() throws Exception {
      assertTick(0);
      Thread.currentThread().setName("Interrupting Thread");
      waitForTick(1);
      waitForTick(2);
      sleeperThread.interrupt();
      assertTick(2);
    }

    @Override
    public void finish() {
      assertThat(sleeperWasInterrupted).isTrue();
      assertThat(actualSleepDuration).isGreaterThan(0);
    }
  }

}
