Evolution of a personal project — Part 2 — Solving untestable code
Progress has been less than I would have hoped for. I decided to unit test the code but have been having a bit of a problem with my assistant!
The assembler is not efficient or fast, efficiency and speed are useless if the result is wrong so basic plan is always get it working and only then improve speed and efficiency if required or desired.
The assembler works by first tokenising the source, basically doing syntax checks and creating a list of tokens for each line in the source.
The tokens are then parsed resolving forward references and determining actual addressing mode hence instruction size and label reference values.
The unit testing for is in 3 files using scalatest AnyFlatSpec.
AssembleSpec.scala It performs a test using the LDA instruction for each of the addressing modes plus each of the assembler commands BYT, WRD and ADDR verifying that the token generated is correct. Currently low coverage but did serve to find 5 errors with the tokenisation particularly around effective address prediction which would have affected most tokenisation processes.
InstructionSpec.scala test each token results in the correct data written to memory, one token for each addressing mode of each instruction — tedious does not cover half of it! But the number of errors found justifies the effort.
ExecutionSpec.scala test the execution unit. This is the most difficult and complex set of tests. The execution unit runs in single step mode, slow run and run modes and after each execution updates the GUI showing the updated register contents and the disassembly of the next instruction to execute. The big problem here was that execution is triggered from the UI and updates the UI which means the UI is being updated in the event handler of the single step button which JavaFX does not like! Solution is to have the update part of the execution happen outside the framework thread using Platform.runLater. However unit test don’t run in the UI so there is no Platform to runLater, an example of code that in untestable. So we have to change the code.
The execution unit uses a disassembler which uses the bit pattern of the byte (on 6502 all instructions a re single byte) returning a case class representing the instruction.
Pattern matching on the case class was used to select the method to perform the actions required for the instruction, it was within that method that the UI was updated using the Platform.runLater.
The changes made were to return from the pater matcher an anonymous function that call the instruction execution method. Modifying the execution unit method to call the returned function from the matcher within a Platform.runLater enables the unit test to Call the match and execute the returned method directly with no invocation of Platform.runLater solving the problem.