diff --git a/.gitignore b/.gitignore index ea8c4bf..eadf8f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/codecov diff --git a/README.md b/README.md index 067a845..30b4483 100644 --- a/README.md +++ b/README.md @@ -12,21 +12,23 @@ cargo install grcov ### Generating Code Coverage ```sh -cargo clean +env CARGO_TARGET_DIR=codecov \ + cargo clean env RUSTFLAGS="-C instrument-coverage" \ - LLVM_PROFILE_FILE="target/debug/profraw/musichoard-%p-%m.profraw" \ + LLVM_PROFILE_FILE="codecov/debug/profraw/musichoard-%p-%m.profraw" \ + CARGO_TARGET_DIR=codecov \ cargo test -grcov target/debug/profraw \ - --binary-path ./target/debug/ \ +grcov codecov/debug/profraw \ + --binary-path ./codecov/debug/ \ --output-types html \ --source-dir . \ --ignore-not-existing \ --ignore "tests/*" \ --ignore "src/main.rs" \ --excl-start "mod tests \{" \ - --output-path ./target/debug/coverage/ -xdg-open target/debug/coverage/index.html + --output-path ./codecov/debug/coverage/ +xdg-open codecov/debug/coverage/index.html ``` -Note that some changes may not be visible until `target/debug/coverage` is removed and the `grcov` +Note that some changes may not be visible until `codecov/debug/coverage` is removed and the `grcov` command is rerun. diff --git a/src/collection/mod.rs b/src/collection/mod.rs index 7f72d72..b1d30e8 100644 --- a/src/collection/mod.rs +++ b/src/collection/mod.rs @@ -12,7 +12,7 @@ use crate::{ pub type Collection = Vec; /// Error type for collection manager. -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum Error { /// The [`CollectionManager`] failed to read/write from/to the library. LibraryError(String), @@ -81,3 +81,91 @@ impl CollectionManager { &self.collection } } + +#[cfg(test)] +mod tests { + use mockall::predicate; + + use crate::{ + database::{self, MockDatabase}, + library::{self, MockLibrary, Query}, + tests::COLLECTION, + }; + + use super::{CollectionManager, Error}; + + #[test] + fn read_get_write() { + let mut library = MockLibrary::new(); + let mut database = MockDatabase::new(); + + let library_input = Query::default(); + let library_result = Ok(COLLECTION.to_owned()); + + let database_input = COLLECTION.to_owned(); + let database_result = Ok(()); + + library + .expect_list() + .with(predicate::eq(library_input)) + .times(1) + .return_once(|_| library_result); + + database + .expect_write() + .with(predicate::eq(database_input)) + .times(1) + .return_once(|_| database_result); + + let mut collection_manager = CollectionManager::new(Box::new(library), Box::new(database)); + + collection_manager.rescan_library().unwrap(); + assert_eq!(collection_manager.get_collection(), &*COLLECTION); + collection_manager.save_to_database().unwrap(); + } + + #[test] + fn library_error() { + let mut library = MockLibrary::new(); + let database = MockDatabase::new(); + + let library_result = Err(library::Error::InvalidData(String::from("invalid data"))); + + library + .expect_list() + .times(1) + .return_once(|_| library_result); + + let mut collection_manager = CollectionManager::new(Box::new(library), Box::new(database)); + + let actual_err = collection_manager.rescan_library().unwrap_err(); + let expected_err = Error::LibraryError( + library::Error::InvalidData(String::from("invalid data")).to_string(), + ); + + assert_eq!(actual_err, expected_err); + assert_eq!(actual_err.to_string(), expected_err.to_string()); + } + + #[test] + fn database_error() { + let library = MockLibrary::new(); + let mut database = MockDatabase::new(); + + let database_result = Err(database::Error::IoError(String::from("I/O error"))); + + database + .expect_write() + .times(1) + .return_once(|_| database_result); + + let mut collection_manager = CollectionManager::new(Box::new(library), Box::new(database)); + + let actual_err = collection_manager.save_to_database().unwrap_err(); + let expected_err = + Error::DatabaseError(database::Error::IoError(String::from("I/O error")).to_string()); + + assert_eq!(actual_err, expected_err); + assert_eq!(actual_err.to_string(), expected_err.to_string()); + } +} diff --git a/src/database/json.rs b/src/database/json.rs index 6c611d5..52955f8 100644 --- a/src/database/json.rs +++ b/src/database/json.rs @@ -8,7 +8,7 @@ use crate::collection::Collection; #[cfg(test)] use mockall::automock; -use super::{Database, DatabaseRead, DatabaseWrite, Error}; +use super::{Database, Error}; impl From for Error { fn from(err: serde_json::Error) -> Error { @@ -38,15 +38,13 @@ impl JsonDatabase { } } -impl DatabaseRead for JsonDatabase { +impl Database for JsonDatabase { fn read(&self, collection: &mut Collection) -> Result<(), Error> { let serialized = self.backend.read()?; *collection = serde_json::from_str(&serialized)?; Ok(()) } -} -impl DatabaseWrite for JsonDatabase { fn write(&mut self, collection: &Collection) -> Result<(), Error> { let serialized = serde_json::to_string(&collection)?; self.backend.write(&serialized)?; @@ -54,8 +52,6 @@ impl DatabaseWrite for JsonDatabase { } } -impl Database for JsonDatabase {} - /// JSON database backend that uses a local file for persistent storage. pub struct JsonDatabaseFileBackend { path: PathBuf, diff --git a/src/database/mod.rs b/src/database/mod.rs index bdfdd51..28aff71 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -2,6 +2,9 @@ use std::fmt; +#[cfg(test)] +use mockall::automock; + use crate::collection::Collection; pub mod json; @@ -32,17 +35,12 @@ impl From for Error { } } -/// Trait for database reads. -pub trait DatabaseRead { +/// Trait for interacting with the database. +#[cfg_attr(test, automock)] +pub trait Database { /// Read collection from the database. fn read(&self, collection: &mut Collection) -> Result<(), Error>; -} -/// Trait for database writes. -pub trait DatabaseWrite { /// Write collection to the database. fn write(&mut self, collection: &Collection) -> Result<(), Error>; } - -/// Trait for database reads and writes. -pub trait Database: DatabaseRead + DatabaseWrite {} diff --git a/src/library/mod.rs b/src/library/mod.rs index 1499097..8136c22 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -2,11 +2,15 @@ use std::{num::ParseIntError, str::Utf8Error, fmt}; +#[cfg(test)] +use mockall::automock; + use crate::Artist; pub mod beets; /// A single query option. +#[derive(Debug, PartialEq, Eq)] pub enum QueryOption { /// Inclusive query. Include(T), @@ -36,7 +40,7 @@ impl Default for QueryOption { } /// Options for refining library queries. -#[derive(Default)] +#[derive(Debug, Default, PartialEq, Eq)] pub struct Query { album_artist: QueryOption, album_year: QueryOption, @@ -142,6 +146,7 @@ impl From for Error { } /// Trait for interacting with the music library. +#[cfg_attr(test, automock)] pub trait Library { /// List lirbary items that match the a specific query. fn list(&mut self, query: &Query) -> Result, Error>; diff --git a/tests/database/json.rs b/tests/database/json.rs index a31996d..e28d870 100644 --- a/tests/database/json.rs +++ b/tests/database/json.rs @@ -3,7 +3,7 @@ use std::{fs, path::PathBuf}; use musichoard::{ database::{ json::{JsonDatabase, JsonDatabaseFileBackend}, - DatabaseRead, DatabaseWrite, + Database, }, Artist, };