1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * 
   4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   5  *
   6  * The contents of this file are subject to the terms of either the Universal Permissive License
   7  * v 1.0 as shown at http://oss.oracle.com/licenses/upl
   8  *
   9  * or the following license:
  10  *
  11  * Redistribution and use in source and binary forms, with or without modification, are permitted
  12  * provided that the following conditions are met:
  13  * 
  14  * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
  15  * and the following disclaimer.
  16  * 
  17  * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
  18  * conditions and the following disclaimer in the documentation and/or other materials provided with
  19  * the distribution.
  20  * 
  21  * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
  22  * endorse or promote products derived from this software without specific prior written permission.
  23  * 
  24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
  25  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  26  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  27  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  29  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  30  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
  31  * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  32  */
  33 package org.openjdk.jmc.common.util;
  34 
  35 import static org.junit.Assert.assertEquals;
  36 import static org.junit.Assert.assertFalse;
  37 import static org.junit.Assert.fail;
  38 
  39 import java.util.Iterator;
  40 import java.util.NoSuchElementException;
  41 
  42 import org.junit.Test;
  43 import org.junit.experimental.categories.Category;
  44 import org.openjdk.jmc.common.collection.BoundedList;
  45 import org.openjdk.jmc.common.test.SlowTests;
  46 
  47 public class BoundedListTest {
  48 
  49         private static class ProducerThread implements Runnable {
  50                 private final BoundedList<Long> list;
  51                 private volatile boolean shouldStop = false;
  52                 private long counter;
  53 
  54                 public ProducerThread(BoundedList<Long> list) {
  55                         this.list = list;
  56                 }
  57 
  58                 @Override
  59                 public void run() {
  60                         while (!shouldStop) {
  61                                 for (int i = 0; i < 100000; i++) {
  62                                         list.add(counter++);
  63                                 }
  64                                 try {
  65                                         Thread.sleep(1000);
  66                                 } catch (InterruptedException e) {
  67                                         e.printStackTrace();
  68                                 }
  69                                 Thread.yield();
  70                         }
  71                 }
  72 
  73                 public void stop() {
  74                         shouldStop = true;
  75                 }
  76         }
  77 
  78         private static class ValidationThread implements Runnable {
  79                 private final BoundedList<Long> list;
  80                 private volatile boolean shouldStop = false;
  81                 private volatile long countError = -1;
  82                 private volatile long sequenceError = -1;
  83                 private volatile long maxNum;
  84 
  85                 public ValidationThread(BoundedList<Long> list) {
  86                         this.list = list;
  87                 }
  88 
  89                 @Override
  90                 public void run() {
  91                         while (!shouldStop) {
  92                                 validate();
  93                         }
  94                 }
  95 
  96                 private void validate() {
  97                         int count = 0;
  98                         long lastNumber = -1;
  99                         for (Long l : list) {
 100                                 if (lastNumber != -1 && (l - lastNumber != 1)) {
 101                                         sequenceError = l - lastNumber;
 102                                 }
 103                                 count++;
 104                                 maxNum = Math.max(l, maxNum);
 105                         }
 106                         if (count > list.getMaxSize()) {
 107                                 countError = count;
 108                         }
 109                 }
 110 
 111                 public void stop() {
 112                         shouldStop = true;
 113                 }
 114         }
 115 
 116         @Test
 117         public void testAdd() {
 118                 BoundedList<Integer> bl = new BoundedList<>(10);
 119                 bl.add(1);
 120                 bl.add(2);
 121                 assertEquals(2, bl.getSize());
 122                 assertEquals(10, bl.getMaxSize());
 123         }
 124 
 125         @Test
 126         public void testBasicIterator() {
 127                 BoundedList<Integer> bl = new BoundedList<>(10);
 128                 bl.add(1);
 129                 bl.add(2);
 130                 bl.add(3);
 131 
 132                 int val = 1;
 133                 for (Integer iterVal : bl) {
 134                         assertEquals(val++, iterVal.intValue());
 135                 }
 136                 assertEquals(4, val);
 137         }
 138 
 139         @Test
 140         public void testWrappingIterator() {
 141                 BoundedList<Integer> bl = new BoundedList<>(10);
 142                 for (int i = 1; i <= 20; i++) {
 143                         bl.add(i);
 144                 }
 145 
 146                 int val = 11;
 147                 for (Integer iterVal : bl) {
 148                         assertEquals(val++, iterVal.intValue());
 149                 }
 150         }
 151 
 152         @Test
 153         public void testEmptyIterator() {
 154                 BoundedList<Integer> bl = new BoundedList<>(10);
 155                 assertFalse("Empty list should have no elements!", bl.iterator().hasNext()); //$NON-NLS-1$
 156                 try {
 157                         bl.iterator().next();
 158                         fail("next should have generated an exception!"); //$NON-NLS-1$
 159                 } catch (NoSuchElementException el) {
 160                         // Fall through...
 161                 }
 162         }
 163 
 164         @Test
 165         public void testMultipleIterators() {
 166                 // Shows that we can leak memory if we add faster than we can consume...
 167                 BoundedList<Integer> bl = new BoundedList<>(10);
 168                 for (int i = 1; i <= 10; i++) {
 169                         bl.add(i);
 170                 }
 171                 Iterator<Integer> iter1 = bl.iterator();
 172                 for (int i = 11; i <= 20; i++) {
 173                         bl.add(i);
 174                 }
 175                 Iterator<Integer> iter2 = bl.iterator();
 176 
 177                 int val1 = 1;
 178                 while (iter1.hasNext()) {
 179                         assertEquals(val1++, iter1.next().intValue());
 180                 }
 181 
 182                 int val2 = 11;
 183                 while (iter2.hasNext()) {
 184                         assertEquals(val2++, iter2.next().intValue());
 185                 }
 186                 // Iterated through all values, even though we've wrapped.
 187                 assertEquals(11, val1);
 188                 assertEquals(21, val2);
 189         }
 190 
 191         @Category(value = SlowTests.class)
 192         @Test
 193         public void testMultiThreadedConsumption() throws InterruptedException {
 194                 final BoundedList<Long> bl = new BoundedList<>(20);
 195                 ProducerThread t = new ProducerThread(bl);
 196                 ValidationThread[] validators = new ValidationThread[10];
 197                 new Thread(t, "Producer").start(); //$NON-NLS-1$
 198                 for (int i = 0; i < validators.length; i++) {
 199                         validators[i] = new ValidationThread(bl);
 200                         new Thread(validators[i], "Validator " + i).start(); //$NON-NLS-1$
 201                 }
 202                 Thread.sleep(30000);
 203                 for (ValidationThread validator : validators) {
 204                         validator.stop();
 205                 }
 206                 t.stop();
 207                 long maxNo = 0;
 208                 for (ValidationThread validator : validators) {
 209                         assertEquals("Failed count validation!", -1, validator.countError); //$NON-NLS-1$
 210                         assertEquals("Failed sequence validation!", -1, validator.sequenceError); //$NON-NLS-1$
 211                         maxNo = Math.max(maxNo, validator.maxNum);
 212                 }
 213                 System.out.println("Allocated up to " + t.counter); //$NON-NLS-1$
 214                 System.out.println("Max no was " + maxNo); //$NON-NLS-1$
 215         }
 216 
 217         // FIXME: This test has been commented out for a long time. Check if it is still relevant and either remove or reintroduce it.
 218 //      @Category(value = SlowTests.class)
 219 //      @Test
 220 //      public void testNoLeak() {
 221 //              BoundedList<Long> bl = new BoundedList<Long>(10);
 222 //              // Adding a few billion numbers, just to show that we do not leak under normal circumstances...
 223 //              for (long i = 1; i <= Integer.MAX_VALUE; i++) {
 224 //                      if (i % (Integer.MAX_VALUE / 20) == 0) {
 225 //                              System.out.println(String.format("Passed %.0f%%", (i * 100f) / Integer.MAX_VALUE)); //$NON-NLS-1$
 226 //                      }
 227 //                      bl.add(i);
 228 //              }
 229 //              // On a 32-bit platform we should either crash or pass...
 230 //      }
 231 
 232 }