Files
k-os/crates/servers/terminal-server/src/tests/server.rs

189 lines
5.8 KiB
Rust

extern crate std;
use crate::server::{BACKSPACE, Console, TerminalServer};
use server_framework::Server;
use std::vec;
use std::vec::Vec;
use types::bounded::CapSlot;
use types::log::{text_of, text_words};
use types::terminal::{CTRL_C, KEYS_BIT, TIMER_BIT};
#[derive(Default)]
struct Fake {
keys: Vec<u8>,
/// Typed while nobody reads a line.
typed: Vec<u8>,
flushes: usize,
timers: usize,
focus_taken: usize,
scrolled: usize,
/// Every row drawn, with how many cells.
rows_drawn: Vec<(usize, usize)>,
interrupted: Vec<CapSlot>,
}
impl Console for Fake {
fn draw_row(&mut self, row: usize, text: &[u8]) {
self.rows_drawn.push((row, text.len()));
}
fn draw_cursor(&mut self, _col: usize, _row: usize, _under: u8) {}
fn flush(&mut self) {
self.flushes += 1;
}
fn scroll(&mut self, rows: usize) {
self.scrolled += rows;
}
fn flush_later(&mut self) {
self.timers += 1;
}
fn next_key(&mut self) -> u8 {
self.keys.remove(0)
}
fn poll_key(&mut self) -> Option<u8> {
(!self.typed.is_empty()).then(|| self.typed.remove(0))
}
fn take_focus(&mut self) {
self.focus_taken += 1;
}
fn interrupt(&mut self, notification: CapSlot) {
self.interrupted.push(notification);
}
}
fn term(keys: &[u8]) -> TerminalServer<Fake> {
TerminalServer::new(Fake { keys: keys.to_vec(), ..Fake::default() }, 40, 10)
}
fn read(t: &mut TerminalServer<Fake>) -> Vec<u8> {
let (words, _) = t.handle_call(1, [types::terminal::READ, 0, 0, 0], None);
let (bytes, len) = text_of(words).unwrap();
bytes[..len].to_vec()
}
#[test]
fn writes_land_on_the_grid_and_flush_once_per_burst() {
let mut t = term(&[]);
let before = t.console().flushes;
for chunk in [&b"hello "[..], b"world"] {
t.handle_call(1, text_words(chunk), None);
}
assert_eq!(&t.grid().row(0)[..11], b"hello world");
assert_eq!((t.console().timers, t.console().flushes), (1, before));
t.signal(TIMER_BIT);
assert_eq!(t.console().flushes, before + 1);
}
#[test]
fn a_read_returns_the_edited_line_in_chunks() {
let mut keys = b"ls -lx".to_vec();
keys.push(BACKSPACE);
keys.extend_from_slice(b"a /home/someone/very/long\n");
let mut t = term(&keys);
let mut line = read(&mut t);
while line.last() != Some(&b'\n') {
line.extend(read(&mut t));
}
assert_eq!(line, b"ls -la /home/someone/very/long\n");
assert_eq!(&t.grid().row(0)[..6], b"ls -la");
}
#[test]
fn backspace_on_an_empty_line_does_nothing() {
let mut t = term(&[BACKSPACE, b'x', b'\n']);
assert_eq!(read(&mut t), b"x\n");
}
fn line_of(t: &mut TerminalServer<Fake>) -> Vec<u8> {
let mut line = read(t);
while line.last() != Some(&b'\n') {
line.extend(read(t));
}
line
}
fn on_interrupt(t: &mut TerminalServer<Fake>, slot: u8) -> usize {
t.handle_call(1, [types::terminal::ON_INTERRUPT, 0, 0, 0], CapSlot::new(slot)).0[0]
}
#[test]
fn ctrl_c_while_a_program_runs_interrupts_it() {
let mut t = term(&[]);
assert_eq!(on_interrupt(&mut t, 7), 0);
t.console_mut_for_test().typed = vec![b'x', CTRL_C];
t.signal(KEYS_BIT);
assert_eq!(t.console().interrupted, [CapSlot::new(7).unwrap()]);
assert_eq!(&t.grid().row(0)[..2], b"^C");
}
#[test]
fn only_the_first_interrupt_notification_is_taken() {
let mut t = term(&[]);
assert_eq!(on_interrupt(&mut t, 7), 0);
assert_ne!(on_interrupt(&mut t, 8), 0);
t.console_mut_for_test().typed = vec![CTRL_C];
t.signal(KEYS_BIT);
assert_eq!(t.console().interrupted, [CapSlot::new(7).unwrap()]);
}
#[test]
fn keys_typed_ahead_start_the_next_line() {
let mut t = term(b"s\n");
t.console_mut_for_test().typed = b"l".to_vec();
t.signal(KEYS_BIT);
assert!(t.console().interrupted.is_empty());
assert_eq!(line_of(&mut t), b"ls\n");
}
#[test]
fn ctrl_c_drops_the_line_being_typed() {
let mut t = term(&[b'r', b'm', CTRL_C, b'l', b's', b'\n']);
assert_eq!(on_interrupt(&mut t, 7), 0);
assert_eq!(line_of(&mut t), b"\n");
assert!(t.console().interrupted.is_empty(), "nothing runs to interrupt");
assert_eq!(line_of(&mut t), b"ls\n");
}
#[test]
fn redraw_takes_focus_back_and_paints_every_row() {
let mut t = term(&[]);
t.handle_call(1, text_words(b"hi"), None);
t.handle_call(1, [types::terminal::REDRAW, 0, 0, 0], None);
assert_eq!(t.console().focus_taken, 1);
assert_eq!(&t.grid().row(0)[..2], b"hi");
}
#[test]
fn a_full_screen_scrolls_its_pixels_and_draws_one_row() {
let mut t = term(&[]);
for _ in 0..12 {
t.handle_call(1, text_words(b"line\n"), None);
}
// 10 rows: two scrolls so far, then one more line.
t.console_mut_for_test().rows_drawn.clear();
let before = t.console().scrolled;
t.handle_call(1, text_words(b"more\n"), None);
assert_eq!(t.console().scrolled, before + 1, "the screen moved up a row");
let drawn = &t.console().rows_drawn;
assert!(drawn.len() <= 2, "only the rows that changed: {drawn:?}");
}
#[test]
fn rows_are_drawn_only_as_wide_as_their_text_or_what_it_replaces() {
let mut t = term(&[]);
t.handle_call(1, text_words(b"hello"), None);
assert_eq!(t.console().rows_drawn.last(), Some(&(0, 5)));
t.handle_call(1, [types::terminal::CLEAR, 0, 0, 0], None);
// Cleared: the old five cells and the cursor after them are erased.
assert!(t.console().rows_drawn.contains(&(0, 6)), "{:?}", t.console().rows_drawn);
}
#[test]
fn a_secret_line_shows_stars_and_is_forgotten() {
let mut t = term(b"hunter2\n");
let (words, _) = t.handle_call(1, [types::terminal::READ_SECRET, 0, 0, 0], None);
let (bytes, len) = text_of(words).unwrap();
assert_eq!(&bytes[..len], b"hunter2\n");
assert_eq!(&t.grid().row(0)[..8], b"*******\x20");
assert!(!t.grid().row(0).windows(7).any(|w| w == b"hunter2"));
}