/*
 * Copyright 2015 Terracotta, Inc., a Software AG company.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.terracotta.ipceventbus.proc;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.terracotta.ipceventbus.ThreadUtil;

import java.io.File;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Arrays;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

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

/**
 * @author Mathieu Carbou
 */
@RunWith(JUnit4.class)
public class AnyProcessTest {

  @Test
  public void test_launch_process() throws InterruptedException {
    AnyProcess anyProcess = AnyProcess.newBuilder()
        .command("bash", "-c", "sleep 2; echo $VAR")
        .env("VAR", "Hello world!")
        .pipeStdout()
        .pipeStderr()
        .recordStdout()
        .recordStderr()
        .build();

    try {
      anyProcess.exitValue();
      fail();
    } catch (Exception e) {
      assertEquals(IllegalThreadStateException.class, e.getClass());
    }

    try {
      anyProcess.getRecordedStdout();
      fail();
    } catch (Exception e) {
      assertEquals(IllegalThreadStateException.class, e.getClass());
      assertEquals("Process not terminated.", e.getMessage());
    }

    try {
      anyProcess.getRecordedStderr();
      fail();
    } catch (Exception e) {
      assertEquals(IllegalThreadStateException.class, e.getClass());
      assertEquals("Process not terminated.", e.getMessage());
    }

    try {
      anyProcess.getRecordedStdoutText();
      fail();
    } catch (Exception e) {
      assertEquals(IllegalThreadStateException.class, e.getClass());
      assertEquals("Process not terminated.", e.getMessage());
    }

    try {
      anyProcess.getRecordedStderrText();
      fail();
    } catch (Exception e) {
      assertEquals(IllegalThreadStateException.class, e.getClass());
      assertEquals("Process not terminated.", e.getMessage());
    }

    assertTrue(anyProcess.getPid() > 0);
    assertTrue(anyProcess.isRunning());
    assertEquals(Arrays.asList("bash", "-c", "sleep 2; echo $VAR"), anyProcess.getCommand());
    assertEquals(new File("."), anyProcess.getWorkingDirectory());
    assertEquals(0, anyProcess.waitFor());
    assertEquals(0, anyProcess.exitValue());
    assertEquals("", anyProcess.getRecordedStderrText());
    assertEquals("Hello world!\n", anyProcess.getRecordedStdoutText());
  }

  @Test
  public void test_launch_process_without_collecting() throws InterruptedException {
    AnyProcess anyProcess = AnyProcess.newBuilder()
        .command("bash", "-c", "sleep 2; echo $VAR")
        .env("VAR", "Hello world!")
        .build();

    try {
      anyProcess.getRecordedStdout();
      fail();
    } catch (Exception e) {
      assertEquals(IllegalThreadStateException.class, e.getClass());
      assertEquals("Stdout not recorded.", e.getMessage());
    }

    try {
      anyProcess.getRecordedStderr();
      fail();
    } catch (Exception e) {
      assertEquals(IllegalThreadStateException.class, e.getClass());
      assertEquals("Stderr not recorded.", e.getMessage());
    }

    try {
      anyProcess.getRecordedStdoutText();
      fail();
    } catch (Exception e) {
      assertEquals(IllegalThreadStateException.class, e.getClass());
      assertEquals("Stdout not recorded.", e.getMessage());
    }

    try {
      anyProcess.getRecordedStderrText();
      fail();
    } catch (Exception e) {
      assertEquals(IllegalThreadStateException.class, e.getClass());
      assertEquals("Stderr not recorded.", e.getMessage());
    }

    assertEquals(0, anyProcess.waitFor());
  }

  @Test(timeout = 1000)
  public void test_destroy() throws InterruptedException {
    AnyProcess proc = AnyProcess.newBuilder()
        .command("bash", "-c", "sleep 3; echo $VAR")
        .pipeStdout()
        .pipeStderr()
        .env("VAR", "Hello world!")
        .build();

    ThreadUtil.minimumSleep(500);
    proc.destroy();
    assertTrue(proc.isDestroyed());

    // 143 = return code when SIGKILL
    assertEquals(143, proc.exitValue());
    assertEquals(143, proc.waitFor());
  }

  @Test
  public void using_future() throws InterruptedException, TimeoutException, ExecutionException {
    AnyProcess proc = AnyProcess.newBuilder()
        .command("bash", "-c", "sleep 2; echo $VAR")
        .pipeStdout()
        .pipeStderr()
        .env("VAR", "Hello world!")
        .build();

    assertEquals(0, proc.getFuture().get(3, TimeUnit.SECONDS).intValue());
    assertTrue(proc.getFuture().isDone());
    assertFalse(proc.getFuture().isCancelled());
    assertEquals(0, proc.getFuture().get().intValue());
  }

  @Test(timeout = 1000)
  public void test_destroy_future() throws InterruptedException {
    AnyProcess proc = AnyProcess.newBuilder()
        .command("bash", "-c", "sleep 3; echo $VAR")
        .pipeStdout()
        .pipeStderr()
        .env("VAR", "Hello world!")
        .build();

    ThreadUtil.minimumSleep(500);
    boolean wasCancelled = proc.getFuture().cancel(true);
    assertTrue(proc.isDestroyed());

    assertTrue(proc.getFuture().isDone());
    assertTrue(proc.getFuture().isCancelled() == wasCancelled);

    // 143 = return code when SIGKILL
    assertEquals(143, proc.exitValue());
    assertEquals(143, proc.waitFor());

    try {
      proc.getFuture().get();
      if (wasCancelled) {
        fail();
      }
    } catch (Exception e) {
      assertEquals(CancellationException.class, e.getClass());
    }
  }

  @Test
  public void test_waitForTime() throws InterruptedException, TimeoutException {
    AnyProcess proc = AnyProcess.newBuilder()
        .command("bash", "-c", "sleep 3; echo $VAR")
        .pipeStdout()
        .pipeStderr()
        .env("VAR", "Hello world!")
        .build();

    try {
      proc.waitForTime(1, TimeUnit.SECONDS);
      fail();
    } catch (TimeoutException ignored) {
    }

    assertEquals(0, proc.waitForTime(3, TimeUnit.SECONDS));
  }

  @Test
  public void pipe_stdin() throws InterruptedException, TimeoutException, ExecutionException, IOException {
    PipedInputStream in = new PipedInputStream();
    PipedOutputStream out = new PipedOutputStream(in);
    AnyProcess proc = AnyProcess.newBuilder()
        .command("bash", "-c", "read input && echo $input")
        .pipeStdout()
        .pipeStderr()
        .pipeStdin(in)
        .recordStderr()
        .recordStdout()
        .build();
    out.write("Hello World!\n".getBytes());
    out.close();
    assertEquals(0, proc.waitFor());
    assertEquals("Hello World!\n", proc.getRecordedStdoutText());
  }

}