blob: b8b3de9f8c806eb66c8c49a46bf3cba76f14c4fd [file]
// Copyright 2021 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
//! Kernel unit tests for the Rust `libc::stdio::StringFile`.
/// StringFile tests
#[unittest::suite(name = "string_file_rust")]
mod string_file_tests {
use core::fmt::Write as _;
use libc::stdio::StringFile;
use unittest::{assert_eq, assert_true, expect_eq, expect_true, subtest};
const FILL_BYTE: u8 = 0xff;
/// Checks the used and available regions of `file`, whose buffer starts at address `base` and
/// holds `capacity` bytes, after `written` bytes have been written to it.
fn validate_regions(
file: &mut StringFile<'_>,
base: usize,
capacity: usize,
written: usize,
) -> bool {
let check =
subtest!(|file: &mut StringFile<'_>, base: usize, capacity: usize, written: usize| {
let effective_capacity = capacity.saturating_sub(1);
let expected_used = written.min(effective_capacity);
let expected_avail = effective_capacity - expected_used;
let used_region = file.used_region();
assert_eq!(expected_used, used_region.len());
// Unlike C++ spans, empty Rust slices have no meaningful (null) base address, so
// the base is only checked for non-empty regions.
if expected_used > 0 {
assert_eq!(base, used_region.as_ptr() as usize);
}
let available_region = file.available_region();
assert_eq!(expected_avail, available_region.len());
if expected_avail > 0 {
assert_eq!(base + expected_used, available_region.as_ptr() as usize);
}
});
check(file, base, capacity, written)
}
/// Checks that the contents of `file` match `expected`.
fn validate_contents(file: &StringFile<'_>, expected: &[u8]) -> bool {
let check = subtest!(|file: &StringFile<'_>, expected: &[u8]| {
// Test the `as_bytes` method.
let contents = file.as_bytes();
assert_eq!(expected.len(), contents.len());
assert_true!(contents == expected);
// Same test, but using the AsRef conversion instead.
let contents: &[u8] = file.as_ref();
assert_eq!(expected.len(), contents.len());
assert_true!(contents == expected);
});
check(file, expected)
}
/// StringFile::Write - With Empty Buffer
#[test]
fn write_on_empty_buffer_is_ok() {
const INPUT: &[u8] = b"12345";
let mut string_file = StringFile::new(&mut []);
expect_eq!(string_file.write(INPUT), INPUT.len());
}
/// StringFile::Write - With Empty Input
#[test]
fn write_empty_string_view_is_ok() {
let mut buffer = [0u8; 1];
let mut string_file = StringFile::new(&mut buffer);
expect_eq!(string_file.write(&[]), 0);
let written_view = string_file.take();
assert_true!(!written_view.is_empty());
assert_eq!(buffer[0], 0);
}
/// StringFile::Write - Input fits in buffer
#[test]
fn write_string_view_that_fits_in_buffer() {
const INPUT: &[u8] = b"12345";
let mut buffer = [0u8; INPUT.len() + 1];
let base = buffer.as_ptr() as usize;
let mut string_file = StringFile::new(&mut buffer);
expect_eq!(string_file.write(INPUT), INPUT.len());
let written_view = string_file.take();
expect_true!(&written_view[..INPUT.len()] == INPUT);
assert_eq!(written_view.as_ptr() as usize, base);
}
/// StringFile::Write - Input does not fit in buffer
#[test]
fn write_truncate_string_view_that_doesnt_fits_in_buffer() {
const INPUT: &[u8] = b"12345";
// We would reserve last char for '\0'
let mut buffer = [0u8; INPUT.len()];
let base = buffer.as_ptr() as usize;
let mut string_file = StringFile::new(&mut buffer);
expect_eq!(string_file.write(INPUT), INPUT.len());
let written_view = string_file.take();
assert_eq!(written_view.len(), INPUT.len());
let written_len = written_view.len();
expect_true!(written_view[..written_len - 1] == INPUT[..written_len - 1]);
assert_eq!(written_view.as_ptr() as usize, base);
assert_eq!(buffer[buffer.len() - 1], 0);
}
/// StringFile::Write - Multiple Calls are correct
#[test]
fn write_many_times() {
const INPUT: &[u8] = b"12345";
// We would reserve last char for '\0'
let mut buffer = [FILL_BYTE; INPUT.len()];
let base = buffer.as_ptr() as usize;
let mut string_file = StringFile::new(&mut buffer);
expect_eq!(string_file.write(&INPUT[0..2]), 2);
expect_true!(string_file.used_region()[0..2] == INPUT[0..2]);
expect_eq!(string_file.write(&INPUT[2..3]), 1);
expect_true!(string_file.used_region()[2..3] == INPUT[2..3]);
expect_eq!(string_file.write(&INPUT[3..5]), 2);
expect_true!(string_file.used_region()[3..4] == INPUT[3..4]);
// The buffer cannot be read while `string_file` borrows it, so instead of checking that the
// last character is still untouched, check that only the space before it has been used.
assert_eq!(string_file.used_region().len(), INPUT.len() - 1);
assert_eq!(string_file.available_region().len(), 0);
let written_view = string_file.take();
assert_eq!(written_view.len(), INPUT.len());
let written_len = written_view.len();
expect_true!(written_view[..written_len - 1] == INPUT[..written_len - 1]);
assert_eq!(written_view.as_ptr() as usize, base);
assert_eq!(buffer[buffer.len() - 1], 0);
}
/// StringFile::take - Adds Null Character
#[test]
fn take_adds_null_character() {
const INPUT: &[u8] = b"12345";
// We would reserve last char for '\0'
let mut buffer = [FILL_BYTE; INPUT.len()];
let base = buffer.as_ptr() as usize;
// The buffer cannot be read while a `StringFile` borrows it, so write once and drop the
// file to inspect the buffer, then repeat the same write with a fresh file to `take()`.
{
let mut string_file = StringFile::new(&mut buffer);
expect_eq!(string_file.write(INPUT), INPUT.len());
}
// Write should not have set the last character set yet.
assert_eq!(buffer[buffer.len() - 1], FILL_BYTE);
let mut string_file = StringFile::new(&mut buffer);
expect_eq!(string_file.write(INPUT), INPUT.len());
let written_view = string_file.take();
let written_len = written_view.len();
let written_ptr = written_view.as_ptr() as usize;
let prefix_matches = written_view[..written_len - 1] == INPUT[..written_len - 1];
assert_eq!(buffer[buffer.len() - 1], 0);
assert_eq!(written_len, INPUT.len());
expect_true!(prefix_matches);
assert_eq!(written_ptr, base);
}
/// StringFile::take - Empty Buffer
#[test]
fn take_on_empty_buffer_is_empty() {
let string_file = StringFile::new(&mut []);
let written_view = string_file.take();
assert_true!(written_view.is_empty());
}
/// StringFile avail/used space
#[test]
fn available_used_space() {
// Empty files should always report no space remaining, and no space used.
let mut empty_file = StringFile::new(&mut []);
assert_true!(validate_regions(&mut empty_file, 0, 0, 0));
// Writing to the file with no buffer should not change anything.
empty_file.write(b"x");
assert_true!(validate_regions(&mut empty_file, 0, 0, 1));
// Repeat the tests, but now with a file backed by a non-empty buffer.
let mut buffer = [0u8; 4];
let capacity = buffer.len();
let base = buffer.as_ptr() as usize;
let mut string_file = StringFile::new(&mut buffer);
for i in 0..capacity {
assert_true!(validate_regions(&mut string_file, base, capacity, i));
string_file.write(b"x");
assert_true!(validate_regions(&mut string_file, base, capacity, i + 1));
}
}
/// StringFile::Skip
#[test]
fn skip() {
let mut buffer = [0u8; 10];
// Skip a part of the start of the file, but overwrite the end.
{
buffer.fill(b'x');
let mut string_file = StringFile::new(&mut buffer);
string_file.skip(3);
string_file.write(b"123456789abcde");
const EXPECTED_STR: &[u8] = b"xxx123456\0";
let actual_str = string_file.take();
assert_eq!(EXPECTED_STR.len(), actual_str.len());
assert_true!(actual_str == EXPECTED_STR);
}
// Skip some of the middle of a file.
{
buffer.fill(b'x');
let mut string_file = StringFile::new(&mut buffer);
string_file.write(b"123");
string_file.skip(3);
string_file.write(b"456789abcde");
const EXPECTED_STR: &[u8] = b"123xxx456\0";
let actual_str = string_file.take();
assert_eq!(EXPECTED_STR.len(), actual_str.len());
assert_true!(actual_str == EXPECTED_STR);
}
// Attempt to skip past the end of a file.
{
buffer.fill(b'x');
let mut string_file = StringFile::new(&mut buffer);
string_file.write(b"123456");
string_file.skip(30);
string_file.write(b"789abcde");
const EXPECTED_STR: &[u8] = b"123456xxx\0";
let actual_str = string_file.take();
assert_eq!(EXPECTED_STR.len(), actual_str.len());
assert_true!(actual_str == EXPECTED_STR);
}
}
/// StringFile string_view conversion
#[test]
fn string_view_conversion() {
{
// A file with no buffer should always yield an empty string view.
let mut empty_file = StringFile::new(&mut []);
assert_true!(validate_contents(&empty_file, b""));
// Attempting to write to the file should not change this.
empty_file.write(b"12345");
assert_true!(validate_contents(&empty_file, b""));
}
{
const PATTERN: &[u8] = b"1234";
let mut buffer = [0u8; 10];
let mut string_file = StringFile::new(&mut buffer);
assert_true!(validate_contents(&string_file, b""));
string_file.write(PATTERN);
assert_true!(validate_contents(&string_file, b"1234"));
string_file.write(PATTERN);
assert_true!(validate_contents(&string_file, b"12341234"));
string_file.write(PATTERN);
assert_true!(validate_contents(&string_file, b"123412341"));
string_file.write(PATTERN);
assert_true!(validate_contents(&string_file, b"123412341"));
}
}
/// StringFile::with_file - Writes through ScopedFile append and truncate
#[test]
fn with_file_round_trip() {
let mut buffer = [0u8; 16];
let mut string_file = StringFile::new(&mut buffer);
string_file.write(b"ab");
let (empty, raw, formatted) = string_file.with_file(|file| {
// Writes through the `FILE` land in the `StringFile` after what is already there.
let empty = file.write(b"");
let raw = file.write(b"cd");
let formatted = write!(file, "{}", 42).is_ok();
(empty, raw, formatted)
});
expect_eq!(empty, 0);
expect_eq!(raw, 2);
expect_true!(formatted);
string_file.write(b"ef");
assert_true!(validate_contents(&string_file, b"abcd42ef"));
// Writes through the `FILE` are truncated just like direct writes.
string_file.with_file(|file| {
file.write(b"0123456789");
});
assert_true!(validate_contents(&string_file, b"abcd42ef0123456"));
let taken = string_file.take();
assert_true!(&taken[..] == b"abcd42ef0123456\0");
}
}