okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

Aly Raffauftest(render): verify contiguous bounded outputda73947

main
7.1 KiB209 linesraw
1//! The binary end to end: a file in, PNG pages and a text report out, or an
2//! image in and the same image fitted to an edge out.
3
4use std::path::{Path, PathBuf};
5use std::process::Command;
6
7struct Scratch(PathBuf);
8
9impl Scratch {
10    fn new(name: &str) -> Scratch {
11        let dir = std::env::temp_dir().join(format!("albedo-render-{name}-{}", std::process::id()));
12        let _ = std::fs::remove_dir_all(&dir);
13        std::fs::create_dir_all(&dir).unwrap();
14        Scratch(dir)
15    }
16
17    fn file(&self, name: &str, content: &[u8]) -> PathBuf {
18        let path = self.0.join(name);
19        std::fs::write(&path, content).unwrap();
20        path
21    }
22}
23
24impl Drop for Scratch {
25    fn drop(&mut self) {
26        let _ = std::fs::remove_dir_all(&self.0);
27    }
28}
29
30fn render(file: &Path, out: &Path, args: &[&str]) -> (bool, String, String) {
31    let output = Command::new(env!("CARGO_BIN_EXE_albedo-render"))
32        .arg(file)
33        .arg("--out")
34        .arg(out)
35        .args(args)
36        .output()
37        .unwrap();
38    let text = |bytes: Vec<u8>| String::from_utf8(bytes).unwrap();
39    (
40        output.status.success(),
41        text(output.stdout),
42        text(output.stderr),
43    )
44}
45
46fn png_size(path: &Path) -> (u32, u32) {
47    size(&std::fs::read(path).unwrap())
48}
49
50fn size(bytes: &[u8]) -> (u32, u32) {
51    assert_eq!(&bytes[..8], b"\x89PNG\r\n\x1a\n");
52    let field = |at: usize| u32::from_be_bytes(bytes[at..at + 4].try_into().unwrap());
53    (field(16), field(20))
54}
55
56#[test]
57fn a_range_becomes_pages_and_a_report() {
58    let scratch = Scratch::new("pages");
59    let source: String = (1..=300)
60        .map(|n| format!("let value_{n} = {n};\n"))
61        .collect();
62    let file = scratch.file("sample.rs", source.as_bytes());
63    let (ok, report, _) = render(
64        &file,
65        &scratch.0,
66        &[
67            "--start",
68            "11",
69            "--end",
70            "400",
71            "--max-rows",
72            "50",
73            "--max-images",
74            "2",
75        ],
76    );
77    assert!(ok);
78    let lines: Vec<&str> = report.lines().collect();
79    assert_eq!(lines[0], "language rust");
80    let image_lines: Vec<&str> = lines
81        .iter()
82        .copied()
83        .filter(|line| line.starts_with("image "))
84        .collect();
85    assert!(!image_lines.is_empty());
86    assert!(image_lines.len() <= 2, "exceeded the requested image cap");
87    let image_count = image_lines.len();
88    let mut next_row = 11;
89    for line in image_lines {
90        // Paths may contain spaces; the final three fields are the row range and dimensions.
91        let mut fields = line.rsplitn(4, ' ');
92        let dimensions = fields.next().unwrap();
93        let last: usize = fields.next().unwrap().parse().unwrap();
94        let first: usize = fields.next().unwrap().parse().unwrap();
95        let path = fields.next().unwrap().strip_prefix("image ").unwrap();
96        assert_eq!(first, next_row, "pages lost or repeated rows");
97        assert!(last >= first && last <= 300);
98        assert!(last - first + 1 <= 50, "exceeded the requested row cap");
99        let (width, height) = png_size(Path::new(path));
100        assert!(width > 0 && height > 0);
101        assert_eq!(dimensions, format!("{width}x{height}"));
102        next_row = last + 1;
103    }
104    assert_eq!(lines.last().unwrap(), &format!("remaining {next_row} 300"));
105    assert_eq!(lines.len(), image_count + 2);
106}
107
108#[test]
109fn keywords_are_colored_and_unknown_files_render_plain() {
110    let scratch = Scratch::new("colors");
111    let rust = scratch.file("a.rs", b"fn main() {}\n");
112    let text = scratch.file("a.unknown", b"fn main() {}\n");
113    let (_, report, _) = render(&rust, &scratch.0, &["--start", "1", "--end", "1"]);
114    assert_eq!(report.lines().next(), Some("language rust"));
115    let colored = std::fs::read(scratch.0.join("view-1.png")).unwrap();
116    let (_, report, _) = render(&text, &scratch.0, &["--start", "1", "--end", "1"]);
117    assert_eq!(
118        report.lines().next(),
119        Some("language plain unknown file type")
120    );
121    let plain = std::fs::read(scratch.0.join("view-1.png")).unwrap();
122    assert_ne!(colored, plain);
123    let (_, report, _) = render(
124        &text,
125        &scratch.0,
126        &["--start", "1", "--end", "1", "--language", "cobol"],
127    );
128    assert_eq!(
129        report.lines().next(),
130        Some("language plain no grammar for cobol")
131    );
132}
133
134#[test]
135fn bad_requests_fail_with_a_reason() {
136    let scratch = Scratch::new("errors");
137    let file = scratch.file("a.py", b"x = 1\n");
138    let binary = scratch.file("a.bin", b"\x00\x01\x02");
139    for (target, args, reason) in [
140        (
141            &file,
142            vec!["--start", "5", "--end", "6"],
143            "has 1 lines; --start 5 is past the end",
144        ),
145        (
146            &file,
147            vec!["--start", "3", "--end", "2"],
148            "--end 2 is before --start 3",
149        ),
150        (
151            &file,
152            vec!["--start", "1"],
153            "--start and --end are required",
154        ),
155        (
156            &file,
157            vec!["--start", "1", "--end", "1", "--columns", "5"],
158            "--columns must be an integer from 20 to 400",
159        ),
160        (&binary, vec!["--start", "1", "--end", "1"], "looks binary"),
161    ] {
162        let (ok, report, error) = render(target, &scratch.0, &args);
163        assert!(!ok, "{args:?} succeeded");
164        assert_eq!(report, "");
165        assert!(error.contains(reason), "{error:?} lacks {reason:?}");
166    }
167}
168
169#[test]
170fn without_line_numbers_the_gutter_keeps_only_wrap_marks() {
171    let scratch = Scratch::new("gutter");
172    let file = scratch.file("a.diff", b"@@ -1 +1 @@\n-old\n+new\n");
173    let page = scratch.0.join("view-1.png");
174    render(&file, &scratch.0, &["--start", "1", "--end", "3"]);
175    let numbered = std::fs::read(&page).unwrap();
176    let (ok, report, _) = render(
177        &file,
178        &scratch.0,
179        &["--start", "1", "--end", "3", "--no-line-numbers"],
180    );
181    assert!(ok);
182    assert_eq!(report.lines().next(), Some("language diff"));
183    let bare = std::fs::read(&page).unwrap();
184    // One digit or one wrap mark: the same width, without the numbers.
185    assert_eq!(size(&bare), size(&numbered));
186    assert_ne!(bare, numbered);
187}
188
189#[test]
190fn a_page_fits_inside_an_edge_and_keeps_its_aspect() {
191    let scratch = Scratch::new("fit");
192    let source: String = (1..=60).map(|n| format!("line {n}\n")).collect();
193    let file = scratch.file("sample.txt", source.as_bytes());
194    let pages = scratch.0.join("pages");
195    std::fs::create_dir_all(&pages).unwrap();
196    assert!(render(&file, &pages, &["--start", "1", "--end", "60"]).0);
197    let page = pages.join("view-1.png");
198    let (width, height) = png_size(&page);
199    let (ok, report, _) = render(&page, &scratch.0, &["--fit", "400"]);
200    assert!(ok);
201    let fitted = scratch.0.join("fit.png");
202    let (w, h) = png_size(&fitted);
203    assert_eq!(report, format!("image {} {w}x{h}\n", fitted.display()));
204    assert_eq!(w.max(h), 400);
205    let expected = width as f64 / height as f64;
206    assert!((w as f64 / h as f64 - expected).abs() < 0.02);
207    let (ok, _, error) = render(&file, &scratch.0, &["--fit", "400"]);
208    assert!(!ok && error.contains("PNG, JPEG, or WebP"));
209}