Test harness for HyphaeVM
All checks were successful
per-push tests / build (push) Successful in 49s
per-push tests / test-frontend (push) Successful in 53s
per-push tests / test-utility (push) Successful in 1m2s
per-push tests / test-backend (push) Successful in 54s
per-push tests / timed-decomposer-parse (push) Successful in 56s
All checks were successful
per-push tests / build (push) Successful in 49s
per-push tests / test-frontend (push) Successful in 53s
per-push tests / test-utility (push) Successful in 1m2s
per-push tests / test-backend (push) Successful in 54s
per-push tests / timed-decomposer-parse (push) Successful in 56s
This commit adds a testing framework for HyphaeVM which enables testing various aspects of the VM state after running input programs against a VM with possibly preinitialized state. This includes a builder pattern initializer for the VM, and bespoke logic for a test case tester. Signed-off-by: Ava Affine <ava@sunnypup.io>
This commit is contained in:
parent
ddb49788af
commit
609e65a8db
4 changed files with 312 additions and 3 deletions
260
hyphae/src/vm.rs
260
hyphae/src/vm.rs
|
|
@ -25,6 +25,7 @@ use crate::util::{Operand, Program, Address};
|
|||
use crate::heap::{Gc, Datum, Cons};
|
||||
|
||||
use core::ops::DerefMut;
|
||||
use core::array;
|
||||
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
|
|
@ -36,12 +37,12 @@ use num::pow::Pow;
|
|||
|
||||
const NUM_OPERAND_REGISTERS: usize = 4;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VM {
|
||||
// execution environment
|
||||
pub stack: StackStack<Gc<Datum>>,
|
||||
pub symtab: QuickMap<Operand>,
|
||||
pub prog: Program,
|
||||
pub fds: Vec<u64>,
|
||||
pub traps: Vec<Arc<dyn Fn(&mut VM)>>,
|
||||
|
||||
// data registers
|
||||
|
|
@ -58,7 +59,71 @@ pub struct VM {
|
|||
pub err_state: bool,
|
||||
}
|
||||
|
||||
impl From<Program> for VM {
|
||||
fn from(value: Program) -> Self {
|
||||
VM{
|
||||
stack: StackStack::new(),
|
||||
symtab: QuickMap::new(),
|
||||
prog: value,
|
||||
traps: vec![],
|
||||
expr: Datum::None.into(),
|
||||
oper: array::from_fn(|_| Datum::None.into()),
|
||||
retn: 0,
|
||||
ictr: 0,
|
||||
errr: Datum::None.into(),
|
||||
running: true,
|
||||
err_state: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VM {
|
||||
pub fn new_with_opts(
|
||||
program: Program,
|
||||
traps: Option<Vec<Arc<dyn Fn(&mut VM)>>>,
|
||||
stack: Option<StackStack<Gc<Datum>>>,
|
||||
syms: Option<QuickMap<Operand>>
|
||||
) -> VM {
|
||||
Into::<VM>::into(program)
|
||||
.with_stack(stack)
|
||||
.with_symbols(syms)
|
||||
.with_traps(traps)
|
||||
.to_owned() // not efficient, but we are not executing
|
||||
}
|
||||
|
||||
pub fn with_stack(
|
||||
&mut self,
|
||||
maybe_stack: Option<StackStack<Gc<Datum>>>,
|
||||
) -> &mut VM {
|
||||
if let Some(stack) = maybe_stack {
|
||||
self.stack = stack;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_symbols(
|
||||
&mut self,
|
||||
maybe_symbols: Option<QuickMap<Operand>>,
|
||||
) -> &mut VM {
|
||||
if let Some(symbols) = maybe_symbols {
|
||||
self.symtab = symbols;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_traps(
|
||||
&mut self,
|
||||
maybe_traps: Option<Vec<Arc<dyn Fn(&mut VM)>>>,
|
||||
) -> &mut VM {
|
||||
if let Some(traps) = maybe_traps {
|
||||
self.traps = traps;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn run_program(&mut self) {
|
||||
if self.prog.0.len() < 1 {
|
||||
self.running = false;
|
||||
|
|
@ -521,3 +586,196 @@ impl VM {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::array;
|
||||
use super::*;
|
||||
use crate::instr;
|
||||
|
||||
const ISA_TESTS: [Option<Vec<(VM, TestResult)>>; instr::TOTAL_INSTRUCTIONS] = [
|
||||
// TRAP
|
||||
None,
|
||||
// BIND
|
||||
None,
|
||||
// UNBIND
|
||||
None,
|
||||
// BOUND
|
||||
None,
|
||||
// PUSH
|
||||
None,
|
||||
// POP
|
||||
None,
|
||||
// ENTER
|
||||
None,
|
||||
// EXIT
|
||||
None,
|
||||
// LOAD
|
||||
None,
|
||||
// DUPL
|
||||
None,
|
||||
// CLEAR
|
||||
None,
|
||||
// NOP
|
||||
None,
|
||||
// HALT
|
||||
None,
|
||||
// PANIC
|
||||
None,
|
||||
// JMP
|
||||
None,
|
||||
// JMPIF
|
||||
None,
|
||||
// EQ
|
||||
None,
|
||||
// LT
|
||||
None,
|
||||
// GT
|
||||
None,
|
||||
// LTE
|
||||
None,
|
||||
// GTE
|
||||
None,
|
||||
// BOOL_NOT
|
||||
None,
|
||||
// BOOL_AND
|
||||
None,
|
||||
// BOOL_OR
|
||||
None,
|
||||
// BYTE_AND
|
||||
None,
|
||||
// BYTE_OR
|
||||
None,
|
||||
// XOR
|
||||
None,
|
||||
// BYTE_NOT
|
||||
None,
|
||||
// ADD
|
||||
None,
|
||||
// SUB
|
||||
None,
|
||||
// MUL
|
||||
None,
|
||||
// FDIV
|
||||
None,
|
||||
// IDIV
|
||||
None,
|
||||
// POW
|
||||
None,
|
||||
// MODULO
|
||||
None,
|
||||
// REM
|
||||
None,
|
||||
// INC
|
||||
None,
|
||||
// DEC
|
||||
None,
|
||||
// CTON
|
||||
None,
|
||||
// NTOC
|
||||
None,
|
||||
// NTOI
|
||||
None,
|
||||
// NTOE
|
||||
None,
|
||||
// CONST
|
||||
None,
|
||||
// MKVEC
|
||||
None,
|
||||
// MKBVEC
|
||||
None,
|
||||
// INDEX
|
||||
None,
|
||||
// LENGTH
|
||||
None,
|
||||
// SUBSL
|
||||
None,
|
||||
// INSER
|
||||
None,
|
||||
// CONS
|
||||
None,
|
||||
// CAR
|
||||
None,
|
||||
// CDR
|
||||
None,
|
||||
// CONCAT
|
||||
None,
|
||||
// S_APPEND
|
||||
None,
|
||||
];
|
||||
|
||||
fn has_stack(state: &VM, idx: usize, target: Gc<Datum>) -> bool {
|
||||
*(state.stack[idx]) == *target
|
||||
}
|
||||
|
||||
fn has_sym(state: &VM, sym: &str, operand: Option<Operand>) -> bool {
|
||||
(operand.is_some() == state.symtab.contains_key(sym)) &&
|
||||
(operand.is_none() || state.symtab.get(&sym.to_owned()) ==
|
||||
operand.as_ref())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TestResult {
|
||||
expr: Option<Gc<Datum>>,
|
||||
stack: Vec<(usize, Gc<Datum>)>,
|
||||
syms: Vec<(&'static str, Option<Operand>)>,
|
||||
errr: Option<&'static str>
|
||||
}
|
||||
|
||||
impl TestResult {
|
||||
fn chk_expr(&self, state: &VM) -> bool {
|
||||
self.expr.is_none() || *(state.expr) == *(self.expr.clone().unwrap())
|
||||
}
|
||||
|
||||
fn chk_err(&self, state: &VM) -> bool {
|
||||
if let Datum::String(ref msg) = *state.errr {
|
||||
let msg = unsafe { str::from_utf8_unchecked(msg) };
|
||||
msg == self.errr.unwrap()
|
||||
} else if let Datum::None = *state.errr {
|
||||
self.errr.is_none()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn chk_stack(&self, state: &VM) -> bool {
|
||||
for i in &self.stack {
|
||||
if !has_stack(&state, i.0, i.1.clone()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn chk_syms(&self, state: &VM) -> bool {
|
||||
for i in &self.syms {
|
||||
if !has_sym(&state, i.0, i.1.clone()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn test_passes(&self, state: VM) -> bool {
|
||||
(self.expr.is_none() || self.chk_expr(&state)) &&
|
||||
(self.stack.is_empty() || self.chk_stack(&state)) &&
|
||||
(self.syms.is_empty() || self.chk_syms(&state)) &&
|
||||
(self.errr.is_none() || self.chk_err(&state))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_isa_tests() {
|
||||
for i in &ISA_TESTS.to_vec() {
|
||||
let Some(test_case) = i else {
|
||||
assert!(false); // dont let untested instructions happen
|
||||
return
|
||||
};
|
||||
|
||||
for i in test_case {
|
||||
let (mut vm, result) = (i.0.clone(), i.1.clone());
|
||||
vm.run_program();
|
||||
assert!(result.test_passes(vm));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue