diff --git a/datman/src/bin/datman.rs b/datman/src/bin/datman.rs index eeac400..1628ab6 100644 --- a/datman/src/bin/datman.rs +++ b/datman/src/bin/datman.rs @@ -30,6 +30,13 @@ use datman::remote::backup_source_requester::backup_remote_source_to_destination use datman::remote::backup_source_responder; use std::str::FromStr; +pub const FAILURE_SYMBOL_OBNOXIOUS_FLASHING: &str = "\x1b[5m\x1b[31m⚠️ \x1b[25m\x1b[22m"; +pub const BOLD: &str = "\x1b[1m"; +pub const BOLD_OFF: &str = "\x1b[22m"; +pub const WHITE: &str = "\x1b[37m"; +pub const RED: &str = "\x1b[31m"; +pub const GREEN: &str = "\x1b[32m"; + #[derive(Clap)] pub enum DatmanCommand { /// Initialise a datman descriptor in this directory. @@ -127,6 +134,33 @@ impl FromStr for HumanDateTime { } } +fn with_obvious_successfail_message(result: anyhow::Result) -> anyhow::Result { + match &result { + Ok(_) => { + eprintln!("Operation {}successful{}.", GREEN, WHITE); + } + Err(error) => { + eprintln!("{:?}", error); + eprintln!( + "{}{}Operation {}{}FAILED{}!{}", + FAILURE_SYMBOL_OBNOXIOUS_FLASHING, WHITE, RED, BOLD, WHITE, BOLD_OFF + ); + } + }; + result +} + +fn with_exitcode(result: anyhow::Result) { + match &result { + Ok(_) => { + std::process::exit(0); + } + Err(_) => { + std::process::exit(5); + } + }; +} + fn main() -> anyhow::Result<()> { env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); @@ -168,7 +202,7 @@ fn main() -> anyhow::Result<()> { } } - if let Some(remote_name) = remote { + let result = if let Some(remote_name) = remote { let remote_host_descriptor = if let Some(rhd) = descriptor.remote_hosts.get(&remote_name) { rhd @@ -185,7 +219,6 @@ fn main() -> anyhow::Result<()> { remote_host_descriptor, yama::utils::get_number_of_workers("YAMA_CHUNKERS"), ) - .unwrap(); } else { backup_source_to_destination( source, @@ -196,8 +229,8 @@ fn main() -> anyhow::Result<()> { &destination_name, yama::utils::get_number_of_workers("YAMA_CHUNKERS"), ) - .unwrap(); - } + }; + with_exitcode(with_obvious_successfail_message(result)) } DatmanCommand::BackupAll { destination_name } => { let descriptor = load_descriptor(Path::new(".")).unwrap(); diff --git a/testsuite/datmantests/test_backup_and_extract.py b/testsuite/datmantests/test_backup_and_extract.py index 58b61c7..c24acf9 100644 --- a/testsuite/datmantests/test_backup_and_extract.py +++ b/testsuite/datmantests/test_backup_and_extract.py @@ -5,7 +5,7 @@ from tempfile import TemporaryDirectory from unittest import TestCase from helpers import DirectoryDescriptor, generate_random_dir, scan_dir -from helpers.datman_helpers import set_up_simple_datman +from helpers.datman_helpers import get_hostname, set_up_simple_datman from helpers.yama_helpers import set_up_simple_yama @@ -59,3 +59,71 @@ class TestBackupAndExtract(TestCase): self.assertEqual( value.ignore_metadata(), later_expected_descriptor.ignore_metadata() ) + + def test_backup_failure_is_loud(self): + """ + Tests that backup failure is noticeable. + """ + + td = TemporaryDirectory("test_backup_failure_is_loud") + tdpath = Path(td.name) + + datman_path = tdpath.joinpath("datman") + src_path = datman_path.joinpath("srca") + yama_path = datman_path.joinpath("main") + + set_up_simple_datman( + datman_path, + f""" +[source.srcimpossible] +directory = "/path/to/absolutely/nowhere" +hostname = "{get_hostname()}" + +[source.srcimpossible2] +directory = "/path/to/absolutely/nowhere" +hostname = "notmymachine" + +[source.srcimpossible3] +helper = "failedhelper" +label = "precious" +kind = {{ stdout = "blahblah.txt" }} + + """, + ) + set_up_simple_yama(yama_path) + + rng = Random() + seed = rng.randint(0, 9001) + print(f"seed: {seed}") + rng.seed(seed) + later_expected_descriptor, _ = generate_random_dir(rng, src_path, 32) + + impossible_proc = subprocess.run( + ("datman", "backup-one", "srcimpossible", "main"), + cwd=datman_path, + stderr=subprocess.PIPE, + ) + self.assertNotEqual(impossible_proc.returncode, 0) + last_line = impossible_proc.stderr.decode().split("\n")[-2] + self.assertIn("\x1b[31m\x1b[1mFAILED", last_line) + + # NOT YET SUPPORTED + print("TEST FOR REMOTE SRC LOUDNESS NOT YET SUPPORTED") + # impossible_proc = subprocess.run(("datman", "backup-one", + # "srcimpossible2", "main"), + # cwd=datman_path, + # stderr=subprocess.PIPE) + # self.assertNotEqual(impossible_proc.returncode, 0) + # last_line = impossible_proc.stderr.decode().split("\n")[-2] + # self.assertIn("\x1b[31m\x1b[1mFAILED", last_line) + + impossible_proc = subprocess.run( + ("datman", "backup-one", "srcimpossible3", "main"), + cwd=datman_path, + stderr=subprocess.PIPE, + ) + self.assertNotEqual(impossible_proc.returncode, 0) + last_line = impossible_proc.stderr.decode().split("\n")[-2] + self.assertIn("\x1b[31m\x1b[1mFAILED", last_line) + + td.cleanup() diff --git a/testsuite/helpers/datman_helpers.py b/testsuite/helpers/datman_helpers.py index cb7c8c8..26f297b 100644 --- a/testsuite/helpers/datman_helpers.py +++ b/testsuite/helpers/datman_helpers.py @@ -1,12 +1,13 @@ import subprocess from pathlib import Path +from typing import Optional def get_hostname(): return subprocess.check_output("hostname").strip().decode() -def set_up_simple_datman(path: Path): +def set_up_simple_datman(path: Path, custom_extra_test: Optional[str]): path.mkdir(exist_ok=True) subprocess.check_call(("datman", "init"), cwd=path) @@ -22,3 +23,5 @@ path = "main" included_labels = ["precious"] """ ) + if custom_extra_test: + file.write(custom_extra_test)