Skip to content

Commit 353736a

Browse files
committed
Added documentation to all of the test files. The documentation for the
clitest.py file could probably be better, but that can happen when/if it is ever actually put in it's own repository to make a little test framework. Each test file has some information on how test are made for its specific needs. The general info was added to the test_word.py file. And of course the information on each actual operation being tested available in the project README. Removed old test files that were no longer in use. Also removed the main functions from the individual test files. They are somewhat useful if you want to run an individual test suite, but so far I have never done it and it reduces a point of maintenance when the testing classes and functions change. Also made some adjustment to the makefile because the default .c rule was compiling with the release flags.
1 parent 9938c7e commit 353736a

File tree

11 files changed

+285
-424
lines changed

11 files changed

+285
-424
lines changed

Makefile

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
CC= gcc
2-
CC_FLAGS = -o lt64 -O3 -I include
3-
CC_DEBUG_FLAGS= -o lt64-debug -Og -D DEBUG -I include
4-
CC_TEST_FLAGS= -o lt64-test -Og -D TEST -I include
2+
CC_FLAGS = -I include
3+
CC_RELEASE_FLAGS = -o lt64 -O3
4+
CC_DEBUG_FLAGS= -o lt64-debug -Og -D DEBUG
5+
CC_TEST_FLAGS= -o lt64-test -Og -D TEST
56

67
%.o: %.cpp
78
$(CC) $(CC_FLAGS) -c $< -o $@
89

910
.PHONY: release
1011
release: src/main.c
11-
$(CC) src/* $(CC_FLAGS)
12+
$(CC) src/* $(CC_FLAGS) $(CC_RELEASE_FLAGS)
1213

1314
.PHONY: debug
1415
debug: src/main.c
15-
$(CC) src/* $(CC_DEBUG_FLAGS)
16+
$(CC) src/* $(CC_FLAGS) $(CC_DEBUG_FLAGS)
1617

1718
.PHONY: tests
1819
tests: src/main.c
19-
$(CC) src/* $(CC_TEST_FLAGS)
20+
$(CC) src/* $(CC_FLAGS) $(CC_TEST_FLAGS)
2021
python3 test/runner.py
2122
rm test.vm lt64-test
2223

test/clitest.py

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
1+
"""
2+
TestSuite and Test objects and some helper functions for running simple tests
3+
on command line programs.
4+
5+
Tries to do a lot and not too much while providing simple clean output for
6+
test results. Uses ansi color codes to color the output. This will make test
7+
output hard to read on terminals that do not support them.
8+
9+
Lots of different kind of tests that feed a command line program some input and
10+
expect some output can be run with these tools. However, it is likely that some
11+
things will need to be adjusted. The test suite has been written with a lot of
12+
small methods that are called by other methods. This gives it a bit of a
13+
template pattern to replace any little peice as needed. This may be to read
14+
input or output differently or to perform a different check to determine
15+
success or failure or to add new data and operations while keeping the same
16+
general pattern. The helper functions are also separate and not TestSuite
17+
methods to allow their use in different ways without always subclassing
18+
TestSuite. To see some project specific uses see the lieutenant-64 repository.
19+
20+
TODO Make this it's own project and repo. Then add some examples of how it
21+
could be used to test certain kinds of program and maybe some simple extensions
22+
to cover common use cases not covered by the base class.
23+
"""
124
import subprocess as sp
225

326
### Exit codes ###
@@ -11,6 +34,15 @@
1134

1235
### Functions ################################################################
1336
def display_totals(failed, total, reprint_failed=True, show_fail_info=True):
37+
"""Display the results of a test run to stdout.
38+
39+
failed is a list of failed Test objects. If there are any failed tests,
40+
and reprint_failed is true, they will have their individual results
41+
printed with all their info. If show_fail_info is false then the short
42+
output for each failed test result will be reprinted.
43+
44+
total is the number of tests that were run.
45+
"""
1446
print()
1547
print()
1648
if len(failed):
@@ -25,6 +57,14 @@ def display_totals(failed, total, reprint_failed=True, show_fail_info=True):
2557
print(f"{GREEN}****{NORMAL} {total} Tests Passed {GREEN}****{NORMAL}")
2658

2759
def display_test_results(test, passed, num, show_fail_info=True):
60+
"""Displays a line with the test name and whether it passed or failed.
61+
62+
If show_fail_info is true more detailed output will be given with any error
63+
text and exit code followed by the expected output and the actual output.
64+
65+
num is the number of the test in it's test suite and will be printed before
66+
the test name.
67+
"""
2868
print(f"{f'{num}:':<3} {test.name:<70}", end="")
2969
if passed:
3070
print(f"[{GREEN}PASS{NORMAL}]")
@@ -41,6 +81,14 @@ def display_test_results(test, passed, num, show_fail_info=True):
4181
print()
4282

4383
def compile_program(command_list):
84+
"""Given a list of the compilation command and it's arguments will run
85+
a subprocess to compile a program.
86+
87+
Captures error output and the exit code and prints them to stderr if the
88+
compilation fails.
89+
90+
Retruns True on successful compilation and False otherwise.
91+
"""
4492
command = " ".join(command_list)
4593
print(f"Compiling: {command}")
4694
comp = sp.run(command_list, capture_output=True)
@@ -53,6 +101,11 @@ def compile_program(command_list):
53101
return True
54102

55103
def run_test_suites(test_suites):
104+
"""Runs each test suite in a list of tests suites.
105+
106+
Sets all test output for the running of the tests to False so that only
107+
the test names and results are printed during the run of each suite.
108+
At the end reprints each failed test with all of it's verbose info."""
56109
failed = []
57110
total = 0
58111
for suite in test_suites:
@@ -69,6 +122,42 @@ def run_test_suites(test_suites):
69122

70123
# TODO implement behaviour for show_line_diff=True
71124
class TestSuite:
125+
"""A collection of tests for a command line program that will be run
126+
as a group.
127+
128+
Takes a test suite name, a command for the command line program to run for
129+
each test, and a list of tests.
130+
131+
The input to the program defaults to stdin, but program_source can be
132+
passed a file name to read the input to pass to the program under test's
133+
subprocess. In a similar way program output can specify where to expect
134+
the actual output. If it is stdout the programs output will be read from
135+
stdout. If it is a filename that file will be read instead.
136+
137+
input_is_file tells the test suite that the program under test expects to
138+
read from a file instead of being given input. If this is true then
139+
program source must be set as the filename and any program input will be
140+
written to that file so the program under test can use it to get test
141+
input.
142+
143+
expected_is_file tells the test suite that the expected output needs to be
144+
read from a file. This might be handy if the expected output is very large
145+
and it is impractical to specify it in a string when creating the Test
146+
object.
147+
148+
print_end_info will tell the suite that when it is finished running it
149+
should display some information about the run of the suite. If this is
150+
true and reprint_failed is true then each failed test will be reprinted
151+
after the tests have all finished running.
152+
153+
show_fail_info specifies that when a test fails a verbose output of
154+
failure information should be printed. This might contain error information
155+
and will always contain the expected output vs the actual output.
156+
157+
show_line_diff(unimplemented) is indended to give a view of the difference
158+
between the expected and actual instead of printing them completely.
159+
However it has not been setup yet.
160+
"""
72161
def __init__(self, name, command,
73162
tests=[], compile_command=None,
74163
program_source="stdin", program_output="stdout",
@@ -90,6 +179,9 @@ def __init__(self, name, command,
90179
self.failed=[]
91180

92181
def run(self):
182+
"""Runs each test in order and displays the results for each run
183+
and the totals if print_end_info is true.
184+
"""
93185
print()
94186
print(f"======== {self.name} ========")
95187
if self.compile_command:
@@ -102,6 +194,8 @@ def run(self):
102194
self.reprint_failed, self.show_fail_info)
103195

104196
def execute(self):
197+
"""Executes each test and prints the individual test info
198+
according to the printing flags that are set."""
105199
for i, t in enumerate(self.tests):
106200
if self.program_source != "stdin":
107201
self.write_program_input(t)
@@ -118,24 +212,41 @@ def execute(self):
118212
self.failed.append((i+1, t))
119213

120214
def write_program_input(self, test):
121-
print("base write")
215+
"""Writes a test's program input to a file so that it can be read
216+
by the program under test. Can be overridden if a specific Test type
217+
might need a different output type. I.e. writing a binary file."""
122218
with open(self.program_source, 'w+') as f:
123219
f.write(test.input)
124220

125221
def get_test_input(self, test):
222+
"""Returns the test input. If the test expects input from a file it
223+
will be read in and returned. Otherwise the test input will be
224+
returned as a byte string. The reason for this is that Subprocess.run
225+
expects program input as a byte string. It can also be overriden if
226+
getting or creating the test input from a test is more complex. I.e.
227+
test input is given as a hex string and needs to be converted to the
228+
appropriate representation before being returned as a byte string."""
126229
if self.input_is_file:
127230
with open(test.input, 'rb') as f:
128231
return f.read()
129232
else:
130233
return test.input().encode('utf-8')
131234

132235
def check(self, test, proc):
236+
"""Checks to see if the process result output matches the expected
237+
output. To pass a test must have matching expected and actual
238+
output as well as return 0 for the exit code."""
133239
actual = self.get_actual(test, proc)
134240
expected = self.get_expected(test, proc)
135241
test.exit_code = proc.returncode
136242
return actual == expected and test.exit_code == EXIT_SUCCESS
137243

138244
def get_actual(self, test, proc):
245+
"""Returns the string of actual output after stripping whitespace
246+
from the ends. If the output is expected to be in a file it reads it
247+
from, otherwise the output captured to stdout by the process run is
248+
returned as a utf-8 string. Can be overriden to provide different
249+
behaviour such as returning an ascii string."""
139250
if self.program_output != "stdout":
140251
with open(self.program_output, 'r'):
141252
test.actual = f.read().strip()
@@ -144,6 +255,9 @@ def get_actual(self, test, proc):
144255
return test.actual
145256

146257
def get_expected(self, test, proc):
258+
"""Retrns the string of expected output. As get_actual it will read
259+
from a file or from the test's expected propery. Strips all leading
260+
and trailing whitespace. Can be also be overriden."""
147261
if self.expected_is_file:
148262
with open(test.expected, 'r'):
149263
test.expected = f.read().strip()
@@ -152,6 +266,20 @@ def get_expected(self, test, proc):
152266
return test.expected
153267

154268
class Test:
269+
"""A simple data object for a test used to test the running of
270+
a cli program.
271+
272+
name is the name that will be printed when showing test results.
273+
274+
input_ and expected are strings for data to pass to the program
275+
under test and what to compare its output to.
276+
277+
show_fail_info tells display functions whether or not to show detailed
278+
information on test failure.
279+
280+
members actual, stderr, and exit_code are provided to add information to
281+
a test after running it to save for processes that may need it but run
282+
in a different place than where the test was executed."""
155283
def __init__(self, name, input_, expected, show_fail_info=True):
156284
self.name = name
157285
self.input = input_

test/runner.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,38 @@
1+
"""
2+
Test runner for the lieutenant-64 virtual machine.
3+
4+
Assembles and runs all test suites and their tests. Calling
5+
`python3 runner.py` will execute all tests for the VM. The only thing it
6+
requires is that the lt64-test binary has been created. To both create the
7+
binary and run the tests and clean up afterward run `make tests`.
8+
9+
For more information on the tests see the individual test files.
10+
"""
111
from clitest import *
212
import vmtest
313
import test_word_ops as word
414
import test_dword_ops as dword
515
import test_movement_ops as move
616
import test_io_ops as io
7-
import test_bit_ops as bits
817
import test_errors as errors
918
import sys
1019

1120
def make_vm_suite(name, tests, prog_name):
21+
"""Helper to more easily create a VmTests object."""
1222
return vmtest.VmTests(name,
1323
prog_name,
1424
tests=tests,
1525
program_source=vmtest.INPUT_FILE)
1626

27+
# The name of the program under test
1728
prog_name = vmtest.TEST_NAME
1829

30+
# The various test suites to use to test the VM
1931
suites = [
2032
make_vm_suite("Handles word ops", word.tests, prog_name),
2133
make_vm_suite("Handles dword ops", dword.tests, prog_name),
22-
#make_vm_suite("Handles qword ops", qword.tests, prog_name),
2334
make_vm_suite("Handles movement ops", move.tests, prog_name),
2435
make_vm_suite("Handles print ops", io.tests, prog_name),
25-
#make_vm_suite("Handles bit ops", bits.tests, prog_name),
2636
vmtest.VmIoTests("Handles read ops",
2737
prog_name,
2838
tests=io.read_tests,
@@ -33,5 +43,6 @@ def make_vm_suite(name, tests, prog_name):
3343
program_source=vmtest.INPUT_FILE),
3444
]
3545

46+
# Execute each given test suite
3647
run_test_suites(suites)
3748

0 commit comments

Comments
 (0)