001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.types; 019 020import static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertFalse; 022import static org.junit.jupiter.api.Assertions.assertNotNull; 023import static org.junit.jupiter.api.Assertions.assertNull; 024import static org.junit.jupiter.api.Assertions.assertTrue; 025 026import java.util.Map; 027import java.util.concurrent.ConcurrentNavigableMap; 028import java.util.concurrent.ConcurrentSkipListMap; 029import java.util.concurrent.ThreadLocalRandom; 030import org.apache.hadoop.hbase.testclassification.MediumTests; 031import org.apache.hadoop.hbase.testclassification.MiscTests; 032import org.junit.jupiter.api.BeforeEach; 033import org.junit.jupiter.api.Tag; 034import org.junit.jupiter.api.Test; 035 036@Tag(MiscTests.TAG) 037@Tag(MediumTests.TAG) 038public class TestCopyOnWriteMaps { 039 040 private static final int MAX_INITIAL_ENTRIES = 10_000; 041 private static final int MAX_RAND = 10 * 1000 * 1000; 042 private ConcurrentNavigableMap<Long, Long> m; 043 private ConcurrentSkipListMap<Long, Long> csm; 044 045 @BeforeEach 046 public void setUp() { 047 m = new CopyOnWriteArrayMap<>(); 048 csm = new ConcurrentSkipListMap<>(); 049 050 for (long i = 0; i < MAX_INITIAL_ENTRIES; i++) { 051 long o = ThreadLocalRandom.current().nextLong(MAX_RAND); 052 m.put(i, o); 053 csm.put(i, o); 054 } 055 long o = ThreadLocalRandom.current().nextLong(MAX_RAND); 056 m.put(0L, o); 057 csm.put(0L, o); 058 } 059 060 @Test 061 public void testPutIfAbsent() { 062 long key = MAX_INITIAL_ENTRIES * 2; 063 long val = ThreadLocalRandom.current().nextLong(MAX_RAND); 064 // initial call should return null, then should return previous value 065 assertNull(m.putIfAbsent(key, val)); 066 assertEquals(val, (long) m.putIfAbsent(key, val * 2)); 067 // test same with csm so we ensure similar semantics 068 assertNull(csm.putIfAbsent(key, val)); 069 assertEquals(val, (long) csm.putIfAbsent(key, val * 2)); 070 } 071 072 @Test 073 public void testSize() { 074 assertEquals(m.size(), csm.size(), "Size should always be equal"); 075 } 076 077 @Test 078 public void testIsEmpty() { 079 m.clear(); 080 assertTrue(m.isEmpty()); 081 m.put(100L, 100L); 082 assertFalse(m.isEmpty()); 083 m.remove(100L); 084 assertTrue(m.isEmpty()); 085 } 086 087 @Test 088 public void testFindOnEmpty() { 089 m.clear(); 090 assertTrue(m.isEmpty()); 091 assertNull(m.get(100L)); 092 assertFalse(m.containsKey(100L)); 093 assertEquals(0, m.tailMap(100L).entrySet().size()); 094 } 095 096 @Test 097 public void testLowerKey() { 098 assertEquals(csm.lowerKey(400L), m.lowerKey(400L)); 099 assertEquals(csm.lowerKey(-1L), m.lowerKey(-1L)); 100 101 for (int i = 0; i < 100; i++) { 102 Long key = ThreadLocalRandom.current().nextLong(); 103 assertEquals(csm.lowerKey(key), m.lowerKey(key)); 104 } 105 } 106 107 @Test 108 public void testFloorEntry() { 109 for (int i = 0; i < 100; i++) { 110 Long key = ThreadLocalRandom.current().nextLong(); 111 assertEquals(csm.floorEntry(key), m.floorEntry(key)); 112 } 113 } 114 115 @Test 116 public void testFloorKey() { 117 for (int i = 0; i < 100; i++) { 118 Long key = ThreadLocalRandom.current().nextLong(); 119 assertEquals(csm.floorKey(key), m.floorKey(key)); 120 } 121 } 122 123 @Test 124 public void testCeilingKey() { 125 assertEquals(csm.ceilingKey(4000L), m.ceilingKey(4000L)); 126 assertEquals(csm.ceilingKey(400L), m.ceilingKey(400L)); 127 assertEquals(csm.ceilingKey(-1L), m.ceilingKey(-1L)); 128 129 for (int i = 0; i < 100; i++) { 130 Long key = ThreadLocalRandom.current().nextLong(); 131 assertEquals(csm.ceilingKey(key), m.ceilingKey(key)); 132 } 133 } 134 135 @Test 136 public void testHigherKey() { 137 assertEquals(csm.higherKey(4000L), m.higherKey(4000L)); 138 assertEquals(csm.higherKey(400L), m.higherKey(400L)); 139 assertEquals(csm.higherKey(-1L), m.higherKey(-1L)); 140 141 for (int i = 0; i < 100; i++) { 142 Long key = ThreadLocalRandom.current().nextLong(); 143 assertEquals(csm.higherKey(key), m.higherKey(key)); 144 } 145 } 146 147 @Test 148 public void testRemove() { 149 for (Map.Entry<Long, Long> e : csm.entrySet()) { 150 assertEquals(csm.remove(e.getKey()), m.remove(e.getKey())); 151 assertNull(m.remove(e.getKey())); 152 } 153 } 154 155 @Test 156 public void testReplace() { 157 for (Map.Entry<Long, Long> e : csm.entrySet()) { 158 Long newValue = ThreadLocalRandom.current().nextLong(); 159 assertEquals(csm.replace(e.getKey(), newValue), m.replace(e.getKey(), newValue)); 160 } 161 assertNull(m.replace(MAX_RAND + 100L, ThreadLocalRandom.current().nextLong())); 162 } 163 164 @Test 165 public void testReplace1() { 166 for (Map.Entry<Long, Long> e : csm.entrySet()) { 167 Long newValue = ThreadLocalRandom.current().nextLong(); 168 assertEquals(csm.replace(e.getKey(), e.getValue() + 1, newValue), 169 m.replace(e.getKey(), e.getValue() + 1, newValue)); 170 assertEquals(csm.replace(e.getKey(), e.getValue(), newValue), 171 m.replace(e.getKey(), e.getValue(), newValue)); 172 assertEquals(newValue, m.get(e.getKey())); 173 assertEquals(csm.get(e.getKey()), m.get(e.getKey())); 174 } 175 assertNull(m.replace(MAX_RAND + 100L, ThreadLocalRandom.current().nextLong())); 176 } 177 178 @Test 179 public void testMultiAdd() throws InterruptedException { 180 Thread[] threads = new Thread[10]; 181 for (int i = 0; i < threads.length; i++) { 182 threads[i] = new Thread(() -> { 183 for (int j = 0; j < 5000; j++) { 184 m.put(ThreadLocalRandom.current().nextLong(), ThreadLocalRandom.current().nextLong()); 185 } 186 }); 187 } 188 189 for (Thread thread1 : threads) { 190 thread1.start(); 191 } 192 193 for (Thread thread2 : threads) { 194 thread2.join(); 195 } 196 } 197 198 @Test 199 public void testFirstKey() { 200 assertEquals(csm.firstKey(), m.firstKey()); 201 } 202 203 @Test 204 public void testLastKey() { 205 assertEquals(csm.lastKey(), m.lastKey()); 206 } 207 208 @Test 209 public void testFirstEntry() { 210 assertEquals(csm.firstEntry().getKey(), m.firstEntry().getKey()); 211 assertEquals(csm.firstEntry().getValue(), m.firstEntry().getValue()); 212 assertEquals(csm.firstEntry(), m.firstEntry()); 213 } 214 215 @Test 216 public void testLastEntry() { 217 assertEquals(csm.lastEntry().getKey(), m.lastEntry().getKey()); 218 assertEquals(csm.lastEntry().getValue(), m.lastEntry().getValue()); 219 assertEquals(csm.lastEntry(), m.lastEntry()); 220 } 221 222 @Test 223 public void testKeys() { 224 for (Long key : csm.keySet()) { 225 assertNotNull(m.get(key)); 226 assertNotNull(m.remove(key)); 227 assertNull(m.get(key)); 228 } 229 } 230 231 @Test 232 public void testValues() { 233 for (Long value : m.values()) { 234 assertTrue(csm.containsValue(value)); 235 assertTrue(m.containsValue(value)); 236 } 237 } 238 239 @Test 240 public void testTailMap() { 241 Map<Long, Long> fromCsm = csm.tailMap(50L); 242 Map<Long, Long> fromM = m.tailMap(50L); 243 assertEquals(fromCsm, fromM); 244 for (Long value : m.keySet()) { 245 assertEquals(csm.tailMap(value), m.tailMap(value)); 246 } 247 248 for (long i = 0; i < 100; i++) { 249 long o = ThreadLocalRandom.current().nextLong(MAX_RAND); 250 assertEquals(csm.tailMap(o), m.tailMap(o)); 251 } 252 } 253 254 @Test 255 public void testTailMapExclusive() { 256 m.clear(); 257 m.put(100L, 100L); 258 m.put(101L, 101L); 259 m.put(101L, 101L); 260 m.put(103L, 103L); 261 m.put(99L, 99L); 262 m.put(102L, 102L); 263 264 long n = 100L; 265 CopyOnWriteArrayMap<Long, Long> tm99 = (CopyOnWriteArrayMap<Long, Long>) m.tailMap(99L, false); 266 for (Map.Entry<Long, Long> e : tm99.entrySet()) { 267 assertEquals(Long.valueOf(n), e.getKey()); 268 assertEquals(Long.valueOf(n), e.getValue()); 269 n++; 270 } 271 } 272 273 @Test 274 public void testTailMapInclusive() { 275 m.clear(); 276 m.put(100L, 100L); 277 m.put(101L, 101L); 278 m.put(101L, 101L); 279 m.put(103L, 103L); 280 m.put(99L, 99L); 281 m.put(102L, 102L); 282 283 long n = 102; 284 CopyOnWriteArrayMap<Long, Long> tm102 = (CopyOnWriteArrayMap<Long, Long>) m.tailMap(102L, true); 285 for (Map.Entry<Long, Long> e : tm102.entrySet()) { 286 assertEquals(Long.valueOf(n), e.getKey()); 287 assertEquals(Long.valueOf(n), e.getValue()); 288 n++; 289 } 290 n = 99; 291 CopyOnWriteArrayMap<Long, Long> tm98 = (CopyOnWriteArrayMap<Long, Long>) m.tailMap(98L, true); 292 for (Map.Entry<Long, Long> e : tm98.entrySet()) { 293 assertEquals(Long.valueOf(n), e.getKey()); 294 assertEquals(Long.valueOf(n), e.getValue()); 295 n++; 296 } 297 } 298 299 @Test 300 public void testPut() { 301 m.clear(); 302 m.put(100L, 100L); 303 m.put(101L, 101L); 304 m.put(101L, 101L); 305 m.put(103L, 103L); 306 m.put(99L, 99L); 307 m.put(102L, 102L); 308 long n = 99; 309 310 for (Map.Entry<Long, Long> e : m.entrySet()) { 311 assertEquals(Long.valueOf(n), e.getKey()); 312 assertEquals(Long.valueOf(n), e.getValue()); 313 n++; 314 } 315 assertEquals(5, m.size()); 316 assertFalse(m.isEmpty()); 317 } 318}