1
0
mirror of https://github.com/rzzzwilson/pymlac.git synced 2025-06-10 09:32:41 +00:00
Files
rzzzwilson.pymlac/pyasm/asm_tests/test_harness
2016-02-19 12:13:47 +07:00

365 lines
9.9 KiB
Python
Executable File

#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
A test harness for code to test 'pyasm'.
Usage: test_harness [ -h ] [ -d <directory> ] [ -p <prefix> ]
Where <prefix> is the test filename prefix of files to test,
<directory> is a directory of test files.
If <directory> is specified run tests in that directory. If not specified
the test directory is assumed to be './tests'.
If <prefix> is specified, run tests on files in the test subdirectory with
a filename of <prefix>*.asm. If <prefix> is not specified all files with
filename like *.asm are tested.
"""
import sys
import os
import glob
import getopt
import tempfile
import shutil
import string
import struct
TestDirectory = './tests'
TempPrefix = '_#TEMP#_'
BlockloaderSize = 8 * 16 # 8 lines of 16 bytes
HighBitMask = 0100000
def eval_expr(dot, expr):
"""Evaluate an expression that may contain '.'.
dot the current value of 'dot'
expr the expression string to avaluate
Returns the value of the expression.
Raises ArithmeticError if something is wrong.
"""
# replace any "." value with "dot" defined in the symbol table
expr = string.replace(expr, '.', 'DOT')
# evaluate the expression
globs = {'DOT': dot} # passed environment contains 'DOT'
try:
result = eval(expr, globs)
except (TypeError, NameError) as e:
Undefined = e.message
if 'is not defined' in e.message:
Undefined = e.message[len("name '"):-len("' is not defined")]
msg = "Test expression has '%s' undefined" % Undefined
raise ArithmeticError(msg)
msg = "Test expression has an error"
raise ArithmeticError(msg)
return result
def oct_dec_int(s):
"""Convert string 's' to binary integer."""
if s[0] == '0':
result = int(s, base=8)
else:
result = int(s)
return result
def pyword(word):
"""Convert a 16bit value to a real python integer.
That is, convert 0xFFFF to -1.
"""
byte = (word >> 8) & 0xff
byte2 = word & 0xff
bstr = chr(byte) + chr(byte2)
return struct.unpack(">h", bstr)[0]
def get_byte(handle):
"""Read one byte from the input file."""
byte = handle.read(1)
val = struct.unpack("B", byte)[0]
return val
def get_word(handle):
"""Read one word from input file."""
val = (get_byte(handle) << 8) + get_byte(handle)
return val
def read_block(handle, memory):
"""Read data blocks from file, update memory dict.
Returns None if the end block was read otherwise returns a tuple
(org, length) where 'org' is the start address of the block and 'length'
is the number of words in the block.
"""
# read load address, num data words and checksum
base_address = get_word(handle)
if base_address & HighBitMask:
return None
dot = base_address
neg_size = get_word(handle)
data_size = -pyword(neg_size)
checksum = get_word(handle)
# read data words, placing into dictionary
for _ in range(data_size):
data_word = get_word(handle)
memory[dot] = data_word
dot += 1
return (base_address, data_size)
def get_memory(ptp):
"""Read a PTP file, return memory contents as a dictionary.
Do not return the blockloader contents.
We don't check the checksum.
Returns a tuple (orgs, memory) where:
'orgs' is a list of ORG block addresses like [0100, 0200]
'memory' is a dictionary of memory contents: {addr: contents}
"""
orgs = []
memory = {}
last_org = None
last_length = 0
with open(ptp, 'rb') as handle:
# skip leading zeros
while get_byte(handle) == 0:
pass
# then skip block loader
for _ in range(BlockloaderSize - 1):
get_byte(handle)
# now read blocks until finished
while True:
result = read_block(handle, memory)
if result is None:
break
(org, length) = result
# this block may continue the last
if last_org and last_org + last_length == org:
last_length += length
else:
orgs.append(org)
last_org = org
last_length = length
return (orgs, memory)
def test_file(filename):
"""Test one ASM test file."""
# counter for number of errors
errors = 0
# assemble file into known listing file and PTP file
lst_file = '%s.lst' % TempPrefix
ptp_file = '%s.ptp' % TempPrefix
cmd = ('../pyasm -o %s -l %s %s >/dev/null 2>&1'
% (ptp_file, lst_file, filename))
status = os.system(cmd)
if status:
errors += 1
warn("Error assembling file '%s'" % filename)
return errors # must end testing
os.remove(lst_file)
# get the test instructions from the ASM file
with open(filename, 'rb') as handle:
lines = handle.readlines()
lines = [line.rstrip() for line in lines]
tests = []
for (lnum, line) in enumerate(lines):
if line.startswith(';|') or line.startswith(';!'):
prefix = line[:2]
must_pass = line.startswith(';|')
test = line[2:]
test = test.replace('\t', ' ')
if ';' in test:
ndx = test.index(';')
test = test[:ndx].rstrip()
tests.append((lnum+1, must_pass, prefix, test))
# make sure we have some tests
if not tests:
errors += 1
print("No tests found in file '%s'" % filename)
return errors # must end testing
# get the generated code and ORG block info from the PTP file
# memory locations are in a dictionary: {addr: contents, ...}
(orgs, memory) = get_memory(ptp_file)
# interpret the test instructions and check generated code
dot = None
for (lnum, must_pass, prefix, test) in tests:
# check for a label
if test[0] == ' ':
# no label
label = None
address = dot
value = test
else:
# have label, $n or octal/decimal?
# set 'dot' either way
(label, value) = test.split(' ', 1)
if label[0] == '$':
org_num = int(label[1:])
try:
dot = orgs[org_num-1]
except IndexError:
if must_pass:
errors += 1
warn("File '%s' has label '%s' with bad ORG number: %s%s"
% (filename, label, prefix, test))
return errors # must end testing
address = dot
else:
address = oct_dec_int(label)
dot = address
# evaluate the value field
value = eval_expr(dot, value)
if address is None:
if must_pass:
errors += 1
warn("File '%s' has label '%s' with bad ORG number: %s"
% (filename, label, test))
return errors # must end testing
try:
mem_value = memory[address]
except KeyError:
if must_pass:
errors += 1
warn("%s\n"
"%2d: %s\n"
"Test comment has check for address %04o which isn't in block"
% (filename, lnum, test, address))
else:
if mem_value != value:
if must_pass:
errors += 1
warn("%s\n"
"%2d: %s\n"
"Memory at address %04o should be %06o, got %06o"
% (filename, lnum, test, address, value, mem_value))
else:
if not must_pass:
errors += 1
warn("%s\n"
"%2d: %s\n"
"Memory contents check should fail but didn't?"
% (filename, lnum, test))
dot += 1
return errors
def warn(msg):
"""Print error message and continue."""
print('=================================\n'
'%s\n'
'---------------------------------' % msg)
def error(msg):
"""Print error message and stop."""
warn(msg)
sys.exit(10)
def run_tests(directory, prefix):
"""Run all appropriate test cases.
directory path to directory holding test cases
prefix filename prefix of tests to run (may be None).
"""
# handle 'all' case
if prefix is None:
prefix = ''
# get list of all test cases we are going to run
files = glob.glob('%s/%s*.asm' % (directory, prefix))
if not files:
if prefix:
error("No test files found in directory '%s' with prefix '%s'"
% (directory, prefix))
else:
error("No test files found in directory '%s'" % directory)
# test each found file
errors = 0
files.sort()
for filename in files:
errors += test_file(filename)
if errors:
if errors == 1:
warn('There was %d error!' % errors)
else:
warn('There were %d errors!' % errors)
# remove temporary files
for f in glob.glob('%s.*' % TempPrefix):
os.remove(f)
def usage(msg=None):
"""Print usage and optional error message."""
if msg is not None:
print('*'*60)
print(msg)
print('*'*60)
print(__doc__)
def main():
"""The test harness."""
# handle the options
try:
(opts, args) = getopt.gnu_getopt(sys.argv, 'd:hp:',
['directory=', 'help', 'prefix='])
except getopt.GetoptError:
usage()
sys.exit(10)
prefix = None
directory = TestDirectory
for opt, arg in opts:
if opt in ('-d', '--directory'):
directory = arg
elif opt in ('-h', '--help'):
usage()
sys.exit(0)
elif opt in ('-p', '--prefix'):
prefix = arg
run_tests(directory, prefix)
if __name__ == '__main__':
main()