Unit testing of the SchemeStation cross-compiler and assembler

Unit testing of the SchemeStation cross-compiler and assembler
SchemeStation documentation

1 Introduction

The SCHEMESTATION cross-compiler and assembler are used to produce binary byte code objects to be run in the SCHEMESTATION operating system simulator.

This document describes the plans to unit test the compiler/assembler system.

2 Structure of the compiler

From here on, we see the compiler and the assembler as a conceptual unit that will be discussed together.

The compiler reads in source code (a dialect of Scheme) from a set of source files in a UNIX file system, and outputs one binary object file containing byte code instructions and constants that implement the compiled source code.

The overall structure of the compiler is presented in Fig 1.


Fig 1. The overall structure of the compiler

The compiler as a whole can be decomposed into the following subparts that can be tested separaterly:

  1. Source code parser
  2. Macro expander and semantic tree builder
  3. Semantic optimizer
  4. Linearizer
  5. Peephole optimizer
  6. Assembler

3 Testing plans

We now describe the testing plans for (i) the subparts 1-6 mentioned above and (ii) the compiler as a whole.

3.1 Source code parser

Testing objective: To ensure that the source code parser reads expression according to the R4RS document specifying a Scheme standard, with extensions from the SCHEMESTATION project.

Testing procedure: Test expressions are made by hand that are stored into a file. The expressions are read one by one by the parser. Then the same expressions are read in using the read procedure of the underlying Scheme that is used to implement the compiler, including the parser.

Checking results: A Scheme procedure is used to check that the two methods of reading have resulted in the same result in all test cases.

(Note. The procedure that is used to compare the two expressions is probably much like Scheme's equal?, but must take into account the different representations for the read expression by the two different methods.)

3.2 Macro expansion

Testing objective: To ensure that (1) the macro expander produces semantically sound expansions of known derived expression types and (2) that expansion hygienicy is maintained.

Testing procedure: Test expressions are made by hand that are stored into a file. The expressions are chosen so that variable shadowing happens much in different ways in order to find hygienicy bugs. The expressions are read and then expanded, resulting in a semantic tree. The semantic tree is then evaluated according to its semantic using a specially built evaluator. The result of this evaluation is stored. Then the same expression is evaluated in the underlying Scheme. This test is dependent on the correct workings of the parser, so parser should be tested first.

Checking results: The results of the two evaluations are compared. The test is succesful if the results are the same or otherwise acceptable together. (Note. The last weakening might be necessary due to the fact that the underlying Scheme possibly cannot evaluate all constructs implemented in the SCHEMESTATION project.)

3.3 Semantic optimizations

Testing objective: To ensure that the semantic optimizations applied to the semantic trees in the compiler preserve the semantics of the compiled expressions.

Testing procedure: As in "Macro expansion", but with optimizations performed.

Checking results: As in "Macro expansion".

3.4 Linearizer

Testing objective: To ensure that the linearizer transform semantic trees into byte code instruction sequences that implement the semantics of the trees.

Testing procedure: Test expressions are generated. Some of the expressions are made by hand and others generated automatically. The expressions are compiled and then run using the implemented SCHEMESTATION virtual machine. The resulting values are stored. The same expressions are then evaluated using an existing Scheme interpreter. Peephole optimization is disabled during this test. This test is dependent on the correct workings of the assembler, so assembler should be tested first.

Checking results: The two results of an expression are compared for equality, but see the note at the end of "Macro expansion".

3.5 Peephole optimizer

Testing objective: To ensure that the peephole optimizer does not make unsound optimizations.

Testing procedure: As in "Linearizer", but with peephole optimization turned on.

Checking results: As in "Linearizer".

3.6 Assembler

Testing objective: To ensure that the assembler creates byte code objects compliant with the SCHEMESTATION byte code object specifications.

Testing procedure: Test assembly files are assembled and then deassembled. Assembling is done using the assembler part of the compiler, and deassembling using an independently implemented deassembler.

Checking results: It is checked manually (or possibly using a script) that the deassembled code corresponds to the original assembly code.

3.7 Compiler as a whole

3.7.1 Compilation functionality

After the subparts have been tested, the whole compiler is tested by compiling and running programs with known assumed behaviour. The same programs are compiled using all possible combinations of, or a reasonable subset of, parameters affecting the compilation. The results from running the programs are compared with the known behaviour.

3.7.2 Error reporting and user interface

Programs with errors generated by hand are compiled and the resulting error messages are checked for consistency. The compiler is also tested by a user that has not implemented the compiler as a user-friendliness check. Random rubbish is sent to the compiler in order to check that it does not choke on completely unpredictable input.