From c28332a54e62a728ac5ad0126ec5dab8468e831d Mon Sep 17 00:00:00 2001 From: David JENNI <3200210+davidjenni@users.noreply.github.com> Date: Sun, 22 Oct 2023 15:52:06 -0700 Subject: [PATCH] fix: share common stdio between main and debugger (#56) * fix: share common stdio between main and debugger introduce StdIo trait and a ConsoleIo and test spy implementation * improve coverage in main --- cli/src/console_io.rs | 158 ++++++++++++++++++++++++++++++++++++++++++ cli/src/debugger.rs | 112 +++++++----------------------- cli/src/main.rs | 110 +++++++++++++++-------------- 3 files changed, 236 insertions(+), 144 deletions(-) create mode 100644 cli/src/console_io.rs diff --git a/cli/src/console_io.rs b/cli/src/console_io.rs new file mode 100644 index 0000000..33beff0 --- /dev/null +++ b/cli/src/console_io.rs @@ -0,0 +1,158 @@ +use std::io; +use std::io::Write; + +pub trait StdIo { + fn read_line(&mut self, buf: &mut String) -> io::Result; + fn write(&mut self, buf: &str) -> io::Result; + fn write_err(&mut self, buf: &str) -> io::Result; + fn get_writer(&mut self) -> &mut dyn Write; +} + +pub struct ConsoleIo {} + +impl StdIo for ConsoleIo { + fn read_line(&mut self, buf: &mut String) -> io::Result { + io::stdin().read_line(buf) + } + + fn write(&mut self, msg: &str) -> io::Result { + let cnt = io::stdout().write(msg.as_bytes())?; + io::stdout().flush()?; + Ok(cnt) + } + + fn write_err(&mut self, msg: &str) -> io::Result { + let cnt = io::stderr().write(msg.as_bytes())?; + io::stderr().flush()?; + Ok(cnt) + } + + fn get_writer(&mut self) -> &mut dyn Write { + self + } +} + +impl Write for ConsoleIo { + fn write(&mut self, buf: &[u8]) -> io::Result { + io::stdout().write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + io::stdout().flush() + } +} + +impl ConsoleIo { + pub fn default() -> ConsoleIo { + ConsoleIo {} + } +} + +#[cfg(test)] +pub mod tests { + use std::io::Write; + use std::str; + + use super::*; + use crate::StdIo; + + pub struct Spy { + stdin: String, + current_offset: usize, + stdout: Vec, + stderr: Vec, + } + + impl StdIo for Spy { + fn read_line(&mut self, buf: &mut String) -> io::Result { + let lines = self.stdin.split('\n').collect::>(); + if lines.len() <= self.current_offset { + return Ok(0); + } + *buf = lines[self.current_offset].to_string().clone(); + self.current_offset += 1; + Ok(buf.len()) + } + + fn write(&mut self, buf: &str) -> io::Result { + self.stdout.write(buf.as_bytes()) + } + + fn write_err(&mut self, buf: &str) -> io::Result { + self.stderr.write(buf.as_bytes()) + } + + fn get_writer(&mut self) -> &mut dyn Write { + self + } + } + + impl Write for Spy { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.stdout.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + } + + impl Spy { + pub fn new(input: &str) -> Spy { + Spy { + stdin: input.to_string(), + current_offset: 0, + stdout: vec![], + stderr: vec![], + } + } + + pub fn get_stdout(&self) -> String { + str::from_utf8(&self.stdout).unwrap().to_string() + } + + pub fn get_stderr(&self) -> String { + str::from_utf8(&self.stderr).unwrap().to_string() + } + } + + #[test] + fn test_spy_read_line() { + let mut spy = Spy::new("foo\nbar\nbaz"); + let mut buf = String::new(); + spy.read_line(&mut buf).unwrap(); + assert_eq!(buf, "foo"); + spy.read_line(&mut buf).unwrap(); + assert_eq!(buf, "bar"); + spy.read_line(&mut buf).unwrap(); + assert_eq!(buf, "baz"); + } + + #[test] + fn test_spy_stdout() { + let mut spy = Spy::new(""); + StdIo::write(&mut spy, "foo").unwrap(); + std::io::Write::write(&mut spy, "bar".as_bytes()).unwrap(); + assert_eq!(spy.get_stdout(), "foobar"); + } + + #[test] + fn test_spy_stderr() { + let mut spy = Spy::new(""); + spy.write_err("foo").unwrap(); + spy.write_err("bar").unwrap(); + assert_eq!(spy.get_stderr(), "foobar"); + } + + #[test] + fn test_console_stdout_stderr() { + let mut io = ConsoleIo::default(); + let written = StdIo::write(&mut io, "foo"); + assert_eq!(written.unwrap(), 3); + let written = StdIo::write_err(&mut io, "bla"); + assert_eq!(written.unwrap(), 3); + let written = std::io::Write::write(&mut io, "bar".as_bytes()); + std::io::Write::flush(&mut io).unwrap(); + assert_eq!(written.unwrap(), 3); + } +} diff --git a/cli/src/debugger.rs b/cli/src/debugger.rs index f07afa4..70dc02e 100644 --- a/cli/src/debugger.rs +++ b/cli/src/debugger.rs @@ -1,30 +1,21 @@ use std::cmp; use std::io; +use crate::console_io::StdIo; use crate::dbg_cmd_parser::{parse_cmd, AddressRange, DebugCmdError, DebugCommand}; use mos6502_emulator::{Cpu, CpuError, CpuRegisterSnapshot}; -pub struct Debugger { - stdin: R, - stdout: W, - #[allow(dead_code)] - stderr: E, +pub struct Debugger<'a> { + stdio: &'a mut dyn StdIo, last_cmd: DebugCommand, last_prog_addr: Option, last_mem_addr: Option, } -impl Debugger -where - R: io::BufRead, - W: io::Write, - E: io::Write, -{ - pub fn new() -> Debugger, Box, Box> { +impl Debugger<'_> { + pub fn new(stdio: &mut dyn StdIo) -> Debugger { Debugger { - stdin: Box::new(io::BufReader::new(io::stdin())), - stdout: Box::new(io::stdout()), - stderr: Box::new(io::stderr()), + stdio, last_cmd: DebugCommand::Invalid, last_prog_addr: None, last_mem_addr: None, @@ -69,7 +60,7 @@ where let (start, end, _) = calculate_range(self.last_mem_addr, addr_range, cpu); let mut next_addr = start; while let Ok((line, next)) = - self.write_memory_ln(cpu, next_addr, cmp::min(end - next_addr, 16)) + self.format_memory_ln(cpu, next_addr, cmp::min(end - next_addr, 16)) { self.writeln(line.as_str()); next_addr = next; @@ -92,7 +83,7 @@ where Ok(cpu.get_register_snapshot()) } - fn write_memory_ln( + fn format_memory_ln( &self, cpu: &mut Box, addr: u16, @@ -113,16 +104,13 @@ where } fn write(&mut self, msg: &str) { - self.stdout.write_all(msg.as_bytes()).unwrap(); - self.stdout.flush().expect("Failed to flush stdout"); + let _ = self.stdio.write(msg); } fn get_user_input(&mut self) -> Result { self.write("(dbg)> "); let mut input = String::new(); - self.stdin - .read_line(&mut input) - .expect("Failed to read user input"); + let _ = self.stdio.read_line(&mut input); let mut cmd = match parse_cmd(input.trim()) { Ok(cmd) => cmd, Err(e) => match e { @@ -157,7 +145,7 @@ where cpu: &mut Box, snapshot: CpuRegisterSnapshot, ) -> Result<(), DebugCmdError> { - print_register(&mut self.stdout, snapshot); + print_register(&mut self.stdio.get_writer(), snapshot); let (current_op, _) = cpu.disassemble(cpu.get_pc(), 1)?; self.writeln(format!(" {}", current_op[0]).as_str()); Ok(()) @@ -221,47 +209,23 @@ fn calculate_range( #[cfg(test)] mod tests { - // use anyhow::Ok; - use std::str; + use crate::console_io::tests::Spy; use super::*; - struct Spy<'a> { - stdin: &'a [u8], - stdout: Vec, - stderr: Vec, - } - - impl<'a> Spy<'a> { - pub fn new(input: &'a str) -> Spy<'a> { - Spy { - stdin: input.as_bytes(), - stdout: vec![], - stderr: vec![], - } - } - - pub fn get_stdout(&'a self) -> String { - str::from_utf8(&self.stdout).unwrap().to_string() - } - - #[allow(dead_code)] - pub fn get_stderr(&self) -> String { - str::from_utf8(&self.stderr).unwrap().to_string() + fn create_debugger(spy: &mut Spy) -> Debugger { + Debugger { + stdio: spy, + last_cmd: DebugCommand::Invalid, + last_prog_addr: None, + last_mem_addr: None, } } #[test] fn debug_loop_disassemble() -> Result<(), DebugCmdError> { let mut spy = Spy::new("disassemble\n\nstep\n\nquit\n"); - let mut debugger = Debugger { - stdin: Box::new(spy.stdin), - stdout: &mut spy.stdout, - stderr: &mut spy.stderr, - last_cmd: DebugCommand::Invalid, - last_prog_addr: None, - last_mem_addr: None, - }; + let mut debugger = create_debugger(&mut spy); let mut cpu = mos6502_emulator::create_cpu(mos6502_emulator::CpuType::MOS6502)?; cpu.load_program(0x0300, &[0xA9, 0x42, 0x85, 0x0F, 0x00], true)?; cpu.set_pc(0x0300)?; @@ -280,14 +244,7 @@ mod tests { #[test] fn debug_loop_disassemble_by_lines() -> Result<(), DebugCmdError> { let mut spy = Spy::new("di $0300,4\n\nquit\n"); - let mut debugger = Debugger { - stdin: Box::new(spy.stdin), - stdout: &mut spy.stdout, - stderr: &mut spy.stderr, - last_cmd: DebugCommand::Invalid, - last_prog_addr: None, - last_mem_addr: None, - }; + let mut debugger = create_debugger(&mut spy); let mut cpu = mos6502_emulator::create_cpu(mos6502_emulator::CpuType::MOS6502)?; cpu.load_program(0x0300, &[0xA9, 0x42, 0x85, 0x0F, 0x00], true)?; cpu.set_pc(0x0300)?; @@ -303,14 +260,7 @@ mod tests { #[test] fn debug_loop_memory() -> Result<(), DebugCmdError> { let mut spy = Spy::new("memory 0x020..0x42\n\nquit\n"); - let mut debugger = Debugger { - stdin: Box::new(spy.stdin), - stdout: &mut spy.stdout, - stderr: &mut spy.stderr, - last_cmd: DebugCommand::Invalid, - last_prog_addr: None, - last_mem_addr: None, - }; + let mut debugger = create_debugger(&mut spy); let mut cpu = mos6502_emulator::create_cpu(mos6502_emulator::CpuType::MOS6502)?; cpu.set_pc(0x0300)?; debugger.debug_loop(&mut cpu)?; @@ -327,20 +277,13 @@ mod tests { #[test] fn debug_loop_memory_illegal_address() -> Result<(), DebugCmdError> { let mut spy = Spy::new("memory 0xA2042\nquit\n"); - let mut debugger = Debugger { - stdin: Box::new(spy.stdin), - stdout: &mut spy.stdout, - stderr: &mut spy.stderr, - last_cmd: DebugCommand::Invalid, - last_prog_addr: None, - last_mem_addr: None, - }; + let mut debugger = create_debugger(&mut spy); let mut cpu = mos6502_emulator::create_cpu(mos6502_emulator::CpuType::MOS6502)?; cpu.set_pc(0x0300)?; debugger.debug_loop(&mut cpu)?; let stdout = spy.get_stdout(); - println!("{}", stdout); + // println!("{}", stdout); assert!(stdout.contains("Invalid address: must be between 0 and 65535")); assert!(stdout.contains("Usage:")); Ok(()) @@ -349,14 +292,7 @@ mod tests { #[test] fn usage() -> Result<(), DebugCmdError> { let mut spy = Spy::new("help\nquit\n"); - let mut debugger = Debugger { - stdin: Box::new(spy.stdin), - stdout: &mut spy.stdout, - stderr: &mut spy.stderr, - last_cmd: DebugCommand::Invalid, - last_prog_addr: None, - last_mem_addr: None, - }; + let mut debugger = create_debugger(&mut spy); let mut cpu = mos6502_emulator::create_cpu(mos6502_emulator::CpuType::MOS6502)?; cpu.load_program(0x0300, &[0x00], true)?; cpu.set_pc(0x0300)?; diff --git a/cli/src/main.rs b/cli/src/main.rs index bafffc8..a5eebb6 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,16 +1,18 @@ mod args; mod bin_file; +mod console_io; mod dbg_cmd_parser; mod debugger; -use std::io; use std::process; use std::result::Result::Ok; use anyhow::{Context, Error, Result}; use clap::Parser; +use console_io::StdIo; use dbg_cmd_parser::DebugCmdError; +use crate::console_io::ConsoleIo; use crate::debugger::{print_register, Debugger}; use args::CliArgs; use mos6502_emulator::{create_cpu, Cpu, CpuRegisterSnapshot, CpuType}; @@ -18,10 +20,9 @@ use mos6502_emulator::{create_cpu, Cpu, CpuRegisterSnapshot, CpuType}; fn main() { let args = CliArgs::parse(); + let mut console = ConsoleIo::default(); let main = Main { - stdin: io::BufReader::new(io::stdin()), - stdout: io::stdout(), - stderr: io::stderr(), + stdio: &mut console, }; let outcome = main.try_main(&args); @@ -35,19 +36,11 @@ fn main() { } } -struct Main { - #[allow(dead_code)] - stdin: R, - stdout: W, - stderr: E, +struct Main<'a> { + stdio: &'a mut dyn StdIo, } -impl Main -where - R: io::BufRead, - W: io::Write, - E: io::Write, -{ +impl Main<'_> { pub fn try_main(mut self, args: &CliArgs) -> Result<()> { let outcome = match args.command { args::Command::Run => self.run(args), @@ -73,11 +66,11 @@ where } fn write(&mut self, msg: &str) { - self.stdout.write_all(msg.as_bytes()).unwrap(); + self.stdio.write(msg).unwrap(); } fn err(&mut self, msg: &str) { - self.stderr.write_all(msg.as_bytes()).unwrap(); + self.stdio.write_err(msg).unwrap(); } fn run(&mut self, args: &CliArgs) -> Result { @@ -90,7 +83,7 @@ where let (mut cpu, start_addr) = self.init_cpu(args)?; cpu.set_pc(start_addr)?; - let mut dbg = Debugger::::new(); + let mut dbg = Debugger::new(self.stdio); dbg.debug_loop(&mut cpu)?; anyhow::Ok(cpu.get_register_snapshot()) @@ -138,7 +131,7 @@ where } fn print_snapshot(&mut self, snapshot: CpuRegisterSnapshot) { - print_register(&mut self.stdout, snapshot.clone()); + print_register(&mut self.stdio.get_writer(), snapshot.clone()); self.writeln( format!( "Instructions: {}; Cycles: {}; Clock speed: {:.3} MHz", @@ -167,48 +160,20 @@ impl From for Error { #[cfg(test)] mod tests { use anyhow::Ok; - use std::str; use std::time; use super::*; + use crate::console_io::tests::Spy; - struct Spy<'a> { - stdin: &'a [u8], - stdout: Vec, - stderr: Vec, - } - - impl<'a> Spy<'a> { - pub fn default() -> Spy<'a> { - Spy { - stdin: "".as_bytes(), - stdout: vec![], - stderr: vec![], - } - } - - pub fn get_stdout(&'a self) -> String { - str::from_utf8(&self.stdout).unwrap().to_string() - } - - pub fn get_stderr(&self) -> String { - str::from_utf8(&self.stderr).unwrap().to_string() - } - } - - fn prepare_main<'a>(spy: &'a mut Spy) -> Main<&'a [u8], &'a mut Vec, &'a mut Vec> { - Main { - stdin: spy.stdin, - stdout: &mut spy.stdout, - stderr: &mut spy.stderr, - } + fn prepare_main(spy: &mut Spy) -> Main { + Main { stdio: spy } } #[test] fn try_main_run() -> Result<(), Error> { let args = CliArgs::parse_from(["run"]); - let mut spy = Spy::default(); + let mut spy = Spy::new(""); let m = prepare_main(&mut spy); m.try_main(&args)?; @@ -221,10 +186,23 @@ mod tests { } #[test] - fn try_main_unknown_file() -> Result<(), Error> { + fn try_main_debug() -> Result<(), Error> { + let args = CliArgs::parse_from(["r6502.exe", "debug"]); + let mut spy = Spy::new("quit\n"); + let m = prepare_main(&mut spy); + m.try_main(&args)?; + let stdout = spy.get_stdout(); + println!("{}", stdout); + assert!(stdout.contains("Start execution at address FFFE")); + assert_eq!(spy.get_stderr().len(), 0); + Ok(()) + } + + #[test] + fn try_main_unknown_file_error() -> Result<(), Error> { let args = CliArgs::parse_from(["run", "-b=unknown.prg"]); - let mut spy = Spy::default(); + let mut spy = Spy::new(""); let m = prepare_main(&mut spy); let r = m.try_main(&args); @@ -241,11 +219,29 @@ mod tests { Ok(()) } + #[test] + fn try_main_unknown_load_address_error() -> Result<(), Error> { + let args = CliArgs::parse_from(["run", "-b=tests/assets/simplest.bin"]); + + let mut spy = Spy::new(""); + let mut m = prepare_main(&mut spy); + + let r = m.run(&args); + assert!(r.is_err()); + let stderr = r.err().unwrap().to_string(); + // println!("ERR: {}", stderr); + assert!( + stderr.contains("Load address not specified, and cannot be inferred from file format") + ); + + Ok(()) + } + #[test] fn main_running_simplest_prg() -> Result<(), Error> { let args = CliArgs::parse_from(["run", "-b=tests/assets/simplest.prg"]); - let mut spy = Spy::default(); + let mut spy = Spy::new(""); let mut m = prepare_main(&mut spy); let snapshot = m.run(&args)?; @@ -254,7 +250,9 @@ mod tests { assert_eq!(snapshot.accumulated_cycles, 12); assert_eq!(snapshot.accumulator, 0x42); - assert!(spy.get_stdout().contains("Start execution at address 0600")); + let stdout = spy.get_stdout(); + println!("{}", stdout); + assert!(stdout.contains("Start execution at address 0600")); assert_eq!(spy.get_stderr().len(), 0); Ok(()) } @@ -275,7 +273,7 @@ mod tests { approximate_clock_speed: 123456.0, }; - let mut spy: Spy = Spy::default(); + let mut spy = Spy::new(""); let mut m = prepare_main(&mut spy); m.print_snapshot(snapshot);