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.junit.jupiter.api.*;
 26 import org.openjdk.skara.host.HostedRepository;
 27 import org.openjdk.skara.json.*;
 28 
 29 import java.nio.file.*;
 30 import java.time.Duration;
 31 import java.util.*;
 32 import java.util.concurrent.TimeoutException;
 33 import java.util.function.Supplier;
 34 import java.util.logging.*;
 35 
 36 class TestWorkItem implements WorkItem {
 37     private final ConcurrencyCheck concurrencyCheck;
 38     private final String description;
 39     boolean hasRun = false;
 40 
 41     interface ConcurrencyCheck {
 42         boolean concurrentWith(WorkItem other);
 43     }
 44 
 45     TestWorkItem(ConcurrencyCheck concurrencyCheck) {
 46         this.concurrencyCheck = concurrencyCheck;
 47         this.description = null;
 48     }
 49 
 50     TestWorkItem(ConcurrencyCheck concurrencyCheck, String description) {
 51         this.concurrencyCheck = concurrencyCheck;
 52         this.description = description;
 53     }
 54 
 55     @Override
 56     public void run(Path scratchPath) {
 57         hasRun = true;
 58         System.out.println("Item " + this.toString() + " now running");
 59     }
 60 
 61     @Override
 62     public boolean concurrentWith(WorkItem other) {
 63         return concurrencyCheck.concurrentWith(other);
 64     }
 65 
 66     @Override
 67     public String toString() {
 68         return description != null ? description : super.toString();
 69     }
 70 }
 71 
 72 class TestWorkItemChild extends TestWorkItem {
 73     TestWorkItemChild(ConcurrencyCheck concurrencyCheck, String description) {
 74         super(concurrencyCheck, description);
 75     }
 76 }
 77 
 78 class TestBot implements Bot {
 79 
 80     private final List<WorkItem> items;
 81     private final Supplier<List<WorkItem>> itemSupplier;
 82 
 83     TestBot(TestWorkItem... items) {
 84         this.items = Arrays.asList(items);
 85         itemSupplier = null;
 86     }
 87 
 88     TestBot(Supplier<List<WorkItem>> itemSupplier) {
 89         items = null;
 90         this.itemSupplier = itemSupplier;
 91     }
 92 
 93     @Override
 94     public List<WorkItem> getPeriodicItems() {
 95         if (items != null) {
 96             return items;
 97         } else {
 98             return itemSupplier.get();
 99         }
100     }
101 }
102 
103 class BotRunnerTests {
104 
105     @BeforeAll
106     static void setUp() {
107         Logger log = Logger.getGlobal();
108         log.setLevel(Level.FINER);
109         log = Logger.getLogger("org.openjdk.bots.cli");
110         log.setLevel(Level.FINER);
111         ConsoleHandler handler = new ConsoleHandler();
112         handler.setLevel(Level.FINER);
113         log.addHandler(handler);
114     }
115 
116     private BotRunnerConfiguration config() {
117         var config = JSON.object();
118         try {
119             return BotRunnerConfiguration.parse(config);
120         } catch (ConfigurationError configurationError) {
121             throw new RuntimeException(configurationError);
122         }
123     }
124 
125     @Test
126     void simpleConcurrent() throws TimeoutException {
127         var item1 = new TestWorkItem(i -> true, "Item 1");
128         var item2 = new TestWorkItem(i -> true, "Item 2");
129         var bot = new TestBot(item1, item2);
130         var runner = new BotRunner(config(), List.of(bot));
131 
132         runner.runOnce(Duration.ofSeconds(10));
133 
134         Assertions.assertTrue(item1.hasRun);
135         Assertions.assertTrue(item2.hasRun);
136     }
137 
138     @Test
139     void simpleSerial() throws TimeoutException {
140         var item1 = new TestWorkItem(i -> false, "Item 1");
141         var item2 = new TestWorkItem(i -> false, "Item 2");
142         var bot = new TestBot(item1, item2);
143         var runner = new BotRunner(config(), List.of(bot));
144 
145         runner.runOnce(Duration.ofSeconds(10));
146 
147         Assertions.assertTrue(item1.hasRun);
148         Assertions.assertTrue(item2.hasRun);
149     }
150 
151     @Test
152     void moreItemsThanScratchPaths() throws TimeoutException {
153         List<TestWorkItem> items = new LinkedList<>();
154         for (int i = 0; i < 20; ++i) {
155             items.add(new TestWorkItem(x -> true, "Item " + i));
156         }
157         var bot = new TestBot(items.toArray(new TestWorkItem[0]));
158         var runner = new BotRunner(config(), List.of(bot));
159 
160         runner.runOnce(Duration.ofSeconds(10));
161 
162         for (var item : items) {
163             Assertions.assertTrue(item.hasRun);
164         }
165     }
166 
167     static class ThrowingItemProvider {
168         private final List<WorkItem> items;
169         private int throwCount;
170 
171         ThrowingItemProvider(List<WorkItem> items, int throwCount) {
172             this.items = items;
173             this.throwCount = throwCount;
174         }
175 
176         List<WorkItem> get() {
177             if (throwCount-- > 0) {
178                 throw new RuntimeException("Sorry, can't provide items just yet");
179             } else {
180                 return items;
181             }
182         }
183     }
184 
185     @Test
186     void periodItemsThrow() throws TimeoutException {
187         var item1 = new TestWorkItem(i -> false, "Item 1");
188         var item2 = new TestWorkItem(i -> false, "Item 2");
189         var provider = new ThrowingItemProvider(List.of(item1, item2), 1);
190 
191         var bot = new TestBot(provider::get);
192 
193         new BotRunner(config(), List.of(bot)).runOnce(Duration.ofSeconds(10));
194         Assertions.assertFalse(item1.hasRun);
195         Assertions.assertFalse(item2.hasRun);
196 
197         new BotRunner(config(), List.of(bot)).runOnce(Duration.ofSeconds(10));
198         Assertions.assertTrue(item1.hasRun);
199         Assertions.assertTrue(item2.hasRun);
200     }
201 
202     @Test
203     void discardAdditionalBlockedItems() throws TimeoutException {
204         var item1 = new TestWorkItem(i -> false, "Item 1");
205         var item2 = new TestWorkItem(i -> false, "Item 2");
206         var item3 = new TestWorkItem(i -> false, "Item 3");
207         var item4 = new TestWorkItem(i -> false, "Item 4");
208         var bot = new TestBot(item1, item2, item3, item4);
209         var runner = new BotRunner(config(), List.of(bot));
210 
211         runner.runOnce(Duration.ofSeconds(10));
212 
213         Assertions.assertTrue(item1.hasRun);
214         Assertions.assertFalse(item2.hasRun);
215         Assertions.assertFalse(item3.hasRun);
216         Assertions.assertTrue(item4.hasRun);
217     }
218 
219     @Test
220     void dontDiscardDifferentBlockedItems() throws TimeoutException {
221         var item1 = new TestWorkItem(i -> false, "Item 1");
222         var item2 = new TestWorkItem(i -> false, "Item 2");
223         var item3 = new TestWorkItem(i -> false, "Item 3");
224         var item4 = new TestWorkItem(i -> false, "Item 4");
225         var item5 = new TestWorkItemChild(i -> false, "Item 5");
226         var item6 = new TestWorkItemChild(i -> false, "Item 6");
227         var item7 = new TestWorkItemChild(i -> false, "Item 7");
228         var bot = new TestBot(item1, item2, item3, item4, item5, item6, item7);
229         var runner = new BotRunner(config(), List.of(bot));
230 
231         runner.runOnce(Duration.ofSeconds(10));
232 
233         Assertions.assertTrue(item1.hasRun);
234         Assertions.assertFalse(item2.hasRun);
235         Assertions.assertFalse(item3.hasRun);
236         Assertions.assertTrue(item4.hasRun);
237         Assertions.assertFalse(item5.hasRun);
238         Assertions.assertFalse(item6.hasRun);
239         Assertions.assertTrue(item7.hasRun);
240     }
241 }