Skip to content

Commit

Permalink
fix: share common stdio between main and debugger (#56)
Browse files Browse the repository at this point in the history
* fix: share common stdio between main and debugger
introduce StdIo trait and a ConsoleIo and test spy implementation
* improve coverage in main
  • Loading branch information
davidjenni authored Oct 22, 2023
1 parent 3e5bd99 commit c28332a
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 144 deletions.
158 changes: 158 additions & 0 deletions cli/src/console_io.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
use std::io;
use std::io::Write;

pub trait StdIo {
fn read_line(&mut self, buf: &mut String) -> io::Result<usize>;
fn write(&mut self, buf: &str) -> io::Result<usize>;
fn write_err(&mut self, buf: &str) -> io::Result<usize>;
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<usize> {
io::stdin().read_line(buf)
}

fn write(&mut self, msg: &str) -> io::Result<usize> {
let cnt = io::stdout().write(msg.as_bytes())?;
io::stdout().flush()?;
Ok(cnt)
}

fn write_err(&mut self, msg: &str) -> io::Result<usize> {
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<usize> {
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<u8>,
stderr: Vec<u8>,
}

impl StdIo for Spy {
fn read_line(&mut self, buf: &mut String) -> io::Result<usize> {
let lines = self.stdin.split('\n').collect::<Vec<&str>>();
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<usize> {
self.stdout.write(buf.as_bytes())
}

fn write_err(&mut self, buf: &str) -> io::Result<usize> {
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<usize> {
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);
}
}
112 changes: 24 additions & 88 deletions cli/src/debugger.rs
Original file line number Diff line number Diff line change
@@ -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<R, W, E> {
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<u16>,
last_mem_addr: Option<u16>,
}

impl<R, W, E> Debugger<R, W, E>
where
R: io::BufRead,
W: io::Write,
E: io::Write,
{
pub fn new() -> Debugger<Box<dyn io::BufRead>, Box<dyn io::Write>, Box<dyn io::Write>> {
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,
Expand Down Expand Up @@ -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;
Expand All @@ -92,7 +83,7 @@ where
Ok(cpu.get_register_snapshot())
}

fn write_memory_ln(
fn format_memory_ln(
&self,
cpu: &mut Box<dyn Cpu>,
addr: u16,
Expand All @@ -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<DebugCommand, DebugCmdError> {
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 {
Expand Down Expand Up @@ -157,7 +145,7 @@ where
cpu: &mut Box<dyn Cpu>,
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(())
Expand Down Expand Up @@ -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<u8>,
stderr: Vec<u8>,
}

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)?;
Expand All @@ -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)?;
Expand All @@ -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)?;
Expand All @@ -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(())
Expand All @@ -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)?;
Expand Down
Loading

0 comments on commit c28332a

Please sign in to comment.