1 /*
   2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   3  *
   4  * This code is free software; you can redistribute it and/or modify it
   5  * under the terms of the GNU General Public License version 2 only, as
   6  * published by the Free Software Foundation.
   7  *
   8  * This code is distributed in the hope that it will be useful, but WITHOUT
   9  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  10  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  11  * version 2 for more details (a copy is included in the LICENSE file that
  12  * accompanied this code).
  13  *
  14  * You should have received a copy of the GNU General Public License version
  15  * 2 along with this work; if not, write to the Free Software Foundation,
  16  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  17  *
  18  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  19  * or visit www.oracle.com if you need additional information or have any
  20  * questions.
  21  */
  22 
  23 /*
  24  * This file is available under and governed by the GNU General Public
  25  * License version 2 only, as published by the Free Software Foundation.
  26  * However, the following notice accompanied the original version of this
  27  * file:
  28  *
  29  * Written by Doug Lea and Martin Buchholz with assistance from
  30  * members of JCP JSR-166 Expert Group and released to the public
  31  * domain, as explained at
  32  * http://creativecommons.org/publicdomain/zero/1.0/
  33  */
  34 
  35 import junit.framework.Test;
  36 
  37 import java.util.ArrayList;
  38 import java.util.Iterator;
  39 import java.util.List;
  40 import java.util.Map;
  41 import java.util.concurrent.ThreadLocalRandom;
  42 
  43 /**
  44  * Contains tests applicable to all Map implementations.
  45  */
  46 public class MapTest extends JSR166TestCase {
  47     final MapImplementation impl;
  48 
  49     /** Tests are parameterized by a Map implementation. */
  50     MapTest(MapImplementation impl, String methodName) {
  51         super(methodName);
  52         this.impl = impl;
  53     }
  54 
  55     public static Test testSuite(MapImplementation impl) {
  56         return newTestSuite(
  57             parameterizedTestSuite(MapTest.class,
  58                                    MapImplementation.class,
  59                                    impl));
  60     }
  61 
  62     public void testImplSanity() {
  63         final ThreadLocalRandom rnd = ThreadLocalRandom.current();
  64         {
  65             Map m = impl.emptyMap();
  66             assertTrue(m.isEmpty());
  67             assertEquals(0, m.size());
  68             Object k = impl.makeKey(rnd.nextInt());
  69             Object v = impl.makeValue(rnd.nextInt());
  70             m.put(k, v);
  71             assertFalse(m.isEmpty());
  72             assertEquals(1, m.size());
  73             assertTrue(m.containsKey(k));
  74             assertTrue(m.containsValue(v));
  75         }
  76         {
  77             Map m = impl.emptyMap();
  78             Object v = impl.makeValue(rnd.nextInt());
  79             if (impl.permitsNullKeys()) {
  80                 m.put(null, v);
  81                 assertTrue(m.containsKey(null));
  82                 assertTrue(m.containsValue(v));
  83             } else {
  84                 assertThrows(NullPointerException.class, () -> m.put(null, v));
  85             }
  86         }
  87         {
  88             Map m = impl.emptyMap();
  89             Object k = impl.makeKey(rnd.nextInt());
  90             if (impl.permitsNullValues()) {
  91                 m.put(k, null);
  92                 assertTrue(m.containsKey(k));
  93                 assertTrue(m.containsValue(null));
  94             } else {
  95                 assertThrows(NullPointerException.class, () -> m.put(k, null));
  96             }
  97         }
  98         {
  99             Map m = impl.emptyMap();
 100             Object k = impl.makeKey(rnd.nextInt());
 101             Object v1 = impl.makeValue(rnd.nextInt());
 102             Object v2 = impl.makeValue(rnd.nextInt());
 103             m.put(k, v1);
 104             if (impl.supportsSetValue()) {
 105                 ((Map.Entry)(m.entrySet().iterator().next())).setValue(v2);
 106                 assertSame(v2, m.get(k));
 107                 assertTrue(m.containsKey(k));
 108                 assertTrue(m.containsValue(v2));
 109                 assertFalse(m.containsValue(v1));
 110             } else {
 111                 assertThrows(UnsupportedOperationException.class,
 112                              () -> ((Map.Entry)(m.entrySet().iterator().next())).setValue(v2));
 113             }
 114         }
 115     }
 116 
 117     /**
 118      * Tests and extends the scenario reported in
 119      * https://bugs.openjdk.java.net/browse/JDK-8186171
 120      * HashMap: Entry.setValue may not work after Iterator.remove() called for previous entries
 121      * ant -Djsr166.tckTestClass=HashMapTest -Djsr166.methodFilter=testBug8186171 -Djsr166.runsPerTest=1000 tck
 122      */
 123     public void testBug8186171() {
 124         if (!impl.supportsSetValue()) return;
 125         final ThreadLocalRandom rnd = ThreadLocalRandom.current();
 126         final boolean permitsNullValues = impl.permitsNullValues();
 127         final Object v1 = (permitsNullValues && rnd.nextBoolean())
 128             ? null : impl.makeValue(1);
 129         final Object v2 = (permitsNullValues && rnd.nextBoolean() && v1 != null)
 130             ? null : impl.makeValue(2);
 131 
 132         // If true, always lands in first bucket in hash tables.
 133         final boolean poorHash = rnd.nextBoolean();
 134         class Key implements Comparable<Key> {
 135             final int i;
 136             Key(int i) { this.i = i; }
 137             public int hashCode() { return poorHash ? 0 : super.hashCode(); }
 138             public int compareTo(Key x) {
 139                 return Integer.compare(this.i, x.i);
 140             }
 141         }
 142 
 143         // Both HashMap and ConcurrentHashMap have:
 144         // TREEIFY_THRESHOLD = 8; UNTREEIFY_THRESHOLD = 6;
 145         final int size = rnd.nextInt(1, 25);
 146 
 147         List<Key> keys = new ArrayList<>();
 148         for (int i = size; i-->0; ) keys.add(new Key(i));
 149         Key keyToFrob = keys.get(rnd.nextInt(keys.size()));
 150 
 151         Map<Key, Object> m = impl.emptyMap();
 152         for (Key key : keys) m.put(key, v1);
 153 
 154         for (Iterator<Map.Entry<Key, Object>> it = m.entrySet().iterator();
 155              it.hasNext(); ) {
 156             Map.Entry<Key, Object> entry = it.next();
 157             if (entry.getKey() == keyToFrob)
 158                 entry.setValue(v2); // does this have the expected effect?
 159             else
 160                 it.remove();
 161         }
 162 
 163         assertFalse(m.containsValue(v1));
 164         assertTrue(m.containsValue(v2));
 165         assertTrue(m.containsKey(keyToFrob));
 166         assertEquals(1, m.size());
 167     }
 168 
 169     /**
 170      * "Missing" test found while investigating JDK-8210280.
 171      * See discussion on mailing list.
 172      * TODO: randomize
 173      */
 174     public void testBug8210280() {
 175         Map m = impl.emptyMap();
 176         for (int i = 0; i < 4; i++) m.put(7 + i * 16, 0);
 177         Map more = impl.emptyMap();
 178         for (int i = 0; i < 128; i++) more.put(-i, 42);
 179         m.putAll(more);
 180         for (int i = 0; i < 4; i++) assertEquals(0, m.get(7 + i * 16));
 181     }
 182 
 183 //     public void testFailsIntentionallyForDebugging() {
 184 //         fail(impl.klazz().getSimpleName());
 185 //     }
 186 }