Files
goon-game/app/src/androidTest/java/com/projectsacrifice/game/GameLifecycleTest.java
T

119 lines
5.8 KiB
Java

package com.projectsacrifice.game;
import android.app.Instrumentation;
import androidx.test.core.app.ActivityScenario;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/** Device-level contract for leaving and returning to an active puzzle. */
@RunWith(AndroidJUnit4.class)
public final class GameLifecycleTest {
@Test
public void returningFromBackgroundKeepsPuzzleVisiblePausedAndClearsHeldInput() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
try (ActivityScenario<MainActivity> scenario = ActivityScenario.launch(MainActivity.class)) {
final MainActivity[] activity = new MainActivity[1];
final GameView[] view = new GameView[1];
scenario.onActivity(current -> {
activity[0] = current;
view[0] = current.gameViewForAutomation();
});
createRetainedCorpse(instrumentation, view[0]);
runOnGame(instrumentation, view[0], false, false, true, false);
instrumentation.runOnMainSync(activity[0]::onPause);
GameView.AutomationSnapshot backgroundStart = snapshot(instrumentation, view[0]);
sleep(700L);
GameView.AutomationSnapshot backgroundEnd = snapshot(instrumentation, view[0]);
assertEquals(5, backgroundEnd.levelNumber());
assertEquals(backgroundStart.deaths(), backgroundEnd.deaths());
assertEquals(backgroundStart.corpseCount(), backgroundEnd.corpseCount());
assertEquals(backgroundStart.playerX(), backgroundEnd.playerX(), 0.01f);
assertEquals(backgroundStart.elapsedMilliseconds(), backgroundEnd.elapsedMilliseconds());
instrumentation.runOnMainSync(activity[0]::onResume);
GameView.AutomationSnapshot returned = snapshot(instrumentation, view[0]);
assertTrue("Returning to an active puzzle must show the pause screen", returned.visiblyPaused());
assertFalse("Held touch input must not survive backgrounding", returned.inputActive());
assertEquals(backgroundEnd.deaths(), returned.deaths());
assertEquals(backgroundEnd.corpseCount(), returned.corpseCount());
scenario.onActivity(MainActivity::onBackPressed);
sleep(80L);
GameView.AutomationSnapshot resumed = snapshot(instrumentation, view[0]);
assertFalse("Back from the visible pause screen must resume the puzzle", resumed.visiblyPaused());
runOnGame(instrumentation, view[0], false, false, true, false);
GameView.AutomationSnapshot moving = waitForMovement(
instrumentation, view[0], resumed.playerX(), 10_000L);
runOnGame(instrumentation, view[0], false, false, false, false);
assertTrue("The resumed simulation must accept movement", moving.playerX() > resumed.playerX() + 20f);
}
}
private static GameView.AutomationSnapshot waitForMovement(
Instrumentation instrumentation, GameView gameView, float startX, long timeoutMillis) {
long deadline = System.currentTimeMillis() + timeoutMillis;
GameView.AutomationSnapshot current = snapshot(instrumentation, gameView);
while (System.currentTimeMillis() < deadline) {
if (current.playerX() > startX + 20f) return current;
sleep(24L);
current = snapshot(instrumentation, gameView);
}
throw new AssertionError(
"The resumed simulation did not move within " + timeoutMillis
+ "ms; startX=" + startX
+ ", finalX=" + current.playerX()
+ ", alive=" + current.playerAlive()
+ ", paused=" + current.visiblyPaused()
+ ", inputActive=" + current.inputActive());
}
private static void createRetainedCorpse(Instrumentation instrumentation, GameView gameView) {
runOnGame(instrumentation, gameView, true, false, true, false);
long deadline = System.currentTimeMillis() + 8_000L;
boolean died = false;
while (System.currentTimeMillis() < deadline) {
GameView.AutomationSnapshot value = snapshot(instrumentation, gameView);
if (!value.playerAlive()) died = true;
if (died && value.playerAlive() && value.corpseCount() >= 1) {
runOnGame(instrumentation, gameView, false, false, false, false);
return;
}
sleep(24L);
}
throw new AssertionError("Level 5 did not produce a retained corpse before lifecycle check");
}
private static void runOnGame(Instrumentation instrumentation, GameView gameView,
boolean startLevelFive, boolean left, boolean right, boolean jump) {
instrumentation.runOnMainSync(() -> {
if (startLevelFive) gameView.startLevelForAutomation(5);
gameView.setAutomationInput(left, right, jump);
});
}
private static GameView.AutomationSnapshot snapshot(Instrumentation instrumentation, GameView gameView) {
final GameView.AutomationSnapshot[] value = new GameView.AutomationSnapshot[1];
instrumentation.runOnMainSync(() -> value[0] = gameView.automationSnapshot());
return value[0];
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
throw new AssertionError("Lifecycle test interrupted", interrupted);
}
}
}