[librsvg: 2/4] tests: restructure modules for the rsvg-convert test framework
- From: Federico Mena Quintero <federico src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [librsvg: 2/4] tests: restructure modules for the rsvg-convert test framework
- Date: Tue, 3 Mar 2020 16:44:14 +0000 (UTC)
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]