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.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertNotNull;
023import static org.junit.Assert.assertNull;
024import static org.junit.Assert.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.HBaseClassTestRule;
031import org.apache.hadoop.hbase.testclassification.MediumTests;
032import org.apache.hadoop.hbase.testclassification.MiscTests;
033import org.junit.Before;
034import org.junit.ClassRule;
035import org.junit.Test;
036import org.junit.experimental.categories.Category;
037
038@Category({ MiscTests.class, MediumTests.class })
039public class TestCopyOnWriteMaps {
040  @ClassRule
041  public static final HBaseClassTestRule CLASS_RULE =
042    HBaseClassTestRule.forClass(TestCopyOnWriteMaps.class);
043
044  private static final int MAX_INITIAL_ENTRIES = 10_000;
045  private static final int MAX_RAND = 10 * 1000 * 1000;
046  private ConcurrentNavigableMap<Long, Long> m;
047  private ConcurrentSkipListMap<Long, Long> csm;
048
049  @Before
050  public void setUp() {
051    m = new CopyOnWriteArrayMap<>();
052    csm = new ConcurrentSkipListMap<>();
053
054    for (long i = 0; i < MAX_INITIAL_ENTRIES; i++) {
055      long o = ThreadLocalRandom.current().nextLong(MAX_RAND);
056      m.put(i, o);
057      csm.put(i, o);
058    }
059    long o = ThreadLocalRandom.current().nextLong(MAX_RAND);
060    m.put(0L, o);
061    csm.put(0L, o);
062  }
063
064  @Test
065  public void testPutIfAbsent() {
066    long key = MAX_INITIAL_ENTRIES * 2;
067    long val = ThreadLocalRandom.current().nextLong(MAX_RAND);
068    // initial call should return null, then should return previous value
069    assertNull(m.putIfAbsent(key, val));
070    assertEquals(val, (long) m.putIfAbsent(key, val * 2));
071    // test same with csm so we ensure similar semantics
072    assertNull(csm.putIfAbsent(key, val));
073    assertEquals(val, (long) csm.putIfAbsent(key, val * 2));
074  }
075
076  @Test
077  public void testSize() {
078    assertEquals("Size should always be equal", m.size(), csm.size());
079  }
080
081  @Test
082  public void testIsEmpty() {
083    m.clear();
084    assertTrue(m.isEmpty());
085    m.put(100L, 100L);
086    assertFalse(m.isEmpty());
087    m.remove(100L);
088    assertTrue(m.isEmpty());
089  }
090
091  @Test
092  public void testFindOnEmpty() {
093    m.clear();
094    assertTrue(m.isEmpty());
095    assertNull(m.get(100L));
096    assertFalse(m.containsKey(100L));
097    assertEquals(0, m.tailMap(100L).entrySet().size());
098  }
099
100  @Test
101  public void testLowerKey() {
102    assertEquals(csm.lowerKey(400L), m.lowerKey(400L));
103    assertEquals(csm.lowerKey(-1L), m.lowerKey(-1L));
104
105    for (int i = 0; i < 100; i++) {
106      Long key = ThreadLocalRandom.current().nextLong();
107      assertEquals(csm.lowerKey(key), m.lowerKey(key));
108    }
109  }
110
111  @Test
112  public void testFloorEntry() {
113    for (int i = 0; i < 100; i++) {
114      Long key = ThreadLocalRandom.current().nextLong();
115      assertEquals(csm.floorEntry(key), m.floorEntry(key));
116    }
117  }
118
119  @Test
120  public void testFloorKey() {
121    for (int i = 0; i < 100; i++) {
122      Long key = ThreadLocalRandom.current().nextLong();
123      assertEquals(csm.floorKey(key), m.floorKey(key));
124    }
125  }
126
127  @Test
128  public void testCeilingKey() {
129    assertEquals(csm.ceilingKey(4000L), m.ceilingKey(4000L));
130    assertEquals(csm.ceilingKey(400L), m.ceilingKey(400L));
131    assertEquals(csm.ceilingKey(-1L), m.ceilingKey(-1L));
132
133    for (int i = 0; i < 100; i++) {
134      Long key = ThreadLocalRandom.current().nextLong();
135      assertEquals(csm.ceilingKey(key), m.ceilingKey(key));
136    }
137  }
138
139  @Test
140  public void testHigherKey() {
141    assertEquals(csm.higherKey(4000L), m.higherKey(4000L));
142    assertEquals(csm.higherKey(400L), m.higherKey(400L));
143    assertEquals(csm.higherKey(-1L), m.higherKey(-1L));
144
145    for (int i = 0; i < 100; i++) {
146      Long key = ThreadLocalRandom.current().nextLong();
147      assertEquals(csm.higherKey(key), m.higherKey(key));
148    }
149  }
150
151  @Test
152  public void testRemove() {
153    for (Map.Entry<Long, Long> e : csm.entrySet()) {
154      assertEquals(csm.remove(e.getKey()), m.remove(e.getKey()));
155      assertNull(m.remove(e.getKey()));
156    }
157  }
158
159  @Test
160  public void testReplace() {
161    for (Map.Entry<Long, Long> e : csm.entrySet()) {
162      Long newValue = ThreadLocalRandom.current().nextLong();
163      assertEquals(csm.replace(e.getKey(), newValue), m.replace(e.getKey(), newValue));
164    }
165    assertNull(m.replace(MAX_RAND + 100L, ThreadLocalRandom.current().nextLong()));
166  }
167
168  @Test
169  public void testReplace1() {
170    for (Map.Entry<Long, Long> e : csm.entrySet()) {
171      Long newValue = ThreadLocalRandom.current().nextLong();
172      assertEquals(csm.replace(e.getKey(), e.getValue() + 1, newValue),
173        m.replace(e.getKey(), e.getValue() + 1, newValue));
174      assertEquals(csm.replace(e.getKey(), e.getValue(), newValue),
175        m.replace(e.getKey(), e.getValue(), newValue));
176      assertEquals(newValue, m.get(e.getKey()));
177      assertEquals(csm.get(e.getKey()), m.get(e.getKey()));
178    }
179    assertNull(m.replace(MAX_RAND + 100L, ThreadLocalRandom.current().nextLong()));
180  }
181
182  @Test
183  public void testMultiAdd() throws InterruptedException {
184    Thread[] threads = new Thread[10];
185    for (int i = 0; i < threads.length; i++) {
186      threads[i] = new Thread(() -> {
187        for (int j = 0; j < 5000; j++) {
188          m.put(ThreadLocalRandom.current().nextLong(), ThreadLocalRandom.current().nextLong());
189        }
190      });
191    }
192
193    for (Thread thread1 : threads) {
194      thread1.start();
195    }
196
197    for (Thread thread2 : threads) {
198      thread2.join();
199    }
200  }
201
202  @Test
203  public void testFirstKey() {
204    assertEquals(csm.firstKey(), m.firstKey());
205  }
206
207  @Test
208  public void testLastKey() {
209    assertEquals(csm.lastKey(), m.lastKey());
210  }
211
212  @Test
213  public void testFirstEntry() {
214    assertEquals(csm.firstEntry().getKey(), m.firstEntry().getKey());
215    assertEquals(csm.firstEntry().getValue(), m.firstEntry().getValue());
216    assertEquals(csm.firstEntry(), m.firstEntry());
217  }
218
219  @Test
220  public void testLastEntry() {
221    assertEquals(csm.lastEntry().getKey(), m.lastEntry().getKey());
222    assertEquals(csm.lastEntry().getValue(), m.lastEntry().getValue());
223    assertEquals(csm.lastEntry(), m.lastEntry());
224  }
225
226  @Test
227  public void testKeys() {
228    for (Long key : csm.keySet()) {
229      assertNotNull(m.get(key));
230      assertNotNull(m.remove(key));
231      assertNull(m.get(key));
232    }
233  }
234
235  @Test
236  public void testValues() {
237    for (Long value : m.values()) {
238      assertTrue(csm.containsValue(value));
239      assertTrue(m.containsValue(value));
240    }
241  }
242
243  @Test
244  public void testTailMap() {
245    Map<Long, Long> fromCsm = csm.tailMap(50L);
246    Map<Long, Long> fromM = m.tailMap(50L);
247    assertEquals(fromCsm, fromM);
248    for (Long value : m.keySet()) {
249      assertEquals(csm.tailMap(value), m.tailMap(value));
250    }
251
252    for (long i = 0; i < 100; i++) {
253      long o = ThreadLocalRandom.current().nextLong(MAX_RAND);
254      assertEquals(csm.tailMap(o), m.tailMap(o));
255    }
256  }
257
258  @Test
259  public void testTailMapExclusive() {
260    m.clear();
261    m.put(100L, 100L);
262    m.put(101L, 101L);
263    m.put(101L, 101L);
264    m.put(103L, 103L);
265    m.put(99L, 99L);
266    m.put(102L, 102L);
267
268    long n = 100L;
269    CopyOnWriteArrayMap<Long, Long> tm99 = (CopyOnWriteArrayMap<Long, Long>) m.tailMap(99L, false);
270    for (Map.Entry<Long, Long> e : tm99.entrySet()) {
271      assertEquals(Long.valueOf(n), e.getKey());
272      assertEquals(Long.valueOf(n), e.getValue());
273      n++;
274    }
275  }
276
277  @Test
278  public void testTailMapInclusive() {
279    m.clear();
280    m.put(100L, 100L);
281    m.put(101L, 101L);
282    m.put(101L, 101L);
283    m.put(103L, 103L);
284    m.put(99L, 99L);
285    m.put(102L, 102L);
286
287    long n = 102;
288    CopyOnWriteArrayMap<Long, Long> tm102 = (CopyOnWriteArrayMap<Long, Long>) m.tailMap(102L, true);
289    for (Map.Entry<Long, Long> e : tm102.entrySet()) {
290      assertEquals(Long.valueOf(n), e.getKey());
291      assertEquals(Long.valueOf(n), e.getValue());
292      n++;
293    }
294    n = 99;
295    CopyOnWriteArrayMap<Long, Long> tm98 = (CopyOnWriteArrayMap<Long, Long>) m.tailMap(98L, true);
296    for (Map.Entry<Long, Long> e : tm98.entrySet()) {
297      assertEquals(Long.valueOf(n), e.getKey());
298      assertEquals(Long.valueOf(n), e.getValue());
299      n++;
300    }
301  }
302
303  @Test
304  public void testPut() {
305    m.clear();
306    m.put(100L, 100L);
307    m.put(101L, 101L);
308    m.put(101L, 101L);
309    m.put(103L, 103L);
310    m.put(99L, 99L);
311    m.put(102L, 102L);
312    long n = 99;
313
314    for (Map.Entry<Long, Long> e : m.entrySet()) {
315      assertEquals(Long.valueOf(n), e.getKey());
316      assertEquals(Long.valueOf(n), e.getValue());
317      n++;
318    }
319    assertEquals(5, m.size());
320    assertFalse(m.isEmpty());
321  }
322}