Website Summary Files Commits Branches @commit @diff @stats
path: root / vm / src
diff options
context:
space:
mode:
Commita6a5cf874fc03296c01bb72abdcb564e5d65ddcd (patch)
Author
 Syndamia <kamen@syndamia.com>
2025-02-15 10:29:05 +0200
Committer
 Syndamia <kamen@syndamia.com>
2025-02-15 10:29:05 +0200
Fileseb585f078e928748dad4e2c7c3b8f2f19bbcaf82 /vm/src
tonkobali-a6a5cf874fc03296c01bb72abdcb564e5d65ddcd.tar
tonkobali-a6a5cf874fc03296c01bb72abdcb564e5d65ddcd.tar.gz
tonkobali-a6a5cf874fc03296c01bb72abdcb564e5d65ddcd.zip
Parent448f86e335b8ecd4699c2eadc03fc97ca0bdc19f (diff)
test(vm): Add tests
4 files changed, 287 insertions, 10 deletions
-rw-r--r--vm/src/vm/memory.rs30
-rw-r--r--vm/src/vm/mod.rs49
-rw-r--r--vm/src/vm/primitive.rs105
-rw-r--r--vm/src/vmm/command.rs113
diff --git a/vm/src/vm/memory.rs b/vm/src/vm/memory.rs
index 0a6b47c..06bf3f5 100644
--- a/vm/src/vm/memory.rs
+++ b/vm/src/vm/memory.rs
@@ -146,6 +146,27 @@ mod tests {
}
#[test]
+ fn new_memory_inits_spa() {
+ let mem = Memory::new();
+ assert_eq!(mem[spa::Z], 0);
+ assert_eq!(mem[spa::E], 1);
+ assert_eq!(mem[spa::N], -1);
+ assert_eq!(mem[spa::PROGRAM_COUNTER], 0);
+ assert_eq!(mem[spa::STACK_POINTER], spa::STACK_POINTER as Word);
+ }
+
+ #[test]
+ fn new_memory_from_data_inits_spa() {
+ let data = vec![ 10, 11, 12, 13, 14, 15 ];
+ let mem = Memory::new_from(&data);
+ assert_eq!(mem[spa::Z], 0);
+ assert_eq!(mem[spa::E], 1);
+ assert_eq!(mem[spa::N], -1);
+ assert_eq!(mem[spa::PROGRAM_COUNTER], data[spa::PROGRAM_COUNTER]);
+ assert_eq!(mem[spa::STACK_POINTER], spa::STACK_POINTER as Word);
+ }
+
+ #[test]
fn parse_63bit_address_correct() {
/* #|-- Address --| */
let value: Word = 0b0111100010000010000000000001111100010101001000000000000100001001u64 as Word;
@@ -164,4 +185,13 @@ mod tests {
assert_eq!(out[1], 0b000001111100010101001, "Second address is incorrect");
assert_eq!(out[2], 0b000000000000100001001, "Third address is incorrect");
}
+
+ #[test]
+ fn get_mut_creates_single_page() {
+ let addr = 10246;
+ let mut mem = Memory::new();
+ mem.get_mut(addr);
+ assert!(mem.sparse_pages.contains_key(&(addr / PAGE_SIZE)));
+ assert_eq!(mem.sparse_pages.len(), 2, "All pages between 0 and address have been created!");
+ }
}
diff --git a/vm/src/vm/mod.rs b/vm/src/vm/mod.rs
index cec5eee..a3af5d1 100644
--- a/vm/src/vm/mod.rs
+++ b/vm/src/vm/mod.rs
@@ -119,3 +119,52 @@ impl VirtualMachineInterface for VirtualMachine {
vm.get_word(index)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use memory::spa;
+
+ #[test]
+ fn start_executes_entire_program() {
+ let vm = create_with_memory(0, &vec![
+ /* 0: */ 0, 0, 0, 5, 0,
+ /* 5: */ 1, 0, 8,
+ /* 8: */ 0, 1, 0,
+ /* 11: */ 0, 0, 0,
+ ]);
+ vm.spawn();
+ thread::sleep(time::Duration::from_millis(200));
+
+ assert!(vm.is_halted());
+ assert_eq!(vm.get_word(spa::Z), 0);
+ assert_eq!(vm.get_word(spa::E), 2);
+ assert_eq!(vm.get_word(spa::PROGRAM_COUNTER), 0);
+ }
+
+ #[test]
+ fn single_step_executes_single_instruction() {
+ let mut vm = create_with_memory(0, &vec![
+ /* 0: */ 0, 0, 0, 5, 0,
+ /* 5: */ 1, 0, 8,
+ /* 8: */ 0, 1, 0,
+ /* 11: */ 0, 0, 0,
+ ]);
+ vm.set_single_step(true);
+ vm.spawn();
+ thread::sleep(time::Duration::from_millis(100));
+
+ assert!(vm.is_halted());
+ assert_eq!(vm.get_word(spa::Z), -1);
+ assert_eq!(vm.get_word(spa::E), 1);
+ assert_eq!(vm.get_word(spa::PROGRAM_COUNTER), 8);
+
+ vm.step();
+ thread::sleep(time::Duration::from_millis(100));
+
+ assert!(vm.is_halted());
+ assert_eq!(vm.get_word(spa::Z), -1);
+ assert_eq!(vm.get_word(spa::E), 2);
+ assert_eq!(vm.get_word(spa::PROGRAM_COUNTER), 11);
+ }
+}
diff --git a/vm/src/vm/primitive.rs b/vm/src/vm/primitive.rs
index ff17b4d..d995870 100644
--- a/vm/src/vm/primitive.rs
+++ b/vm/src/vm/primitive.rs
@@ -23,7 +23,6 @@ macro_rules! above {
impl PrimitiveVirtualMachine {
pub fn new(tick_gap: u64) -> PrimitiveVirtualMachine {
PrimitiveVirtualMachine {
- halted: false,
tick_gap,
..Default::default()
}
@@ -31,10 +30,9 @@ impl PrimitiveVirtualMachine {
pub fn new_with_memory(tick_gap: u64, mem: &Vec<Word>) -> PrimitiveVirtualMachine {
PrimitiveVirtualMachine {
- halted: false,
- single_step: false,
tick_gap,
mem: Memory::new_from(mem),
+ ..Default::default()
}
}
@@ -134,3 +132,104 @@ impl PrimitiveVirtualMachine {
self.mem.get_mut(spa::PROGRAM_COUNTER)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn new_primitive_vm_no_halt_no_step() {
+ let vm = PrimitiveVirtualMachine::new(100);
+ assert!(!vm.halted);
+ assert!(!vm.single_step);
+ }
+
+ #[test]
+ fn new_with_memory_primitive_vm_no_halt_no_step() {
+ let vm = PrimitiveVirtualMachine::new_with_memory(100, &vec![]);
+ assert!(!vm.halted);
+ assert!(!vm.single_step);
+ }
+
+ #[test]
+ fn run_subtracts() {
+ let mut vm = PrimitiveVirtualMachine::new_with_memory(0, &vec![
+ /* 0: */ 0, 0, 0, 5, 0,
+ /* 5: */ 1, 50, 8,
+ ]);
+ vm.step();
+ assert_eq!(vm.mem[50], -1);
+ }
+
+ #[test]
+ fn run_jumps_on_zero() {
+ let mut vm = PrimitiveVirtualMachine::new_with_memory(0, &vec![
+ /* 0: */ 0, 0, 0, 5, 0,
+ /* 5: */ 1, 2, 100,
+ ]);
+ vm.step();
+ assert_eq!(vm.mem[spa::PROGRAM_COUNTER], 100);
+ }
+
+ #[test]
+ fn run_jumps_on_neg() {
+ let mut vm = PrimitiveVirtualMachine::new_with_memory(0, &vec![
+ /* 0: */ 0, 0, 0, 5, 0,
+ /* 5: */ 1, 0, 100,
+ ]);
+ vm.step();
+ assert_eq!(vm.mem[spa::PROGRAM_COUNTER], 100);
+ }
+
+ #[test]
+ fn run_doesnt_jump_on_pos() {
+ let mut vm = PrimitiveVirtualMachine::new_with_memory(0, &vec![
+ /* 0: */ 0, 0, 0, 5, 0,
+ /* 5: */ 2, 1, 100,
+ ]);
+ vm.step();
+ assert_eq!(vm.mem[spa::PROGRAM_COUNTER], 8);
+ }
+
+ #[test]
+ fn run_executes_entire_program() {
+ let mut vm = PrimitiveVirtualMachine::new_with_memory(0, &vec![
+ /* 0: */ 0, 0, 0, 5, 0,
+ /* 5: */ 1, 0, 8,
+ /* 8: */ 0, 1, 0,
+ /* 11: */ 0, 0, 0,
+ ]);
+
+ vm.step();
+ assert!(!vm.halted);
+ assert_eq!(vm.mem[spa::Z], -1);
+
+ vm.step();
+ assert!(!vm.halted);
+ assert_eq!(vm.mem[spa::Z], -1);
+ assert_eq!(vm.mem[spa::E], 2);
+
+ vm.step();
+ assert!(vm.halted);
+
+ assert_eq!(vm.mem[spa::Z], 0);
+ assert_eq!(vm.mem[spa::E], 2);
+ assert_eq!(vm.mem[spa::PROGRAM_COUNTER], 0);
+ }
+
+ #[test]
+ fn single_step_executes_only_once() {
+ let mut vm = PrimitiveVirtualMachine::new_with_memory(0, &vec![
+ /* 0: */ 0, 0, 0, 5, 0,
+ /* 5: */ 1, 0, 8,
+ /* 8: */ 0, 1, 0,
+ /* 11: */ 0, 0, 0,
+ ]);
+ vm.single_step = true;
+ vm.step();
+ assert!(vm.halted);
+
+ assert_eq!(vm.mem[spa::Z], -1);
+ assert_eq!(vm.mem[spa::PROGRAM_COUNTER], 8);
+ }
+}
diff --git a/vm/src/vmm/command.rs b/vm/src/vmm/command.rs
index 188030e..22d0ff3 100644
--- a/vm/src/vmm/command.rs
+++ b/vm/src/vmm/command.rs
@@ -11,14 +11,17 @@ fn expects_n_args(args: &Vec<&str>, n: usize) -> Result<(), String> {
}
}
-fn parse_location(arg: &str, assignment: bool) -> Result<Vec<usize>, String> {
+fn parse_location(arg: &str, assignment: bool) -> Result<Vec<Word>, String> {
let nums_str = arg.to_string();
let nums = nums_str
.split(&['.', ':'])
.collect::<Vec<&str>>();
- let start = nums[0].parse::<usize>()
+ let start = nums[0].parse::<Word>()
.map_err(|_| format!("Unknown command '{}'!", nums[0]))?;
+ if start < 0 {
+ return Err(format!("Address '{}' cannot be negative!", start));
+ }
// Single location
if nums.len() == 1 {
@@ -26,22 +29,25 @@ fn parse_location(arg: &str, assignment: bool) -> Result<Vec<usize>, String> {
}
// Range location
else {
- let end = nums[1].parse::<usize>()
+ let end = nums[1].parse::<Word>()
.map_err(|_| format!("Expected location!"))?;
if assignment {
Ok(vec![start, end])
}
// Range addresses
+ else if end < 0 {
+ return Err(format!("Address '{}' cannot be negative!", end));
+ }
else {
Ok(Vec::from_iter(start..=end))
}
}
}
-fn assign_location(locs: Vec<usize>, vm: &mut VirtualMachine) {
- print_locations(vec![locs[0]], vm);
- vm.set_word(locs[0], locs[1] as Word);
+fn assign_location(locs: Vec<Word>, vm: &mut VirtualMachine) {
+ print_locations(vec![locs[0] as usize], vm);
+ vm.set_word(locs[0] as usize, locs[1] as Word);
}
fn print_locations(locs: Vec<usize>, vm: &VirtualMachine) {
@@ -148,10 +154,103 @@ Memory IO:
assign_location(locs, vm);
}
else {
- print_locations(locs, vm);
+ let mut locs_casted = Vec::new();
+ for elem in locs {
+ let elem = elem.try_into()
+ .map_err(|_| format!("Expected address, got '{}'!", elem))?;
+ locs_casted.push(elem);
+ }
+ print_locations(locs_casted, vm);
}
}
},
}
Ok(())
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn expect_args_too_few() {
+ let res = expects_n_args(&vec!["abc", "arg1"], 2);
+ assert!(res.is_err());
+ }
+
+ #[test]
+ fn expect_args_too_many() {
+ let res = expects_n_args(&vec!["abc", "arg1", "arg2", "arg3"], 2);
+ assert!(res.is_err());
+ }
+
+ #[test]
+ fn expect_args_exact() {
+ let res = expects_n_args(&vec!["abc", "arg1", "arg2"], 2);
+ assert!(res.is_ok());
+ }
+
+ #[test]
+ fn parse_location_single_positive() {
+ let res = parse_location("1234", false);
+ assert!(res.is_ok());
+ let res = res.unwrap();
+ assert_eq!(res.len(), 1);
+ assert_eq!(res[0], 1234);
+ }
+
+ #[test]
+ fn parse_location_single_negative() {
+ let res = parse_location("-1234", false);
+ assert!(res.is_err());
+ }
+
+ #[test]
+ fn parse_location_range_positives() {
+ let res = parse_location("1002.1011", false);
+ assert!(res.is_ok());
+
+ let res = res.unwrap();
+ assert_eq!(res.len(), 10);
+ for i in 1002..=1011 {
+ assert_eq!(res[i-1002], i as Word);
+ }
+ }
+
+ #[test]
+ fn parse_location_range_negative() {
+ let res = parse_location("-1002.1011", false);
+ assert!(res.is_err());
+
+ let res = parse_location("1002.-1011", false);
+ assert!(res.is_err());
+ }
+
+ #[test]
+ fn parse_location_assignment_positive() {
+ let res = parse_location("1829:99", true);
+ assert!(res.is_ok());
+
+ let res = res.unwrap();
+ assert_eq!(res.len(), 2);
+ assert_eq!(res[0], 1829);
+ assert_eq!(res[1], 99);
+ }
+
+ #[test]
+ fn parse_location_assignment_negative_address() {
+ let res = parse_location("-1829:99", true);
+ assert!(res.is_err());
+ }
+
+ #[test]
+ fn parse_location_assignment_negative_value() {
+ let res = parse_location("1829:-99", true);
+ assert!(res.is_ok());
+
+ let res = res.unwrap();
+ assert_eq!(res.len(), 2);
+ assert_eq!(res[0], 1829);
+ assert_eq!(res[1], -99);
+ }
+}