mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 11:07:10 +00:00
test(sidecar): pin the bound on a wedged adb read
This commit is contained in:
1 parent
23a2454459
commit
34b8be2d8c
1 file changed
+120
@@ -0,0 +1,120 @@
|
|||||||
|
package dev.sanderling.sidecar
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class AdbOutputTimeoutTest {
|
||||||
|
|
||||||
|
// An adb wedged on the link neither writes nor exits, so the read never
|
||||||
|
// reaches EOF. Without a bound on the READ the step waits for as long as
|
||||||
|
// adb feels like it: one such stall measured ~100s against a remote adb
|
||||||
|
// server. The bound has to release the reader as well as return, which is
|
||||||
|
// what killing the process does.
|
||||||
|
@Test(timeout = 20_000L)
|
||||||
|
fun aWedgedReadIsAbandonedAtTheBoundInsteadOfHangingForever() {
|
||||||
|
val process = FakeProcess(BlockingStream())
|
||||||
|
val logged = mutableListOf<String>()
|
||||||
|
|
||||||
|
val started = System.currentTimeMillis()
|
||||||
|
val output = readProcessOutput(process, 200L, { "adb shell pidof" }) {
|
||||||
|
logged.add(it)
|
||||||
|
}
|
||||||
|
val elapsed = System.currentTimeMillis() - started
|
||||||
|
|
||||||
|
assertEquals("", output, "a read that never lands is no answer")
|
||||||
|
assertTrue(process.destroyed, "the wedged adb must be killed, not left")
|
||||||
|
assertTrue(
|
||||||
|
elapsed < 10_000L,
|
||||||
|
"returned in ${elapsed}ms, not at a bound",
|
||||||
|
)
|
||||||
|
assertEquals(1, logged.size, "a silent timeout hides a degrading link")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 20_000L)
|
||||||
|
fun theAbandonedReadNamesTheCommandAndTheBound() {
|
||||||
|
val logged = mutableListOf<String>()
|
||||||
|
readProcessOutput(
|
||||||
|
FakeProcess(BlockingStream()),
|
||||||
|
200L,
|
||||||
|
{ "adb -s emulator-5556 shell cat /proc/6103/stat" },
|
||||||
|
) { logged.add(it) }
|
||||||
|
|
||||||
|
val line = logged.single()
|
||||||
|
assertTrue(
|
||||||
|
line.contains("adb -s emulator-5556 shell cat /proc/6103/stat"),
|
||||||
|
line,
|
||||||
|
)
|
||||||
|
assertTrue(line.contains("200"), line)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The bound must cost the healthy path nothing: output that arrives comes
|
||||||
|
// back whole, and the process is left to exit on its own.
|
||||||
|
@Test(timeout = 20_000L)
|
||||||
|
fun outputThatArrivesComesBackWholeAndTheProcessSurvives() {
|
||||||
|
val text = "VmRSS:\t 123456 kB\nVmSize:\t 654321 kB\n"
|
||||||
|
val process = FakeProcess(text.byteInputStream())
|
||||||
|
val logged = mutableListOf<String>()
|
||||||
|
|
||||||
|
val output = readProcessOutput(process, 5_000L, { "adb shell cat" }) {
|
||||||
|
logged.add(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(text, output)
|
||||||
|
assertTrue(!process.destroyed, "a process that answered is not killed")
|
||||||
|
assertTrue(logged.isEmpty(), "nothing to report on the healthy path")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output larger than a pipe buffer is why the read cannot be deferred
|
||||||
|
// until after the process exits: a process with more to say than the
|
||||||
|
// buffer holds blocks writing while a waiter waits for it to finish.
|
||||||
|
@Test(timeout = 20_000L)
|
||||||
|
fun outputLargerThanAPipeBufferComesBackWhole() {
|
||||||
|
val text = "x".repeat(512 * 1024)
|
||||||
|
val output = readProcessOutput(
|
||||||
|
FakeProcess(text.byteInputStream()),
|
||||||
|
5_000L,
|
||||||
|
{ "adb logcat -d" },
|
||||||
|
) {}
|
||||||
|
assertEquals(text.length, output.length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockingStream models a wedged adb: no bytes, and no EOF either, until the
|
||||||
|
// process is killed and the pipe closes under the reader.
|
||||||
|
private class BlockingStream : InputStream() {
|
||||||
|
private val released = CountDownLatch(1)
|
||||||
|
|
||||||
|
override fun read(): Int {
|
||||||
|
released.await()
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
released.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FakeProcess(private val stream: InputStream) : Process() {
|
||||||
|
@Volatile var destroyed = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
override fun getOutputStream(): OutputStream =
|
||||||
|
OutputStream.nullOutputStream()
|
||||||
|
|
||||||
|
override fun getInputStream(): InputStream = stream
|
||||||
|
|
||||||
|
override fun getErrorStream(): InputStream = InputStream.nullInputStream()
|
||||||
|
|
||||||
|
override fun waitFor(): Int = 0
|
||||||
|
|
||||||
|
override fun exitValue(): Int = 0
|
||||||
|
|
||||||
|
override fun destroy() {
|
||||||
|
destroyed = true
|
||||||
|
stream.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user