Commit | Line | Data |
---|---|---|
9d979fda MK |
1 | /******************************************************************************* |
2 | * Copyright (c) 2015 Ericsson | |
3 | * | |
4 | * All rights reserved. This program and the accompanying materials are | |
5 | * made available under the terms of the Eclipse Public License v1.0 which | |
6 | * accompanies this distribution, and is available at | |
7 | * http://www.eclipse.org/legal/epl-v10.html | |
8 | * | |
9 | * Contributors: | |
10 | * Matthew Khouzam - Initial API and implementation | |
11 | *******************************************************************************/ | |
12 | ||
13 | package org.eclipse.tracecompass.common.core.tests.collect; | |
14 | ||
15 | import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString; | |
16 | import static org.junit.Assert.assertEquals; | |
17 | import static org.junit.Assert.assertFalse; | |
18 | import static org.junit.Assert.assertTrue; | |
19 | ||
47c79d9f AM |
20 | import java.util.Collection; |
21 | import java.util.Deque; | |
d6e2666b | 22 | import java.util.HashSet; |
9d979fda MK |
23 | import java.util.LinkedList; |
24 | import java.util.Random; | |
d6e2666b | 25 | import java.util.Set; |
9d979fda MK |
26 | import java.util.concurrent.Callable; |
27 | import java.util.concurrent.ExecutionException; | |
28 | import java.util.concurrent.ExecutorService; | |
29 | import java.util.concurrent.Executors; | |
30 | import java.util.concurrent.Future; | |
31 | import java.util.concurrent.TimeUnit; | |
32 | ||
33 | import org.eclipse.tracecompass.common.core.NonNullUtils; | |
34 | import org.eclipse.tracecompass.common.core.collect.BufferedBlockingQueue; | |
35 | import org.junit.Before; | |
36 | import org.junit.Rule; | |
37 | import org.junit.Test; | |
38 | import org.junit.rules.TestRule; | |
39 | import org.junit.rules.Timeout; | |
40 | ||
47c79d9f AM |
41 | import com.google.common.collect.HashMultiset; |
42 | import com.google.common.collect.Iterators; | |
43 | ||
9d979fda MK |
44 | /** |
45 | * Test suite for the {@link BufferedBlockingQueue} | |
46 | */ | |
47 | public class BufferedBlockingQueueTest { | |
48 | ||
49 | /** Timeout the tests after 2 minutes */ | |
50 | @Rule | |
51 | public TestRule timeoutRule = new Timeout(120000); | |
52 | ||
47c79d9f AM |
53 | private static final String testString = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" + |
54 | "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" + | |
55 | "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"; | |
56 | ||
9d979fda MK |
57 | private BufferedBlockingQueue<Character> charQueue; |
58 | ||
59 | /** | |
60 | * Test setup | |
61 | */ | |
62 | @Before | |
63 | public void init() { | |
64 | charQueue = new BufferedBlockingQueue<>(15, 15); | |
65 | } | |
66 | ||
67 | /** | |
68 | * Test inserting one element and removing it. | |
69 | */ | |
70 | @Test | |
71 | public void testSingleInsertion() { | |
72 | Character element = 'x'; | |
73 | charQueue.put(element); | |
74 | charQueue.flushInputBuffer(); | |
75 | ||
76 | Character out = charQueue.take(); | |
77 | assertEquals(element, out); | |
78 | } | |
79 | ||
80 | /** | |
81 | * Test insertion of elements that fit into the input buffer. | |
82 | */ | |
83 | @Test | |
84 | public void testSimpleInsertion() { | |
85 | String string = "Hello world!"; | |
86 | for (char elem : string.toCharArray()) { | |
87 | charQueue.put(elem); | |
88 | } | |
89 | charQueue.flushInputBuffer(); | |
90 | ||
91 | StringBuilder sb = new StringBuilder(); | |
92 | while (!charQueue.isEmpty()) { | |
93 | sb.append(charQueue.take()); | |
94 | } | |
95 | assertEquals(string, sb.toString()); | |
96 | } | |
97 | ||
98 | /** | |
99 | * Test insertion of elements that will require more than one input buffer. | |
100 | */ | |
101 | @Test | |
102 | public void testLargeInsertion() { | |
103 | String string = testString.substring(0, 222); | |
104 | for (char elem : string.toCharArray()) { | |
105 | charQueue.put(elem); | |
106 | } | |
107 | charQueue.flushInputBuffer(); | |
108 | ||
109 | StringBuilder sb = new StringBuilder(); | |
110 | while (!charQueue.isEmpty()) { | |
111 | sb.append(charQueue.take()); | |
112 | } | |
113 | assertEquals(string, sb.toString()); | |
114 | } | |
115 | ||
116 | /** | |
117 | * Test the state of the {@link BufferedBlockingQueue#isEmpty()} method at | |
118 | * various moments. | |
119 | */ | |
120 | @Test | |
121 | public void testIsEmpty() { | |
122 | BufferedBlockingQueue<String> stringQueue = new BufferedBlockingQueue<>(15, 15); | |
123 | assertTrue(stringQueue.isEmpty()); | |
124 | ||
125 | stringQueue.put("Hello"); | |
126 | assertFalse(stringQueue.isEmpty()); | |
127 | ||
128 | stringQueue.flushInputBuffer(); | |
129 | assertFalse(stringQueue.isEmpty()); | |
130 | ||
131 | stringQueue.flushInputBuffer(); | |
132 | assertFalse(stringQueue.isEmpty()); | |
133 | ||
134 | stringQueue.flushInputBuffer(); | |
135 | stringQueue.take(); | |
136 | assertTrue(stringQueue.isEmpty()); | |
137 | ||
138 | stringQueue.flushInputBuffer(); | |
139 | assertTrue(stringQueue.isEmpty()); | |
140 | } | |
141 | ||
142 | /** | |
143 | * Write random data in and read it, several times. | |
144 | */ | |
145 | @Test | |
146 | public void testOddInsertions() { | |
147 | BufferedBlockingQueue<Object> objectQueue = new BufferedBlockingQueue<>(15, 15); | |
148 | LinkedList<Object> expectedValues = new LinkedList<>(); | |
149 | Random rnd = new Random(); | |
150 | rnd.setSeed(123); | |
151 | ||
152 | for (int i = 0; i < 10; i++) { | |
153 | /* | |
154 | * The queue's total size is 225 (15x15). We must make sure to not | |
155 | * fill it up here! | |
156 | */ | |
157 | for (int j = 0; j < 50; j++) { | |
158 | Integer testInt = NonNullUtils.checkNotNull(rnd.nextInt()); | |
159 | Long testLong = NonNullUtils.checkNotNull(rnd.nextLong()); | |
160 | Double testDouble = NonNullUtils.checkNotNull(rnd.nextDouble()); | |
161 | Double testGaussian = NonNullUtils.checkNotNull(rnd.nextGaussian()); | |
162 | ||
163 | expectedValues.add(testInt); | |
164 | expectedValues.add(testLong); | |
165 | expectedValues.add(testDouble); | |
166 | expectedValues.add(testGaussian); | |
167 | objectQueue.put(testInt); | |
168 | objectQueue.put(testLong); | |
169 | objectQueue.put(testDouble); | |
170 | objectQueue.put(testGaussian); | |
171 | } | |
172 | objectQueue.flushInputBuffer(); | |
173 | ||
174 | while (!expectedValues.isEmpty()) { | |
175 | Object expected = expectedValues.removeFirst(); | |
176 | Object actual = objectQueue.take(); | |
177 | assertEquals(expected, actual); | |
178 | } | |
179 | } | |
180 | } | |
181 | ||
182 | /** | |
183 | * Read with a producer and a consumer | |
184 | * | |
185 | * @throws InterruptedException | |
186 | * The test was interrupted | |
187 | */ | |
188 | @Test | |
189 | public void testMultiThread() throws InterruptedException { | |
190 | /* A character not found in the test string */ | |
191 | final Character lastElement = '%'; | |
192 | ||
193 | Thread producer = new Thread() { | |
194 | @Override | |
195 | public void run() { | |
196 | for (char c : testString.toCharArray()) { | |
197 | charQueue.put(c); | |
198 | } | |
199 | charQueue.put(lastElement); | |
200 | charQueue.flushInputBuffer(); | |
201 | } | |
202 | }; | |
203 | producer.start(); | |
204 | ||
205 | Thread consumer = new Thread() { | |
206 | @Override | |
207 | public void run() { | |
208 | Character s = charQueue.take(); | |
209 | while (!s.equals(lastElement)) { | |
210 | s = charQueue.take(); | |
211 | } | |
212 | } | |
213 | }; | |
214 | consumer.start(); | |
215 | ||
216 | consumer.join(); | |
217 | producer.join(); | |
218 | } | |
219 | ||
47c79d9f AM |
220 | /** |
221 | * Test the contents returned by {@link BufferedBlockingQueue#iterator()}. | |
222 | * | |
223 | * The test is sequential, because the iterator has no guarantee wrt to its | |
224 | * contents when run concurrently. | |
225 | */ | |
226 | @Test | |
227 | public void testIteratorContents() { | |
228 | Deque<Character> expected = new LinkedList<>(); | |
229 | ||
230 | /* Iterator should be empty initially */ | |
231 | assertFalse(charQueue.iterator().hasNext()); | |
232 | ||
233 | /* Insert the first 50 elements */ | |
234 | for (int i = 0; i < 50; i++) { | |
235 | char c = testString.charAt(i); | |
236 | charQueue.put(c); | |
237 | expected.addFirst(c); | |
238 | } | |
239 | LinkedList<Character> actual = new LinkedList<>(); | |
240 | Iterators.addAll(actual, charQueue.iterator()); | |
241 | assertSameElements(expected, actual); | |
242 | ||
243 | /* | |
244 | * Insert more elements, flush the input buffer (should not affect the | |
245 | * iteration). | |
246 | */ | |
247 | for (int i = 50; i < 60; i++) { | |
248 | char c = testString.charAt(i); | |
249 | charQueue.put(c); | |
250 | charQueue.flushInputBuffer(); | |
251 | expected.addFirst(c); | |
252 | } | |
253 | actual = new LinkedList<>(); | |
254 | Iterators.addAll(actual, charQueue.iterator()); | |
255 | assertSameElements(expected, actual); | |
256 | ||
257 | /* Consume the 30 last elements from the queue */ | |
258 | for (int i = 0; i < 30; i++) { | |
259 | charQueue.take(); | |
260 | expected.removeLast(); | |
261 | } | |
262 | actual = new LinkedList<>(); | |
263 | Iterators.addAll(actual, charQueue.iterator()); | |
264 | assertSameElements(expected, actual); | |
265 | ||
266 | /* Now empty the queue */ | |
267 | while (!charQueue.isEmpty()) { | |
268 | charQueue.take(); | |
269 | expected.removeLast(); | |
270 | } | |
271 | assertFalse(charQueue.iterator().hasNext()); | |
272 | } | |
273 | ||
274 | /** | |
275 | * Utility method to verify that two collections contain the exact same | |
276 | * elements, not necessarily in the same iteration order. | |
277 | * | |
278 | * {@link Collection#equals} requires the iteration order to be the same, | |
279 | * which we do not want here. | |
280 | * | |
281 | * Using a {@link Set} or {@link Collection#containsAll} is not sufficient | |
282 | * either, because those will throw away duplicate elements. | |
283 | */ | |
284 | private static <T> void assertSameElements(Collection<T> c1, Collection<T> c2) { | |
285 | assertEquals(HashMultiset.create(c1), HashMultiset.create(c2)); | |
286 | } | |
287 | ||
9d979fda | 288 | /** |
d6e2666b AM |
289 | * Test iterating on the queue while a producer and a consumer threads are |
290 | * using it. The iteration should not affect the elements taken by the | |
291 | * consumer. | |
9d979fda MK |
292 | * |
293 | * @throws InterruptedException | |
294 | * The test was interrupted | |
295 | * @throws ExecutionException | |
296 | * If one of the sub-threads throws an exception, which should | |
297 | * not happen | |
298 | */ | |
299 | @Test | |
d6e2666b AM |
300 | public void testConcurrentIteration() throws InterruptedException, ExecutionException { |
301 | final BufferedBlockingQueue<String> queue = new BufferedBlockingQueue<>(15, 15); | |
9d979fda | 302 | |
d6e2666b | 303 | ExecutorService pool = Executors.newFixedThreadPool(3); |
9d979fda MK |
304 | |
305 | final String poisonPill = "That's all folks!"; | |
9d979fda MK |
306 | |
307 | Runnable producer = new Runnable() { | |
308 | @Override | |
309 | public void run() { | |
310 | for (int i = 0; i < testString.length(); i++) { | |
d6e2666b | 311 | queue.put(nullToEmptyString(String.valueOf(testString.charAt(i)))); |
9d979fda | 312 | } |
d6e2666b AM |
313 | queue.put(poisonPill); |
314 | queue.flushInputBuffer(); | |
9d979fda MK |
315 | } |
316 | }; | |
317 | ||
318 | Callable<String> consumer = new Callable<String>() { | |
319 | @Override | |
320 | public String call() { | |
321 | StringBuilder sb = new StringBuilder(); | |
d6e2666b | 322 | String s = queue.take(); |
9d979fda MK |
323 | while (!s.equals(poisonPill)) { |
324 | sb.append(s); | |
d6e2666b | 325 | s = queue.take(); |
9d979fda MK |
326 | } |
327 | return sb.toString(); | |
328 | } | |
329 | }; | |
330 | ||
331 | Runnable inquisitor = new Runnable() { | |
332 | @Override | |
333 | public void run() { | |
d6e2666b AM |
334 | for (int i = 0; i < 10; i++) { |
335 | final Set<String> results = new HashSet<>(); | |
9d979fda MK |
336 | /* |
337 | * The interest of this test is here: we are iterating on | |
338 | * the queue while it is being used. | |
339 | */ | |
d6e2666b AM |
340 | for (String input : queue) { |
341 | results.add(input); | |
9d979fda MK |
342 | } |
343 | } | |
9d979fda MK |
344 | } |
345 | }; | |
346 | ||
347 | pool.submit(producer); | |
348 | pool.submit(inquisitor); | |
349 | Future<String> message = pool.submit(consumer); | |
9d979fda MK |
350 | |
351 | pool.shutdown(); | |
352 | pool.awaitTermination(2, TimeUnit.MINUTES); | |
353 | ||
354 | assertEquals(testString, message.get()); | |
9d979fda MK |
355 | } |
356 | ||
47c79d9f AM |
357 | |
358 | } |