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.assertTrue; /** Production-input acceptance coverage for the intended routes through levels 1-4. */ @RunWith(AndroidJUnit4.class) public final class EarlyLevelPlaythroughTest { @Test public void levelOneProvesMovementAndJumpWithoutDeaths() { for (int pass = 1; pass <= 3; pass++) { LevelPlaytestBot.Result result = run(EarlyLevelScenarios.levelOne(), 20_000L); assertTrue(passDiagnostic(pass, result), result.completed()); assertEquals("Pass " + pass + ": level 1 must not require a volunteer", 0, result.deaths()); } } @Test public void levelTwoBuildsTheIntendedThreeCorpseBridge() { for (int pass = 1; pass <= 3; pass++) { LevelPlaytestBot.Result result = run(EarlyLevelScenarios.levelTwo(), 30_000L); assertTrue(passDiagnostic(pass, result), result.completed()); assertEquals("Pass " + pass + ": the intended bridge uses three volunteers", 3, result.deaths()); assertEquals("Pass " + pass + ": all three bridge bodies must remain", 3, result.corpseCount()); } } @Test public void levelThreeLeavesASawCorpseOnThePlateAndOpensTheDoor() { for (int pass = 1; pass <= 3; pass++) { LevelPlaytestBot.Result result = run(EarlyLevelScenarios.levelThree(), 25_000L); assertTrue(passDiagnostic(pass, result), result.completed()); assertEquals("Pass " + pass + ": the saw solution requires one volunteer", 1, result.deaths()); assertEquals("Pass " + pass + ": the plate body must remain", 1, result.corpseCount()); assertTrue("Pass " + pass + ": the corpse must keep the pressure door open", result.doorOpened()); } } @Test public void levelFourTogglesAndRidesTheLiftToTheUpperGallery() { for (int pass = 1; pass <= 3; pass++) { LevelPlaytestBot.Result result = run(EarlyLevelScenarios.levelFour(), 30_000L); assertTrue(passDiagnostic(pass, result), result.completed()); assertEquals("Pass " + pass + ": the lift route must not require a volunteer", 0, result.deaths()); } } private static String passDiagnostic(int pass, LevelPlaytestBot.Result result) { return "Consecutive pass " + pass + ": " + result.diagnostic(); } private static LevelPlaytestBot.Result run(LevelPlaytestBot.Scenario scenario, long timeoutMillis) { try (ActivityScenario activity = ActivityScenario.launch(MainActivity.class)) { final GameView[] view = new GameView[1]; activity.onActivity(mainActivity -> view[0] = mainActivity.gameViewForAutomation()); return new LevelPlaytestBot().run( new AndroidDriver(InstrumentationRegistry.getInstrumentation(), view[0]), scenario, timeoutMillis ); } } private static final class AndroidDriver implements LevelPlaytestBot.Driver { private final Instrumentation instrumentation; private final GameView gameView; AndroidDriver(Instrumentation instrumentation, GameView gameView) { this.instrumentation = instrumentation; this.gameView = gameView; } @Override public void startLevel(int oneBasedLevelNumber) { instrumentation.runOnMainSync(() -> gameView.startLevelForAutomation(oneBasedLevelNumber)); } @Override public void setInput(boolean moveLeft, boolean moveRight, boolean jump) { instrumentation.runOnMainSync(() -> gameView.setAutomationInput(moveLeft, moveRight, jump)); } @Override public GameView.AutomationSnapshot snapshot() { final GameView.AutomationSnapshot[] value = new GameView.AutomationSnapshot[1]; instrumentation.runOnMainSync(() -> value[0] = gameView.automationSnapshot()); return value[0]; } @Override public void waitForNextDecision(long millis) { try { Thread.sleep(millis); } catch (InterruptedException interrupted) { Thread.currentThread().interrupt(); throw new AssertionError("Early-level bot interrupted", interrupted); } } } }