kr580 1.0.0

Desktop KR580VM80 / Intel 8080 emulator.
Documentation
use k580_ui::backend::{AppCommand, AppEvent, Emulator, NetworkMode, spawn_emulator};
use std::io::{Read, Write};
use std::time::{Duration, Instant};

#[test]
fn configure_network_command_applies_settings_and_starts_worker() {
    let mut emulator = Emulator::default();

    emulator.handle_command(AppCommand::ConfigureNetwork {
        mode: NetworkMode::Server,
        host: "127.0.0.1".to_owned(),
        port: 0,
    });

    let network = emulator.snapshot().devices.network;
    assert_eq!(network.mode, NetworkMode::Server);
    assert_eq!(network.host, "127.0.0.1");
    assert_eq!(network.port, 0);
}

#[test]
fn clear_network_buffers_command_preserves_endpoint() {
    let mut emulator = Emulator::default();
    emulator.handle_command(AppCommand::ConfigureNetwork {
        mode: NetworkMode::Client,
        host: "127.0.0.1".to_owned(),
        port: 5800,
    });
    emulator.bus_mut().network.queue_received(0x55);
    emulator.handle_command(AppCommand::WritePort(0x03, 0x10));

    emulator.handle_command(AppCommand::ClearNetworkBuffers);

    let network = emulator.snapshot().devices.network;
    assert_eq!(network.host, "127.0.0.1");
    assert_eq!(network.port, 5800);
    assert!(network.rx_buffer.is_empty());
    assert!(network.tx_buffer.is_empty());
}

#[test]
fn clearing_empty_network_buffers_emits_no_events() {
    let mut emulator = Emulator::default();

    let events = emulator.handle_command(AppCommand::ClearNetworkBuffers);

    assert!(events.is_empty());
}

#[test]
fn clearing_empty_network_buffers_does_not_publish_a_worker_error() {
    let occupied = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
    let port = occupied.local_addr().unwrap().port();
    let mut emulator = Emulator::default();
    emulator.handle_command(AppCommand::ConfigureNetwork {
        mode: NetworkMode::Server,
        host: "127.0.0.1".to_owned(),
        port,
    });

    for _ in 0..40 {
        std::thread::sleep(Duration::from_millis(25));
        if emulator.snapshot().devices.network.last_error.is_some() {
            break;
        }
    }
    assert!(emulator.snapshot().devices.network.last_error.is_some());

    let events = emulator.handle_command(AppCommand::ClearNetworkBuffers);

    assert!(events.is_empty());
}

#[test]
fn actor_publishes_network_rx_and_tx_without_cpu_ticks() {
    let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
    let port = listener.local_addr().unwrap().port();
    drop(listener);
    let handle = spawn_emulator();
    handle.drain_events();
    handle
        .send(AppCommand::ConfigureNetwork {
            mode: NetworkMode::Server,
            host: "127.0.0.1".to_owned(),
            port,
        })
        .unwrap();

    let deadline = Instant::now() + Duration::from_secs(2);
    let mut client = loop {
        match std::net::TcpStream::connect(("127.0.0.1", port)) {
            Ok(client) => break client,
            Err(error) if Instant::now() < deadline => {
                let _ = error;
                std::thread::sleep(Duration::from_millis(25));
            }
            Err(error) => panic!("network worker did not start listening: {error}"),
        }
    };
    client.write_all(b"ABC").unwrap();

    let mut received = None;
    while Instant::now() < deadline {
        for event in handle.drain_events() {
            if let AppEvent::StateChanged(snapshot) = event
                && snapshot.devices.network.rx_buffer == b"ABC"
            {
                received = Some(snapshot.devices.network);
                break;
            }
        }
        if received.is_some() {
            break;
        }
        std::thread::sleep(Duration::from_millis(25));
    }
    let received = received.expect("actor did not publish bytes received by TCP worker");
    assert_eq!(received.rx_total, 3);

    handle.send(AppCommand::WritePort(0x03, b'Z')).unwrap();
    client
        .set_read_timeout(Some(Duration::from_secs(2)))
        .unwrap();
    let mut sent = [0_u8; 1];
    client.read_exact(&mut sent).unwrap();
    assert_eq!(sent, [b'Z']);

    let deadline = Instant::now() + Duration::from_secs(2);
    let mut transmitted = false;
    while Instant::now() < deadline {
        for event in handle.drain_events() {
            if let AppEvent::StateChanged(snapshot) = event
                && snapshot.devices.network.tx_buffer == b"Z"
                && snapshot.devices.network.tx_total == 1
            {
                transmitted = true;
                break;
            }
        }
        if transmitted {
            break;
        }
        std::thread::sleep(Duration::from_millis(25));
    }
    handle.send(AppCommand::Shutdown).unwrap();

    assert!(
        transmitted,
        "actor did not publish bytes sent by TCP worker"
    );
}