Skip to content

Commit 57dbbaa

Browse files
committed
test: add unit tests and GitHub Actions CI/CD pipeline
- Added unit tests for core comparison and region ignoring - Configured GitHub Actions for multi-platform testing (Linux, macOS, Windows) - Added automatic binary release workflow on tag push
1 parent 01698d5 commit 57dbbaa

3 files changed

Lines changed: 123 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: CI & Release
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
tags: [ 'v*' ]
7+
pull_request:
8+
branches: [ main ]
9+
10+
env:
11+
CARGO_TERM_COLOR: always
12+
13+
jobs:
14+
test:
15+
name: Test on ${{ matrix.os }}
16+
runs-on: ${{ matrix.os }}
17+
strategy:
18+
matrix:
19+
os: [ubuntu-latest, macos-latest, windows-latest]
20+
21+
steps:
22+
- uses: actions/checkout@v4
23+
- name: Build
24+
run: cargo build --verbose
25+
- name: Run tests
26+
run: cargo test --verbose
27+
28+
release:
29+
name: Release Binaries
30+
if: startsWith(github.ref, 'refs/tags/')
31+
needs: test
32+
runs-on: ${{ matrix.os }}
33+
strategy:
34+
matrix:
35+
include:
36+
- os: ubuntu-latest
37+
target: x86_64-unknown-linux-gnu
38+
artifact_name: image-diff-linux
39+
- os: macos-latest
40+
target: x86_64-apple-darwin
41+
artifact_name: image-diff-macos
42+
- os: windows-latest
43+
target: x86_64-pc-windows-msvc
44+
artifact_name: image-diff-windows.exe
45+
46+
steps:
47+
- uses: actions/checkout@v4
48+
- name: Build release
49+
run: cargo build --release --target ${{ matrix.target }}
50+
- name: Upload artifact
51+
uses: actions/upload-artifact@v4
52+
with:
53+
name: ${{ matrix.artifact_name }}
54+
path: target/${{ matrix.target }}/release/${{ matrix.os == 'windows-latest' && 'image-diff.exe' || 'image-diff' }}

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ pathdiff = "0.2.1"
1818
serde = { version = "1.0.200", features = ["derive"] }
1919
serde_json = "1.0.116"
2020
image-compare = "0.5.0"
21+
22+
[dev-dependencies]
23+
tempfile = "3.10.1"

src/compare.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,69 @@ fn color_distance(p1: &Rgba<u8>, p2: &Rgba<u8>) -> f64 {
126126
// Euclidean distance in RGBA space
127127
(r_diff * r_diff + g_diff * g_diff + b_diff * b_diff + a_diff * a_diff).sqrt()
128128
}
129+
130+
#[cfg(test)]
131+
mod tests {
132+
use super::*;
133+
134+
#[test]
135+
fn test_color_distance() {
136+
let p1 = Rgba([0, 0, 0, 255]);
137+
let p2 = Rgba([255, 255, 255, 255]);
138+
assert!((color_distance(&p1, &p2) - 1.732).abs() < 0.01);
139+
140+
let p3 = Rgba([100, 100, 100, 255]);
141+
assert_eq!(color_distance(&p3, &p3), 0.0);
142+
}
143+
144+
#[test]
145+
fn test_region_contains() {
146+
let region = Region { x: 10, y: 10, width: 20, height: 20 };
147+
assert!(region.contains(10, 10));
148+
assert!(region.contains(29, 29));
149+
assert!(!region.contains(9, 10));
150+
assert!(!region.contains(30, 30));
151+
}
152+
153+
#[test]
154+
fn test_compare_identical() -> Result<()> {
155+
let mut img: ImageBuffer<Rgba<u8>, Vec<u8>> = ImageBuffer::new(10, 10);
156+
for p in img.pixels_mut() { *p = Rgba([100, 100, 100, 255]); }
157+
158+
let file_a = tempfile::Builder::new().suffix(".png").tempfile()?;
159+
let file_b = tempfile::Builder::new().suffix(".png").tempfile()?;
160+
img.save(file_a.path())?;
161+
img.save(file_b.path())?;
162+
163+
let res = compare_images(file_a.path(), file_b.path(), 0.1, false, &[])?;
164+
assert_eq!(res.diff_pixels, 0);
165+
assert_eq!(res.score, 1.0);
166+
assert!(res.ssim_score > 0.99);
167+
Ok(())
168+
}
169+
170+
#[test]
171+
fn test_compare_different_with_ignore() -> Result<()> {
172+
let mut img_a: ImageBuffer<Rgba<u8>, Vec<u8>> = ImageBuffer::new(10, 10);
173+
for p in img_a.pixels_mut() { *p = Rgba([100, 100, 100, 255]); }
174+
175+
let mut img_b = img_a.clone();
176+
img_b.put_pixel(5, 5, Rgba([255, 0, 0, 255]));
177+
178+
let file_a = tempfile::Builder::new().suffix(".png").tempfile()?;
179+
let file_b = tempfile::Builder::new().suffix(".png").tempfile()?;
180+
img_a.save(file_a.path())?;
181+
img_b.save(file_b.path())?;
182+
183+
// Without ignore
184+
let res1 = compare_images(file_a.path(), file_b.path(), 0.1, false, &[])?;
185+
assert_eq!(res1.diff_pixels, 1);
186+
187+
// With ignore
188+
let ignore = [Region { x: 5, y: 5, width: 1, height: 1 }];
189+
let res2 = compare_images(file_a.path(), file_b.path(), 0.1, false, &ignore)?;
190+
assert_eq!(res2.diff_pixels, 0);
191+
assert_eq!(res2.score, 1.0);
192+
Ok(())
193+
}
194+
}

0 commit comments

Comments
 (0)