From 132952fb162f3fb5ecdc9fd32f806849d71f810f Mon Sep 17 00:00:00 2001 From: PhillypHenning Date: Wed, 15 Mar 2023 12:56:29 -0400 Subject: [PATCH 01/24] Adding OpenAI generated functional level tests --- scripts/plugins/utilities.py | 6 ++- scripts/tests/utilities_test.py | 67 +++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 scripts/tests/utilities_test.py diff --git a/scripts/plugins/utilities.py b/scripts/plugins/utilities.py index 59b1eded..c3c6012f 100644 --- a/scripts/plugins/utilities.py +++ b/scripts/plugins/utilities.py @@ -4,7 +4,7 @@ from typing import Union import yaml -from .settings import BITOPS_FAST_FAIL_MODE +from .settings import BITOPS_FAST_FAIL_MODE, BITOPS_RUN_MODE from .logging import logger, mask_message from .doc import get_doc @@ -56,10 +56,14 @@ def load_yaml(inc_yaml): with open(inc_yaml, "r", encoding="utf8") as stream: out_yaml = yaml.load(stream, Loader=yaml.FullLoader) except FileNotFoundError as e: + # if in test mode: + if BITOPS_RUN_MODE == "testing": + raise e msg, exit_code = get_doc("missing_required_file") logger.error(f"{msg} [{e.filename}]") logger.debug(e) sys.exit(exit_code) + return out_yaml diff --git a/scripts/tests/utilities_test.py b/scripts/tests/utilities_test.py new file mode 100644 index 00000000..da71c86a --- /dev/null +++ b/scripts/tests/utilities_test.py @@ -0,0 +1,67 @@ +import os +import unittest + +from ..plugins.utilities import add_value_to_env, load_yaml + + +class TestAddValueToEnv(unittest.TestCase): + def setUp(self): + self.export_env = "" + self.value = "" + os.environ.clear() + + def test_add_value_to_env_with_value(self): + """Test the add_value_to_env() function with valid value""" + self.export_env = "ANSIBLE_VERBOSITY" + self.value = "1" + + add_value_to_env(self.export_env, self.value) + self.assertEqual(os.environ[self.export_env], self.value) + self.assertEqual(os.environ["BITOPS_" + self.export_env], self.value) + + def test_add_value_to_env_with_none(self): + """Test the add_value_to_env() function with None""" + self.export_env = "ANSIBLE_VERBOSITY" + self.value = None + + add_value_to_env(self.export_env, self.value) + self.assertNotIn(self.export_env, os.environ) + self.assertNotIn("BITOPS_" + self.export_env, os.environ) + + def test_add_value_to_env_with_list(self): + """Test the add_value_to_env() function with a list""" + self.export_env = "ANSIBLE_VERBOSITY" + self.value = ["1", "2", "3"] + + add_value_to_env(self.export_env, self.value) + self.assertEqual(os.environ[self.export_env], " ".join(self.value)) + self.assertEqual(os.environ["BITOPS_" + self.export_env], " ".join(self.value)) + + +class TestLoadYAML(unittest.TestCase): + """ + Class for testing the load_yaml function. + """ + + def setUp(self): + root_dir = os.getcwd() + self.inc_yaml = f"{root_dir}/prebuilt-config/omnibus/bitops.config.yaml" + + def test_load_yaml(self): + """ + Test the load_yaml function. + """ + out_yaml = load_yaml(self.inc_yaml) + self.assertIsNotNone(out_yaml) + self.assertIsInstance(out_yaml, dict) + + # def test_load_yaml_with_invalid_filename(self): + # """ + # Test the load_yaml function with a non-existent file. + # """ + # with self.assertRaises(FileNotFoundError): + # load_yaml("invalid_file.yaml") + + +if __name__ == "__main__": + unittest.main() From a11a81d84985b462253b0f49163427f880f2feca Mon Sep 17 00:00:00 2001 From: PhillypHenning Date: Thu, 16 Mar 2023 16:55:27 -0400 Subject: [PATCH 02/24] Fixing naming and folder path --- .../test_utilities.py} | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) rename scripts/tests/{utilities_test.py => unit/test_utilities.py} (79%) diff --git a/scripts/tests/utilities_test.py b/scripts/tests/unit/test_utilities.py similarity index 79% rename from scripts/tests/utilities_test.py rename to scripts/tests/unit/test_utilities.py index da71c86a..2ba9cd36 100644 --- a/scripts/tests/utilities_test.py +++ b/scripts/tests/unit/test_utilities.py @@ -1,7 +1,8 @@ import os import unittest +import subprocess -from ..plugins.utilities import add_value_to_env, load_yaml +from ..plugins.utilities import add_value_to_env, load_yaml, run_cmd class TestAddValueToEnv(unittest.TestCase): @@ -63,5 +64,18 @@ def test_load_yaml(self): # load_yaml("invalid_file.yaml") +class TestRunCmd(unittest.TestCase): + def setUp(self): + self.command = "ls" + + def test_run_cmd(self): + process = run_cmd(self.command) + self.assertIsInstance(process, subprocess.Popen) + self.assertEqual(process.stdout, subprocess.PIPE) + self.assertEqual(process.stderr, subprocess.STDOUT) + self.assertTrue(process.universal_newlines) + self.assertIsNotNone(process.communicate()) + + if __name__ == "__main__": unittest.main() From f10468ff590ca2a12fd09b5ba22afe0d4b52574f Mon Sep 17 00:00:00 2001 From: PhillypHenning Date: Thu, 16 Mar 2023 17:06:48 -0400 Subject: [PATCH 03/24] Adding handle_hook tests --- scripts/tests/unit/test_utilities.py | 49 +++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/scripts/tests/unit/test_utilities.py b/scripts/tests/unit/test_utilities.py index 2ba9cd36..ec5a626c 100644 --- a/scripts/tests/unit/test_utilities.py +++ b/scripts/tests/unit/test_utilities.py @@ -1,8 +1,9 @@ import os import unittest import subprocess +from unittest.mock import patch -from ..plugins.utilities import add_value_to_env, load_yaml, run_cmd +from ...plugins.utilities import add_value_to_env, load_yaml, run_cmd, handle_hooks class TestAddValueToEnv(unittest.TestCase): @@ -77,5 +78,51 @@ def test_run_cmd(self): self.assertIsNotNone(process.communicate()) +class TestHandleHooks(unittest.TestCase): + def setUp(self): + self.hooks_folder = "./test_folder/hooks" + self.source_folder = "./test_folder/source" + self.hook_script = "test_script.sh" + self.mode = "before" + self.original_cwd = os.getcwd() + + def tearDown(self): + os.chdir(self.original_cwd) + + def test_handle_hooks_called_with_invalid_folder(self): + """ + Test handle_hooks with invalid folder path + """ + invalid_folder = "./invalid_folder" + result = handle_hooks(self.mode, invalid_folder, self.source_folder) + self.assertIsNone(result) + + def test_handle_hooks_called_with_valid_folder(self): + """ + Test handle_hooks with valid folder path + """ + valid_folder = "./test_folder" + result = handle_hooks(self.mode, valid_folder, self.source_folder) + self.assertIsInstance(result, subprocess.Popen) + + @patch("subprocess.Popen") + def test_handle_hooks_called_with_valid_hook_script(self, mock_subprocess_popen): + """ + Test handle_hooks with valid hook script + """ + valid_hook_script = "test_script.sh" + result = handle_hooks(self.mode, self.hooks_folder, self.source_folder) + mock_subprocess_popen.assert_called_with(["bash", valid_hook_script]) + self.assertIsInstance(result, subprocess.Popen) + + def test_handle_hooks_called_with_invalid_hook_script(self): + """ + Test handle_hooks with invalid hook script + """ + invalid_hook_script = "invalid_script.sh" + result = handle_hooks(self.mode, self.hooks_folder, self.source_folder) + self.assertIsInstance(result, subprocess.Popen) # Should still return a Popen instance + + if __name__ == "__main__": unittest.main() From eaeba19a9895915c4990ae004ea7d880ba3d275f Mon Sep 17 00:00:00 2001 From: PhillypHenning Date: Fri, 17 Mar 2023 11:34:52 -0400 Subject: [PATCH 04/24] test_utilities.py - 11 tests added --- scripts/plugins/config/parser.py | 8 +- scripts/plugins/deploy_plugins.py | 31 ++++--- scripts/plugins/install_plugins.py | 5 +- scripts/plugins/logging.py | 10 ++- scripts/plugins/utilities.py | 31 ++++--- .../bitops.before-deploy.d/before_test.sh | 5 ++ scripts/tests/unit/test_utilities.py | 81 ++++++++++--------- 7 files changed, 103 insertions(+), 68 deletions(-) create mode 100644 scripts/tests/test_assets/bitops.before-deploy.d/before_test.sh diff --git a/scripts/plugins/config/parser.py b/scripts/plugins/config/parser.py index a79e7e1e..732ff838 100644 --- a/scripts/plugins/config/parser.py +++ b/scripts/plugins/config/parser.py @@ -17,9 +17,11 @@ def get_config_list(config_file, schema_file): \n\t PLUGIN SCHEMA FILE PATH: [{schema_file}] \ \n\n" ) - - schema_yaml = load_yaml(schema_file) - config_yaml = load_yaml(config_file) + try: + schema_yaml = load_yaml(schema_file) + config_yaml = load_yaml(config_file) + except FileNotFoundError: + exit(2) schema = convert_yaml_to_dict(schema_yaml) schema_properties_list = generate_schema_keys(schema) schema_list = generate_populated_schema_list(schema, schema_properties_list, config_yaml) diff --git a/scripts/plugins/deploy_plugins.py b/scripts/plugins/deploy_plugins.py index d70f3207..9597cf43 100644 --- a/scripts/plugins/deploy_plugins.py +++ b/scripts/plugins/deploy_plugins.py @@ -271,19 +271,24 @@ def deploy_plugins(): # pylint: disable=too-many-locals,too-many-branches,too-m \n\t\t\tSTACK ACTION: [{stack_action}]" ) - result = run_cmd( - [ - plugin_deploy_language, - plugin_deploy_script_path, - stack_action, - ] - ) - if result.returncode != 0: - logger.warning(f"\n~#~#~#~DEPLOYING OPS REPO [{deployment}] FAILED~#~#~#~") - if BITOPS_FAST_FAIL_MODE: - sys.exit(result.returncode) - else: - logger.info(f"\n~#~#~#~DEPLOYING OPS REPO [{deployment}] SUCCESSFULLY COMPLETED~#~#~#~") + try: + result = run_cmd( + [ + plugin_deploy_language, + plugin_deploy_script_path, + stack_action, + ] + ) + if result.returncode != 0: + logger.warning(f"\n~#~#~#~DEPLOYING OPS REPO [{deployment}] FAILED~#~#~#~") + if BITOPS_FAST_FAIL_MODE: + sys.exit(result.returncode) + else: + logger.info( + f"\n~#~#~#~DEPLOYING OPS REPO [{deployment}] SUCCESSFULLY COMPLETED~#~#~#~" + ) + except Exception: + sys.exit(101) # ~#~#~#~#~#~# STAGE 5 - AFTER HOOKS #~#~#~#~#~#~# if plugin_deploy_after_hook_scripts_flag: diff --git a/scripts/plugins/install_plugins.py b/scripts/plugins/install_plugins.py index 06b7b1f7..e1ac4fad 100755 --- a/scripts/plugins/install_plugins.py +++ b/scripts/plugins/install_plugins.py @@ -223,7 +223,10 @@ def install_plugins(): # pylint: disable=too-many-locals,too-many-statements,to logger.error(f"File does not exist: [{plugin_install_script_path}]") sys.exit(1) - result = run_cmd([plugin_install_language, plugin_install_script_path]) + try: + result = run_cmd([plugin_install_language, plugin_install_script_path]) + except Exception: + sys.exit(101) if result.returncode == 0: logger.info(f"~#~#~#~INSTALLING PLUGIN [{plugin_config}] SUCCESSFULLY COMPLETED~#~#~#~") logger.debug( diff --git a/scripts/plugins/logging.py b/scripts/plugins/logging.py index 12f6aaa2..a5249838 100644 --- a/scripts/plugins/logging.py +++ b/scripts/plugins/logging.py @@ -63,6 +63,14 @@ def formatter_message(message, use_color=BITOPS_LOGGING_COLOR): return message +def turn_off_logger(): + """ + Disables the logger from printing. Useful for unit testing + """ + logger = logging.getLogger("bitops-logger") + logger.disabled = True + + class BitOpsFormatter(logging.Formatter): """ Class that controls the formatting of logging text, adds colors if enabled. @@ -93,7 +101,7 @@ def format(self, record): ) -logger = logging.getLogger() +logger = logging.getLogger("bitops-logger") logger.setLevel(BITOPS_LOGGING_LEVEL) handler = logging.StreamHandler(sys.stdout) diff --git a/scripts/plugins/utilities.py b/scripts/plugins/utilities.py index c3c6012f..06c3e7dc 100644 --- a/scripts/plugins/utilities.py +++ b/scripts/plugins/utilities.py @@ -46,7 +46,7 @@ def add_value_to_env(export_env, value): ) -def load_yaml(inc_yaml): +def load_yaml(inc_yaml, required=True): """ This function attempts to load a YAML file from a given location, and exits if the file is not found. It returns the loaded YAML file if successful. @@ -56,13 +56,12 @@ def load_yaml(inc_yaml): with open(inc_yaml, "r", encoding="utf8") as stream: out_yaml = yaml.load(stream, Loader=yaml.FullLoader) except FileNotFoundError as e: - # if in test mode: - if BITOPS_RUN_MODE == "testing": + if required: + logger.error( + f"Required file was not found. To fix this please add the following file: [{e.filename}]" + ) + logger.debug(e) raise e - msg, exit_code = get_doc("missing_required_file") - logger.error(f"{msg} [{e.filename}]") - logger.debug(e) - sys.exit(exit_code) return out_yaml @@ -76,11 +75,15 @@ def run_cmd(command: Union[list, str]) -> subprocess.Popen: stderr=subprocess.STDOUT, universal_newlines=True, ) as process: - for combined_output in process.stdout: + combined_output = "" + for output in process.stdout: # TODO: parse output for secrets # TODO: specify plugin and output tight output (no extra newlines) # TODO: can we modify a specific handler to add handler.terminator = "" ? - sys.stdout.write(mask_message(combined_output)) + # TODO: This should be updated to use logger if possible + # TODO: This should have a quiet option + combined_output += output + logger.info(mask_message(combined_output)) # This polls the async function to get information # about the status of the process execution. @@ -89,7 +92,7 @@ def run_cmd(command: Union[list, str]) -> subprocess.Popen: except Exception as exc: logger.error(exc) - sys.exit(101) + raise exc return process @@ -100,6 +103,8 @@ def handle_hooks(mode, hooks_folder, source_folder): # Checks if the folder exists, if not, move on if not os.path.isdir(hooks_folder): return + if mode not in ["before", "after"]: + return original_directory = os.getcwd() os.chdir(source_folder) @@ -119,7 +124,10 @@ def handle_hooks(mode, hooks_folder, source_folder): plugin_before_hook_script_path = hooks_folder + "/" + hook_script os.chmod(plugin_before_hook_script_path, 775) - result = run_cmd(["bash", plugin_before_hook_script_path]) + try: + result = run_cmd(["bash", plugin_before_hook_script_path]) + except Exception: + sys.exit(101) if result.returncode == 0: logger.info(f"~#~#~#~{umode} HOOK [{hook_script}] SUCCESSFULLY COMPLETED~#~#~#~") logger.debug(result.stdout) @@ -130,3 +138,4 @@ def handle_hooks(mode, hooks_folder, source_folder): sys.exit(result.returncode) os.chdir(original_directory) + return True diff --git a/scripts/tests/test_assets/bitops.before-deploy.d/before_test.sh b/scripts/tests/test_assets/bitops.before-deploy.d/before_test.sh new file mode 100644 index 00000000..dbe748df --- /dev/null +++ b/scripts/tests/test_assets/bitops.before-deploy.d/before_test.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +echo "In before_test.sh" + +ls ../../ \ No newline at end of file diff --git a/scripts/tests/unit/test_utilities.py b/scripts/tests/unit/test_utilities.py index ec5a626c..23b4aa93 100644 --- a/scripts/tests/unit/test_utilities.py +++ b/scripts/tests/unit/test_utilities.py @@ -2,8 +2,10 @@ import unittest import subprocess from unittest.mock import patch - from ...plugins.utilities import add_value_to_env, load_yaml, run_cmd, handle_hooks +from ...plugins.logging import turn_off_logger + +turn_off_logger() class TestAddValueToEnv(unittest.TestCase): @@ -57,34 +59,45 @@ def test_load_yaml(self): self.assertIsNotNone(out_yaml) self.assertIsInstance(out_yaml, dict) - # def test_load_yaml_with_invalid_filename(self): - # """ - # Test the load_yaml function with a non-existent file. - # """ - # with self.assertRaises(FileNotFoundError): - # load_yaml("invalid_file.yaml") + def test_load_yaml_with_required_invalid_filename(self): + """ + Test the load_yaml function with a non-existent file. + """ + with self.assertRaises(FileNotFoundError): + load_yaml("invalid_file.yaml") + def test_load_yaml_with_invalid_filename(self): + """ + Test the load_yaml function with a non-existent file. + """ + self.assertIsNone(load_yaml("invalid_file.yaml", required=False)) -class TestRunCmd(unittest.TestCase): - def setUp(self): - self.command = "ls" - def test_run_cmd(self): - process = run_cmd(self.command) +class TestRunCmd(unittest.TestCase): + def test_valid_run_cmd(self): + """ + Test the run_cmd function with a valid command + """ + process = run_cmd("ls") self.assertIsInstance(process, subprocess.Popen) - self.assertEqual(process.stdout, subprocess.PIPE) - self.assertEqual(process.stderr, subprocess.STDOUT) self.assertTrue(process.universal_newlines) - self.assertIsNotNone(process.communicate()) + self.assertEqual(process.returncode, 0) + self.assertEqual(process.args, "ls") + + def test_invalid_run_cmd(self): + """ + Test the run_cmd function with a valid command + """ + with self.assertRaises(Exception) as context: + run_cmd("not_a_real_command") + self.assertIsInstance(context.exception, FileNotFoundError) class TestHandleHooks(unittest.TestCase): def setUp(self): - self.hooks_folder = "./test_folder/hooks" - self.source_folder = "./test_folder/source" - self.hook_script = "test_script.sh" - self.mode = "before" self.original_cwd = os.getcwd() + self.hooks_folder = f"{self.original_cwd}/scripts/tests/test_assets/bitops.before-deploy.d" + self.source_folder = f"{self.original_cwd}/scripts/tests/test_assets" def tearDown(self): os.chdir(self.original_cwd) @@ -93,35 +106,25 @@ def test_handle_hooks_called_with_invalid_folder(self): """ Test handle_hooks with invalid folder path """ + invalid_folder = "./invalid_folder" - result = handle_hooks(self.mode, invalid_folder, self.source_folder) + result = handle_hooks("before", invalid_folder, self.source_folder) self.assertIsNone(result) - def test_handle_hooks_called_with_valid_folder(self): + def test_handle_hooks_called_with_invalid_mode(self): """ - Test handle_hooks with valid folder path + Test handle_hooks with invalid mode """ - valid_folder = "./test_folder" - result = handle_hooks(self.mode, valid_folder, self.source_folder) - self.assertIsInstance(result, subprocess.Popen) + result = handle_hooks("random_mode.exe", self.hooks_folder, self.source_folder) + self.assertIsNone(result) - @patch("subprocess.Popen") - def test_handle_hooks_called_with_valid_hook_script(self, mock_subprocess_popen): + def test_handle_hooks_called_with_valid_folder(self): """ - Test handle_hooks with valid hook script + Test handle_hooks with valid folder path """ - valid_hook_script = "test_script.sh" - result = handle_hooks(self.mode, self.hooks_folder, self.source_folder) - mock_subprocess_popen.assert_called_with(["bash", valid_hook_script]) - self.assertIsInstance(result, subprocess.Popen) - def test_handle_hooks_called_with_invalid_hook_script(self): - """ - Test handle_hooks with invalid hook script - """ - invalid_hook_script = "invalid_script.sh" - result = handle_hooks(self.mode, self.hooks_folder, self.source_folder) - self.assertIsInstance(result, subprocess.Popen) # Should still return a Popen instance + result = handle_hooks("before", self.hooks_folder, self.source_folder) + self.assertTrue(result) if __name__ == "__main__": From e053e3f28c4935ccb1659e056ffbb272476a2be0 Mon Sep 17 00:00:00 2001 From: PhillypHenning Date: Fri, 17 Mar 2023 12:46:42 -0400 Subject: [PATCH 05/24] renaming to avoid naming conflict --- scripts/plugins/logging.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/plugins/logging.py b/scripts/plugins/logging.py index a5249838..b7ed72b3 100644 --- a/scripts/plugins/logging.py +++ b/scripts/plugins/logging.py @@ -67,8 +67,8 @@ def turn_off_logger(): """ Disables the logger from printing. Useful for unit testing """ - logger = logging.getLogger("bitops-logger") - logger.disabled = True + to_logger = logging.getLogger("bitops-logger") + to_logger.disabled = True class BitOpsFormatter(logging.Formatter): From 3b1985b9e722d4ebe1170b53a20400c4fae2eb79 Mon Sep 17 00:00:00 2001 From: PhillypHenning Date: Fri, 17 Mar 2023 12:54:42 -0400 Subject: [PATCH 06/24] formatting --- scripts/plugins/config/parser.py | 2 +- scripts/plugins/utilities.py | 10 +++++----- scripts/tests/unit/test_utilities.py | 11 +++++++---- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/scripts/plugins/config/parser.py b/scripts/plugins/config/parser.py index 732ff838..efb9fbbe 100644 --- a/scripts/plugins/config/parser.py +++ b/scripts/plugins/config/parser.py @@ -21,7 +21,7 @@ def get_config_list(config_file, schema_file): schema_yaml = load_yaml(schema_file) config_yaml = load_yaml(config_file) except FileNotFoundError: - exit(2) + sys.exit(2) schema = convert_yaml_to_dict(schema_yaml) schema_properties_list = generate_schema_keys(schema) schema_list = generate_populated_schema_list(schema, schema_properties_list, config_yaml) diff --git a/scripts/plugins/utilities.py b/scripts/plugins/utilities.py index 06c3e7dc..9ecf241e 100644 --- a/scripts/plugins/utilities.py +++ b/scripts/plugins/utilities.py @@ -4,9 +4,8 @@ from typing import Union import yaml -from .settings import BITOPS_FAST_FAIL_MODE, BITOPS_RUN_MODE +from .settings import BITOPS_FAST_FAIL_MODE from .logging import logger, mask_message -from .doc import get_doc def add_value_to_env(export_env, value): @@ -58,7 +57,8 @@ def load_yaml(inc_yaml, required=True): except FileNotFoundError as e: if required: logger.error( - f"Required file was not found. To fix this please add the following file: [{e.filename}]" + f"Required file was not found. \ + To fix this please add the following file: [{e.filename}]" ) logger.debug(e) raise e @@ -82,18 +82,18 @@ def run_cmd(command: Union[list, str]) -> subprocess.Popen: # TODO: can we modify a specific handler to add handler.terminator = "" ? # TODO: This should be updated to use logger if possible # TODO: This should have a quiet option - combined_output += output + combined_output.join(output) logger.info(mask_message(combined_output)) # This polls the async function to get information # about the status of the process execution. # Namely the return code which is used elsewhere. process.communicate() + return process except Exception as exc: logger.error(exc) raise exc - return process def handle_hooks(mode, hooks_folder, source_folder): diff --git a/scripts/tests/unit/test_utilities.py b/scripts/tests/unit/test_utilities.py index 23b4aa93..97e5bfbd 100644 --- a/scripts/tests/unit/test_utilities.py +++ b/scripts/tests/unit/test_utilities.py @@ -1,7 +1,6 @@ import os import unittest import subprocess -from unittest.mock import patch from ...plugins.utilities import add_value_to_env, load_yaml, run_cmd, handle_hooks from ...plugins.logging import turn_off_logger @@ -9,6 +8,8 @@ class TestAddValueToEnv(unittest.TestCase): + """Testing add_value_to_env utilties function""" + def setUp(self): self.export_env = "" self.value = "" @@ -43,9 +44,7 @@ def test_add_value_to_env_with_list(self): class TestLoadYAML(unittest.TestCase): - """ - Class for testing the load_yaml function. - """ + """Testing load_yaml utilties function""" def setUp(self): root_dir = os.getcwd() @@ -74,6 +73,8 @@ def test_load_yaml_with_invalid_filename(self): class TestRunCmd(unittest.TestCase): + """Testing run_cmd utilties function""" + def test_valid_run_cmd(self): """ Test the run_cmd function with a valid command @@ -94,6 +95,8 @@ def test_invalid_run_cmd(self): class TestHandleHooks(unittest.TestCase): + """Testing handle_hooks utilties function""" + def setUp(self): self.original_cwd = os.getcwd() self.hooks_folder = f"{self.original_cwd}/scripts/tests/test_assets/bitops.before-deploy.d" From 739c8799d8e83cb89389d5ef870c308056ddcd5f Mon Sep 17 00:00:00 2001 From: PhillypHenning Date: Fri, 17 Mar 2023 16:35:47 -0400 Subject: [PATCH 07/24] removing test_hello --- scripts/tests/unit/test_hello.py | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 scripts/tests/unit/test_hello.py diff --git a/scripts/tests/unit/test_hello.py b/scripts/tests/unit/test_hello.py deleted file mode 100644 index 195750a4..00000000 --- a/scripts/tests/unit/test_hello.py +++ /dev/null @@ -1,13 +0,0 @@ -import unittest - - -class TestHello(unittest.TestCase): - """Basic test example demonstrating the hello world.""" - - def test_hello(self): - """Test hello world.""" - self.assertEqual(True, True) - - -if __name__ == "__main__": - unittest.main() From 7c427d3782c76ee6dfca50cd560e6e20b639b00a Mon Sep 17 00:00:00 2001 From: PhillypHenning Date: Mon, 20 Mar 2023 09:43:14 -0400 Subject: [PATCH 08/24] Updating import statements --- .../test_assets/bitops.before-deploy.d/before_test.sh | 0 scripts/tests/unit/test_utilities.py | 10 ++++++---- 2 files changed, 6 insertions(+), 4 deletions(-) rename scripts/tests/{ => unit}/test_assets/bitops.before-deploy.d/before_test.sh (100%) diff --git a/scripts/tests/test_assets/bitops.before-deploy.d/before_test.sh b/scripts/tests/unit/test_assets/bitops.before-deploy.d/before_test.sh similarity index 100% rename from scripts/tests/test_assets/bitops.before-deploy.d/before_test.sh rename to scripts/tests/unit/test_assets/bitops.before-deploy.d/before_test.sh diff --git a/scripts/tests/unit/test_utilities.py b/scripts/tests/unit/test_utilities.py index 97e5bfbd..5ef13c06 100644 --- a/scripts/tests/unit/test_utilities.py +++ b/scripts/tests/unit/test_utilities.py @@ -1,8 +1,8 @@ import os import unittest import subprocess -from ...plugins.utilities import add_value_to_env, load_yaml, run_cmd, handle_hooks -from ...plugins.logging import turn_off_logger +from scripts.plugins.utilities import add_value_to_env, load_yaml, run_cmd, handle_hooks +from scripts.plugins.logging import turn_off_logger turn_off_logger() @@ -99,8 +99,10 @@ class TestHandleHooks(unittest.TestCase): def setUp(self): self.original_cwd = os.getcwd() - self.hooks_folder = f"{self.original_cwd}/scripts/tests/test_assets/bitops.before-deploy.d" - self.source_folder = f"{self.original_cwd}/scripts/tests/test_assets" + self.hooks_folder = ( + f"{self.original_cwd}/scripts/tests/unit/test_assets/bitops.before-deploy.d" + ) + self.source_folder = f"{self.original_cwd}/scripts/tests/unit/test_assets" def tearDown(self): os.chdir(self.original_cwd) From 5b695a9ce543b4d1e6ef42cbdd533e1ce69a5ce9 Mon Sep 17 00:00:00 2001 From: PhillypHenning Date: Mon, 20 Mar 2023 09:44:19 -0400 Subject: [PATCH 09/24] Updating `test_assets` -> `assets` --- .../bitops.before-deploy.d/before_test.sh | 0 scripts/tests/unit/test_utilities.py | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename scripts/tests/unit/{test_assets => assets}/bitops.before-deploy.d/before_test.sh (100%) diff --git a/scripts/tests/unit/test_assets/bitops.before-deploy.d/before_test.sh b/scripts/tests/unit/assets/bitops.before-deploy.d/before_test.sh similarity index 100% rename from scripts/tests/unit/test_assets/bitops.before-deploy.d/before_test.sh rename to scripts/tests/unit/assets/bitops.before-deploy.d/before_test.sh diff --git a/scripts/tests/unit/test_utilities.py b/scripts/tests/unit/test_utilities.py index 5ef13c06..ba1623ed 100644 --- a/scripts/tests/unit/test_utilities.py +++ b/scripts/tests/unit/test_utilities.py @@ -100,9 +100,9 @@ class TestHandleHooks(unittest.TestCase): def setUp(self): self.original_cwd = os.getcwd() self.hooks_folder = ( - f"{self.original_cwd}/scripts/tests/unit/test_assets/bitops.before-deploy.d" + f"{self.original_cwd}/scripts/tests/unit/assets/bitops.before-deploy.d" ) - self.source_folder = f"{self.original_cwd}/scripts/tests/unit/test_assets" + self.source_folder = f"{self.original_cwd}/scripts/tests/unit/assets" def tearDown(self): os.chdir(self.original_cwd) From b0afda62447923420b9755469353089842ec81cc Mon Sep 17 00:00:00 2001 From: PhillypHenning Date: Mon, 20 Mar 2023 10:43:06 -0400 Subject: [PATCH 10/24] Fixing pylinting --- scripts/plugins/utilities.py | 8 +++++--- scripts/tests/unit/test_utilities.py | 12 ++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/scripts/plugins/utilities.py b/scripts/plugins/utilities.py index 9ecf241e..b9012c5b 100644 --- a/scripts/plugins/utilities.py +++ b/scripts/plugins/utilities.py @@ -66,7 +66,9 @@ def load_yaml(inc_yaml, required=True): return out_yaml -def run_cmd(command: Union[list, str]) -> subprocess.Popen: +def run_cmd( + command: Union[list, str] +) -> subprocess.Popen: # pylint: disable=inconsistent-return-statements """Run a linux command and return Popen instance as a result""" try: with subprocess.Popen( @@ -102,9 +104,9 @@ def handle_hooks(mode, hooks_folder, source_folder): """ # Checks if the folder exists, if not, move on if not os.path.isdir(hooks_folder): - return + return None if mode not in ["before", "after"]: - return + return None original_directory = os.getcwd() os.chdir(source_folder) diff --git a/scripts/tests/unit/test_utilities.py b/scripts/tests/unit/test_utilities.py index ba1623ed..6f4bbf40 100644 --- a/scripts/tests/unit/test_utilities.py +++ b/scripts/tests/unit/test_utilities.py @@ -1,8 +1,8 @@ import os import unittest import subprocess -from scripts.plugins.utilities import add_value_to_env, load_yaml, run_cmd, handle_hooks -from scripts.plugins.logging import turn_off_logger +from plugins.utilities import add_value_to_env, load_yaml, run_cmd, handle_hooks +from plugins.logging import turn_off_logger turn_off_logger() @@ -70,6 +70,9 @@ def test_load_yaml_with_invalid_filename(self): Test the load_yaml function with a non-existent file. """ self.assertIsNone(load_yaml("invalid_file.yaml", required=False)) + with self.assertRaises(Exception) as context: + self.assertIsNone(load_yaml("invalid_file.yaml")) + self.assertIsInstance(context.exception, FileNotFoundError) class TestRunCmd(unittest.TestCase): @@ -81,7 +84,6 @@ def test_valid_run_cmd(self): """ process = run_cmd("ls") self.assertIsInstance(process, subprocess.Popen) - self.assertTrue(process.universal_newlines) self.assertEqual(process.returncode, 0) self.assertEqual(process.args, "ls") @@ -99,9 +101,7 @@ class TestHandleHooks(unittest.TestCase): def setUp(self): self.original_cwd = os.getcwd() - self.hooks_folder = ( - f"{self.original_cwd}/scripts/tests/unit/assets/bitops.before-deploy.d" - ) + self.hooks_folder = f"{self.original_cwd}/scripts/tests/unit/assets/bitops.before-deploy.d" self.source_folder = f"{self.original_cwd}/scripts/tests/unit/assets" def tearDown(self): From 08f5cde6495abddef7f855f1ace45dd893d6a5c2 Mon Sep 17 00:00:00 2001 From: PhillypHenning Date: Mon, 20 Mar 2023 10:52:01 -0400 Subject: [PATCH 11/24] Updating unit test start command --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 7d8ec47b..69917f89 100644 --- a/tox.ini +++ b/tox.ini @@ -40,4 +40,4 @@ commands = # tox -e unit [testenv:unit] commands = - python3 -m unittest discover --verbose --top-level-directory scripts --start-directory scripts/tests/unit --pattern "test_*.py" + python3 -m unittest discover --verbose --top-level-directory scripts --start-directory scripts/ --pattern "test_*.py" From dddf555caf94e0b0dc742613a3e824c4171a68d7 Mon Sep 17 00:00:00 2001 From: PhillypHenning Date: Mon, 20 Mar 2023 10:58:12 -0400 Subject: [PATCH 12/24] Adding newline - refresh unit test (updated tox.ini) --- scripts/tests/unit/assets/bitops.before-deploy.d/before_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/tests/unit/assets/bitops.before-deploy.d/before_test.sh b/scripts/tests/unit/assets/bitops.before-deploy.d/before_test.sh index dbe748df..afa6bddf 100644 --- a/scripts/tests/unit/assets/bitops.before-deploy.d/before_test.sh +++ b/scripts/tests/unit/assets/bitops.before-deploy.d/before_test.sh @@ -2,4 +2,4 @@ echo "In before_test.sh" -ls ../../ \ No newline at end of file +ls ../../ From 99dccaca254b94f495e045a1ab9ba6f63caf2014 Mon Sep 17 00:00:00 2001 From: PhillypHenning Date: Mon, 20 Mar 2023 11:06:20 -0400 Subject: [PATCH 13/24] Updating tox.ini file (adding unit env) --- scripts/tests/unit/assets/bitops.before-deploy.d/before_test.sh | 1 + tox.ini | 1 + 2 files changed, 2 insertions(+) diff --git a/scripts/tests/unit/assets/bitops.before-deploy.d/before_test.sh b/scripts/tests/unit/assets/bitops.before-deploy.d/before_test.sh index afa6bddf..00d28c89 100644 --- a/scripts/tests/unit/assets/bitops.before-deploy.d/before_test.sh +++ b/scripts/tests/unit/assets/bitops.before-deploy.d/before_test.sh @@ -3,3 +3,4 @@ echo "In before_test.sh" ls ../../ + diff --git a/tox.ini b/tox.ini index 69917f89..46bbe72b 100644 --- a/tox.ini +++ b/tox.ini @@ -39,5 +39,6 @@ commands = # Run: # tox -e unit [testenv:unit] +setenv=BITOPS_LOGGING_PATH=./logs commands = python3 -m unittest discover --verbose --top-level-directory scripts --start-directory scripts/ --pattern "test_*.py" From 8eb4020229c050335dec76e0faa90f6a06f5d2f2 Mon Sep 17 00:00:00 2001 From: Eugen C <1533818+armab@users.noreply.github.com> Date: Wed, 22 Mar 2023 23:00:24 +0100 Subject: [PATCH 14/24] Disable logging --- scripts/plugins/settings.py | 2 ++ tox.ini | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/plugins/settings.py b/scripts/plugins/settings.py index 86222f32..2f580d50 100644 --- a/scripts/plugins/settings.py +++ b/scripts/plugins/settings.py @@ -86,6 +86,7 @@ def parse_config(dictionary, dotted_key_list): BITOPS_ENV_fast_fail_mode = os.environ.get("BITOPS_FAST_FAIL") BITOPS_ENV_run_mode = os.environ.get("BITOPS_MODE") # TODO: CLEAN BITOPS_ENV_logging_level = os.environ.get("BITOPS_LOGGING_LEVEL") +BITOPS_ENV_logging_filename = os.environ.get("BITOPS_LOGGING_FILENAME") BITOPS_ENV_plugin_dir = os.environ.get("BITOPS_PLUGIN_DIR") BITOPS_ENV_default_folder = os.environ.get("BITOPS_DEFAULT_FOLDER") @@ -135,6 +136,7 @@ def parse_config(dictionary, dotted_key_list): ) BITOPS_LOGGING_FILENAME = get_first( + BITOPS_ENV_logging_filename, parse_config(bitops_user_configuration, "bitops.logging.filename"), parse_config(bitops_build_configuration, "bitops.logging.filename"), None, diff --git a/tox.ini b/tox.ini index 46bbe72b..c67ea7ef 100644 --- a/tox.ini +++ b/tox.ini @@ -39,6 +39,6 @@ commands = # Run: # tox -e unit [testenv:unit] -setenv=BITOPS_LOGGING_PATH=./logs +setenv=BITOPS_LOGGING_FILENAME="" commands = - python3 -m unittest discover --verbose --top-level-directory scripts --start-directory scripts/ --pattern "test_*.py" + python3 -m unittest discover --verbose --top-level-directory scripts --start-directory scripts/tests/unit --pattern "test_*.py" From 1253cde324fcba5ff92d5e949a90ee68614508a6 Mon Sep 17 00:00:00 2001 From: Eugen Date: Wed, 22 Mar 2023 23:14:57 +0100 Subject: [PATCH 15/24] Disable logging to file, when the filename is not specified --- scripts/plugins/logging.py | 2 +- tox.ini | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/plugins/logging.py b/scripts/plugins/logging.py index b7ed72b3..8ec4b25c 100644 --- a/scripts/plugins/logging.py +++ b/scripts/plugins/logging.py @@ -112,7 +112,7 @@ def format(self, record): logger.addHandler(handler) -if BITOPS_LOGGING_FILENAME is not None: +if BITOPS_LOGGING_FILENAME: # This assumes that the user wants to save output to a filename # Create the directory if it doesn't exist diff --git a/tox.ini b/tox.ini index c67ea7ef..2aa5a054 100644 --- a/tox.ini +++ b/tox.ini @@ -39,6 +39,7 @@ commands = # Run: # tox -e unit [testenv:unit] -setenv=BITOPS_LOGGING_FILENAME="" +setenv = + BITOPS_LOGGING_FILENAME= commands = python3 -m unittest discover --verbose --top-level-directory scripts --start-directory scripts/tests/unit --pattern "test_*.py" From 7fc6697e9af968e551f5c2658c297ca60f85ab27 Mon Sep 17 00:00:00 2001 From: Eugen Date: Wed, 29 Mar 2023 16:57:35 +0100 Subject: [PATCH 16/24] Add a condition to skip the filename logging --- scripts/plugins/logging.py | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/plugins/logging.py b/scripts/plugins/logging.py index 8ec4b25c..63934b58 100644 --- a/scripts/plugins/logging.py +++ b/scripts/plugins/logging.py @@ -112,7 +112,7 @@ def format(self, record): logger.addHandler(handler) -if BITOPS_LOGGING_FILENAME: +if BITOPS_LOGGING_FILENAME not in [None, "", "false"]: # This assumes that the user wants to save output to a filename # Create the directory if it doesn't exist diff --git a/tox.ini b/tox.ini index 2aa5a054..5ae7d7ca 100644 --- a/tox.ini +++ b/tox.ini @@ -40,6 +40,6 @@ commands = # tox -e unit [testenv:unit] setenv = - BITOPS_LOGGING_FILENAME= + BITOPS_LOGGING_FILENAME=false commands = python3 -m unittest discover --verbose --top-level-directory scripts --start-directory scripts/tests/unit --pattern "test_*.py" From dd60f3a3dd4f70766e8e6a5fa7e97e06ec112c46 Mon Sep 17 00:00:00 2001 From: Eugen Date: Wed, 29 Mar 2023 20:25:37 +0100 Subject: [PATCH 17/24] Fix disable logging to filename for all the testing environments This also fixes VSCode tests autodiscovery --- scripts/tests/unit/__init__.py | 4 ++++ tox.ini | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/tests/unit/__init__.py b/scripts/tests/unit/__init__.py index e69de29b..453efe4d 100644 --- a/scripts/tests/unit/__init__.py +++ b/scripts/tests/unit/__init__.py @@ -0,0 +1,4 @@ +import os + +# Disable logging to file when running tests +os.environ["BITOPS_LOGGING_FILENAME"] = "false" diff --git a/tox.ini b/tox.ini index 5ae7d7ca..7d8ec47b 100644 --- a/tox.ini +++ b/tox.ini @@ -39,7 +39,5 @@ commands = # Run: # tox -e unit [testenv:unit] -setenv = - BITOPS_LOGGING_FILENAME=false commands = python3 -m unittest discover --verbose --top-level-directory scripts --start-directory scripts/tests/unit --pattern "test_*.py" From 791c207ba5258ba01f2fc5364cac62b0afd4dd56 Mon Sep 17 00:00:00 2001 From: Eugen Date: Wed, 29 Mar 2023 20:40:31 +0100 Subject: [PATCH 18/24] Simplify test load_yaml() function --- scripts/plugins/config/parser.py | 6 +++++- scripts/plugins/utilities.py | 15 +++------------ scripts/tests/unit/test_utilities.py | 11 +---------- 3 files changed, 9 insertions(+), 23 deletions(-) diff --git a/scripts/plugins/config/parser.py b/scripts/plugins/config/parser.py index efb9fbbe..347bf458 100644 --- a/scripts/plugins/config/parser.py +++ b/scripts/plugins/config/parser.py @@ -20,7 +20,11 @@ def get_config_list(config_file, schema_file): try: schema_yaml = load_yaml(schema_file) config_yaml = load_yaml(config_file) - except FileNotFoundError: + except FileNotFoundError as e: + logger.error( + f"Required config file was not found. \ + To fix this please add the following file: [{e.filename}]" + ) sys.exit(2) schema = convert_yaml_to_dict(schema_yaml) schema_properties_list = generate_schema_keys(schema) diff --git a/scripts/plugins/utilities.py b/scripts/plugins/utilities.py index b9012c5b..0a3d3625 100644 --- a/scripts/plugins/utilities.py +++ b/scripts/plugins/utilities.py @@ -45,23 +45,14 @@ def add_value_to_env(export_env, value): ) -def load_yaml(inc_yaml, required=True): +def load_yaml(filename: str) -> Union[dict, None]: """ This function attempts to load a YAML file from a given location, and exits if the file is not found. It returns the loaded YAML file if successful. """ out_yaml = None - try: - with open(inc_yaml, "r", encoding="utf8") as stream: - out_yaml = yaml.load(stream, Loader=yaml.FullLoader) - except FileNotFoundError as e: - if required: - logger.error( - f"Required file was not found. \ - To fix this please add the following file: [{e.filename}]" - ) - logger.debug(e) - raise e + with open(filename, "r", encoding="utf8") as stream: + out_yaml = yaml.load(stream, Loader=yaml.FullLoader) return out_yaml diff --git a/scripts/tests/unit/test_utilities.py b/scripts/tests/unit/test_utilities.py index 6f4bbf40..cbb5f169 100644 --- a/scripts/tests/unit/test_utilities.py +++ b/scripts/tests/unit/test_utilities.py @@ -58,22 +58,13 @@ def test_load_yaml(self): self.assertIsNotNone(out_yaml) self.assertIsInstance(out_yaml, dict) - def test_load_yaml_with_required_invalid_filename(self): + def test_load_yaml_with_invalid_filename(self): """ Test the load_yaml function with a non-existent file. """ with self.assertRaises(FileNotFoundError): load_yaml("invalid_file.yaml") - def test_load_yaml_with_invalid_filename(self): - """ - Test the load_yaml function with a non-existent file. - """ - self.assertIsNone(load_yaml("invalid_file.yaml", required=False)) - with self.assertRaises(Exception) as context: - self.assertIsNone(load_yaml("invalid_file.yaml")) - self.assertIsInstance(context.exception, FileNotFoundError) - class TestRunCmd(unittest.TestCase): """Testing run_cmd utilties function""" From aadff83c7e90870defec12dcd105b35e29b2bf97 Mon Sep 17 00:00:00 2001 From: Eugen Date: Thu, 30 Mar 2023 21:15:07 +0100 Subject: [PATCH 19/24] Update run_cmd for the tests --- scripts/plugins/deploy_plugins.py | 3 +- scripts/plugins/install_plugins.py | 5 ++- scripts/plugins/utilities.py | 49 +++++++++++----------------- scripts/tests/unit/test_utilities.py | 33 +++++++++---------- 4 files changed, 40 insertions(+), 50 deletions(-) diff --git a/scripts/plugins/deploy_plugins.py b/scripts/plugins/deploy_plugins.py index 9597cf43..b4400239 100644 --- a/scripts/plugins/deploy_plugins.py +++ b/scripts/plugins/deploy_plugins.py @@ -287,7 +287,8 @@ def deploy_plugins(): # pylint: disable=too-many-locals,too-many-branches,too-m logger.info( f"\n~#~#~#~DEPLOYING OPS REPO [{deployment}] SUCCESSFULLY COMPLETED~#~#~#~" ) - except Exception: + except Exception as e: + logger.error(f"Error running deployment script: {e}") sys.exit(101) # ~#~#~#~#~#~# STAGE 5 - AFTER HOOKS #~#~#~#~#~#~# diff --git a/scripts/plugins/install_plugins.py b/scripts/plugins/install_plugins.py index e1ac4fad..fc157fd3 100755 --- a/scripts/plugins/install_plugins.py +++ b/scripts/plugins/install_plugins.py @@ -225,7 +225,10 @@ def install_plugins(): # pylint: disable=too-many-locals,too-many-statements,to try: result = run_cmd([plugin_install_language, plugin_install_script_path]) - except Exception: + except Exception as e: + logger.error( + f"Failed to run plugin install script: [{plugin_install_script_path}]. Error: [{e}]" + ) sys.exit(101) if result.returncode == 0: logger.info(f"~#~#~#~INSTALLING PLUGIN [{plugin_config}] SUCCESSFULLY COMPLETED~#~#~#~") diff --git a/scripts/plugins/utilities.py b/scripts/plugins/utilities.py index 0a3d3625..e29a6d34 100644 --- a/scripts/plugins/utilities.py +++ b/scripts/plugins/utilities.py @@ -57,36 +57,24 @@ def load_yaml(filename: str) -> Union[dict, None]: return out_yaml -def run_cmd( - command: Union[list, str] -) -> subprocess.Popen: # pylint: disable=inconsistent-return-statements +def run_cmd(command: Union[list, str]) -> subprocess.Popen: """Run a linux command and return Popen instance as a result""" - try: - with subprocess.Popen( - command, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True, - ) as process: - combined_output = "" - for output in process.stdout: - # TODO: parse output for secrets - # TODO: specify plugin and output tight output (no extra newlines) - # TODO: can we modify a specific handler to add handler.terminator = "" ? - # TODO: This should be updated to use logger if possible - # TODO: This should have a quiet option - combined_output.join(output) - logger.info(mask_message(combined_output)) - - # This polls the async function to get information - # about the status of the process execution. - # Namely the return code which is used elsewhere. - process.communicate() - return process - - except Exception as exc: - logger.error(exc) - raise exc + with subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + ) as process: + combined_output = "" + for combined_output in process.stdout: + # TODO: specify plugin and output tight output (no extra newlines) + sys.stdout.write(mask_message(combined_output)) + # This polls the async function to get information + # about the status of the process execution. + # Namely the return code which is used elsewhere. + process.communicate() + + return process def handle_hooks(mode, hooks_folder, source_folder): @@ -119,7 +107,8 @@ def handle_hooks(mode, hooks_folder, source_folder): try: result = run_cmd(["bash", plugin_before_hook_script_path]) - except Exception: + except Exception as e: + logger.error(f"Failed to execute before_hook script command. Error: {e}") sys.exit(101) if result.returncode == 0: logger.info(f"~#~#~#~{umode} HOOK [{hook_script}] SUCCESSFULLY COMPLETED~#~#~#~") diff --git a/scripts/tests/unit/test_utilities.py b/scripts/tests/unit/test_utilities.py index cbb5f169..92c00c87 100644 --- a/scripts/tests/unit/test_utilities.py +++ b/scripts/tests/unit/test_utilities.py @@ -1,13 +1,13 @@ import os -import unittest import subprocess +from unittest import mock, TestCase from plugins.utilities import add_value_to_env, load_yaml, run_cmd, handle_hooks from plugins.logging import turn_off_logger turn_off_logger() -class TestAddValueToEnv(unittest.TestCase): +class TestAddValueToEnv(TestCase): """Testing add_value_to_env utilties function""" def setUp(self): @@ -43,7 +43,7 @@ def test_add_value_to_env_with_list(self): self.assertEqual(os.environ["BITOPS_" + self.export_env], " ".join(self.value)) -class TestLoadYAML(unittest.TestCase): +class TestLoadYAML(TestCase): """Testing load_yaml utilties function""" def setUp(self): @@ -66,10 +66,11 @@ def test_load_yaml_with_invalid_filename(self): load_yaml("invalid_file.yaml") -class TestRunCmd(unittest.TestCase): +class TestRunCmd(TestCase): """Testing run_cmd utilties function""" - def test_valid_run_cmd(self): + @mock.patch("sys.stdout") + def test_valid_run_cmd(self, argv): """ Test the run_cmd function with a valid command """ @@ -78,16 +79,17 @@ def test_valid_run_cmd(self): self.assertEqual(process.returncode, 0) self.assertEqual(process.args, "ls") - def test_invalid_run_cmd(self): + @mock.patch("sys.stdout") + def test_invalid_run_cmd(self, argv): """ - Test the run_cmd function with a valid command + Test the run_cmd function with an invalid command should throw an exception """ with self.assertRaises(Exception) as context: run_cmd("not_a_real_command") self.assertIsInstance(context.exception, FileNotFoundError) -class TestHandleHooks(unittest.TestCase): +class TestHandleHooks(TestCase): """Testing handle_hooks utilties function""" def setUp(self): @@ -98,13 +100,12 @@ def setUp(self): def tearDown(self): os.chdir(self.original_cwd) - def test_handle_hooks_called_with_invalid_folder(self): + @mock.patch("sys.stdout") + def test_handle_hooks_called_with_invalid_folder(self, argv): """ Test handle_hooks with invalid folder path """ - - invalid_folder = "./invalid_folder" - result = handle_hooks("before", invalid_folder, self.source_folder) + result = handle_hooks("before", "./invalid_folder", self.source_folder) self.assertIsNone(result) def test_handle_hooks_called_with_invalid_mode(self): @@ -114,14 +115,10 @@ def test_handle_hooks_called_with_invalid_mode(self): result = handle_hooks("random_mode.exe", self.hooks_folder, self.source_folder) self.assertIsNone(result) - def test_handle_hooks_called_with_valid_folder(self): + @mock.patch("sys.stdout") + def test_handle_hooks_called_with_valid_folder(self, argv): """ Test handle_hooks with valid folder path """ - result = handle_hooks("before", self.hooks_folder, self.source_folder) self.assertTrue(result) - - -if __name__ == "__main__": - unittest.main() From 95a5d6c3ae82a65edf937bfd58c6e72e30e3dbf7 Mon Sep 17 00:00:00 2001 From: Eugen Date: Thu, 30 Mar 2023 21:22:25 +0100 Subject: [PATCH 20/24] Silence pylint unused arguments --- scripts/plugins/utilities.py | 1 - scripts/tests/unit/test_utilities.py | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/scripts/plugins/utilities.py b/scripts/plugins/utilities.py index e29a6d34..3205ea39 100644 --- a/scripts/plugins/utilities.py +++ b/scripts/plugins/utilities.py @@ -65,7 +65,6 @@ def run_cmd(command: Union[list, str]) -> subprocess.Popen: stderr=subprocess.STDOUT, universal_newlines=True, ) as process: - combined_output = "" for combined_output in process.stdout: # TODO: specify plugin and output tight output (no extra newlines) sys.stdout.write(mask_message(combined_output)) diff --git a/scripts/tests/unit/test_utilities.py b/scripts/tests/unit/test_utilities.py index 92c00c87..6d00c5b5 100644 --- a/scripts/tests/unit/test_utilities.py +++ b/scripts/tests/unit/test_utilities.py @@ -70,7 +70,7 @@ class TestRunCmd(TestCase): """Testing run_cmd utilties function""" @mock.patch("sys.stdout") - def test_valid_run_cmd(self, argv): + def test_valid_run_cmd(self, argv): # pylint: disable=unused-argument """ Test the run_cmd function with a valid command """ @@ -80,7 +80,7 @@ def test_valid_run_cmd(self, argv): self.assertEqual(process.args, "ls") @mock.patch("sys.stdout") - def test_invalid_run_cmd(self, argv): + def test_invalid_run_cmd(self, argv): # pylint: disable=unused-argument """ Test the run_cmd function with an invalid command should throw an exception """ @@ -101,7 +101,7 @@ def tearDown(self): os.chdir(self.original_cwd) @mock.patch("sys.stdout") - def test_handle_hooks_called_with_invalid_folder(self, argv): + def test_handle_hooks_called_with_invalid_folder(self, argv): # pylint: disable=unused-argument """ Test handle_hooks with invalid folder path """ @@ -116,7 +116,7 @@ def test_handle_hooks_called_with_invalid_mode(self): self.assertIsNone(result) @mock.patch("sys.stdout") - def test_handle_hooks_called_with_valid_folder(self, argv): + def test_handle_hooks_called_with_valid_folder(self, argv): # pylint: disable=unused-argument """ Test handle_hooks with valid folder path """ From 9b65f8cd25db0788be17e8d39aa383c8b320da51 Mon Sep 17 00:00:00 2001 From: Eugen Date: Thu, 30 Mar 2023 21:28:11 +0100 Subject: [PATCH 21/24] Remove the turn_off_logger call in tests as it's unnecesarry now --- scripts/plugins/logging.py | 5 ++--- scripts/tests/unit/test_utilities.py | 3 --- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/scripts/plugins/logging.py b/scripts/plugins/logging.py index 63934b58..4de55856 100644 --- a/scripts/plugins/logging.py +++ b/scripts/plugins/logging.py @@ -65,10 +65,9 @@ def formatter_message(message, use_color=BITOPS_LOGGING_COLOR): def turn_off_logger(): """ - Disables the logger from printing. Useful for unit testing + Disables the logger from printing. """ - to_logger = logging.getLogger("bitops-logger") - to_logger.disabled = True + logging.getLogger("bitops-logger").disabled = True class BitOpsFormatter(logging.Formatter): diff --git a/scripts/tests/unit/test_utilities.py b/scripts/tests/unit/test_utilities.py index 6d00c5b5..e0541866 100644 --- a/scripts/tests/unit/test_utilities.py +++ b/scripts/tests/unit/test_utilities.py @@ -2,9 +2,6 @@ import subprocess from unittest import mock, TestCase from plugins.utilities import add_value_to_env, load_yaml, run_cmd, handle_hooks -from plugins.logging import turn_off_logger - -turn_off_logger() class TestAddValueToEnv(TestCase): From 8cc1637bc4d1d1c06c8e57c56bf4afb81abf52e9 Mon Sep 17 00:00:00 2001 From: Eugen Date: Thu, 30 Mar 2023 21:37:48 +0100 Subject: [PATCH 22/24] Update handle_hooks to return bool --- scripts/plugins/utilities.py | 6 +++--- scripts/tests/unit/test_utilities.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/plugins/utilities.py b/scripts/plugins/utilities.py index 3205ea39..b279e99d 100644 --- a/scripts/plugins/utilities.py +++ b/scripts/plugins/utilities.py @@ -76,15 +76,15 @@ def run_cmd(command: Union[list, str]) -> subprocess.Popen: return process -def handle_hooks(mode, hooks_folder, source_folder): +def handle_hooks(mode, hooks_folder, source_folder) -> bool: """ Processes a bitops before/after hook by invoking bash script(s) within the hooks folder(s). """ # Checks if the folder exists, if not, move on if not os.path.isdir(hooks_folder): - return None + return False if mode not in ["before", "after"]: - return None + return False original_directory = os.getcwd() os.chdir(source_folder) diff --git a/scripts/tests/unit/test_utilities.py b/scripts/tests/unit/test_utilities.py index e0541866..f70dd58d 100644 --- a/scripts/tests/unit/test_utilities.py +++ b/scripts/tests/unit/test_utilities.py @@ -103,14 +103,14 @@ def test_handle_hooks_called_with_invalid_folder(self, argv): # pylint: disable Test handle_hooks with invalid folder path """ result = handle_hooks("before", "./invalid_folder", self.source_folder) - self.assertIsNone(result) + self.assertFalse(result) def test_handle_hooks_called_with_invalid_mode(self): """ Test handle_hooks with invalid mode """ result = handle_hooks("random_mode.exe", self.hooks_folder, self.source_folder) - self.assertIsNone(result) + self.assertFalse(result) @mock.patch("sys.stdout") def test_handle_hooks_called_with_valid_folder(self, argv): # pylint: disable=unused-argument From 446569c262f698453952dbe2f99fa5ed4e75b111 Mon Sep 17 00:00:00 2001 From: Eugen Date: Thu, 30 Mar 2023 22:09:32 +0100 Subject: [PATCH 23/24] Better use patching in the Unit tests --- scripts/tests/unit/test_utilities.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/scripts/tests/unit/test_utilities.py b/scripts/tests/unit/test_utilities.py index f70dd58d..9c114193 100644 --- a/scripts/tests/unit/test_utilities.py +++ b/scripts/tests/unit/test_utilities.py @@ -1,5 +1,6 @@ import os import subprocess +from io import StringIO from unittest import mock, TestCase from plugins.utilities import add_value_to_env, load_yaml, run_cmd, handle_hooks @@ -66,8 +67,8 @@ def test_load_yaml_with_invalid_filename(self): class TestRunCmd(TestCase): """Testing run_cmd utilties function""" - @mock.patch("sys.stdout") - def test_valid_run_cmd(self, argv): # pylint: disable=unused-argument + @mock.patch("sys.stdout", new_callable=StringIO) + def test_valid_run_cmd(self, stdout): """ Test the run_cmd function with a valid command """ @@ -75,9 +76,10 @@ def test_valid_run_cmd(self, argv): # pylint: disable=unused-argument self.assertIsInstance(process, subprocess.Popen) self.assertEqual(process.returncode, 0) self.assertEqual(process.args, "ls") + self.assertIn("README.md", stdout.getvalue()) + self.assertIn("LICENSE.md", stdout.getvalue()) - @mock.patch("sys.stdout") - def test_invalid_run_cmd(self, argv): # pylint: disable=unused-argument + def test_invalid_run_cmd(self): """ Test the run_cmd function with an invalid command should throw an exception """ @@ -97,8 +99,7 @@ def setUp(self): def tearDown(self): os.chdir(self.original_cwd) - @mock.patch("sys.stdout") - def test_handle_hooks_called_with_invalid_folder(self, argv): # pylint: disable=unused-argument + def test_handle_hooks_called_with_invalid_folder(self): """ Test handle_hooks with invalid folder path """ @@ -112,10 +113,11 @@ def test_handle_hooks_called_with_invalid_mode(self): result = handle_hooks("random_mode.exe", self.hooks_folder, self.source_folder) self.assertFalse(result) - @mock.patch("sys.stdout") - def test_handle_hooks_called_with_valid_folder(self, argv): # pylint: disable=unused-argument + @mock.patch("sys.stdout", new_callable=StringIO) + def test_handle_hooks_called_with_valid_folder(self, stdout): """ Test handle_hooks with valid folder path """ result = handle_hooks("before", self.hooks_folder, self.source_folder) self.assertTrue(result) + self.assertIn("In before_test.sh", stdout.getvalue()) From 92ffbd156f09a5d123b5a6259013e2e6353081d7 Mon Sep 17 00:00:00 2001 From: Eugen C <1533818+armab@users.noreply.github.com> Date: Tue, 4 Apr 2023 15:06:38 +0100 Subject: [PATCH 24/24] Update scripts/plugins/config/parser.py to exit with 101 --- scripts/plugins/config/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/plugins/config/parser.py b/scripts/plugins/config/parser.py index 347bf458..71a190ec 100644 --- a/scripts/plugins/config/parser.py +++ b/scripts/plugins/config/parser.py @@ -25,7 +25,7 @@ def get_config_list(config_file, schema_file): f"Required config file was not found. \ To fix this please add the following file: [{e.filename}]" ) - sys.exit(2) + sys.exit(101) schema = convert_yaml_to_dict(schema_yaml) schema_properties_list = generate_schema_keys(schema) schema_list = generate_populated_schema_list(schema, schema_properties_list, config_yaml)