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