continuing the battle classes for robocode

In a previous post I started creating the framework for running acceptance tests through robocode and I had created my definition of a battle in a Battle class and had defined a BattleRunner that would set up the start information required for running the robocode process.

I'll now continue with implementing the concrete process runner but as this is a very lightweight class that simply starts a process I'm not going to write a specification for it directly. Instead I'm going to write an integration test that will allow me to see if it does indeed start the process as I intend:

    public class when_integrating_the_process_runner_with_the_battle_runner
    {
        Establish context = () =>
        {
            battleName = "llk0i908";
            battle = new Battle(battleName);

            battleRunner = new BattleRunner(new ProcessRunner());
        };

        It should_run_the_process = () => battleRunner.Run(battle);

        static Battle battle;
        static string battleName;
        static BattleRunner battleRunner;
    }

I'm setting up a new battle runner with the concrete ProcessRunner and in my "It" delegate am running the battle - ordinarily this would be in the "Because" delegate, but we need an "It" delegate for MSpec to pick the test up so I moved the code that exercises the process.  This test is merely a visual aid to see if the process is executed through a shell window - at present it is not, so I code the ProcessRunner:

    public class ProcessRunner : IProcessRunner
    {
        public void RunProcess(ProcessStartInfo processStartInfo)
        {
            var process = Process.Start(processStartInfo);
        }
    }

Now when I run this test, the command window fires up:

integrating_the_process_runner 

This is working exactly as intended - it locates the run_battle.bat file and the arguments are injected into the java call for the battle file and the battle results file locations.
It then runs robocode, but our test battle file is not found and this is totally expected (and I don't really want to run one at this stage) - I now have confidence that this integration between BattleRunner and ProcessRunner works they way I intended.

The next step is to look at parsing the results and returning them when we run a battle - I therefore need a new BattleResult class and I will return a collection of them when Run(battle) is called. Each item in the collection will represent a result for a particular robot showing their place in the battle and their score.
So I change the specifications for BattleRunner (and only new/changed code is shown below for brevity)

    public class when_the_battle_runner_runs_a_battle
    {
        ...

        Because of = () => results = battleRunner.Run(battle);

        It should_return_battle_results = () => results.ShouldNotBeNull();

        static BattleRunner battleRunner;
        static IProcessRunner processRunner;
        static Battle battle;
        static ProcessStartInfo processStartInformation;
        static IEnumerable<BattleResult> results;
    }

And I can implement this as follows for the time being:

    public class BattleRunner
    {
        readonly IProcessRunner processRunner;

        public BattleRunner(IProcessRunner processRunner)
        {
            this.processRunner = processRunner;
        }

        public IEnumerable<BattleResult> Run(Battle battle)
        {
            var startInformation = new ProcessStartInfo();
            startInformation.FileName = BattleConstants.BattleFilesLocation + "run_battle.bat";
            startInformation.WorkingDirectory = BattleConstants.RobocodeWorkingDirectory;
            startInformation.Arguments = string.Format("-battle {0} -results {1}",
                                                       battle.BattleFile,
                                                       battle.BattleResultFile);

            processRunner.RunProcess(startInformation);

            return new List<BattleResult>();
        }
    }

I'm nearly ready to move onto specifying exactly how to read the results, but I've noticed something when running the manual integration test - the test is over well before the command from the .bat file finishes - therefore my results will not be available to assert against in any acceptance test as the robocode process will still be running.
I need to put a wait mechanism in and again, only changes to the specification are shown below:

    public class when_the_battle_runner_runs_a_battle
    {
        Establish context = () =>
        {
            ...
            processRunner.Stub(p => p.RunProcess(null, 0))
                         .IgnoreArguments().Do(RecordProcessStartInfoUsedInCall);
            ...
        };


        static Action<ProcessStartInfo, int> RecordProcessStartInfoUsedInCall = (s, i) => 
            processStartInformation = s;

        Because of = () => results = battleRunner.Run(battle);

        It should_tell_the_process_runner_to_run_a_new_battle_process = () =>
            processRunner.AssertWasCalled(p => p.RunProcess(Arg<ProcessStartInfo>.Is.NotNull, 
                                                            Arg<int>.Is.GreaterThan(0)));
    }

I've added an integer to the signature of the RunProcess() method, so I can now add my implementation:

    public class BattleRunner
    {
        public IEnumerable<BattleResult> Run(Battle battle)
        {
            ...
            processRunner.RunProcess(startInformation, 60);

            return new List<BattleResult>();
        }
    }

    public class ProcessRunner : IProcessRunner
    {
        public void RunProcess(ProcessStartInfo processStartInfo, int numberOfSecondsToWait)
        {
            var process = Process.Start(processStartInfo);
            process.WaitForExit(numberOfSecondsToWait * 1000);
        }
    }

Now when I run the integration test, I can see that the test does wait for the process to end and my results should be available to be read and passed back to my acceptance tests. I also verify that it took only 6.83 seconds and not the full 60 seconds wait period the BattleRunner passes through.

My next robocode post will delve into parsing the results file produced by robocode.



3 comments:

  1. Pavel Šavara July 19, 2010 at 9:50 PM

    Hi, just quickly skimmed thru the article. It seems you are .NET building wrapper around robocode.control namespace. I guess that could be done more elegant way using jni4net. Just generate proxies and setup the bridge on .NET side. I think that could be made part of Robocode .NET distribution then. It would be much faster on runtime, because it will be one process all the time. Please let me know what you think.

     
  2. Jason Hyland July 20, 2010 at 11:14 AM

    Pavel,

    As discussed I'm going to try and bridge the robocode.control.robocodeengine class using jni4net. Other than the samples provided is there any docs for jni4net?

    Regards, Jason

     
  3. Pavel Šavara July 20, 2010 at 9:46 PM

    http://groups.google.com/group/robocode-developers/browse_thread/thread/e6654487cf2f3c5?hl=en