use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use crate::core::clock::GlobalTime;
use crate::host::chardev::{CharPort, ports};
use crate::machine::{Machine, build, catalog};
use super::monitor::RSMON;
fn boot(port_name: &str, paced: bool) -> (Machine, Arc<CharPort>) {
let mut options = catalog::build_options().expect("this build has the classes");
options.realize.media.insert("rom", &RSMON[..]);
let options = options
.with_param("console", port_name)
.with_param("pace", if paced { "true" } else { "false" });
let registry = catalog::registry().expect("this build has the classes");
let machine = match build(
"apple1.machine",
catalog::APPLE1.source,
®istry,
&options,
) {
Ok(m) => m,
Err(e) => panic!("apple1.machine: {e}"),
};
let port = ports::open(&options.realize.hosts, port_name).expect("the PIA opened it");
(machine, port)
}
fn exchange(machine: &mut Machine, port: &CharPort, input: &str, millis: u64) -> Vec<u8> {
port.feed(input.as_bytes());
machine
.run_for(GlobalTime::from_nanos(millis * 1_000_000))
.expect("the machine runs");
port.drain()
}
fn shown(bytes: &[u8]) -> String {
let mut out = String::new();
for &b in bytes {
match b {
b'\r' => out.push('\n'),
0x20..=0x7e => out.push(b as char),
other => out.push_str(&alloc::format!("\\x{other:02x}")),
}
}
out
}
#[test]
fn it_boots_prints_a_prompt_and_dumps_memory_on_request() {
let (mut machine, port) = boot("apple1.test.dump", false);
let banner = exchange(&mut machine, &port, "", 20);
assert_eq!(banner, b"RSMON\r>", "got {:?}", shown(&banner));
let dump = exchange(&mut machine, &port, "FF00\r", 20);
assert_eq!(
dump,
b"FF00\rFF00: D8 A2 FF 9A A9 7F 8D 12\r>",
"got {:?}",
shown(&dump)
);
assert_eq!(
&RSMON[..8],
&[0xd8, 0xa2, 0xff, 0x9a, 0xa9, 0x7f, 0x8d, 0x12]
);
let next = exchange(&mut machine, &port, "\r", 20);
assert_eq!(
next,
b"\rFF08: D0 A9 A7 8D 11 D0 8D 13\r>",
"got {:?}",
shown(&next)
);
}
#[test]
fn it_deposits_a_byte_and_reads_it_back() {
let (mut machine, port) = boot("apple1.test.deposit", false);
let _banner = exchange(&mut machine, &port, "", 20);
let deposit = exchange(&mut machine, &port, "0300:AA\r", 20);
assert_eq!(deposit, b"0300:AA\r>", "got {:?}", shown(&deposit));
let readback = exchange(&mut machine, &port, "0300\r", 20);
assert_eq!(
readback,
b"0300\r0300: AA 00 00 00 00 00 00 00\r>",
"got {:?}",
shown(&readback)
);
let space = machine.space("cpubus").expect("cpubus");
let byte = space
.read(
0x0300,
crate::core::value::Width::U8,
crate::core::space::MemAttrs::DEBUG,
)
.expect("ram answers");
assert_eq!(byte, 0xaa);
}
#[test]
fn a_paced_display_takes_about_sixty_characters_a_second() {
let (mut machine, port) = boot("apple1.test.paced", true);
let early = exchange(&mut machine, &port, "", 100);
assert!(
early.len() <= 7,
"{} bytes in 100 ms is faster than the hardware: {:?}",
early.len(),
shown(&early)
);
let mut total = early;
total.extend(exchange(&mut machine, &port, "", 1_000));
assert_eq!(total, b"RSMON\r>", "got {:?}", shown(&total));
let dump = exchange(&mut machine, &port, "FF00\r", 200);
assert!(
dump.len() < 30,
"{} characters in 200 ms: {:?}",
dump.len(),
shown(&dump)
);
let mut whole = dump;
whole.extend(exchange(&mut machine, &port, "", 1_000));
assert_eq!(
whole,
b"FF00\rFF00: D8 A2 FF 9A A9 7F 8D 12\r>",
"got {:?}",
shown(&whole)
);
}
#[test]
fn a_scripted_session_is_deterministic_and_snapshots() {
let script = "FF00\r0300:5A\r0300\r";
let (mut first, port_a) = boot("apple1.test.determinism.a", false);
let a = exchange(&mut first, &port_a, script, 50);
let (mut second, port_b) = boot("apple1.test.determinism.b", false);
let b = exchange(&mut second, &port_b, script, 50);
assert_eq!(a, b, "the same script produced different bytes");
assert_eq!(
first.state_hash().expect("hashes"),
second.state_hash().expect("hashes"),
"identical output but divergent state"
);
let saved = first.save().expect("saves");
let (mut restored, port_c) = boot("apple1.test.determinism.c", false);
restored.load(&saved).expect("loads");
assert_eq!(
restored.state_hash().expect("hashes"),
first.state_hash().expect("hashes")
);
let _ = port_c.drain();
let after_a = exchange(&mut first, &port_a, "0300\r", 20);
let after_c = exchange(&mut restored, &port_c, "0300\r", 20);
assert_eq!(after_a, after_c);
assert_eq!(
after_a,
b"0300\r0300: 5A 00 00 00 00 00 00 00\r>",
"got {:?}",
shown(&after_a)
);
}
#[test]
fn the_cpu_runs_at_the_apple_1s_clock() {
let (mut machine, _port) = boot("apple1.test.clock", false);
machine
.run_for(GlobalTime::from_nanos(1_000_000_000))
.expect("runs");
let domain = machine
.device("cpu")
.and_then(crate::machine::machine::DeviceEntry::domain)
.expect("the cpu has a clock domain");
let ticks = machine.clocks().ticks(domain).expect("a tick count");
assert!(
ticks.abs_diff(1_022_727) <= 1,
"one virtual second is {ticks} cycles, not 1022727"
);
}
#[cfg(feature = "std")]
#[test]
fn the_woz_monitor_boots_and_answers_a_memory_dump() {
let Ok(path) = std::env::var("RSEMU_APPLE1_ROM") else {
std::println!("SKIP: set RSEMU_APPLE1_ROM to a 256-byte Apple 1 monitor image");
return;
};
let image = std::fs::read(&path).expect("RSEMU_APPLE1_ROM is readable");
let mut options = catalog::build_options().expect("classes");
options.realize.media.insert("rom", image.as_slice());
let options = options
.with_param("console", "apple1.test.wozmon")
.with_param("pace", "false");
let registry = catalog::registry().expect("classes");
let mut machine = match build(
"apple1.machine",
catalog::APPLE1.source,
®istry,
&options,
) {
Ok(m) => m,
Err(e) => panic!("{path}: {e}"),
};
let port =
ports::open(&options.realize.hosts, "apple1.test.wozmon").expect("the PIA opened it");
let banner = exchange(&mut machine, &port, "", 50);
std::println!("apple1 + {path}: banner {:?}", shown(&banner));
assert!(
banner.contains(&b'\\'),
"no prompt from {path}: {:?}",
shown(&banner)
);
let dump = exchange(&mut machine, &port, "FF00.FF07\r", 50);
let text = shown(&dump);
std::println!("apple1 + {path}: dump {text:?}");
assert!(text.contains("FF00:"), "no dump from {path}: {text:?}");
for byte in &image[..8] {
assert!(
text.contains(&alloc::format!("{byte:02X}")),
"byte {byte:02X} missing from {text:?}"
);
}
}
#[cfg(feature = "std")]
#[test]
fn a_session_transcript() {
let (mut machine, port) = boot("apple1.test.transcript", false);
let mut transcript = exchange(&mut machine, &port, "", 20);
for line in ["FF00\r", "\r", "0300:C0\r", "0300\r"] {
transcript.extend(exchange(&mut machine, &port, line, 20));
}
std::println!("--- rsemu run apple1 ---\n{}", shown(&transcript));
assert!(transcript.starts_with(b"RSMON"));
}