/* * SonarQube * Copyright (C) 2009-2017 SonarSource SA * mailto:info AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonar.core.util; import java.io.IOException; import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; import org.junit.Test; import org.mockito.InOrder; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; public class CloseableIteratorTest { @Test public void iterate() { SimpleCloseableIterator it = new SimpleCloseableIterator(); assertThat(it.isClosed).isFalse(); // multiple calls to hasNext() moves only once the cursor assertThat(it.hasNext()).isTrue(); assertThat(it.hasNext()).isTrue(); assertThat(it.hasNext()).isTrue(); assertThat(it.next()).isEqualTo(1); assertThat(it.isClosed).isFalse(); assertThat(it.hasNext()).isTrue(); assertThat(it.next()).isEqualTo(2); assertThat(it.isClosed).isFalse(); assertThat(it.hasNext()).isFalse(); // automatic close assertThat(it.isClosed).isTrue(); // explicit close does not fail it.close(); assertThat(it.isClosed).isTrue(); } @Test public void call_next_without_hasNext() { SimpleCloseableIterator it = new SimpleCloseableIterator(); assertThat(it.next()).isEqualTo(1); assertThat(it.next()).isEqualTo(2); try { it.next(); fail(); } catch (NoSuchElementException expected) { } } @Test public void automatic_close_if_traversal_error() { FailureCloseableIterator it = new FailureCloseableIterator(); try { it.next(); fail(); } catch (IllegalStateException expected) { assertThat(expected).hasMessage("expected failure"); assertThat(it.isClosed).isTrue(); } } @Test public void remove_is_not_supported_by_default() { SimpleCloseableIterator it = new SimpleCloseableIterator(); try { it.remove(); fail(); } catch (UnsupportedOperationException expected) { assertThat(it.isClosed).isTrue(); } } @Test public void remove_can_be_overridden() { RemovableCloseableIterator it = new RemovableCloseableIterator(); it.remove(); assertThat(it.isRemoved).isTrue(); } @Test public void has_next_should_not_call_do_next_when_already_closed() { DoNextShouldNotBeCalledWhenClosedIterator it = new DoNextShouldNotBeCalledWhenClosedIterator(); it.next(); it.next(); assertThat(it.hasNext()).isFalse(); // this call to hasNext close the stream assertThat(it.hasNext()).isFalse(); assertThat(it.isClosed).isTrue(); // calling hasNext should not fail it.hasNext(); } @Test public void emptyIterator_has_next_is_false() { assertThat(CloseableIterator.emptyCloseableIterator().hasNext()).isFalse(); } @Test(expected = NoSuchElementException.class) public void emptyIterator_next_throws_NoSuchElementException() { CloseableIterator.emptyCloseableIterator().next(); } @Test(expected = NullPointerException.class) public void from_iterator_throws_early_NPE_if_arg_is_null() { CloseableIterator.from(null); } @Test(expected = IllegalArgumentException.class) public void from_iterator_throws_IAE_if_arg_is_a_CloseableIterator() { CloseableIterator.from(new SimpleCloseableIterator()); } @Test(expected = IllegalArgumentException.class) public void from_iterator_throws_IAE_if_arg_is_a_AutoCloseable() { CloseableIterator.from(new CloseableIt()); } @Test public void wrap_closeables() throws Exception { AutoCloseable closeable1 = mock(AutoCloseable.class); AutoCloseable closeable2 = mock(AutoCloseable.class); CloseableIterator iterator = new SimpleCloseableIterator(); CloseableIterator wrapper = CloseableIterator.wrap(iterator, closeable1, closeable2); assertThat(wrapper.next()).isEqualTo(1); assertThat(wrapper.next()).isEqualTo(2); assertThat(wrapper.hasNext()).isFalse(); assertThat(wrapper.isClosed).isTrue(); assertThat(iterator.isClosed).isTrue(); InOrder order = inOrder(closeable1, closeable2); order.verify(closeable1).close(); order.verify(closeable2).close(); } @Test(expected = IllegalArgumentException.class) public void wrap_fails_if_iterator_declared_in_other_closeables() throws Exception { CloseableIterator iterator = new SimpleCloseableIterator(); CloseableIterator.wrap(iterator, iterator); } @Test(expected = NullPointerException.class) public void wrap_fails_if_null_closeable() throws Exception { CloseableIterator.wrap(new SimpleCloseableIterator(), null); } private static class CloseableIt implements Iterator<String>, AutoCloseable { private final Iterator<String> delegate = Collections.<String>emptyList().iterator(); @Override public void remove() { delegate.remove(); } @Override public String next() { return delegate.next(); } @Override public boolean hasNext() { return delegate.hasNext(); } @Override public void close() throws IOException { // no need to implement it for real } } @Test public void verify_has_next_from_iterator_with_empty_iterator() { assertThat(CloseableIterator.from(Collections.<String>emptyList().iterator()).hasNext()).isFalse(); } @Test(expected = NoSuchElementException.class) public void verify_next_from_iterator_with_empty_iterator() { CloseableIterator.from(Collections.<String>emptyList().iterator()).next(); } static class SimpleCloseableIterator extends CloseableIterator { int count = 0; boolean isClosed = false; @Override protected Object doNext() { count++; if (count < 3) { return count; } return null; } @Override protected void doClose() { isClosed = true; } } static class FailureCloseableIterator extends CloseableIterator { boolean isClosed = false; @Override protected Object doNext() { throw new IllegalStateException("expected failure"); } @Override protected void doClose() { isClosed = true; } } static class RemovableCloseableIterator extends CloseableIterator { boolean isClosed = false; boolean isRemoved = false; @Override protected Object doNext() { return "foo"; } @Override protected void doRemove() { isRemoved = true; } @Override protected void doClose() { isClosed = true; } } static class DoNextShouldNotBeCalledWhenClosedIterator extends SimpleCloseableIterator { @Override protected Object doNext() { if (!isClosed) { return super.doNext(); } else { throw new IllegalStateException("doNext should not be called when already closed"); } } } }