def test_get_nodes(): in_node = EONode(InputTask()) inc_node0 = EONode(IncTask(), inputs=[in_node]) inc_node1 = EONode(IncTask(), inputs=[inc_node0]) inc_node2 = EONode(IncTask(), inputs=[inc_node1]) output_node = EONode(OutputTask(name="out"), inputs=[inc_node2]) eow = EOWorkflow([in_node, inc_node0, inc_node1, inc_node2, output_node]) returned_nodes = eow.get_nodes() assert [ in_node, inc_node0, inc_node1, inc_node2, output_node, ] == returned_nodes, "Returned nodes differ from original nodes" arguments_dict = {in_node: {"val": 2}, inc_node0: {"d": 2}} workflow_res = eow.execute(arguments_dict) manual_res = [] for _, node in enumerate(returned_nodes): manual_res = [ node.task.execute(*manual_res, **arguments_dict.get(node, {})) ] assert workflow_res.outputs["out"] == manual_res[ 0], "Manually running returned nodes produces different results."
def test_exception_handling(): input_node = EONode(InputTask(), name="xyz") exception_node = EONode(ExceptionTask(), inputs=[input_node]) increase_node = EONode(IncTask(), inputs=[exception_node]) workflow = EOWorkflow([input_node, exception_node, increase_node]) with pytest.raises(CustomException): workflow.execute() results = workflow.execute(raise_errors=False) assert results.outputs == {} assert results.error_node_uid == exception_node.uid assert len(results.stats) == 2 for node in [input_node, exception_node]: node_stats = results.stats[node.uid] assert node_stats.node_uid == node.uid assert node_stats.node_name == node.name if node is exception_node: assert isinstance(node_stats.exception, CustomException) assert node_stats.exception_traceback.startswith("Traceback") else: assert node_stats.exception is None assert node_stats.exception_traceback is None
def test_workflow_arguments(): input_node1 = EONode(InputTask()) input_node2 = EONode(InputTask(), name="some name") divide_node = EONode(DivideTask(), inputs=(input_node1, input_node2), name="some name") output_node = EONode(OutputTask(name="output"), inputs=[divide_node]) workflow = EOWorkflow([input_node1, input_node2, divide_node, output_node]) with concurrent.futures.ProcessPoolExecutor(max_workers=5) as executor: k2future = { k: executor.submit(workflow.execute, { input_node1: { "val": k**3 }, input_node2: { "val": k**2 } }) for k in range(2, 100) } executor.shutdown() for k in range(2, 100): assert k2future[k].result().outputs["output"] == k result1 = workflow.execute({ input_node1: { "val": 15 }, input_node2: { "val": 3 } }) assert result1.outputs["output"] == 5 result2 = workflow.execute({ input_node1: { "val": 6 }, input_node2: { "val": 3 } }) assert result2.outputs["output"] == 2 result3 = workflow.execute({ input_node1: { "val": 6 }, input_node2: { "val": 3 }, divide_node: { "z": 1 } }) assert result3.outputs[output_node.task.name] == 3
def test_output_task_in_workflow(test_eopatch_path, test_eopatch): load = EONode(LoadTask(test_eopatch_path)) output = EONode(OutputTask(name="result-name"), inputs=[load]) workflow = EOWorkflow([load, output, EONode(DummyTask(), inputs=[load])]) results = workflow.execute() assert len(results.outputs) == 1 assert results.outputs["result-name"] == test_eopatch
def test_multiedge_workflow(): in_node = EONode(InputTask()) inc_node = EONode(IncTask(), inputs=[in_node]) div_node = EONode(DivideTask(), inputs=[inc_node, inc_node]) output_node = EONode(OutputTask(name="out"), inputs=[div_node]) workflow = EOWorkflow([in_node, output_node, inc_node, div_node]) arguments_dict = {in_node: {"val": 2}} workflow_res = workflow.execute(arguments_dict) assert workflow_res.outputs["out"] == 1
def test_get_node_with_uid(): in_node = EONode(InputTask()) inc_node = EONode(IncTask(), inputs=[in_node]) output_node = EONode(OutputTask(name="out"), inputs=[inc_node]) eow = EOWorkflow([in_node, inc_node, output_node]) assert all(node == eow.get_node_with_uid(node.uid) for node in (in_node, inc_node, output_node)) assert eow.get_node_with_uid("nonexsitant") is None with pytest.raises(KeyError): eow.get_node_with_uid("nonexsitant", fail_if_missing=True)
def test_workflows_reusing_nodes(): in_node = EONode(InputTask()) node1 = EONode(IncTask(), inputs=[in_node]) node2 = EONode(IncTask(), inputs=[node1]) out_node = EONode(OutputTask(name="out"), inputs=[node2]) input_args = {in_node: {"val": 2}, node2: {"d": 2}} original = EOWorkflow([in_node, node1, node2, out_node]) node_reuse = EOWorkflow([in_node, node1, node2, out_node]) assert original.execute(input_args).outputs["out"] == node_reuse.execute( input_args).outputs["out"]
def test_run_after_interrupt(workflow, execution_kwargs, simple_cluster): foo_node = EONode(FooTask()) exception_node = EONode(KeyboardExceptionTask(), inputs=[foo_node]) exception_workflow = EOWorkflow([foo_node, exception_node]) exception_executor = RayExecutor(exception_workflow, [{}]) executor = RayExecutor(workflow, execution_kwargs[:-1]) # removes args for exception result_preexception = executor.run() with pytest.raises( (ray.exceptions.TaskCancelledError, ray.exceptions.RayTaskError)): exception_executor.run() result_postexception = executor.run() assert [res.outputs for res in result_preexception ] == [res.outputs for res in result_postexception]
def test_nodes_different_uids(): uids = set() for _ in range(5000): node = EONode(Inc()) uids.add(node.uid) assert len(uids) == 5000, "Different nodes should have different uids."
def test_workflow_from_endnodes(): input_node1 = EONode(InputTask()) input_node2 = EONode(InputTask(), name="some name") divide_node = EONode(DivideTask(), inputs=(input_node1, input_node2), name="some name") output_node = EONode(OutputTask(name="out"), inputs=[divide_node]) regular_workflow = EOWorkflow( [input_node1, input_node2, divide_node, output_node]) endnode_workflow = EOWorkflow.from_endnodes(output_node) assert isinstance(endnode_workflow, EOWorkflow) assert set(endnode_workflow.get_nodes()) == set( regular_workflow.get_nodes()), "Nodes are different" with concurrent.futures.ProcessPoolExecutor(max_workers=5) as executor: regular_results = [ executor.submit(regular_workflow.execute, { input_node1: { "val": k**3 }, input_node2: { "val": k**2 } }) for k in range(2, 100) ] endnode_results = [ executor.submit(endnode_workflow.execute, { input_node1: { "val": k**3 }, input_node2: { "val": k**2 } }) for k in range(2, 100) ] executor.shutdown() assert all(x.result().outputs["out"] == y.result().outputs["out"] for x, y in zip(regular_results, endnode_results)) endnode_duplicates = EOWorkflow.from_endnodes(output_node, output_node, divide_node) assert set(endnode_duplicates.get_nodes()) == set( regular_workflow.get_nodes()), "Fails if endnodes are repeated"
def test_keyboard_interrupt(simple_cluster): exception_node = EONode(KeyboardExceptionTask()) workflow = EOWorkflow([exception_node]) execution_kwargs = [] for _ in range(10): execution_kwargs.append({exception_node: {"arg1": 1}}) with pytest.raises( (ray.exceptions.TaskCancelledError, ray.exceptions.RayTaskError)): RayExecutor(workflow, execution_kwargs).run()
def test_keyboard_interrupt(): exception_node = EONode(KeyboardExceptionTask()) workflow = EOWorkflow([exception_node]) execution_kwargs = [] for _ in range(10): execution_kwargs.append({exception_node: {"arg1": 1}}) run_kwargs = [{"workers": 1}, {"workers": 3, "multiprocess": True}, {"workers": 3, "multiprocess": False}] for kwarg in run_kwargs: with pytest.raises(KeyboardInterrupt): EOExecutor(workflow, execution_kwargs).run(**kwarg)
def test_workflow_copying_eopatches(): feature1 = FeatureType.DATA, "data1" feature2 = FeatureType.DATA, "data2" create_node = EONode(CreateEOPatchTask()) init_node = EONode( InitializeFeatureTask([feature1, feature2], shape=(2, 4, 4, 3), init_value=1), inputs=[create_node], ) remove_node1 = EONode(RemoveFeatureTask([feature1]), inputs=[init_node]) remove_node2 = EONode(RemoveFeatureTask([feature2]), inputs=[init_node]) output_node1 = EONode(OutputTask(name="out1"), inputs=[remove_node1]) output_node2 = EONode(OutputTask(name="out2"), inputs=[remove_node2]) workflow = EOWorkflow([ create_node, init_node, remove_node1, remove_node2, output_node1, output_node2 ]) results = workflow.execute() eop1 = results.outputs["out1"] eop2 = results.outputs["out2"] assert eop1 == EOPatch( data={"data2": np.ones((2, 4, 4, 3), dtype=np.uint8)}) assert eop2 == EOPatch( data={"data1": np.ones((2, 4, 4, 3), dtype=np.uint8)})
def test_workflow_results(): input_node = EONode(InputTask()) output_node = EONode(OutputTask(name="out"), inputs=[input_node]) workflow = EOWorkflow([input_node, output_node]) results = workflow.execute({input_node: {"val": 10}}) assert isinstance(results, WorkflowResults) assert results.outputs == {"out": 10} results_without_outputs = results.drop_outputs() assert results_without_outputs.outputs == {} assert id(results_without_outputs) != id(results) assert isinstance(results.start_time, dt.datetime) assert isinstance(results.end_time, dt.datetime) assert results.start_time < results.end_time < dt.datetime.now() assert isinstance(results.stats, dict) assert len(results.stats) == 2 for node in [input_node, output_node]: stats_uid = node.uid assert isinstance(results.stats.get(stats_uid), NodeStats)
def test_bad_structure_exceptions(): in_node = EONode(InputTask()) inc_node0 = EONode(IncTask(), inputs=[in_node]) inc_node1 = EONode(IncTask(), inputs=[inc_node0]) inc_node2 = EONode(IncTask(), inputs=[inc_node1]) output_node = EONode(OutputTask(name="out"), inputs=[inc_node2]) # This one must work EOWorkflow([in_node, inc_node0, inc_node1, inc_node2, output_node]) # Duplicated node with pytest.raises(ValueError): EOWorkflow( [in_node, inc_node0, inc_node0, inc_node1, inc_node2, output_node]) # Missing node with pytest.raises(ValueError): EOWorkflow([in_node, inc_node0, inc_node2, output_node]) # Create circle (much more difficult now) super(EONode, inc_node0).__setattr__("inputs", (inc_node1, )) with pytest.raises(ValueError): EOWorkflow([in_node, inc_node0, inc_node1, inc_node2, output_node])
def test_get_dependencies(): input_node1 = EONode(InputTask()) input_node2 = EONode(InputTask(), name="some name") divide_node1 = EONode(DivideTask(), inputs=(input_node1, input_node2), name="some name") divide_node2 = EONode(DivideTask(), inputs=(divide_node1, input_node2), name="some name") output_node = EONode(OutputTask(name="output"), inputs=[divide_node2]) all_nodes = { input_node1, input_node2, divide_node1, divide_node2, output_node } assert len(output_node.get_dependencies()) == len( all_nodes), "Wrong number of nodes returned" assert all_nodes == set(output_node.get_dependencies())
def test_nodes_fixture(): example = EONode(ExampleTask()) foo = EONode(FooTask(), inputs=[example, example]) output = EONode(OutputTask("output"), inputs=[foo]) nodes = {"example": example, "foo": foo, "output": output} return nodes
output_node = EONode(OutputTask(name="out"), inputs=[inc_node]) eow = EOWorkflow([in_node, inc_node, output_node]) assert all(node == eow.get_node_with_uid(node.uid) for node in (in_node, inc_node, output_node)) assert eow.get_node_with_uid("nonexsitant") is None with pytest.raises(KeyError): eow.get_node_with_uid("nonexsitant", fail_if_missing=True) @pytest.mark.parametrize( "faulty_parameters", [ [InputTask(), IncTask(), IncTask()], EONode(InputTask()), [EONode(IncTask()), IncTask()], [EONode(IncTask()), (EONode(IncTask()), "name")], [EONode(IncTask()), (EONode(IncTask(), inputs=[EONode(IncTask())]))], [EONode(IncTask()), (EONode(IncTask()), IncTask())], ], ) def test_input_exceptions(faulty_parameters): with pytest.raises(ValueError): EOWorkflow(faulty_parameters) def test_bad_structure_exceptions(): in_node = EONode(InputTask()) inc_node0 = EONode(IncTask(), inputs=[in_node]) inc_node1 = EONode(IncTask(), inputs=[inc_node0])
class ExampleTask(EOTask): def execute(self, *_, **kwargs): my_logger = logging.getLogger(__file__) my_logger.info("Info statement of Example task with kwargs: %s", kwargs) my_logger.warning("Warning statement of Example task with kwargs: %s", kwargs) my_logger.debug("Debug statement of Example task with kwargs: %s", kwargs) if "arg1" in kwargs and kwargs["arg1"] is None: raise Exception NODE = EONode(ExampleTask()) WORKFLOW = EOWorkflow([NODE, EONode(task=ExampleTask(), inputs=[NODE, NODE])]) EXECUTION_KWARGS = [{ NODE: { "arg1": 1 } }, {}, { NODE: { "arg1": 3, "arg3": 10 } }, { NODE: { "arg1": None } }]
def workflow_fixture(): node1, node2 = EONode(FooTask()), EONode(FooTask()) node3 = EONode(FooTask(), [node1, node2]) workflow = EOWorkflow([node1, node2, node3]) return workflow