001/*
002 * Copyright (C) 2007 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.testers;
018
019import static com.google.common.collect.testing.Helpers.getMethod;
020import static com.google.common.collect.testing.features.CollectionSize.ZERO;
021import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS;
022import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES;
023import static com.google.common.collect.testing.features.MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION;
024import static com.google.common.collect.testing.features.MapFeature.SUPPORTS_PUT;
025import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows;
026
027import com.google.common.annotations.GwtCompatible;
028import com.google.common.annotations.GwtIncompatible;
029import com.google.common.annotations.J2ktIncompatible;
030import com.google.common.collect.testing.AbstractMapTester;
031import com.google.common.collect.testing.features.CollectionSize;
032import com.google.common.collect.testing.features.MapFeature;
033import com.google.errorprone.annotations.CanIgnoreReturnValue;
034import java.lang.reflect.Method;
035import java.util.ConcurrentModificationException;
036import java.util.Iterator;
037import java.util.Map.Entry;
038import org.junit.Ignore;
039
040/**
041 * A generic JUnit test which tests {@code put} operations on a map. Can't be invoked directly;
042 * please see {@link com.google.common.collect.testing.MapTestSuiteBuilder}.
043 *
044 * @author Chris Povirk
045 * @author Kevin Bourrillion
046 */
047@GwtCompatible(emulated = true)
048@Ignore("test runners must not instantiate and run this directly, only via suites we build")
049// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests.
050@SuppressWarnings("JUnit4ClassUsedInJUnit3")
051public class MapPutTester<K, V> extends AbstractMapTester<K, V> {
052  private Entry<K, V> nullKeyEntry;
053  private Entry<K, V> nullValueEntry;
054  private Entry<K, V> nullKeyValueEntry;
055  private Entry<K, V> presentKeyNullValueEntry;
056
057  @Override
058  public void setUp() throws Exception {
059    super.setUp();
060    nullKeyEntry = entry(null, v3());
061    nullValueEntry = entry(k3(), null);
062    nullKeyValueEntry = entry(null, null);
063    presentKeyNullValueEntry = entry(k0(), null);
064  }
065
066  @MapFeature.Require(SUPPORTS_PUT)
067  @CollectionSize.Require(absent = ZERO)
068  public void testPut_supportedPresent() {
069    assertEquals("put(present, value) should return the old value", v0(), getMap().put(k0(), v3()));
070    expectReplacement(entry(k0(), v3()));
071  }
072
073  @MapFeature.Require(SUPPORTS_PUT)
074  public void testPut_supportedNotPresent() {
075    assertNull("put(notPresent, value) should return null", put(e3()));
076    expectAdded(e3());
077  }
078
079  @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_PUT})
080  @CollectionSize.Require(absent = ZERO)
081  public void testPutAbsentConcurrentWithEntrySetIteration() {
082    assertThrows(
083        ConcurrentModificationException.class,
084        () -> {
085          Iterator<Entry<K, V>> iterator = getMap().entrySet().iterator();
086          put(e3());
087          iterator.next();
088        });
089  }
090
091  @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_PUT})
092  @CollectionSize.Require(absent = ZERO)
093  public void testPutAbsentConcurrentWithKeySetIteration() {
094    assertThrows(
095        ConcurrentModificationException.class,
096        () -> {
097          Iterator<K> iterator = getMap().keySet().iterator();
098          put(e3());
099          iterator.next();
100        });
101  }
102
103  @MapFeature.Require({FAILS_FAST_ON_CONCURRENT_MODIFICATION, SUPPORTS_PUT})
104  @CollectionSize.Require(absent = ZERO)
105  public void testPutAbsentConcurrentWithValueIteration() {
106    assertThrows(
107        ConcurrentModificationException.class,
108        () -> {
109          Iterator<V> iterator = getMap().values().iterator();
110          put(e3());
111          iterator.next();
112        });
113  }
114
115  @MapFeature.Require(absent = SUPPORTS_PUT)
116  public void testPut_unsupportedNotPresent() {
117    assertThrows(UnsupportedOperationException.class, () -> put(e3()));
118    expectUnchanged();
119    expectMissing(e3());
120  }
121
122  @MapFeature.Require(absent = SUPPORTS_PUT)
123  @CollectionSize.Require(absent = ZERO)
124  public void testPut_unsupportedPresentExistingValue() {
125    try {
126      assertEquals("put(present, existingValue) should return present or throw", v0(), put(e0()));
127    } catch (UnsupportedOperationException tolerated) {
128    }
129    expectUnchanged();
130  }
131
132  @MapFeature.Require(absent = SUPPORTS_PUT)
133  @CollectionSize.Require(absent = ZERO)
134  public void testPut_unsupportedPresentDifferentValue() {
135    assertThrows(UnsupportedOperationException.class, () -> getMap().put(k0(), v3()));
136    expectUnchanged();
137  }
138
139  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_KEYS})
140  public void testPut_nullKeySupportedNotPresent() {
141    assertNull("put(null, value) should return null", put(nullKeyEntry));
142    expectAdded(nullKeyEntry);
143  }
144
145  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_KEYS})
146  @CollectionSize.Require(absent = ZERO)
147  public void testPut_nullKeySupportedPresent() {
148    Entry<K, V> newEntry = entry(null, v3());
149    initMapWithNullKey();
150    assertEquals(
151        "put(present, value) should return the associated value",
152        getValueForNullKey(),
153        put(newEntry));
154
155    Entry<K, V>[] expected = createArrayWithNullKey();
156    expected[getNullLocation()] = newEntry;
157    expectContents(expected);
158  }
159
160  @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_KEYS)
161  public void testPut_nullKeyUnsupported() {
162    assertThrows(NullPointerException.class, () -> put(nullKeyEntry));
163    expectUnchanged();
164    expectNullKeyMissingWhenNullKeysUnsupported(
165        "Should not contain null key after unsupported put(null, value)");
166  }
167
168  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES})
169  public void testPut_nullValueSupported() {
170    assertNull("put(key, null) should return null", put(nullValueEntry));
171    expectAdded(nullValueEntry);
172  }
173
174  @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES)
175  public void testPut_nullValueUnsupported() {
176    assertThrows(NullPointerException.class, () -> put(nullValueEntry));
177    expectUnchanged();
178    expectNullValueMissingWhenNullValuesUnsupported(
179        "Should not contain null value after unsupported put(key, null)");
180  }
181
182  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES})
183  @CollectionSize.Require(absent = ZERO)
184  public void testPut_replaceWithNullValueSupported() {
185    assertEquals(
186        "put(present, null) should return the associated value",
187        v0(),
188        put(presentKeyNullValueEntry));
189    expectReplacement(presentKeyNullValueEntry);
190  }
191
192  @MapFeature.Require(value = SUPPORTS_PUT, absent = ALLOWS_NULL_VALUES)
193  @CollectionSize.Require(absent = ZERO)
194  public void testPut_replaceWithNullValueUnsupported() {
195    assertThrows(NullPointerException.class, () -> put(presentKeyNullValueEntry));
196    expectUnchanged();
197    expectNullValueMissingWhenNullValuesUnsupported(
198        "Should not contain null after unsupported put(present, null)");
199  }
200
201  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES})
202  @CollectionSize.Require(absent = ZERO)
203  public void testPut_replaceNullValueWithNullSupported() {
204    initMapWithNullValue();
205    assertNull(
206        "put(present, null) should return the associated value (null)",
207        getMap().put(getKeyForNullValue(), null));
208    expectContents(createArrayWithNullValue());
209  }
210
211  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_VALUES})
212  @CollectionSize.Require(absent = ZERO)
213  public void testPut_replaceNullValueWithNonNullSupported() {
214    Entry<K, V> newEntry = entry(getKeyForNullValue(), v3());
215    initMapWithNullValue();
216    assertNull("put(present, value) should return the associated value (null)", put(newEntry));
217
218    Entry<K, V>[] expected = createArrayWithNullValue();
219    expected[getNullLocation()] = newEntry;
220    expectContents(expected);
221  }
222
223  @MapFeature.Require({SUPPORTS_PUT, ALLOWS_NULL_KEYS, ALLOWS_NULL_VALUES})
224  public void testPut_nullKeyAndValueSupported() {
225    assertNull("put(null, null) should return null", put(nullKeyValueEntry));
226    expectAdded(nullKeyValueEntry);
227  }
228
229  @CanIgnoreReturnValue
230  private V put(Entry<K, V> entry) {
231    return getMap().put(entry.getKey(), entry.getValue());
232  }
233
234  /**
235   * Returns the {@link Method} instance for {@link #testPut_nullKeyUnsupported()} so that tests of
236   * {@link java.util.TreeMap} can suppress it with {@code
237   * FeatureSpecificTestSuiteBuilder.suppressing()} until <a
238   * href="https://bugs.openjdk.org/browse/JDK-5045147">JDK-5045147</a> is fixed.
239   */
240  @J2ktIncompatible
241  @GwtIncompatible // reflection
242  public static Method getPutNullKeyUnsupportedMethod() {
243    return getMethod(MapPutTester.class, "testPut_nullKeyUnsupported");
244  }
245}