parent
d7384476d4
commit
1bc612dc45
@ -1,12 +1,17 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
def main(coverage_file, fail_under):
|
def main(coverage_file, fail_under):
|
||||||
with open(coverage_file, encoding="utf-8") as f:
|
with open(coverage_file, encoding="utf-8") as f:
|
||||||
coverage_json = json.load(f)
|
coverage_json = json.load(f)
|
||||||
coverage = float(coverage_json["message"][:-1])
|
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__":
|
if __name__ == "__main__":
|
||||||
|
@ -20,6 +20,19 @@ pub trait IDatabase {
|
|||||||
fn save<S: Serialize + 'static>(&mut self, collection: &S) -> Result<(), SaveError>;
|
fn save<S: Serialize + 'static>(&mut self, collection: &S) -> Result<(), SaveError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Non-implementation defined to make generics easier.
|
||||||
|
pub struct NoDatabase {}
|
||||||
|
|
||||||
|
impl IDatabase for NoDatabase {
|
||||||
|
fn load<D: DeserializeOwned + 'static>(&self, _collection: &mut D) -> Result<(), LoadError> {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save<S: Serialize + 'static>(&mut self, _collection: &S) -> Result<(), SaveError> {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Error type for database calls.
|
/// Error type for database calls.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum LoadError {
|
pub enum LoadError {
|
||||||
@ -76,7 +89,23 @@ impl From<std::io::Error> for SaveError {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use std::io;
|
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<String> = vec![];
|
||||||
|
_ = database.load(&mut collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn no_database_save() {
|
||||||
|
let mut database = NoDatabase {};
|
||||||
|
let collection: Vec<String> = vec![];
|
||||||
|
_ = database.save(&collection);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn errors() {
|
fn errors() {
|
||||||
|
89
src/lib.rs
89
src/lib.rs
@ -460,14 +460,14 @@ impl From<InvalidUrlError> for Error {
|
|||||||
/// The Music Hoard. It is responsible for pulling information from both the library and the
|
/// The Music Hoard. It is responsible for pulling information from both the library and the
|
||||||
/// database, ensuring its consistent and writing back any changes.
|
/// database, ensuring its consistent and writing back any changes.
|
||||||
pub struct MusicHoard<LIB, DB> {
|
pub struct MusicHoard<LIB, DB> {
|
||||||
library: LIB,
|
library: Option<LIB>,
|
||||||
database: DB,
|
database: Option<DB>,
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<LIB: ILibrary, DB: IDatabase> MusicHoard<LIB, DB> {
|
impl<LIB: ILibrary, DB: IDatabase> MusicHoard<LIB, DB> {
|
||||||
/// Create a new [`MusicHoard`] with the provided [`ILibrary`] and [`IDatabase`].
|
/// Create a new [`MusicHoard`] with the provided [`ILibrary`] and [`IDatabase`].
|
||||||
pub fn new(library: LIB, database: DB) -> Self {
|
pub fn new(library: Option<LIB>, database: Option<DB>) -> Self {
|
||||||
MusicHoard {
|
MusicHoard {
|
||||||
library,
|
library,
|
||||||
database,
|
database,
|
||||||
@ -476,31 +476,46 @@ impl<LIB: ILibrary, DB: IDatabase> MusicHoard<LIB, DB> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn rescan_library(&mut self) -> Result<(), Error> {
|
pub fn rescan_library(&mut self) -> Result<(), Error> {
|
||||||
let items = self.library.list(&Query::new())?;
|
match self.library {
|
||||||
let mut library = Self::items_to_artists(items);
|
Some(ref mut library) => {
|
||||||
Self::sort(&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);
|
let collection = mem::take(&mut self.collection);
|
||||||
self.collection = Self::merge(library, 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> {
|
pub fn load_from_database(&mut self) -> Result<(), Error> {
|
||||||
let mut database: Collection = vec![];
|
match self.database {
|
||||||
self.database.load(&mut database)?;
|
Some(ref mut database) => {
|
||||||
Self::sort(&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);
|
let collection = mem::take(&mut self.collection);
|
||||||
self.collection = Self::merge(collection, database);
|
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> {
|
pub fn save_to_database(&mut self) -> Result<(), Error> {
|
||||||
self.database.save(&self.collection)?;
|
match self.database {
|
||||||
|
Some(ref mut database) => {
|
||||||
|
database.save(&self.collection)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
None => Err(Error::DatabaseError(String::from("database not provided"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_collection(&self) -> &Collection {
|
pub fn get_collection(&self) -> &Collection {
|
||||||
&self.collection
|
&self.collection
|
||||||
@ -601,7 +616,10 @@ mod tests {
|
|||||||
use mockall::predicate;
|
use mockall::predicate;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use crate::{database::MockIDatabase, library::MockILibrary};
|
use crate::{
|
||||||
|
database::{MockIDatabase, NoDatabase},
|
||||||
|
library::{MockILibrary, NoLibrary},
|
||||||
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -834,7 +852,7 @@ mod tests {
|
|||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|_| library_result);
|
.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();
|
music_hoard.rescan_library().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -861,7 +879,7 @@ mod tests {
|
|||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|_| library_result);
|
.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();
|
music_hoard.rescan_library().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -888,7 +906,7 @@ mod tests {
|
|||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|_| library_result);
|
.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();
|
music_hoard.rescan_library().unwrap();
|
||||||
assert_eq!(music_hoard.get_collection(), &expected);
|
assert_eq!(music_hoard.get_collection(), &expected);
|
||||||
@ -907,7 +925,7 @@ mod tests {
|
|||||||
Ok(())
|
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();
|
music_hoard.load_from_database().unwrap();
|
||||||
assert_eq!(music_hoard.get_collection(), &*COLLECTION);
|
assert_eq!(music_hoard.get_collection(), &*COLLECTION);
|
||||||
@ -936,7 +954,7 @@ mod tests {
|
|||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|_: &Collection| database_result);
|
.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();
|
music_hoard.rescan_library().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -946,6 +964,18 @@ mod tests {
|
|||||||
music_hoard.save_to_database().unwrap();
|
music_hoard.save_to_database().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn library_not_provided() {
|
||||||
|
let library: Option<NoLibrary> = 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]
|
#[test]
|
||||||
fn library_error() {
|
fn library_error() {
|
||||||
let mut library = MockILibrary::new();
|
let mut library = MockILibrary::new();
|
||||||
@ -958,7 +988,7 @@ mod tests {
|
|||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|_| library_result);
|
.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 actual_err = music_hoard.rescan_library().unwrap_err();
|
||||||
let expected_err =
|
let expected_err =
|
||||||
@ -968,6 +998,23 @@ mod tests {
|
|||||||
assert_eq!(actual_err.to_string(), expected_err.to_string());
|
assert_eq!(actual_err.to_string(), expected_err.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn database_not_provided() {
|
||||||
|
let library = Some(MockILibrary::new());
|
||||||
|
let database: Option<NoDatabase> = 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]
|
#[test]
|
||||||
fn database_load_error() {
|
fn database_load_error() {
|
||||||
let library = MockILibrary::new();
|
let library = MockILibrary::new();
|
||||||
@ -980,7 +1027,7 @@ mod tests {
|
|||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|_: &mut Collection| database_result);
|
.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 actual_err = music_hoard.load_from_database().unwrap_err();
|
||||||
let expected_err = Error::DatabaseError(
|
let expected_err = Error::DatabaseError(
|
||||||
@ -1003,7 +1050,7 @@ mod tests {
|
|||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|_: &Collection| database_result);
|
.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 actual_err = music_hoard.save_to_database().unwrap_err();
|
||||||
let expected_err = Error::DatabaseError(
|
let expected_err = Error::DatabaseError(
|
||||||
|
@ -17,6 +17,15 @@ pub trait ILibrary {
|
|||||||
fn list(&mut self, query: &Query) -> Result<Vec<Item>, Error>;
|
fn list(&mut self, query: &Query) -> Result<Vec<Item>, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Non-implementation defined to make generics easier.
|
||||||
|
pub struct NoLibrary {}
|
||||||
|
|
||||||
|
impl ILibrary for NoLibrary {
|
||||||
|
fn list(&mut self, _query: &Query) -> Result<Vec<Item>, Error> {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An item from the library. An item corresponds to an individual file (usually a single track).
|
/// An item from the library. An item corresponds to an individual file (usually a single track).
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
@ -127,7 +136,14 @@ impl From<Utf8Error> for Error {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use std::io;
|
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]
|
#[test]
|
||||||
fn query() {
|
fn query() {
|
||||||
|
@ -42,7 +42,7 @@ struct Opt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn with<LIB: ILibrary, DB: IDatabase>(lib: LIB, db: DB) {
|
fn with<LIB: ILibrary, DB: IDatabase>(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.
|
// Initialize the terminal user interface.
|
||||||
let backend = CrosstermBackend::new(io::stdout());
|
let backend = CrosstermBackend::new(io::stdout());
|
||||||
|
@ -30,7 +30,7 @@ fn merge_library_then_database() {
|
|||||||
let backend = JsonDatabaseFileBackend::new(&*database::json::DATABASE_TEST_FILE);
|
let backend = JsonDatabaseFileBackend::new(&*database::json::DATABASE_TEST_FILE);
|
||||||
let database = JsonDatabase::new(backend);
|
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.rescan_library().unwrap();
|
||||||
music_hoard.load_from_database().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 backend = JsonDatabaseFileBackend::new(&*database::json::DATABASE_TEST_FILE);
|
||||||
let database = JsonDatabase::new(backend);
|
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.load_from_database().unwrap();
|
||||||
music_hoard.rescan_library().unwrap();
|
music_hoard.rescan_library().unwrap();
|
||||||
|
Loading…
Reference in New Issue
Block a user