/*
 * This is free and unencumbered software released into the public domain.
 *
 * Anyone is free to copy, modify, publish, use, compile, sell, or
 * distribute this software, either in source code form or as a compiled
 * binary, for any purpose, commercial or non-commercial, and by any
 * means.
 *
 * In jurisdictions that recognize copyright laws, the author or authors
 * of this software dedicate any and all copyright interest in the
 * software to the public domain. We make this dedication for the benefit
 * of the public at large and to the detriment of our heirs and
 * successors. We intend this dedication to be an overt act of
 * relinquishment in perpetuity of all present and future rights to this
 * software under copyright law.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * For more information, please refer to <http://unlicense.org/>.
 */

package hm.binkley.util.concurrent;

import hm.binkley.util.concurrent.CompletableExecutors
        .CompletableExecutorService;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.Timeout;

import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;

import static hm.binkley.util.concurrent.CompletableExecutors.completable;
import static hm.binkley.util.concurrent.CompletableExecutors
        .unwrappedCompletable;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

/**
 * {@code CompleteableExecutorsTest} tests {@link CompletableFuture}.
 *
 * @author <a href="mailto:binkley@alumni.rice.edu">B. K. Oxley (binkley)</a>
 */
public class CompletableExecutorsTest {
    @Rule
    public final Timeout timeout = Timeout.builder().
            withTimeout(1, SECONDS).
            withLookingForStuckThread(true).
            build();
    @Rule
    public final ExpectedException thrown = ExpectedException.none();

    private CompletableExecutorService threads;

    @Before
    public void setUp() {
        threads = completable(newSingleThreadExecutor());
    }

    @After
    public void tearDown() {
        threads.shutdownNow();
    }

    @Test
    public void shouldGetNormally()
            throws ExecutionException, InterruptedException {
        assertThat(threads.submit(() -> 3).get(), is(equalTo(3)));
    }

    @Test
    public void shouldJoinNormally()
            throws ExecutionException, InterruptedException {
        assertThat(threads.submit(() -> 3).join(), is(equalTo(3)));
    }

    @Test
    public void shouldGetExceptionally()
            throws ExecutionException, InterruptedException {
        thrown.expect(ExecutionException.class);
        thrown.expectCause(is(instanceOf(Foobar.class)));

        threads.submit(() -> {throw new Foobar();}).get();
    }

    @Test
    public void shouldJoinExceptionally()
            throws ExecutionException, InterruptedException {
        thrown.expect(CompletionException.class);
        thrown.expectCause(is(instanceOf(Foobar.class)));

        threads.submit(() -> {throw new Foobar();}).join();
    }

    @Test
    public void shouldCancelForGet()
            throws InterruptedException, ExecutionException {
        thrown.expect(CancellationException.class);

        final CompletableFuture<Object> future = threads.submit(() -> {
            MILLISECONDS.sleep(100);
            return null;
        });
        future.cancel(true);
        future.get();
    }

    @Test
    public void shouldCancelForJoin()
            throws InterruptedException, ExecutionException {
        thrown.expect(CancellationException.class);

        final CompletableFuture<Object> future = threads.submit(() -> {
            MILLISECONDS.sleep(100);
            return null;
        });
        future.cancel(true);
        future.join();
    }

    @Test
    public void shouldInterruptGetExternallyWhenWrapped()
            throws InterruptedException, ExecutionException {
        thrown.expect(ExecutionException.class);
        thrown.expectCause(is(instanceOf(InterruptedException.class)));

        final CompletableFuture<Object> future = threads.submit(() -> {
            MILLISECONDS.sleep(100);
            return null;
        });
        threads.shutdownNow();
        future.get();
    }

    @Test
    public void shouldInterruptJoinExternallyWhenWrapped() {
        thrown.expect(CompletionException.class);
        thrown.expectCause(is(instanceOf(InterruptedException.class)));

        final CompletableFuture<Object> future = threads.submit(() -> {
            MILLISECONDS.sleep(100);
            return null;
        });
        threads.shutdownNow();
        future.join();
    }

    @Test
    public void shouldInterruptGetExternallyWhenUnwrapped()
            throws InterruptedException, ExecutionException {
        thrown.expect(InterruptedException.class);

        threads.shutdownNow();
        threads = unwrappedCompletable(newSingleThreadExecutor());
        final CompletableFuture<Object> future = threads.submit(() -> {
            MILLISECONDS.sleep(100);
            return null;
        });
        threads.shutdownNow();
        future.get();
    }

    @Test
    public void shouldInterruptTimedGetExternallyWhenUnwrapped()
            throws InterruptedException, ExecutionException, TimeoutException {
        thrown.expect(InterruptedException.class);

        threads.shutdownNow();
        threads = unwrappedCompletable(newSingleThreadExecutor());
        final CompletableFuture<Object> future = threads.submit(() -> {
            MILLISECONDS.sleep(100);
            return null;
        });
        threads.shutdownNow();
        future.get(1, SECONDS);
    }

    @Test
    public void shouldInterruptJoinExternallyWhenUnwrapped() {
        thrown.expect(CompletionException.class);

        threads.shutdownNow();
        threads = unwrappedCompletable(newSingleThreadExecutor());
        final CompletableFuture<Object> future = threads.submit(() -> {
            MILLISECONDS.sleep(100);
            return null;
        });
        threads.shutdownNow();
        future.join();
    }

    @Test
    public void shouldInterruptGetInternallyWhenWrapped()
            throws InterruptedException, ExecutionException {
        thrown.expect(ExecutionException.class);
        thrown.expectCause(is(instanceOf(InterruptedException.class)));

        final CompletableFuture<Object> future = threads.submit(() -> {
            throw new InterruptedException();
        });
        future.get();
    }

    @Test
    public void shouldInterruptJoinInternallyWhenWrapped() {
        thrown.expect(CompletionException.class);
        thrown.expectCause(is(instanceOf(InterruptedException.class)));

        final CompletableFuture<Object> future = threads.submit(() -> {
            throw new InterruptedException();
        });
        future.join();
    }

    @Test
    public void shouldInterruptGetInternallyWhenUnwrapped()
            throws InterruptedException, ExecutionException {
        thrown.expect(InterruptedException.class);

        threads.shutdownNow();
        threads = unwrappedCompletable(newSingleThreadExecutor());
        final CompletableFuture<Object> future = threads.submit(() -> {
            throw new InterruptedException();
        });
        future.get();
    }

    @Test
    public void shouldInterruptTimedGetInternallyWhenUnwrapped()
            throws InterruptedException, ExecutionException, TimeoutException {
        thrown.expect(InterruptedException.class);

        threads.shutdownNow();
        threads = unwrappedCompletable(newSingleThreadExecutor());
        final CompletableFuture<Object> future = threads.submit(() -> {
            throw new InterruptedException();
        });
        future.get(1, SECONDS);
    }

    @Test
    public void shouldInterruptJoinInternallyWhenUnwrapped() {
        thrown.expect(CompletionException.class); // Join should be wrapped
        thrown.expectCause(is(instanceOf(InterruptedException.class)));

        threads.shutdownNow();
        threads = unwrappedCompletable(newSingleThreadExecutor());
        final CompletableFuture<Object> future = threads.submit(() -> {
            throw new InterruptedException();
        });
        future.join();
    }

    @Test
    public void shouldClose()
            throws InterruptedException {
        threads.close();
        threads.awaitTermination(1, SECONDS);
    }

    public static final class Foobar
            extends Exception {}
}

@@ -6684,32 +6684,104 @@
 xception.class);
+%0A        thrown.expectCause(is(instanceOf(InterruptedException.class)));
 %0A%0A        thread

Fix test.