diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index af1a16640..06a2144bc 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -35,7 +35,7 @@ jobs: # ld to work, so build all the objects without performing the # final linking step. - name: Build - run: make FEATURES="default,enable-gdb" bin/svsm-kernel.elf stage1/stage1.o stage1/reset.o + run: make FEATURES="default-debug,enable-gdb" bin/svsm-kernel.elf stage1/stage1.o stage1/reset.o - name: Run tests run: make test diff --git a/Makefile b/Makefile index 2628f1395..746987994 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,3 @@ -FEATURES ?= "default" -SVSM_ARGS = --features ${FEATURES} - SVSM_ARGS_TEST = --no-default-features ifdef FEATURES_TEST SVSM_ARGS_TEST += --features ${FEATURES_TEST} @@ -8,9 +5,15 @@ endif ifdef RELEASE TARGET_PATH=release +FEATURES ?= "default" +SVSM_ARGS = --features ${FEATURES} CARGO_ARGS += --release +STAGE2_ARGS = else TARGET_PATH=debug +FEATURES ?= "default-debug" +SVSM_ARGS = --features ${FEATURES} +STAGE2_ARGS = --features "enable-console-log" endif ifdef OFFLINE @@ -111,7 +114,7 @@ bin/meta.bin: utils/gen_meta utils/print-meta ./utils/gen_meta $@ bin/stage2.bin: bin - cargo build --manifest-path kernel/Cargo.toml ${CARGO_ARGS} --no-default-features --bin stage2 + cargo build --manifest-path kernel/Cargo.toml ${CARGO_ARGS} ${STAGE2_ARGS} --no-default-features --bin stage2 objcopy -O binary ${STAGE2_ELF} $@ bin/svsm-kernel.elf: bin diff --git a/igvmbuilder/src/gpa_map.rs b/igvmbuilder/src/gpa_map.rs index 15a785785..444f02bc9 100644 --- a/igvmbuilder/src/gpa_map.rs +++ b/igvmbuilder/src/gpa_map.rs @@ -62,7 +62,6 @@ pub struct GpaMap { pub general_params: GpaRange, pub memory_map: GpaRange, pub guest_context: GpaRange, - pub firmware: GpaRange, pub kernel: GpaRange, pub vmsa: GpaRange, } @@ -172,7 +171,6 @@ impl GpaMap { general_params, memory_map, guest_context, - firmware: firmware_range, kernel, vmsa, }; diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 3031edf9c..ddc18139d 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -39,8 +39,10 @@ test.workspace = true [features] default = ["mstpm"] +default-debug = ["enable-console-log", "mstpm"] enable-gdb = ["dep:gdbstub", "dep:gdbstub_arch"] mstpm = ["dep:libmstpm"] +enable-console-log = [] [dev-dependencies] diff --git a/kernel/src/cpu/line_buffer.rs b/kernel/src/cpu/line_buffer.rs new file mode 100644 index 000000000..bff42ad6d --- /dev/null +++ b/kernel/src/cpu/line_buffer.rs @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2022-2023 SUSE LLC +// +// Author: Vasant Karasulli + +use crate::console::_print; +use crate::cpu::percpu::this_cpu; +use crate::log_buffer::log_buffer; +use crate::string::FixedString; +use crate::types::LINE_BUFFER_SIZE; +use crate::utils::immut_after_init::{ImmutAfterInitCell, ImmutAfterInitResult}; +use core::fmt; +use core::fmt::Write; + +#[derive(Debug)] +pub struct LineBuffer { + buf: FixedString, +} + +impl LineBuffer { + pub const fn new() -> Self { + LineBuffer { + buf: FixedString::new(), + } + } + + pub fn write_buffer(&mut self, s: &str) { + for c in s.chars() { + self.buf.push(c); + if c == '\n' || self.buf.length() == LINE_BUFFER_SIZE { + // when buffer is full or '\n' character is encountered + log_buffer().write_log(&self.buf); + self.buf.clear(); + } + } + } +} + +impl Write for LineBuffer { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.write_buffer(s); + Ok(()) + } +} + +impl Default for LineBuffer { + fn default() -> Self { + Self::new() + } +} + +#[derive(Clone, Copy)] +struct BufferLogger { + component: &'static str, +} + +impl BufferLogger { + fn new(component: &'static str) -> BufferLogger { + BufferLogger { component } + } +} + +impl log::Log for BufferLogger { + fn enabled(&self, _metadata: &log::Metadata<'_>) -> bool { + true + } + + fn log(&self, record: &log::Record<'_>) { + let comp: &'static str = self.component; + let line_buf = &mut this_cpu().get_line_buffer(); + // Log format/detail depends on the level. + let rec_args = record.args(); + let lvl = record.metadata().level().as_str(); + let target = record.metadata().target(); + match record.metadata().level() { + log::Level::Error | log::Level::Warn => { + line_buf + .write_fmt(format_args!("[{}] {}: {}\n", comp, lvl, rec_args)) + .unwrap(); + #[cfg(feature = "enable-console-log")] + { + _print(format_args!("[{}] {}: {}\n", comp, lvl, rec_args)); + } + } + + log::Level::Info => { + line_buf + .write_fmt(format_args!("[{}] {}\n", comp, rec_args)) + .unwrap(); + #[cfg(feature = "enable-console-log")] + { + _print(format_args!("[{}] {}\n", comp, rec_args)); + } + } + + log::Level::Debug | log::Level::Trace => { + line_buf + .write_fmt(format_args!("[{}/{}] {} {}\n", comp, target, lvl, rec_args)) + .unwrap(); + #[cfg(feature = "enable-console-log")] + { + _print(format_args!("[{}/{}] {} {}\n", comp, target, lvl, rec_args)); + } + } + } + } + + fn flush(&self) {} +} + +static BUFFER_LOGGER: ImmutAfterInitCell = ImmutAfterInitCell::uninit(); + +pub fn install_buffer_logger(component: &'static str) -> ImmutAfterInitResult<()> { + BUFFER_LOGGER.init(&BufferLogger::new(component))?; + + if let Err(e) = log::set_logger(&*BUFFER_LOGGER) { + // Failed to install the BufferLogger, presumably because something had + // installed another logger before. No logs will be stored in the buffer. + // Print an error string. + _print(format_args!( + "[{}]: ERROR: failed to install buffer logger: {:?}", + component, e, + )); + } + + // Log levels are to be configured via the log's library feature configuration. + log::set_max_level(log::LevelFilter::Trace); + Ok(()) +} diff --git a/kernel/src/cpu/mod.rs b/kernel/src/cpu/mod.rs index 74f5ac462..212e31535 100644 --- a/kernel/src/cpu/mod.rs +++ b/kernel/src/cpu/mod.rs @@ -12,6 +12,7 @@ pub mod extable; pub mod features; pub mod gdt; pub mod idt; +pub mod line_buffer; pub mod msr; pub mod percpu; pub mod registers; diff --git a/kernel/src/cpu/percpu.rs b/kernel/src/cpu/percpu.rs index a1e7f0f3c..174036860 100644 --- a/kernel/src/cpu/percpu.rs +++ b/kernel/src/cpu/percpu.rs @@ -11,6 +11,7 @@ use super::tss::{X86Tss, IST_DF}; use crate::address::{Address, PhysAddr, VirtAddr}; use crate::cpu::apic::ApicError; use crate::cpu::idt::common::INT_INJ_VECTOR; +use crate::cpu::line_buffer::LineBuffer; use crate::cpu::tss::TSS_LIMIT; use crate::cpu::vmsa::init_guest_vmsa; use crate::cpu::vmsa::vmsa_mut_ref_from_vaddr; @@ -337,6 +338,7 @@ pub struct PerCpu { hv_doorbell: Cell<*const HVDoorbell>, init_stack: Cell>, ist: IstStacks, + ln_buf: RefCell, /// Stack boundaries of the currently running task. current_stack: Cell>, @@ -362,6 +364,7 @@ impl PerCpu { hv_doorbell: Cell::new(ptr::null()), init_stack: Cell::new(None), ist: IstStacks::new(), + ln_buf: RefCell::new(LineBuffer::new()), current_stack: Cell::new(MemoryRegion::new(VirtAddr::null(), 0)), } } @@ -843,6 +846,10 @@ impl PerCpu { tss.stacks[0] = addr; self.tss.set(tss); } + + pub fn get_line_buffer(&self) -> RefMut<'_, LineBuffer> { + self.ln_buf.borrow_mut() + } } pub fn this_cpu() -> &'static PerCpu { diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 5b3ec4119..b39bfd3e2 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -27,6 +27,8 @@ pub mod insn_decode; pub mod io; pub mod kernel_region; pub mod locking; +pub mod log_buffer; +pub mod migrate; pub mod mm; pub mod platform; pub mod protocols; diff --git a/kernel/src/log_buffer/mod.rs b/kernel/src/log_buffer/mod.rs new file mode 100644 index 000000000..9758afbcf --- /dev/null +++ b/kernel/src/log_buffer/mod.rs @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2022-2023 SUSE LLC +// +// Author: Vasant Karasulli + +#[cfg(test)] +extern crate alloc; +use core::fmt::Debug; + +use crate::locking::{LockGuard, SpinLock}; +use crate::string::FixedString; + +use crate::types::LINE_BUFFER_SIZE; +#[cfg(not(test))] +use crate::types::PAGE_SIZE; +use crate::utils::StringRingBuffer; + +#[cfg(test)] +use alloc::vec; +#[cfg(test)] +use alloc::vec::Vec; + +#[cfg(not(test))] +const BUF_SIZE: usize = PAGE_SIZE / core::mem::size_of::(); +#[cfg(test)] +const BUF_SIZE: usize = 64; + +#[derive(Copy, Clone, Debug)] +pub struct LogBuffer { + buf: StringRingBuffer, +} + +impl LogBuffer { + const fn new() -> Self { + Self { + buf: StringRingBuffer::::new(), + } + } + + pub fn migrate(&mut self, lb: &SpinLock) { + self.buf = lb.lock().buf; + } + + pub fn write_log(&mut self, s: &FixedString) { + self.buf.write(s.iter()); + } + + #[cfg(test)] + pub fn read_log(&mut self) -> Vec { + if let Some(str) = self.buf.read() { + str.as_bytes().to_vec() + } else { + vec![] + } + } +} + +pub fn migrate_log_buffer(log_buf: &SpinLock) { + LB.lock().migrate(log_buf); +} + +static LB: SpinLock = SpinLock::new(LogBuffer::new()); +pub fn log_buffer() -> LockGuard<'static, LogBuffer> { + LB.lock() +} + +pub fn get_lb() -> &'static SpinLock { + &LB +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::LINE_BUFFER_SIZE; + + #[test] + fn test_read_write_normal() { + let mut fs = FixedString::::new(); + for i in 1..=LINE_BUFFER_SIZE { + fs.push(char::from_u32(i as u32).unwrap()); + } + + let mut lb = LogBuffer::new(); + lb.write_log(&fs); + + let v = lb.read_log(); + assert_eq!(v.len(), LINE_BUFFER_SIZE); + for i in 1..=v.len() { + assert_eq!(i as u8, v[i - 1]); + } + } + + #[test] + fn test_read_write_interleaved() { + let mut fs = FixedString::::new(); + for i in 1..=LINE_BUFFER_SIZE / 2 { + fs.push(char::from_u32(i as u32).unwrap()); + } + + let mut lb = LogBuffer::new(); + lb.write_log(&fs); + + let v = lb.read_log(); + assert_eq!(v.len(), LINE_BUFFER_SIZE / 2); + for i in 1..=v.len() { + assert_eq!(i as u8, v[i - 1]); + } + + fs.clear(); + for i in LINE_BUFFER_SIZE / 2..LINE_BUFFER_SIZE { + fs.push(char::from_u32((i + 1) as u32).unwrap()); + } + + lb.write_log(&fs); + + let v = lb.read_log(); + assert_eq!(v.len(), LINE_BUFFER_SIZE / 2); + for i in 1..v.len() { + let val = (i + LINE_BUFFER_SIZE / 2) as u8; + assert_eq!(val, v[i - 1]); + } + } + + #[test] + fn test_write_wrap_around() { + let mut fs = FixedString::::new(); + for i in 1..=LINE_BUFFER_SIZE / 2 { + fs.push(char::from_u32(i as u32).unwrap()); + } + + let mut lb = LogBuffer::new(); + lb.write_log(&fs); + + let v = lb.read_log(); + assert_eq!(v.len(), LINE_BUFFER_SIZE / 2); + for i in 1..=v.len() { + assert_eq!(i as u8, v[i - 1]); + } + + fs.clear(); + for i in 1..=LINE_BUFFER_SIZE { + let val = (i + LINE_BUFFER_SIZE / 2) as u32; + fs.push(char::from_u32(val).unwrap()); + } + + lb.write_log(&fs); + + let v = lb.read_log(); + assert_eq!(v.len(), LINE_BUFFER_SIZE); + for i in 1..v.len() { + let val = (i + LINE_BUFFER_SIZE / 2) as u8; + assert_eq!(val, v[i - 1]); + } + } + + #[test] + fn test_read_empty_buffer() { + let mut lb = LogBuffer::new(); + let v = lb.read_log(); + assert_eq!(v.len(), 0); + } +} diff --git a/kernel/src/migrate.rs b/kernel/src/migrate.rs new file mode 100644 index 000000000..3e58f398a --- /dev/null +++ b/kernel/src/migrate.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2022-2023 SUSE LLC +// +// Author: Vasant Karasulli +use crate::address::VirtAddr; +use crate::locking::SpinLock; +use crate::log_buffer::LogBuffer; + +// struct containing information that +// is migrated from stage2 to svsm kernel +#[derive(Copy, Clone, Debug)] +pub struct MigrateInfo { + pub bitmap_addr: VirtAddr, + pub lb: &'static SpinLock, +} + +impl MigrateInfo { + pub fn new(vb: VirtAddr, lb: &'static SpinLock) -> Self { + MigrateInfo { + bitmap_addr: vb, + lb, + } + } +} diff --git a/kernel/src/stage2.rs b/kernel/src/stage2.rs index f62b85374..ac865767a 100755 --- a/kernel/src/stage2.rs +++ b/kernel/src/stage2.rs @@ -19,14 +19,17 @@ use cpuarch::snp_cpuid::SnpCpuidTable; use elf::ElfError; use svsm::address::{Address, PhysAddr, VirtAddr}; use svsm::config::SvsmConfig; -use svsm::console::{init_console, install_console_logger}; +use svsm::console::init_console; use svsm::cpu::cpuid::{dump_cpuid_table, register_cpuid_table}; use svsm::cpu::gdt; use svsm::cpu::idt::stage2::{early_idt_init, early_idt_init_no_ghcb}; +use svsm::cpu::line_buffer::install_buffer_logger; use svsm::cpu::percpu::{this_cpu, PerCpu}; use svsm::error::SvsmError; use svsm::fw_cfg::FwCfg; use svsm::igvm_params::IgvmParams; +use svsm::log_buffer::get_lb; +use svsm::migrate::MigrateInfo; use svsm::mm::alloc::{memory_info, print_memory_info, root_mem_init}; use svsm::mm::init_kernel_mapping_info; use svsm::mm::pagetable::{ @@ -85,7 +88,6 @@ fn setup_env( early_idt_init_no_ghcb(); platform.env_setup(); - install_console_logger("Stage2").expect("Console logger already initialized"); init_kernel_mapping_info( VirtAddr::null(), VirtAddr::from(640 * 1024usize), @@ -109,6 +111,7 @@ fn setup_env( .expect("console serial output already configured"); (*CONSOLE_SERIAL).init(); init_console(&*CONSOLE_SERIAL).expect("Console writer already initialized"); + install_buffer_logger("Stage2").expect("Buffer logger already initialized"); // Console is fully working now and any unsupported configuration can be // properly reported. @@ -426,6 +429,7 @@ pub extern "C" fn stage2_main(launch_info: &Stage2LaunchInfo) { ); let valid_bitmap = valid_bitmap_addr(); + let migrate_info = MigrateInfo::new(VirtAddr::from(valid_bitmap.bits()), get_lb()); // Shut down the GHCB shutdown_percpu(); @@ -434,7 +438,7 @@ pub extern "C" fn stage2_main(launch_info: &Stage2LaunchInfo) { asm!("jmp *%rax", in("rax") u64::from(kernel_entry), in("r8") &launch_info, - in("r9") valid_bitmap.bits(), + in("r9") &migrate_info, options(att_syntax)) }; diff --git a/kernel/src/string.rs b/kernel/src/string.rs index a91b2150b..f6e0969b4 100644 --- a/kernel/src/string.rs +++ b/kernel/src/string.rs @@ -36,6 +36,14 @@ impl FixedString { pub fn length(&self) -> usize { self.len } + + pub fn iter(&self) -> impl Iterator + '_ { + self.data.iter().take(self.len).copied() + } + + pub fn clear(&mut self) { + self.len = 0; + } } impl Default for FixedString { diff --git a/kernel/src/svsm.rs b/kernel/src/svsm.rs index 3dc806ced..e212c018b 100755 --- a/kernel/src/svsm.rs +++ b/kernel/src/svsm.rs @@ -18,12 +18,13 @@ use core::slice; use cpuarch::snp_cpuid::SnpCpuidTable; use svsm::address::{PhysAddr, VirtAddr}; use svsm::config::SvsmConfig; -use svsm::console::{init_console, install_console_logger}; +use svsm::console::init_console; use svsm::cpu::control_regs::{cr0_init, cr4_init}; use svsm::cpu::cpuid::{dump_cpuid_table, register_cpuid_table}; use svsm::cpu::efer::efer_init; use svsm::cpu::gdt; use svsm::cpu::idt::svsm::{early_idt_init, idt_init}; +use svsm::cpu::line_buffer::install_buffer_logger; use svsm::cpu::percpu::current_ghcb; use svsm::cpu::percpu::PerCpu; use svsm::cpu::percpu::{this_cpu, this_cpu_shared}; @@ -36,6 +37,8 @@ use svsm::fw_cfg::FwCfg; use svsm::greq::driver::guest_request_driver_init; use svsm::igvm_params::IgvmParams; use svsm::kernel_region::new_kernel_region; +use svsm::log_buffer::migrate_log_buffer; +use svsm::migrate::MigrateInfo; use svsm::mm::alloc::{memory_info, print_memory_info, root_mem_init}; use svsm::mm::memory::{init_memory_map, write_guest_memory_map}; use svsm::mm::pagetable::paging_init; @@ -68,7 +71,7 @@ extern "C" { * startup_64. * * %r8 Pointer to the KernelLaunchInfo structure - * %r9 Pointer to the valid-bitmap from stage2 + * %r9 Pointer to the MigrateInfo from stage2 */ global_asm!( r#" @@ -277,9 +280,9 @@ fn init_cpuid_table(addr: VirtAddr) { } #[no_mangle] -pub extern "C" fn svsm_start(li: &KernelLaunchInfo, vb_addr: usize) { +pub extern "C" fn svsm_start(li: &KernelLaunchInfo, mi: &MigrateInfo) { let launch_info: KernelLaunchInfo = *li; - let vb_ptr = VirtAddr::new(vb_addr).as_mut_ptr::(); + let vb_ptr = mi.bitmap_addr.as_mut_ptr::(); mapping_info_init(&launch_info); @@ -312,6 +315,7 @@ pub extern "C" fn svsm_start(li: &KernelLaunchInfo, vb_addr: usize) { memory_init(&launch_info); migrate_valid_bitmap().expect("Failed to migrate valid-bitmap"); + migrate_log_buffer(mi.lb); let kernel_elf_len = (launch_info.kernel_elf_stage2_virt_end - launch_info.kernel_elf_stage2_virt_start) as usize; @@ -356,7 +360,7 @@ pub extern "C" fn svsm_start(li: &KernelLaunchInfo, vb_addr: usize) { (*CONSOLE_SERIAL).init(); init_console(&*CONSOLE_SERIAL).expect("Console writer already initialized"); - install_console_logger("SVSM").expect("Console logger already initialized"); + install_buffer_logger("SVSM").expect("Buffer logger already initializeed"); log::info!("COCONUT Secure Virtual Machine Service Module (SVSM)"); diff --git a/kernel/src/types.rs b/kernel/src/types.rs index e77ba8b0b..dc07b166d 100644 --- a/kernel/src/types.rs +++ b/kernel/src/types.rs @@ -48,6 +48,12 @@ const _: () = assert!(GUEST_VMPL > 0 && GUEST_VMPL < VMPL_MAX); pub const MAX_CPUS: usize = 512; +#[cfg(not(test))] +pub const LINE_BUFFER_SIZE: usize = 256; + +#[cfg(test)] +pub const LINE_BUFFER_SIZE: usize = 64; + /// Length in byte which represents maximum 8 bytes(u64) #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] pub enum Bytes { diff --git a/kernel/src/utils/mod.rs b/kernel/src/utils/mod.rs index e2cca7290..424a4e7b0 100644 --- a/kernel/src/utils/mod.rs +++ b/kernel/src/utils/mod.rs @@ -7,9 +7,11 @@ pub mod bitmap_allocator; pub mod immut_after_init; pub mod memory_region; +pub mod string_ring_buffer; pub mod util; pub use memory_region::MemoryRegion; +pub use string_ring_buffer::StringRingBuffer; pub use util::{ align_down, align_up, halt, is_aligned, overlap, page_align_up, page_offset, zero_mem_region, }; diff --git a/kernel/src/utils/string_ring_buffer.rs b/kernel/src/utils/string_ring_buffer.rs new file mode 100644 index 000000000..4f3c8b6ac --- /dev/null +++ b/kernel/src/utils/string_ring_buffer.rs @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2022-2024 SUSE LLC +// +// Author: Roy Hopkins + +extern crate alloc; + +use alloc::string::String; +use core::cmp::min; +use core::fmt::Debug; + +#[derive(Copy, Clone, Debug)] +pub struct StringRingBuffer { + data: [char; T], + tail: usize, + head: usize, + empty: bool, +} + +impl StringRingBuffer { + pub const fn new() -> Self { + Self { + data: ['\0'; T], + tail: 0, + head: 0, + empty: true, + } + } + + pub fn write(&mut self, s: impl Iterator) { + s.for_each(|c| self.write_char(c)); + } + + pub fn write_char(&mut self, c: char) { + let full = !self.empty && (self.head == self.tail); + self.data[self.head] = c; + self.head = (self.head + 1) % T; + if full { + self.tail = self.head; + } + self.empty = false; + } + + pub fn read_char(&mut self) -> Option { + if !self.empty { + let c = self.data[self.tail]; + self.tail = (self.tail + 1) % T; + self.empty = self.tail == self.head; + Some(c) + } else { + None + } + } + + pub fn read(&mut self) -> Option { + if !self.empty { + let len = if self.head == self.tail { + T + } else { + ((self.head + T) - self.tail) % T + }; + let end_len = min(T - self.tail, len); + let start_len = len - end_len; + + let a: String = self.data[self.tail..(self.tail + end_len)].iter().collect(); + let b: String = self.data[0..start_len].iter().collect(); + self.tail = self.head; + self.empty = true; + Some(a + &b) + } else { + None + } + } +} + +impl Default for StringRingBuffer { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ring_one_string() { + let mut ring = StringRingBuffer::<32>::new(); + ring.write("Hello".chars()); + + let s = ring.read(); + assert!(s.is_some()); + assert_eq!(s.unwrap(), "Hello"); + } + + #[test] + fn test_ring_two_strings() { + let mut ring = StringRingBuffer::<32>::new(); + ring.write("Hello".chars()); + ring.write("Hello".chars()); + + let s = ring.read(); + assert!(s.is_some()); + assert_eq!(s.unwrap(), "HelloHello"); + } + + #[test] + fn test_ring_wrap() { + let mut ring = StringRingBuffer::<32>::new(); + ring.write("0000000000000000".chars()); + ring.write("1111111111111111".chars()); + ring.write("2222222222222222".chars()); + + let s = ring.read(); + assert!(s.is_some()); + assert_eq!(s.unwrap(), "11111111111111112222222222222222"); + } + + #[test] + fn test_ring_overflow() { + let mut ring = StringRingBuffer::<32>::new(); + ring.write("000000000000000011111111111111112222222222222222".chars()); + + let s = ring.read(); + assert!(s.is_some()); + assert_eq!(s.unwrap(), "11111111111111112222222222222222"); + } + + #[test] + fn test_ring_second_read() { + let mut ring = StringRingBuffer::<32>::new(); + ring.write("Testing".chars()); + + let s = ring.read(); + assert!(s.is_some()); + assert_eq!(s.unwrap(), "Testing"); + let s = ring.read(); + assert!(s.is_none()); + } + + #[test] + fn test_ring_second_wrwr() { + let mut ring = StringRingBuffer::<32>::new(); + ring.write("Testing1".chars()); + let s = ring.read(); + assert!(s.is_some()); + assert_eq!(s.unwrap(), "Testing1"); + + ring.write("Testing2".chars()); + let s = ring.read(); + assert!(s.is_some()); + assert_eq!(s.unwrap(), "Testing2"); + } +}