/**
   * Verifies that a client timeout will be detected by a Nailgun NGInputStream reading from a
   * blocking heartbeat stream.
   */
  @Test(expected = InterruptedException.class)
  public void whenClientTimeoutDetectedThenMainThreadIsInterrupted()
      throws InterruptedException, IOException {
    final long timeoutMillis = 100;
    final long intervalMillis = timeoutMillis * 2; // Interval > timeout to trigger disconnection.
    final ProjectWorkspace workspace =
        TestDataHelper.createProjectWorkspaceForScenario(this, "exclusive_execution", tmp);
    workspace.setUp();

    // Build an NGContext connected to an NGInputStream reading from a stream that will timeout.
    Thread.currentThread().setName("Test");
    try (TestContext context =
        new TestContext(
            ImmutableMap.copyOf(System.getenv()),
            TestContext.createHeartBeatStream(intervalMillis),
            timeoutMillis)) {
      final Thread commandThread = Thread.currentThread();
      context.addClientListener(
          new NGClientListener() {
            @Override
            public void clientDisconnected() throws InterruptedException {
              commandThread.interrupt();
            }
          });
      Thread.sleep(1000);
      fail("Should have been interrupted.");
    }
  }
  /**
   * This verifies that a client disconnection will be detected by a Nailgun NGInputStream reading
   * from an empty heartbeat stream and that the generated InterruptedException will interrupt
   * command execution causing it to fail.
   */
  @Test
  public void whenClientDisconnectionDetectedThenTestIsInterrupted()
      throws InterruptedException, IOException {

    // Sub process interruption not supported on Windows.
    assumeTrue(Platform.detect() != Platform.WINDOWS);

    final long timeoutMillis = 2000; // Stream timeout > test timeout.
    final long disconnectMillis = 100; // Disconnect before test timeout.
    final ProjectWorkspace workspace =
        TestDataHelper.createProjectWorkspaceForScenario(this, "exclusive_execution", tmp);
    workspace.setUp();

    // Start with an input stream that sends heartbeats at a regular rate.
    final DelegatingInputStream inputStream =
        new DelegatingInputStream(TestContext.createHeartBeatStream(timeoutMillis / 10));

    // Build an NGContext connected to an NGInputStream reading from stream that will timeout.
    try (TestContext context =
        new TestContext(ImmutableMap.copyOf(System.getenv()), inputStream, timeoutMillis)) {
      BuckEventListener listener =
          new BuckEventListener() {
            @Subscribe
            @SuppressWarnings("unused")
            public void onEvent(TestRunEvent.Started event) {
              // When tests start running, make the heartbeat stream simulate a disconnection.
              inputStream.setDelegate(TestContext.createDisconnectionStream(disconnectMillis));
            }

            @Override
            public void outputTrace(BuildId buildId) throws InterruptedException {
              // do nothing
            }
          };
      ProcessResult result = workspace.runBuckdCommand(context, listener, "test", "//:test");
      result.assertFailure();
      assertThat(result.getStderr(), containsString("InterruptedException"));
    }
  }
  /**
   * This verifies that a client timeout will be detected by a Nailgun NGInputStream reading from an
   * empty heartbeat stream and that the generated InterruptedException will cause command execution
   * to fail after timeout.
   */
  @Test
  public void whenClientDisconnectionDetectedThenBuildIsInterrupted()
      throws InterruptedException, IOException {

    // Sub process interruption not supported on Windows.
    assumeTrue(Platform.detect() != Platform.WINDOWS);

    final long timeoutMillis = 2000; // Stream timeout > test timeout.
    final long disconnectMillis = 100; // Disconnect before test timeout.
    final ProjectWorkspace workspace =
        TestDataHelper.createProjectWorkspaceForScenario(this, "exclusive_execution", tmp);
    workspace.setUp();

    // Build an NGContext connected to an NGInputStream reading from stream that will timeout.
    try (TestContext context =
        new TestContext(
            ImmutableMap.copyOf(System.getenv()),
            TestContext.createDisconnectionStream(disconnectMillis),
            timeoutMillis)) {
      ProcessResult result = workspace.runBuckdCommand(context, "build", "//:sleep");
      result.assertFailure();
      assertThat(result.getStderr(), containsString("InterruptedException"));
    }
  }