You need test programs in order to check whether your compiler emits code that actually works. Writing test-cases is quite a skill, and it is very difficult to get good test coverage. As much as possible you should develop independent test-cases that look at individual features you are working on. Also, once a test-case is successfully passed, no future features should stop that test-case passing. The situation where the addition of something new stops something old working is generally called a regression, and regression testing is used to make sure that functionality doesn't go backwards.
In order to do regression testing, you need two things:
-
a set of test-cases, and
-
an automated system for running the test-cases.
Your task is to develop ten test cases looking for different functionality. There is no requirement that your compiler implements these features, only that your test-cases try to test them.
Each test case has a name ${NAME}, and consists of two files:
-
${NAME}.c
: The source file to be compiled by the compiler-under-test. This should be very minimal, and use the smallest amount of code needed to perform the test. -
${NAME}_driver.c
: A driver file to be compiled by GCC, which knows how to invoke the functionality in the tested file. This can contain any C code at all, as it will be compiled by GCC.
The testing process for a test-case is then:
-
Compile
${NAME}.c
using the compiler-under-test into MIPS assembly. -
Compile
${NAME}_driver.c
using GCC into a MIPS object file. -
Link the generated assembly and the driver object into a MIPS executable.
-
Run the executable under QEMU.
-
If the executable returns 0 (via
$?
in the shell), then the test-case has passed.
If any of these steps fail, then either the test-case is malformed, or the compiler-under-test is not generating correct code.
The requirements for the deliverable are to create ten tests:
-
IF_T
: Can the compiled code correctly execute a branch of an if. -
IF_F
: Can the compiled code correctly not execute a branch of an if. -
IF_ELSE_T
: Test that an if else condition correctly executes the true branch. -
IF_ELSE_F
: Test that an if else condition correctly executes the false branch. -
FOR_N
: Check that a for-loop can execute more than one iteration. -
LOCAL_ARRAY
: Check that an array can be allocated locally and used within a function. -
SELF_RECURSION
: A minimal test that a single function can call itself. -
GLOBAL
: Check that global variables can be shared between object files. -
MAIN
: Check that the compiler under test can emit a validmain
entry point. -
ATOF
: Check that the generated code can call the C functionatof
(note thatatof
is just a function, so all you need is the declaration foratof
-- you don't need to be able to handle all of the C standard library header).
Note that these tests do not reflect either a required level of achievement for your compiler, nor should they mean that you can't add other tests.
Your tests cases should be included in a folder called
test_deliverable/test_cases
and follow the naming convention in the example files we'll develop in the lecture.