返回 CodeWhale
PetLifecycleTest.kt
根目录 / pet / android / src / androidTest / kotlin / codewhale / pet / PetLifecycleTest.kt
1 package codewhale.pet
2
3 import androidx.compose.ui.test.junit4.createAndroidComposeRule
4 import androidx.compose.ui.test.onNodeWithText
5 import androidx.compose.ui.test.performClick
6 import androidx.lifecycle.Lifecycle
7 import androidx.lifecycle.ViewModelProvider
8 import android.net.Uri
9 import org.json.JSONObject
10 import org.junit.Assert.*
11 import org.junit.Rule
12 import org.junit.Test
13
14 class PetLifecycleTest {
15 @get:Rule val compose = createAndroidComposeRule<PetActivity>()
16
17 @Test fun pauseStillSoundAndBackgroundLifecycleUseTheVisibleWorld() {
18 val model = ViewModelProvider(compose.activity)[PetViewModel::class.java]
19 model.mode(PetMode.WILD) // Explicit isolated lifecycle fixture.
20 compose.waitUntil(15_000) { model.ui.value.scene != null }
21 assertFalse(model.ui.value.sound)
22 compose.runOnIdle { model.setStill(false); model.setPaused(false) }
23 compose.onNodeWithText("Pause").performClick()
24 compose.waitUntil(5_000) { model.ui.value.paused && !model.ui.value.running }
25 val paused = model.ui.value.scene!!
26 Thread.sleep(300)
27 assertEquals(paused.timeMs, model.ui.value.scene!!.timeMs, 0.0)
28 assertArrayEquals(paused.dots, model.ui.value.scene!!.dots, 0f)
29 compose.onNodeWithText("More").performClick()
30 compose.onNodeWithText("Reopen saved habitat").performClick()
31 compose.onNodeWithText("Reopen the saved habitat?").assertExists()
32 compose.onNodeWithText("Cancel").performClick()
33 assertEquals(paused.timeMs, model.ui.value.scene!!.timeMs, 0.0)
34 compose.onNodeWithText("Still off").performClick()
35 compose.onNodeWithText("Sound off").performClick()
36 compose.onNodeWithText("Resume").performClick()
37 compose.waitUntil(5_000) { model.ui.value.still && model.ui.value.scene!!.timeMs > paused.timeMs + 200 }
38 assertTrue(model.ui.value.sound)
39 Thread.sleep(100)
40 val still = model.ui.value.scene!!
41 Thread.sleep(200)
42 assertArrayEquals(still.dots, model.ui.value.scene!!.dots, 0f)
43 compose.activityRule.scenario.moveToState(Lifecycle.State.CREATED)
44 compose.waitUntil(5_000) { !model.ui.value.running && model.ui.value.savedAtMs == model.ui.value.scene!!.timeMs }
45 val hidden = model.ui.value.scene!!
46 Thread.sleep(300)
47 assertEquals(hidden.timeMs, model.ui.value.scene!!.timeMs, 0.0)
48 assertFalse(Thread.getAllStackTraces().keys.any { it.name == "codewhale-pet-audio" && it.isAlive })
49 assertEquals(hidden.timeMs, model.ui.value.savedAtMs!!, 0.0)
50 compose.activityRule.scenario.moveToState(Lifecycle.State.RESUMED)
51 compose.waitUntil(5_000) { model.ui.value.scene!!.timeMs > hidden.timeMs }
52 compose.onNodeWithText("Sound on").performClick()
53 compose.onNodeWithText("Still on").performClick()
54 assertFalse(model.ui.value.sound)
55 }
56
57 @Test fun localLiveFileStaysFreshAcrossPauseBackgroundAndProducerRestart() {
58 val model = ViewModelProvider(compose.activity)[PetViewModel::class.java]
59 model.mode(PetMode.WILD) // This fixture imports an isolated telemetry file.
60 compose.waitUntil(15_000) { model.ui.value.scene != null }
61 val resolver = compose.activity.contentResolver
62 val uri = Uri.parse("content://dev.shannonlabs.codewhale.pet.test.live/tape")
63 val human = compose.activity.assets.open("demo.jsonl").bufferedReader().use { input ->
64 input.lineSequence().map(::JSONObject).first { it.getString("channel") == "human" && it.getBoolean("waiting") }.toString()
65 }
66 fun packet(sequence: Int) = JSONObject(human).put("sequence", sequence).put("simTimeMs", sequence * 400).toString() + "\n"
67 fun action(name: String, text: String? = null) = resolver.call(uri, name, text, null)!!.getInt("reads")
68 fun observed() = model.ui.value.scene!!.state.observed >= .92
69 action("replace", packet(10))
70 val initialReads = action("stats")
71 compose.runOnIdle { model.setPaused(false); model.followLocalTape(uri) }
72 try {
73 compose.waitUntil(10_000) { model.ui.value.mode == PetMode.LIVE && action("stats") >= initialReads + 2 }
74 assertFalse("Existing file must establish a baseline", observed())
75 action("append", packet(11))
76 compose.waitUntil(5_000) { observed() && model.ui.value.scene!!.state.channel == "human" }
77 compose.onNodeWithText("Pause").performClick()
78 compose.waitUntil(5_000) { model.ui.value.paused && !model.ui.value.running }
79 val stoppedReads = action("stats")
80 action("append", packet(12)); Thread.sleep(600)
81 assertEquals("Pause closes live polling", stoppedReads, action("stats"))
82 compose.onNodeWithText("Resume").performClick()
83 compose.waitUntil(5_000) { model.ui.value.running && action("stats") >= stoppedReads + 2 }
84 assertFalse("Background bytes are not fresh requests", observed())
85 assertEquals("none", model.ui.value.scene!!.needs)
86 action("append", packet(13))
87 compose.waitUntil(5_000) { observed() }
88 compose.waitUntil(5_000) { !observed() }
89 val beforeRestart = action("stats")
90 action("replace", packet(0))
91 compose.waitUntil(5_000) { action("stats") >= beforeRestart + 2 }
92 assertFalse(observed())
93 action("append", packet(1))
94 compose.waitUntil(5_000) { observed() }
95 compose.activityRule.scenario.moveToState(Lifecycle.State.CREATED)
96 compose.waitUntil(5_000) { !model.ui.value.running }
97 val backgroundReads = action("stats")
98 action("append", packet(2)); Thread.sleep(600)
99 assertEquals(backgroundReads, action("stats"))
100 compose.activityRule.scenario.moveToState(Lifecycle.State.RESUMED)
101 compose.waitUntil(5_000) { model.ui.value.running && action("stats") >= backgroundReads + 2 }
102 assertFalse(observed())
103 val beforeError = model.ui.value.scene!!.timeMs
104 action("replace", "{invalid}\n")
105 compose.waitUntil(5_000) { model.ui.value.message?.startsWith("Invalid local telemetry") == true }
106 compose.waitUntil(5_000) { model.ui.value.scene!!.timeMs > beforeError + 800 }
107 assertFalse(model.ui.value.paused)
108 assertFalse(observed())
109 } finally {
110 compose.activityRule.scenario.moveToState(Lifecycle.State.RESUMED)
111 compose.runOnIdle { model.mode(PetMode.WILD) }
112 compose.waitUntil(5_000) { model.ui.value.mode == PetMode.WILD }
113 // Settle this activity's final frame/save before the next test opens
114 // the same private habitat with a new ViewModel and store revision.
115 compose.runOnIdle { model.setPaused(true) }
116 compose.waitUntil(5_000) { model.ui.value.paused && !model.ui.value.running && model.ui.value.savedAtMs == model.ui.value.scene!!.timeMs }
117 action("delete")
118 }
119 }
120 }
121
121 lines Plain Text