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, /// Typed while nobody reads a line. typed: Vec, flushes: usize, timers: usize, focus_taken: usize, interrupted: Vec, } impl Console for Fake { fn draw_row(&mut self, _row: usize, _text: &[u8]) {} fn draw_cursor(&mut self, _col: usize, _row: usize, _under: u8) {} fn flush(&mut self) { self.flushes += 1; } fn flush_later(&mut self) { self.timers += 1; } fn next_key(&mut self) -> u8 { self.keys.remove(0) } fn poll_key(&mut self) -> Option { (!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 { TerminalServer::new(Fake { keys: keys.to_vec(), ..Fake::default() }, 40, 10) } fn read(t: &mut TerminalServer) -> Vec { 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) -> Vec { let mut line = read(t); while line.last() != Some(&b'\n') { line.extend(read(t)); } line } fn on_interrupt(t: &mut TerminalServer, 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"); }