package rabbitescape.engine.solution; import static org.junit.Assert.fail; import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.CoreMatchers.*; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import org.junit.Test; import rabbitescape.engine.Token; import rabbitescape.engine.World; import rabbitescape.engine.World.CompletionState; import rabbitescape.engine.solution.SolutionExceptions; import rabbitescape.engine.textworld.TextWorldManip; public class TestSolutionRunner { @Test( expected = SolutionExceptions.UnexpectedState.class ) public void Unexpected_state_is_an_error() { SolutionRunner.runSolution( expectingSolution( CompletionState.LOST ), neverEndingWorld() ); } @Test public void Unexpected_state_is_serialised_to_helpful_message() { try { SolutionRunner.runSolution( expectingSolution( CompletionState.LOST ), neverEndingWorld() ); fail( "Expected exception!" ); } catch( SolutionExceptions.UnexpectedState e ) { e.solutionId = 3; e.level = "baz"; e.world = "z"; assertThat( e.getMessage(), equalTo( "Solution failed: state was RUNNING but we expected LOST" + " at command 1 of solution 3 in baz:\nz." + "\nTo see: ./runrabbit swing -l baz -s3" ) ); } } @Test( expected = SolutionExceptions.DidNotWin.class ) public void Failing_unexpectedly_is_an_error() { SolutionRunner.runSolution( expectingSolution( CompletionState.WON ), neverEndingWorld() ); } @Test public void Failing_unexpectedly_is_serialised_to_helpful_message() { try { SolutionRunner.runSolution( expectingSolution( CompletionState.WON ), neverEndingWorld() ); fail( "Expected exception!" ); } catch( SolutionExceptions.UnexpectedState e ) { e.solutionId = 4; e.level = "X"; e.world = ""; assertThat( e.getMessage(), equalTo( "Solution failed: We expected to win, but the state was" + " RUNNING at command 1 of solution 4 in X:\n." + "\nTo see: ./runrabbit swing -l X -s4" ) ); } } @Test( expected = SolutionExceptions.RanPastEnd.class ) public void Going_on_beyond_the_end_is_an_error() { SolutionRunner.runSolution( waitFourSolution(), threeStepWorld() ); } @Test public void Going_on_beyond_the_end_is_serialised_to_helpful_message() { try { SolutionRunner.runSolution( waitFourSolution(), threeStepWorld() ); fail( "Expected exception!" ); } catch( SolutionExceptions.RanPastEnd e ) { e.solutionId = 5; e.world = ""; assertThat( e.getMessage(), equalTo( "Solution failed: world has stopped (state: WON) but" + " there are more solution commands" + " at command 3 of solution 5 in <>:\n." + "\nTo see: ./runrabbit swing -l <> -s5" ) ); } } @Test( expected = SolutionExceptions.UsedRunOutAbility.class ) public void Using_missing_ability_is_an_error() { SolutionRunner.runSolution( useBash30Solution(), neverEndingWorldWithBash() ); } @Test public void Using_missing_ability_is_serialised_to_helpful_message() { try { SolutionRunner.runSolution( useBash30Solution(), neverEndingWorldWithBash() ); fail( "Expected exception!" ); } catch( SolutionExceptions.UsedRunOutAbility e ) { e.solutionId = 6; e.level = "foo"; e.world = ""; assertThat( e.getMessage(), equalTo( "Solution failed: ability 'bash' was used when there" + " were none left at command 4 of solution 6 in foo:\n." + "\nTo see: ./runrabbit swing -l foo -s6" ) ); } } @Test( expected = SolutionExceptions.UsedMissingAbility.class ) public void Using_run_out_ability_is_an_error() { SolutionRunner.runSolution( useBash30Solution(), neverEndingWorld() ); } @Test public void Using_run_out_ability_is_serialised_to_helpful_message() { try { SolutionRunner.runSolution( useBash30Solution(), neverEndingWorld() ); fail( "Expected exception!" ); } catch( SolutionExceptions.UsedMissingAbility e ) { e.solutionId = 7; e.level = "foo"; e.world = ""; assertThat( e.getMessage(), equalTo( "Solution failed: ability 'bash' was used but this level" + " does not provide it at command 2 of solution 7 in" + " foo:\n." + "\nTo see: ./runrabbit swing -l foo -s7" ) ); } } @Test( expected = SolutionExceptions.PlacedTokenOutsideWorld.class ) public void Placing_a_token_outside_the_world_is_an_error() { SolutionRunner.runSolution( useBash100Solution(), neverEndingWorldWithBash() ); } @Test public void Placing_a_token_outside_the_world_is_serialised_nicely() { try { SolutionRunner.runSolution( useBash100Solution(), neverEndingWorldWithBash() ); fail( "Expected exception!" ); } catch( SolutionExceptions.PlacedTokenOutsideWorld e ) { e.solutionId = 8; e.level = "bar"; e.world = ""; assertThat( e.getMessage(), equalTo( "Solution failed: placed a token at (10, 0) but the" + " world is only 5x2 in size" + " at command 2 of solution 8 in bar:\n." + "\nTo see: ./runrabbit swing -l bar -s8" ) ); } } @Test public void Rabbit_dying_by_walking_out_of_level_is_not_an_error() { SolutionRunner.runSolution( waitFiveThenLostSolution(), TextWorldManip.createWorld( "#r ", "#####", ":num_rabbits=0", ":num_to_save=1" ) ); } @Test( expected = SolutionExceptions.FailedToPlaceToken.class ) public void Placing_a_token_on_a_block_is_an_error() { SolutionRunner.runSolution( useBash30Solution(), blockAt30World() ); } @Test public void Placing_a_token_on_a_block_is_serialised_nicely() { try { SolutionRunner.runSolution( useBash30Solution(), blockAt30World() ); fail( "Expected exception!" ); } catch( SolutionExceptions.FailedToPlaceToken e ) { e.solutionId = 9; e.level = "bar"; e.world = "y\nx"; assertThat( e.getMessage(), equalTo( "Solution failed: tried to place a bash token at (3, 0) but" + " a block was already there so it did not place" + " at command 4 of solution 9 in bar:\ny\nx." + "\nTo see: ./runrabbit swing -l bar -s9" ) ); } } @Test public void Until_never_ending_is_serialised_nicely() { try { SolutionRunner.runSolution( new Solution( new SolutionCommand( new UntilAction( CompletionState.WON ) ) ), neverEndingWorld() ); fail( "Expected exception!" ); } catch( SolutionExceptions.UntilActionNeverEnded e ) { e.solutionId = 10; e.level = "qux"; e.world = "x\ny"; assertThat( e.getMessage(), equalTo( "Solution failed: the level never finished, but" + " there was an until:WON" + " action at command 1 of solution 10 in qux:\nx\ny." + "\nTo see: ./runrabbit swing -l qux -s10" ) ); } } @Test public void Real_level_with_WON_at_end_works() { World world = TextWorldManip.createWorld( ":num_rabbits=1", ":num_to_save=1", "Q ", " O", "#####" ); boolean solved = SolutionRunner.runSolution( SolutionParser.parse( "6;WON" ), world ); assertThat( solved, is( true ) ); } @Test public void Real_level_with_no_assert_but_we_won_works() { World world = TextWorldManip.createWorld( ":num_rabbits=1", ":num_to_save=1", "Q ", " O", "#####" ); boolean solved = SolutionRunner.runSolution( SolutionParser.parse( "6" ), world ); assertThat( solved, is( true ) ); } @Test public void Real_level_with_LOST_at_end_works() { World world = TextWorldManip.createWorld( ":num_rabbits=1", ":num_to_save=1", "Q ", " ", "#####" ); boolean solved = SolutionRunner.runSolution( SolutionParser.parse( "7;LOST" ), world ); assertThat( solved, is( false ) ); } @Test public void Real_level_with_no_assert_and_still_running_did_not_solve() { World world = TextWorldManip.createWorld( ":num_rabbits=1", ":num_to_save=1", "Q ", " ", "#####" ); boolean solved = SolutionRunner.runSolution( SolutionParser.parse( "2;RUNNING" ), world ); assertThat( solved, is( false ) ); } @Test public void Real_level_with_until_WON_works() { World world = TextWorldManip.createWorld( ":num_rabbits=1", ":num_to_save=1", "Q ", " O", "#####" ); boolean solved = SolutionRunner.runSolution( SolutionParser.parse( "until:WON" ), world ); assertThat( solved, is( true ) ); } @Test public void Real_level_with_until_LOST_works() { World world = TextWorldManip.createWorld( ":num_rabbits=1", ":num_to_save=1", "Q ", " ", "#####" ); boolean solved = SolutionRunner.runSolution( SolutionParser.parse( "until:LOST" ), world ); assertThat( solved, is( false ) ); } @Test public void Print_step() throws UnsupportedEncodingException // for the UTF8 { World world = TextWorldManip.createWorld( ":num_rabbits=0", ":num_to_save=1", ":solution.1=until:WON", " d j ", "#####", "#O###", "#####" ); Solution solution = SolutionParser.parse( world.solutions[0] ); ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream( baos ); SolutionRunner.runSolution( solution, world, ps, false ); String out = baos.toString("UTF8"); String exp = "Waiting:0" + "\n" + " Saved:0" + "\n" + "00 d j " + "\n" + "01 #####" + "\n" + "02 #O###" + "\n" + "03 #####" + "\n" + " 00000" + "\n" + " 01234" + "\n" + "Waiting:0" + "\n" + " Saved:0" + "\n" + "00 dj " + "\n" + "01 #####" + "\n" + "02 #O###" + "\n" + "03 #####" + "\n" + " 00000" + "\n" + " 01234" + "\n" + "Waiting:0" + "\n" + " Saved:0" + "\n" + "00 j " + "\n" + "01 #####" + "\n" + "02 #O###" + "\n" + "03 #####" + "\n" + " 00000" + "\n" + " 01234" + "\n" + "Waiting:0" + "\n" + " Saved:0" + "\n" + "00 " + "\n" + "01 #j###" + "\n" + "02 #O###" + "\n" + "03 #####" + "\n" + " 00000" + "\n" + " 01234" + "\n" + "Waiting:0" + "\n" + " Saved:0" + "\n" + "00 " + "\n" + "01 #j###" + "\n" + "02 #O###" + "\n" + "03 #####" + "\n" + " 00000" + "\n" + " 01234" + "\n" + "Waiting:0" + "\n" + " Saved:0" + "\n" + "00 " + "\n" + "01 # ###" + "\n" + "02 #O###" + "\n" + "03 #####" + "\n" + " 00000" + "\n" + " 01234" + "\n" + "Waiting:0" + "\n" + " Saved:1" + "\n" + "00 " + "\n" + "01 # ###" + "\n" + "02 #O###" + "\n" + "03 #####" + "\n" + " 00000" + "\n" + " 01234" + "\n" + "Waiting:0" + "\n" + " Saved:1" + "\n" + "00 " + "\n" + "01 # ###" + "\n" + "02 #O###" + "\n" + "03 #####" + "\n" + " 00000" + "\n" + " 01234" + "\n"; assertThat( out, equalTo( exp ) ); } // -- private World neverEndingWorld() { return TextWorldManip.createWorld( "#r #", "#####" ); } private World neverEndingWorldWithBash() { return TextWorldManip.createWorld( "#r #", "#####", ":bash=1" ); } private World threeStepWorld() { return TextWorldManip.createWorld( "#r O", "####", ":num_rabbits=0", ":num_to_save=1" ); } private World blockAt30World() { return TextWorldManip.createWorld( "#r ##", "#####", ":bash=2" ); } private Solution expectingSolution( CompletionState expected ) { return new Solution( new SolutionCommand( new AssertStateAction( expected ) ) ); } private Solution waitFourSolution() { return new Solution( new SolutionCommand( new WaitAction( 1 ) ), new SolutionCommand( new WaitAction( 2 ) ), new SolutionCommand( new WaitAction( 2 ) ) ); } private Solution waitFiveThenLostSolution() { return new Solution( new SolutionCommand( new WaitAction( 5 ) ), new SolutionCommand( new AssertStateAction( CompletionState.LOST ) ) ); } private Solution useBash30Solution() { return new Solution( new SolutionCommand( new SelectAction( Token.Type.bash ) ), new SolutionCommand( new PlaceTokenAction( 1, 0 ) ), new SolutionCommand( new WaitAction( 1 ) ), new SolutionCommand( new PlaceTokenAction( 3, 0 ) ) ); } private Solution useBash100Solution() { return new Solution( new SolutionCommand( new SelectAction( Token.Type.bash ) ), new SolutionCommand( new PlaceTokenAction( 10, 0 ) ) ); } }