use cuttlefish_abi::MediaKind;
use cuttlefish_host::{documents, handles::Handles};
use std::path::PathBuf;
fn write(dir: &tempfile::TempDir, name: &str, bytes: &[u8]) -> PathBuf {
let path = dir.path().join(name);
std::fs::write(&path, bytes).unwrap();
path
}
fn png_bytes() -> Vec<u8> {
fn chunk(tag: &[u8], data: &[u8]) -> Vec<u8> {
let mut out = (data.len() as u32).to_be_bytes().to_vec();
let body: Vec<u8> = tag.iter().chain(data).copied().collect();
out.extend(&body);
out.extend(crc32(&body).to_be_bytes());
out
}
fn crc32(data: &[u8]) -> u32 {
let mut crc = 0xFFFF_FFFFu32;
for byte in data {
crc ^= *byte as u32;
for _ in 0..8 {
crc = if crc & 1 != 0 {
(crc >> 1) ^ 0xEDB8_8320
} else {
crc >> 1
};
}
}
!crc
}
let mut png = b"\x89PNG\r\n\x1a\n".to_vec();
let mut ihdr = 8u32.to_be_bytes().to_vec();
ihdr.extend(8u32.to_be_bytes());
ihdr.extend([8, 2, 0, 0, 0]);
png.extend(chunk(b"IHDR", &ihdr));
png.extend(chunk(
b"IDAT",
&[0x78, 0x9c, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01],
));
png.extend(chunk(b"IEND", &[]));
png
}
#[test]
fn text_is_recognised_as_text() {
let dir = tempfile::tempdir().unwrap();
let path = write(&dir, "a.txt", b"hello world");
let (_h, _len, kind) = Handles::default().open(&path).unwrap();
assert_eq!(kind, MediaKind::Text);
}
#[test]
fn an_empty_file_is_text_not_binary() {
let dir = tempfile::tempdir().unwrap();
let path = write(&dir, "empty.txt", b"");
let (_h, len, kind) = Handles::default().open(&path).unwrap();
assert_eq!(len, 0);
assert_eq!(kind, MediaKind::Text);
}
#[test]
fn utf8_that_spans_the_sniff_window_is_still_text() {
let dir = tempfile::tempdir().unwrap();
let mut content = "é".repeat(3000).into_bytes();
content.truncate(4097); let path = write(&dir, "wide.txt", &content);
let (_h, _len, kind) = Handles::default().open(&path).unwrap();
assert_eq!(kind, MediaKind::Text);
}
#[test]
fn a_png_is_recognised_by_content_not_extension() {
let dir = tempfile::tempdir().unwrap();
let path = write(&dir, "actually-an-image.txt", &png_bytes());
let (_h, _len, kind) = Handles::default().open(&path).unwrap();
assert_eq!(
kind,
MediaKind::Image {
format: "png".into()
}
);
}
#[test]
fn a_jpeg_is_recognised() {
let dir = tempfile::tempdir().unwrap();
let path = write(&dir, "photo.jpg", &[0xFF, 0xD8, 0xFF, 0xE0, 0, 0, 0, 0]);
let (_h, _len, kind) = Handles::default().open(&path).unwrap();
assert_eq!(
kind,
MediaKind::Image {
format: "jpeg".into()
}
);
}
#[test]
fn arbitrary_binary_is_recognised_as_binary() {
let dir = tempfile::tempdir().unwrap();
let path = write(&dir, "blob.bin", &[0x00, 0xFF, 0xFE, 0x01, 0x80, 0x7F]);
let (_h, _len, kind) = Handles::default().open(&path).unwrap();
assert_eq!(kind, MediaKind::Binary);
}
#[test]
fn byte_slices_do_not_truncate_at_character_boundaries() {
let dir = tempfile::tempdir().unwrap();
let path = write(&dir, "m.txt", "aéb".as_bytes());
let mut handles = Handles::default();
let (h, _len, _) = handles.open(&path).unwrap();
let (bytes, next) = handles.slice_bytes(h, 0, 2).unwrap();
assert_eq!(
bytes,
vec![b'a', 0xC3],
"must return the raw bytes asked for"
);
assert_eq!(next, 2);
}
#[test]
fn byte_slices_are_clamped_to_the_end_of_the_file() {
let dir = tempfile::tempdir().unwrap();
let path = write(&dir, "s.bin", &[1, 2, 3]);
let mut handles = Handles::default();
let (h, _len, _) = handles.open(&path).unwrap();
let (bytes, next) = handles.slice_bytes(h, 0, 999).unwrap();
assert_eq!(bytes, vec![1, 2, 3]);
assert_eq!(next, 3);
}
#[test]
fn reading_a_whole_handle_returns_every_byte() {
let dir = tempfile::tempdir().unwrap();
let png = png_bytes();
let path = write(&dir, "i.png", &png);
let mut handles = Handles::default();
let (h, _len, _) = handles.open(&path).unwrap();
assert_eq!(handles.read_all(h).unwrap(), png);
}
#[test]
fn host_produced_bytes_become_a_handle_like_any_other() {
let mut handles = Handles::default();
let (h, len) = handles.insert_bytes(
vec![1, 2, 3, 4],
MediaKind::Image {
format: "png".into(),
},
);
assert_eq!(len, 4);
assert_eq!(handles.read_all(h).unwrap(), vec![1, 2, 3, 4]);
assert_eq!(
handles.kind(h).unwrap(),
MediaKind::Image {
format: "png".into()
}
);
let (window, _) = handles.slice_bytes(h, 1, 2).unwrap();
assert_eq!(window, vec![2, 3]);
}
#[test]
fn an_unknown_handle_has_no_kind() {
assert!(Handles::default().kind(42).is_err());
}
fn sample_pdf() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../examples/docs/sample.pdf")
}
#[test]
fn a_pdf_is_recognised_and_its_pages_counted() {
let (_h, _len, kind) = Handles::default().open(&sample_pdf()).unwrap();
assert!(matches!(kind, MediaKind::Document { .. }), "got {kind:?}");
}
#[test]
fn inspecting_a_pdf_reports_pages_and_a_text_layer() {
let info = documents::inspect(&sample_pdf()).expect("the sample PDF should inspect");
assert_eq!(info.pages, 1);
assert!(
info.has_text_layer,
"the sample PDF has real text and must be reported as such"
);
}
#[test]
fn extracting_document_text_returns_the_documents_words() {
let text = documents::document_text(&sample_pdf()).expect("the document should extract");
assert!(
text.to_lowercase().contains("cuttlefish"),
"expected the document's text, got: {text:?}"
);
}
#[test]
fn a_page_past_the_end_is_refused_and_names_both_counts() {
let text = documents::document_text(&sample_pdf()).unwrap();
let err = documents::page_text_from(&text, 99, 1).unwrap_err();
let message = err.to_string();
assert!(message.contains("out of range"), "{message}");
assert!(
message.contains("addressable text segment"),
"the count that would have told the caller what to do: {message}"
);
}
#[test]
#[cfg(not(feature = "pdf-render"))]
fn rendering_without_the_feature_explains_itself() {
let err = documents::render_page(&sample_pdf(), 0, 512).unwrap_err();
let msg = err.to_string();
assert!(msg.contains("pdf-render"), "{msg}");
}
#[test]
#[cfg(feature = "pdf-render")]
fn rendering_produces_a_png() {
std::env::set_var(
cuttlefish_host::render_worker::WORKER_EXE_ENV,
env!("CARGO_BIN_EXE_cuttlefish-render-worker"),
);
let png = documents::render_page(&sample_pdf(), 0, 256).expect("page 0 should render");
assert!(png.starts_with(&[0x89, b'P', b'N', b'G']), "not a PNG");
}
fn scanned_pdf() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../examples/docs/scanned.pdf")
}
#[test]
fn a_scanned_pdf_reports_no_text_layer() {
let info = documents::inspect(&scanned_pdf()).expect("the scanned PDF should inspect");
assert_eq!(info.pages, 1);
assert!(
!info.has_text_layer,
"an image-only PDF must not claim a text layer"
);
}
#[test]
#[cfg(feature = "pdf-render")]
fn a_renderer_crash_does_not_take_the_process_with_it() {
std::env::set_var(
cuttlefish_host::render_worker::WORKER_EXE_ENV,
env!("CARGO_BIN_EXE_cuttlefish-render-worker"),
);
let result = documents::render_page(&scanned_pdf(), 0, 256);
match result {
Err(e) => {
let msg = e.to_string();
assert!(
msg.contains("crashed") || msg.contains("renderer"),
"a crash should be reported as one: {msg}"
);
}
Ok(png) => assert!(png.starts_with(&[0x89, b'P', b'N', b'G'])),
}
}
#[test]
#[cfg(feature = "pdf-render")]
#[ignore = "superseded by a_renderer_crash_does_not_take_the_process_with_it"]
fn a_scanned_page_renders_even_though_it_has_no_text() {
let png = documents::render_page(&scanned_pdf(), 0, 256).expect("a scan should render");
assert!(png.starts_with(&[0x89, b'P', b'N', b'G']), "not a PNG");
assert!(
png.len() > 100,
"suspiciously small render: {} bytes",
png.len()
);
}