print: the live driver test compares the vendor prefixes instead of re-running respell

Review follow-up. The test asserted `respell(validated, driver_default) == None`.
respell also returns None when it refuses a ticket, and in_drivers_spelling then
keeps the ticket as it is, so a ticket still spelled `ns0000` passed.

The test now compares the vendor namespace declarations of the ticket driver_ticket
returns with those of the driver's own default ticket and fails when a namespace the
driver declares is under another prefix. The comparison is
`ticket::prefixes_unlike_the_drivers` (test-only), unit-tested here: it flags the ticket
as the merge returns it, passes the respelled one and the driver's own, and flags a
ticket respell refused, where `respell(..) == None` can't tell it from a correct one.

Checked against a Brother HL-L2390DW: the test passes, and fails with
`(brother namespace, "ns0000", "brpsk")` when in_drivers_spelling is made to return
the merged ticket.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FQ5PzHVUcntEHU97zniM5U
This commit is contained in:
Claude committed 2026-10-25 06:34:58 +00:00
1 parent 766f1c6f2d
commit 23bd96375a
2 files changed
+62 -1

No files matched your search

+58
View File
@@ -376,6 +376,29 @@ pub(crate) fn respell(ticket: &str, driver: &str) -> Option<String> {
(now == wanted && prefixes_resolve(&check)).then_some(out)
}
/// Each vendor namespace that `ticket` declares under a prefix the driver does not declare it with,
/// as (namespace, prefix in the ticket, prefix of the driver); empty when the ticket is spelled the
/// way the driver spells it. `driver` is the driver's own ticket ([`empty_ticket`] validated).
/// `None` when either does not parse.
///
/// For checking the ticket a job ends up with. [`respell`] returning `None` for it does not: that
/// is also what it returns when it refused to change a ticket, which then stays as it is.
#[cfg(test)]
pub(crate) fn prefixes_unlike_the_drivers(ticket: &str, driver: &str) -> Option<Vec<(String, String, String)>> {
let own = vendor_prefixes(&roxmltree::Document::parse(driver).ok()?);
let declared = vendor_prefixes(&roxmltree::Document::parse(ticket).ok()?);
Some(
declared
.into_iter()
.filter(|pair| !own.contains(pair))
.filter_map(|(prefix, uri)| {
let (theirs, _) = own.iter().find(|(_, u)| *u == uri)?;
Some((uri, prefix, theirs.clone()))
})
.collect(),
)
}
/// The widest unprintable margin of the paper, in micrometres, from PrintCapabilities computed
/// for the job's ticket (`PageImageableSize`). The largest of the four, so content centred inside
/// it is clear of every edge whichever way the driver measures. `None` when the driver doesn't say.
@@ -552,6 +575,41 @@ mod tests {
assert_eq!(refused("<psf:Feature brpsk:flag=\"1\"/>"), None, "attribute");
}
/// What the live driver test checks. A ticket `respell` refused keeps the prefix the merge gave
/// it, but `respell` says `None` for it, as it does for a ticket that is already right: only the
/// prefixes tell the two apart.
#[test]
fn the_prefixes_tell_a_refused_ticket_from_one_in_the_drivers_spelling() {
let driver = brother("brpsk");
let unlike = |ticket: &str| prefixes_unlike_the_drivers(ticket, &driver).expect("both tickets parse");
let merged = brother("ns0000");
let mismatch = (BROTHER.to_string(), "ns0000".to_string(), "brpsk".to_string());
assert_eq!(unlike(&merged), std::slice::from_ref(&mismatch), "as the merge returns it");
assert_eq!(unlike(&respell(&merged, &driver).unwrap()), [], "respelled");
assert_eq!(unlike(&driver), [], "already the driver's");
// `respell` refuses this one (`ns0000` is declared again below the root), so it stays as the
// merge left it, and `respell` says `None` for it just as it does for the driver's own.
let refused = merged.replace(
"<psf:Feature name=\"ns0000:JobPrintSpeed\">",
"<psf:Feature name=\"ns0000:JobPrintSpeed\" xmlns:ns0000=\"http://example.com/nested\">",
);
assert_ne!(refused, merged);
assert_eq!(respell(&refused, &driver), None);
assert_eq!(respell(&driver, &driver), None);
assert_eq!(unlike(&refused), [mismatch], "refused, and still wrong");
// Two namespaces that trade prefixes are both reported.
let traded = prefixes_unlike_the_drivers(&two_namespaces("ns0000", "ns0001", ""), &two_namespaces("ns0001", "ns0000", "")).unwrap();
assert_eq!(traded.len(), 2, "{traded:?}");
// A namespace the driver doesn't declare has no prefix of the driver's to differ from.
assert_eq!(unlike(&empty_ticket()), [], "no vendor namespace to compare");
assert_eq!(unlike(&two_namespaces("ns0000", "ns0001", "")).len(), 1, "only BROTHER is the driver's");
assert_eq!(prefixes_unlike_the_drivers("<not xml", &driver), None);
assert_eq!(prefixes_unlike_the_drivers(&merged, "<not xml"), None);
}
/// Only names in a namespace whose prefix changes block the respelling.
#[test]
fn names_in_a_namespace_that_keeps_its_prefix_do_not_block_the_respelling() {
+4 -1
View File
@@ -217,10 +217,13 @@ mod tests {
ticket::verify_job_options(std::str::from_utf8(ticket.get_xml()).unwrap(), &job, 1).unwrap();
assert_eq!(paper, (215_900, 279_400));
// The ticket names the driver's vendor namespaces as the driver does (the merge renamed them).
// `respell` returning `None` would not show it: it also does when it refused the ticket.
let mut own = PrintTicketBuilder::new(&device).unwrap();
own.merge(PrintTicket::from_xml(ticket::empty_ticket())).unwrap();
let own = own.build().unwrap();
assert_eq!(ticket::respell(std::str::from_utf8(ticket.get_xml()).unwrap(), std::str::from_utf8(own.get_xml()).unwrap()), None);
let unlike =
ticket::prefixes_unlike_the_drivers(std::str::from_utf8(ticket.get_xml()).unwrap(), std::str::from_utf8(own.get_xml()).unwrap()).unwrap();
assert!(unlike.is_empty(), "vendor namespaces under another prefix than the driver's (namespace, ticket, driver): {unlike:?}");
eprintln!("unprintable margin: {} pt", imageable_margin(&device, &ticket));
if let Ok(path) = std::env::var("CRAFT_PRINT_TEST_TICKET_OUTPUT") {
std::fs::write(path, ticket.get_xml()).unwrap();