Refactor code structure for improved readability and maintainability
mandatory-regression-gate / deterministic-tests (push) Has been cancelled
mandatory-regression-gate / protected-performance-path (push) Has been cancelled

This commit is contained in:
Your Name
2026-07-23 15:34:05 +03:00
parent 593522e83f
commit 8447b2d796
21 changed files with 3439 additions and 4 deletions
+277
View File
@@ -0,0 +1,277 @@
use hcie_filter::apply_filter;
/// Value-verification tests for hcie-filter.
///
/// Upgrades existing "does not panic" smoke tests to actual
/// correctness assertions with known input → known output checks.
// ---------------------------------------------------------------------------
// Helper: create a small test layer with known pixel data
// ---------------------------------------------------------------------------
fn make_gradient_layer() -> hcie_protocol::Layer {
let mut layer = hcie_protocol::Layer::new_blank("gradient", 4, 4);
for y in 0..4 {
for x in 0..4 {
layer.set_pixel(x, y, [x as u8 * 64, y as u8 * 64, 128, 255]);
}
}
layer
}
fn make_solid_layer(r: u8, g: u8, b: u8, a: u8) -> hcie_protocol::Layer {
let mut layer = hcie_protocol::Layer::new_blank("solid", 4, 4);
for y in 0..4 {
for x in 0..4 {
layer.set_pixel(x, y, [r, g, b, a]);
}
}
layer
}
fn make_1x1_layer(r: u8, g: u8, b: u8, a: u8) -> hcie_protocol::Layer {
let mut layer = hcie_protocol::Layer::new_blank("tiny", 1, 1);
layer.set_pixel(0, 0, [r, g, b, a]);
layer
}
// ---------------------------------------------------------------------------
// Invert filter — exact value verification
// ---------------------------------------------------------------------------
#[test]
fn invert_white_becomes_black() {
let mut layer = make_solid_layer(255, 255, 255, 255);
apply_filter(&mut layer, "invert", &serde_json::json!({}));
let pixel = layer.get_pixel(0, 0);
assert_eq!(pixel[0], 0, "invert of white R should be 0");
assert_eq!(pixel[1], 0, "invert of white G should be 0");
assert_eq!(pixel[2], 0, "invert of white B should be 0");
// Alpha typically unchanged
assert_eq!(pixel[3], 255, "invert should leave alpha unchanged");
}
#[test]
fn invert_black_becomes_white() {
let mut layer = make_solid_layer(0, 0, 0, 255);
apply_filter(&mut layer, "invert", &serde_json::json!({}));
let pixel = layer.get_pixel(0, 0);
assert_eq!(pixel[0], 255);
assert_eq!(pixel[1], 255);
assert_eq!(pixel[2], 255);
}
#[test]
fn invert_red_becomes_cyan() {
let mut layer = make_solid_layer(255, 0, 0, 255);
apply_filter(&mut layer, "invert", &serde_json::json!({}));
let pixel = layer.get_pixel(0, 0);
assert_eq!(pixel[0], 0, "invert red: R should be 0");
assert_eq!(pixel[1], 255, "invert red: G should be 255");
assert_eq!(pixel[2], 255, "invert red: B should be 255");
}
#[test]
fn invert_preserves_transparency() {
let mut layer = make_solid_layer(100, 150, 200, 0);
apply_filter(&mut layer, "invert", &serde_json::json!({}));
let pixel = layer.get_pixel(0, 0);
assert_eq!(pixel[3], 0, "invert should preserve zero alpha");
}
#[test]
fn invert_invert_is_identity() {
let original = make_gradient_layer();
let mut layer = make_gradient_layer();
apply_filter(&mut layer, "invert", &serde_json::json!({}));
apply_filter(&mut layer, "invert", &serde_json::json!({}));
for y in 0..4 {
for x in 0..4 {
assert_eq!(
layer.get_pixel(x, y),
original.get_pixel(x, y),
"double invert should restore original at ({},{})", x, y
);
}
}
}
// ---------------------------------------------------------------------------
// Grayscale filter — value verification
// ---------------------------------------------------------------------------
#[test]
fn grayscale_makes_rgb_equal() {
let mut layer = make_gradient_layer();
apply_filter(&mut layer, "grayscale", &serde_json::json!({}));
for y in 0..4 {
for x in 0..4 {
let p = layer.get_pixel(x, y);
let diff_rg = (p[0] as i16 - p[1] as i16).abs();
let diff_rb = (p[0] as i16 - p[2] as i16).abs();
let diff_gb = (p[1] as i16 - p[2] as i16).abs();
// Allow small tolerance for integer rounding
assert!(
diff_rg <= 3 && diff_rb <= 3 && diff_gb <= 3,
"grayscale should produce R≈G≈B at ({},{}): got {:?}",
x, y, p
);
}
}
}
#[test]
fn grayscale_identity_on_gray_input() {
let mut layer = make_solid_layer(128, 128, 128, 255);
apply_filter(&mut layer, "grayscale", &serde_json::json!({}));
let pixel = layer.get_pixel(0, 0);
assert_eq!(pixel[0], 128);
assert_eq!(pixel[1], 128);
assert_eq!(pixel[2], 128);
}
#[test]
fn grayscale_preserves_alpha() {
let mut layer = make_solid_layer(100, 150, 200, 128);
apply_filter(&mut layer, "grayscale", &serde_json::json!({}));
let pixel = layer.get_pixel(0, 0);
assert_eq!(pixel[3], 128, "grayscale should preserve alpha");
}
// ---------------------------------------------------------------------------
// Brightness/Contrast filter
// ---------------------------------------------------------------------------
#[test]
fn brightness_zero_is_identity() {
let original = make_gradient_layer();
let mut layer = make_gradient_layer();
// PS-style brightness/contrast: 0 brightness, 0 contrast should be identity
apply_filter(&mut layer, "brightness_contrast", &serde_json::json!({
"brightness": 0.0,
"contrast": 0.0
}));
for y in 0..4 {
for x in 0..4 {
let o = original.get_pixel(x, y);
let p = layer.get_pixel(x, y);
// Allow ±1 for rounding
assert!(
(o[0] as i16 - p[0] as i16).abs() <= 1 &&
(o[1] as i16 - p[1] as i16).abs() <= 1 &&
(o[2] as i16 - p[2] as i16).abs() <= 1,
"identity params should preserve pixel at ({},{}): orig={:?}, got={:?}",
x, y, o, p
);
}
}
}
#[test]
fn brightness_changes_pixels() {
let mut layer = make_solid_layer(64, 64, 64, 255);
let before = layer.get_pixel(0, 0);
// PS-style brightness uses 0-100 scale
apply_filter(&mut layer, "brightness_contrast", &serde_json::json!({
"brightness": 50.0,
"contrast": 0.0
}));
let after = layer.get_pixel(0, 0);
assert!(
after[0] != before[0] || after[1] != before[1] || after[2] != before[2],
"brightness=50 should change at least one channel"
);
}
#[test]
fn brightness_negative_changes_pixels() {
let mut layer = make_solid_layer(128, 128, 128, 255);
let before = layer.get_pixel(0, 0);
// PS-style brightness uses 0-100 scale
apply_filter(&mut layer, "brightness_contrast", &serde_json::json!({
"brightness": -50.0,
"contrast": 0.0
}));
let after = layer.get_pixel(0, 0);
assert!(
after[0] != before[0] || after[1] != before[1] || after[2] != before[2],
"brightness=-50 should change at least one channel"
);
}
// ---------------------------------------------------------------------------
// Edge cases: 1×1 images (minimal boundary)
// ---------------------------------------------------------------------------
#[test]
fn invert_works_on_1x1() {
let mut layer = make_1x1_layer(128, 64, 32, 255);
apply_filter(&mut layer, "invert", &serde_json::json!({}));
let pixel = layer.get_pixel(0, 0);
assert_eq!(pixel[0], 127); // 255 - 128
assert_eq!(pixel[1], 191); // 255 - 64
assert_eq!(pixel[2], 223); // 255 - 32
}
#[test]
fn grayscale_works_on_1x1() {
let mut layer = make_1x1_layer(100, 50, 200, 255);
apply_filter(&mut layer, "grayscale", &serde_json::json!({}));
let pixel = layer.get_pixel(0, 0);
// Grayscale: R, G, B should be approximately equal
let diff = (pixel[0] as i16 - pixel[1] as i16).abs()
.max((pixel[0] as i16 - pixel[2] as i16).abs())
.max((pixel[1] as i16 - pixel[2] as i16).abs());
assert!(diff <= 3, "grayscale on 1x1 should make R≈G≈B: {:?}", pixel);
}
// ---------------------------------------------------------------------------
// Unknown / missing parameters do not crash
// ---------------------------------------------------------------------------
#[test]
fn invert_with_empty_params_works() {
let mut layer = make_solid_layer(100, 100, 100, 255);
apply_filter(&mut layer, "invert", &serde_json::json!({}));
let pixel = layer.get_pixel(0, 0);
assert_eq!(pixel[0], 155);
}
#[test]
fn invert_with_extra_params_does_not_crash() {
let mut layer = make_solid_layer(0, 0, 0, 255);
apply_filter(&mut layer, "invert", &serde_json::json!({
"unknown_param": 42,
"another_one": "hello"
}));
let pixel = layer.get_pixel(0, 0);
assert_eq!(pixel[0], 255, "invert with extra params should still work");
}
// ---------------------------------------------------------------------------
// Filters preserve layer dimensions
// ---------------------------------------------------------------------------
#[test]
fn filter_preserves_dimensions() {
for filter_name in &["invert", "grayscale", "brightness_contrast", "box_blur"] {
let mut layer = make_gradient_layer();
apply_filter(&mut layer, filter_name, &serde_json::json!({}));
assert_eq!(layer.width, 4, "{} should preserve width", filter_name);
assert_eq!(layer.height, 4, "{} should preserve height", filter_name);
assert_eq!(layer.pixels.len(), 4 * 4 * 4, "{} should preserve pixel count", filter_name);
}
}
// ---------------------------------------------------------------------------
// Multiple filters in sequence do not crash
// ---------------------------------------------------------------------------
#[test]
fn sequence_of_filters_does_not_crash() {
let mut layer = make_gradient_layer();
for &filter in &["invert", "grayscale", "brightness_contrast", "sharpen", "box_blur"] {
apply_filter(&mut layer, filter, &serde_json::json!({}));
}
// If we got here without panic, the test passes
assert_eq!(layer.width, 4);
}