1 /** 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 package org.apache.hadoop.util; 19 20 import java.util.ConcurrentModificationException; 21 import java.util.Iterator; 22 import java.util.Random; 23 24 import org.apache.hadoop.HadoopIllegalArgumentException; 25 import org.apache.hadoop.util.Time; 26 import org.junit.Assert; 27 import org.junit.Test; 28 29 public class TestGSet { 30 private static final Random ran = new Random(); 31 private static final long starttime = Time.now(); 32 print(Object s)33 private static void print(Object s) { 34 System.out.print(s); 35 System.out.flush(); 36 } 37 println(Object s)38 private static void println(Object s) { 39 System.out.println(s); 40 } 41 42 @Test testExceptionCases()43 public void testExceptionCases() { 44 { 45 //test contains 46 final LightWeightGSet<Integer, Integer> gset 47 = new LightWeightGSet<Integer, Integer>(16); 48 try { 49 //test contains with a null element 50 gset.contains(null); 51 Assert.fail(); 52 } catch(NullPointerException e) { 53 LightWeightGSet.LOG.info("GOOD: getting " + e, e); 54 } 55 } 56 57 { 58 //test get 59 final LightWeightGSet<Integer, Integer> gset 60 = new LightWeightGSet<Integer, Integer>(16); 61 try { 62 //test get with a null element 63 gset.get(null); 64 Assert.fail(); 65 } catch(NullPointerException e) { 66 LightWeightGSet.LOG.info("GOOD: getting " + e, e); 67 } 68 } 69 70 { 71 //test put 72 final LightWeightGSet<Integer, Integer> gset 73 = new LightWeightGSet<Integer, Integer>(16); 74 try { 75 //test put with a null element 76 gset.put(null); 77 Assert.fail(); 78 } catch(NullPointerException e) { 79 LightWeightGSet.LOG.info("GOOD: getting " + e, e); 80 } 81 try { 82 //test putting an element which is not implementing LinkedElement 83 gset.put(1); 84 Assert.fail(); 85 } catch(IllegalArgumentException e) { 86 LightWeightGSet.LOG.info("GOOD: getting " + e, e); 87 } 88 } 89 90 { 91 //test iterator 92 final IntElement[] data = new IntElement[5]; 93 for(int i = 0; i < data.length; i++) { 94 data[i] = new IntElement(i, i); 95 } 96 97 for(int v = 1; v < data.length-1; v++) { 98 { 99 //test remove while iterating 100 final GSet<IntElement, IntElement> gset = createGSet(data); 101 for(IntElement i : gset) { 102 if (i.value == v) { 103 //okay because data[0] is not in gset 104 gset.remove(data[0]); 105 } 106 } 107 108 try { 109 //exception because data[1] is in gset 110 for(IntElement i : gset) { 111 if (i.value == v) { 112 gset.remove(data[1]); 113 } 114 } 115 Assert.fail(); 116 } catch(ConcurrentModificationException e) { 117 LightWeightGSet.LOG.info("GOOD: getting " + e, e); 118 } 119 } 120 121 { 122 //test put new element while iterating 123 final GSet<IntElement, IntElement> gset = createGSet(data); 124 try { 125 for(IntElement i : gset) { 126 if (i.value == v) { 127 gset.put(data[0]); 128 } 129 } 130 Assert.fail(); 131 } catch(ConcurrentModificationException e) { 132 LightWeightGSet.LOG.info("GOOD: getting " + e, e); 133 } 134 } 135 136 { 137 //test put existing element while iterating 138 final GSet<IntElement, IntElement> gset = createGSet(data); 139 try { 140 for(IntElement i : gset) { 141 if (i.value == v) { 142 gset.put(data[3]); 143 } 144 } 145 Assert.fail(); 146 } catch(ConcurrentModificationException e) { 147 LightWeightGSet.LOG.info("GOOD: getting " + e, e); 148 } 149 } 150 } 151 } 152 } 153 createGSet(final IntElement[] data)154 private static GSet<IntElement, IntElement> createGSet(final IntElement[] data) { 155 final GSet<IntElement, IntElement> gset 156 = new LightWeightGSet<IntElement, IntElement>(8); 157 for(int i = 1; i < data.length; i++) { 158 gset.put(data[i]); 159 } 160 return gset; 161 } 162 163 @Test testGSet()164 public void testGSet() { 165 //The parameters are: table length, data size, modulus. 166 check(new GSetTestCase(1, 1 << 4, 65537)); 167 check(new GSetTestCase(17, 1 << 16, 17)); 168 check(new GSetTestCase(255, 1 << 10, 65537)); 169 } 170 171 /** 172 * A long running test with various data sets and parameters. 173 * It may take ~5 hours, 174 * If you are changing the implementation, 175 * please un-comment the following line in order to run the test. 176 */ 177 //@Test runMultipleTestGSet()178 public void runMultipleTestGSet() { 179 for(int offset = -2; offset <= 2; offset++) { 180 runTestGSet(1, offset); 181 for(int i = 1; i < Integer.SIZE - 1; i++) { 182 runTestGSet((1 << i) + 1, offset); 183 } 184 } 185 } 186 runTestGSet(final int modulus, final int offset)187 private static void runTestGSet(final int modulus, final int offset) { 188 println("\n\nmodulus=" + modulus + ", offset=" + offset); 189 for(int i = 0; i <= 16; i += 4) { 190 final int tablelength = (1 << i) + offset; 191 192 final int upper = i + 2; 193 final int steps = Math.max(1, upper/3); 194 195 for(int j = 0; j <= upper; j += steps) { 196 final int datasize = 1 << j; 197 check(new GSetTestCase(tablelength, datasize, modulus)); 198 } 199 } 200 } 201 check(final GSetTestCase test)202 private static void check(final GSetTestCase test) { 203 //check add 204 print(" check add .................. "); 205 for(int i = 0; i < test.data.size()/2; i++) { 206 test.put(test.data.get(i)); 207 } 208 for(int i = 0; i < test.data.size(); i++) { 209 test.put(test.data.get(i)); 210 } 211 println("DONE " + test.stat()); 212 213 //check remove and add 214 print(" check remove & add ......... "); 215 for(int j = 0; j < 10; j++) { 216 for(int i = 0; i < test.data.size()/2; i++) { 217 final int r = ran.nextInt(test.data.size()); 218 test.remove(test.data.get(r)); 219 } 220 for(int i = 0; i < test.data.size()/2; i++) { 221 final int r = ran.nextInt(test.data.size()); 222 test.put(test.data.get(r)); 223 } 224 } 225 println("DONE " + test.stat()); 226 227 //check remove 228 print(" check remove ............... "); 229 for(int i = 0; i < test.data.size(); i++) { 230 test.remove(test.data.get(i)); 231 } 232 Assert.assertEquals(0, test.gset.size()); 233 println("DONE " + test.stat()); 234 235 //check remove and add again 236 print(" check remove & add again ... "); 237 for(int j = 0; j < 10; j++) { 238 for(int i = 0; i < test.data.size()/2; i++) { 239 final int r = ran.nextInt(test.data.size()); 240 test.remove(test.data.get(r)); 241 } 242 for(int i = 0; i < test.data.size()/2; i++) { 243 final int r = ran.nextInt(test.data.size()); 244 test.put(test.data.get(r)); 245 } 246 } 247 println("DONE " + test.stat()); 248 249 final long s = (Time.now() - starttime)/1000L; 250 println("total time elapsed=" + s + "s\n"); 251 } 252 253 /** Test cases */ 254 private static class GSetTestCase implements GSet<IntElement, IntElement> { 255 final GSet<IntElement, IntElement> expected 256 = new GSetByHashMap<IntElement, IntElement>(1024, 0.75f); 257 final GSet<IntElement, IntElement> gset; 258 final IntData data; 259 260 final String info; 261 final long starttime = Time.now(); 262 /** Determine the probability in {@link #check()}. */ 263 final int denominator; 264 int iterate_count = 0; 265 int contain_count = 0; 266 GSetTestCase(int tablelength, int datasize, int modulus)267 GSetTestCase(int tablelength, int datasize, int modulus) { 268 denominator = Math.min((datasize >> 7) + 1, 1 << 16); 269 info = getClass().getSimpleName() 270 + ": tablelength=" + tablelength 271 + ", datasize=" + datasize 272 + ", modulus=" + modulus 273 + ", denominator=" + denominator; 274 println(info); 275 276 data = new IntData(datasize, modulus); 277 gset = new LightWeightGSet<IntElement, IntElement>(tablelength); 278 279 Assert.assertEquals(0, gset.size()); 280 } 281 containsTest(IntElement key)282 private boolean containsTest(IntElement key) { 283 final boolean e = expected.contains(key); 284 Assert.assertEquals(e, gset.contains(key)); 285 return e; 286 } 287 @Override contains(IntElement key)288 public boolean contains(IntElement key) { 289 final boolean e = containsTest(key); 290 check(); 291 return e; 292 } 293 getTest(IntElement key)294 private IntElement getTest(IntElement key) { 295 final IntElement e = expected.get(key); 296 Assert.assertEquals(e.id, gset.get(key).id); 297 return e; 298 } 299 @Override get(IntElement key)300 public IntElement get(IntElement key) { 301 final IntElement e = getTest(key); 302 check(); 303 return e; 304 } 305 putTest(IntElement element)306 private IntElement putTest(IntElement element) { 307 final IntElement e = expected.put(element); 308 if (e == null) { 309 Assert.assertEquals(null, gset.put(element)); 310 } else { 311 Assert.assertEquals(e.id, gset.put(element).id); 312 } 313 return e; 314 } 315 @Override put(IntElement element)316 public IntElement put(IntElement element) { 317 final IntElement e = putTest(element); 318 check(); 319 return e; 320 } 321 removeTest(IntElement key)322 private IntElement removeTest(IntElement key) { 323 final IntElement e = expected.remove(key); 324 if (e == null) { 325 Assert.assertEquals(null, gset.remove(key)); 326 } else { 327 Assert.assertEquals(e.id, gset.remove(key).id); 328 } 329 return e; 330 } 331 @Override remove(IntElement key)332 public IntElement remove(IntElement key) { 333 final IntElement e = removeTest(key); 334 check(); 335 return e; 336 } 337 sizeTest()338 private int sizeTest() { 339 final int s = expected.size(); 340 Assert.assertEquals(s, gset.size()); 341 return s; 342 } 343 @Override size()344 public int size() { 345 final int s = sizeTest(); 346 check(); 347 return s; 348 } 349 350 @Override iterator()351 public Iterator<IntElement> iterator() { 352 throw new UnsupportedOperationException(); 353 } 354 check()355 void check() { 356 //test size 357 sizeTest(); 358 359 if (ran.nextInt(denominator) == 0) { 360 //test get(..), check content and test iterator 361 iterate_count++; 362 for(IntElement i : gset) { 363 getTest(i); 364 } 365 } 366 367 if (ran.nextInt(denominator) == 0) { 368 //test contains(..) 369 contain_count++; 370 final int count = Math.min(data.size(), 1000); 371 if (count == data.size()) { 372 for(IntElement i : data.integers) { 373 containsTest(i); 374 } 375 } else { 376 for(int j = 0; j < count; j++) { 377 containsTest(data.get(ran.nextInt(data.size()))); 378 } 379 } 380 } 381 } 382 stat()383 String stat() { 384 final long t = Time.now() - starttime; 385 return String.format(" iterate=%5d, contain=%5d, time elapsed=%5d.%03ds", 386 iterate_count, contain_count, t/1000, t%1000); 387 } 388 389 @Override clear()390 public void clear() { 391 expected.clear(); 392 gset.clear(); 393 Assert.assertEquals(0, size()); 394 } 395 } 396 397 /** Test data set */ 398 private static class IntData { 399 final IntElement[] integers; 400 IntData(int size, int modulus)401 IntData(int size, int modulus) { 402 integers = new IntElement[size]; 403 for(int i = 0; i < integers.length; i++) { 404 integers[i] = new IntElement(i, ran.nextInt(modulus)); 405 } 406 } 407 get(int i)408 IntElement get(int i) { 409 return integers[i]; 410 } 411 size()412 int size() { 413 return integers.length; 414 } 415 } 416 417 /** Elements of {@link LightWeightGSet} in this test */ 418 private static class IntElement implements LightWeightGSet.LinkedElement, 419 Comparable<IntElement> { 420 private LightWeightGSet.LinkedElement next; 421 final int id; 422 final int value; 423 IntElement(int id, int value)424 IntElement(int id, int value) { 425 this.id = id; 426 this.value = value; 427 } 428 429 @Override equals(Object obj)430 public boolean equals(Object obj) { 431 return obj != null && obj instanceof IntElement 432 && value == ((IntElement)obj).value; 433 } 434 435 @Override hashCode()436 public int hashCode() { 437 return value; 438 } 439 440 @Override compareTo(IntElement that)441 public int compareTo(IntElement that) { 442 return value - that.value; 443 } 444 445 @Override toString()446 public String toString() { 447 return id + "#" + value; 448 } 449 450 @Override getNext()451 public LightWeightGSet.LinkedElement getNext() { 452 return next; 453 } 454 455 @Override setNext(LightWeightGSet.LinkedElement e)456 public void setNext(LightWeightGSet.LinkedElement e) { 457 next = e; 458 } 459 } 460 461 /** 462 * Test for {@link LightWeightGSet#computeCapacity(double, String)} 463 * with invalid percent less than 0. 464 */ 465 @Test(expected=HadoopIllegalArgumentException.class) testComputeCapacityNegativePercent()466 public void testComputeCapacityNegativePercent() { 467 LightWeightGSet.computeCapacity(1024, -1.0, "testMap"); 468 } 469 470 /** 471 * Test for {@link LightWeightGSet#computeCapacity(double, String)} 472 * with invalid percent greater than 100. 473 */ 474 @Test(expected=HadoopIllegalArgumentException.class) testComputeCapacityInvalidPercent()475 public void testComputeCapacityInvalidPercent() { 476 LightWeightGSet.computeCapacity(1024, 101.0, "testMap"); 477 } 478 479 /** 480 * Test for {@link LightWeightGSet#computeCapacity(double, String)} 481 * with invalid negative max memory 482 */ 483 @Test(expected=HadoopIllegalArgumentException.class) testComputeCapacityInvalidMemory()484 public void testComputeCapacityInvalidMemory() { 485 LightWeightGSet.computeCapacity(-1, 50.0, "testMap"); 486 } 487 isPowerOfTwo(int num)488 private static boolean isPowerOfTwo(int num) { 489 return num == 0 || (num > 0 && Integer.bitCount(num) == 1); 490 } 491 492 /** Return capacity as percentage of total memory */ getPercent(long total, int capacity)493 private static int getPercent(long total, int capacity) { 494 // Reference size in bytes 495 double referenceSize = 496 System.getProperty("sun.arch.data.model").equals("32") ? 4.0 : 8.0; 497 return (int)(((capacity * referenceSize)/total) * 100.0); 498 } 499 500 /** Return capacity as percentage of total memory */ testCapacity(long maxMemory, double percent)501 private static void testCapacity(long maxMemory, double percent) { 502 int capacity = LightWeightGSet.computeCapacity(maxMemory, percent, "map"); 503 LightWeightGSet.LOG.info("Validating - total memory " + maxMemory + " percent " 504 + percent + " returned capacity " + capacity); 505 // Returned capacity is zero or power of two 506 Assert.assertTrue(isPowerOfTwo(capacity)); 507 508 // Ensure the capacity returned is the nearest to the asked perecentage 509 int capacityPercent = getPercent(maxMemory, capacity); 510 if (capacityPercent == percent) { 511 return; 512 } else if (capacityPercent > percent) { 513 Assert.assertTrue(getPercent(maxMemory, capacity * 2) > percent); 514 } else { 515 Assert.assertTrue(getPercent(maxMemory, capacity / 2) < percent); 516 } 517 } 518 519 /** 520 * Test for {@link LightWeightGSet#computeCapacity(double, String)} 521 */ 522 @Test 523 public void testComputeCapacity() { 524 // Tests for boundary conditions where percent or memory are zero 525 testCapacity(0, 0.0); 526 testCapacity(100, 0.0); 527 testCapacity(0, 100.0); 528 529 // Compute capacity for some 100 random max memory and percentage 530 Random r = new Random(); 531 for (int i = 0; i < 100; i++) { 532 long maxMemory = r.nextInt(Integer.MAX_VALUE); 533 double percent = r.nextInt(101); 534 testCapacity(maxMemory, percent); 535 } 536 } 537 } 538