<lambda>null1 package com.android.testutils
2
3 import android.os.SystemClock
4 import java.util.concurrent.CyclicBarrier
5 import kotlin.test.assertEquals
6 import kotlin.test.assertFails
7 import kotlin.test.assertNull
8 import kotlin.test.assertTrue
9
10 // The table contains pairs associating a regexp with the code to run. The statement is matched
11 // against each matcher in sequence and when a match is found the associated code is run, passing
12 // it the TrackRecord under test and the result of the regexp match.
13 typealias InterpretMatcher<T> = Pair<Regex, (ConcurrentInterpreter<T>, T, MatchResult) -> Any?>
14
15 // The default unit of time for interpreted tests
16 const val INTERPRET_TIME_UNIT = 60L // ms
17
18 /**
19 * A small interpreter for testing parallel code.
20 *
21 * The interpreter will read a list of lines consisting of "|"-separated statements, e.g. :
22 * sleep 2 ; unblock thread2 | wait thread2 time 2..5
23 * sendMessage "x" | obtainMessage = "x" time 0..1
24 *
25 * Each column runs in a different concurrent thread and all threads wait for each other in
26 * between lines. Each statement is split on ";" then matched with regular expressions in the
27 * instructionTable constant, which contains the code associated with each statement. The
28 * interpreter supports an object being passed to the interpretTestSpec() method to be passed
29 * in each lambda (think about the object under test), and an optional transform function to be
30 * executed on the object at the start of every thread.
31 *
32 * The time unit is defined in milliseconds by the interpretTimeUnit member, which has a default
33 * value but can be passed to the constructor. Whitespace is ignored.
34 *
35 * The interpretation table has to be passed as an argument. It's a table associating a regexp
36 * with the code that should execute, as a function taking three arguments : the interpreter,
37 * the regexp match, and the object. See the individual tests for the DSL of that test.
38 * Implementors for new interpreting languages are encouraged to look at the defaultInterpretTable
39 * constant below for an example of how to write an interpreting table.
40 * Some expressions already exist by default and can be used by all interpreters. Refer to
41 * getDefaultInstructions() below for a list and documentation.
42 */
43 open class ConcurrentInterpreter<T>(localInterpretTable: List<InterpretMatcher<T>>) {
44 private val interpretTable: List<InterpretMatcher<T>> =
45 localInterpretTable + getDefaultInstructions()
46 // The last time the thread became blocked, with base System.currentTimeMillis(). This should
47 // be set immediately before any time the thread gets blocked.
48 internal val lastBlockedTime = ThreadLocal<Long>()
49
50 // Split the line into multiple statements separated by ";" and execute them. Return whatever
51 // the last statement returned.
52 fun interpretMultiple(instr: String, r: T): Any? {
53 return instr.split(";").map { interpret(it.trim(), r) }.last()
54 }
55
56 // Match the statement to a regex and interpret it.
57 fun interpret(instr: String, r: T): Any? {
58 val (matcher, code) =
59 interpretTable.find { instr matches it.first } ?: throw SyntaxException(instr)
60 val match = matcher.matchEntire(instr) ?: throw SyntaxException(instr)
61 return code(this, r, match)
62 }
63
64 /**
65 * Spins as many threads as needed by the test spec and interpret each program concurrently.
66 *
67 * All threads wait on a CyclicBarrier after each line.
68 * |lineShift| says how many lines after the call the spec starts. This is used for error
69 * reporting. Unfortunately AFAICT there is no way to get the line of an argument rather
70 * than the line at which the expression starts.
71 *
72 * This method is mostly meant for implementations that extend the ConcurrentInterpreter
73 * class to add their own directives and instructions. These may need to operate on some
74 * data, which can be passed in |initial|. For example, an interpreter specialized in callbacks
75 * may want to pass the callback there. In some cases, it's necessary that each thread
76 * performs a transformation *after* it starts on that value before starting ; in this case,
77 * the transformation can be passed to |threadTransform|. The default is to return |initial| as
78 * is. Look at some existing child classes of this interpreter for some examples of how this
79 * can be used.
80 *
81 * @param spec The test spec, as a string of lines separated by pipes.
82 * @param initial An initial value passed to all threads.
83 * @param lineShift How many lines after the call the spec starts, for error reporting.
84 * @param threadTransform an optional transformation that each thread will apply to |initial|
85 */
86 fun interpretTestSpec(
87 spec: String,
88 initial: T,
89 lineShift: Int = 0,
90 threadTransform: (T) -> T = { it }
91 ) {
92 // For nice stack traces
93 val callSite = getCallingMethod()
94 val lines = spec.trim().trim('\n').split("\n").map { it.split("|") }
95 // |lines| contains arrays of strings that make up the statements of a thread : in other
96 // words, it's an array that contains a list of statements for each column in the spec.
97 // E.g. if the string is """
98 // a | b | c
99 // d | e | f
100 // """, then lines is [ [ "a", "b", "c" ], [ "d", "e", "f" ] ].
101 val threadCount = lines[0].size
102 assertTrue(lines.all { it.size == threadCount })
103 val threadInstructions = (0 until threadCount).map { i -> lines.map { it[i].trim() } }
104 // |threadInstructions| is a list where each element is the list of instructions for the
105 // thread at the index. In other words, it's just |lines| transposed. In the example
106 // above, it would be [ [ "a", "d" ], [ "b", "e" ], [ "c", "f" ] ]
107 // mapIndexed below will pass in |instructions| the list of instructions for this thread.
108 val barrier = CyclicBarrier(threadCount)
109 var crash: InterpretException? = null
110 threadInstructions.mapIndexed { threadIndex, instructions ->
111 Thread {
112 val threadLocal = threadTransform(initial)
113 lastBlockedTime.set(System.currentTimeMillis())
114 barrier.await()
115 var lineNum = 0
116 instructions.forEach {
117 if (null != crash) return@Thread
118 lineNum += 1
119 try {
120 interpretMultiple(it, threadLocal)
121 } catch (e: Throwable) {
122 // If fail() or some exception was called, the thread will come here ; if
123 // the exception isn't caught the process will crash, which is not nice for
124 // testing. Instead, catch the exception, cancel other threads, and report
125 // nicely. Catch throwable because fail() is AssertionError, which inherits
126 // from Error.
127 crash = InterpretException(threadIndex, it,
128 callSite.lineNumber + lineNum + lineShift,
129 callSite.className, callSite.methodName, callSite.fileName, e)
130 }
131 lastBlockedTime.set(System.currentTimeMillis())
132 barrier.await()
133 }
134 }.also { it.start() }
135 }.forEach { it.join() }
136 // If the test failed, crash with line number
137 crash?.let { throw it }
138 }
139
140 // Helper to get the stack trace for a calling method
141 private fun getCallingStackTrace(): Array<StackTraceElement> {
142 try {
143 throw RuntimeException()
144 } catch (e: RuntimeException) {
145 return e.stackTrace
146 }
147 }
148
149 // Find the calling method. This is the first method in the stack trace that is annotated
150 // with @Test.
151 fun getCallingMethod(): StackTraceElement {
152 val stackTrace = getCallingStackTrace()
153 return stackTrace.find { element ->
154 val clazz = Class.forName(element.className)
155 // Because the stack trace doesn't list the formal arguments, find all methods with
156 // this name and return this name if any of them is annotated with @Test.
157 clazz.declaredMethods
158 .filter { method -> method.name == element.methodName }
159 .any { method -> method.getAnnotation(org.junit.Test::class.java) != null }
160 } ?: stackTrace[3]
161 // If no method is annotated return the 4th one, because that's what it usually is :
162 // 0 is getCallingStackTrace, 1 is this method, 2 is ConcurrentInterpreter#interpretTestSpec
163 }
164 }
165
166 /**
167 * Default instructions available to all interpreters.
168 * sleep(x) : sleeps for x time units and returns Unit ; sleep alone means sleep(1)
169 * EXPR = VALUE : asserts that EXPR equals VALUE. EXPR is interpreted. VALUE can either be the
170 * string "null" or an int. Returns Unit.
171 * EXPR time x..y : measures the time taken by EXPR and asserts it took at least x and at most
172 * y time units.
173 * EXPR // any text : comments are ignored.
174 * EXPR fails : checks that EXPR throws some exception.
175 */
getDefaultInstructionsnull176 private fun <T> getDefaultInstructions() = listOf<InterpretMatcher<T>>(
177 // Interpret an empty line as doing nothing.
178 Regex("") to { _, _, _ -> null },
179 // Ignore comments.
rnull180 Regex("(.*)//.*") to { i, t, r -> i.interpret(r.strArg(1), t) },
181 // Interpret "XXX time x..y" : run XXX and check it took at least x and not more than y
rnull182 Regex("""(.*)\s*time\s*(\d+)\.\.(\d+)""") to { i, t, r ->
183 val lateStart = System.currentTimeMillis()
184 i.interpret(r.strArg(1), t)
185 val end = System.currentTimeMillis()
186 // There is uncertainty in measuring time.
187 // It takes some (small) time for the thread to even measure the time at which it
188 // starts interpreting the instruction. It is therefore possible that thread A sleeps for
189 // n milliseconds, and B expects to have waited for at least n milliseconds, but because
190 // B started measuring after 1ms or so, B thinks it didn't wait long enough.
191 // To avoid this, when the `time` instruction tests the instruction took at least X and
192 // at most Y, it tests X against a time measured since *before* the thread blocked but
193 // Y against a time measured as late as possible. This ensures that the timer is
194 // sufficiently lenient in both directions that there are no flaky measures.
195 val minTime = end - lateStart
196 val maxTime = end - i.lastBlockedTime.get()!!
197
198 assertTrue(maxTime >= r.timeArg(2),
199 "Should have taken at least ${r.timeArg(2)} but took less than $maxTime")
200 assertTrue(minTime <= r.timeArg(3),
201 "Should have taken at most ${r.timeArg(3)} but took more than $minTime")
202 },
203 // Interpret "XXX = YYY" : run XXX and assert its return value is equal to YYY. "null" supported
rnull204 Regex("""(.*)\s*=\s*(null|\d+)""") to { i, t, r ->
205 i.interpret(r.strArg(1), t).also {
206 if ("null" == r.strArg(2)) assertNull(it) else assertEquals(r.intArg(2), it)
207 }
208 },
209 // Interpret sleep. Optional argument for the count, in INTERPRET_TIME_UNIT units.
rnull210 Regex("""sleep(\((\d+)\))?""") to { i, t, r ->
211 SystemClock.sleep(if (r.strArg(2).isEmpty()) INTERPRET_TIME_UNIT else r.timeArg(2))
212 },
rnull213 Regex("""(.*)\s*fails""") to { i, t, r ->
214 assertFails { i.interpret(r.strArg(1), t) }
215 }
216 )
217
218 class SyntaxException(msg: String, cause: Throwable? = null) : RuntimeException(msg, cause)
219 class InterpretException(
220 threadIndex: Int,
221 instr: String,
222 lineNum: Int,
223 className: String,
224 methodName: String,
225 fileName: String,
226 cause: Throwable
227 ) : RuntimeException("Failure: $instr", cause) {
228 init {
229 stackTrace = arrayOf(StackTraceElement(
230 className,
231 "$methodName:thread$threadIndex",
232 fileName,
233 lineNum)) + super.getStackTrace()
234 }
235 }
236
237 // Some small helpers to avoid to say the large ".groupValues[index].trim()" every time
strArgnull238 fun MatchResult.strArg(index: Int) = this.groupValues[index].trim()
239 fun MatchResult.intArg(index: Int) = strArg(index).toInt()
240 fun MatchResult.timeArg(index: Int) = INTERPRET_TIME_UNIT * intArg(index)
241