use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
use memmap2::Mmap;
use rucc_diag::SourceBytes;
pub(crate) const MAP_THRESHOLD: u64 = 2 * 1024 * 1024;
pub(crate) fn read(path: &Path) -> io::Result<SourceBytes> {
let file = File::open(path)?;
let meta = file.metadata()?;
if meta.is_file() && meta.len() >= MAP_THRESHOLD {
if let Ok(map) = unsafe { Mmap::map(&file) } {
return Ok(SourceBytes::new(map));
}
}
slurp(&file, meta.len())
}
fn slurp(mut file: &File, hint: u64) -> io::Result<SourceBytes> {
let hint = usize::try_from(hint).unwrap_or(0);
let mut bytes = Vec::with_capacity(hint);
file.read_to_end(&mut bytes)?;
Ok(SourceBytes::new(bytes))
}
#[cfg(test)]
mod tests {
use std::io::Write;
use std::path::PathBuf;
use super::*;
struct TempFile(PathBuf);
impl TempFile {
fn new(name: &str, contents: &[u8]) -> TempFile {
let path = std::env::temp_dir().join(format!("rucc-map-{}-{name}", std::process::id()));
let mut file = File::create(&path).expect("temporary directory should be writable");
file.write_all(contents).expect("writing a temporary file should work");
TempFile(path)
}
}
impl Drop for TempFile {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.0);
}
}
#[test]
fn a_small_file_arrives_whole() {
let file = TempFile::new("small", b"int main(void) { return 0; }\n");
let bytes = read(&file.0).expect("should read");
assert_eq!(bytes.as_slice(), b"int main(void) { return 0; }\n");
}
#[test]
fn an_empty_file_is_empty_rather_than_an_error() {
let file = TempFile::new("empty", b"");
let bytes = read(&file.0).expect("should read");
assert!(bytes.as_slice().is_empty());
}
#[test]
fn a_file_over_the_threshold_arrives_whole_too() {
let big: Vec<u8> = (0..MAP_THRESHOLD as usize + 4321).map(|i| (i % 251) as u8).collect();
let file = TempFile::new("big", &big);
let bytes = read(&file.0).expect("should read");
assert_eq!(bytes.as_slice().len(), big.len());
assert_eq!(bytes.as_slice(), &big[..]);
}
#[test]
fn a_file_that_is_not_there_says_so() {
let path = std::env::temp_dir().join("rucc-map-no-such-file-at-all");
let error = read(&path).expect_err("should not be there");
assert_eq!(error.kind(), io::ErrorKind::NotFound);
}
}