mod common;
use brushkit_preview::{
preview_abr, preview_brush, preview_brushset, PreviewOptions, SourceDimensions, TipPreview,
UnavailableReason,
};
use common::{brush_archive, brushset_plist, corpus_files, dimension_bomb_png, gray_png, zip_with};
use std::path::Path;
#[test]
fn dimensions_require_two_positive_sides() {
assert_eq!(SourceDimensions::new(0, 1), None);
assert_eq!(SourceDimensions::new(1, 0), None);
let dimensions = SourceDimensions::new(16, 8).unwrap();
assert_eq!((dimensions.width(), dimensions.height()), (16, 8));
}
#[test]
fn sampled_abr_dimensions_survive_downsampling() {
let bytes = include_bytes!("../../../fuzz/corpus/preview_abr/sampled_tip");
for max_cell in [1, 4, 64] {
let set = preview_abr(bytes, PreviewOptions { max_cell }).unwrap();
assert_eq!(set.entries.len(), 1);
assert_eq!(
set.entries[0].source_dimensions,
SourceDimensions::new(16, 8)
);
assert!(matches!(set.entries[0].tip, TipPreview::Available(_)));
}
}
#[test]
fn sampled_dimensions_survive_decode_failure() {
let mut bytes = include_bytes!("../../../fuzz/corpus/preview_abr/sampled_tip").to_vec();
bytes[42] = 1; let set = preview_abr(&bytes, PreviewOptions { max_cell: 8 }).unwrap();
assert_eq!(set.entries.len(), 1);
assert_eq!(
set.entries[0].source_dimensions,
SourceDimensions::new(16, 8)
);
assert!(matches!(
set.entries[0].tip,
TipPreview::Unavailable(UnavailableReason::Corrupt(_))
));
}
#[test]
fn procreate_dimensions_follow_members_and_survive_pixel_failures() {
let archive = brush_archive("Same name");
let shape = gray_png(64, 16, 200);
let idat = shape.windows(4).position(|bytes| bytes == b"IDAT").unwrap();
let corrupt_pixels = &shape[..idat + 4];
let plist = brushset_plist(
"Sizes",
&[
"missing", "valid", "pixels", "header", "large", "archive", "nothing",
],
);
let bytes = zip_with(&[
("brushset.plist", &plist),
("valid/Brush.archive", &archive),
("valid/Shape.png", &shape),
("missing/Brush.archive", &archive),
("pixels/Brush.archive", &archive),
("pixels/Shape.png", corrupt_pixels),
("header/Brush.archive", &archive),
("header/Shape.png", b"bad header"),
("large/Brush.archive", &archive),
("large/Shape.png", &dimension_bomb_png(60000, 30000)),
("archive/Brush.archive", b"bad archive"),
("archive/Shape.png", &shape),
("nothing/Brush.archive", b"bad archive"),
]);
for max_cell in [1, 8, 128] {
let set = preview_brushset(&bytes, PreviewOptions { max_cell }).unwrap();
assert_eq!(
set.entries
.iter()
.map(|entry| entry.index)
.collect::<Vec<_>>(),
vec![0, 1, 2, 3, 4, 5, 6]
);
assert_eq!(
set.entries
.iter()
.map(|entry| entry.source_dimensions)
.collect::<Vec<_>>(),
vec![
None,
SourceDimensions::new(64, 16),
SourceDimensions::new(64, 16),
None,
SourceDimensions::new(60000, 30000),
SourceDimensions::new(64, 16),
None
]
);
assert!(matches!(
set.entries[0].tip,
TipPreview::Unavailable(UnavailableReason::NoShapePng)
));
assert!(matches!(set.entries[1].tip, TipPreview::Available(_)));
assert!(matches!(
set.entries[2].tip,
TipPreview::Unavailable(UnavailableReason::Corrupt(_))
));
assert!(matches!(
set.entries[4].tip,
TipPreview::Unavailable(UnavailableReason::TooLarge {
width: 60000,
height: 30000
})
));
for archive_error in &set.entries[5..] {
assert!(matches!(
archive_error.tip,
TipPreview::Unavailable(UnavailableReason::Corrupt(_))
));
}
}
}
#[test]
fn computed_and_unsupported_previews_have_no_source_raster() {
let mut descriptor = vec![0, 0, 0, 16, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0];
descriptor.extend_from_slice(b"null\0\0\0\x01\0\0\0\0BrshVlLs\0\0\0\x01Objc\0\0\0\x01\0\0\0\0\0\x0bbrushPreset\0\0\0\x01\0\0\0\0BrshObjc\0\0\0\x01\0\0\0\0\0\x0dcomputedBrush\0\0\0\x01\0\0\0\0DmtrUntF#Pxl");
descriptor.extend_from_slice(&30f64.to_be_bytes());
let mut bytes = vec![0, 6, 0, 2];
bytes.extend_from_slice(b"8BIMdesc");
bytes.extend_from_slice(&(descriptor.len() as u32).to_be_bytes());
bytes.extend_from_slice(&descriptor);
bytes.resize(bytes.len().next_multiple_of(4), 0);
let set = preview_abr(&bytes, PreviewOptions { max_cell: 8 }).unwrap();
assert_eq!(set.entries.len(), 1);
assert_eq!(set.entries[0].source_dimensions, None);
assert!(matches!(set.entries[0].tip, TipPreview::Available(_)));
let class = bytes
.windows(13)
.position(|value| value == b"computedBrush")
.unwrap();
bytes[class..class + 13].copy_from_slice(b"computedBrusX");
let set = preview_abr(&bytes, PreviewOptions { max_cell: 8 }).unwrap();
assert_eq!(set.entries.len(), 1);
assert_eq!(set.entries[0].source_dimensions, None);
assert!(matches!(
set.entries[0].tip,
TipPreview::Unavailable(UnavailableReason::UnsupportedTipKind(_))
));
}
#[test]
fn real_file_dimensions_are_independent_of_cell_size() {
let Some(root) = std::env::var_os("BRUSHKIT_CORPUS_DIR") else {
return;
};
let mut files = Vec::new();
corpus_files(Path::new(&root), &mut files);
assert!(
!files.is_empty(),
"configured corpus must contain brush files"
);
let mut known = 0;
for path in files {
let bytes = std::fs::read(&path).unwrap();
let reader = match path
.extension()
.unwrap()
.to_str()
.unwrap()
.to_lowercase()
.as_str()
{
"abr" => preview_abr,
"brush" => preview_brush,
_ => preview_brushset,
};
let large = match reader(&bytes, PreviewOptions { max_cell: 160 }) {
Ok(set) => set,
Err(error) => {
println!("skipped {}: {error}", path.display());
continue;
}
};
let small = reader(&bytes, PreviewOptions { max_cell: 8 })
.unwrap_or_else(|error| panic!("{}: {error}", path.display()));
assert_eq!(small.entries.len(), large.entries.len());
for (small, large) in small.entries.iter().zip(&large.entries) {
assert_eq!(small.name, large.name);
assert_eq!(small.index, large.index);
assert_eq!(small.source_dimensions, large.source_dimensions);
if large.source_dimensions.is_some() {
known += 1;
}
for (entry, max_cell) in [(small, 8), (large, 160)] {
let (Some(dimensions), TipPreview::Available(bitmap)) =
(entry.source_dimensions, &entry.tip)
else {
continue;
};
let source = (dimensions.width(), dimensions.height());
let preview = (bitmap.width, bitmap.height);
if source.0 <= max_cell && source.1 <= max_cell {
assert_eq!(source, preview, "{} #{}", path.display(), entry.index);
} else {
assert_eq!(
preview.0.max(preview.1),
max_cell,
"{} #{}",
path.display(),
entry.index
);
assert!(source.0 >= preview.0 && source.1 >= preview.1);
}
}
}
println!(
"checked {} entries in {}",
large.entries.len(),
path.display()
);
}
assert!(known > 0, "corpus must exercise known raster dimensions");
}