mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-06 18:52:36 +00:00
M13 batch: test that failed folder runs leave no output or staging file (#6298)
Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
This commit is contained in:
1 parent
7fd6860f4a
commit
5aff7c83db
2 files changed
+90
No files matched your search
@@ -0,0 +1,89 @@
|
||||
//! A batch run that fails a file publishes only the completed outputs, and a
|
||||
//! publish the file system refuses publishes nothing.
|
||||
|
||||
use pdfcraft_automation::{Automation, Content};
|
||||
use pdfcraft_engine::Session;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
fn result(auto: &mut Automation, args: Value) -> Value {
|
||||
match auto.call("doc_batch", &args).unwrap().remove(0) {
|
||||
Content::Json(value) => value,
|
||||
_ => panic!("expected a report"),
|
||||
}
|
||||
}
|
||||
|
||||
fn pdf() -> Vec<u8> {
|
||||
Session::new().create_from_text("Original", "Original text").unwrap().as_ref().clone()
|
||||
}
|
||||
|
||||
/// Sets a folder's permission bits.
|
||||
#[cfg(unix)]
|
||||
fn set_mode(path: &std::path::Path, mode: u32) {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode)).unwrap();
|
||||
}
|
||||
|
||||
/// Puts back the write permission the temporary folder needs before it is removed.
|
||||
#[cfg(unix)]
|
||||
struct RestoreMode(std::path::PathBuf);
|
||||
|
||||
#[cfg(unix)]
|
||||
impl Drop for RestoreMode {
|
||||
fn drop(&mut self) {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let _ = std::fs::set_permissions(&self.0, std::fs::Permissions::from_mode(0o755));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_inputs_publish_only_completed_outputs() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
std::fs::create_dir(dir.path().join("input")).unwrap();
|
||||
std::fs::write(dir.path().join("input/good.pdf"), pdf()).unwrap();
|
||||
std::fs::write(dir.path().join("input/bad.pdf"), "not a pdf").unwrap();
|
||||
let mut auto = Automation::new().with_root(dir.path()).unwrap();
|
||||
let report = result(&mut auto, json!({"paths":["input"],"steps":["optimize"],"out_dir":"out","recursive":false}));
|
||||
assert_eq!(report["completed"], 1, "{report}");
|
||||
assert_eq!(report["failed"], 1, "{report}");
|
||||
let files = report["files"].as_array().unwrap();
|
||||
assert_eq!(files.len(), 2, "{report}");
|
||||
let bad = files.iter().find(|file| file["path"].as_str().is_some_and(|path| path.ends_with("bad.pdf"))).unwrap();
|
||||
assert_eq!(bad["status"], "error", "{report}");
|
||||
assert!(bad["output"].is_null(), "a failed file reports no output: {report}");
|
||||
let names: Vec<String> =
|
||||
std::fs::read_dir(dir.path().join("out")).unwrap().map(|entry| entry.unwrap().file_name().to_string_lossy().into_owned()).collect();
|
||||
// Publish stages its bytes under a dot-prefixed name in the output folder, so
|
||||
// no entry carrying that prefix may survive the run.
|
||||
for name in &names {
|
||||
assert!(!name.starts_with('.'), "temporary name left behind: {name} ({names:?})");
|
||||
}
|
||||
assert_eq!(names, vec!["good.pdf".to_string()], "only the completed output is published");
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn read_only_output_folder_publishes_nothing() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
std::fs::create_dir(dir.path().join("input")).unwrap();
|
||||
std::fs::write(dir.path().join("input/good.pdf"), pdf()).unwrap();
|
||||
let out = dir.path().join("out");
|
||||
std::fs::create_dir(&out).unwrap();
|
||||
set_mode(&out, 0o555);
|
||||
let _restore = RestoreMode(out.clone());
|
||||
// A root process ignores the mode bits, and then the assertions below would
|
||||
// pass without testing the refusal. Fail loudly instead of skipping.
|
||||
assert!(std::fs::write(out.join("probe"), b"").is_err(), "run as a non-root user: the read-only output folder accepted a new file");
|
||||
let mut auto = Automation::new().with_root(dir.path()).unwrap();
|
||||
let report = result(&mut auto, json!({"paths":["input"],"steps":["optimize"],"out_dir":"out","recursive":false}));
|
||||
assert_eq!(report["completed"], 0, "{report}");
|
||||
assert_eq!(report["failed"], 1, "{report}");
|
||||
let files = report["files"].as_array().unwrap();
|
||||
assert_eq!(files.len(), 1, "{report}");
|
||||
// Publish cannot create its staging file in a folder it may not write, so
|
||||
// the entry carries the system's error and no output.
|
||||
assert_eq!(files[0]["status"], "error", "{report}");
|
||||
assert!(files[0]["output"].is_null(), "a refused publish reports no output: {report}");
|
||||
let error = files[0]["error"].as_str().unwrap();
|
||||
assert!(!error.is_empty(), "{report}");
|
||||
assert_eq!(std::fs::read_dir(&out).unwrap().count(), 0, "the refused publish writes nothing into the read-only folder: {report}");
|
||||
}
|
||||
@@ -5,6 +5,7 @@ mod automation;
|
||||
mod automation_roots;
|
||||
mod backdrop;
|
||||
mod batch;
|
||||
mod batch_failures;
|
||||
mod button_caption;
|
||||
mod form_bulk;
|
||||
mod hostile_args;
|
||||
|
||||
Reference in new issue
Block a user