• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.collect;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import com.google.common.collect.testing.IteratorFeature;
22 import com.google.common.collect.testing.IteratorTester;
23 import com.google.common.testing.NullPointerTester;
24 
25 import junit.framework.TestCase;
26 
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.ConcurrentModificationException;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.NoSuchElementException;
36 import java.util.PriorityQueue;
37 import java.util.Random;
38 import java.util.SortedMap;
39 import java.util.concurrent.atomic.AtomicInteger;
40 
41 /**
42  * Unit test for {@link MinMaxPriorityQueue}.
43  *
44  * @author Alexei Stolboushkin
45  * @author Sverre Sundsdal
46  */
47 public class MinMaxPriorityQueueTest extends TestCase {
48   private Ordering<Integer> SOME_COMPARATOR = Ordering.natural().reverse();
49 
50   // Overkill alert!  Test all combinations of 0-2 options during creation.
51 
testCreation_simple()52   public void testCreation_simple() {
53     MinMaxPriorityQueue<Integer> queue = MinMaxPriorityQueue
54         .create();
55     assertEquals(11, queue.capacity());
56     checkUnbounded(queue);
57     checkNatural(queue);
58   }
59 
testCreation_comparator()60   public void testCreation_comparator() {
61     MinMaxPriorityQueue<Integer> queue = MinMaxPriorityQueue
62         .orderedBy(SOME_COMPARATOR)
63         .create();
64     assertEquals(11, queue.capacity());
65     checkUnbounded(queue);
66     assertSame(SOME_COMPARATOR, queue.comparator());
67   }
68 
testCreation_expectedSize()69   public void testCreation_expectedSize() {
70     MinMaxPriorityQueue<Integer> queue = MinMaxPriorityQueue
71         .expectedSize(8)
72         .create();
73     assertEquals(8, queue.capacity());
74     checkUnbounded(queue);
75     checkNatural(queue);
76   }
77 
testCreation_expectedSize_comparator()78   public void testCreation_expectedSize_comparator() {
79     MinMaxPriorityQueue<Integer> queue = MinMaxPriorityQueue
80         .orderedBy(SOME_COMPARATOR)
81         .expectedSize(8)
82         .create();
83     assertEquals(8, queue.capacity());
84     checkUnbounded(queue);
85     assertSame(SOME_COMPARATOR, queue.comparator());
86   }
87 
testCreation_maximumSize()88   public void testCreation_maximumSize() {
89     MinMaxPriorityQueue<Integer> queue = MinMaxPriorityQueue
90         .maximumSize(42)
91         .create();
92     assertEquals(11, queue.capacity());
93     assertEquals(42, queue.maximumSize);
94     checkNatural(queue);
95   }
96 
testCreation_comparator_maximumSize()97   public void testCreation_comparator_maximumSize() {
98     MinMaxPriorityQueue<Integer> queue = MinMaxPriorityQueue
99         .orderedBy(SOME_COMPARATOR)
100         .maximumSize(42)
101         .create();
102     assertEquals(11, queue.capacity());
103     assertEquals(42, queue.maximumSize);
104     assertSame(SOME_COMPARATOR, queue.comparator());
105   }
106 
testCreation_expectedSize_maximumSize()107   public void testCreation_expectedSize_maximumSize() {
108     MinMaxPriorityQueue<Integer> queue = MinMaxPriorityQueue
109         .expectedSize(8)
110         .maximumSize(42)
111         .create();
112     assertEquals(8, queue.capacity());
113     assertEquals(42, queue.maximumSize);
114     checkNatural(queue);
115   }
116 
117   private static final List<Integer> NUMBERS =
118       ImmutableList.of(4, 8, 15, 16, 23, 42);
119 
testCreation_withContents()120   public void testCreation_withContents() {
121     MinMaxPriorityQueue<Integer> queue = MinMaxPriorityQueue
122         .create(NUMBERS);
123     assertEquals(6, queue.size());
124     assertEquals(11, queue.capacity());
125     checkUnbounded(queue);
126     checkNatural(queue);
127   }
128 
testCreation_comparator_withContents()129   public void testCreation_comparator_withContents() {
130     MinMaxPriorityQueue<Integer> queue = MinMaxPriorityQueue
131         .orderedBy(SOME_COMPARATOR)
132         .create(NUMBERS);
133     assertEquals(6, queue.size());
134     assertEquals(11, queue.capacity());
135     checkUnbounded(queue);
136     assertSame(SOME_COMPARATOR, queue.comparator());
137   }
138 
testCreation_expectedSize_withContents()139   public void testCreation_expectedSize_withContents() {
140     MinMaxPriorityQueue<Integer> queue = MinMaxPriorityQueue
141         .expectedSize(8)
142         .create(NUMBERS);
143     assertEquals(6, queue.size());
144     assertEquals(8, queue.capacity());
145     checkUnbounded(queue);
146     checkNatural(queue);
147   }
148 
testCreation_maximumSize_withContents()149   public void testCreation_maximumSize_withContents() {
150     MinMaxPriorityQueue<Integer> queue = MinMaxPriorityQueue
151         .maximumSize(42)
152         .create(NUMBERS);
153     assertEquals(6, queue.size());
154     assertEquals(11, queue.capacity());
155     assertEquals(42, queue.maximumSize);
156     checkNatural(queue);
157   }
158 
159   // Now test everything at once
160 
testCreation_allOptions()161   public void testCreation_allOptions() {
162     MinMaxPriorityQueue<Integer> queue = MinMaxPriorityQueue
163         .orderedBy(SOME_COMPARATOR)
164         .expectedSize(8)
165         .maximumSize(42)
166         .create(NUMBERS);
167     assertEquals(6, queue.size());
168     assertEquals(8, queue.capacity());
169     assertEquals(42, queue.maximumSize);
170     assertSame(SOME_COMPARATOR, queue.comparator());
171   }
172 
173   // TODO: tests that check the weird interplay between expected size,
174   // maximum size, size of initial contents, default capacity...
175 
checkNatural(MinMaxPriorityQueue<Integer> queue)176   private static void checkNatural(MinMaxPriorityQueue<Integer> queue) {
177     assertSame(Ordering.natural(), queue.comparator());
178   }
179 
checkUnbounded(MinMaxPriorityQueue<Integer> queue)180   private static void checkUnbounded(MinMaxPriorityQueue<Integer> queue) {
181     assertEquals(Integer.MAX_VALUE, queue.maximumSize);
182   }
183 
testHeapIntact()184   public void testHeapIntact() {
185     Random random = new Random(0);
186     int heapSize = 999;
187     int numberOfModifications = 500;
188     MinMaxPriorityQueue<Integer> mmHeap =
189         MinMaxPriorityQueue.expectedSize(heapSize).create();
190     /*
191      * this map would contain the same exact elements as the MinMaxHeap; the
192      * value in the map is the number of occurrences of the key.
193      */
194     SortedMap<Integer, AtomicInteger> replica = Maps.newTreeMap();
195     assertTrue("Empty heap should be OK", mmHeap.isIntact());
196     for (int i = 0; i < heapSize; i++) {
197       int randomInt = random.nextInt();
198       mmHeap.offer(randomInt);
199       insertIntoReplica(replica, randomInt);
200     }
201     assertTrue("MinMaxHeap not intact after " + heapSize + " insertions",
202         mmHeap.isIntact());
203     assertEquals(heapSize, mmHeap.size());
204     int currentHeapSize = heapSize;
205     for (int i = 0; i < numberOfModifications; i++) {
206       if (random.nextBoolean()) {
207         /* insert a new element */
208         int randomInt = random.nextInt();
209         mmHeap.offer(randomInt);
210         insertIntoReplica(replica, randomInt);
211         currentHeapSize++;
212       } else {
213         /* remove either min or max */
214         if (random.nextBoolean()) {
215           removeMinFromReplica(replica, mmHeap.poll());
216         } else {
217           removeMaxFromReplica(replica, mmHeap.pollLast());
218         }
219         for (Integer v : replica.keySet()) {
220           assertTrue("MinMax queue has lost " + v, mmHeap.contains(v));
221         }
222         assertTrue(mmHeap.isIntact());
223         currentHeapSize--;
224         assertEquals(currentHeapSize, mmHeap.size());
225       }
226     }
227     assertEquals(currentHeapSize, mmHeap.size());
228     assertTrue("Heap not intact after " + numberOfModifications +
229         " random mixture of operations", mmHeap.isIntact());
230   }
231 
testSmall()232   public void testSmall() {
233     MinMaxPriorityQueue<Integer> mmHeap = MinMaxPriorityQueue.create();
234     mmHeap.add(1);
235     mmHeap.add(4);
236     mmHeap.add(2);
237     mmHeap.add(3);
238     assertEquals(4, (int) mmHeap.pollLast());
239     assertEquals(3, (int) mmHeap.peekLast());
240     assertEquals(3, (int) mmHeap.pollLast());
241     assertEquals(1, (int) mmHeap.peek());
242     assertEquals(2, (int) mmHeap.peekLast());
243     assertEquals(2, (int) mmHeap.pollLast());
244     assertEquals(1, (int) mmHeap.peek());
245     assertEquals(1, (int) mmHeap.peekLast());
246     assertEquals(1, (int) mmHeap.pollLast());
247     assertNull(mmHeap.peek());
248     assertNull(mmHeap.peekLast());
249     assertNull(mmHeap.pollLast());
250   }
251 
testSmallMinHeap()252   public void testSmallMinHeap() {
253     MinMaxPriorityQueue<Integer> mmHeap = MinMaxPriorityQueue.create();
254     mmHeap.add(1);
255     mmHeap.add(3);
256     mmHeap.add(2);
257     assertEquals(1, (int) mmHeap.peek());
258     assertEquals(1, (int) mmHeap.poll());
259     assertEquals(3, (int) mmHeap.peekLast());
260     assertEquals(2, (int) mmHeap.peek());
261     assertEquals(2, (int) mmHeap.poll());
262     assertEquals(3, (int) mmHeap.peekLast());
263     assertEquals(3, (int) mmHeap.peek());
264     assertEquals(3, (int) mmHeap.poll());
265     assertNull(mmHeap.peekLast());
266     assertNull(mmHeap.peek());
267     assertNull(mmHeap.poll());
268   }
269 
testRemove()270   public void testRemove() {
271     MinMaxPriorityQueue<Integer> mmHeap = MinMaxPriorityQueue.create();
272     mmHeap.addAll(Lists.newArrayList(1, 2, 3, 4, 47, 1, 5, 3, 0));
273     assertTrue("Heap is not intact initally", mmHeap.isIntact());
274     assertEquals(9, mmHeap.size());
275     mmHeap.remove(5);
276     assertEquals(8, mmHeap.size());
277     assertTrue("Heap is not intact after remove()", mmHeap.isIntact());
278     assertEquals(47, (int) mmHeap.pollLast());
279     assertEquals(4, (int) mmHeap.pollLast());
280     mmHeap.removeAll(Lists.newArrayList(2, 3));
281     assertEquals(3, mmHeap.size());
282     assertTrue("Heap is not intact after removeAll()", mmHeap.isIntact());
283   }
284 
testContains()285   public void testContains() {
286     MinMaxPriorityQueue<Integer> mmHeap = MinMaxPriorityQueue.create();
287     mmHeap.addAll(Lists.newArrayList(1, 1, 2));
288     assertEquals(3, mmHeap.size());
289     assertFalse("Heap does not contain null", mmHeap.contains(null));
290     assertFalse("Heap does not contain 3", mmHeap.contains(3));
291     assertFalse("Heap does not contain 3", mmHeap.remove(3));
292     assertEquals(3, mmHeap.size());
293     assertTrue("Heap is not intact after remove()", mmHeap.isIntact());
294     assertTrue("Heap contains two 1's", mmHeap.contains(1));
295     assertTrue("Heap contains two 1's", mmHeap.remove(1));
296     assertTrue("Heap contains 1", mmHeap.contains(1));
297     assertTrue("Heap contains 1", mmHeap.remove(1));
298     assertFalse("Heap does not contain 1", mmHeap.contains(1));
299     assertTrue("Heap contains 2", mmHeap.remove(2));
300     assertEquals(0, mmHeap.size());
301     assertFalse("Heap does not contain anything", mmHeap.contains(1));
302     assertFalse("Heap does not contain anything", mmHeap.remove(2));
303   }
304 
testIteratorPastEndException()305   public void testIteratorPastEndException() {
306     MinMaxPriorityQueue<Integer> mmHeap = MinMaxPriorityQueue.create();
307     mmHeap.addAll(Lists.newArrayList(1, 2));
308     Iterator<Integer> it = mmHeap.iterator();
309     assertTrue("Iterator has reached end prematurely", it.hasNext());
310     it.next();
311     it.next();
312     try {
313       it.next();
314       fail("No exception thrown when iterating past end of heap");
315     } catch (NoSuchElementException e) {
316       // expected error
317     }
318   }
319 
testIteratorConcurrentModification()320   public void testIteratorConcurrentModification() {
321     MinMaxPriorityQueue<Integer> mmHeap = MinMaxPriorityQueue.create();
322     mmHeap.addAll(Lists.newArrayList(1, 2, 3, 4));
323     Iterator<Integer> it = mmHeap.iterator();
324     assertTrue("Iterator has reached end prematurely", it.hasNext());
325     it.next();
326     it.next();
327     mmHeap.remove(4);
328     try {
329       it.next();
330       fail("No exception thrown when iterating a modified heap");
331     } catch (ConcurrentModificationException e) {
332       // expected error
333     }
334   }
335 
336   /**
337    * Tests a failure caused by fix to childless uncle issue.
338    */
testIteratorRegressionChildlessUncle()339   public void testIteratorRegressionChildlessUncle() {
340     final ArrayList<Integer> initial = Lists.newArrayList(
341         1, 15, 13, 8, 9, 10, 11, 14);
342     MinMaxPriorityQueue<Integer> q = MinMaxPriorityQueue.create(initial);
343     assertTrue("State " + Arrays.toString(q.toArray()), q.isIntact());
344     q.remove(9);
345     q.remove(11);
346     q.remove(10);
347     // Now we're in the critical state: [1, 15, 13, 8, 14]
348     // Removing 8 while iterating caused duplicates in iteration result.
349     List<Integer> result = Lists.newArrayListWithCapacity(initial.size());
350     for (Iterator<Integer> iter = q.iterator(); iter.hasNext();) {
351       Integer value = iter.next();
352       result.add(value);
353       if (value == 8) {
354         iter.remove();
355       }
356     }
357     assertTrue(q.isIntact());
358     assertThat(result).has().exactly(1, 15, 13, 8, 14);
359   }
360 
361   /**
362    * This tests a special case of the removeAt() call. Moving an element
363    * sideways on the heap could break the invariants. Sometimes we need to
364    * bubble an element up instead of trickling down. See implementation.
365    */
testInvalidatingRemove()366   public void testInvalidatingRemove() {
367     MinMaxPriorityQueue<Integer> mmHeap = MinMaxPriorityQueue.create();
368     mmHeap.addAll(Lists.newArrayList(
369             1, 20, 1000, 2, 3, 30, 40, 10, 11, 12, 13, 300, 400, 500, 600));
370     assertEquals(15, mmHeap.size());
371     assertTrue("Heap is not intact initially", mmHeap.isIntact());
372     mmHeap.remove(12);
373     assertEquals(14, mmHeap.size());
374     assertTrue("Heap is not intact after remove()", mmHeap.isIntact());
375   }
376 
377   /**
378    * This tests a more obscure special case, but otherwise similar to above.
379    */
testInvalidatingRemove2()380   public void testInvalidatingRemove2() {
381     MinMaxPriorityQueue<Integer> mmHeap = MinMaxPriorityQueue.create();
382     List<Integer> values = Lists.newArrayList(
383         1, 20, 1000, 2, 3, 30, 40, 10, 11, 12, 13, 300, 400, 500, 600, 4, 5,
384         6, 7, 8, 9, 4, 5, 200, 250);
385     mmHeap.addAll(values);
386     assertEquals(25, mmHeap.size());
387     assertTrue("Heap is not intact initially", mmHeap.isIntact());
388     mmHeap.remove(2);
389     assertEquals(24, mmHeap.size());
390     assertTrue("Heap is not intact after remove()", mmHeap.isIntact());
391     values.removeAll(Lists.newArrayList(2));
392     assertEquals(values.size(), mmHeap.size());
393     assertTrue(values.containsAll(mmHeap));
394     assertTrue(mmHeap.containsAll(values));
395   }
396 
testIteratorInvalidatingIteratorRemove()397   public void testIteratorInvalidatingIteratorRemove() {
398     MinMaxPriorityQueue<Integer> mmHeap = MinMaxPriorityQueue.create();
399     mmHeap.addAll(Lists.newArrayList(
400             1, 20, 100, 2, 3, 30, 40));
401     assertEquals(7, mmHeap.size());
402     assertTrue("Heap is not intact initially", mmHeap.isIntact());
403     Iterator<Integer> it = mmHeap.iterator();
404     assertEquals((Integer) 1, it.next());
405     assertEquals((Integer) 20, it.next());
406     assertEquals((Integer) 100, it.next());
407     assertEquals((Integer) 2, it.next());
408     it.remove();
409     assertFalse(mmHeap.contains(2));
410     assertTrue(it.hasNext());
411     assertEquals((Integer) 3, it.next());
412     assertTrue(it.hasNext());
413     assertEquals((Integer) 30, it.next());
414     assertTrue(it.hasNext());
415     assertEquals((Integer) 40, it.next());
416     assertFalse(it.hasNext());
417     assertEquals(6, mmHeap.size());
418     assertTrue("Heap is not intact after remove()", mmHeap.isIntact());
419     assertFalse(mmHeap.contains(2));
420 
421     // This tests that it.remove() above actually changed the order. It
422     // indicates that the value 40 was stored in forgetMeNot, so it is
423     // returned in the last call to it.next(). Without it, 30 should be the last
424     // item returned by the iterator.
425     Integer lastItem = 0;
426     for (Integer tmp : mmHeap) {
427       lastItem = tmp;
428     }
429     assertEquals((Integer) 30, lastItem);
430   }
431 
432   /**
433    * This tests a special case where removeAt has to trickle an element
434    * first down one level from a min to a max level, then up one level above
435    * the index of the removed element.
436    * It also tests that skipMe in the iterator plays nicely with
437    * forgetMeNot.
438    */
testIteratorInvalidatingIteratorRemove2()439   public void testIteratorInvalidatingIteratorRemove2() {
440     MinMaxPriorityQueue<Integer> mmHeap = MinMaxPriorityQueue.create();
441     mmHeap.addAll(Lists.newArrayList(
442         1, 20, 1000, 2, 3, 30, 40, 10, 11, 12, 13, 200, 300, 500, 400));
443     assertTrue("Heap is not intact initially", mmHeap.isIntact());
444     Iterator<Integer> it = mmHeap.iterator();
445     assertEquals((Integer) 1, it.next());
446     assertEquals((Integer) 20, it.next());
447     assertEquals((Integer) 1000, it.next());
448     assertEquals((Integer) 2, it.next());
449     it.remove();
450     assertTrue("Heap is not intact after remove", mmHeap.isIntact());
451     assertEquals((Integer) 10, it.next());
452     assertEquals((Integer) 3, it.next());
453     it.remove();
454     assertTrue("Heap is not intact after remove", mmHeap.isIntact());
455     assertEquals((Integer) 12, it.next());
456     assertEquals((Integer) 30, it.next());
457     assertEquals((Integer) 40, it.next());
458     // Skipping 20
459     assertEquals((Integer) 11, it.next());
460     // Skipping 400
461     assertEquals((Integer) 13, it.next());
462     assertEquals((Integer) 200, it.next());
463     assertEquals((Integer) 300, it.next());
464     // Last two from forgetMeNot.
465     assertEquals((Integer) 400, it.next());
466     assertEquals((Integer) 500, it.next());
467   }
468 
testRemoveFromStringHeap()469   public void testRemoveFromStringHeap() {
470     MinMaxPriorityQueue<String> mmHeap =
471         MinMaxPriorityQueue.expectedSize(5).create();
472     Collections.addAll(mmHeap,
473         "foo", "bar", "foobar", "barfoo", "larry", "sergey", "eric");
474     assertTrue("Heap is not intact initially", mmHeap.isIntact());
475     assertEquals("bar", mmHeap.peek());
476     assertEquals("sergey", mmHeap.peekLast());
477     assertEquals(7, mmHeap.size());
478     assertTrue("Could not remove larry", mmHeap.remove("larry"));
479     assertEquals(6, mmHeap.size());
480     assertFalse("heap contains larry which has been removed",
481         mmHeap.contains("larry"));
482     assertTrue("heap does not contain sergey",
483         mmHeap.contains("sergey"));
484     assertTrue("Could not remove larry", mmHeap.removeAll(
485         Lists.newArrayList("sergey", "eric")));
486     assertFalse("Could remove nikesh which is not in the heap",
487         mmHeap.remove("nikesh"));
488     assertEquals(4, mmHeap.size());
489   }
490 
testCreateWithOrdering()491   public void testCreateWithOrdering() {
492     MinMaxPriorityQueue<String> mmHeap =
493         MinMaxPriorityQueue.orderedBy(Ordering.natural().reverse()).create();
494     Collections.addAll(mmHeap,
495         "foo", "bar", "foobar", "barfoo", "larry", "sergey", "eric");
496     assertTrue("Heap is not intact initially", mmHeap.isIntact());
497     assertEquals("sergey", mmHeap.peek());
498     assertEquals("bar", mmHeap.peekLast());
499   }
500 
testCreateWithCapacityAndOrdering()501   public void testCreateWithCapacityAndOrdering() {
502     MinMaxPriorityQueue<Integer> mmHeap = MinMaxPriorityQueue.orderedBy(
503         Ordering.natural().reverse()).expectedSize(5).create();
504     Collections.addAll(mmHeap, 1, 7, 2, 56, 2, 5, 23, 68, 0, 3);
505     assertTrue("Heap is not intact initially", mmHeap.isIntact());
506     assertEquals(68, (int) mmHeap.peek());
507     assertEquals(0, (int) mmHeap.peekLast());
508   }
509 
runIterator( final List<T> values, int steps)510   private <T extends Comparable<T>> void runIterator(
511       final List<T> values, int steps) throws Exception {
512     IteratorTester<T> tester =
513         new IteratorTester<T>(
514             steps,
515             IteratorFeature.MODIFIABLE,
516             Lists.newLinkedList(values),
517             IteratorTester.KnownOrder.UNKNOWN_ORDER) {
518           private MinMaxPriorityQueue<T> mmHeap;
519           @Override protected Iterator<T> newTargetIterator() {
520             mmHeap = MinMaxPriorityQueue.create(values);
521             return mmHeap.iterator();
522           }
523           @Override protected void verify(List<T> elements) {
524             assertEquals(Sets.newHashSet(elements),
525                 Sets.newHashSet(mmHeap.iterator()));
526             assertTrue("Invalid MinMaxHeap: " + mmHeap, mmHeap.isIntact());
527           }
528         };
529     tester.test();
530   }
531 
testIteratorTester()532   public void testIteratorTester() throws Exception {
533     Random random = new Random(0);
534     List<Integer> list = Lists.newArrayList();
535     for (int i = 0; i < 3; i++) {
536       list.add(random.nextInt());
537     }
538     runIterator(list, 6);
539   }
540 
testIteratorTesterLarger()541   public void testIteratorTesterLarger() throws Exception {
542     runIterator(Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 5);
543   }
544 
testRemoveAt()545   public void testRemoveAt() {
546     long seed = new Random().nextLong();
547     Random random = new Random(seed);
548     int heapSize = 999;
549     int numberOfModifications = 500;
550     MinMaxPriorityQueue<Integer> mmHeap =
551         MinMaxPriorityQueue.expectedSize(heapSize).create();
552     for (int i = 0; i < heapSize; i++) {
553       mmHeap.add(random.nextInt());
554     }
555     for (int i = 0; i < numberOfModifications; i++) {
556       mmHeap.removeAt(random.nextInt(mmHeap.size()));
557       assertTrue("Modification " + i + " of seed " + seed, mmHeap.isIntact());
558       mmHeap.add(random.nextInt());
559       assertTrue("Modification " + i + " of seed " + seed, mmHeap.isIntact());
560     }
561   }
562 
testRemoveAt_exhaustive()563   public void testRemoveAt_exhaustive() {
564     int size = 8;
565     List<Integer> expected = createOrderedList(size);
566     for (Collection<Integer> perm : Collections2.permutations(expected)) {
567       for (int i = 0; i < perm.size(); i++) {
568         MinMaxPriorityQueue<Integer> q = MinMaxPriorityQueue.create(perm);
569         q.removeAt(i);
570         assertTrue("Remove at " + i + " perm " + perm, q.isIntact());
571       }
572     }
573   }
574 
575   /**
576    * Regression test for bug found.
577    */
testCorrectOrdering_regression()578   public void testCorrectOrdering_regression() {
579     MinMaxPriorityQueue<Integer> q = MinMaxPriorityQueue
580         .create(ImmutableList.of(3, 5, 1, 4, 7));
581     List<Integer> expected = ImmutableList.of(1, 3, 4, 5, 7);
582     List<Integer> actual = new ArrayList<Integer>(5);
583     for (int i = 0; i < expected.size(); i++) {
584       actual.add(q.pollFirst());
585     }
586     assertEquals(expected, actual);
587   }
588 
testCorrectOrdering_smallHeapsPollFirst()589   public void testCorrectOrdering_smallHeapsPollFirst() {
590     for (int size = 2; size < 16; size++) {
591       for (int attempts = 0; attempts < size * (size - 1); attempts++) {
592         ArrayList<Integer> elements = createOrderedList(size);
593         List<Integer> expected = ImmutableList.copyOf(elements);
594         MinMaxPriorityQueue<Integer> q = MinMaxPriorityQueue.create();
595         long seed = insertRandomly(elements, q);
596         while (!q.isEmpty()) {
597           elements.add(q.pollFirst());
598         }
599         assertEquals("Using seed " + seed, expected, elements);
600       }
601     }
602   }
603 
testCorrectOrdering_smallHeapsPollLast()604   public void testCorrectOrdering_smallHeapsPollLast() {
605     for (int size = 2; size < 16; size++) {
606       for (int attempts = 0; attempts < size * (size - 1); attempts++) {
607         ArrayList<Integer> elements = createOrderedList(size);
608         List<Integer> expected = ImmutableList.copyOf(elements);
609         MinMaxPriorityQueue<Integer> q = MinMaxPriorityQueue.create();
610         long seed = insertRandomly(elements, q);
611         while (!q.isEmpty()) {
612           elements.add(0, q.pollLast());
613         }
614         assertEquals("Using seed " + seed, expected, elements);
615       }
616     }
617   }
618 
testCorrectOrdering_mediumHeapsPollFirst()619   public void testCorrectOrdering_mediumHeapsPollFirst() {
620     for (int attempts = 0; attempts < 5000; attempts++) {
621       int size = new Random().nextInt(256) + 16;
622       ArrayList<Integer> elements = createOrderedList(size);
623       List<Integer> expected = ImmutableList.copyOf(elements);
624       MinMaxPriorityQueue<Integer> q = MinMaxPriorityQueue.create();
625       long seed = insertRandomly(elements, q);
626       while (!q.isEmpty()) {
627         elements.add(q.pollFirst());
628       }
629       assertEquals("Using seed " + seed, expected, elements);
630     }
631   }
632 
633   /**
634    * Regression test for bug found in random testing.
635    */
testCorrectOrdering_73ElementBug()636   public void testCorrectOrdering_73ElementBug() {
637     int size = 73;
638     long seed = 7522346378524621981L;
639     ArrayList<Integer> elements = createOrderedList(size);
640     List<Integer> expected = ImmutableList.copyOf(elements);
641     MinMaxPriorityQueue<Integer> q = MinMaxPriorityQueue.create();
642     insertRandomly(elements, q, new Random(seed));
643     assertTrue(q.isIntact());
644     while (!q.isEmpty()) {
645       elements.add(q.pollFirst());
646       assertTrue("State " + Arrays.toString(q.toArray()), q.isIntact());
647     }
648     assertEquals("Using seed " + seed, expected, elements);
649   }
650 
testCorrectOrdering_mediumHeapsPollLast()651   public void testCorrectOrdering_mediumHeapsPollLast() {
652     for (int attempts = 0; attempts < 5000; attempts++) {
653       int size = new Random().nextInt(256) + 16;
654       ArrayList<Integer> elements = createOrderedList(size);
655       List<Integer> expected = ImmutableList.copyOf(elements);
656       MinMaxPriorityQueue<Integer> q = MinMaxPriorityQueue.create();
657       long seed = insertRandomly(elements, q);
658       while (!q.isEmpty()) {
659         elements.add(0, q.pollLast());
660       }
661       assertEquals("Using seed " + seed, expected, elements);
662     }
663   }
664 
testCorrectOrdering_randomAccess()665   public void testCorrectOrdering_randomAccess() {
666     long seed = new Random().nextLong();
667     Random random = new Random(seed);
668     PriorityQueue<Integer> control = new PriorityQueue<Integer>();
669     MinMaxPriorityQueue<Integer> q = MinMaxPriorityQueue.create();
670     for (int i = 0; i < 73; i++) { // 73 is a childless uncle case.
671       Integer element = random.nextInt();
672       control.add(element);
673       assertTrue(q.add(element));
674     }
675     assertTrue("State " + Arrays.toString(q.toArray()), q.isIntact());
676     for (int i = 0; i < 500000; i++) {
677       if (random.nextBoolean()) {
678         Integer element = random.nextInt();
679         control.add(element);
680         q.add(element);
681       } else {
682         assertEquals("Using seed " + seed, control.poll(), q.pollFirst());
683       }
684     }
685     while (!control.isEmpty()) {
686       assertEquals("Using seed " + seed, control.poll(), q.pollFirst());
687     }
688     assertTrue(q.isEmpty());
689   }
690 
testExhaustive_pollAndPush()691   public void testExhaustive_pollAndPush() {
692     int size = 8;
693     List<Integer> expected = createOrderedList(size);
694     for (Collection<Integer> perm : Collections2.permutations(expected)) {
695       MinMaxPriorityQueue<Integer> q = MinMaxPriorityQueue.create(perm);
696       List<Integer> elements = Lists.newArrayListWithCapacity(size);
697       while (!q.isEmpty()) {
698         Integer next = q.pollFirst();
699         for (int i = 0; i <= size; i++) {
700           assertTrue(q.add(i));
701           assertTrue(q.add(next));
702           assertTrue(q.remove(i));
703           assertEquals(next, q.poll());
704         }
705         elements.add(next);
706       }
707       assertEquals("Started with " + perm, expected, elements);
708     }
709   }
710 
711   /**
712    * Regression test for b/4124577
713    */
testRegression_dataCorruption()714   public void testRegression_dataCorruption() {
715     int size = 8;
716     List<Integer> expected = createOrderedList(size);
717     MinMaxPriorityQueue<Integer> q = MinMaxPriorityQueue.create(expected);
718     List<Integer> contents = Lists.newArrayList(expected);
719     List<Integer> elements = Lists.newArrayListWithCapacity(size);
720     while (!q.isEmpty()) {
721       assertThat(q).has().exactlyAs(contents);
722       Integer next = q.pollFirst();
723       contents.remove(next);
724       assertThat(q).has().exactlyAs(contents);
725       for (int i = 0; i <= size; i++) {
726         q.add(i);
727         contents.add(i);
728         assertThat(q).has().exactlyAs(contents);
729         q.add(next);
730         contents.add(next);
731         assertThat(q).has().exactlyAs(contents);
732         q.remove(i);
733         assertTrue(contents.remove(Integer.valueOf(i)));
734         assertThat(q).has().exactlyAs(contents);
735         assertEquals(next, q.poll());
736         contents.remove(next);
737         assertThat(q).has().exactlyAs(contents);
738       }
739       elements.add(next);
740     }
741     assertEquals(expected, elements);
742   }
743 
744   /**
745    * Returns the seed used for the randomization.
746    */
insertRandomly(ArrayList<Integer> elements, MinMaxPriorityQueue<Integer> q)747   private long insertRandomly(ArrayList<Integer> elements,
748       MinMaxPriorityQueue<Integer> q) {
749     long seed = new Random().nextLong();
750     Random random = new Random(seed);
751     insertRandomly(elements, q, random);
752     return seed;
753   }
754 
insertRandomly(ArrayList<Integer> elements, MinMaxPriorityQueue<Integer> q, Random random)755   private void insertRandomly(ArrayList<Integer> elements, MinMaxPriorityQueue<Integer> q,
756       Random random) {
757     while (!elements.isEmpty()) {
758       int selectedIndex = random.nextInt(elements.size());
759       q.offer(elements.remove(selectedIndex));
760     }
761   }
762 
createOrderedList(int size)763   private ArrayList<Integer> createOrderedList(int size) {
764     ArrayList<Integer> elements = new ArrayList<Integer>(size);
765     for (int i = 0; i < size; i++) {
766       elements.add(i);
767     }
768     return elements;
769   }
770 
testIsEvenLevel()771   public void testIsEvenLevel() {
772     assertTrue(MinMaxPriorityQueue.isEvenLevel(0));
773     assertFalse(MinMaxPriorityQueue.isEvenLevel(1));
774     assertFalse(MinMaxPriorityQueue.isEvenLevel(2));
775     assertTrue(MinMaxPriorityQueue.isEvenLevel(3));
776 
777     assertFalse(MinMaxPriorityQueue.isEvenLevel((1 << 10) - 2));
778     assertTrue(MinMaxPriorityQueue.isEvenLevel((1 << 10) - 1));
779 
780     int i = 1 << 29;
781     assertTrue(MinMaxPriorityQueue.isEvenLevel(i - 2));
782     assertFalse(MinMaxPriorityQueue.isEvenLevel(i - 1));
783     assertFalse(MinMaxPriorityQueue.isEvenLevel(i));
784 
785     i = 1 << 30;
786     assertFalse(MinMaxPriorityQueue.isEvenLevel(i - 2));
787     assertTrue(MinMaxPriorityQueue.isEvenLevel(i - 1));
788     assertTrue(MinMaxPriorityQueue.isEvenLevel(i));
789 
790     // 1 << 31 is negative because of overflow, 1 << 31 - 1 is positive
791     // since isEvenLevel adds 1, we need to do - 2.
792     assertTrue(MinMaxPriorityQueue.isEvenLevel((1 << 31) - 2));
793     assertTrue(MinMaxPriorityQueue.isEvenLevel(Integer.MAX_VALUE - 1));
794     try {
795       MinMaxPriorityQueue.isEvenLevel((1 << 31) - 1);
796       fail("Should overflow");
797     } catch (IllegalStateException e) {
798       // expected
799     }
800     try {
801       MinMaxPriorityQueue.isEvenLevel(Integer.MAX_VALUE);
802       fail("Should overflow");
803     } catch (IllegalStateException e) {
804       // expected
805     }
806     try {
807       MinMaxPriorityQueue.isEvenLevel(1 << 31);
808       fail("Should be negative");
809     } catch (IllegalStateException e) {
810       // expected
811     }
812     try {
813       MinMaxPriorityQueue.isEvenLevel(Integer.MIN_VALUE);
814       fail("Should be negative");
815     } catch (IllegalStateException e) {
816       // expected
817     }
818   }
819 
testNullPointers()820   public void testNullPointers() {
821     NullPointerTester tester = new NullPointerTester();
822     tester.testAllPublicConstructors(MinMaxPriorityQueue.class);
823     tester.testAllPublicStaticMethods(MinMaxPriorityQueue.class);
824     tester.testAllPublicInstanceMethods(MinMaxPriorityQueue.<String>create());
825   }
826 
insertIntoReplica( Map<Integer, AtomicInteger> replica, int newValue)827   private static void insertIntoReplica(
828       Map<Integer, AtomicInteger> replica,
829       int newValue) {
830     if (replica.containsKey(newValue)) {
831       replica.get(newValue).incrementAndGet();
832     } else {
833       replica.put(newValue, new AtomicInteger(1));
834     }
835   }
836 
removeMinFromReplica( SortedMap<Integer, AtomicInteger> replica, int minValue)837   private static void removeMinFromReplica(
838       SortedMap<Integer, AtomicInteger> replica,
839       int minValue) {
840     Integer replicatedMinValue = replica.firstKey();
841     assertEquals(replicatedMinValue, (Integer) minValue);
842     removeFromReplica(replica, replicatedMinValue);
843   }
844 
removeMaxFromReplica( SortedMap<Integer, AtomicInteger> replica, int maxValue)845   private static void removeMaxFromReplica(
846       SortedMap<Integer, AtomicInteger> replica,
847       int maxValue) {
848     Integer replicatedMaxValue = replica.lastKey();
849     assertTrue("maxValue is incorrect", replicatedMaxValue == maxValue);
850     removeFromReplica(replica, replicatedMaxValue);
851   }
852 
removeFromReplica( Map<Integer, AtomicInteger> replica, int value)853   private static void removeFromReplica(
854       Map<Integer, AtomicInteger> replica, int value) {
855     AtomicInteger numOccur = replica.get(value);
856     if (numOccur.decrementAndGet() == 0) {
857       replica.remove(value);
858     }
859   }
860 }
861