molframe-core 0.1.0

Core types, storage and analysis contracts for molframe
Documentation
use super::*;
use std::cell::Cell;
use std::io::{self, Write};
use std::rc::Rc;

const BYTES: &[u8] = b"data_test\n_entry.id test\n#\n";

struct CountingWriter {
    bytes: Rc<Cell<usize>>,
    writes: Rc<Cell<usize>>,
}

impl Write for CountingWriter {
    fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
        self.bytes.set(self.bytes.get() + bytes.len());
        self.writes.set(self.writes.get() + 1);
        Ok(bytes.len())
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

fn output_path(name: &str) -> std::path::PathBuf {
    std::env::temp_dir().join(format!("molframe-output-{}-{name}", std::process::id()))
}

fn write_and_read(path: &Path) -> Vec<u8> {
    if let Err(error) = write_output(path, BYTES) {
        panic!("write failed: {error}")
    }
    let actual = match std::fs::read(path) {
        Ok(actual) => actual,
        Err(error) => panic!("read failed: {error}"),
    };
    if let Err(error) = std::fs::remove_file(path) {
        panic!("cleanup failed: {error}")
    }
    actual
}

#[test]
fn plain_output_writes_exactly_the_supplied_bytes() {
    let actual = write_and_read(&output_path("plain.cif"));
    assert_eq!(actual, BYTES);
}

#[test]
fn the_default_is_explicit_and_a_zero_limit_is_refused() {
    assert_eq!(
        OutputOptions::default().memory_limit_bytes,
        DEFAULT_OUTPUT_MEMORY_LIMIT_BYTES
    );
    let path = output_path("invalid.cif");
    let options = OutputOptions::default().with_memory_limit(0);
    assert!(OutputSink::create(&path, options).is_err());
    assert!(
        !path.exists(),
        "invalid policy must refuse before file creation"
    );
}

#[test]
fn a_limit_larger_than_the_default_is_accepted() {
    let path = output_path("large-limit.cif");
    let options =
        OutputOptions::default().with_memory_limit(DEFAULT_OUTPUT_MEMORY_LIMIT_BYTES * 100);
    assert!(OutputSink::create(&path, options).is_ok());
}

#[test]
fn bounded_sink_forwards_chunks_before_finish() {
    let written_bytes = Rc::new(Cell::new(0));
    let write_calls = Rc::new(Cell::new(0));
    let writer = CountingWriter {
        bytes: Rc::clone(&written_bytes),
        writes: Rc::clone(&write_calls),
    };
    let options = OutputOptions::default().with_memory_limit(1_024);
    let mut output =
        match OutputSink::from_writer(writer, OutputCompression::None, options, "counter".into()) {
            Ok(output) => output,
            Err(error) => panic!("sink construction failed: {error}"),
        };
    assert_eq!(output.buffer_capacity(), Some(1_024));
    for _ in 0..3 {
        if let Err(error) = output.write_all(&[7; 1_024]) {
            panic!("stream write failed: {error}");
        }
    }
    assert!(write_calls.get() > 0, "buffer must drain before finish");
    assert!(
        written_bytes.get() > 0,
        "bytes must reach the destination early"
    );
    if let Err(error) = output.finish() {
        panic!("stream finish failed: {error}");
    }
    assert_eq!(written_bytes.get(), 3_072);
}

#[cfg(feature = "gzip")]
#[test]
fn gzip_output_is_byte_deterministic_and_round_trips() {
    use std::io::Read;

    let first = write_and_read(&output_path("first.cif.gz"));
    let second = write_and_read(&output_path("second.cif.gz"));
    assert_eq!(first, second);

    let mut round_tripped = Vec::new();
    let mut decoder = flate2::read::GzDecoder::new(first.as_slice());
    if let Err(error) = decoder.read_to_end(&mut round_tripped) {
        panic!("gzip decode failed: {error}")
    }
    assert_eq!(round_tripped, BYTES);
}

#[cfg(feature = "zstd")]
#[test]
fn zstd_output_is_byte_deterministic_and_round_trips() {
    let first = write_and_read(&output_path("first.cif.zst"));
    let second = write_and_read(&output_path("second.cif.zst"));
    assert_eq!(first, second);

    let decoded = match zstd::stream::decode_all(first.as_slice()) {
        Ok(decoded) => decoded,
        Err(error) => panic!("zstandard decode failed: {error}"),
    };
    assert_eq!(decoded, BYTES);
}