diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/IntArrayQueue.java b/libraries/common/src/main/java/androidx/media3/common/util/LongArrayQueue.java similarity index 57% rename from libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/IntArrayQueue.java rename to libraries/common/src/main/java/androidx/media3/common/util/LongArrayQueue.java index 5b70d116f7..5d2b47d45f 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/IntArrayQueue.java +++ b/libraries/common/src/main/java/androidx/media3/common/util/LongArrayQueue.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,39 +13,57 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package androidx.media3.exoplayer.mediacodec; +package androidx.media3.common.util; -import androidx.media3.common.util.UnstableApi; +import static androidx.media3.common.util.Assertions.checkArgument; + +import androidx.annotation.VisibleForTesting; import java.util.NoSuchElementException; /** - * Array-based unbounded queue for int primitives with amortized O(1) add and remove. + * Array-based unbounded queue for long primitives with amortized O(1) add and remove. * - *

Use this class instead of a {@link java.util.Deque} to avoid boxing int primitives to {@link - * Integer} instances. + *

Use this class instead of a {@link java.util.Deque} to avoid boxing long primitives to {@link + * Long} instances. */ @UnstableApi -/* package */ final class IntArrayQueue { +public final class LongArrayQueue { - /** Default capacity needs to be a power of 2. */ - private static final int DEFAULT_INITIAL_CAPACITY = 16; + /** Default initial capacity. */ + public static final int DEFAULT_INITIAL_CAPACITY = 16; private int headIndex; private int tailIndex; private int size; - private int[] data; + private long[] data; private int wrapAroundMask; - public IntArrayQueue() { + /** Creates a queue with an initial capacity of {@link #DEFAULT_INITIAL_CAPACITY}. */ + public LongArrayQueue() { + this(DEFAULT_INITIAL_CAPACITY); + } + + /** + * Creates a queue with capacity for at least {@code minCapacity} + * + * @param minCapacity minCapacity the minimum capacity, between 1 and 2^30 inclusive + */ + public LongArrayQueue(int minCapacity) { + checkArgument(minCapacity >= 0 && minCapacity <= (1 << 30)); + minCapacity = minCapacity == 0 ? 1 : minCapacity; + // If capacity isn't a power of 2, round up to the next highest power of 2. + if (Integer.bitCount(minCapacity) != 1) { + minCapacity = Integer.highestOneBit(minCapacity - 1) << 1; + } headIndex = 0; tailIndex = -1; size = 0; - data = new int[DEFAULT_INITIAL_CAPACITY]; + data = new long[minCapacity]; wrapAroundMask = data.length - 1; } /** Add a new item to the queue. */ - public void add(int value) { + public void add(long value) { if (size == data.length) { doubleArraySize(); } @@ -60,18 +78,31 @@ import java.util.NoSuchElementException; * * @throws NoSuchElementException if the queue is empty. */ - public int remove() { + public long remove() { if (size == 0) { throw new NoSuchElementException(); } - int value = data[headIndex]; + long value = data[headIndex]; headIndex = (headIndex + 1) & wrapAroundMask; size--; return value; } + /** + * Retrieves, but does not remove, the head of the queue. + * + * @throws NoSuchElementException if the queue is empty. + */ + public long element() { + if (size == 0) { + throw new NoSuchElementException(); + } + + return data[headIndex]; + } + /** Returns the number of items in the queue. */ public int size() { return size; @@ -90,7 +121,8 @@ import java.util.NoSuchElementException; } /** Returns the length of the backing array. */ - public int capacity() { + @VisibleForTesting + /* package */ int capacity() { return data.length; } @@ -100,7 +132,7 @@ import java.util.NoSuchElementException; throw new IllegalStateException(); } - int[] newData = new int[newCapacity]; + long[] newData = new long[newCapacity]; int itemsToRight = data.length - headIndex; int itemsToLeft = headIndex; System.arraycopy(data, headIndex, newData, 0, itemsToRight); diff --git a/libraries/common/src/test/java/androidx/media3/common/util/LongArrayQueueTest.java b/libraries/common/src/test/java/androidx/media3/common/util/LongArrayQueueTest.java new file mode 100644 index 0000000000..5f5202651b --- /dev/null +++ b/libraries/common/src/test/java/androidx/media3/common/util/LongArrayQueueTest.java @@ -0,0 +1,136 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * 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 androidx.media3.common.util; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import java.util.NoSuchElementException; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Tests for {@link LongArrayQueue}. */ +@RunWith(AndroidJUnit4.class) +public class LongArrayQueueTest { + + @Test + public void capacity() { + LongArrayQueue queue = new LongArrayQueue(2); + + assertThat(queue.capacity()).isEqualTo(2); + } + + @Test + public void capacity_setTo0_increasedTo1() { + LongArrayQueue queue = new LongArrayQueue(0); + + assertThat(queue.capacity()).isEqualTo(1); + } + + @Test + public void capacity_setToNextPowerOf2() { + LongArrayQueue queue = new LongArrayQueue(6); + + assertThat(queue.capacity()).isEqualTo(8); + } + + @Test + public void capacity_invalidMinCapacity_throws() { + assertThrows(IllegalArgumentException.class, () -> new LongArrayQueue(-1)); + } + + @Test + public void add_beyondInitialCapacity_doublesCapacity() { + LongArrayQueue queue = new LongArrayQueue(2); + + queue.add(0); + queue.add(1); + queue.add(2); + + assertThat(queue.size()).isEqualTo(3); + assertThat(queue.capacity()).isEqualTo(4); + } + + @Test + public void isEmpty_afterConstruction_returnsTrue() { + LongArrayQueue queue = new LongArrayQueue(); + + assertThat(queue.isEmpty()).isTrue(); + } + + @Test + public void isEmpty_afterAddition_returnsFalse() { + LongArrayQueue queue = new LongArrayQueue(); + + queue.add(0); + + assertThat(queue.isEmpty()).isFalse(); + } + + @Test + public void isEmpty_afterRemoval_returnsTrue() { + LongArrayQueue queue = new LongArrayQueue(); + + queue.add(0); + queue.remove(); + + assertThat(queue.isEmpty()).isTrue(); + } + + @Test + public void remove_onEmptyQueue_throwsException() { + LongArrayQueue queue = new LongArrayQueue(); + + assertThrows(NoSuchElementException.class, queue::remove); + } + + @Test + public void remove_returnsCorrectItem() { + LongArrayQueue queue = new LongArrayQueue(); + + queue.add(20); + + assertThat(queue.remove()).isEqualTo(20); + } + + @Test + public void element_withEmptyQueue_throws() { + LongArrayQueue queue = new LongArrayQueue(); + + assertThrows(NoSuchElementException.class, queue::element); + } + + @Test + public void element_returnsQueueHead() { + LongArrayQueue queue = new LongArrayQueue(); + + queue.add(5); + + assertThat(queue.element()).isEqualTo(5); + } + + @Test + public void clear_resetsQueue() { + LongArrayQueue queue = new LongArrayQueue(); + + queue.add(123); + queue.clear(); + + assertThat(queue.size()).isEqualTo(0); + assertThat(queue.isEmpty()).isTrue(); + } +} diff --git a/libraries/effect/src/main/java/androidx/media3/effect/DefaultVideoCompositor.java b/libraries/effect/src/main/java/androidx/media3/effect/DefaultVideoCompositor.java index b041abf15a..88a2368931 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/DefaultVideoCompositor.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/DefaultVideoCompositor.java @@ -35,6 +35,7 @@ import androidx.media3.common.VideoFrameProcessingException; import androidx.media3.common.util.GlProgram; import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.Log; +import androidx.media3.common.util.LongArrayQueue; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import com.google.common.collect.ImmutableList; @@ -81,8 +82,8 @@ public final class DefaultVideoCompositor implements VideoCompositor { private boolean allInputsEnded; // Whether all inputSources have signaled end of input. private final TexturePool outputTexturePool; - private final Queue outputTextureTimestamps; // Synchronized with outputTexturePool. - private final Queue syncObjects; // Synchronized with outputTexturePool. + private final LongArrayQueue outputTextureTimestamps; // Synchronized with outputTexturePool. + private final LongArrayQueue syncObjects; // Synchronized with outputTexturePool. // Only used on the GL Thread. private @MonotonicNonNull EGLContext eglContext; @@ -111,8 +112,8 @@ public final class DefaultVideoCompositor implements VideoCompositor { inputSources = new ArrayList<>(); outputTexturePool = new TexturePool(/* useHighPrecisionColorComponents= */ false, textureOutputCapacity); - outputTextureTimestamps = new ArrayDeque<>(textureOutputCapacity); - syncObjects = new ArrayDeque<>(textureOutputCapacity); + outputTextureTimestamps = new LongArrayQueue(textureOutputCapacity); + syncObjects = new LongArrayQueue(textureOutputCapacity); boolean ownsExecutor = executorService == null; ExecutorService instanceExecutorService = @@ -374,7 +375,7 @@ public final class DefaultVideoCompositor implements VideoCompositor { private synchronized void releaseOutputFrameInternal(long presentationTimeUs) throws VideoFrameProcessingException, GlUtil.GlException { while (outputTexturePool.freeTextureCount() < outputTexturePool.capacity() - && checkNotNull(outputTextureTimestamps.peek()) <= presentationTimeUs) { + && outputTextureTimestamps.element() <= presentationTimeUs) { outputTexturePool.freeTexture(); outputTextureTimestamps.remove(); GlUtil.deleteSyncObject(syncObjects.remove()); diff --git a/libraries/effect/src/main/java/androidx/media3/effect/FinalShaderProgramWrapper.java b/libraries/effect/src/main/java/androidx/media3/effect/FinalShaderProgramWrapper.java index 784077e899..919f358c01 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/FinalShaderProgramWrapper.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/FinalShaderProgramWrapper.java @@ -41,10 +41,10 @@ import androidx.media3.common.VideoFrameProcessingException; import androidx.media3.common.VideoFrameProcessor; import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.Log; +import androidx.media3.common.util.LongArrayQueue; import androidx.media3.common.util.Size; import androidx.media3.common.util.Util; import com.google.common.collect.ImmutableList; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; import java.util.Queue; @@ -88,8 +88,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private final VideoFrameProcessor.Listener videoFrameProcessorListener; private final Queue> availableFrames; private final TexturePool outputTexturePool; - private final Queue outputTextureTimestamps; // Synchronized with outputTexturePool. - private final Queue syncObjects; + private final LongArrayQueue outputTextureTimestamps; // Synchronized with outputTexturePool. + private final LongArrayQueue syncObjects; @Nullable private final DefaultVideoFrameProcessor.TextureOutputListener textureOutputListener; private int inputWidth; @@ -148,8 +148,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; boolean useHighPrecisionColorComponents = ColorInfo.isTransferHdr(outputColorInfo); outputTexturePool = new TexturePool(useHighPrecisionColorComponents, textureOutputCapacity); - outputTextureTimestamps = new ArrayDeque<>(textureOutputCapacity); - syncObjects = new ArrayDeque<>(textureOutputCapacity); + outputTextureTimestamps = new LongArrayQueue(textureOutputCapacity); + syncObjects = new LongArrayQueue(textureOutputCapacity); } @Override @@ -227,7 +227,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private void releaseOutputFrameInternal(long presentationTimeUs) throws GlUtil.GlException { checkState(textureOutputListener != null); while (outputTexturePool.freeTextureCount() < outputTexturePool.capacity() - && checkNotNull(outputTextureTimestamps.peek()) <= presentationTimeUs) { + && outputTextureTimestamps.element() <= presentationTimeUs) { outputTexturePool.freeTexture(); outputTextureTimestamps.remove(); GlUtil.deleteSyncObject(syncObjects.remove()); diff --git a/libraries/exoplayer/build.gradle b/libraries/exoplayer/build.gradle index 730cd5bf8b..fecd9bcbf9 100644 --- a/libraries/exoplayer/build.gradle +++ b/libraries/exoplayer/build.gradle @@ -50,6 +50,7 @@ dependencies { api project(modulePrefix + 'lib-extractor') api project(modulePrefix + 'lib-database') implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion + implementation 'androidx.collection:collection:' + androidxCollectionVersion implementation 'androidx.core:core:' + androidxCoreVersion implementation 'androidx.exifinterface:exifinterface:1.3.6' compileOnly 'com.google.code.findbugs:jsr305:' + jsr305Version diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecCallback.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecCallback.java index 922771b1d6..5b595ffa89 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecCallback.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecCallback.java @@ -26,6 +26,7 @@ import android.os.HandlerThread; import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; +import androidx.collection.CircularIntArray; import androidx.media3.common.util.Util; import java.util.ArrayDeque; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -39,10 +40,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private @MonotonicNonNull Handler handler; @GuardedBy("lock") - private final IntArrayQueue availableInputBuffers; + private final CircularIntArray availableInputBuffers; @GuardedBy("lock") - private final IntArrayQueue availableOutputBuffers; + private final CircularIntArray availableOutputBuffers; @GuardedBy("lock") private final ArrayDeque bufferInfos; @@ -81,8 +82,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /* package */ AsynchronousMediaCodecCallback(HandlerThread callbackThread) { this.lock = new Object(); this.callbackThread = callbackThread; - this.availableInputBuffers = new IntArrayQueue(); - this.availableOutputBuffers = new IntArrayQueue(); + this.availableInputBuffers = new CircularIntArray(); + this.availableOutputBuffers = new CircularIntArray(); this.bufferInfos = new ArrayDeque<>(); this.formats = new ArrayDeque<>(); } @@ -132,7 +133,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } else { return availableInputBuffers.isEmpty() ? MediaCodec.INFO_TRY_AGAIN_LATER - : availableInputBuffers.remove(); + : availableInputBuffers.popFirst(); } } } @@ -152,7 +153,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; if (availableOutputBuffers.isEmpty()) { return MediaCodec.INFO_TRY_AGAIN_LATER; } else { - int bufferIndex = availableOutputBuffers.remove(); + int bufferIndex = availableOutputBuffers.popFirst(); if (bufferIndex >= 0) { checkStateNotNull(currentFormat); MediaCodec.BufferInfo nextBufferInfo = bufferInfos.remove(); @@ -204,7 +205,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Override public void onInputBufferAvailable(MediaCodec codec, int index) { synchronized (lock) { - availableInputBuffers.add(index); + availableInputBuffers.addLast(index); } } @@ -215,7 +216,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; addOutputFormat(pendingOutputFormat); pendingOutputFormat = null; } - availableOutputBuffers.add(index); + availableOutputBuffers.addLast(index); bufferInfos.add(info); } } @@ -278,7 +279,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @GuardedBy("lock") private void addOutputFormat(MediaFormat mediaFormat) { - availableOutputBuffers.add(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED); + availableOutputBuffers.addLast(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED); formats.add(mediaFormat); } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/CompositingVideoSinkProvider.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/CompositingVideoSinkProvider.java index cdfc279988..b5de31258c 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/CompositingVideoSinkProvider.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/CompositingVideoSinkProvider.java @@ -37,13 +37,13 @@ import androidx.media3.common.SurfaceInfo; import androidx.media3.common.VideoFrameProcessingException; import androidx.media3.common.VideoFrameProcessor; import androidx.media3.common.VideoSize; +import androidx.media3.common.util.LongArrayQueue; import androidx.media3.common.util.Size; import androidx.media3.common.util.TimedValueQueue; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import java.lang.reflect.Constructor; import java.lang.reflect.Method; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; @@ -168,8 +168,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private final Context context; private final RenderControl renderControl; private final VideoFrameProcessor videoFrameProcessor; - // TODO b/293447478 - Use a queue for primitive longs to avoid the cost of boxing to Long. - private final ArrayDeque processedFramesBufferTimestampsUs; + private final LongArrayQueue processedFramesBufferTimestampsUs; private final TimedValueQueue streamOffsets; private final TimedValueQueue videoSizeChanges; private final Handler handler; @@ -219,7 +218,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; throws VideoFrameProcessingException { this.context = context; this.renderControl = renderControl; - processedFramesBufferTimestampsUs = new ArrayDeque<>(); + processedFramesBufferTimestampsUs = new LongArrayQueue(); streamOffsets = new TimedValueQueue<>(); videoSizeChanges = new TimedValueQueue<>(); // TODO b/226330223 - Investigate increasing frame count when frame dropping is @@ -362,7 +361,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Override public void render(long positionUs, long elapsedRealtimeUs) { while (!processedFramesBufferTimestampsUs.isEmpty()) { - long bufferPresentationTimeUs = checkNotNull(processedFramesBufferTimestampsUs.peek()); + long bufferPresentationTimeUs = processedFramesBufferTimestampsUs.element(); // check whether this buffer comes with a new stream offset. if (maybeUpdateOutputStreamOffset(bufferPresentationTimeUs)) { renderedFirstFrame = false; diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/IntArrayQueueTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/IntArrayQueueTest.java deleted file mode 100644 index c197230b35..0000000000 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/IntArrayQueueTest.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * 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 androidx.media3.exoplayer.mediacodec; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import java.util.NoSuchElementException; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** Tests for {@link IntArrayQueue}. */ -@RunWith(AndroidJUnit4.class) -public class IntArrayQueueTest { - - @Test - public void add_willDoubleCapacity() { - IntArrayQueue queue = new IntArrayQueue(); - int capacity = queue.capacity(); - - for (int i = 0; i <= capacity; i++) { - queue.add(i); - } - - assertThat(queue.capacity()).isEqualTo(2 * capacity); - assertThat(queue.size()).isEqualTo(capacity + 1); - } - - @Test - public void isEmpty_returnsTrueAfterConstruction() { - IntArrayQueue queue = new IntArrayQueue(); - - assertThat(queue.isEmpty()).isTrue(); - } - - @Test - public void isEmpty_returnsFalseAfterAddition() { - IntArrayQueue queue = new IntArrayQueue(); - queue.add(0); - - assertThat(queue.isEmpty()).isFalse(); - } - - @Test - public void isEmpty_returnsFalseAfterRemoval() { - IntArrayQueue queue = new IntArrayQueue(); - queue.add(0); - queue.remove(); - - assertThat(queue.isEmpty()).isTrue(); - } - - @Test - public void remove_onEmptyQueue_throwsException() { - IntArrayQueue queue = new IntArrayQueue(); - - try { - queue.remove(); - fail(); - } catch (NoSuchElementException expected) { - // expected - } - } - - @Test - public void remove_returnsCorrectItem() { - IntArrayQueue queue = new IntArrayQueue(); - int value = 20; - queue.add(value); - - assertThat(queue.remove()).isEqualTo(value); - } - - @Test - public void remove_untilIsEmpty() { - IntArrayQueue queue = new IntArrayQueue(); - for (int i = 0; i < 1024; i++) { - queue.add(i); - } - - int expectedRemoved = 0; - while (!queue.isEmpty()) { - if (expectedRemoved == 15) { - System.out.println("foo"); - } - int removed = queue.remove(); - assertThat(removed).isEqualTo(expectedRemoved++); - } - } - - @Test - public void remove_withResize_returnsCorrectItem() { - IntArrayQueue queue = new IntArrayQueue(); - int nextToAdd = 0; - - while (queue.size() < queue.capacity()) { - queue.add(nextToAdd++); - } - - queue.remove(); - queue.remove(); - - // This will force the queue to wrap-around and then resize - int howManyToResize = queue.capacity() - queue.size() + 1; - for (int i = 0; i < howManyToResize; i++) { - queue.add(nextToAdd++); - } - - assertThat(queue.remove()).isEqualTo(2); - } - - @Test - public void clear_resetsQueue() { - IntArrayQueue queue = new IntArrayQueue(); - - // Add items until array re-sizes twice (capacity grows by 4) - for (int i = 0; i < 1024; i++) { - queue.add(i); - } - - queue.clear(); - - assertThat(queue.size()).isEqualTo(0); - assertThat(queue.isEmpty()).isTrue(); - } -}