/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.commons.collections.collection; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import org.apache.commons.collections.AbstractTestObject; /** * Abstract test class for {@link java.util.Collection} methods and contracts. * <p> * You should create a concrete subclass of this class to test any custom * {@link Collection} implementation. At minimum, you'll have to * implement the {@link #makeCollection()} method. You might want to * override some of the additional public methods as well: * <p> * <b>Element Population Methods</b> * <p> * Override these if your collection restricts what kind of elements are * allowed (for instance, if <code>null</code> is not permitted): * <ul> * <li>{@link #getFullElements()} * <li>{@link #getOtherElements()} * </ul> * <p> * <b>Supported Operation Methods</b> * <p> * Override these if your collection doesn't support certain operations: * <ul> * <li>{@link #isAddSupported()} * <li>{@link #isRemoveSupported()} * <li>{@link #areEqualElementsDistinguishable()} * <li>{@link #isNullSupported()} * <li>{@link #isFailFastSupported()} * </ul> * <p> * <b>Fixture Methods</b> * <p> * Fixtures are used to verify that the the operation results in correct state * for the collection. Basically, the operation is performed against your * collection implementation, and an identical operation is performed against a * <i>confirmed</i> collection implementation. A confirmed collection * implementation is something like <code>java.util.ArrayList</code>, which is * known to conform exactly to its collection interface's contract. After the * operation takes place on both your collection implementation and the * confirmed collection implementation, the two collections are compared to see * if their state is identical. The comparison is usually much more involved * than a simple <code>equals</code> test. This verification is used to ensure * proper modifications are made along with ensuring that the collection does * not change when read-only modifications are made. * <p> * The {@link #collection} field holds an instance of your collection * implementation; the {@link #confirmed} field holds an instance of the * confirmed collection implementation. The {@link #resetEmpty()} and * {@link #resetFull()} methods set these fields to empty or full collections, * so that tests can proceed from a known state. * <p> * After a modification operation to both {@link #collection} and * {@link #confirmed}, the {@link #verify()} method is invoked to compare * the results. You may want to override {@link #verify()} to perform * additional verifications. For instance, when testing the collection * views of a map, {@link AbstractTestMap} would override {@link #verify()} to make * sure the map is changed after the collection view is changed. * <p> * If you're extending this class directly, you will have to provide * implementations for the following: * <ul> * <li>{@link #makeConfirmedCollection()} * <li>{@link #makeConfirmedFullCollection()} * </ul> * <p> * Those methods should provide a confirmed collection implementation * that's compatible with your collection implementation. * <p> * If you're extending {@link AbstractTestList}, {@link AbstractTestSet}, * or {@link AbstractTestBag}, you probably don't have to worry about the * above methods, because those three classes already override the methods * to provide standard JDK confirmed collections.<P> * <p> * <b>Other notes</b> * <p> * If your {@link Collection} fails one of these tests by design, * you may still use this base set of cases. Simply override the * test case (method) your {@link Collection} fails. * * @version $Revision: 646780 $ $Date: 2008-04-10 13:48:07 +0100 (Thu, 10 Apr 2008) $ * * @author Rodney Waldhoff * @author Paul Jack * @author Michael A. Smith * @author Neil O'Toole * @author Stephen Colebourne */ public abstract class AbstractTestCollection extends AbstractTestObject { // // NOTE: // // Collection doesn't define any semantics for equals, and recommends you // use reference-based default behavior of Object.equals. (And a test for // that already exists in AbstractTestObject). Tests for equality of lists, sets // and bags will have to be written in test subclasses. Thus, there is no // tests on Collection.equals nor any for Collection.hashCode. // // These fields are used by reset() and verify(), and any test // method that tests a modification. /** * A collection instance that will be used for testing. */ public Collection collection; /** * Confirmed collection. This is an instance of a collection that is * confirmed to conform exactly to the java.util.Collection contract. * Modification operations are tested by performing a mod on your * collection, performing the exact same mod on an equivalent confirmed * collection, and then calling verify() to make sure your collection * still matches the confirmed collection. */ public Collection confirmed; /** * JUnit constructor. * * @param testName the test class name */ public AbstractTestCollection(String testName) { super(testName); } //----------------------------------------------------------------------- /** * Specifies whether equal elements in the collection are, in fact, * distinguishable with information not readily available. That is, if a * particular value is to be removed from the collection, then there is * one and only one value that can be removed, even if there are other * elements which are equal to it. * * <P>In most collection cases, elements are not distinguishable (equal is * equal), thus this method defaults to return false. In some cases, * however, they are. For example, the collection returned from the map's * values() collection view are backed by the map, so while there may be * two values that are equal, their associated keys are not. Since the * keys are distinguishable, the values are. * * <P>This flag is used to skip some verifications for iterator.remove() * where it is impossible to perform an equivalent modification on the * confirmed collection because it is not possible to determine which * value in the confirmed collection to actually remove. Tests that * override the default (i.e. where equal elements are distinguishable), * should provide additional tests on iterator.remove() to make sure the * proper elements are removed when remove() is called on the iterator. **/ public boolean areEqualElementsDistinguishable() { return false; } /** * Returns true if the collections produced by * {@link #makeCollection()} and {@link #makeFullCollection()} * support the <code>add</code> and <code>addAll</code> * operations.<P> * Default implementation returns true. Override if your collection * class does not support add or addAll. */ public boolean isAddSupported() { return true; } /** * Returns true if the collections produced by * {@link #makeCollection()} and {@link #makeFullCollection()} * support the <code>remove</code>, <code>removeAll</code>, * <code>retainAll</code>, <code>clear</code> and * <code>iterator().remove()</code> methods. * Default implementation returns true. Override if your collection * class does not support removal operations. */ public boolean isRemoveSupported() { return true; } /** * Returns true to indicate that the collection supports holding null. * The default implementation returns true; */ public boolean isNullSupported() { return true; } /** * Returns true to indicate that the collection supports fail fast iterators. * The default implementation returns true; */ public boolean isFailFastSupported() { return false; } /** * Returns true to indicate that the collection supports equals() comparisons. * This implementation returns false; */ public boolean isEqualsCheckable() { return false; } //----------------------------------------------------------------------- /** * Verifies that {@link #collection} and {@link #confirmed} have * identical state. */ public void verify() { int confirmedSize = confirmed.size(); assertEquals("Collection size should match confirmed collection's", confirmedSize, collection.size()); assertEquals("Collection isEmpty() result should match confirmed " + " collection's", confirmed.isEmpty(), collection.isEmpty()); // verify the collections are the same by attempting to match each // object in the collection and confirmed collection. To account for // duplicates and differing orders, each confirmed element is copied // into an array and a flag is maintained for each element to determine // whether it has been matched once and only once. If all elements in // the confirmed collection are matched once and only once and there // aren't any elements left to be matched in the collection, // verification is a success. // copy each collection value into an array Object[] confirmedValues = new Object[confirmedSize]; Iterator iter; iter = confirmed.iterator(); int pos = 0; while(iter.hasNext()) { confirmedValues[pos++] = iter.next(); } // allocate an array of boolean flags for tracking values that have // been matched once and only once. boolean[] matched = new boolean[confirmedSize]; // now iterate through the values of the collection and try to match // the value with one in the confirmed array. iter = collection.iterator(); while(iter.hasNext()) { Object o = iter.next(); boolean match = false; for(int i = 0; i < confirmedSize; i++) { if(matched[i]) { // skip values already matched continue; } if(o == confirmedValues[i] || (o != null && o.equals(confirmedValues[i]))) { // values matched matched[i] = true; match = true; break; } } // no match found! if(!match) { fail("Collection should not contain a value that the " + "confirmed collection does not have: " + o + "\nTest: " + collection + "\nReal: " + confirmed); } } // make sure there aren't any unmatched values for(int i = 0; i < confirmedSize; i++) { if(!matched[i]) { // the collection didn't match all the confirmed values fail("Collection should contain all values that are in the confirmed collection" + "\nTest: " + collection + "\nReal: " + confirmed); } } } //----------------------------------------------------------------------- /** * Resets the {@link #collection} and {@link #confirmed} fields to empty * collections. Invoke this method before performing a modification * test. */ public void resetEmpty() { this.collection = makeCollection(); this.confirmed = makeConfirmedCollection(); } /** * Resets the {@link #collection} and {@link #confirmed} fields to full * collections. Invoke this method before performing a modification * test. */ public void resetFull() { this.collection = makeFullCollection(); this.confirmed = makeConfirmedFullCollection(); } //----------------------------------------------------------------------- /** * Returns a confirmed empty collection. * For instance, an {@link java.util.ArrayList} for lists or a * {@link java.util.HashSet} for sets. * * @return a confirmed empty collection */ public abstract Collection makeConfirmedCollection(); /** * Returns a confirmed full collection. * For instance, an {@link java.util.ArrayList} for lists or a * {@link java.util.HashSet} for sets. The returned collection * should contain the elements returned by {@link #getFullElements()}. * * @return a confirmed full collection */ public abstract Collection makeConfirmedFullCollection(); /** * Return a new, empty {@link Collection} to be used for testing. */ public abstract Collection makeCollection(); /** * Returns a full collection to be used for testing. The collection * returned by this method should contain every element returned by * {@link #getFullElements()}. The default implementation, in fact, * simply invokes <code>addAll</code> on an empty collection with * the results of {@link #getFullElements()}. Override this default * if your collection doesn't support addAll. */ public Collection makeFullCollection() { Collection c = makeCollection(); c.addAll(Arrays.asList(getFullElements())); return c; } /** * Returns an empty collection for Object tests. */ public Object makeObject() { return makeCollection(); } /** * Creates a new Map Entry that is independent of the first and the map. */ public Map.Entry cloneMapEntry(Map.Entry entry) { HashMap map = new HashMap(); map.put(entry.getKey(), entry.getValue()); return (Map.Entry) map.entrySet().iterator().next(); } //----------------------------------------------------------------------- /** * Returns an array of objects that are contained in a collection * produced by {@link #makeFullCollection()}. Every element in the * returned array <I>must</I> be an element in a full collection.<P> * The default implementation returns a heterogenous array of * objects with some duplicates. null is added if allowed. * Override if you require specific testing elements. Note that if you * override {@link #makeFullCollection()}, you <I>must</I> override * this method to reflect the contents of a full collection. */ public Object[] getFullElements() { if (isNullSupported()) { ArrayList list = new ArrayList(); list.addAll(Arrays.asList(getFullNonNullElements())); list.add(4, null); return list.toArray(); } else { return (Object[]) getFullNonNullElements().clone(); } } /** * Returns an array of elements that are <I>not</I> contained in a * full collection. Every element in the returned array must * not exist in a collection returned by {@link #makeFullCollection()}. * The default implementation returns a heterogenous array of elements * without null. Note that some of the tests add these elements * to an empty or full collection, so if your collection restricts * certain kinds of elements, you should override this method. */ public Object[] getOtherElements() { return getOtherNonNullElements(); } //----------------------------------------------------------------------- /** * Returns a list of elements suitable for return by * {@link #getFullElements()}. The array returned by this method * does not include null, but does include a variety of objects * of different types. Override getFullElements to return * the results of this method if your collection does not support * the null element. */ public Object[] getFullNonNullElements() { return new Object[] { new String(""), new String("One"), new Integer(2), "Three", new Integer(4), "One", new Double(5), new Float(6), "Seven", "Eight", new String("Nine"), new Integer(10), new Short((short)11), new Long(12), "Thirteen", "14", "15", new Byte((byte)16) }; } /** * Returns the default list of objects returned by * {@link #getOtherElements()}. Includes many objects * of different types. */ public Object[] getOtherNonNullElements() { return new Object[] { new Integer(0), new Float(0), new Double(0), "Zero", new Short((short)0), new Byte((byte)0), new Long(0), new Character('\u0000'), "0" }; } /** * Returns a list of string elements suitable for return by * {@link #getFullElements()}. Override getFullElements to return * the results of this method if your collection does not support * heterogenous elements or the null element. */ public Object[] getFullNonNullStringElements() { return new Object[] { "If","the","dull","substance","of","my","flesh","were","thought", "Injurious","distance","could","not","stop","my","way", }; } /** * Returns a list of string elements suitable for return by * {@link #getOtherElements()}. Override getOtherElements to return * the results of this method if your collection does not support * heterogenous elements or the null element. */ public Object[] getOtherNonNullStringElements() { return new Object[] { "For","then","despite",/* of */"space","I","would","be","brought", "From","limits","far","remote","where","thou","dost","stay" }; } // Tests //----------------------------------------------------------------------- /** * Tests {@link Collection#add(Object)}. */ public void testCollectionAdd() { if (!isAddSupported()) return; Object[] elements = getFullElements(); for (int i = 0; i < elements.length; i++) { resetEmpty(); boolean r = collection.add(elements[i]); confirmed.add(elements[i]); verify(); assertTrue("Empty collection changed after add", r); assertEquals("Collection size is 1 after first add", 1, collection.size()); } resetEmpty(); int size = 0; for (int i = 0; i < elements.length; i++) { boolean r = collection.add(elements[i]); confirmed.add(elements[i]); verify(); if (r) size++; assertEquals("Collection size should grow after add", size, collection.size()); assertTrue("Collection should contain added element", collection.contains(elements[i])); } } /** * Tests {@link Collection#addAll(Collection)}. */ public void testCollectionAddAll() { if (!isAddSupported()) return; resetEmpty(); Object[] elements = getFullElements(); boolean r = collection.addAll(Arrays.asList(elements)); confirmed.addAll(Arrays.asList(elements)); verify(); assertTrue("Empty collection should change after addAll", r); for (int i = 0; i < elements.length; i++) { assertTrue("Collection should contain added element", collection.contains(elements[i])); } resetFull(); int size = collection.size(); elements = getOtherElements(); r = collection.addAll(Arrays.asList(elements)); confirmed.addAll(Arrays.asList(elements)); verify(); assertTrue("Full collection should change after addAll", r); for (int i = 0; i < elements.length; i++) { assertTrue("Full collection should contain added element", collection.contains(elements[i])); } assertEquals("Size should increase after addAll", size + elements.length, collection.size()); resetFull(); size = collection.size(); r = collection.addAll(Arrays.asList(getFullElements())); confirmed.addAll(Arrays.asList(getFullElements())); verify(); if (r) { assertTrue("Size should increase if addAll returns true", size < collection.size()); } else { assertEquals("Size should not change if addAll returns false", size, collection.size()); } } /** * If {@link #isAddSupported()} returns false, tests that add operations * raise <code>UnsupportedOperationException. */ public void testUnsupportedAdd() { if (isAddSupported()) return; resetEmpty(); try { collection.add(new Object()); fail("Emtpy collection should not support add."); } catch (UnsupportedOperationException e) { // expected } // make sure things didn't change even if the expected exception was // thrown. verify(); try { collection.addAll(Arrays.asList(getFullElements())); fail("Emtpy collection should not support addAll."); } catch (UnsupportedOperationException e) { // expected } // make sure things didn't change even if the expected exception was // thrown. verify(); resetFull(); try { collection.add(new Object()); fail("Full collection should not support add."); } catch (UnsupportedOperationException e) { // expected } // make sure things didn't change even if the expected exception was // thrown. verify(); try { collection.addAll(Arrays.asList(getOtherElements())); fail("Full collection should not support addAll."); } catch (UnsupportedOperationException e) { // expected } // make sure things didn't change even if the expected exception was // thrown. verify(); } /** * Test {@link Collection#clear()}. */ public void testCollectionClear() { if (!isRemoveSupported()) return; resetEmpty(); collection.clear(); // just to make sure it doesn't raise anything verify(); resetFull(); collection.clear(); confirmed.clear(); verify(); } /** * Tests {@link Collection#contains(Object)}. */ public void testCollectionContains() { Object[] elements; resetEmpty(); elements = getFullElements(); for(int i = 0; i < elements.length; i++) { assertTrue("Empty collection shouldn't contain element[" + i + "]", !collection.contains(elements[i])); } // make sure calls to "contains" don't change anything verify(); elements = getOtherElements(); for(int i = 0; i < elements.length; i++) { assertTrue("Empty collection shouldn't contain element[" + i + "]", !collection.contains(elements[i])); } // make sure calls to "contains" don't change anything verify(); resetFull(); elements = getFullElements(); for(int i = 0; i < elements.length; i++) { assertTrue("Full collection should contain element[" + i + "]", collection.contains(elements[i])); } // make sure calls to "contains" don't change anything verify(); resetFull(); elements = getOtherElements(); for(int i = 0; i < elements.length; i++) { assertTrue("Full collection shouldn't contain element", !collection.contains(elements[i])); } } /** * Tests {@link Collection#containsAll(Collection)}. */ public void testCollectionContainsAll() { resetEmpty(); Collection col = new HashSet(); assertTrue("Every Collection should contain all elements of an " + "empty Collection.", collection.containsAll(col)); col.addAll(Arrays.asList(getOtherElements())); assertTrue("Empty Collection shouldn't contain all elements of " + "a non-empty Collection.", !collection.containsAll(col)); // make sure calls to "containsAll" don't change anything verify(); resetFull(); assertTrue("Full collection shouldn't contain other elements", !collection.containsAll(col)); col.clear(); col.addAll(Arrays.asList(getFullElements())); assertTrue("Full collection should containAll full elements", collection.containsAll(col)); // make sure calls to "containsAll" don't change anything verify(); int min = (getFullElements().length < 2 ? 0 : 2); int max = (getFullElements().length == 1 ? 1 : (getFullElements().length <= 5 ? getFullElements().length - 1 : 5)); col = Arrays.asList(getFullElements()).subList(min, max); assertTrue("Full collection should containAll partial full " + "elements", collection.containsAll(col)); assertTrue("Full collection should containAll itself", collection.containsAll(collection)); // make sure calls to "containsAll" don't change anything verify(); col = new ArrayList(); col.addAll(Arrays.asList(getFullElements())); col.addAll(Arrays.asList(getFullElements())); assertTrue("Full collection should containAll duplicate full " + "elements", collection.containsAll(col)); // make sure calls to "containsAll" don't change anything verify(); } /** * Tests {@link Collection#isEmpty()}. */ public void testCollectionIsEmpty() { resetEmpty(); assertEquals("New Collection should be empty.", true, collection.isEmpty()); // make sure calls to "isEmpty() don't change anything verify(); resetFull(); assertEquals("Full collection shouldn't be empty", false, collection.isEmpty()); // make sure calls to "isEmpty() don't change anything verify(); } /** * Tests the read-only functionality of {@link Collection#iterator()}. */ public void testCollectionIterator() { resetEmpty(); Iterator it1 = collection.iterator(); assertEquals("Iterator for empty Collection shouldn't have next.", false, it1.hasNext()); try { it1.next(); fail("Iterator at end of Collection should throw " + "NoSuchElementException when next is called."); } catch(NoSuchElementException e) { // expected } // make sure nothing has changed after non-modification verify(); resetFull(); it1 = collection.iterator(); for (int i = 0; i < collection.size(); i++) { assertTrue("Iterator for full collection should haveNext", it1.hasNext()); it1.next(); } assertTrue("Iterator should be finished", !it1.hasNext()); ArrayList list = new ArrayList(); it1 = collection.iterator(); for (int i = 0; i < collection.size(); i++) { Object next = it1.next(); assertTrue("Collection should contain element returned by " + "its iterator", collection.contains(next)); list.add(next); } try { it1.next(); fail("iterator.next() should raise NoSuchElementException " + "after it finishes"); } catch (NoSuchElementException e) { // expected } // make sure nothing has changed after non-modification verify(); } /** * Tests removals from {@link Collection#iterator()}. */ public void testCollectionIteratorRemove() { if (!isRemoveSupported()) return; resetEmpty(); try { collection.iterator().remove(); fail("New iterator.remove should raise IllegalState"); } catch (IllegalStateException e) { // expected } verify(); try { Iterator iter = collection.iterator(); iter.hasNext(); iter.remove(); fail("New iterator.remove should raise IllegalState " + "even after hasNext"); } catch (IllegalStateException e) { // expected } verify(); resetFull(); int size = collection.size(); Iterator iter = collection.iterator(); while (iter.hasNext()) { Object o = iter.next(); // TreeMap reuses the Map Entry, so the verify below fails // Clone it here if necessary if (o instanceof Map.Entry) { o = cloneMapEntry((Map.Entry) o); } iter.remove(); // if the elements aren't distinguishable, we can just remove a // matching element from the confirmed collection and verify // contents are still the same. Otherwise, we don't have the // ability to distinguish the elements and determine which to // remove from the confirmed collection (in which case, we don't // verify because we don't know how). // // see areEqualElementsDistinguishable() if(!areEqualElementsDistinguishable()) { confirmed.remove(o); verify(); } size--; assertEquals("Collection should shrink by one after " + "iterator.remove", size, collection.size()); } assertTrue("Collection should be empty after iterator purge", collection.isEmpty()); resetFull(); iter = collection.iterator(); iter.next(); iter.remove(); try { iter.remove(); fail("Second iter.remove should raise IllegalState"); } catch (IllegalStateException e) { // expected } } /** * Tests {@link Collection#remove(Object)}. */ public void testCollectionRemove() { if (!isRemoveSupported()) return; resetEmpty(); Object[] elements = getFullElements(); for (int i = 0; i < elements.length; i++) { assertTrue("Shouldn't remove nonexistent element", !collection.remove(elements[i])); verify(); } Object[] other = getOtherElements(); resetFull(); for (int i = 0; i < other.length; i++) { assertTrue("Shouldn't remove nonexistent other element", !collection.remove(other[i])); verify(); } int size = collection.size(); for (int i = 0; i < elements.length; i++) { resetFull(); assertTrue("Collection should remove extant element: " + elements[i], collection.remove(elements[i])); // if the elements aren't distinguishable, we can just remove a // matching element from the confirmed collection and verify // contents are still the same. Otherwise, we don't have the // ability to distinguish the elements and determine which to // remove from the confirmed collection (in which case, we don't // verify because we don't know how). // // see areEqualElementsDistinguishable() if(!areEqualElementsDistinguishable()) { confirmed.remove(elements[i]); verify(); } assertEquals("Collection should shrink after remove", size - 1, collection.size()); } } /** * Tests {@link Collection#removeAll(Collection)}. */ public void testCollectionRemoveAll() { if (!isRemoveSupported()) return; resetEmpty(); assertTrue("Emtpy collection removeAll should return false for " + "empty input", !collection.removeAll(Collections.EMPTY_SET)); verify(); assertTrue("Emtpy collection removeAll should return false for " + "nonempty input", !collection.removeAll(new ArrayList(collection))); verify(); resetFull(); assertTrue("Full collection removeAll should return false for " + "empty input", !collection.removeAll(Collections.EMPTY_SET)); verify(); assertTrue("Full collection removeAll should return false for other elements", !collection.removeAll(Arrays.asList(getOtherElements()))); verify(); assertTrue("Full collection removeAll should return true for full elements", collection.removeAll(new HashSet(collection))); confirmed.removeAll(new HashSet(confirmed)); verify(); resetFull(); int size = collection.size(); int min = (getFullElements().length < 2 ? 0 : 2); int max = (getFullElements().length == 1 ? 1 : (getFullElements().length <= 5 ? getFullElements().length - 1 : 5)); Collection all = Arrays.asList(getFullElements()).subList(min, max); assertTrue("Full collection removeAll should work", collection.removeAll(all)); confirmed.removeAll(all); verify(); assertTrue("Collection should shrink after removeAll", collection.size() < size); Iterator iter = all.iterator(); while (iter.hasNext()) { assertTrue("Collection shouldn't contain removed element", !collection.contains(iter.next())); } } /** * Tests {@link Collection#retainAll(Collection)}. */ public void testCollectionRetainAll() { if (!isRemoveSupported()) return; resetEmpty(); List elements = Arrays.asList(getFullElements()); List other = Arrays.asList(getOtherElements()); assertTrue("Empty retainAll() should return false", !collection.retainAll(Collections.EMPTY_SET)); verify(); assertTrue("Empty retainAll() should return false", !collection.retainAll(elements)); verify(); resetFull(); assertTrue("Collection should change from retainAll empty", collection.retainAll(Collections.EMPTY_SET)); confirmed.retainAll(Collections.EMPTY_SET); verify(); resetFull(); assertTrue("Collection changed from retainAll other", collection.retainAll(other)); confirmed.retainAll(other); verify(); resetFull(); int size = collection.size(); assertTrue("Collection shouldn't change from retainAll elements", !collection.retainAll(elements)); verify(); assertEquals("Collection size shouldn't change", size, collection.size()); if (getFullElements().length > 1) { resetFull(); size = collection.size(); int min = (getFullElements().length < 2 ? 0 : 2); int max = (getFullElements().length <= 5 ? getFullElements().length - 1 : 5); assertTrue("Collection should changed by partial retainAll", collection.retainAll(elements.subList(min, max))); confirmed.retainAll(elements.subList(min, max)); verify(); Iterator iter = collection.iterator(); while (iter.hasNext()) { assertTrue("Collection only contains retained element", elements.subList(min, max).contains(iter.next())); } } resetFull(); HashSet set = new HashSet(elements); size = collection.size(); assertTrue("Collection shouldn't change from retainAll without " + "duplicate elements", !collection.retainAll(set)); verify(); assertEquals("Collection size didn't change from nonduplicate " + "retainAll", size, collection.size()); } /** * Tests {@link Collection#size()}. */ public void testCollectionSize() { resetEmpty(); assertEquals("Size of new Collection is 0.", 0, collection.size()); resetFull(); assertTrue("Size of full collection should be greater than zero", collection.size() > 0); } /** * Tests {@link Collection#toArray()}. */ public void testCollectionToArray() { resetEmpty(); assertEquals("Empty Collection should return empty array for toArray", 0, collection.toArray().length); resetFull(); Object[] array = collection.toArray(); assertEquals("Full collection toArray should be same size as " + "collection", array.length, collection.size()); Object[] confirmedArray = confirmed.toArray(); assertEquals("length of array from confirmed collection should " + "match the length of the collection's array", confirmedArray.length, array.length); boolean[] matched = new boolean[array.length]; for (int i = 0; i < array.length; i++) { assertTrue("Collection should contain element in toArray", collection.contains(array[i])); boolean match = false; // find a match in the confirmed array for(int j = 0; j < array.length; j++) { // skip already matched if(matched[j]) continue; if(array[i] == confirmedArray[j] || (array[i] != null && array[i].equals(confirmedArray[j]))) { matched[j] = true; match = true; break; } } if(!match) { fail("element " + i + " in returned array should be found " + "in the confirmed collection's array"); } } for(int i = 0; i < matched.length; i++) { assertEquals("Collection should return all its elements in " + "toArray", true, matched[i]); } } /** * Tests {@link Collection#toArray(Object[])}. */ public void testCollectionToArray2() { resetEmpty(); Object[] a = new Object[] { new Object(), null, null }; Object[] array = collection.toArray(a); assertEquals("Given array shouldn't shrink", array, a); assertEquals("Last element should be set to null", a[0], null); verify(); resetFull(); try { array = collection.toArray(new Void[collection.size()]); fail("toArray(new Void[0]) should raise ArrayStore"); } catch (ArrayStoreException e) { // expected } verify(); try { array = collection.toArray(null); fail("toArray(null) should raise NPE"); } catch (NullPointerException e) { // expected } verify(); array = collection.toArray(new Object[collection.size()]); a = collection.toArray(); assertEquals("toArrays should be equal", Arrays.asList(array), Arrays.asList(a)); // Figure out if they're all the same class // TODO: It'd be nicer to detect a common superclass HashSet classes = new HashSet(); for (int i = 0; i < array.length; i++) { classes.add((array[i] == null) ? null : array[i].getClass()); } if (classes.size() > 1) return; Class cl = (Class)classes.iterator().next(); if (Map.Entry.class.isAssignableFrom(cl)) { // check needed for protective cases like Predicated/Unmod map entrySet cl = Map.Entry.class; } a = (Object[])Array.newInstance(cl, 0); array = collection.toArray(a); assertEquals("toArray(Object[]) should return correct array type", a.getClass(), array.getClass()); assertEquals("type-specific toArrays should be equal", Arrays.asList(array), Arrays.asList(collection.toArray())); verify(); } /** * Tests <code>toString</code> on a collection. */ public void testCollectionToString() { resetEmpty(); assertTrue("toString shouldn't return null", collection.toString() != null); resetFull(); assertTrue("toString shouldn't return null", collection.toString() != null); } /** * If isRemoveSupported() returns false, tests to see that remove * operations raise an UnsupportedOperationException. */ public void testUnsupportedRemove() { if (isRemoveSupported()) return; resetEmpty(); try { collection.clear(); fail("clear should raise UnsupportedOperationException"); } catch (UnsupportedOperationException e) { // expected } verify(); try { collection.remove(null); fail("remove should raise UnsupportedOperationException"); } catch (UnsupportedOperationException e) { // expected } verify(); try { collection.removeAll(null); fail("removeAll should raise UnsupportedOperationException"); } catch (UnsupportedOperationException e) { // expected } verify(); try { collection.retainAll(null); fail("removeAll should raise UnsupportedOperationException"); } catch (UnsupportedOperationException e) { // expected } verify(); resetFull(); try { Iterator iterator = collection.iterator(); iterator.next(); iterator.remove(); fail("iterator.remove should raise UnsupportedOperationException"); } catch (UnsupportedOperationException e) { // expected } verify(); } /** * Tests that the collection's iterator is fail-fast. */ public void testCollectionIteratorFailFast() { if (!isFailFastSupported()) return; if (isAddSupported()) { resetFull(); try { Iterator iter = collection.iterator(); Object o = getOtherElements()[0]; collection.add(o); confirmed.add(o); iter.next(); fail("next after add should raise ConcurrentModification"); } catch (ConcurrentModificationException e) { // expected } verify(); resetFull(); try { Iterator iter = collection.iterator(); collection.addAll(Arrays.asList(getOtherElements())); confirmed.addAll(Arrays.asList(getOtherElements())); iter.next(); fail("next after addAll should raise ConcurrentModification"); } catch (ConcurrentModificationException e) { // expected } verify(); } if (!isRemoveSupported()) return; resetFull(); try { Iterator iter = collection.iterator(); collection.clear(); iter.next(); fail("next after clear should raise ConcurrentModification"); } catch (ConcurrentModificationException e) { // expected } catch (NoSuchElementException e) { // (also legal given spec) } resetFull(); try { Iterator iter = collection.iterator(); collection.remove(getFullElements()[0]); iter.next(); fail("next after remove should raise ConcurrentModification"); } catch (ConcurrentModificationException e) { // expected } resetFull(); try { Iterator iter = collection.iterator(); List sublist = Arrays.asList(getFullElements()).subList(2,5); collection.removeAll(sublist); iter.next(); fail("next after removeAll should raise ConcurrentModification"); } catch (ConcurrentModificationException e) { // expected } resetFull(); try { Iterator iter = collection.iterator(); List sublist = Arrays.asList(getFullElements()).subList(2,5); collection.retainAll(sublist); iter.next(); fail("next after retainAll should raise ConcurrentModification"); } catch (ConcurrentModificationException e) { // expected } } public void testSerializeDeserializeThenCompare() throws Exception { Object obj = makeCollection(); if (obj instanceof Serializable && isTestSerialization()) { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(buffer); out.writeObject(obj); out.close(); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray())); Object dest = in.readObject(); in.close(); if (isEqualsCheckable()) { assertEquals("obj != deserialize(serialize(obj)) - EMPTY Collection", obj, dest); } } obj = makeFullCollection(); if (obj instanceof Serializable && isTestSerialization()) { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(buffer); out.writeObject(obj); out.close(); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray())); Object dest = in.readObject(); in.close(); if (isEqualsCheckable()) { assertEquals("obj != deserialize(serialize(obj)) - FULL Collection", obj, dest); } } } }