001/*
002 * Copyright (C) 2009 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.google.common.collect.testing.google;
018
019import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES;
020import static com.google.common.collect.testing.features.CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION;
021import static com.google.common.collect.testing.features.CollectionFeature.RESTRICTS_ELEMENTS;
022import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_ADD;
023import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_REMOVE;
024import static com.google.common.collect.testing.features.CollectionSize.SEVERAL;
025import static com.google.common.collect.testing.features.CollectionSize.ZERO;
026import static com.google.common.collect.testing.google.ReflectionFreeAssertThrows.assertThrows;
027import static java.util.Arrays.asList;
028
029import com.google.common.annotations.GwtCompatible;
030import com.google.common.annotations.GwtIncompatible;
031import com.google.common.annotations.J2ktIncompatible;
032import com.google.common.collect.Multiset;
033import com.google.common.collect.Multiset.Entry;
034import com.google.common.collect.testing.Helpers;
035import com.google.common.collect.testing.features.CollectionFeature;
036import com.google.common.collect.testing.features.CollectionSize;
037import java.lang.reflect.Method;
038import java.util.ConcurrentModificationException;
039import java.util.Iterator;
040import java.util.List;
041import org.junit.Ignore;
042
043/**
044 * Common superclass for {@link MultisetSetCountUnconditionallyTester} and {@link
045 * MultisetSetCountConditionallyTester}. It is used by those testers to test calls to the
046 * unconditional {@code setCount()} method and calls to the conditional {@code setCount()} method
047 * when the expected present count is correct.
048 *
049 * @author Chris Povirk
050 */
051@GwtCompatible(emulated = true)
052@Ignore("test runners must not instantiate and run this directly, only via suites we build")
053// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests.
054@SuppressWarnings("JUnit4ClassUsedInJUnit3")
055public abstract class AbstractMultisetSetCountTester<E> extends AbstractMultisetTester<E> {
056  /*
057   * TODO: consider adding MultisetFeatures.SUPPORTS_SET_COUNT. Currently we
058   * assume that using setCount() to increase the count is permitted iff add()
059   * is permitted and similarly for decrease/remove(). We assume that a
060   * setCount() no-op is permitted if either add() or remove() is permitted,
061   * though we also allow it to "succeed" if neither is permitted.
062   */
063
064  private void assertSetCount(E element, int count) {
065    setCountCheckReturnValue(element, count);
066
067    assertEquals(
068        "multiset.count() should return the value passed to setCount()",
069        count,
070        getMultiset().count(element));
071
072    int size = 0;
073    for (Multiset.Entry<E> entry : getMultiset().entrySet()) {
074      size += entry.getCount();
075    }
076    assertEquals(
077        "multiset.size() should be the sum of the counts of all entries",
078        size,
079        getMultiset().size());
080  }
081
082  /** Call the {@code setCount()} method under test, and check its return value. */
083  abstract void setCountCheckReturnValue(E element, int count);
084
085  /**
086   * Call the {@code setCount()} method under test, but do not check its return value. Callers
087   * should use this method over {@link #setCountCheckReturnValue(Object, int)} when they expect
088   * {@code setCount()} to throw an exception, as checking the return value could produce an
089   * incorrect error message like "setCount() should return the original count" instead of the
090   * message passed to a later invocation of {@code fail()}, like "setCount should throw
091   * UnsupportedOperationException."
092   */
093  abstract void setCountNoCheckReturnValue(E element, int count);
094
095  private void assertSetCountIncreasingFailure(E element, int count) {
096    try {
097      setCountNoCheckReturnValue(element, count);
098      fail("a call to multiset.setCount() to increase an element's count should throw");
099    } catch (UnsupportedOperationException expected) {
100    }
101  }
102
103  private void assertSetCountDecreasingFailure(E element, int count) {
104    try {
105      setCountNoCheckReturnValue(element, count);
106      fail("a call to multiset.setCount() to decrease an element's count should throw");
107    } catch (UnsupportedOperationException expected) {
108    }
109  }
110
111  // Unconditional setCount no-ops.
112
113  private void assertZeroToZero() {
114    assertSetCount(e3(), 0);
115  }
116
117  private void assertOneToOne() {
118    assertSetCount(e0(), 1);
119  }
120
121  private void assertThreeToThree() {
122    initThreeCopies();
123    assertSetCount(e0(), 3);
124  }
125
126  @CollectionFeature.Require(SUPPORTS_ADD)
127  public void testSetCount_zeroToZero_addSupported() {
128    assertZeroToZero();
129  }
130
131  @CollectionFeature.Require(SUPPORTS_REMOVE)
132  public void testSetCount_zeroToZero_removeSupported() {
133    assertZeroToZero();
134  }
135
136  @CollectionFeature.Require(absent = {SUPPORTS_ADD, SUPPORTS_REMOVE})
137  public void testSetCount_zeroToZero_unsupported() {
138    try {
139      assertZeroToZero();
140    } catch (UnsupportedOperationException tolerated) {
141    }
142  }
143
144  @CollectionSize.Require(absent = ZERO)
145  @CollectionFeature.Require(SUPPORTS_ADD)
146  public void testSetCount_oneToOne_addSupported() {
147    assertOneToOne();
148  }
149
150  @CollectionSize.Require(absent = ZERO)
151  @CollectionFeature.Require(SUPPORTS_REMOVE)
152  public void testSetCount_oneToOne_removeSupported() {
153    assertOneToOne();
154  }
155
156  @CollectionSize.Require(absent = ZERO)
157  @CollectionFeature.Require(absent = {SUPPORTS_ADD, SUPPORTS_REMOVE})
158  public void testSetCount_oneToOne_unsupported() {
159    try {
160      assertOneToOne();
161    } catch (UnsupportedOperationException tolerated) {
162    }
163  }
164
165  @CollectionSize.Require(SEVERAL)
166  @CollectionFeature.Require(SUPPORTS_ADD)
167  public void testSetCount_threeToThree_addSupported() {
168    assertThreeToThree();
169  }
170
171  @CollectionSize.Require(SEVERAL)
172  @CollectionFeature.Require(SUPPORTS_REMOVE)
173  public void testSetCount_threeToThree_removeSupported() {
174    assertThreeToThree();
175  }
176
177  @CollectionSize.Require(SEVERAL)
178  @CollectionFeature.Require(absent = {SUPPORTS_ADD, SUPPORTS_REMOVE})
179  public void testSetCount_threeToThree_unsupported() {
180    try {
181      assertThreeToThree();
182    } catch (UnsupportedOperationException tolerated) {
183    }
184  }
185
186  // Unconditional setCount size increases:
187
188  @CollectionFeature.Require(SUPPORTS_ADD)
189  public void testSetCount_zeroToOne_supported() {
190    assertSetCount(e3(), 1);
191  }
192
193  @CollectionFeature.Require({SUPPORTS_ADD, FAILS_FAST_ON_CONCURRENT_MODIFICATION})
194  public void testSetCountZeroToOneConcurrentWithIteration() {
195    Iterator<E> iterator = collection.iterator();
196    assertSetCount(e3(), 1);
197    assertThrows(ConcurrentModificationException.class, iterator::next);
198  }
199
200  @CollectionFeature.Require({SUPPORTS_ADD, FAILS_FAST_ON_CONCURRENT_MODIFICATION})
201  public void testSetCountZeroToOneConcurrentWithEntrySetIteration() {
202    Iterator<Entry<E>> iterator = getMultiset().entrySet().iterator();
203    assertSetCount(e3(), 1);
204    assertThrows(ConcurrentModificationException.class, iterator::next);
205  }
206
207  @CollectionFeature.Require(SUPPORTS_ADD)
208  public void testSetCount_zeroToThree_supported() {
209    assertSetCount(e3(), 3);
210  }
211
212  @CollectionSize.Require(absent = ZERO)
213  @CollectionFeature.Require(SUPPORTS_ADD)
214  public void testSetCount_oneToThree_supported() {
215    assertSetCount(e0(), 3);
216  }
217
218  @CollectionFeature.Require(absent = SUPPORTS_ADD)
219  public void testSetCount_zeroToOne_unsupported() {
220    assertSetCountIncreasingFailure(e3(), 1);
221  }
222
223  @CollectionFeature.Require(absent = SUPPORTS_ADD)
224  public void testSetCount_zeroToThree_unsupported() {
225    assertSetCountIncreasingFailure(e3(), 3);
226  }
227
228  @CollectionSize.Require(absent = ZERO)
229  @CollectionFeature.Require(absent = SUPPORTS_ADD)
230  public void testSetCount_oneToThree_unsupported() {
231    assertSetCountIncreasingFailure(e3(), 3);
232  }
233
234  // Unconditional setCount size decreases:
235
236  @CollectionSize.Require(absent = ZERO)
237  @CollectionFeature.Require(SUPPORTS_REMOVE)
238  public void testSetCount_oneToZero_supported() {
239    assertSetCount(e0(), 0);
240  }
241
242  @CollectionFeature.Require({SUPPORTS_REMOVE, FAILS_FAST_ON_CONCURRENT_MODIFICATION})
243  @CollectionSize.Require(absent = ZERO)
244  public void testSetCountOneToZeroConcurrentWithIteration() {
245    Iterator<E> iterator = collection.iterator();
246    assertSetCount(e0(), 0);
247    assertThrows(ConcurrentModificationException.class, iterator::next);
248  }
249
250  @CollectionFeature.Require({SUPPORTS_REMOVE, FAILS_FAST_ON_CONCURRENT_MODIFICATION})
251  @CollectionSize.Require(absent = ZERO)
252  public void testSetCountOneToZeroConcurrentWithEntrySetIteration() {
253    Iterator<Entry<E>> iterator = getMultiset().entrySet().iterator();
254    assertSetCount(e0(), 0);
255    assertThrows(ConcurrentModificationException.class, iterator::next);
256  }
257
258  @CollectionSize.Require(SEVERAL)
259  @CollectionFeature.Require(SUPPORTS_REMOVE)
260  public void testSetCount_threeToZero_supported() {
261    initThreeCopies();
262    assertSetCount(e0(), 0);
263  }
264
265  @CollectionSize.Require(SEVERAL)
266  @CollectionFeature.Require(SUPPORTS_REMOVE)
267  public void testSetCount_threeToOne_supported() {
268    initThreeCopies();
269    assertSetCount(e0(), 1);
270  }
271
272  @CollectionSize.Require(absent = ZERO)
273  @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
274  public void testSetCount_oneToZero_unsupported() {
275    assertSetCountDecreasingFailure(e0(), 0);
276  }
277
278  @CollectionSize.Require(SEVERAL)
279  @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
280  public void testSetCount_threeToZero_unsupported() {
281    initThreeCopies();
282    assertSetCountDecreasingFailure(e0(), 0);
283  }
284
285  @CollectionSize.Require(SEVERAL)
286  @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
287  public void testSetCount_threeToOne_unsupported() {
288    initThreeCopies();
289    assertSetCountDecreasingFailure(e0(), 1);
290  }
291
292  // setCount with nulls:
293
294  @CollectionSize.Require(absent = ZERO)
295  @CollectionFeature.Require({SUPPORTS_REMOVE, ALLOWS_NULL_VALUES})
296  public void testSetCount_removeNull_nullSupported() {
297    initCollectionWithNullElement();
298    assertSetCount(null, 0);
299  }
300
301  @CollectionFeature.Require(
302      value = {SUPPORTS_ADD, ALLOWS_NULL_VALUES},
303      absent = RESTRICTS_ELEMENTS)
304  public void testSetCount_addNull_nullSupported() {
305    assertSetCount(null, 1);
306  }
307
308  @CollectionFeature.Require(value = SUPPORTS_ADD, absent = ALLOWS_NULL_VALUES)
309  public void testSetCount_addNull_nullUnsupported() {
310    assertThrows(NullPointerException.class, () -> setCountNoCheckReturnValue(null, 1));
311  }
312
313  @CollectionFeature.Require(ALLOWS_NULL_VALUES)
314  public void testSetCount_noOpNull_nullSupported() {
315    try {
316      assertSetCount(null, 0);
317    } catch (UnsupportedOperationException tolerated) {
318    }
319  }
320
321  @CollectionFeature.Require(absent = ALLOWS_NULL_VALUES)
322  public void testSetCount_noOpNull_nullUnsupported() {
323    try {
324      assertSetCount(null, 0);
325    } catch (NullPointerException | UnsupportedOperationException tolerated) {
326    }
327  }
328
329  @CollectionSize.Require(absent = ZERO)
330  @CollectionFeature.Require(ALLOWS_NULL_VALUES)
331  public void testSetCount_existingNoNopNull_nullSupported() {
332    initCollectionWithNullElement();
333    try {
334      assertSetCount(null, 1);
335    } catch (UnsupportedOperationException tolerated) {
336    }
337  }
338
339  // Negative count.
340
341  @CollectionFeature.Require(SUPPORTS_REMOVE)
342  public void testSetCount_negative_removeSupported() {
343    assertThrows(IllegalArgumentException.class, () -> setCountNoCheckReturnValue(e3(), -1));
344  }
345
346  @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
347  public void testSetCount_negative_removeUnsupported() {
348    try {
349      setCountNoCheckReturnValue(e3(), -1);
350      fail(
351          "calling setCount() with a negative count should throw "
352              + "IllegalArgumentException or UnsupportedOperationException");
353    } catch (IllegalArgumentException | UnsupportedOperationException expected) {
354    }
355  }
356
357  // TODO: test adding element of wrong type
358
359  /**
360   * Returns {@link Method} instances for the {@code setCount()} tests that assume multisets support
361   * duplicates so that the test of {@code Multisets.forSet()} can suppress them.
362   */
363  @J2ktIncompatible
364  @GwtIncompatible // reflection
365  public static List<Method> getSetCountDuplicateInitializingMethods() {
366    return asList(
367        getMethod("testSetCount_threeToThree_removeSupported"),
368        getMethod("testSetCount_threeToZero_supported"),
369        getMethod("testSetCount_threeToOne_supported"));
370  }
371
372  @J2ktIncompatible
373  @GwtIncompatible // reflection
374  private static Method getMethod(String methodName) {
375    return Helpers.getMethod(AbstractMultisetSetCountTester.class, methodName);
376  }
377}