/** * Copyright 2014 Netflix, Inc. * * 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 rx.internal.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import rx.Scheduler; import rx.Subscription; import rx.functions.Action0; import rx.functions.Func1; import rx.schedulers.Schedulers; public class IndexedRingBufferTest { @Test public void add() { @SuppressWarnings("unchecked") IndexedRingBuffer<LSubscription> list = IndexedRingBuffer.getInstance(); list.add(new LSubscription(1)); list.add(new LSubscription(2)); final AtomicInteger c = new AtomicInteger(); list.forEach(newCounterAction(c)); assertEquals(2, c.get()); } @Test public void removeEnd() { @SuppressWarnings("unchecked") IndexedRingBuffer<LSubscription> list = IndexedRingBuffer.getInstance(); list.add(new LSubscription(1)); int n2 = list.add(new LSubscription(2)); final AtomicInteger c = new AtomicInteger(); list.forEach(newCounterAction(c)); assertEquals(2, c.get()); list.remove(n2); final AtomicInteger c2 = new AtomicInteger(); list.forEach(newCounterAction(c2)); assertEquals(1, c2.get()); } @Test public void removeMiddle() { @SuppressWarnings("unchecked") IndexedRingBuffer<LSubscription> list = IndexedRingBuffer.getInstance(); list.add(new LSubscription(1)); int n2 = list.add(new LSubscription(2)); list.add(new LSubscription(3)); list.remove(n2); final AtomicInteger c = new AtomicInteger(); list.forEach(newCounterAction(c)); assertEquals(2, c.get()); } @Test public void addRemoveAdd() { @SuppressWarnings("unchecked") IndexedRingBuffer<String> list = IndexedRingBuffer.getInstance(); list.add("one"); list.add("two"); list.add("three"); ArrayList<String> values = new ArrayList<String>(); list.forEach(accumulate(values)); assertEquals(3, values.size()); assertEquals("one", values.get(0)); assertEquals("two", values.get(1)); assertEquals("three", values.get(2)); list.remove(1); values.clear(); list.forEach(accumulate(values)); assertEquals(2, values.size()); assertEquals("one", values.get(0)); assertEquals("three", values.get(1)); list.add("four"); values.clear(); list.forEach(accumulate(values)); assertEquals(3, values.size()); assertEquals("one", values.get(0)); assertEquals("four", values.get(1)); assertEquals("three", values.get(2)); final AtomicInteger c = new AtomicInteger(); list.forEach(newCounterAction(c)); assertEquals(3, c.get()); } @Test public void addThousands() { String s = "s"; @SuppressWarnings("unchecked") IndexedRingBuffer<String> list = IndexedRingBuffer.getInstance(); for (int i = 0; i < 10000; i++) { list.add(s); } AtomicInteger c = new AtomicInteger(); list.forEach(newCounterAction(c)); assertEquals(10000, c.get()); list.remove(5000); c.set(0); list.forEach(newCounterAction(c)); assertEquals(9999, c.get()); list.add("one"); list.add("two"); c.set(0); // list.forEach(print()); list.forEach(newCounterAction(c)); assertEquals(10001, c.get()); } @Test public void testForEachWithIndex() { @SuppressWarnings("unchecked") IndexedRingBuffer<String> buffer = IndexedRingBuffer.getInstance(); buffer.add("zero"); buffer.add("one"); buffer.add("two"); buffer.add("three"); final ArrayList<String> list = new ArrayList<String>(); int nextIndex = buffer.forEach(accumulate(list)); assertEquals(4, list.size()); assertEquals(list, Arrays.asList("zero", "one", "two", "three")); assertEquals(0, nextIndex); list.clear(); nextIndex = buffer.forEach(accumulate(list), 0); assertEquals(4, list.size()); assertEquals(list, Arrays.asList("zero", "one", "two", "three")); assertEquals(0, nextIndex); list.clear(); nextIndex = buffer.forEach(accumulate(list), 2); assertEquals(4, list.size()); assertEquals(list, Arrays.asList("two", "three", "zero", "one")); assertEquals(2, nextIndex); // 2, 3, 0, 1 list.clear(); nextIndex = buffer.forEach(accumulate(list), 3); assertEquals(4, list.size()); assertEquals(list, Arrays.asList("three", "zero", "one", "two")); assertEquals(3, nextIndex); // 3, 0, 1, 2 list.clear(); nextIndex = buffer.forEach(new Func1<String, Boolean>() { @Override public Boolean call(String t1) { list.add(t1); return false; } }, 3); assertEquals(1, list.size()); assertEquals(list, Arrays.asList("three")); assertEquals(3, nextIndex); // we ended early so we'll go back to this index again next time list.clear(); nextIndex = buffer.forEach(new Func1<String, Boolean>() { int i = 0; @Override public Boolean call(String t1) { list.add(t1); if (i++ == 2) { return false; } else { return true; } } }, 0); assertEquals(3, list.size()); assertEquals(list, Arrays.asList("zero", "one", "two")); assertEquals(2, nextIndex); // 0, 1, 2 (// we ended early so we'll go back to the last index again next time) } @Test public void testForEachAcrossSections() { @SuppressWarnings("unchecked") IndexedRingBuffer<Integer> buffer = IndexedRingBuffer.getInstance(); for (int i = 0; i < 10000; i++) { buffer.add(i); } final ArrayList<Integer> list = new ArrayList<Integer>(); int nextIndex = buffer.forEach(accumulate(list), 5000); assertEquals(10000, list.size()); assertEquals(Integer.valueOf(5000), list.get(0)); assertEquals(Integer.valueOf(9999), list.get(4999)); assertEquals(Integer.valueOf(0), list.get(5000)); assertEquals(Integer.valueOf(4999), list.get(9999)); assertEquals(5000, nextIndex); } @Test public void longRunningAddRemoveAddDoesntLeakMemory() { String s = "s"; @SuppressWarnings("unchecked") IndexedRingBuffer<String> list = IndexedRingBuffer.getInstance(); for (int i = 0; i < 20000; i++) { int index = list.add(s); list.remove(index); } AtomicInteger c = new AtomicInteger(); list.forEach(newCounterAction(c)); assertEquals(0, c.get()); // System.out.println("Index is: " + list.index.get() + " when it should be no bigger than " + list.SIZE); assertTrue(list.index.get() < list.SIZE); // it should actually be 1 since we only did add/remove sequentially assertEquals(1, list.index.get()); } @Test public void testConcurrentAdds() throws InterruptedException { @SuppressWarnings("unchecked") final IndexedRingBuffer<Integer> list = IndexedRingBuffer.getInstance(); Scheduler.Worker w1 = Schedulers.computation().createWorker(); Scheduler.Worker w2 = Schedulers.computation().createWorker(); final CountDownLatch latch = new CountDownLatch(2); w1.schedule(new Action0() { @Override public void call() { for (int i = 0; i < 10000; i++) { list.add(i); } latch.countDown(); } }); w2.schedule(new Action0() { @Override public void call() { for (int i = 10000; i < 20000; i++) { list.add(i); } latch.countDown(); } }); latch.await(); w1.unsubscribe(); w2.unsubscribe(); AtomicInteger c = new AtomicInteger(); list.forEach(newCounterAction(c)); assertEquals(20000, c.get()); ArrayList<Integer> values = new ArrayList<Integer>(); list.forEach(accumulate(values)); Collections.sort(values); int j = 0; for (int i : values) { assertEquals(i, j++); } } @Test public void testConcurrentAddAndRemoves() throws InterruptedException { @SuppressWarnings("unchecked") final IndexedRingBuffer<Integer> list = IndexedRingBuffer.getInstance(); final List<Exception> exceptions = Collections.synchronizedList(new ArrayList<Exception>()); Scheduler.Worker w1 = Schedulers.computation().createWorker(); Scheduler.Worker w2 = Schedulers.computation().createWorker(); final CountDownLatch latch = new CountDownLatch(2); w1.schedule(new Action0() { @Override public void call() { try { for (int i = 10000; i < 20000; i++) { list.add(i); // Integer v = list.remove(index); } } catch (Exception e) { e.printStackTrace(); exceptions.add(e); } latch.countDown(); } }); w2.schedule(new Action0() { @Override public void call() { try { for (int i = 0; i < 10000; i++) { int index = list.add(i); // cause some random remove/add interference Integer v = list.remove(index); if (v == null) { throw new RuntimeException("should not get null"); } list.add(v); } } catch (Exception e) { e.printStackTrace(); exceptions.add(e); } latch.countDown(); } }); latch.await(); w1.unsubscribe(); w2.unsubscribe(); AtomicInteger c = new AtomicInteger(); list.forEach(newCounterAction(c)); assertEquals(20000, c.get()); ArrayList<Integer> values = new ArrayList<Integer>(); list.forEach(accumulate(values)); Collections.sort(values); int j = 0; for (int i : values) { assertEquals(i, j++); } if (exceptions.size() > 0) { System.out.println("Exceptions: " + exceptions); } assertEquals(0, exceptions.size()); } private <T> Func1<T, Boolean> accumulate(final ArrayList<T> list) { return new Func1<T, Boolean>() { @Override public Boolean call(T t1) { list.add(t1); return true; } }; } @SuppressWarnings("unused") private Func1<Object, Boolean> print() { return new Func1<Object, Boolean>() { @Override public Boolean call(Object t1) { System.out.println("Object: " + t1); return true; } }; } private Func1<Object, Boolean> newCounterAction(final AtomicInteger c) { return new Func1<Object, Boolean>() { @Override public Boolean call(Object t1) { c.incrementAndGet(); return true; } }; } public static class LSubscription implements Subscription { private final int n; public LSubscription(int n) { this.n = n; } @Override public void unsubscribe() { } @Override public boolean isUnsubscribed() { return false; } @Override public String toString() { return "Subscription=>" + n; } } }