@Test
  public void testPollBatch() {
    try {
      ClosableBlockingQueue<String> queue = new ClosableBlockingQueue<>();

      assertNull(queue.pollBatch());

      queue.add("a");
      queue.add("b");

      assertEquals(asList("a", "b"), queue.pollBatch());
      assertNull(queue.pollBatch());

      queue.add("c");

      assertEquals(singletonList("c"), queue.pollBatch());
      assertNull(queue.pollBatch());

      assertTrue(queue.close());

      try {
        queue.pollBatch();
        fail("should cause an exception");
      } catch (IllegalStateException ignored) {
        // expected
      }
    } catch (Exception e) {
      e.printStackTrace();
      fail(e.getMessage());
    }
  }
  private static void testCallExitsOnClose(
      final QueueCall call, ClosableBlockingQueue<String> queue) throws Exception {

    final AtomicReference<Throwable> errorRef = new AtomicReference<>();

    Runnable runnable =
        new Runnable() {
          @Override
          public void run() {
            try {
              call.call();
            } catch (Throwable t) {
              errorRef.set(t);
            }
          }
        };

    Thread thread = new Thread(runnable);
    thread.start();
    Thread.sleep(100);
    queue.close();
    thread.join();

    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
    Throwable cause = errorRef.get();
    assertTrue(cause instanceof IllegalStateException);
  }
  @Test
  public void testCloseEmptyQueue() {
    try {
      ClosableBlockingQueue<String> queue = new ClosableBlockingQueue<>();
      assertTrue(queue.isOpen());
      assertTrue(queue.close());
      assertFalse(queue.isOpen());

      assertFalse(queue.addIfOpen("element"));
      assertTrue(queue.isEmpty());

      try {
        queue.add("some element");
        fail("should cause an exception");
      } catch (IllegalStateException ignored) {
        // expected
      }
    } catch (Exception e) {
      e.printStackTrace();
      fail(e.getMessage());
    }
  }
  @Test
  public void testCreateQueueHashCodeEquals() {
    try {
      ClosableBlockingQueue<String> queue1 = new ClosableBlockingQueue<>();
      ClosableBlockingQueue<String> queue2 = new ClosableBlockingQueue<>(22);

      assertTrue(queue1.isOpen());
      assertTrue(queue2.isOpen());
      assertTrue(queue1.isEmpty());
      assertTrue(queue2.isEmpty());
      assertEquals(0, queue1.size());
      assertEquals(0, queue2.size());

      assertTrue(queue1.hashCode() == queue2.hashCode());
      //noinspection EqualsWithItself
      assertTrue(queue1.equals(queue1));
      //noinspection EqualsWithItself
      assertTrue(queue2.equals(queue2));
      assertTrue(queue1.equals(queue2));

      assertNotNull(queue1.toString());
      assertNotNull(queue2.toString());

      List<String> elements = new ArrayList<>();
      elements.add("a");
      elements.add("b");
      elements.add("c");

      ClosableBlockingQueue<String> queue3 = new ClosableBlockingQueue<>(elements);
      ClosableBlockingQueue<String> queue4 = new ClosableBlockingQueue<>(asList("a", "b", "c"));

      assertTrue(queue3.isOpen());
      assertTrue(queue4.isOpen());
      assertFalse(queue3.isEmpty());
      assertFalse(queue4.isEmpty());
      assertEquals(3, queue3.size());
      assertEquals(3, queue4.size());

      assertTrue(queue3.hashCode() == queue4.hashCode());
      //noinspection EqualsWithItself
      assertTrue(queue3.equals(queue3));
      //noinspection EqualsWithItself
      assertTrue(queue4.equals(queue4));
      assertTrue(queue3.equals(queue4));

      assertNotNull(queue3.toString());
      assertNotNull(queue4.toString());
    } catch (Exception e) {
      e.printStackTrace();
      fail(e.getMessage());
    }
  }
  @Test
  public void testGetBatchBlocking() {
    try {
      ClosableBlockingQueue<String> queue = new ClosableBlockingQueue<>();

      assertEquals(emptyList(), queue.getBatchBlocking(1));
      assertEquals(emptyList(), queue.getBatchBlocking(3));
      assertEquals(emptyList(), queue.getBatchBlocking(2));

      queue.add("a");
      queue.add("b");

      assertEquals(asList("a", "b"), queue.getBatchBlocking(900000009));

      queue.add("c");
      queue.add("d");

      assertEquals(asList("c", "d"), queue.getBatchBlocking());

      assertEquals(emptyList(), queue.getBatchBlocking(2));

      queue.add("e");

      assertEquals(singletonList("e"), queue.getBatchBlocking(0));

      queue.add("f");

      assertEquals(singletonList("f"), queue.getBatchBlocking(1000000000));

      assertEquals(0, queue.size());

      assertEquals(emptyList(), queue.getBatchBlocking(1));
      assertEquals(emptyList(), queue.getBatchBlocking(3));
      assertEquals(emptyList(), queue.getBatchBlocking(2));

      assertTrue(queue.close());

      try {
        queue.getBatchBlocking();
        fail("should cause an exception");
      } catch (IllegalStateException ignored) {
        // expected
      }

      try {
        queue.getBatchBlocking(1000000000L);
        fail("should cause an exception");
      } catch (IllegalStateException ignored) {
        // expected
      }
    } catch (Exception e) {
      e.printStackTrace();
      fail(e.getMessage());
    }
  }
  @Test
  public void testGetElementBlocking() {
    try {
      ClosableBlockingQueue<String> queue = new ClosableBlockingQueue<>();

      assertNull(queue.getElementBlocking(1));
      assertNull(queue.getElementBlocking(3));
      assertNull(queue.getElementBlocking(2));

      assertEquals(0, queue.size());

      queue.add("a");
      queue.add("b");
      queue.add("c");
      queue.add("d");
      queue.add("e");
      queue.add("f");

      assertEquals(6, queue.size());

      assertEquals("a", queue.getElementBlocking(99));
      assertEquals("b", queue.getElementBlocking());

      assertEquals(4, queue.size());

      assertEquals("c", queue.getElementBlocking(0));
      assertEquals("d", queue.getElementBlocking(1000000));
      assertEquals("e", queue.getElementBlocking());
      assertEquals("f", queue.getElementBlocking(1786598));

      assertEquals(0, queue.size());

      assertNull(queue.getElementBlocking(1));
      assertNull(queue.getElementBlocking(3));
      assertNull(queue.getElementBlocking(2));

      assertTrue(queue.close());

      try {
        queue.getElementBlocking();
        fail("should cause an exception");
      } catch (IllegalStateException ignored) {
        // expected
      }

      try {
        queue.getElementBlocking(1000000000L);
        fail("should cause an exception");
      } catch (IllegalStateException ignored) {
        // expected
      }
    } catch (Exception e) {
      e.printStackTrace();
      fail(e.getMessage());
    }
  }
  @Test
  public void testPeekAndPoll() {
    try {
      ClosableBlockingQueue<String> queue = new ClosableBlockingQueue<>();

      assertNull(queue.peek());
      assertNull(queue.peek());
      assertNull(queue.poll());
      assertNull(queue.poll());

      assertEquals(0, queue.size());

      queue.add("a");
      queue.add("b");
      queue.add("c");

      assertEquals(3, queue.size());

      assertEquals("a", queue.peek());
      assertEquals("a", queue.peek());
      assertEquals("a", queue.peek());

      assertEquals(3, queue.size());

      assertEquals("a", queue.poll());
      assertEquals("b", queue.poll());

      assertEquals(1, queue.size());

      assertEquals("c", queue.peek());
      assertEquals("c", queue.peek());

      assertEquals("c", queue.poll());

      assertEquals(0, queue.size());
      assertNull(queue.poll());
      assertNull(queue.peek());
      assertNull(queue.peek());

      assertTrue(queue.close());

      try {
        queue.peek();
        fail("should cause an exception");
      } catch (IllegalStateException ignored) {
        // expected
      }

      try {
        queue.poll();
        fail("should cause an exception");
      } catch (IllegalStateException ignored) {
        // expected
      }
    } catch (Exception e) {
      e.printStackTrace();
      fail(e.getMessage());
    }
  }
  @Test
  public void testCloseNonEmptyQueue() {
    try {
      ClosableBlockingQueue<Integer> queue = new ClosableBlockingQueue<>(asList(1, 2, 3));
      assertTrue(queue.isOpen());

      assertFalse(queue.close());
      assertFalse(queue.close());

      queue.poll();

      assertFalse(queue.close());
      assertFalse(queue.close());

      queue.pollBatch();

      assertTrue(queue.close());
      assertFalse(queue.isOpen());

      assertFalse(queue.addIfOpen(42));
      assertTrue(queue.isEmpty());

      try {
        queue.add(99);
        fail("should cause an exception");
      } catch (IllegalStateException ignored) {
        // expected
      }
    } catch (Exception e) {
      e.printStackTrace();
      fail(e.getMessage());
    }
  }