use std::path::Path;
use snafu::{ResultExt, Snafu};
use crate::{
coverage::layout::CoverageLayoutError,
coverage::{
Coverage,
serde::{CoverageSerdeError, coverage_from_bytes, coverage_to_bytes},
},
storage::{self, StorageError, TableLocation},
};
#[derive(Debug, Snafu)]
pub enum CoverageError {
#[snafu(display("{source}"))]
Layout {
source: CoverageLayoutError,
},
#[snafu(display("{source}"))]
Serde {
source: CoverageSerdeError,
},
#[snafu(display("Coverage sidecar not found: {path}"))]
NotFound {
path: String,
},
#[snafu(display("Storage error while reading/writing coverage sidecar: {source}"))]
Storage {
#[snafu(source, backtrace)]
source: StorageError,
},
}
pub async fn write_coverage_sidecar_atomic(
location: &TableLocation,
rel_path: &Path,
cov: &Coverage,
) -> Result<(), CoverageError> {
let bytes = coverage_to_bytes(cov).context(SerdeSnafu)?;
storage::write_atomic(location.as_ref(), rel_path, &bytes)
.await
.context(StorageSnafu)?;
Ok(())
}
pub async fn write_coverage_sidecar_new(
location: &TableLocation,
rel_path: &Path,
cov: &Coverage,
) -> Result<(), CoverageError> {
let bytes = coverage_to_bytes(cov).context(SerdeSnafu)?;
storage::write_new(location.as_ref(), rel_path, &bytes)
.await
.context(StorageSnafu)?;
Ok(())
}
pub async fn write_coverage_sidecar_new_bytes(
location: &TableLocation,
rel_path: &Path,
bytes: &[u8],
) -> Result<(), CoverageError> {
storage::write_new(location.as_ref(), rel_path, bytes)
.await
.context(StorageSnafu)?;
Ok(())
}
pub async fn read_coverage_sidecar(
location: &TableLocation,
rel_path: &Path,
) -> Result<Coverage, CoverageError> {
match storage::read_all_bytes(location.as_ref(), rel_path).await {
Ok(bytes) => coverage_from_bytes(&bytes).context(SerdeSnafu),
Err(StorageError::NotFound { path, .. }) => Err(CoverageError::NotFound { path }),
Err(e) => Err(CoverageError::Storage { source: e }),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{coverage::serde::coverage_from_bytes, storage::StorageLocation};
use tempfile::TempDir;
fn temp_location() -> (TempDir, TableLocation) {
let tmp = TempDir::new().expect("tempdir");
let loc = TableLocation::local(tmp.path());
(tmp, loc)
}
#[tokio::test]
async fn write_atomic_overwrites_existing() {
let (_tmp, loc) = temp_location();
let rel = Path::new("_coverage/table/1.roar");
let cov1 = Coverage::from_iter(vec![1u32, 2, 3]);
write_coverage_sidecar_atomic(&loc, rel, &cov1)
.await
.expect("first write");
let cov2 = Coverage::from_iter(vec![10u32, 11]);
write_coverage_sidecar_atomic(&loc, rel, &cov2)
.await
.expect("overwrite");
let abs = match &loc.as_ref() {
StorageLocation::Local(root) => root.join(rel),
};
let bytes = std::fs::read(abs).expect("read file");
let restored = coverage_from_bytes(&bytes).expect("deserialize");
assert_eq!(cov2.present(), restored.present());
}
#[tokio::test]
async fn write_new_fails_if_exists() {
let (_tmp, loc) = temp_location();
let rel = Path::new("_coverage/segments/seg-1.roar");
let cov = Coverage::from_iter(vec![5u32]);
write_coverage_sidecar_new(&loc, rel, &cov)
.await
.expect("first write");
let err = write_coverage_sidecar_new(&loc, rel, &cov)
.await
.expect_err("second write should fail");
match err {
CoverageError::Storage {
source: StorageError::AlreadyExists { .. },
..
} => {}
_ => panic!("expected AlreadyExists storage error"),
}
}
#[tokio::test]
async fn read_sidecar_round_trip() {
let (_tmp, loc) = temp_location();
let rel = Path::new("_coverage/table/2.roar");
let cov = Coverage::from_iter(vec![1u32, 3, 5, 7]);
write_coverage_sidecar_atomic(&loc, rel, &cov)
.await
.expect("write sidecar");
let restored = read_coverage_sidecar(&loc, rel)
.await
.expect("read sidecar");
assert_eq!(cov.present(), restored.present());
}
#[tokio::test]
async fn read_sidecar_missing_returns_not_found() {
let (_tmp, loc) = temp_location();
let rel = Path::new("_coverage/table/missing.roar");
let err = read_coverage_sidecar(&loc, rel)
.await
.expect_err("should be missing");
match err {
CoverageError::NotFound { path } => {
assert!(path.contains("missing.roar"));
}
_ => panic!("expected NotFound error"),
}
}
#[tokio::test]
async fn read_sidecar_corrupt_bytes_returns_serde_error() {
let (tmp, loc) = temp_location();
let rel = Path::new("_coverage/table/corrupt.roar");
let abs = match &loc.as_ref() {
StorageLocation::Local(root) => root.join(rel),
};
std::fs::create_dir_all(abs.parent().unwrap()).expect("create dirs");
std::fs::write(&abs, b"not a bitmap").expect("write corrupt");
let err = read_coverage_sidecar(&loc, rel)
.await
.expect_err("should fail to deserialize");
match err {
CoverageError::Serde { .. } => {}
_ => panic!("expected Serde error"),
}
drop(tmp); }
}