Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve coverage testing and filesystem coverage #351

Merged
merged 14 commits into from
Jun 9, 2024
Merged
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ logs/
.vscode/**/*
!.vscode/settings.json
!.vscode/extensions.json
!.vscode/tasks.json
!.vscode/*.todo

build/
Expand Down Expand Up @@ -46,4 +47,7 @@ apps/server/client/*

# nix
.envrc
.direnv
.direnv

#Code coverage
lcov.info
3 changes: 2 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"bradlc.vscode-tailwindcss",
"Gruntfuggly.todo-tree",
"mike-co.import-sorter",
"aaron-bond.better-comments"
"aaron-bond.better-comments",
"ryanluker.vscode-coverage-gutters"
]
}
41 changes: 41 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Setup",
"type": "shell",
"command": "yarn setup",
"group": "build",
"problemMatcher": [],
"presentation": {
"panel": "shared",
"showReuseMessage": true,
"clear": false
}
},
{
"label": "Run tests",
"type": "shell",
"command": "cargo test",
"group": "test",
"problemMatcher": ["$rustc"],
"presentation": {
"panel": "shared",
"showReuseMessage": true,
"clear": false
}
},
{
"label": "Generate coverage",
"type": "shell",
"command": "cargo llvm-cov --lcov --output-path lcov.info",
"group": "test",
"problemMatcher": ["$rustc"],
"presentation": {
"panel": "shared",
"showReuseMessage": true,
"clear": false
}
}
]
}
Binary file added core/integration-tests/data/example.webp
Binary file not shown.
Binary file added core/integration-tests/data/rust_book.pdf
Binary file not shown.
Binary file not shown.
73 changes: 69 additions & 4 deletions core/src/filesystem/image/webp.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use image::{imageops, io::Reader, DynamicImage, EncodableLayout, GenericImageView};
use std::fs;

use image::{imageops, DynamicImage, EncodableLayout, GenericImageView};
use webp::Encoder;

use crate::filesystem::{error::FileError, image::process::resized_dimensions};
Expand All @@ -12,7 +14,8 @@ impl ImageProcessor for WebpProcessor {
buffer: &[u8],
options: ImageProcessorOptions,
) -> Result<Vec<u8>, FileError> {
let mut image = image::load_from_memory(buffer)?;
let mut image =
image::load_from_memory_with_format(buffer, image::ImageFormat::WebP)?;

if let Some(resize_options) = options.resize_options {
let resized_image = WebpProcessor::resize_image(image, resize_options);
Expand All @@ -30,8 +33,8 @@ impl ImageProcessor for WebpProcessor {
path: &str,
options: ImageProcessorOptions,
) -> Result<Vec<u8>, FileError> {
let image = Reader::open(path)?.with_guessed_format()?.decode()?;
Self::generate(image.as_bytes(), options)
let bytes = fs::read(path)?;
Self::generate(&bytes, options)
}
}

Expand All @@ -53,3 +56,65 @@ impl WebpProcessor {
))
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::filesystem::image::{ImageFormat, ImageProcessorOptions, ImageResizeMode};
use std::{fs, path::PathBuf};

#[test]
fn test_generate_webp_from_data() {
let bytes = get_test_webp_data();
let options = ImageProcessorOptions {
resize_options: None,
format: ImageFormat::Webp,
quality: None,
page: None,
};

WebpProcessor::generate(&bytes, options).unwrap();
}

#[test]
fn test_generate_webp_from_path() {
let webp_path = get_test_webp_path();
let options = ImageProcessorOptions {
resize_options: None,
format: ImageFormat::Webp,
quality: None,
page: None,
};

WebpProcessor::generate_from_path(&webp_path, options).unwrap();
}

#[test]
fn test_resize_webp() {
let webp_path = get_test_webp_path();
let options = ImageProcessorOptions {
resize_options: Some(ImageResizeOptions {
mode: ImageResizeMode::Scaled,
height: 2.0,
width: 2.0,
}),
format: ImageFormat::Webp,
quality: None,
page: None,
};

WebpProcessor::generate_from_path(&webp_path, options).unwrap();
}

fn get_test_webp_path() -> String {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("integration-tests/data/example.webp")
.to_string_lossy()
.to_string()
}

fn get_test_webp_data() -> Vec<u8> {
let path = get_test_webp_path();
fs::read(path).expect("Failed to fetch example webp image")
}
}
55 changes: 55 additions & 0 deletions core/src/filesystem/media/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,58 @@ impl SeriesBuilder {
})
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::filesystem::media::tests::{
get_test_cbz_path, get_test_epub_path, get_test_pdf_path, get_test_rar_path,
get_test_zip_path,
};

#[test]
fn test_build_media() {
// Test with zip
let media = build_media_test_helper(get_test_zip_path());
assert!(media.is_ok());
let media = media.unwrap();
assert_eq!(media.extension, "zip");

// Test with cbz
let media = build_media_test_helper(get_test_cbz_path());
assert!(media.is_ok());
let media = media.unwrap();
assert_eq!(media.extension, "cbz");

// Test with rar
let media = build_media_test_helper(get_test_rar_path());
assert!(media.is_ok());
let media = media.unwrap();
assert_eq!(media.extension, "rar");

// Test with epub
let media = build_media_test_helper(get_test_epub_path());
assert!(media.is_ok());
let media = media.unwrap();
assert_eq!(media.extension, "epub");

// Test with pdf
let media = build_media_test_helper(get_test_pdf_path());
assert!(media.is_ok());
let media = media.unwrap();
assert_eq!(media.extension, "pdf");
}

fn build_media_test_helper(path: String) -> Result<Media, CoreError> {
let path = Path::new(&path);
let library_options = LibraryOptions {
convert_rar_to_zip: false,
hard_delete_conversions: false,
..Default::default()
};
let series_id = "series_id";
let config = Arc::new(StumpConfig::debug());

MediaBuilder::new(&path, series_id, library_options, &config).build()
}
}
27 changes: 21 additions & 6 deletions core/src/filesystem/media/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,45 +36,60 @@ where

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_is_accepted_cover_name() {
let cover_file_names = ["cover", "thumbnail", "folder"];
for cover_name in cover_file_names {
assert!(super::is_accepted_cover_name(cover_name));
assert!(is_accepted_cover_name(cover_name));
}
}

#[test]
fn test_is_not_accepted_cover_name() {
let cover_file_names = vec!["cover1", "thumbnail1", "folder1"];
for cover_name in cover_file_names {
assert!(!super::is_accepted_cover_name(cover_name));
assert!(!is_accepted_cover_name(cover_name));
}
}

#[test]
fn test_sort_numeric_file_names() {
let mut names = ["3.jpg", "1.jpg", "5.jpg", "2.jpg", "4.jpg"];
super::sort_file_names(&mut names);
sort_file_names(&mut names);
let expected = ["1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg"];
assert_eq!(names, expected);
}

#[test]
fn test_sort_alphanumeric_file_names() {
let mut names = ["shot-2", "shot-1", "shot-11", "shot-10", "shot-3"];
super::sort_file_names(&mut names);
sort_file_names(&mut names);
let expected = ["shot-1", "shot-2", "shot-3", "shot-10", "shot-11"];
assert_eq!(names, expected);
}

#[test]
fn should_parse_incomplete_metadata() {
fn test_should_parse_incomplete_metadata() {
let contents = "<?xml version=\"1.0\"?>\n<ComicInfo xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n <Series>Delete</Series>\n <Number>1</Number>\n <Volume>2016</Volume>\n <Summary>In the near future, where science can implant or remove human memories and the government uses brain scan technology in criminal investigations, a mute girl witnesses a multiple murder and must turn to a handyman for protection from the police and an army of killers. From the Harley Quinn team of writers Jimmy Palmiotti and Justin Grey and artist John Timms, with covers by Amanda Conner.\n\n\nNote: The digital edition (3/2/2016) for this issue was released before the print edition.</Summary>\n <Notes>Tagged with ComicTagger 1.3.0-alpha.0 using info from Comic Vine on 2021-12-01 20:34:52. [Issue ID 517895]</Notes>\n <Year>2016</Year>\n <Month>03</Month>\n <Day>31</Day>\n <Writer>Jimmy Palmiotti, Justin Gray</Writer>\n <Penciller>John Timms, John Timms</Penciller>\n <Inker>John Timms, John Timms</Inker>\n <Colorist>David Curiel, Paul Mounts</Colorist>\n <Letterer>Bill Tortolini</Letterer>\n <CoverArtist>Amanda Conner, Paul Mounts</CoverArtist>\n <Editor>Alex Wald, Joanne Starer</Editor>\n <Publisher>1First Comics</Publisher>\n <Web>https://comicvine.gamespot.com/delete-1/4000-517895/</Web>\n <PageCount>27</PageCount>\n <ScanInformation>(digital) (Son of Ultron-Empire)</ScanInformation>\n <Pages>\n <Page Image=\"0\" ImageSize=\"907332\" Type=\"FrontCover\" />\n <Page Image=\"1\" ImageSize=\"431378\" />\n <Page Image=\"2\" ImageSize=\"776720\" />\n <Page Image=\"3\" ImageSize=\"524902\" />\n <Page Image=\"4\" ImageSize=\"753942\" />\n <Page Image=\"5\" ImageSize=\"607990\" />\n <Page Image=\"6\" ImageSize=\"438880\" />\n <Page Image=\"7\" ImageSize=\"504806\" />\n <Page Image=\"8\" ImageSize=\"532746\" />\n <Page Image=\"9\" ImageSize=\"542816\" />\n <Page Image=\"10\" ImageSize=\"571650\" />\n <Page Image=\"11\" ImageSize=\"626656\" />\n <Page Image=\"12\" ImageSize=\"605810\" />\n <Page Image=\"13\" ImageSize=\"585234\" />\n <Page Image=\"14\" ImageSize=\"553270\" />\n <Page Image=\"15\" ImageSize=\"440568\" />\n <Page Image=\"16\" ImageSize=\"483816\" />\n <Page Image=\"17\" ImageSize=\"492922\" />\n <Page Image=\"18\" ImageSize=\"470748\" />\n <Page Image=\"19\" ImageSize=\"644256\" />\n <Page Image=\"20\" ImageSize=\"584142\" />\n <Page Image=\"21\" ImageSize=\"425322\" />\n <Page Image=\"22\" ImageSize=\"565166\" />\n <Page Image=\"23\" ImageSize=\"582706\" />\n <Page Image=\"24\" ImageSize=\"507370\" />\n <Page Image=\"25\" ImageSize=\"489280\" />\n <Page Image=\"26\" ImageSize=\"519906\" />\n </Pages>\n</ComicInfo>";
let metadata = super::metadata_from_buf(contents.to_string()).unwrap();
let metadata = metadata_from_buf(contents.to_string()).unwrap();

assert_eq!(metadata.series, Some("Delete".to_string()));
assert_eq!(metadata.number, Some(1f64));
assert_eq!(metadata.volume, Some(2016));
}

#[test]
fn test_malformed_media_xml() {
// An empty string
let contents = String::from("");
let metadata = metadata_from_buf(contents);
assert!(metadata.is_none());

// Something JSON-ish instead of xml
let contents = String::from("metadata: { contents: oops }");
let metadata = metadata_from_buf(contents);
assert!(metadata.is_none());
}
}
46 changes: 46 additions & 0 deletions core/src/filesystem/media/epub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,49 @@ pub(crate) fn normalize_resource_path(path: PathBuf, root: &str) -> PathBuf {

PathBuf::from(adjusted_str)
}

#[cfg(test)]
mod tests {
use super::*;
use crate::filesystem::media::tests::get_test_epub_path;

#[test]
fn test_process() {
let path = get_test_epub_path();
let config = StumpConfig::debug();

let processed_file = EpubProcessor::process(
&path,
FileProcessorOptions {
convert_rar_to_zip: false,
delete_conversion_source: false,
},
&config,
);
assert!(processed_file.is_ok());
}

#[test]
fn test_get_page_content_types() {
let path = get_test_epub_path();

let cover = EpubProcessor::get_page_content_types(&path, vec![1]);
assert!(cover.is_ok());
}

#[test]
fn test_get_cover() {
let path = get_test_epub_path();

let cover = EpubProcessor::get_cover(&path);
assert!(cover.is_ok());
}

#[test]
fn test_get_chapter() {
let path = get_test_epub_path();

let chapter = EpubProcessor::get_chapter(&path, 1);
assert!(chapter.is_ok());
}
}
46 changes: 46 additions & 0 deletions core/src/filesystem/media/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,49 @@ pub use process::{
get_content_types_for_pages, get_page, process, FileProcessor, FileProcessorOptions,
ProcessedFile, SeriesJson,
};

#[cfg(test)]
mod tests {
use std::{fs, path::PathBuf};

pub fn get_test_zip_path() -> String {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("integration-tests/data/book.zip")
.to_string_lossy()
.to_string()
}

pub fn get_test_rar_path() -> String {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("integration-tests/data/book.rar")
.to_string_lossy()
.to_string()
}

pub fn get_test_rar_file_data() -> Vec<u8> {
let test_rar_path = get_test_rar_path();

fs::read(test_rar_path).expect("Failed to fetch test rar file")
}

pub fn get_test_epub_path() -> String {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("integration-tests/data/book.epub")
.to_string_lossy()
.to_string()
}

pub fn get_test_pdf_path() -> String {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("integration-tests/data/rust_book.pdf")
.to_string_lossy()
.to_string()
}

pub fn get_test_cbz_path() -> String {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("integration-tests/data/science_comics_001.cbz")
.to_string_lossy()
.to_string()
}
}
Loading
Loading