acceptance, more debugging and head scratching

In my last few posts in my robocode series I've developed a bounded context for decision making and it is ready to try out. I have some acceptance tests written that ensure I meet some conditions and the definitive one to check things are working is the test that ensures I reach turn 50, thus asserting that the robot is not disabled at the start of the battle.

When I run the acceptance test it fails which indicates there is something wrong and the robot is being disabled. The error from the acceptance test is actually that the statistics file is old (and not because it doesn't reach turn 50).
This means that the robot's Run() method hasn't been called correctly and the robot engine is not initialised.

Sure enough, when I manually run the robocode GUI and watch my robot it is immediately disabled at the start of the battle.
So what could be going wrong?
After much head scratching and rechecking the code and the specifications I can't see anything wrong - the execution paths look like they should be doing what I've specified so I need to do some debugging.

I insert some debugging statements into the code of the Slayer Robot as the first line of the following two methods:

        public void Run(bool shouldContinueToLoop)
        {
            DebugProperty["run is called"] = "true";

            robotEngine = ServiceLocator.ProvideRobotEngine();
            robotEngine.Initialise(this);

            Scan();

            bool shouldLoop = true;

            while(shouldLoop)
            {
                robotEngine.EnsureTheWorldStateIsUpToDate();

                shouldLoop = shouldContinueToLoop;
            }
        }

        public override void OnStatus(StatusEvent e)
        {
            DebugProperty["on status called"] = "true";

            robotEngine.PublishToTheWorldState(w =>
               {
                   w.CurrentTurnNumber = e.Status.Time;
                   w.GunTemperature = e.Status.GunHeat;
               });
        }

Now when I observe a battle through the GUI and bring up the turn snapshot from the battle log I observe that on the very first turn, turn 0, the following debug properties are available:

debugStatus

It appears that the status method is being called, but the run is not and would suggest that there is a sequencing issue with my code.

I write a specification for this potential bug:

    public class when_robocode_updates_the_turn_status_on_a_robot_and_run_has_not_occurred
    {
        Establish context = () =>
                                    {
                                        robotStatus = RobocodeSpy.CreateARobotStatus(0, 0);
                                        statusEvent = new StatusEvent(robotStatus);

                                        robot = new SlayerRobot();
                                    };

        Because of = () => caughtException = Catch.Exception(() => robot.OnStatus(statusEvent));

        It should_not_fail_because_the_robot_engine_is_not_available = 
                                               () => caughtException.ShouldBeNull();

        static SlayerRobot robot;
        static RobotStatus robotStatus;
        static StatusEvent statusEvent;
        static Exception caughtException;
    }

When I run this specification it fails because a null reference exception is returned due to the missing robot engine instance.
I can fix this quickly with the following code:

        public override void OnStatus(StatusEvent e)
        {
            if (robotEngine == null)
                return;

            robotEngine.PublishToTheWorldState(w =>
               {
                   w.CurrentTurnNumber = e.Status.Time;
                   w.GunTemperature = e.Status.GunHeat;
               });
        }

Although this fixes this particular issue, if I want to keep this same code structure I've been developing then I have to accept that I will never get a status update on turn 0, the first turn, right at the start of the battle - I decide that this is acceptable, at least at this very early stage of development.

When I run the acceptance test once again, it now passes:

passingAcceptance

The important takeaway from this lesson in head scratching is what I can learn from this error. I had a huge assumption that robocode would call my Run() method before the status would update for turn zero. On reflection it makes sense that before commands are accepted from a robot, the status at the beginning of the battle is published, so I'm not really sure where that came from.
I also think that if this was a professional codebase at a client and this was, for example, and email class deriving from a template patterned base class, I would have coded for every eventuality of calls being made out of sequence.
So lesson learned - no assumptions in my personal projects and cover every possibility with specifications, just like I normally would at work.



0 comments: