1 /* 2 * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package org.openjdk.skara.bot; 24 25 import org.openjdk.skara.json.JSON; 26 27 import org.junit.jupiter.api.*; 28 29 import java.nio.file.Path; 30 import java.time.Duration; 31 import java.util.*; 32 import java.util.concurrent.*; 33 import java.util.function.Supplier; 34 import java.util.logging.*; 35 36 import static org.junit.jupiter.api.Assertions.*; 37 38 class TestWorkItem implements WorkItem { 39 private final ConcurrencyCheck concurrencyCheck; 40 private final String description; 41 boolean hasRun = false; 42 43 interface ConcurrencyCheck { 44 boolean concurrentWith(WorkItem other); 45 } 46 47 TestWorkItem(ConcurrencyCheck concurrencyCheck) { 48 this.concurrencyCheck = concurrencyCheck; 49 this.description = null; 50 } 51 52 TestWorkItem(ConcurrencyCheck concurrencyCheck, String description) { 53 this.concurrencyCheck = concurrencyCheck; 54 this.description = description; 55 } 56 57 @Override 58 public void run(Path scratchPath) { 59 hasRun = true; 60 System.out.println("Item " + this.toString() + " now running"); 61 } 62 63 @Override 64 public boolean concurrentWith(WorkItem other) { 65 return concurrencyCheck.concurrentWith(other); 66 } 67 68 @Override 69 public String toString() { 70 return description != null ? description : super.toString(); 71 } 72 } 73 74 class TestWorkItemChild extends TestWorkItem { 75 TestWorkItemChild(ConcurrencyCheck concurrencyCheck, String description) { 76 super(concurrencyCheck, description); 77 } 78 } 79 80 class TestBlockedWorkItem implements WorkItem { 81 private final CountDownLatch countDownLatch; 82 83 TestBlockedWorkItem(CountDownLatch countDownLatch) { 84 this.countDownLatch = countDownLatch; 85 } 86 87 @Override 88 public boolean concurrentWith(WorkItem other) { 89 return false; 90 } 91 92 @Override 93 public void run(Path scratchPath) { 94 System.out.println("Starting to wait...");; 95 try { 96 countDownLatch.await(); 97 } catch (InterruptedException e) { 98 throw new RuntimeException(e); 99 } 100 System.out.println("Done waiting"); 101 } 102 } 103 104 class TestBot implements Bot { 105 106 private final List<WorkItem> items; 107 private final Supplier<List<WorkItem>> itemSupplier; 108 109 TestBot(WorkItem... items) { 110 this.items = Arrays.asList(items); 111 itemSupplier = null; 112 } 113 114 TestBot(Supplier<List<WorkItem>> itemSupplier) { 115 items = null; 116 this.itemSupplier = itemSupplier; 117 } 118 119 @Override 120 public List<WorkItem> getPeriodicItems() { 121 if (items != null) { 122 return items; 123 } else { 124 return itemSupplier.get(); 125 } 126 } 127 } 128 129 class BotRunnerTests { 130 131 @BeforeAll 132 static void setUp() { 133 Logger log = Logger.getGlobal(); 134 log.setLevel(Level.FINER); 135 log = Logger.getLogger("org.openjdk.bots.cli"); 136 log.setLevel(Level.FINER); 137 ConsoleHandler handler = new ConsoleHandler(); 138 handler.setLevel(Level.FINER); 139 log.addHandler(handler); 140 } 141 142 private BotRunnerConfiguration config() { 143 var config = JSON.object(); 144 try { 145 return BotRunnerConfiguration.parse(config); 146 } catch (ConfigurationError configurationError) { 147 throw new RuntimeException(configurationError); 148 } 149 } 150 151 private BotRunnerConfiguration config(String json) { 152 var config = JSON.parse(json).asObject(); 153 try { 154 return BotRunnerConfiguration.parse(config); 155 } catch (ConfigurationError configurationError) { 156 throw new RuntimeException(configurationError); 157 } 158 } 159 @Test 160 void simpleConcurrent() throws TimeoutException { 161 var item1 = new TestWorkItem(i -> true, "Item 1"); 162 var item2 = new TestWorkItem(i -> true, "Item 2"); 163 var bot = new TestBot(item1, item2); 164 var runner = new BotRunner(config(), List.of(bot)); 165 166 runner.runOnce(Duration.ofSeconds(10)); 167 168 assertTrue(item1.hasRun); 169 assertTrue(item2.hasRun); 170 } 171 172 @Test 173 void simpleSerial() throws TimeoutException { 174 var item1 = new TestWorkItem(i -> false, "Item 1"); 175 var item2 = new TestWorkItem(i -> false, "Item 2"); 176 var bot = new TestBot(item1, item2); 177 var runner = new BotRunner(config(), List.of(bot)); 178 179 runner.runOnce(Duration.ofSeconds(10)); 180 181 assertTrue(item1.hasRun); 182 assertTrue(item2.hasRun); 183 } 184 185 @Test 186 void moreItemsThanScratchPaths() throws TimeoutException { 187 List<TestWorkItem> items = new LinkedList<>(); 188 for (int i = 0; i < 20; ++i) { 189 items.add(new TestWorkItem(x -> true, "Item " + i)); 190 } 191 var bot = new TestBot(items.toArray(new TestWorkItem[0])); 192 var runner = new BotRunner(config(), List.of(bot)); 193 194 runner.runOnce(Duration.ofSeconds(10)); 195 196 for (var item : items) { 197 assertTrue(item.hasRun); 198 } 199 } 200 201 static class ThrowingItemProvider { 202 private final List<WorkItem> items; 203 private int throwCount; 204 205 ThrowingItemProvider(List<WorkItem> items, int throwCount) { 206 this.items = items; 207 this.throwCount = throwCount; 208 } 209 210 List<WorkItem> get() { 211 if (throwCount-- > 0) { 212 throw new RuntimeException("Sorry, can't provide items just yet"); 213 } else { 214 return items; 215 } 216 } 217 } 218 219 @Test 220 void periodItemsThrow() throws TimeoutException { 221 var item1 = new TestWorkItem(i -> false, "Item 1"); 222 var item2 = new TestWorkItem(i -> false, "Item 2"); 223 var provider = new ThrowingItemProvider(List.of(item1, item2), 1); 224 225 var bot = new TestBot(provider::get); 226 227 new BotRunner(config(), List.of(bot)).runOnce(Duration.ofSeconds(10)); 228 Assertions.assertFalse(item1.hasRun); 229 Assertions.assertFalse(item2.hasRun); 230 231 new BotRunner(config(), List.of(bot)).runOnce(Duration.ofSeconds(10)); 232 assertTrue(item1.hasRun); 233 assertTrue(item2.hasRun); 234 } 235 236 @Test 237 void discardAdditionalBlockedItems() throws TimeoutException { 238 var item1 = new TestWorkItem(i -> false, "Item 1"); 239 var item2 = new TestWorkItem(i -> false, "Item 2"); 240 var item3 = new TestWorkItem(i -> false, "Item 3"); 241 var item4 = new TestWorkItem(i -> false, "Item 4"); 242 var bot = new TestBot(item1, item2, item3, item4); 243 var runner = new BotRunner(config(), List.of(bot)); 244 245 runner.runOnce(Duration.ofSeconds(10)); 246 247 assertTrue(item1.hasRun); 248 Assertions.assertFalse(item2.hasRun); 249 Assertions.assertFalse(item3.hasRun); 250 assertTrue(item4.hasRun); 251 } 252 253 @Test 254 void dontDiscardDifferentBlockedItems() throws TimeoutException { 255 var item1 = new TestWorkItem(i -> false, "Item 1"); 256 var item2 = new TestWorkItem(i -> false, "Item 2"); 257 var item3 = new TestWorkItem(i -> false, "Item 3"); 258 var item4 = new TestWorkItem(i -> false, "Item 4"); 259 var item5 = new TestWorkItemChild(i -> false, "Item 5"); 260 var item6 = new TestWorkItemChild(i -> false, "Item 6"); 261 var item7 = new TestWorkItemChild(i -> false, "Item 7"); 262 var bot = new TestBot(item1, item2, item3, item4, item5, item6, item7); 263 var runner = new BotRunner(config(), List.of(bot)); 264 265 runner.runOnce(Duration.ofSeconds(10)); 266 267 assertTrue(item1.hasRun); 268 Assertions.assertFalse(item2.hasRun); 269 Assertions.assertFalse(item3.hasRun); 270 assertTrue(item4.hasRun); 271 Assertions.assertFalse(item5.hasRun); 272 Assertions.assertFalse(item6.hasRun); 273 assertTrue(item7.hasRun); 274 } 275 276 @Test 277 void watchdogTrigger() throws TimeoutException { 278 var countdownLatch = new CountDownLatch(1); 279 var item = new TestBlockedWorkItem(countdownLatch); 280 var bot = new TestBot(item); 281 var runner = new BotRunner(config("{ \"runner\": { \"watchdog\": \"PT0.01S\" } }"), List.of(bot)); 282 283 var errors = new ArrayList<String>(); 284 var log = Logger.getLogger("org.openjdk.skara.bot"); 285 log.addHandler(new Handler() { 286 @Override 287 public void publish(LogRecord record) { 288 if (record.getLevel().equals(Level.SEVERE)) { 289 errors.add(record.getMessage()); 290 } 291 } 292 293 @Override 294 public void flush() { 295 } 296 297 @Override 298 public void close() throws SecurityException { 299 } 300 }); 301 302 assertThrows(TimeoutException.class, () -> runner.runOnce(Duration.ofMillis(100))); 303 assertTrue(errors.size() > 0); 304 assertTrue(errors.size() <= 10); 305 countdownLatch.countDown(); 306 } 307 }