@@ -21,33 +21,76 @@ import java.util.concurrent.atomic.AtomicBoolean
21
21
import java.util.concurrent.atomic.AtomicInteger
22
22
import java.util.concurrent.atomic.AtomicReference
23
23
24
+ /* *
25
+ * Base class for tests, so that tests for predictable scheduling of actions in multiple coroutines sharing a single
26
+ * thread can be written. Use it like this:
27
+ *
28
+ * ```
29
+ * class MyTest {
30
+ * @Test
31
+ * fun testSomething() = runBlocking<Unit> { // run in the context of the main thread
32
+ * expect(1) // initiate action counter
33
+ * val job = launch(context) { // use the context of the main thread
34
+ * expect(3) // the body of this coroutine in going to be executed in the 3rd step
35
+ * }
36
+ * expect(2) // launch just scheduled coroutine for exectuion later, so this line is executed second
37
+ * yield() // yield main thread to the launched job
38
+ * finish(4) // fourth step is the last one. `finish` must be invoked or test fails
39
+ * }
40
+ * }
41
+ * ```
42
+ */
24
43
open class TestBase {
44
+ /* *
45
+ * Is `true` when nightly stress test is done.
46
+ */
25
47
val isStressTest = System .getProperty(" stressTest" ) != null
48
+
49
+ /* *
50
+ * Multiply various constants in stress tests by this factor, so that they run longer during nightly stress test.
51
+ */
26
52
val stressTestMultiplier = if (isStressTest) 30 else 1
27
53
28
- var actionIndex = AtomicInteger ()
29
- var finished = AtomicBoolean ()
30
- var error = AtomicReference <Throwable >()
54
+ private var actionIndex = AtomicInteger ()
55
+ private var finished = AtomicBoolean ()
56
+ private var error = AtomicReference <Throwable >()
31
57
58
+ /* *
59
+ * Throws [IllegalStateException] like `error` in stdlib, but also ensures that the test will not
60
+ * complete successfully even if this exception is consumed somewhere in the test.
61
+ */
32
62
public fun error (message : Any ): Nothing {
33
63
val exception = IllegalStateException (message.toString())
34
64
error.compareAndSet(null , exception)
35
65
throw exception
36
66
}
37
67
68
+ /* *
69
+ * Throws [IllegalStateException] when `value` is false like `check` in stdlib, but also ensures that the
70
+ * test will not complete successfully even if this exception is consumed somewhere in the test.
71
+ */
38
72
public inline fun check (value : Boolean , lazyMessage : () -> Any ): Unit {
39
73
if (! value) error(lazyMessage())
40
74
}
41
75
76
+ /* *
77
+ * Asserts that this invocation is `index`-th in the execution sequence (counting from one).
78
+ */
42
79
fun expect (index : Int ) {
43
80
val wasIndex = actionIndex.incrementAndGet()
44
81
check(index == wasIndex) { " Expecting action index $index but it is actually $wasIndex " }
45
82
}
46
83
84
+ /* *
85
+ * Asserts that this line is never executed.
86
+ */
47
87
fun expectUnreached () {
48
88
error(" Should not be reached" )
49
89
}
50
90
91
+ /* *
92
+ * Asserts that this it the last action in the test. It must be invoked by any test that used [expect].
93
+ */
51
94
fun finish (index : Int ) {
52
95
expect(index)
53
96
check(! finished.getAndSet(true )) { " Should call 'finish(...)' at most once" }
@@ -58,5 +101,4 @@ open class TestBase {
58
101
error.get()?.let { throw it }
59
102
check(actionIndex.get() == 0 || finished.get()) { " Expecting that 'finish(...)' was invoked, but it was not" }
60
103
}
61
-
62
104
}
0 commit comments