def get_flow_from_plan(self, plan: Plan, ctx: WorkableModel, skip: List[str] = None): steps = [ step.to_spec(skip=step.path in skip) for step in plan.steps.all() ] if isinstance(ctx, PreflightResult): flow_config = self.project_config.get_flow( plan.preflight_flow_name) return FlowCoordinator( self.project_config, flow_config, name=plan.preflight_flow_name, callbacks=PreflightFlowCallback(ctx), ) elif isinstance(ctx, Job): return FlowCoordinator.from_steps( self.project_config, steps, name="default", callbacks=JobFlowCallback(ctx), ) else: # pragma: no cover raise AttributeError(f"ctx must be either a PreflightResult " f"or Job, but was passed {type(ctx)}.")
def test_run__skip_from_init(self): """ A flow can receive during init a list of tasks to skip """ # instantiate a flow with two tasks flow_config = FlowConfig({ "description": "Run two tasks", "steps": { 1: { "task": "pass_name" }, 2: { "task": "name_response", "options": { "response": "^^pass_name.name" }, }, }, }) flow = FlowCoordinator(self.project_config, flow_config, skip=["name_response"]) flow.run(self.org_config) # the number of results should be 1 instead of 2 self.assertEqual(1, len(flow.results))
def run_flow(self, project_config, org_config): # Add the repo root to syspath to allow for custom tasks and flows in # the repo sys.path.append(project_config.repo_root) flow_config = project_config.get_flow(self.flow) # If it's a release build, pass the dates in options = self._get_flow_options() callbacks = None if settings.METACI_FLOW_CALLBACK_ENABLED: from metaci.build.flows import MetaCIFlowCallback callbacks = MetaCIFlowCallback(buildflow_id=self.pk) # Create the flow and handle initialization exceptions self.flow_instance = FlowCoordinator( project_config, flow_config, name=self.flow, options=options, callbacks=callbacks, ) # Run the flow return self.flow_instance.run(org_config)
def test_run__no_steps(self): """ A flow with no tasks will have no results. """ flow_config = FlowConfig({"description": "Run no tasks", "steps": {}}) flow = FlowCoordinator(self.project_config, flow_config) flow.run(self.org_config) self.assertEqual([], flow.steps) self.assertEqual([], flow.results)
def test_run__no_steps(self): """ A flow with no tasks will have no results. """ flow_config = FlowConfig({"description": "Run no tasks", "steps": {}}) flow = FlowCoordinator(self.project_config, flow_config) flow.run(self.org_config) self.assertEqual([], flow.steps) self.assertEqual([], flow.results)
def test_run__task_raises_exception_fail(self): """ A flow aborts when a task raises an exception """ flow_config = FlowConfig( {"description": "Run a task", "steps": {1: {"task": "raise_exception"}}} ) flow = FlowCoordinator(self.project_config, flow_config) with self.assertRaises(Exception): flow.run(self.org_config)
def test_run__skip_conditional_step(self): flow_config = FlowConfig( {"steps": { 1: { "task": "pass_name", "when": "False" } }}) flow = FlowCoordinator(self.project_config, flow_config) flow.run(self.org_config) assert len(flow.results) == 0
def test_run__nested_flow(self): """ Flows can run inside other flows """ flow_config = FlowConfig( { "description": "Run a task and a flow", "steps": {1: {"task": "pass_name"}, 2: {"flow": "nested_flow"}}, } ) flow = FlowCoordinator(self.project_config, flow_config) flow.run(self.org_config) self.assertEqual(2, len(flow.steps)) self.assertEqual(flow.results[0].return_values, flow.results[1].return_values)
def test_run__skip_flow_None(self): flow_config = FlowConfig( { "description": "A flow that skips its only step", "steps": {1: {"task": "None"}}, } ) callbacks = mock.Mock() flow = FlowCoordinator( self.project_config, flow_config, name="skip", callbacks=callbacks ) flow.run(self.org_config) callbacks.pre_task.assert_not_called()
def test_init_org_updates_keychain(self): self.org_config.save = save = mock.Mock() def change_username(keychain): self.org_config.config["username"] = "******" self.org_config.refresh_oauth_token = change_username flow_config = FlowConfig({"steps": {1: {"task": "pass_name"}}}) flow = FlowCoordinator(self.project_config, flow_config) flow.org_config = self.org_config flow._init_org() save.assert_called_once()
def test_run__one_task(self): """ A flow with one task will execute the task """ flow_config = FlowConfig( {"description": "Run one task", "steps": {1: {"task": "pass_name"}}} ) flow = FlowCoordinator(self.project_config, flow_config) self.assertEqual(1, len(flow.steps)) flow.run(self.org_config) self.assertTrue( any(flow_config.description in s for s in self.flow_log["info"]) ) self.assertEqual({"name": "supername"}, flow.results[0].return_values)
def test_run__prints_org_id(self): """ A flow with an org prints the org ID """ flow_config = FlowConfig( { "description": "Run two tasks", "steps": {1: {"task": "pass_name"}, 2: {"task": "sfdc_task"}}, } ) flow = FlowCoordinator(self.project_config, flow_config) flow.run(self.org_config) org_id_logs = [s for s in self.flow_log["info"] if ORG_ID in s] self.assertEqual(1, len(org_id_logs))
def test_step_sorting(self): self.project_config.config["flows"] = { "test": { "steps": { "1": { "flow": "subflow" }, "1.1": { "task": "pass_name" } } }, "subflow": { "steps": { "1": { "task": "pass_name" } } }, } flow_config = self.project_config.get_flow("test") flow = FlowCoordinator(self.project_config, flow_config, name="test_flow") assert [str(step.step_num) for step in flow.steps] == ["1/1", "1.1"]
def run(self, ctx, plan, steps, org): flow_coordinator = FlowCoordinator.from_steps( ctx.project_config, steps, name="default", callbacks=JobFlowCallback(self)) flow_coordinator.run(org)
def test_get_summary__multiple_sources(self): other_project_config = mock.MagicMock() other_project_config.source.__str__.return_value = "other source" flow = FlowCoordinator.from_steps( self.project_config, [ StepSpec( "1/1", "other:test1", {}, None, other_project_config, from_flow="test", ), StepSpec("1/2", "test2", {}, None, self.project_config, from_flow="test"), ], ) assert ("1) flow: test" + "\n 1) task: other:test1 [from other source]" + "\n 2) task: test2 [from current folder]" ) == flow.get_summary()
def test_run__task_raises_exception_ignore(self): """ A flow continues when a task configured with ignore_failure raises an exception """ flow_config = FlowConfig( { "description": "Run a task", "steps": { 1: {"task": "raise_exception", "ignore_failure": True}, 2: {"task": "pass_name"}, }, } ) flow = FlowCoordinator(self.project_config, flow_config) flow.run(self.org_config) self.assertEqual(2, len(flow.results)) self.assertIsNotNone(flow.results[0].exception)
def test_init__options(self): """ A flow can accept task options and pass them to the task. """ # instantiate a flow with two tasks flow_config = FlowConfig({ "description": "Run two tasks", "steps": { 1: { "task": "name_response", "options": { "response": "foo" } } }, }) flow = FlowCoordinator( self.project_config, flow_config, options={"name_response": { "response": "bar" }}, ) # the first step should have the option self.assertEqual("bar", flow.steps[0].task_config["options"]["response"])
def test_flow_info(self, echo): config = mock.Mock() flow_config = FlowConfig({"description": "Test Flow", "steps": {}}) config.get_flow.return_value = FlowCoordinator(None, flow_config) run_click_command(cci.flow_info, config=config, flow_name="test") echo.assert_called_with("Description: Test Flow")
def test_init(self): flow_config = FlowConfig({"steps": {"1": {"task": "pass_name"}}}) flow = FlowCoordinator(self.project_config, flow_config, name="test_flow") self.assertEqual(len(flow.steps), 1) self.assertEqual(hasattr(flow, "logger"), True)
def test_run__nested_option_backrefs(self): flow_config = FlowConfig( { "description": "Run two tasks", "steps": { 1: {"flow": "nested_flow"}, 2: { "task": "name_response", "options": {"response": "^^nested_flow.pass_name.name"}, }, }, } ) flow = FlowCoordinator(self.project_config, flow_config) flow.run(self.org_config) self.assertEqual("supername", flow.results[-1].result)
def test_get_summary(self): flow_config = FlowConfig({ "description": "test description", "steps": { "1": { "flow": "nested_flow_2" } }, }) flow = FlowCoordinator(self.project_config, flow_config, name="test_flow") actual_output = flow.get_summary() expected_output = ("Description: test description" + "\n1) flow: nested_flow_2" + "\n 1) task: pass_name" + "\n 2) flow: nested_flow" + "\n 1) task: pass_name") self.assertEqual(expected_output, actual_output)
def test_init_ambiguous_step(self): flow_config = FlowConfig( {"steps": { 1: { "task": "None", "flow": "None" } }}) with self.assertRaises(FlowConfigError): FlowCoordinator(self.project_config, flow_config, name="test")
def test_init__nested_options(self): flow_config = FlowConfig( { "description": "Run a flow with task options", "steps": { 1: {"flow": "nested_flow", "options": {"pass_name": {"foo": "bar"}}} }, } ) flow = FlowCoordinator(self.project_config, flow_config) self.assertEqual("bar", flow.steps[0].task_config["options"]["foo"])
def _freeze_steps(self, project_config, plan_config): steps = plan_config["steps"] flow_config = FlowConfig(plan_config) flow = FlowCoordinator(project_config, flow_config) steps = [] for step in flow.steps: task = step.task_class(project_config, TaskConfig(step.task_config), name=step.task_name) steps.extend(task.freeze(step)) return steps
def _freeze_steps(self, project_config): flow_name = self.options["flow"] flow_config = project_config.get_flow(flow_name) flow = FlowCoordinator(project_config, flow_config, name=flow_name) steps = [] for step in flow.steps: task = step.task_class(project_config, TaskConfig(step.task_config), name=step.task_name) steps.extend(task.freeze(step)) return steps
def test_get_summary(self): self.project_config.config["flows"]["test"] = { "description": "test description", "steps": { "1": { "flow": "nested_flow_2" } }, } flow_config = self.project_config.get_flow("test") flow = FlowCoordinator(self.project_config, flow_config, name="test_flow") actual_output = flow.get_summary() expected_output = ("Description: test description" + "\n1) flow: nested_flow_2 [from current folder]" + "\n 1) task: pass_name" + "\n 2) flow: nested_flow" + "\n 1) task: pass_name") self.assertEqual(expected_output, actual_output)
def test_run__option_backref_not_found(self): # instantiate a flow with two tasks flow_config = FlowConfig({ "description": "Run two tasks", "steps": { 1: { "task": "pass_name" }, 2: { "task": "name_response", "options": { "response": "^^bogus.name" }, }, }, }) flow = FlowCoordinator(self.project_config, flow_config) with self.assertRaises(NameError): flow.run(self.org_config)
def test_run__option_backrefs(self): """ A flow's options reach into return values from other tasks. """ # instantiate a flow with two tasks flow_config = FlowConfig( { "description": "Run two tasks", "steps": { 1: {"task": "pass_name"}, 2: { "task": "name_response", "options": {"response": "^^pass_name.name"}, }, }, } ) flow = FlowCoordinator(self.project_config, flow_config) flow.run(self.org_config) # the flow results for the second task should be 'name' self.assertEqual("supername", flow.results[1].result)
def test_run__skip_from_init(self): """ A flow can receive during init a list of tasks to skip """ # instantiate a flow with two tasks flow_config = FlowConfig( { "description": "Run two tasks", "steps": { 1: {"task": "pass_name"}, 2: { "task": "name_response", "options": {"response": "^^pass_name.name"}, }, }, } ) flow = FlowCoordinator(self.project_config, flow_config, skip=["name_response"]) flow.run(self.org_config) # the number of results should be 1 instead of 2 self.assertEqual(1, len(flow.results))
def test_get_flow_steps__for_docs(self): self.project_config.config["flows"]["test"] = { "description": "test description", "steps": { "1": { "flow": "nested_flow_2" } }, } flow_config = self.project_config.get_flow("test") flow = FlowCoordinator(self.project_config, flow_config, name="test_flow") actual_output = flow.get_flow_steps(for_docs=True) expected_output = [ "1) flow: nested_flow_2", " 1) task: pass_name", " 2) flow: nested_flow", " 1) task: pass_name", ] self.assertEqual(expected_output, actual_output)
def test_run__nested_flow_2(self): """ Flows can run inside other flows and call other flows """ self.project_config.config["flows"]["test"] = { "description": "Run a task and a flow", "steps": { 1: { "task": "pass_name" }, 2: { "flow": "nested_flow_2" } }, } flow_config = self.project_config.get_flow("test") flow = FlowCoordinator(self.project_config, flow_config) flow.run(self.org_config) self.assertEqual(3, len(flow.steps)) self.assertEqual(flow.results[0].return_values, flow.results[1].return_values) self.assertEqual(flow.results[1].return_values, flow.results[2].return_values)
def test_get_summary__substeps(self): flow = FlowCoordinator.from_steps( self.project_config, [ StepSpec("1", "test", {}, None, self.project_config, from_flow="test") ], ) assert flow.get_summary() == ""
def test_from_steps(self): steps = [StepSpec("1", "test", {}, _TaskReturnsStuff)] flow = FlowCoordinator.from_steps(self.project_config, steps) self.assertEqual(1, len(flow.steps))