Skip to content

Commit 55c3ac2

Browse files
committed
Start of threadly-test artifact code from 5.42 threadly
Dependencies on threadly core project: * Clock is depended on most frequently * ArgumentVerifier (undocumented public API) is used to reduce code-duplication * NoThreadScheduler depended on by TestableScheduler The dependency on NoThreadScheduler is the greatest risk of producing byte code incompatibility. I looked at copying the code and bringing it over, and while possible, it's a very large amount of duplicated logic. Some of the logic could be simplified (for example NoThreadScheduler extends the task types to allow the time override). But not very much, it would be ~1k lines of duplicated code to handle the priority based queuing and the NoThreadScheduler concepts. Similar I am not seeing any impacts to the parent threadly artifacts we could simplify.
1 parent 9c4d6d0 commit 55c3ac2

18 files changed

+2659
-0
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
package org.threadly.test.concurrent;
2+
3+
import java.util.concurrent.TimeoutException;
4+
5+
import org.threadly.util.Clock;
6+
7+
/**
8+
* A simple class for verifying multi-threaded unit tests. If any thread has a failed a failed
9+
* {@code assert} or call to {@link #fail()} the main threads call to {@link #waitForTest()} will
10+
* throw an exception to indicate the test as a failure.
11+
* <p>
12+
* This class also provides a way to control the flow of a unit test by blocking main test thread
13+
* until {@link #signalComplete()} is called from the other thread.
14+
*
15+
* @since 1.0 (since 1.0.0 in threadly artifact)
16+
*/
17+
public class AsyncVerifier {
18+
protected static final int DEFAULT_TIMEOUT = 10_000;
19+
20+
protected final Object notifyLock;
21+
private int signalCount;
22+
private RuntimeException failure;
23+
24+
/**
25+
* Constructs a new {@link AsyncVerifier}.
26+
*/
27+
public AsyncVerifier() {
28+
notifyLock = new Object();
29+
signalCount = 0;
30+
failure = null;
31+
}
32+
33+
/**
34+
* Waits for a default of 10 seconds, or until signalComplete has been called once, or until a
35+
* failure occurs. If signalComplete has been called before this, this call will never block.
36+
*
37+
* @throws InterruptedException Thrown if thread is interrupted while waiting
38+
* @throws TimeoutException Thrown if timeout occurs without signalComplete being called
39+
*/
40+
public void waitForTest() throws InterruptedException, TimeoutException {
41+
waitForTest(DEFAULT_TIMEOUT, 1);
42+
}
43+
44+
/**
45+
* Waits a specified amount of time, or until signalComplete has been called once, or until a
46+
* failure occurs. If signalComplete has been called before this, this call will never block.
47+
*
48+
* @param timeoutInMs Timeout to wait for signalComplete action to occur
49+
* @throws InterruptedException Thrown if thread is interrupted while waiting
50+
* @throws TimeoutException Thrown if timeout occurs without signalComplete being called
51+
*/
52+
public void waitForTest(long timeoutInMs) throws InterruptedException, TimeoutException {
53+
waitForTest(timeoutInMs, 1);
54+
}
55+
56+
/**
57+
* Waits a specified amount of time, or until signalComplete has been called a specified amount
58+
* of times, or until a failure occurs.
59+
* <p>
60+
* If {@link #waitForTest()} is being called multiple times on the same instance, the
61+
* signalComplete count is NOT reset. So you must either create new instances, or pass in a
62+
* larger value for the expected signalComplete count.
63+
*
64+
* @param timeoutInMs Timeout to wait for signalComplete action to occur
65+
* @param signalCount Amount of signalComplete calls to expect before unblocking
66+
* @throws InterruptedException Thrown if thread is interrupted while waiting
67+
* @throws TimeoutException Thrown if timeout occurs without signalComplete being called
68+
*/
69+
public void waitForTest(long timeoutInMs, int signalCount) throws InterruptedException,
70+
TimeoutException {
71+
long startTime = Clock.accurateForwardProgressingMillis();
72+
long remainingWaitTime = timeoutInMs;
73+
synchronized (notifyLock) {
74+
while (this.signalCount < signalCount &&
75+
remainingWaitTime > 0 &&
76+
failure == null) {
77+
notifyLock.wait(remainingWaitTime);
78+
79+
remainingWaitTime = timeoutInMs - (Clock.accurateForwardProgressingMillis() - startTime);
80+
}
81+
}
82+
83+
if (failure != null) {
84+
throw failure;
85+
} else if (remainingWaitTime <= 0) {
86+
throw new TimeoutException();
87+
}
88+
// if neither are true we exited normally
89+
}
90+
91+
/**
92+
* Call to indicate that this thread has finished, and should notify the waiting main test
93+
* thread that the test may be complete.
94+
*/
95+
public void signalComplete() {
96+
synchronized (notifyLock) {
97+
signalCount++;
98+
99+
notifyLock.notifyAll();
100+
}
101+
}
102+
103+
/**
104+
* Verifies that the passed in condition is true. If it is not an exception is thrown in this
105+
* thread, as well as possibly any blocking thread waiting at {@link #waitForTest()}.
106+
*
107+
* @param condition condition to verify is {@code true}
108+
*/
109+
public void assertTrue(boolean condition) {
110+
if (! condition) {
111+
synchronized (notifyLock) {
112+
failure = new TestFailure("assertTrue failure");
113+
114+
notifyLock.notifyAll();
115+
}
116+
117+
throw failure;
118+
}
119+
}
120+
121+
/**
122+
* Verifies that the passed in condition is false. If it is not an exception is thrown in this
123+
* thread, as well as possibly any blocking thread waiting at {@link #waitForTest()}.
124+
*
125+
* @param condition condition to verify is {@code false}
126+
*/
127+
public void assertFalse(boolean condition) {
128+
if (condition) {
129+
synchronized (notifyLock) {
130+
failure = new TestFailure("assertFalse failure");
131+
132+
notifyLock.notifyAll();
133+
}
134+
135+
throw failure;
136+
}
137+
}
138+
139+
/**
140+
* Verifies that the passed in object is null. If it is not null an exception is thrown in this
141+
* thread, as well as possibly any blocking thread waiting at {@link #waitForTest()}.
142+
*
143+
* @param o Object to verify is {@code null}
144+
*/
145+
public void assertNull(Object o) {
146+
if (o != null) {
147+
synchronized (notifyLock) {
148+
failure = new TestFailure("Object is not null: " + o);
149+
150+
notifyLock.notifyAll();
151+
}
152+
153+
throw failure;
154+
}
155+
}
156+
157+
/**
158+
* Verifies that the passed in object is not null. If it is null an exception is thrown in this
159+
* thread, as well as possibly any blocking thread waiting at {@link #waitForTest()}.
160+
*
161+
* @param o Object to verify is not {@code null}
162+
*/
163+
public void assertNotNull(Object o) {
164+
if (o == null) {
165+
synchronized (notifyLock) {
166+
failure = new TestFailure("Object is null");
167+
168+
notifyLock.notifyAll();
169+
}
170+
171+
throw failure;
172+
}
173+
}
174+
175+
/**
176+
* Verifies that the passed in values are equal using the o1.equals(o2) relationship. If this
177+
* check fails an exception is thrown in this thread, as well as any blocking thread waiting at
178+
* {@link #waitForTest()}.
179+
*
180+
* @param o1 First object to compare against
181+
* @param o2 Second object to compare against
182+
*/
183+
public void assertEquals(Object o1, Object o2) {
184+
boolean nullMissmatch = (o1 == null && o2 != null) ||
185+
(o1 != null && o2 == null);
186+
if (nullMissmatch ||
187+
! ((o1 == null && o2 == null) || o1.equals(o2))) {
188+
synchronized (notifyLock) {
189+
failure = new TestFailure(o1 + " != " + o2);
190+
191+
notifyLock.notifyAll();
192+
}
193+
194+
throw failure;
195+
}
196+
}
197+
198+
/**
199+
* Marks a failure with no cause. This causes an exception to be thrown in the calling thread,
200+
* as well was any blocking thread waiting at {@link #waitForTest()}.
201+
*/
202+
public void fail() {
203+
fail("");
204+
}
205+
206+
/**
207+
* Marks a failure with a specified message. This causes an exception to be thrown in the
208+
* calling thread, as well was any blocking thread waiting at {@link #waitForTest()}.
209+
*
210+
* @param message Message to be provided in failure exception
211+
*/
212+
public void fail(String message) {
213+
synchronized (notifyLock) {
214+
failure = new TestFailure(message);
215+
216+
notifyLock.notifyAll();
217+
}
218+
219+
throw failure;
220+
}
221+
222+
/**
223+
* Marks a failure with a specified throwable cause. This causes an exception to be thrown in
224+
* the calling thread, as well was any blocking thread waiting at {@link #waitForTest()}.
225+
*
226+
* @param cause Throwable cause to be provided in failure exception
227+
*/
228+
public void fail(Throwable cause) {
229+
synchronized (notifyLock) {
230+
failure = new TestFailure(cause);
231+
232+
notifyLock.notifyAll();
233+
}
234+
235+
throw failure;
236+
}
237+
238+
/**
239+
* Exception to represent a failure in a test assertion.
240+
*
241+
* @since 1.0 (since 1.0.0 in threadly artifact)
242+
*/
243+
public static class TestFailure extends RuntimeException {
244+
private static final long serialVersionUID = -4683332806581392944L;
245+
246+
protected TestFailure(String failureMsg) {
247+
super(failureMsg);
248+
}
249+
250+
protected TestFailure(Throwable cause) {
251+
super(cause);
252+
}
253+
}
254+
}

0 commit comments

Comments
 (0)