""" Test suite for vulture, our in-house mock of HTCondor """ import os from enum import Enum import pytest from vulture.condorlite import DAGMan, Job, MockLogger def test_job_execution(caplog): """ Test execution of the null executable """ log_entries = [ "000 (4242.000.000)", "Job submitted from host: <0.0.0.0:7777>", "Job executing on host: <0.0.0.0:7777>", "Job terminated.", " Job node: null" " Normal termination (return value 0)", ] test_job = Job("test/null.condor") test_job.execute() output = "\n".join([record.message for record in caplog.records]) assert [entry in output for entry in log_entries] def test_job_exec_failed(caplog): """ Test null -e, which will terminate with return value -1 """ log_entries = [ "000 (4242.000.000)", "Job submitted from host: <0.0.0.0:7777>", "Job executing on host: <0.0.0.0:7777>", "Job terminated.", " Job node: null" " Error in execution (return value -1)", ] test_job = Job("test/null_fail.condor") test_job.execute() output = "\n".join([record.message for record in caplog.records]) assert [entry in output for entry in log_entries] def test_job_stdout(): """ Test that vulture writes correct output from stdout to a log file """ test_job = Job("test/null.condor") test_job.execute() with open("null.out", "r") as f: contents = f.read() assert "Hello, world!" in contents # Clean up output files os.remove("null.out") os.remove("null.error") def test_shell_script_in_working_directory(caplog): test_job = Job("test/localscript.condor") test_job.execute() with open("test.sh.out", "r") as f: contents = f.read() assert "hello" in contents os.remove("test.sh.out") os.remove("test.sh.error") def test_job_stderr(): """ Test that vulture writes correct output from stderr to a log file """ test_job = Job("test/null_fail.condor") test_job.execute() with open("null.error", "r") as f: contents = f.read() assert "ERROR: An error has been purposefully induced." in contents # Clean up output files os.remove("null.out") os.remove("null.error") def test_write_out_error(): """ Tests that write_out correctly errors out """ with pytest.raises(ValueError): MockLogger("test/null.condor", "null.out", "null.err").write_out( "wrong", "this should fail" ) class MockWorkflowEventType(Enum): TEST = -2 def test_create_header_error(): """ Tests that create_header correctly errors out """ with pytest.raises(ValueError): MockLogger("test/null.condor", "null.out", "null.err").create_header( MockWorkflowEventType.TEST ) def test_job_parse_error(): """ Tests that Job.parse() correctly errors out """ with pytest.raises(SystemExit): Job("does_not_exist.txt", False) with pytest.raises(ValueError) as e: Job("test/malformed.condor", False) def test_job_execute_error(): """ Tests that Job.execute() correctly errors out """ with pytest.raises(ValueError) as e: Job("test/no-exec.condor", False).execute() def test_dag_parse(): """ Tests that a HTCondor-formatted DAG file can be correctly parsed """ # Null DAG (linear) # A -> B -> C test_dag_null = DAGMan("test/null.dag") expected_dag_null = { "null_greeting": {"null_error"}, "null_error": {"null_exit_fail"}, "null_exit_fail": set(), } assert test_dag_null.dag.graph == expected_dag_null # Diamond DAG # A # / \ # B C # \ / # D test_dag_diamond = DAGMan("test/diamond.dag") expected_dag_diamond = { "A": {"B", "C"}, "B": {"D"}, "C": {"D"}, "D": set(), } assert test_dag_diamond.dag.graph == expected_dag_diamond def test_dag_parse_error(): """ Tests that DAG parsing correctly detects a malformed DAG file """ with pytest.raises(SystemExit): DAGMan("test/does_not_exist.dag") with pytest.raises(ValueError): # Malformed DAG file DAGMan("test/malformed.dag") # DAG created will contain a cycle DAGMan("test/invalid.dag") def test_dag_execute(caplog): """ Tests that a HTCondor-formatted DAG file can be correctly executed """ test_dag = DAGMan("test/null.dag") test_dag.execute(False) expected_messages = [ "Hello, world!\n", "ERROR: This is an error.\n", "ERROR: An error has been purposefully induced.\n", ] messages = [record.message for record in caplog.records] assert [expected_message in messages for expected_message in expected_messages] # Clear logged messages caplog.clear() # DAG with reversed order from the last test_dag_2 = DAGMan("test/null2.dag") test_dag_2.execute(False) messages = [record.message for record in caplog.records] assert [expected_message in messages for expected_message in expected_messages]