From 1bc612dc454c4a93d3949edd288e31dc62b6cd0c Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sun, 7 Jan 2024 11:07:35 +0100 Subject: [PATCH] Make database and library optional (#86) Closes #25 Reviewed-on: https://git.thenineworlds.net/wojtek/musichoard/pulls/86 --- .gitea/scripts/coverage.py | 7 ++- src/database/mod.rs | 31 +++++++++++- src/lib.rs | 99 ++++++++++++++++++++++++++++---------- src/library/mod.rs | 18 ++++++- src/main.rs | 2 +- tests/lib.rs | 4 +- 6 files changed, 129 insertions(+), 32 deletions(-) diff --git a/.gitea/scripts/coverage.py b/.gitea/scripts/coverage.py index 944be8c..b83cd82 100644 --- a/.gitea/scripts/coverage.py +++ b/.gitea/scripts/coverage.py @@ -1,12 +1,17 @@ import argparse import json +import sys def main(coverage_file, fail_under): with open(coverage_file, encoding="utf-8") as f: coverage_json = json.load(f) coverage = float(coverage_json["message"][:-1]) - return coverage >= fail_under + print(f"Code coverage: {coverage:.2f}%; Threshold: {fail_under:.2f}%") + success = coverage >= fail_under + if coverage < fail_under: + print("Insufficient code coverage", file=sys.stderr) + return success if __name__ == "__main__": diff --git a/src/database/mod.rs b/src/database/mod.rs index 3846314..6c0c0b4 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -20,6 +20,19 @@ pub trait IDatabase { fn save(&mut self, collection: &S) -> Result<(), SaveError>; } +/// Non-implementation defined to make generics easier. +pub struct NoDatabase {} + +impl IDatabase for NoDatabase { + fn load(&self, _collection: &mut D) -> Result<(), LoadError> { + panic!() + } + + fn save(&mut self, _collection: &S) -> Result<(), SaveError> { + panic!() + } +} + /// Error type for database calls. #[derive(Debug)] pub enum LoadError { @@ -76,7 +89,23 @@ impl From for SaveError { mod tests { use std::io; - use super::{LoadError, SaveError}; + use super::{IDatabase, LoadError, NoDatabase, SaveError}; + + #[test] + #[should_panic] + fn no_database_load() { + let database = NoDatabase {}; + let mut collection: Vec = vec![]; + _ = database.load(&mut collection); + } + + #[test] + #[should_panic] + fn no_database_save() { + let mut database = NoDatabase {}; + let collection: Vec = vec![]; + _ = database.save(&collection); + } #[test] fn errors() { diff --git a/src/lib.rs b/src/lib.rs index 2adafd6..93fe3c6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -460,14 +460,14 @@ impl From for Error { /// The Music Hoard. It is responsible for pulling information from both the library and the /// database, ensuring its consistent and writing back any changes. pub struct MusicHoard { - library: LIB, - database: DB, + library: Option, + database: Option, collection: Collection, } impl MusicHoard { /// Create a new [`MusicHoard`] with the provided [`ILibrary`] and [`IDatabase`]. - pub fn new(library: LIB, database: DB) -> Self { + pub fn new(library: Option, database: Option) -> Self { MusicHoard { library, database, @@ -476,30 +476,45 @@ impl MusicHoard { } pub fn rescan_library(&mut self) -> Result<(), Error> { - let items = self.library.list(&Query::new())?; - let mut library = Self::items_to_artists(items); - Self::sort(&mut library); + match self.library { + Some(ref mut library) => { + let items = library.list(&Query::new())?; + let mut library_collection = Self::items_to_artists(items); + Self::sort(&mut library_collection); - let collection = mem::take(&mut self.collection); - self.collection = Self::merge(library, collection); + let collection = mem::take(&mut self.collection); + self.collection = Self::merge(library_collection, collection); - Ok(()) + Ok(()) + } + None => Err(Error::LibraryError(String::from("library not provided"))), + } } pub fn load_from_database(&mut self) -> Result<(), Error> { - let mut database: Collection = vec![]; - self.database.load(&mut database)?; - Self::sort(&mut database); + match self.database { + Some(ref mut database) => { + let mut database_collection: Collection = vec![]; + database.load(&mut database_collection)?; + Self::sort(&mut database_collection); - let collection = mem::take(&mut self.collection); - self.collection = Self::merge(collection, database); + let collection = mem::take(&mut self.collection); + self.collection = Self::merge(collection, database_collection); - Ok(()) + Ok(()) + } + None => Err(Error::DatabaseError(String::from("database not provided"))), + } } pub fn save_to_database(&mut self) -> Result<(), Error> { - self.database.save(&self.collection)?; - Ok(()) + match self.database { + Some(ref mut database) => { + database.save(&self.collection)?; + Ok(()) + } + None => Err(Error::DatabaseError(String::from("database not provided"))), + } } pub fn get_collection(&self) -> &Collection { @@ -601,7 +616,10 @@ mod tests { use mockall::predicate; use once_cell::sync::Lazy; - use crate::{database::MockIDatabase, library::MockILibrary}; + use crate::{ + database::{MockIDatabase, NoDatabase}, + library::{MockILibrary, NoLibrary}, + }; use super::*; @@ -834,7 +852,7 @@ mod tests { .times(1) .return_once(|_| library_result); - let mut music_hoard = MusicHoard::new(library, database); + let mut music_hoard = MusicHoard::new(Some(library), Some(database)); music_hoard.rescan_library().unwrap(); assert_eq!( @@ -861,7 +879,7 @@ mod tests { .times(1) .return_once(|_| library_result); - let mut music_hoard = MusicHoard::new(library, database); + let mut music_hoard = MusicHoard::new(Some(library), Some(database)); music_hoard.rescan_library().unwrap(); assert_eq!( @@ -888,7 +906,7 @@ mod tests { .times(1) .return_once(|_| library_result); - let mut music_hoard = MusicHoard::new(library, database); + let mut music_hoard = MusicHoard::new(Some(library), Some(database)); music_hoard.rescan_library().unwrap(); assert_eq!(music_hoard.get_collection(), &expected); @@ -907,7 +925,7 @@ mod tests { Ok(()) }); - let mut music_hoard = MusicHoard::new(library, database); + let mut music_hoard = MusicHoard::new(Some(library), Some(database)); music_hoard.load_from_database().unwrap(); assert_eq!(music_hoard.get_collection(), &*COLLECTION); @@ -936,7 +954,7 @@ mod tests { .times(1) .return_once(|_: &Collection| database_result); - let mut music_hoard = MusicHoard::new(library, database); + let mut music_hoard = MusicHoard::new(Some(library), Some(database)); music_hoard.rescan_library().unwrap(); assert_eq!( @@ -946,6 +964,18 @@ mod tests { music_hoard.save_to_database().unwrap(); } + #[test] + fn library_not_provided() { + let library: Option = None; + let database = Some(MockIDatabase::new()); + let mut music_hoard = MusicHoard::new(library, database); + + let actual_err = music_hoard.rescan_library().unwrap_err(); + let expected_err = Error::LibraryError(String::from("library not provided")); + + assert_eq!(actual_err, expected_err); + } + #[test] fn library_error() { let mut library = MockILibrary::new(); @@ -958,7 +988,7 @@ mod tests { .times(1) .return_once(|_| library_result); - let mut music_hoard = MusicHoard::new(library, database); + let mut music_hoard = MusicHoard::new(Some(library), Some(database)); let actual_err = music_hoard.rescan_library().unwrap_err(); let expected_err = @@ -968,6 +998,23 @@ mod tests { assert_eq!(actual_err.to_string(), expected_err.to_string()); } + #[test] + fn database_not_provided() { + let library = Some(MockILibrary::new()); + let database: Option = None; + let mut music_hoard = MusicHoard::new(library, database); + + let expected_err = Error::DatabaseError(String::from("database not provided")); + + let actual_err = music_hoard.load_from_database().unwrap_err(); + assert_eq!(actual_err, expected_err); + assert_eq!(actual_err.to_string(), expected_err.to_string()); + + let actual_err = music_hoard.save_to_database().unwrap_err(); + assert_eq!(actual_err, expected_err); + assert_eq!(actual_err.to_string(), expected_err.to_string()); + } + #[test] fn database_load_error() { let library = MockILibrary::new(); @@ -980,7 +1027,7 @@ mod tests { .times(1) .return_once(|_: &mut Collection| database_result); - let mut music_hoard = MusicHoard::new(library, database); + let mut music_hoard = MusicHoard::new(Some(library), Some(database)); let actual_err = music_hoard.load_from_database().unwrap_err(); let expected_err = Error::DatabaseError( @@ -1003,7 +1050,7 @@ mod tests { .times(1) .return_once(|_: &Collection| database_result); - let mut music_hoard = MusicHoard::new(library, database); + let mut music_hoard = MusicHoard::new(Some(library), Some(database)); let actual_err = music_hoard.save_to_database().unwrap_err(); let expected_err = Error::DatabaseError( diff --git a/src/library/mod.rs b/src/library/mod.rs index 3e4ce7d..5f43671 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -17,6 +17,15 @@ pub trait ILibrary { fn list(&mut self, query: &Query) -> Result, Error>; } +/// Non-implementation defined to make generics easier. +pub struct NoLibrary {} + +impl ILibrary for NoLibrary { + fn list(&mut self, _query: &Query) -> Result, Error> { + panic!() + } +} + /// An item from the library. An item corresponds to an individual file (usually a single track). #[derive(Debug, PartialEq, Eq, Hash)] pub struct Item { @@ -127,7 +136,14 @@ impl From for Error { mod tests { use std::io; - use super::{Error, Field, Query}; + use super::{Error, Field, ILibrary, NoLibrary, Query}; + + #[test] + #[should_panic] + fn no_library_list() { + let mut library = NoLibrary {}; + _ = library.list(&Query::default()); + } #[test] fn query() { diff --git a/src/main.rs b/src/main.rs index c0be878..ac7ecd4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,7 +42,7 @@ struct Opt { } fn with(lib: LIB, db: DB) { - let music_hoard = MusicHoard::new(lib, db); + let music_hoard = MusicHoard::new(Some(lib), Some(db)); // Initialize the terminal user interface. let backend = CrosstermBackend::new(io::stdout()); diff --git a/tests/lib.rs b/tests/lib.rs index c1e74bd..f945f38 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -30,7 +30,7 @@ fn merge_library_then_database() { let backend = JsonDatabaseFileBackend::new(&*database::json::DATABASE_TEST_FILE); let database = JsonDatabase::new(backend); - let mut music_hoard = MusicHoard::new(library, database); + let mut music_hoard = MusicHoard::new(Some(library), Some(database)); music_hoard.rescan_library().unwrap(); music_hoard.load_from_database().unwrap(); @@ -55,7 +55,7 @@ fn merge_database_then_library() { let backend = JsonDatabaseFileBackend::new(&*database::json::DATABASE_TEST_FILE); let database = JsonDatabase::new(backend); - let mut music_hoard = MusicHoard::new(library, database); + let mut music_hoard = MusicHoard::new(Some(library), Some(database)); music_hoard.load_from_database().unwrap(); music_hoard.rescan_library().unwrap();