fixed priv start code, added tests
parent
4f1c73df9e
commit
cd5795bb74
@ -0,0 +1,73 @@
|
||||
import contextlib
|
||||
import os
|
||||
from abc import abstractmethod
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import Optional, Union, Tuple
|
||||
from unittest import TestCase
|
||||
|
||||
from riscemu import CPU, UserModeCPU, InstructionSetDict, RunConfig
|
||||
from riscemu.types import Program
|
||||
|
||||
|
||||
class EndToEndTest(TestCase):
|
||||
|
||||
def __init__(self, cpu: Optional[CPU] = None):
|
||||
super().__init__()
|
||||
if cpu is None:
|
||||
cpu = UserModeCPU(InstructionSetDict.values(), RunConfig())
|
||||
self.cpu = cpu
|
||||
|
||||
@abstractmethod
|
||||
def get_source(self) -> Tuple[str, Union[bytes, str, bytearray]]:
|
||||
"""
|
||||
This method returns the source code of the program
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
def test_run_program(self):
|
||||
"""
|
||||
Runs the program and verifies output
|
||||
:return:
|
||||
"""
|
||||
with self.with_source_file() as names:
|
||||
fname, orig_name = names
|
||||
loader = self.cpu.get_best_loader_for(fname)
|
||||
self.program = loader.instantiate(fname, loader.get_options([])).parse()
|
||||
self._change_program_file_name(self.program, orig_name)
|
||||
self.cpu.load_program(self.program)
|
||||
self.after_program_load(self.program)
|
||||
if isinstance(self.cpu, UserModeCPU):
|
||||
self.cpu.setup_stack()
|
||||
try:
|
||||
self.cpu.launch(self.program)
|
||||
except Exception as ex:
|
||||
if self.is_exception_expected(ex):
|
||||
pass
|
||||
raise ex
|
||||
|
||||
@contextlib.contextmanager
|
||||
def with_source_file(self):
|
||||
name, content = self.get_source()
|
||||
if isinstance(content, str):
|
||||
f = NamedTemporaryFile('w', suffix=name, delete=False)
|
||||
else:
|
||||
f = NamedTemporaryFile('wb', suffix=name, delete=False)
|
||||
f.write(content)
|
||||
f.flush()
|
||||
f.close()
|
||||
try:
|
||||
yield f.name, name
|
||||
finally:
|
||||
os.unlink(f.name)
|
||||
|
||||
def after_program_load(self, program):
|
||||
pass
|
||||
|
||||
def is_exception_expected(self, ex: Exception) -> bool:
|
||||
return False
|
||||
|
||||
def _change_program_file_name(self, program: Program, new_name: str):
|
||||
program.name = new_name
|
||||
for sec in program.sections:
|
||||
sec.owner = new_name
|
@ -0,0 +1,77 @@
|
||||
from riscemu.colors import FMT_ERROR, FMT_NONE, FMT_BOLD, FMT_GREEN
|
||||
from riscemu.exceptions import ASSERT_LEN
|
||||
from riscemu.helpers import int_from_bytes
|
||||
from riscemu.instructions import InstructionSet
|
||||
from riscemu.types import Instruction, CPU
|
||||
from riscemu.decoder import RISCV_REGS
|
||||
|
||||
FMT_SUCCESS = FMT_GREEN + FMT_BOLD
|
||||
|
||||
|
||||
def assert_equals(ins: Instruction, cpu: CPU):
|
||||
a, b = (get_arg_from_ins(ins, i, cpu) for i in (0, 2))
|
||||
return a == b
|
||||
|
||||
|
||||
def assert_equals_mem(ins: Instruction, cpu: CPU):
|
||||
a, b = (get_arg_from_ins(ins, i, cpu) for i in (0, 2))
|
||||
a = cpu.mmu.read_int(a)
|
||||
return a == b
|
||||
|
||||
|
||||
def assert_in(ins: Instruction, cpu: CPU):
|
||||
a = get_arg_from_ins(ins, 0, cpu)
|
||||
others = [get_arg_from_ins(ins, i, cpu) for i in range(2, len(ins.args))]
|
||||
return a in others
|
||||
|
||||
|
||||
def _not(func):
|
||||
def test(ins: Instruction, cpu: CPU):
|
||||
return not func(ins, cpu)
|
||||
|
||||
return test
|
||||
|
||||
|
||||
def get_arg_from_ins(ins: Instruction, num: int, cpu: CPU):
|
||||
a = ins.args[num]
|
||||
if a in RISCV_REGS:
|
||||
return cpu.regs.get(a)
|
||||
return ins.get_imm(num)
|
||||
|
||||
|
||||
assert_ops = {
|
||||
'==': assert_equals,
|
||||
'!=': _not(assert_equals),
|
||||
'in': assert_in,
|
||||
'not_in': _not(assert_in),
|
||||
}
|
||||
|
||||
|
||||
class TestIS(InstructionSet):
|
||||
def __init__(self, cpu: 'CPU'):
|
||||
print('[Test] loading testing ISA, this is only meant for running testcases and is not part of the RISC-V ISA!')
|
||||
self.failed = False
|
||||
super().__init__(cpu)
|
||||
|
||||
def instruction_assert(self, ins: Instruction):
|
||||
if len(ins.args) < 3:
|
||||
print(FMT_ERROR + '[Test] Unknown assert statement: {}'.format(ins) + FMT_NONE)
|
||||
return
|
||||
op = ins.args[1]
|
||||
if op not in assert_ops:
|
||||
print(FMT_ERROR + '[Test] Unknown operation statement: {} in {}'.format(op, ins) + FMT_NONE)
|
||||
return
|
||||
|
||||
if assert_ops[op](ins, self.cpu):
|
||||
print(FMT_SUCCESS + '[TestCase] 🟢 passed assertion {}'.format(ins))
|
||||
else:
|
||||
print(FMT_ERROR + '[TestCase] 🔴 failed assertion {}'.format(ins))
|
||||
self.cpu.halted = True
|
||||
self.failed = True
|
||||
|
||||
def instruction_fail(self, ins: Instruction):
|
||||
print(FMT_ERROR + '[TestCase] 🔴 reached fail instruction! {}'.format(ins))
|
||||
self.cpu.halted = True
|
||||
self.failed = True
|
||||
|
||||
def assert_mem(self, ins: Instruction):
|
@ -0,0 +1,53 @@
|
||||
from riscemu import AssemblyFileLoader
|
||||
from riscemu.colors import *
|
||||
|
||||
FMT_SUCCESS = FMT_GREEN + FMT_BOLD
|
||||
|
||||
def run_test(path: str):
|
||||
from riscemu import CPU, UserModeCPU, RunConfig
|
||||
from riscemu.instructions import InstructionSetDict
|
||||
from test.test_isa import TestIS
|
||||
import os
|
||||
|
||||
fname = os.path.basename(path)
|
||||
|
||||
ISAs = list(InstructionSetDict.values())
|
||||
ISAs.append(TestIS)
|
||||
|
||||
cpu = UserModeCPU(ISAs, RunConfig())
|
||||
try:
|
||||
program = AssemblyFileLoader(path, {}).parse()
|
||||
cpu.load_program(program)
|
||||
cpu.launch(program)
|
||||
except Exception as ex:
|
||||
print(FMT_ERROR + '[Test] 🔴 failed with exception "{}" ({})'.format(ex, fname) + FMT_NONE)
|
||||
raise ex
|
||||
|
||||
if cpu.halted:
|
||||
for isa in cpu.instruction_sets:
|
||||
if isinstance(isa, TestIS):
|
||||
if not isa.failed:
|
||||
print(FMT_SUCCESS + '[Test] 🟢 successful {}'.format(fname) + FMT_NONE)
|
||||
return not isa.failed
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
import os
|
||||
import glob
|
||||
|
||||
successes = 0
|
||||
failures = 0
|
||||
ttl = 0
|
||||
|
||||
for path in glob.glob(f'{os.path.dirname(__file__)}/*.asm'):
|
||||
print(FMT_BLUE + '[Test] running testcase ' + os.path.basename(path) + FMT_NONE)
|
||||
ttl += 1
|
||||
if run_test(path):
|
||||
successes += 1
|
||||
else:
|
||||
failures += 1
|
||||
|
||||
|
||||
|
@ -0,0 +1,7 @@
|
||||
.data
|
||||
|
||||
data:
|
||||
.word 0xFFFFFFFF, 0x0000FFFF, 0xFF00FF00, 0x7FFFFFFF
|
||||
|
||||
.text
|
||||
ebreak
|
@ -0,0 +1,20 @@
|
||||
.text
|
||||
|
||||
main:
|
||||
addi a0, zero, main
|
||||
addi a1, zero, main
|
||||
addi t0, zero, 1000
|
||||
assert a0, ==, 0x100
|
||||
1:
|
||||
addi a1, a1, 1
|
||||
blt a1, t0, 1b
|
||||
sub a1, a1, a0
|
||||
j 1f
|
||||
addi a1, zero, 0
|
||||
fail
|
||||
1:
|
||||
assert a1, ==, 744
|
||||
add a0, zero, a1 ; set exit code to a1
|
||||
addi a7, zero, SCALL_EXIT ; exit syscall code
|
||||
scall
|
||||
fail
|
Loading…
Reference in New Issue