use std::io::{Read, Seek, SeekFrom, Write};
use xpct::{be_err, be_ok, eq_diff, equal, expect, match_pattern, pattern};
use crate::{
Connection, CreateOptions, Error, FileBy, FileOrigin,
file_metadata::{FileKind, Owner},
settings::{Chunking, Settings},
testing::{Case, random_string},
};
impl Connection {
fn with_fixed_chunk_size(size: usize) -> crate::Result<Self> {
let settings = Settings {
chunking: Chunking::Fixed { size },
..Default::default()
};
Connection::open_for_testing(&settings)
}
fn with_cdc_chunk_sizes(
min_size: usize,
avg_size: usize,
max_size: usize,
) -> crate::Result<Self> {
let settings = Settings {
chunking: Chunking::FastCdc {
min_size,
avg_size,
max_size,
},
..Default::default()
};
Connection::open_for_testing(&settings)
}
}
#[test]
fn read_and_write_less_than_one_block() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let expected_bytes = random_string(16, Case::Lower);
let mut actual_bytes = String::new();
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(expected_bytes.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
file.read_to_string(&mut actual_bytes)?;
expect!(actual_bytes).to(eq_diff(expected_bytes));
crate::Result::Ok(())
})?;
Ok(())
}
mod empty_buffer_edge_cases {
use super::*;
#[test]
fn write_empty_buffer() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
expect!(file.write(&[])).to(be_ok()).to(equal(0));
expect!(file.len()).to(be_ok()).to(equal(0));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn read_into_empty_buffer() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let empty_buf: &mut [u8] = &mut [];
expect!(file.read(empty_buf)).to(be_ok()).to(equal(0));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn write_then_read_empty() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(50, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
let empty_buf: &mut [u8] = &mut [];
expect!(file.read(empty_buf)).to(be_ok()).to(equal(0));
crate::Result::Ok(())
})?;
Ok(())
}
}
mod basic_write_read_seek_scenarios {
use super::*;
#[test]
fn write_multiple_chunks_then_read() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let expected = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(expected.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn write_seek_start_read() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let expected = random_string(50, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(expected.as_bytes())?;
expect!(file.seek(SeekFrom::Start(0)))
.to(be_ok())
.to(equal(0));
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn write_seek_middle_read() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
expect!(file.seek(SeekFrom::Start(50)))
.to(be_ok())
.to(equal(50));
let mut actual = String::new();
file.read_to_string(&mut actual)?;
let expected_part = &data[50..];
expect!(actual).to(eq_diff(expected_part));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn write_seek_end_read() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
expect!(file.seek(SeekFrom::End(0)))
.to(be_ok())
.to(equal(100));
crate::Result::Ok(())
})?;
Ok(())
}
}
mod fixed_chunking_edge_cases {
use super::*;
#[test]
fn fixed_write_chunk_size_minus_one() -> crate::Result<()> {
let chunk_size = 32;
let mut conn = Connection::with_fixed_chunk_size(chunk_size)?;
let expected = random_string(chunk_size - 1, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(expected.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn fixed_write_chunk_size_exact() -> crate::Result<()> {
let chunk_size = 32;
let mut conn = Connection::with_fixed_chunk_size(chunk_size)?;
let expected = random_string(chunk_size, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(expected.as_bytes())?;
expect!(file.len()).to(be_ok()).to(equal(chunk_size as u64));
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn fixed_write_chunk_size_plus_one() -> crate::Result<()> {
let chunk_size = 32;
let mut conn = Connection::with_fixed_chunk_size(chunk_size)?;
let expected = random_string(chunk_size + 1, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(expected.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn fixed_write_two_chunks_exact() -> crate::Result<()> {
let chunk_size = 32;
let mut conn = Connection::with_fixed_chunk_size(chunk_size)?;
let expected = random_string(chunk_size * 2, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(expected.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn fixed_write_partial_then_complete_chunk() -> crate::Result<()> {
let chunk_size = 32;
let mut conn = Connection::with_fixed_chunk_size(chunk_size)?;
let part1 = random_string(chunk_size / 2, Case::Lower);
let part2 = random_string(chunk_size / 2, Case::Lower);
let expected = format!("{}{}", part1, part2);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(part1.as_bytes())?;
file.write_all(part2.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn fixed_multiple_small_writes_spanning_chunks() -> crate::Result<()> {
let chunk_size = 32;
let mut conn = Connection::with_fixed_chunk_size(chunk_size)?;
let write_size = 5;
let mut parts = Vec::new();
for _ in 0..10 {
parts.push(random_string(write_size, Case::Lower));
}
let expected = parts.join("");
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
for part in &parts {
file.write_all(part.as_bytes())?;
}
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn fixed_write_just_under_two_chunks() -> crate::Result<()> {
let chunk_size = 32;
let mut conn = Connection::with_fixed_chunk_size(chunk_size)?;
let expected = random_string(chunk_size * 2 - 1, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(expected.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn fixed_read_after_partial_chunk_buffered() -> crate::Result<()> {
let chunk_size = 32;
let mut conn = Connection::with_fixed_chunk_size(chunk_size)?;
let expected = random_string(chunk_size / 2, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(expected.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
}
#[cfg(feature = "chunking")]
mod cdc_chunking_edge_cases {
use super::*;
#[test]
fn cdc_write_less_than_min_size() -> crate::Result<()> {
let min_size = 64;
let avg_size = 256;
let max_size = 1024;
let mut conn = Connection::with_cdc_chunk_sizes(min_size, avg_size, max_size)?;
let expected = random_string(32, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(expected.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn cdc_write_between_min_and_avg() -> crate::Result<()> {
let min_size = 64;
let avg_size = 256;
let max_size = 1024;
let mut conn = Connection::with_cdc_chunk_sizes(min_size, avg_size, max_size)?;
let expected = random_string(128, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(expected.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn cdc_write_between_avg_and_max() -> crate::Result<()> {
let min_size = 64;
let avg_size = 256;
let max_size = 1024;
let mut conn = Connection::with_cdc_chunk_sizes(min_size, avg_size, max_size)?;
let expected = random_string(512, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(expected.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn cdc_write_exact_max_size() -> crate::Result<()> {
let min_size = 64;
let avg_size = 256;
let max_size = 1024;
let mut conn = Connection::with_cdc_chunk_sizes(min_size, avg_size, max_size)?;
let expected = random_string(max_size, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(expected.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn cdc_write_over_max_size() -> crate::Result<()> {
let min_size = 64;
let avg_size = 256;
let max_size = 1024;
let mut conn = Connection::with_cdc_chunk_sizes(min_size, avg_size, max_size)?;
let expected = random_string(2048, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(expected.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn cdc_same_content_same_boundaries() -> crate::Result<()> {
let min_size = 64;
let avg_size = 256;
let max_size = 1024;
let mut conn = Connection::with_cdc_chunk_sizes(min_size, avg_size, max_size)?;
let data = random_string(4096, Case::Lower);
conn.exec(|fs| {
{
let mut file1 = fs.create("file1.txt", FileKind::Regular, Owner::ROOT)?;
file1.write_all(data.as_bytes())?;
}
{
let mut file2 = fs.create("file2.txt", FileKind::Regular, Owner::ROOT)?;
file2.write_all(data.as_bytes())?;
}
let actual1 = {
let mut file1 = fs.open("file1.txt")?;
let mut actual1 = String::new();
file1.read_to_string(&mut actual1)?;
actual1
};
let actual2 = {
let mut file2 = fs.open("file2.txt")?;
let mut actual2 = String::new();
file2.read_to_string(&mut actual2)?;
actual2
};
expect!(actual1.clone()).to(eq_diff(actual2));
expect!(actual1).to(eq_diff(data));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn cdc_different_content_different_boundaries() -> crate::Result<()> {
let min_size = 64;
let avg_size = 256;
let max_size = 1024;
let mut conn = Connection::with_cdc_chunk_sizes(min_size, avg_size, max_size)?;
let data1 = random_string(4096, Case::Lower);
let data2 = random_string(4096, Case::Upper);
conn.exec(|fs| {
{
let mut file1 = fs.create("file1.txt", FileKind::Regular, Owner::ROOT)?;
file1.write_all(data1.as_bytes())?;
}
{
let mut file2 = fs.create("file2.txt", FileKind::Regular, Owner::ROOT)?;
file2.write_all(data2.as_bytes())?;
}
let actual1 = {
let mut file1 = fs.open("file1.txt")?;
let mut actual1 = String::new();
file1.read_to_string(&mut actual1)?;
actual1
};
let actual2 = {
let mut file2 = fs.open("file2.txt")?;
let mut actual2 = String::new();
file2.read_to_string(&mut actual2)?;
actual2
};
expect!(actual1).to(eq_diff(data1));
expect!(actual2).to(eq_diff(data2));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn cdc_incremental_writes_to_max() -> crate::Result<()> {
let min_size = 64;
let avg_size = 256;
let max_size = 1024;
let mut conn = Connection::with_cdc_chunk_sizes(min_size, avg_size, max_size)?;
let write_size = 512;
let mut parts = Vec::new();
for _ in 0..8 {
parts.push(random_string(write_size, Case::Lower));
}
let expected = parts.join("");
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
for part in &parts {
file.write_all(part.as_bytes())?;
}
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
}
mod flush_behavior {
use super::*;
#[test]
fn flush_empty_chunker() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.flush()?;
expect!(file.len()).to(be_ok()).to(equal(0));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn flush_partial_chunk() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(16, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.flush()?;
expect!(file.len()).to(be_ok()).to(equal(16));
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(data));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn multiple_flushes_in_sequence() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.flush()?;
file.flush()?;
file.flush()?;
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(data));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn write_flush_write_flush() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data1 = random_string(10, Case::Lower);
let data2 = random_string(10, Case::Upper);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data1.as_bytes())?;
file.flush()?;
file.write_all(data2.as_bytes())?;
file.flush()?;
expect!(file.len()).to(be_ok()).to(equal(20));
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
let mut expected = String::new();
expected.push_str(&data1);
expected.push_str(&data2);
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn flush_then_seek() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.flush()?;
expect!(file.seek(SeekFrom::Start(50)))
.to(be_ok())
.to(equal(50));
let mut actual = String::new();
file.read_to_string(&mut actual)?;
let expected_part = &data[50..];
expect!(actual).to(eq_diff(expected_part));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn flush_then_read() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.flush()?;
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(data));
crate::Result::Ok(())
})?;
Ok(())
}
}
mod seek_position_edge_cases {
use super::*;
#[test]
fn seek_start_zero() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
expect!(file.seek(SeekFrom::Start(0)))
.to(be_ok())
.to(equal(0));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn seek_start_middle() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
expect!(file.seek(SeekFrom::Start(50)))
.to(be_ok())
.to(equal(50));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn seek_start_beyond_end() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(10, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
expect!(file.seek(SeekFrom::Start(100)))
.to(be_ok())
.to(equal(100));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn seek_end_zero() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
expect!(file.seek(SeekFrom::End(0)))
.to(be_ok())
.to(equal(100));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn seek_end_positive() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
expect!(file.seek(SeekFrom::End(10)))
.to(be_ok())
.to(equal(110));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn seek_end_negative() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
expect!(file.seek(SeekFrom::End(-10)))
.to(be_ok())
.to(equal(90));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn seek_end_negative_before_start() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
expect!(file.seek(SeekFrom::End(-200))).to(be_err());
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn seek_current_zero() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.seek(SeekFrom::Start(50))?;
expect!(file.stream_position()).to(be_ok()).to(equal(50));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn seek_current_positive() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.seek(SeekFrom::Start(50))?;
expect!(file.seek(SeekFrom::Current(20)))
.to(be_ok())
.to(equal(70));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn seek_current_negative() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.seek(SeekFrom::Start(50))?;
expect!(file.seek(SeekFrom::Current(-20)))
.to(be_ok())
.to(equal(30));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn seek_current_to_negative() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.seek(SeekFrom::Start(10))?;
expect!(file.seek(SeekFrom::Current(-20))).to(be_err());
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn seek_returns_new_position() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
expect!(file.seek(SeekFrom::Start(25)))
.to(be_ok())
.to(equal(25));
expect!(file.seek(SeekFrom::End(-15)))
.to(be_ok())
.to(equal(85));
expect!(file.seek(SeekFrom::Current(10)))
.to(be_ok())
.to(equal(95));
crate::Result::Ok(())
})?;
Ok(())
}
}
mod overwrite_vs_append_paths {
use super::*;
#[test]
fn overwrite_beginning_of_file() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let initial = random_string(100, Case::Lower);
let overwrite = random_string(20, Case::Upper);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(initial.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
file.write_all(overwrite.as_bytes())?;
expect!(file.len()).to(be_ok()).to(equal(100));
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
let mut expected = String::new();
expected.push_str(&overwrite);
expected.push_str(&initial[20..]);
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn overwrite_middle_of_file() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let initial = random_string(100, Case::Lower);
let overwrite = random_string(10, Case::Upper);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(initial.as_bytes())?;
file.seek(SeekFrom::Start(50))?;
file.write_all(overwrite.as_bytes())?;
expect!(file.len()).to(be_ok()).to(equal(100));
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
let mut expected = String::new();
expected.push_str(&initial[..50]);
expected.push_str(&overwrite);
expected.push_str(&initial[60..]);
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn overwrite_partial_overlap_end() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let initial = random_string(100, Case::Lower);
let overwrite = random_string(20, Case::Upper);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(initial.as_bytes())?;
file.seek(SeekFrom::Start(90))?;
file.write_all(overwrite.as_bytes())?;
expect!(file.len()).to(be_ok()).to(equal(110));
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
let mut expected = String::new();
expected.push_str(&initial[..90]);
expected.push_str(&overwrite);
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn overwrite_exact_file_length() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let initial = random_string(100, Case::Lower);
let overwrite = random_string(100, Case::Upper);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(initial.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
file.write_all(overwrite.as_bytes())?;
expect!(file.len()).to(be_ok()).to(equal(100));
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(overwrite));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn overwrite_beyond_file_length() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let initial = random_string(50, Case::Lower);
let overwrite = random_string(20, Case::Upper);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(initial.as_bytes())?;
file.seek(SeekFrom::Start(40))?;
file.write_all(overwrite.as_bytes())?;
expect!(file.len()).to(be_ok()).to(equal(60));
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
let mut expected = String::new();
expected.push_str(&initial[..40]);
expected.push_str(&overwrite);
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn append_at_exact_end() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let initial = random_string(50, Case::Lower);
let append = random_string(30, Case::Upper);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(initial.as_bytes())?;
file.write_all(append.as_bytes())?;
expect!(file.len()).to(be_ok()).to(equal(80));
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
let mut expected = String::new();
expected.push_str(&initial);
expected.push_str(&append);
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn append_after_seek_to_end() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let initial = random_string(50, Case::Lower);
let append = random_string(30, Case::Upper);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(initial.as_bytes())?;
file.seek(SeekFrom::Start(25))?;
file.seek(SeekFrom::End(0))?;
file.write_all(append.as_bytes())?;
expect!(file.len()).to(be_ok()).to(equal(80));
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
let mut expected = String::new();
expected.push_str(&initial);
expected.push_str(&append);
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn overwrite_in_middle_preserves_surrounding_data() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let initial = random_string(40, Case::Lower);
let overwrite = random_string(20, Case::Upper);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(initial.as_bytes())?;
file.seek(SeekFrom::Start(10))?;
file.write_all(overwrite.as_bytes())?;
expect!(file.len()).to(be_ok()).to(equal(40));
file.seek(SeekFrom::Start(10))?;
let mut buf = Vec::new();
expect!(file.read_to_end(&mut buf))
.to(be_ok())
.to(equal(30));
let actual = String::from_utf8_lossy(&buf[..overwrite.len()]).to_string();
expect!(actual).to(eq_diff(overwrite));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn sequential_writes_preserve_all_data() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let part1 = random_string(16, Case::Lower);
let part2 = random_string(16, Case::Upper);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(part1.as_bytes())?;
file.write_all(part2.as_bytes())?;
expect!(file.len()).to(be_ok()).to(equal(32));
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
let mut expected = String::new();
expected.push_str(&part1);
expected.push_str(&part2);
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
}
mod sequential_write_patterns {
use super::*;
#[test]
fn many_single_byte_writes() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
for i in 0..1000 {
let byte = (i % 256) as u8;
expect!(file.write(&[byte])).to(be_ok()).to(equal(1));
}
expect!(file.len()).to(be_ok()).to(equal(1000));
file.seek(SeekFrom::Start(0))?;
let mut actual = Vec::new();
file.read_to_end(&mut actual)?;
expect!(actual.len()).to(equal(1000));
for (i, byte) in actual.into_iter().enumerate() {
expect!(byte).to(equal((i % 256) as u8));
}
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn alternating_small_large_writes() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let small = random_string(1, Case::Lower);
let large = random_string(30, Case::Upper);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(small.as_bytes())?;
file.write_all(large.as_bytes())?;
file.write_all(small.as_bytes())?;
file.write_all(large.as_bytes())?;
file.write_all(small.as_bytes())?;
let expected_len = 3 + 2 * 30;
expect!(file.len())
.to(be_ok())
.to(equal(expected_len as u64));
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual.len()).to(equal(expected_len));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn write_exactly_filling_buffer() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let chunk_data = random_string(32, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(chunk_data.as_bytes())?;
file.write_all(chunk_data.as_bytes())?;
file.write_all(chunk_data.as_bytes())?;
let expected_len = 3 * 32;
expect!(file.len())
.to(be_ok())
.to(equal(expected_len as u64));
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual.len()).to(equal(expected_len));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn write_creating_many_chunks() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(512, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
expect!(file.len()).to(be_ok()).to(equal(512));
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(data));
crate::Result::Ok(())
})?;
Ok(())
}
}
mod read_edge_cases {
use super::*;
#[test]
fn read_from_empty_file() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let mut buf = vec![0u8; 100];
expect!(file.read(&mut buf)).to(be_ok()).to(equal(0));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn read_past_end_of_file() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(50, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.seek(SeekFrom::Start(100))?;
let mut buf = vec![0u8; 100];
expect!(file.read(&mut buf)).to(be_ok()).to(equal(0));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn read_exactly_file_length() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(data));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn read_in_small_increments() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut actual = Vec::new();
for _ in 0..100 {
let mut buf = [0u8; 1];
expect!(file.read(&mut buf)).to(be_ok()).to(equal(1));
actual.push(buf[0]);
}
let actual_str = String::from_utf8_lossy(&actual).to_string();
expect!(actual_str).to(eq_diff(data));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn read_spanning_chunk_boundaries() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
loop {
let mut buf = vec![0u8; 40];
let n = expect!(file.read(&mut buf)).to(be_ok()).into_inner();
if n == 0 {
break;
}
actual.push_str(&String::from_utf8_lossy(&buf[..n]));
}
expect!(actual).to(eq_diff(data));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn read_from_sparse_hole() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.set_len(100)?;
file.seek(SeekFrom::Start(0))?;
let mut buf = vec![0u8; 100];
expect!(file.read(&mut buf)).to(be_ok()).to(equal(100));
for byte in &buf {
expect!(*byte).to(equal(0));
}
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn read_after_write_without_seek() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
let mut buf = vec![0u8; 100];
expect!(file.read(&mut buf)).to(be_ok()).to(equal(0));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn read_with_cached_block() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut actual1 = String::new();
file.read_to_string(&mut actual1)?;
expect!(actual1).to(eq_diff(data.clone()));
file.seek(SeekFrom::Start(0))?;
let mut actual2 = String::new();
file.read_to_string(&mut actual2)?;
expect!(actual2).to(eq_diff(data));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn read_invalidates_cache() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data_a = random_string(100, Case::Lower);
let data_b = random_string(100, Case::Upper);
conn.exec(|fs| {
{
let mut file_a = fs.create("file_a.txt", FileKind::Regular, Owner::ROOT)?;
file_a.write_all(data_a.as_bytes())?;
}
{
let mut file_b = fs.create("file_b.txt", FileKind::Regular, Owner::ROOT)?;
file_b.write_all(data_b.as_bytes())?;
}
let actual_a = {
let mut file_a = fs.open("file_a.txt")?;
let mut actual_a = String::new();
file_a.read_to_string(&mut actual_a)?;
actual_a
};
expect!(actual_a).to(eq_diff(data_a));
let actual_b = {
let mut file_b = fs.open("file_b.txt")?;
let mut actual_b = String::new();
file_b.read_to_string(&mut actual_b)?;
actual_b
};
expect!(actual_b).to(eq_diff(data_b));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn read_partial_fill_buffer_larger_than_remaining() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(50, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.seek(SeekFrom::Start(20))?;
let mut buf = Vec::new();
let total_bytes_read = expect!(file.read_to_end(&mut buf)).to(be_ok()).into_inner();
expect!(total_bytes_read).to(equal(30));
let expected_data = &data[20..];
expect!(&buf[..]).to(equal(expected_data.as_bytes()));
crate::Result::Ok(())
})?;
Ok(())
}
}
mod sparse_files_and_set_len {
use super::*;
#[test]
fn set_len_extend_file() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(10, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.set_len(100)?;
expect!(file.len()).to(be_ok()).to(equal(100));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn set_len_truncate_file() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.set_len(10)?;
expect!(file.len()).to(be_ok()).to(equal(10));
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
let expected_part = &data[..10];
expect!(actual).to(eq_diff(expected_part));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn set_len_to_current_length() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(50, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.set_len(50)?;
expect!(file.len()).to(be_ok()).to(equal(50));
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(data));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn write_after_sparse_hole() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(20, Case::Upper);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.set_len(100)?;
file.seek(SeekFrom::Start(100))?;
file.write_all(data.as_bytes())?;
expect!(file.len()).to(be_ok()).to(equal(120));
file.seek(SeekFrom::Start(0))?;
let mut buf = vec![0u8; 100];
expect!(file.read(&mut buf)).to(be_ok()).to(equal(100));
for byte in &buf {
expect!(*byte).to(equal(0));
}
let mut actual = String::new();
file.read_to_string(&mut actual)?;
expect!(actual).to(eq_diff(data));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn read_through_sparse_hole() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let prefix = random_string(10, Case::Lower);
let suffix = random_string(10, Case::Upper);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(prefix.as_bytes())?;
file.set_len(100)?;
file.seek(SeekFrom::Start(100))?;
file.write_all(suffix.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut buf = Vec::new();
expect!(file.read_to_end(&mut buf))
.to(be_ok())
.to(equal(110));
let prefix_actual = String::from_utf8_lossy(&buf[..10]).to_string();
expect!(prefix_actual).to(eq_diff(prefix));
expect!(&buf[10..100]).to(eq_diff(vec![0u8; 90]));
let suffix_actual = String::from_utf8_lossy(&buf[100..110]).to_string();
expect!(suffix_actual).to(eq_diff(suffix));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn seek_within_sparse_hole() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.set_len(100)?;
expect!(file.seek(SeekFrom::Start(50)))
.to(be_ok())
.to(equal(50));
let mut buf = vec![0u8; 10];
expect!(file.read(&mut buf)).to(be_ok()).to(equal(10));
for byte in &buf {
expect!(*byte).to(equal(0));
}
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn set_len_truncate_past_current_position_clamps_position() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.seek(SeekFrom::Start(80))?;
file.set_len(50)?;
let position = expect!(file.stream_position()).to(be_ok()).into_inner();
expect!(position).to(equal(50));
let mut buf = vec![0u8; 10];
expect!(file.read(&mut buf)).to(be_ok()).to(equal(0));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn set_len_zero_on_empty_file_is_noop() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
expect!(file.len()).to(be_ok()).to(equal(0));
file.set_len(0)?;
expect!(file.len()).to(be_ok()).to(equal(0));
let position = expect!(file.stream_position()).to(be_ok()).into_inner();
expect!(position).to(equal(0));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn set_len_truncate_file_with_holes() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let prefix = random_string(10, Case::Lower);
let suffix = random_string(10, Case::Upper);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(prefix.as_bytes())?;
file.set_len(100)?; file.seek(SeekFrom::Start(100))?;
file.write_all(suffix.as_bytes())?;
expect!(file.len()).to(be_ok()).to(equal(110));
file.set_len(50)?;
expect!(file.len()).to(be_ok()).to(equal(50));
file.seek(SeekFrom::Start(0))?;
let mut buf = vec![0u8; 10];
expect!(file.read(&mut buf)).to(be_ok()).to(equal(10));
expect!(&buf[..]).to(equal(prefix.as_bytes()));
let mut hole_buf = vec![0u8; 40];
expect!(file.read(&mut hole_buf)).to(be_ok()).to(equal(40));
for byte in &hole_buf {
expect!(*byte).to(equal(0));
}
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn seek_end_after_truncate_uses_new_file_length() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.set_len(30)?;
let position = expect!(file.seek(SeekFrom::End(0)))
.to(be_ok())
.into_inner();
expect!(position).to(equal(30));
let position = expect!(file.seek(SeekFrom::End(-10)))
.to(be_ok())
.into_inner();
expect!(position).to(equal(20));
crate::Result::Ok(())
})?;
Ok(())
}
}
mod error_scenarios {
use super::*;
#[test]
fn seek_overflow_positive() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.seek(SeekFrom::Start(i64::MAX as u64))?;
file.write_all(b"XXX")?;
expect!(file.seek(SeekFrom::End(i64::MAX))).to(be_err());
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn seek_overflow_negative() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
expect!(file.seek(SeekFrom::End(i64::MIN))).to(be_err());
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn seek_current_overflow() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.seek(SeekFrom::Start(u64::MAX))?;
expect!(file.seek(SeekFrom::Current(1))).to(be_err());
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn seek_current_underflow() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
expect!(file.seek(SeekFrom::Current(i64::MIN))).to(be_err());
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn read_on_directory_returns_not_a_regular_file() -> crate::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("dir", FileKind::Dir, Owner::ROOT)?;
let mut buf = Vec::new();
expect!(file.read(&mut buf))
.to(be_err())
.into::<Error>()
.to(match_pattern(
pattern!(Error::NotARegularFile { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from(file.file_id())),
));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn write_on_directory_returns_not_a_regular_file() -> crate::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("dir", FileKind::Dir, Owner::ROOT)?;
expect!(file.write(b"test"))
.to(be_err())
.into::<Error>()
.to(match_pattern(
pattern!(Error::NotARegularFile { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from(file.file_id())),
));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn seek_on_directory_returns_not_a_regular_file() -> crate::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("dir", FileKind::Dir, Owner::ROOT)?;
expect!(file.seek(SeekFrom::Start(0)))
.to(be_err())
.into::<Error>()
.to(match_pattern(
pattern!(Error::NotARegularFile { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from(file.file_id())),
));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn flush_on_directory_returns_not_a_regular_file() -> crate::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("dir", FileKind::Dir, Owner::ROOT)?;
expect!(file.flush())
.to(be_err())
.into::<Error>()
.to(match_pattern(
pattern!(Error::NotARegularFile { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from(file.file_id())),
));
crate::Result::Ok(())
})?;
Ok(())
}
}
mod cross_trait_integration_scenarios {
use super::*;
#[test]
fn write_seek_write_read() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let initial = random_string(100, Case::Lower);
let overwrite = random_string(20, Case::Upper);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(initial.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
file.write_all(overwrite.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
let mut expected = String::new();
expected.push_str(&overwrite);
expected.push_str(&initial[20..]);
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn interleaved_read_write() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data1 = random_string(50, Case::Lower);
let data2 = random_string(50, Case::Upper);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data1.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut buf1 = Vec::new();
expect!(file.read_to_end(&mut buf1))
.to(be_ok())
.to(equal(50));
let actual1 = String::from_utf8_lossy(&buf1).to_string();
expect!(actual1).to(eq_diff(data1.clone()));
file.seek(SeekFrom::End(0))?;
file.write_all(data2.as_bytes())?;
file.seek(SeekFrom::Start(50))?;
let mut actual2 = String::new();
file.read_to_string(&mut actual2)?;
expect!(actual2).to(eq_diff(data2));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn seek_write_seek_read_multiple() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let chunk1 = random_string(30, Case::Lower);
let chunk2 = random_string(30, Case::Upper);
let chunk3 = random_string(30, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.seek(SeekFrom::Start(0))?;
file.write_all(chunk1.as_bytes())?;
file.seek(SeekFrom::Start(50))?;
file.write_all(chunk2.as_bytes())?;
file.seek(SeekFrom::Start(100))?;
file.write_all(chunk3.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut buf1 = vec![0u8; 30];
expect!(file.read(&mut buf1)).to(be_ok()).to(equal(30));
let actual1 = String::from_utf8_lossy(&buf1).to_string();
expect!(actual1).to(eq_diff(chunk1));
file.seek(SeekFrom::Start(50))?;
let mut buf2 = vec![0u8; 30];
expect!(file.read(&mut buf2)).to(be_ok()).to(equal(30));
let actual2 = String::from_utf8_lossy(&buf2).to_string();
expect!(actual2).to(eq_diff(chunk2));
file.seek(SeekFrom::Start(100))?;
let mut actual3 = String::new();
file.read_to_string(&mut actual3)?;
expect!(actual3).to(eq_diff(chunk3));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn partial_read_seek_back_reread() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let data = random_string(100, Case::Lower);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(data.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut buf1 = Vec::new();
expect!(file.read_to_end(&mut buf1))
.to(be_ok())
.to(equal(100));
let actual1 = String::from_utf8_lossy(&buf1).to_string();
file.seek(SeekFrom::Start(0))?;
let mut buf2 = Vec::new();
expect!(file.read_to_end(&mut buf2))
.to(be_ok())
.to(equal(100));
let actual2 = String::from_utf8_lossy(&buf2).to_string();
expect!(actual1).to(eq_diff(actual2.clone()));
expect!(actual2).to(eq_diff(data));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn write_seek_overwrite_read() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let initial = random_string(100, Case::Lower);
let overwrite = random_string(20, Case::Upper);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(initial.as_bytes())?;
file.seek(SeekFrom::Start(50))?;
file.write_all(overwrite.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
let mut expected = String::new();
expected.push_str(&initial[..50]);
expected.push_str(&overwrite);
expected.push_str(&initial[70..]);
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn append_then_overwrite_same_data() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
let initial = random_string(100, Case::Lower);
let overwrite = random_string(20, Case::Upper);
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(initial.as_bytes())?;
file.seek(SeekFrom::Start(40))?;
file.write_all(overwrite.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
let mut actual = String::new();
file.read_to_string(&mut actual)?;
let mut expected = String::new();
expected.push_str(&initial[..40]);
expected.push_str(&overwrite);
expected.push_str(&initial[60..]);
expect!(actual).to(eq_diff(expected));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn rechunk_then_set_len_extend_then_span_write_keeps_contiguous_indices() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32 * 1024)?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let initial = crate::testing::random_buf(262_144, Case::Lower); file.write_all(&initial)?;
file.flush()?;
expect!(file.len()).to(be_ok()).to(equal(262_144));
file.set_len(109_108)?;
expect!(file.len()).to(be_ok()).to(equal(109_108));
let middle = crate::testing::random_buf(56_760, Case::Upper);
file.seek(SeekFrom::Start(6_996))?;
file.write_all(&middle)?;
file.flush()?;
expect!(file.len()).to(be_ok()).to(equal(109_108));
file.set_len(215_928)?;
expect!(file.len()).to(be_ok()).to(equal(215_928));
let spanning = crate::testing::random_buf(62_492, Case::Upper);
file.seek(SeekFrom::Start(65_468))?;
file.write_all(&spanning)?;
file.flush()?;
expect!(file.len()).to(be_ok()).to(equal(215_928));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn write_spanning_data_into_set_len_trailing_hole_preserves_size() -> crate::Result<()> {
let mut conn = Connection::with_fixed_chunk_size(32)?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let data = random_string(100, Case::Lower);
file.write_all(data.as_bytes())?;
file.flush()?;
expect!(file.len()).to(be_ok()).to(equal(100));
file.set_len(200)?;
expect!(file.len()).to(be_ok()).to(equal(200));
let overwrite = random_string(50, Case::Upper);
file.seek(SeekFrom::Start(80))?;
file.write_all(overwrite.as_bytes())?;
file.flush()?;
expect!(file.len()).to(be_ok()).to(equal(200));
crate::Result::Ok(())
})?;
Ok(())
}
}