[librsvg: 2/4] tests: restructure modules for the rsvg-convert test framework



commit 549ed938e626cb33d7164449b457c7e2212c34a4
Author: Sven Neumann <sven svenfoo org>
Date:   Sat Feb 22 10:55:16 2020 +0100

    tests: restructure modules for the rsvg-convert test framework
    
    Split up file predicates into several files.

 tests/Makefile.am                 |   7 +-
 tests/src/cmdline/mod.rs          |   1 -
 tests/src/cmdline/predicates.rs   | 367 --------------------------------------
 tests/src/cmdline/rsvg_convert.rs |   2 +-
 tests/src/main.rs                 |   6 +-
 tests/src/predicates/file.rs      |  28 +++
 tests/src/predicates/mod.rs       |   6 +
 tests/src/predicates/pdf.rs       | 231 ++++++++++++++++++++++++
 tests/src/predicates/png.rs       | 101 +++++++++++
 9 files changed, 377 insertions(+), 372 deletions(-)
---
diff --git a/tests/Makefile.am b/tests/Makefile.am
index ccb79d10..a3da6a60 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -4,8 +4,11 @@ test_sources =                         \
        Cargo.toml                      \
        src/main.rs                     \
        src/cmdline/mod.rs              \
-       src/cmdline/predicates.rs       \
-       src/cmdline/rsvg_convert.rs
+       src/cmdline/rsvg_convert.rs     \
+       src/predicates/file.rs          \
+       src/predicates/mod.rs           \
+       src/predicates/pdf.rs           \
+       src/predicates/png.rs
 
 EXTRA_DIST +=          \
        $(test_sources) \
diff --git a/tests/src/cmdline/mod.rs b/tests/src/cmdline/mod.rs
index a6d65a08..c694bf2e 100644
--- a/tests/src/cmdline/mod.rs
+++ b/tests/src/cmdline/mod.rs
@@ -1,2 +1 @@
-mod predicates;
 mod rsvg_convert;
diff --git a/tests/src/cmdline/rsvg_convert.rs b/tests/src/cmdline/rsvg_convert.rs
index f906be81..2ce4a187 100644
--- a/tests/src/cmdline/rsvg_convert.rs
+++ b/tests/src/cmdline/rsvg_convert.rs
@@ -3,7 +3,7 @@ extern crate chrono;
 extern crate predicates;
 extern crate tempfile;
 
-use super::predicates::file;
+use crate::predicates::file;
 
 use assert_cmd::assert::IntoOutputPredicate;
 use assert_cmd::Command;
diff --git a/tests/src/main.rs b/tests/src/main.rs
index 931ec4af..dd7afcb0 100644
--- a/tests/src/main.rs
+++ b/tests/src/main.rs
@@ -1,9 +1,13 @@
+#[cfg(test)]
 #[macro_use]
 extern crate float_cmp;
 
 #[cfg(test)]
 mod cmdline;
 
+#[cfg(test)]
+mod predicates;
+
 fn main() {
-    println!("Use 'cargo test' to run the tests");
+    println!("Use 'cargo test' to run the tests.");
 }
diff --git a/tests/src/predicates/file.rs b/tests/src/predicates/file.rs
new file mode 100644
index 00000000..1a026ce1
--- /dev/null
+++ b/tests/src/predicates/file.rs
@@ -0,0 +1,28 @@
+use predicates::boolean::AndPredicate;
+use predicates::prelude::*;
+use predicates::str::{ContainsPredicate, StartsWithPredicate};
+
+use crate::predicates::pdf::PdfPredicate;
+use crate::predicates::png::PngPredicate;
+
+/// Predicates to check that some output ([u8]) is of a certain file type
+
+pub fn is_png() -> PngPredicate {
+    PngPredicate {}
+}
+
+pub fn is_ps() -> StartsWithPredicate {
+    predicate::str::starts_with("%!PS-Adobe-3.0\n")
+}
+
+pub fn is_eps() -> StartsWithPredicate {
+    predicate::str::starts_with("%!PS-Adobe-3.0 EPSF-3.0\n")
+}
+
+pub fn is_pdf() -> PdfPredicate {
+    PdfPredicate {}
+}
+
+pub fn is_svg() -> AndPredicate<StartsWithPredicate, ContainsPredicate, str> {
+    predicate::str::starts_with("<?xml ").and(predicate::str::contains("<svg "))
+}
diff --git a/tests/src/predicates/mod.rs b/tests/src/predicates/mod.rs
new file mode 100644
index 00000000..adac53fc
--- /dev/null
+++ b/tests/src/predicates/mod.rs
@@ -0,0 +1,6 @@
+extern crate predicates;
+
+pub mod file;
+
+mod pdf;
+mod png;
diff --git a/tests/src/predicates/pdf.rs b/tests/src/predicates/pdf.rs
new file mode 100644
index 00000000..d514652b
--- /dev/null
+++ b/tests/src/predicates/pdf.rs
@@ -0,0 +1,231 @@
+extern crate chrono;
+extern crate lopdf;
+
+use chrono::{DateTime, Utc};
+use predicates::prelude::*;
+use predicates::reflection::{Case, Child, PredicateReflection, Product};
+use std::cmp;
+use std::fmt;
+
+/// Checks that the variable of type [u8] can be parsed as a PDF file.
+#[derive(Debug)]
+pub struct PdfPredicate {}
+
+impl PdfPredicate {
+    pub fn with_page_count(self: Self, num_pages: usize) -> DetailPredicate<Self> {
+        DetailPredicate::<Self> {
+            p: self,
+            d: Detail::PageCount(num_pages),
+        }
+    }
+
+    pub fn with_page_size(self: Self, width: i64, height: i64, dpi: f64) -> DetailPredicate<Self> {
+        DetailPredicate::<Self> {
+            p: self,
+            d: Detail::PageSize(Dimensions {
+                w: width,
+                h: height,
+                unit: dpi / 72.0,
+            }),
+        }
+    }
+
+    pub fn with_creation_date(self: Self, when: DateTime<Utc>) -> DetailPredicate<Self> {
+        DetailPredicate::<Self> {
+            p: self,
+            d: Detail::CreationDate(when),
+        }
+    }
+}
+
+impl Predicate<[u8]> for PdfPredicate {
+    fn eval(&self, data: &[u8]) -> bool {
+        lopdf::Document::load_mem(data).is_ok()
+    }
+
+    fn find_case<'a>(&'a self, _expected: bool, data: &[u8]) -> Option<Case<'a>> {
+        match lopdf::Document::load_mem(data) {
+            Ok(_) => None,
+            Err(e) => Some(Case::new(Some(self), false).add_product(Product::new("Error", e))),
+        }
+    }
+}
+
+impl PredicateReflection for PdfPredicate {}
+
+impl fmt::Display for PdfPredicate {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "is a PDF")
+    }
+}
+
+/// Extends a PdfPredicate by a check for page count, page size or creation date.
+#[derive(Debug)]
+pub struct DetailPredicate<PdfPredicate> {
+    p: PdfPredicate,
+    d: Detail,
+}
+
+#[derive(Debug)]
+enum Detail {
+    PageCount(usize),
+    PageSize(Dimensions),
+    CreationDate(DateTime<Utc>),
+}
+
+#[derive(Debug)]
+struct Dimensions {
+    w: i64,
+    h: i64,    //
+    unit: f64, // UserUnit, in points (1/72 of an inch)
+}
+
+impl Dimensions {
+    pub fn from_media_box(obj: &lopdf::Object, unit: Option<f64>) -> lopdf::Result<Dimensions> {
+        let a = obj.as_array()?;
+        Ok(Dimensions {
+            w: a[2].as_i64()?,
+            h: a[3].as_i64()?,
+            unit: unit.unwrap_or(1.0),
+        })
+    }
+
+    pub fn width_in_mm(self: &Self) -> f64 {
+        self.w as f64 * self.unit * 72.0 / 254.0
+    }
+
+    pub fn height_in_mm(self: &Self) -> f64 {
+        self.h as f64 * self.unit * 72.0 / 254.0
+    }
+}
+
+impl fmt::Display for Dimensions {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{} mm x {} mm", self.width_in_mm(), self.height_in_mm())
+    }
+}
+
+impl cmp::PartialEq for Dimensions {
+    fn eq(&self, other: &Self) -> bool {
+        approx_eq!(f64, self.width_in_mm(), other.width_in_mm())
+            && approx_eq!(f64, self.height_in_mm(), other.height_in_mm())
+    }
+}
+
+impl cmp::Eq for Dimensions {}
+
+trait Details {
+    fn get_page_count(&self) -> usize;
+    fn get_page_size(&self) -> Option<Dimensions>;
+    fn get_creation_date(&self) -> Option<DateTime<Utc>>;
+    fn get_from_trailer<'a>(self: &'a Self, key: &[u8]) -> lopdf::Result<&'a lopdf::Object>;
+    fn get_from_first_page<'a>(self: &'a Self, key: &[u8]) -> lopdf::Result<&'a lopdf::Object>;
+}
+
+impl DetailPredicate<PdfPredicate> {
+    fn eval_doc(&self, doc: &lopdf::Document) -> bool {
+        match &self.d {
+            Detail::PageCount(n) => doc.get_page_count() == *n,
+            Detail::PageSize(d) => doc.get_page_size().map_or(false, |dim| dim == *d),
+            Detail::CreationDate(d) => doc.get_creation_date().map_or(false, |date| date == *d),
+        }
+    }
+
+    fn find_case_for_doc<'a>(&'a self, expected: bool, doc: &lopdf::Document) -> Option<Case<'a>> {
+        if self.eval_doc(doc) == expected {
+            let product = self.product_for_doc(doc);
+            Some(Case::new(Some(self), false).add_product(product))
+        } else {
+            None
+        }
+    }
+
+    fn product_for_doc(&self, doc: &lopdf::Document) -> Product {
+        match &self.d {
+            Detail::PageCount(_) => Product::new(
+                "actual page count",
+                format!("{} page(s)", doc.get_page_count()),
+            ),
+            Detail::PageSize(_) => Product::new(
+                "actual page size",
+                match doc.get_page_size() {
+                    Some(dim) => format!("{}", dim),
+                    None => "None".to_string(),
+                },
+            ),
+            Detail::CreationDate(_) => Product::new(
+                "actual creation date",
+                format!("{:?}", doc.get_creation_date()),
+            ),
+        }
+    }
+}
+
+impl Details for lopdf::Document {
+    fn get_page_count(self: &Self) -> usize {
+        self.get_pages().len()
+    }
+
+    fn get_page_size(self: &Self) -> Option<Dimensions> {
+        let to_f64 = |obj: &lopdf::Object| obj.as_f64();
+        match self.get_from_first_page(b"MediaBox") {
+            Ok(obj) => {
+                let unit = self.get_from_first_page(b"UserUnit").and_then(to_f64).ok();
+                Dimensions::from_media_box(obj, unit).ok()
+            }
+            Err(_) => None,
+        }
+    }
+
+    fn get_creation_date(self: &Self) -> Option<DateTime<Utc>> {
+        match self.get_from_trailer(b"CreationDate") {
+            Ok(obj) => obj.as_datetime().map(|date| date.with_timezone(&Utc)),
+            Err(_) => None,
+        }
+    }
+
+    fn get_from_trailer<'a>(self: &'a Self, key: &[u8]) -> lopdf::Result<&'a lopdf::Object> {
+        let id = self.trailer.get(b"Info")?.as_reference()?;
+        self.get_object(id)?.as_dict()?.get(key)
+    }
+
+    fn get_from_first_page<'a>(self: &'a Self, key: &[u8]) -> lopdf::Result<&'a lopdf::Object> {
+        match self.page_iter().next() {
+            Some(id) => self.get_object(id)?.as_dict()?.get(key),
+            None => Err(lopdf::Error::ObjectNotFound),
+        }
+    }
+}
+
+impl Predicate<[u8]> for DetailPredicate<PdfPredicate> {
+    fn eval(&self, data: &[u8]) -> bool {
+        match lopdf::Document::load_mem(data) {
+            Ok(doc) => self.eval_doc(&doc),
+            _ => false,
+        }
+    }
+
+    fn find_case<'a>(&'a self, expected: bool, data: &[u8]) -> Option<Case<'a>> {
+        match lopdf::Document::load_mem(data) {
+            Ok(doc) => self.find_case_for_doc(expected, &doc),
+            Err(e) => Some(Case::new(Some(self), false).add_product(Product::new("Error", e))),
+        }
+    }
+}
+
+impl PredicateReflection for DetailPredicate<PdfPredicate> {
+    fn children<'a>(&'a self) -> Box<dyn Iterator<Item = Child<'a>> + 'a> {
+        let params = vec![Child::new("predicate", &self.p)];
+        Box::new(params.into_iter())
+    }
+}
+
+impl fmt::Display for DetailPredicate<PdfPredicate> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match &self.d {
+            Detail::PageCount(n) => write!(f, "is a PDF with {} page(s)", n),
+            Detail::PageSize(d) => write!(f, "is a PDF sized {}", d),
+            Detail::CreationDate(d) => write!(f, "is a PDF created {:?}", d),
+        }
+    }
+}
diff --git a/tests/src/predicates/png.rs b/tests/src/predicates/png.rs
new file mode 100644
index 00000000..f8179564
--- /dev/null
+++ b/tests/src/predicates/png.rs
@@ -0,0 +1,101 @@
+extern crate png;
+
+use predicates::prelude::*;
+use predicates::reflection::{Case, Child, PredicateReflection, Product};
+use std::fmt;
+
+/// Checks that the variable of type [u8] can be parsed as a PNG file.
+#[derive(Debug)]
+pub struct PngPredicate {}
+
+impl PngPredicate {
+    pub fn with_size(self: Self, w: u32, h: u32) -> SizePredicate<Self> {
+        SizePredicate::<Self> { p: self, w, h }
+    }
+}
+
+impl Predicate<[u8]> for PngPredicate {
+    fn eval(&self, data: &[u8]) -> bool {
+        let decoder = png::Decoder::new(data);
+        decoder.read_info().is_ok()
+    }
+
+    fn find_case<'a>(&'a self, _expected: bool, data: &[u8]) -> Option<Case<'a>> {
+        let decoder = png::Decoder::new(data);
+        match decoder.read_info() {
+            Ok(_) => None,
+            Err(e) => Some(Case::new(Some(self), false).add_product(Product::new("Error", e))),
+        }
+    }
+}
+
+impl PredicateReflection for PngPredicate {}
+
+impl fmt::Display for PngPredicate {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "is a PNG")
+    }
+}
+
+/// Extends a PngPredicate by a check for a given size of the PNG file.
+#[derive(Debug)]
+pub struct SizePredicate<PngPredicate> {
+    p: PngPredicate,
+    w: u32,
+    h: u32,
+}
+
+impl SizePredicate<PngPredicate> {
+    fn eval_info(&self, info: &png::OutputInfo) -> bool {
+        info.width == self.w && info.height == self.h
+    }
+
+    fn find_case_for_info<'a>(
+        &'a self,
+        expected: bool,
+        info: &png::OutputInfo,
+    ) -> Option<Case<'a>> {
+        if self.eval_info(info) == expected {
+            let product = self.product_for_info(info);
+            Some(Case::new(Some(self), false).add_product(product))
+        } else {
+            None
+        }
+    }
+
+    fn product_for_info(&self, info: &png::OutputInfo) -> Product {
+        let actual_size = format!("{} x {}", info.width, info.height);
+        Product::new("actual size", actual_size)
+    }
+}
+
+impl Predicate<[u8]> for SizePredicate<PngPredicate> {
+    fn eval(&self, data: &[u8]) -> bool {
+        let decoder = png::Decoder::new(data);
+        match decoder.read_info() {
+            Ok((info, _)) => self.eval_info(&info),
+            _ => false,
+        }
+    }
+
+    fn find_case<'a>(&'a self, expected: bool, data: &[u8]) -> Option<Case<'a>> {
+        let decoder = png::Decoder::new(data);
+        match decoder.read_info() {
+            Ok((info, _)) => self.find_case_for_info(expected, &info),
+            Err(e) => Some(Case::new(Some(self), false).add_product(Product::new("Error", e))),
+        }
+    }
+}
+
+impl PredicateReflection for SizePredicate<PngPredicate> {
+    fn children<'a>(&'a self) -> Box<dyn Iterator<Item = Child<'a>> + 'a> {
+        let params = vec![Child::new("predicate", &self.p)];
+        Box::new(params.into_iter())
+    }
+}
+
+impl fmt::Display for SizePredicate<PngPredicate> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "is a PNG with size {} x {}", self.w, self.h)
+    }
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]