diff --git a/tests/test_conductors.py b/tests/test_conductors.py index 2b1188b..03592b1 100644 --- a/tests/test_conductors.py +++ b/tests/test_conductors.py @@ -28,6 +28,7 @@ class MeowTests(unittest.TestCase): super().tearDown() teardown() + # Test LocalPythonConductor creation and job types def testLocalPythonConductorCreation(self)->None: lpc = LocalPythonConductor() @@ -35,6 +36,7 @@ class MeowTests(unittest.TestCase): self.assertEqual(valid_jobs, [PYTHON_TYPE]) + # Test LocalPythonConductor executes valid jobs def testLocalPythonConductorValidJob(self)->None: lpc = LocalPythonConductor() @@ -99,6 +101,7 @@ class MeowTests(unittest.TestCase): self.assertTrue(os.path.exists(result_path)) + # Test LocalPythonConductor does not execute jobs with bad arguments def testLocalPythonConductorBadArgs(self)->None: lpc = LocalPythonConductor() @@ -189,6 +192,7 @@ class MeowTests(unittest.TestCase): self.assertTrue(os.path.exists(result_path)) + # Test LocalPythonConductor does not execute jobs with bad functions def testLocalPythonConductorBadFunc(self)->None: lpc = LocalPythonConductor() diff --git a/tests/test_functionality.py b/tests/test_functionality.py index 2dd114d..cceeebc 100644 --- a/tests/test_functionality.py +++ b/tests/test_functionality.py @@ -33,7 +33,8 @@ class CorrectnessTests(unittest.TestCase): def tearDown(self)->None: super().tearDown() teardown() - + + # Test that generate_id creates unique ids def testGenerateIDWorking(self)->None: id = generate_id() self.assertEqual(len(id), 16) @@ -61,6 +62,7 @@ class CorrectnessTests(unittest.TestCase): self.assertEqual(len(prefix_id), 16) self.assertTrue(prefix_id.startswith("Test")) + # Test that wait can wait on multiple pipes def testWaitPipes(self)->None: pipe_one_reader, pipe_one_writer = Pipe() pipe_two_reader, pipe_two_writer = Pipe() @@ -92,6 +94,7 @@ class CorrectnessTests(unittest.TestCase): msg = readable.recv() self.assertEqual(msg, 2) + # Test that wait can wait on multiple queues def testWaitQueues(self)->None: queue_one = Queue() queue_two = Queue() @@ -124,6 +127,7 @@ class CorrectnessTests(unittest.TestCase): msg = readable.get() self.assertEqual(msg, 2) + # Test that wait can wait on multiple pipes and queues def testWaitPipesAndQueues(self)->None: pipe_one_reader, pipe_one_writer = Pipe() pipe_two_reader, pipe_two_writer = Pipe() @@ -197,6 +201,7 @@ class CorrectnessTests(unittest.TestCase): msg = readable.recv() self.assertEqual(msg, 1) + # Test that get_file_hash produces the expected hash def testGetFileHashSha256(self)->None: file_path = os.path.join(TEST_MONITOR_BASE, "hased_file.txt") with open(file_path, 'w') as hashed_file: @@ -207,12 +212,14 @@ class CorrectnessTests(unittest.TestCase): hash = get_file_hash(file_path, SHA256) self.assertEqual(hash, expected_hash) + # Test that get_file_hash raises on a missing file def testGetFileHashSha256NoFile(self)->None: file_path = os.path.join(TEST_MONITOR_BASE, "file.txt") with self.assertRaises(FileNotFoundError): get_file_hash(file_path, SHA256) + # Test that parameterize_jupyter_notebook parameterises given notebook def testParameteriseNotebook(self)->None: pn = parameterize_jupyter_notebook( COMPLETE_NOTEBOOK, {}) @@ -232,6 +239,7 @@ class CorrectnessTests(unittest.TestCase): pn["cells"][0]["source"], "# The first cell\n\ns = 4\nnum = 1000") + # Test that create_event produces valid event dictionary def testCreateEvent(self)->None: event = create_event("test", "path") @@ -250,6 +258,7 @@ class CorrectnessTests(unittest.TestCase): self.assertEqual(event2[EVENT_PATH], "path2") self.assertEqual(event2["a"], 1) + # Test that create_job produces valid job dictionary def testCreateJob(self)->None: pattern = FileEventPattern( "pattern", @@ -311,6 +320,7 @@ class CorrectnessTests(unittest.TestCase): self.assertIn(JOB_REQUIREMENTS, job_dict) self.assertEqual(job_dict[JOB_REQUIREMENTS], {}) + # Test that replace_keywords replaces MEOW keywords in a given dictionary def testReplaceKeywords(self)->None: test_dict = { "A": f"--{KEYWORD_PATH}--", @@ -359,6 +369,7 @@ class CorrectnessTests(unittest.TestCase): self.assertEqual(replaced["M"], "A") self.assertEqual(replaced["N"], 1) + # Test that write_notebook can read jupyter notebooks to files def testWriteNotebook(self)->None: notebook_path = os.path.join(TEST_MONITOR_BASE, "test_notebook.ipynb") self.assertFalse(os.path.exists(notebook_path)) @@ -402,6 +413,7 @@ class CorrectnessTests(unittest.TestCase): self.assertEqual(data, expected_bytes) + # Test that read_notebook can read jupyter notebooks from files def testReadNotebook(self)->None: notebook_path = os.path.join(TEST_MONITOR_BASE, "test_notebook.ipynb") write_notebook(APPENDING_NOTEBOOK, notebook_path) @@ -409,7 +421,6 @@ class CorrectnessTests(unittest.TestCase): notebook = read_notebook(notebook_path) self.assertEqual(notebook, APPENDING_NOTEBOOK) - with self.assertRaises(FileNotFoundError): read_notebook("doesNotExist.ipynb") @@ -427,6 +438,7 @@ class CorrectnessTests(unittest.TestCase): with self.assertRaises(json.decoder.JSONDecodeError): read_notebook(filepath) + # Test that write_yaml can write dicts to yaml files def testWriteYaml(self)->None: yaml_dict = { "A": "a", @@ -461,6 +473,7 @@ class CorrectnessTests(unittest.TestCase): self.assertEqual(data, expected_bytes) + # Test that read_yaml can read yaml files def testReadYaml(self)->None: yaml_dict = { "A": "a", @@ -489,6 +502,7 @@ class CorrectnessTests(unittest.TestCase): data = read_yaml(filepath) self.assertEqual(data, "Data") + # Test that make_dir creates a directory and path to it def testMakeDir(self)->None: testDir = os.path.join(TEST_MONITOR_BASE, "Test") self.assertFalse(os.path.exists(testDir)) @@ -516,6 +530,7 @@ class CorrectnessTests(unittest.TestCase): self.assertTrue(os.path.exists(halfway)) self.assertEqual(len(os.listdir(halfway)), 0) + # Test that rmtree removes a directory, its content, and subdirectory def testRemoveTree(self)->None: nested = os.path.join(TEST_MONITOR_BASE, "A", "B") self.assertFalse(os.path.exists(os.path.join(TEST_MONITOR_BASE, "A"))) diff --git a/tests/test_meow.py b/tests/test_meow.py index 138884a..3ab0144 100644 --- a/tests/test_meow.py +++ b/tests/test_meow.py @@ -30,6 +30,7 @@ class MeowTests(unittest.TestCase): super().tearDown() teardown() + # Test that BaseRecipe instantiation def testBaseRecipe(self)->None: with self.assertRaises(TypeError): BaseRecipe("name", "") @@ -48,6 +49,7 @@ class MeowTests(unittest.TestCase): pass FullRecipe("name", "") + # Test that BaseRecipe instantiation def testBasePattern(self)->None: with self.assertRaises(TypeError): BasePattern("name", "", "", "") @@ -66,6 +68,7 @@ class MeowTests(unittest.TestCase): pass FullPattern("name", "", "", "") + # Test that BaseRecipe instantiation def testBaseRule(self)->None: with self.assertRaises(TypeError): BaseRule("name", "", "") @@ -84,6 +87,7 @@ class MeowTests(unittest.TestCase): pass FullRule("name", "", "") + # Test that create_rule creates a rule from pattern and recipe def testCreateRule(self)->None: rule = create_rule(valid_pattern_one, valid_recipe_one) @@ -92,11 +96,13 @@ class MeowTests(unittest.TestCase): with self.assertRaises(ValueError): rule = create_rule(valid_pattern_one, valid_recipe_two) + # Test that create_rules creates nothing from nothing def testCreateRulesMinimum(self)->None: rules = create_rules({}, {}) self.assertEqual(len(rules), 0) + # Test that create_rules creates rules from patterns and recipes def testCreateRulesPatternsAndRecipesDicts(self)->None: patterns = { valid_pattern_one.name: valid_pattern_one, @@ -114,6 +120,7 @@ class MeowTests(unittest.TestCase): self.assertIsInstance(rule, BaseRule) self.assertEqual(k, rule.name) + # Test that create_rules creates nothing from invalid pattern inputs def testCreateRulesMisindexedPatterns(self)->None: patterns = { valid_pattern_two.name: valid_pattern_one, @@ -122,6 +129,7 @@ class MeowTests(unittest.TestCase): with self.assertRaises(KeyError): create_rules(patterns, {}) + # Test that create_rules creates nothing from invalid recipe inputs def testCreateRulesMisindexedRecipes(self)->None: recipes = { valid_recipe_two.name: valid_recipe_one, @@ -130,6 +138,7 @@ class MeowTests(unittest.TestCase): with self.assertRaises(KeyError): create_rules({}, recipes) + # Test that BaseMonitor instantiation def testBaseMonitor(self)->None: with self.assertRaises(TypeError): BaseMonitor({}, {}) @@ -170,6 +179,7 @@ class MeowTests(unittest.TestCase): FullTestMonitor({}, {}) + # Test that BaseHandler instantiation def testBaseHandler(self)->None: with self.assertRaises(TypeError): BaseHandler() @@ -194,6 +204,7 @@ class MeowTests(unittest.TestCase): FullTestHandler() + # Test that BaseConductor instantiation def testBaseConductor(self)->None: with self.assertRaises(TypeError): BaseConductor() diff --git a/tests/test_patterns.py b/tests/test_patterns.py index 80da78c..ce31bd5 100644 --- a/tests/test_patterns.py +++ b/tests/test_patterns.py @@ -45,57 +45,70 @@ class CorrectnessTests(unittest.TestCase): super().tearDown() teardown() + # Test FileEventPattern created def testFileEventPatternCreationMinimum(self)->None: FileEventPattern("name", "path", "recipe", "file") + # Test FileEventPattern not created with empty name def testFileEventPatternCreationEmptyName(self)->None: with self.assertRaises(ValueError): FileEventPattern("", "path", "recipe", "file") + # Test FileEventPattern not created with empty path def testFileEventPatternCreationEmptyPath(self)->None: with self.assertRaises(ValueError): FileEventPattern("name", "", "recipe", "file") + # Test FileEventPattern not created with empty recipe def testFileEventPatternCreationEmptyRecipe(self)->None: with self.assertRaises(ValueError): FileEventPattern("name", "path", "", "file") + # Test FileEventPattern not created with empty file def testFileEventPatternCreationEmptyFile(self)->None: with self.assertRaises(ValueError): FileEventPattern("name", "path", "recipe", "") + # Test FileEventPattern not created with invalid name def testFileEventPatternCreationInvalidName(self)->None: with self.assertRaises(ValueError): FileEventPattern("@name", "path", "recipe", "file") + # Test FileEventPattern not created with invalid recipe def testFileEventPatternCreationInvalidRecipe(self)->None: with self.assertRaises(ValueError): FileEventPattern("name", "path", "@recipe", "file") + # Test FileEventPattern not created with invalid file def testFileEventPatternCreationInvalidFile(self)->None: with self.assertRaises(ValueError): FileEventPattern("name", "path", "recipe", "@file") + # Test FileEventPattern created with valid name def testFileEventPatternSetupName(self)->None: name = "name" fep = FileEventPattern(name, "path", "recipe", "file") self.assertEqual(fep.name, name) + # Test FileEventPattern created with valid path def testFileEventPatternSetupPath(self)->None: path = "path" fep = FileEventPattern("name", path, "recipe", "file") self.assertEqual(fep.triggering_path, path) + # Test FileEventPattern created with valid recipe def testFileEventPatternSetupRecipe(self)->None: recipe = "recipe" fep = FileEventPattern("name", "path", recipe, "file") self.assertEqual(fep.recipe, recipe) + # Test FileEventPattern created with valid file def testFileEventPatternSetupFile(self)->None: file = "file" fep = FileEventPattern("name", "path", "recipe", file) self.assertEqual(fep.triggering_file, file) + # Test FileEventPattern created with valid parameters def testFileEventPatternSetupParementers(self)->None: parameters = { "a": 1, @@ -105,6 +118,7 @@ class CorrectnessTests(unittest.TestCase): "name", "path", "recipe", "file", parameters=parameters) self.assertEqual(fep.parameters, parameters) + # Test FileEventPattern created with valid outputs def testFileEventPatternSetupOutputs(self)->None: outputs = { "a": "a", @@ -114,6 +128,7 @@ class CorrectnessTests(unittest.TestCase): "name", "path", "recipe", "file", outputs=outputs) self.assertEqual(fep.outputs, outputs) + # Test FileEventPattern created with valid event mask def testFileEventPatternEventMask(self)->None: fep = FileEventPattern("name", "path", "recipe", "file") self.assertEqual(fep.event_mask, _DEFAULT_MASK) @@ -130,6 +145,7 @@ class CorrectnessTests(unittest.TestCase): fep = FileEventPattern("name", "path", "recipe", "file", event_mask=[FILE_CREATE_EVENT, "nope"]) + # Test FileEventPattern created with valid parameter sweep def testFileEventPatternSweep(self)->None: sweeps = { 'first':{ @@ -168,10 +184,12 @@ class CorrectnessTests(unittest.TestCase): fep = FileEventPattern("name", "path", "recipe", "file", sweep=bad_sweep) + # Test WatchdogMonitor created def testWatchdogMonitorMinimum(self)->None: from_monitor = Pipe() WatchdogMonitor(TEST_MONITOR_BASE, {}, {}, from_monitor[1]) + # Test WatchdogMonitor identifies expected events in base directory def testWatchdogMonitorEventIdentificaion(self)->None: from_monitor_reader, from_monitor_writer = Pipe() @@ -210,7 +228,8 @@ class CorrectnessTests(unittest.TestCase): self.assertTrue(WATCHDOG_BASE in event.keys()) self.assertTrue(WATCHDOG_RULE in event.keys()) self.assertEqual(event[EVENT_TYPE], WATCHDOG_TYPE) - self.assertEqual(event[EVENT_PATH], os.path.join(TEST_MONITOR_BASE, "A")) + self.assertEqual(event[EVENT_PATH], + os.path.join(TEST_MONITOR_BASE, "A")) self.assertEqual(event[WATCHDOG_BASE], TEST_MONITOR_BASE) self.assertEqual(event[WATCHDOG_RULE].name, rule.name) @@ -223,6 +242,7 @@ class CorrectnessTests(unittest.TestCase): wm.stop() + # Test WatchdogMonitor identifies expected events in sub directories def testMonitoring(self)->None: pattern_one = FileEventPattern( "pattern_one", "start/A.txt", "recipe_one", "infile", @@ -237,15 +257,10 @@ class CorrectnessTests(unittest.TestCase): recipe.name: recipe, } - monitor_debug_stream = io.StringIO("") - wm = WatchdogMonitor( TEST_MONITOR_BASE, patterns, recipes, - print=monitor_debug_stream, - logging=3, - settletime=1 ) rules = wm.get_rules() @@ -286,6 +301,7 @@ class CorrectnessTests(unittest.TestCase): wm.stop() + # Test WatchdogMonitor identifies fake events for retroactive patterns def testMonitoringRetroActive(self)->None: pattern_one = FileEventPattern( "pattern_one", "start/A.txt", "recipe_one", "infile", @@ -349,6 +365,7 @@ class CorrectnessTests(unittest.TestCase): wm.stop() + # Test WatchdogMonitor get_patterns function def testMonitorGetPatterns(self)->None: pattern_one = FileEventPattern( "pattern_one", "start/A.txt", "recipe_one", "infile", @@ -375,6 +392,7 @@ class CorrectnessTests(unittest.TestCase): self.assertIn(pattern_two.name, patterns) patterns_equal(self, patterns[pattern_two.name], pattern_two) + # Test WatchdogMonitor add_pattern function def testMonitorAddPattern(self)->None: pattern_one = FileEventPattern( "pattern_one", "start/A.txt", "recipe_one", "infile", @@ -419,6 +437,7 @@ class CorrectnessTests(unittest.TestCase): self.assertIn(pattern_two.name, patterns) patterns_equal(self, patterns[pattern_two.name], pattern_two) + # Test WatchdogMonitor update_patterns function def testMonitorUpdatePattern(self)->None: pattern_one = FileEventPattern( "pattern_one", "start/A.txt", "recipe_one", "infile", @@ -480,6 +499,7 @@ class CorrectnessTests(unittest.TestCase): self.assertIn(pattern_one.name, patterns) patterns_equal(self, patterns[pattern_one.name], pattern_one) + # Test WatchdogMonitor remove_patterns function def testMonitorRemovePattern(self)->None: pattern_one = FileEventPattern( "pattern_one", "start/A.txt", "recipe_one", "infile", @@ -518,6 +538,7 @@ class CorrectnessTests(unittest.TestCase): self.assertIsInstance(patterns, dict) self.assertEqual(len(patterns), 0) + # Test WatchdogMonitor get_recipes function def testMonitorGetRecipes(self)->None: recipe_one = JupyterNotebookRecipe( "recipe_one", BAREBONES_NOTEBOOK) @@ -542,6 +563,7 @@ class CorrectnessTests(unittest.TestCase): self.assertIn(recipe_two.name, recipes) recipes_equal(self, recipes[recipe_two.name], recipe_two) + # Test WatchdogMonitor add_recipe function def testMonitorAddRecipe(self)->None: recipe_one = JupyterNotebookRecipe( "recipe_one", BAREBONES_NOTEBOOK) @@ -587,6 +609,7 @@ class CorrectnessTests(unittest.TestCase): self.assertIn(recipe_two.name, recipes) recipes_equal(self, recipes[recipe_two.name], recipe_two) + # Test WatchdogMonitor update_recipe function def testMonitorUpdateRecipe(self)->None: recipe_one = JupyterNotebookRecipe( "recipe_one", BAREBONES_NOTEBOOK) @@ -642,6 +665,7 @@ class CorrectnessTests(unittest.TestCase): self.assertIn(recipe_one.name, recipes) recipes_equal(self, recipes[recipe_one.name], recipe_one) + # Test WatchdogMonitor remove_recipe function def testMonitorRemoveRecipe(self)->None: recipe_one = JupyterNotebookRecipe( "recipe_one", BAREBONES_NOTEBOOK) @@ -680,6 +704,7 @@ class CorrectnessTests(unittest.TestCase): self.assertIsInstance(recipes, dict) self.assertEqual(len(recipes), 0) + # Test WatchdogMonitor get_rules function def testMonitorGetRules(self)->None: pattern_one = FileEventPattern( "pattern_one", "start/A.txt", "recipe_one", "infile", diff --git a/tests/test_recipes.py b/tests/test_recipes.py index 3883bda..fa3a78c 100644 --- a/tests/test_recipes.py +++ b/tests/test_recipes.py @@ -30,44 +30,54 @@ class CorrectnessTests(unittest.TestCase): super().tearDown() teardown() + # Test JupyterNotebookRecipe can be created def testJupyterNotebookRecipeCreationMinimum(self)->None: JupyterNotebookRecipe("test_recipe", BAREBONES_NOTEBOOK) + # Test JupyterNotebookRecipe can be created with source def testJupyterNotebookRecipeCreationSource(self)->None: JupyterNotebookRecipe( "test_recipe", BAREBONES_NOTEBOOK, source="notebook.ipynb") + # Test JupyterNotebookRecipe cannot be created without name def testJupyterNotebookRecipeCreationNoName(self)->None: with self.assertRaises(ValueError): JupyterNotebookRecipe("", BAREBONES_NOTEBOOK) + # Test JupyterNotebookRecipe cannot be created with invalid name def testJupyterNotebookRecipeCreationInvalidName(self)->None: with self.assertRaises(ValueError): JupyterNotebookRecipe("@test_recipe", BAREBONES_NOTEBOOK) + # Test JupyterNotebookRecipe cannot be created with invalid recipe def testJupyterNotebookRecipeCreationInvalidRecipe(self)->None: with self.assertRaises(jsonschema.exceptions.ValidationError): JupyterNotebookRecipe("test_recipe", {}) + # Test JupyterNotebookRecipe cannot be created with invalid source def testJupyterNotebookRecipeCreationInvalidSourceExtension(self)->None: with self.assertRaises(ValueError): JupyterNotebookRecipe( "test_recipe", BAREBONES_NOTEBOOK, source="notebook") + # Test JupyterNotebookRecipe cannot be created with invalid source name def testJupyterNotebookRecipeCreationInvalidSoureChar(self)->None: with self.assertRaises(ValueError): JupyterNotebookRecipe( "test_recipe", BAREBONES_NOTEBOOK, source="@notebook.ipynb") + # Test JupyterNotebookRecipe name setup correctly def testJupyterNotebookRecipeSetupName(self)->None: name = "name" jnr = JupyterNotebookRecipe(name, BAREBONES_NOTEBOOK) self.assertEqual(jnr.name, name) + # Test JupyterNotebookRecipe recipe setup correctly def testJupyterNotebookRecipeSetupRecipe(self)->None: jnr = JupyterNotebookRecipe("name", BAREBONES_NOTEBOOK) self.assertEqual(jnr.recipe, BAREBONES_NOTEBOOK) + # Test JupyterNotebookRecipe parameters setup correctly def testJupyterNotebookRecipeSetupParameters(self)->None: parameters = { "a": 1, @@ -77,6 +87,7 @@ class CorrectnessTests(unittest.TestCase): "name", BAREBONES_NOTEBOOK, parameters=parameters) self.assertEqual(jnr.parameters, parameters) + # Test JupyterNotebookRecipe requirements setup correctly def testJupyterNotebookRecipeSetupRequirements(self)->None: requirements = { "a": 1, @@ -86,18 +97,21 @@ class CorrectnessTests(unittest.TestCase): "name", BAREBONES_NOTEBOOK, requirements=requirements) self.assertEqual(jnr.requirements, requirements) + # Test JupyterNotebookRecipe source setup correctly def testJupyterNotebookRecipeSetupSource(self)->None: source = "source.ipynb" jnr = JupyterNotebookRecipe( "name", BAREBONES_NOTEBOOK, source=source) self.assertEqual(jnr.source, source) + # Test PapermillHandler can be created def testPapermillHanderMinimum(self)->None: PapermillHandler( TEST_HANDLER_BASE, TEST_JOB_OUTPUT ) + # Test PapermillHandler will handle given events def testPapermillHandlerHandling(self)->None: from_handler_reader, from_handler_writer = Pipe() ph = PapermillHandler( @@ -147,6 +161,7 @@ class CorrectnessTests(unittest.TestCase): valid_job(job) + # Test jobFunc performs as expected def testJobFunc(self)->None: file_path = os.path.join(TEST_MONITOR_BASE, "test") result_path = os.path.join(TEST_MONITOR_BASE, "output", "test") @@ -209,6 +224,7 @@ class CorrectnessTests(unittest.TestCase): self.assertTrue(os.path.exists(result_path)) + # Test jobFunc doesn't execute with no args def testJobFuncBadArgs(self)->None: try: job_func({}) diff --git a/tests/test_rules.py b/tests/test_rules.py index 5c3a8f6..b4e9358 100644 --- a/tests/test_rules.py +++ b/tests/test_rules.py @@ -16,12 +16,14 @@ class CorrectnessTests(unittest.TestCase): super().tearDown() teardown() + # Test FileEventJupyterNotebookRule created from valid pattern and recipe def testFileEventJupyterNotebookRuleCreationMinimum(self)->None: fep = FileEventPattern("name", "path", "recipe", "file") jnr = JupyterNotebookRecipe("recipe", BAREBONES_NOTEBOOK) FileEventJupyterNotebookRule("name", fep, jnr) + # Test FileEventJupyterNotebookRule not created with empty name def testFileEventJupyterNotebookRuleCreationNoName(self)->None: fep = FileEventPattern("name", "path", "recipe", "file") jnr = JupyterNotebookRecipe("recipe", BAREBONES_NOTEBOOK) @@ -29,6 +31,7 @@ class CorrectnessTests(unittest.TestCase): with self.assertRaises(ValueError): FileEventJupyterNotebookRule("", fep, jnr) + # Test FileEventJupyterNotebookRule not created with invalid name def testFileEventJupyterNotebookRuleCreationInvalidName(self)->None: fep = FileEventPattern("name", "path", "recipe", "file") jnr = JupyterNotebookRecipe("recipe", BAREBONES_NOTEBOOK) @@ -36,18 +39,21 @@ class CorrectnessTests(unittest.TestCase): with self.assertRaises(TypeError): FileEventJupyterNotebookRule(1, fep, jnr) + # Test FileEventJupyterNotebookRule not created with invalid pattern def testFileEventJupyterNotebookRuleCreationInvalidPattern(self)->None: jnr = JupyterNotebookRecipe("recipe", BAREBONES_NOTEBOOK) with self.assertRaises(TypeError): FileEventJupyterNotebookRule("name", "pattern", jnr) + # Test FileEventJupyterNotebookRule not created with invalid recipe def testFileEventJupyterNotebookRuleCreationInvalidRecipe(self)->None: fep = FileEventPattern("name", "path", "recipe", "file") with self.assertRaises(TypeError): FileEventJupyterNotebookRule("name", fep, "recipe") + # Test FileEventJupyterNotebookRule not created with mismatched recipe def testFileEventJupyterNotebookRuleCreationMissmatchedRecipe(self)->None: fep = FileEventPattern("name", "path", "recipe", "file") jnr = JupyterNotebookRecipe("test_recipe", BAREBONES_NOTEBOOK) @@ -55,6 +61,7 @@ class CorrectnessTests(unittest.TestCase): with self.assertRaises(ValueError): FileEventJupyterNotebookRule("name", fep, jnr) + # Test FileEventJupyterNotebookRule created with valid name def testFileEventJupyterNotebookRuleSetupName(self)->None: name = "name" fep = FileEventPattern("name", "path", "recipe", "file") @@ -64,6 +71,7 @@ class CorrectnessTests(unittest.TestCase): self.assertEqual(fejnr.name, name) + # Test FileEventJupyterNotebookRule not created with valid pattern def testFileEventJupyterNotebookRuleSetupPattern(self)->None: fep = FileEventPattern("name", "path", "recipe", "file") jnr = JupyterNotebookRecipe("recipe", BAREBONES_NOTEBOOK) @@ -72,6 +80,7 @@ class CorrectnessTests(unittest.TestCase): self.assertEqual(fejnr.pattern, fep) + # Test FileEventJupyterNotebookRule not created with valid recipe def testFileEventJupyterNotebookRuleSetupRecipe(self)->None: fep = FileEventPattern("name", "path", "recipe", "file") jnr = JupyterNotebookRecipe("recipe", BAREBONES_NOTEBOOK) diff --git a/tests/test_runner.py b/tests/test_runner.py index b645ab7..362bf5d 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -26,6 +26,7 @@ class MeowTests(unittest.TestCase): super().tearDown() teardown() + # Test MeowRunner creation def testMeowRunnerSetup(self)->None: monitor_one = WatchdogMonitor(TEST_MONITOR_BASE, {}, {}) @@ -166,6 +167,7 @@ class MeowTests(unittest.TestCase): for rc in relevent_conductors: self.assertIn(rc, runner.conductors[job_type]) + # Test single meow job execution def testMeowRunnerExecution(self)->None: pattern_one = FileEventPattern( "pattern_one", "start/A.txt", "recipe_one", "infile", @@ -251,6 +253,7 @@ class MeowTests(unittest.TestCase): self.assertEqual(data, "Initial Data\nA line from a test Pattern") + # Test meow job chaining within runner def testMeowRunnerLinkedExecution(self)->None: pattern_one = FileEventPattern( "pattern_one", "start/A.txt", "recipe_one", "infile", diff --git a/tests/test_validation.py b/tests/test_validation.py index 882e155..96d02f9 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -25,105 +25,130 @@ class CorrectnessTests(unittest.TestCase): super().tearDown() teardown() + # Test check_type accepts valid types def testCheckTypeValid(self)->None: check_type(1, int) check_type(0, int) check_type(False, bool) check_type(True, bool) + # Test check_type accepts Any type def testCheckTypeValidAny(self)->None: check_type(1, Any) + # Test check_type accepts Union of types def testCheckTypeValidUnion(self)->None: check_type(1, Union[int,str]) with self.assertRaises(TypeError): check_type(Union[int, str], Union[int,str]) + # Test check_type raises on mismatched type def testCheckTypeMistyped(self)->None: with self.assertRaises(TypeError): check_type(1, str) + # Test or_none arg for check_type def testCheckTypeOrNone(self)->None: check_type(None, int, or_none=True) with self.assertRaises(TypeError): check_type(None, int, or_none=False) + # Test valid_string with valid chars def testValidStringValid(self)->None: valid_string("David_Marchant", VALID_NAME_CHARS) + # Test valid_string with empty input def testValidStringEmptyString(self)->None: valid_string("", VALID_NAME_CHARS, min_length=0) + # Test valid_string with no valid chars def testValidStringNoValidChars(self)->None: with self.assertRaises(ValueError): valid_string("David_Marchant", "") + # Test valid_string with wrong types def testValidStringMistypedInput(self)->None: with self.assertRaises(TypeError): valid_string(1, VALID_NAME_CHARS) with self.assertRaises(TypeError): valid_string("David_Marchant", 1) + # Test valid_string with invalid chars def testValidStringMissingChars(self)->None: with self.assertRaises(ValueError): valid_string("David Marchant", VALID_NAME_CHARS) + # Test valid_string with not long enough input def testValidStringInsufficientLength(self)->None: with self.assertRaises(ValueError): valid_string("", VALID_NAME_CHARS) valid_string("David Marchant", VALID_NAME_CHARS, min_length=50) + # Test valid_dict with not long enough input def testValidDictMinimum(self)->None: valid_dict({"a": 0, "b": 1}, str, int, strict=False) + # Test valid_dict with invalid key types def testValidDictAnyKeyType(self)->None: valid_dict({"a": 0, "b": 1}, Any, int, strict=False) + # Test valid_dict with invalid value types def testValidDictAnyValueType(self)->None: valid_dict({"a": 0, "b": 1}, str, Any, strict=False) + # Test valid_dict with required keys def testValidDictAllRequiredKeys(self)->None: valid_dict({"a": 0, "b": 1}, str, int, required_keys=["a", "b"]) + # Test valid_dict with required and optional keys def testValidDictAllRequiredOrOptionalKeys(self)->None: valid_dict( {"a": 0, "b": 1}, str, int, required_keys=["a"], optional_keys=["b"]) + # Test valid_dict with extra keys def testValidDictExtraKeys(self)->None: valid_dict( {"a": 0, "b": 1, "c": 2}, str, int, required_keys=["a"], optional_keys=["b"], strict=False) + # Test valid_dict with missing required keys def testValidDictMissingRequiredKeys(self)->None: with self.assertRaises(KeyError): valid_dict( {"a": 0, "b": 1}, str, int, required_keys=["a", "b", "c"]) + # Test strict checking of valid_dict def testValidDictOverlyStrict(self)->None: with self.assertRaises(ValueError): - valid_dict({"a": 0, "b": 1}, str, int) + valid_dict({"a": 0, "b": 1}, str, int, strict=True) + # Test valid_list with sufficent lengths def testValidListMinimum(self)->None: valid_list([1, 2, 3], int) valid_list(["1", "2", "3"], str) valid_list([1], int) + # Test valid_list with alternate types def testValidListAltTypes(self)->None: valid_list([1, "2", 3], int, alt_types=[str]) + # Test valid_list with wrong type def testValidListMismatchedNotList(self)->None: with self.assertRaises(TypeError): valid_list((1, 2, 3), int) + # Test valid_list with mismatch value types def testValidListMismatchedType(self)->None: with self.assertRaises(TypeError): valid_list([1, 2, 3], str) + # Test valid_list with insufficient length def testValidListMinLength(self)->None: with self.assertRaises(ValueError): valid_list([1, 2, 3], str, min_length=10) + # Test check_implementation doesn't raise on implemented def testCheckImplementationMinimum(self)->None: class Parent: def func(): @@ -135,6 +160,7 @@ class CorrectnessTests(unittest.TestCase): check_implementation(Child.func, Parent) + # Test check_implementation does raise on not implemented def testCheckImplementationUnaltered(self)->None: class Parent: def func(): @@ -146,6 +172,7 @@ class CorrectnessTests(unittest.TestCase): with self.assertRaises(NotImplementedError): check_implementation(Child.func, Parent) + # Test check_implementation does raise on differing signature def testCheckImplementationDifferingSig(self)->None: class Parent: def func(): @@ -158,6 +185,7 @@ class CorrectnessTests(unittest.TestCase): with self.assertRaises(NotImplementedError): check_implementation(Child.func, Parent) + # Test check_implementation doesn't raise on Any type in signature def testCheckImplementationAnyType(self)->None: class Parent: def func(var:Any): @@ -169,6 +197,7 @@ class CorrectnessTests(unittest.TestCase): check_implementation(Child.func, Parent) + # Test valid_existing_file_path can find files, or not def testValidExistingFilePath(self)->None: file_path = os.path.join(TEST_MONITOR_BASE, "file.txt") with open(file_path, 'w') as hashed_file: @@ -185,6 +214,7 @@ class CorrectnessTests(unittest.TestCase): with self.assertRaises(ValueError): valid_existing_file_path(dir_path, SHA256) + # Test valid_existing_dir_path can find directories, or not def testValidExistingDirPath(self)->None: valid_existing_dir_path(TEST_MONITOR_BASE) @@ -200,6 +230,7 @@ class CorrectnessTests(unittest.TestCase): with self.assertRaises(ValueError): valid_existing_dir_path(file_path, SHA256) + # Test valid_non_existing_path can find existing paths, or not def testValidNonExistingPath(self)->None: valid_non_existing_path("does_not_exist") @@ -211,6 +242,7 @@ class CorrectnessTests(unittest.TestCase): with self.assertRaises(ValueError): valid_non_existing_path("first/second") + # Test valid_event can check given event dictionary def testEventValidation(self)->None: valid_event({EVENT_TYPE: "test", EVENT_PATH: "path"}) valid_event({EVENT_TYPE: "anything", EVENT_PATH: "path", "a": 1}) @@ -224,6 +256,7 @@ class CorrectnessTests(unittest.TestCase): with self.assertRaises(KeyError): valid_event({}) + # Test valid_job can check given job dictionary def testJobValidation(self)->None: valid_job({ JOB_TYPE: "test", @@ -245,6 +278,7 @@ class CorrectnessTests(unittest.TestCase): with self.assertRaises(KeyError): valid_job({}) + # Test setup_debugging will create writeable location def testSetupDebugging(self)->None: stream = io.StringIO("")