def test_task_raises_exception_ignore(self, mock_class): """ 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 = BaseFlow(self.project_config, flow_config, self.org_config) flow() self.assertEquals(2, len(flow.steps))
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_flow_no_org_no_org_id(self, mock_class): """ A flow without an org does not print the org ID """ flow_config = FlowConfig({ "description": "Run two tasks", "steps": { 1: { "task": "pass_name" }, 2: { "task": "pass_name" } }, }) flow = BaseFlow(self.project_config, flow_config, None) flow() self.assertFalse(any(ORG_ID in s for s in self.flow_log["info"]))
def test_nested_flow(self, mock_class): """ 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 = BaseFlow(self.project_config, flow_config, self.org_config) flow() self.assertEqual(2, len(flow.steps)) self.assertEqual(flow.step_return_values[0], flow.step_return_values[1][0])
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_flow_no_org_no_org_id(self, mock_class): """ A flow without an org does not print the org ID """ flow_config = FlowConfig({ 'description': 'Run two tasks', 'tasks': { 1: { 'task': 'pass_name' }, 2: { 'task': 'pass_name' }, } }) flow = BaseFlow(self.project_config, flow_config, None) flow() self.assertFalse(any(ORG_ID in s for s in self.flow_log['info']))
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_call_task_not_found(self, mock_class): """ A flow with reference to a task that doesn't exist in the project will throw an AttributeError """ flow_config = FlowConfig({ 'description': 'Run two tasks', 'tasks': { 1: { 'task': 'pass_name' }, 2: { 'task': 'do_delightulthings' }, } }) flow = BaseFlow(self.project_config, flow_config, self.org_config) self.assertRaises(AttributeError, flow)
def test_task_raises_exception_ignore(self, mock_class): """ A flow continues when a task configured with ignore_failure raises an exception """ flow_config = FlowConfig({ 'description': 'Run a task', 'tasks': { 1: { 'task': 'raise_exception', 'ignore_failure': True }, 2: { 'task': 'pass_name' }, } }) flow = BaseFlow(self.project_config, flow_config, self.org_config) flow() self.assertEquals(2, len(flow.tasks))
def test_call_one_task(self, mock_class): """ A flow with one task will execute the task """ flow_config = FlowConfig({ "description": "Run one task", "steps": { 1: { "task": "pass_name" } } }) flow = BaseFlow(self.project_config, flow_config, self.org_config) flow() self.assertTrue( any("Flow Description: Run one task" in s for s in self.flow_log["info"])) self.assertEqual([{"name": "supername"}], flow.step_return_values) self.assertEqual(1, len(flow.steps))
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_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_call_one_task(self, mock_class): """ A flow with one task will execute the task """ flow_config = FlowConfig({ 'description': 'Run one task', 'steps': { 1: { 'task': 'pass_name' }, } }) flow = BaseFlow(self.project_config, flow_config, self.org_config) flow() self.assertTrue( any("Flow Description: Run one task" in s for s in self.flow_log['info'])) self.assertEqual([{'name': 'supername'}], flow.step_return_values) self.assertEqual(1, len(flow.steps))
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_prints_org_id(self, mock_class): """ A flow with an org prints the org ID """ flow_config = FlowConfig({ 'description': 'Run two tasks', 'tasks': { 1: { 'task': 'pass_name' }, 2: { 'task': 'pass_name' }, } }) flow = BaseFlow(self.project_config, flow_config, self.org_config) flow() 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_nested_flow(self, mock_class): """ 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 = BaseFlow(self.project_config, flow_config, self.org_config) flow() self.assertEqual(2, len(flow.steps)) self.assertEqual( flow.step_return_values[0], flow.step_return_values[1][0], )
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_flow_prints_org_id_once_only(self, mock_class): """ A flow with sf tasks prints the org ID only once.""" flow_config = FlowConfig({ 'description': 'Run two tasks', 'steps': { 1: { 'task': 'sfdc_task' }, 2: { 'task': 'sfdc_task' }, } }) flow = BaseFlow(self.project_config, flow_config, self.org_config) flow() 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_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_flow_prints_org_id_once_only(self, mock_class): """ A flow with sf tasks prints the org ID only once.""" flow_config = FlowConfig({ "description": "Run two tasks", "steps": { 1: { "task": "sfdc_task" }, 2: { "task": "sfdc_task" } }, }) flow = BaseFlow(self.project_config, flow_config, self.org_config) flow() 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_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_call_many_tasks(self, mock_class): """ A flow with many tasks will dispatch each task """ flow_config = FlowConfig({ "description": "Run two tasks", "steps": { 1: { "task": "pass_name" }, 2: { "task": "pass_name" } }, }) flow = BaseFlow(self.project_config, flow_config, self.org_config) flow() self.assertEqual([{ "name": "supername" }, { "name": "supername" }], flow.step_return_values) self.assertEqual(2, len(flow.steps))
def test_call_many_tasks(self, mock_class): """ A flow with many tasks will dispatch each task """ flow_config = FlowConfig({ 'description': 'Run two tasks', 'steps': { 1: { 'task': 'pass_name' }, 2: { 'task': 'pass_name' }, } }) flow = BaseFlow(self.project_config, flow_config, self.org_config) flow() self.assertEqual([{ 'name': 'supername' }, { 'name': 'supername' }], flow.step_return_values) self.assertEqual(2, len(flow.steps))
def test_run(self): flow_config = FlowConfig({ "checks": [{ "when": "tasks.log(level='info', line='plan') or True", "action": "error", "message": "Failed plan check", }], "steps": { 1: { "task": "log", "options": { "level": "info", "line": "step" }, "checks": [{ "when": "tasks.log(level='info', line='plan') or True", "action": "error", "message": "Failed step check", }], } }, }) flow = PreflightFlowCoordinator(self.project_config, flow_config) flow.run(self.org_config) self.assertDictEqual( { None: [{ "status": "error", "message": "Failed plan check" }], "log": [{ "status": "error", "message": "Failed step check" }], }, flow.preflight_results, )
def test_find_step_by_name__flow(self, mock_class): flow_config = FlowConfig({ "description": "Run two tasks", "steps": { 1: { "flow": "nested_flow" }, 2: { "task": "name_response", "options": { "response": "^^nested_flow.pass_name.name", "from_flow": "^^nested_flow.name", }, }, }, }) flow = BaseFlow(self.project_config, flow_config, self.org_config) flow() step = flow._find_step_by_name("nested_flow") self.assertIsInstance(step, BaseFlow)
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 = getattr(project_config, 'flows__{}'.format(self.flow)) if not flow: raise FlowNotFoundError('Flow not found: {}'.format(self.flow)) flow_config = FlowConfig(flow) 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, callbacks=callbacks) # Run the flow return self.flow_instance.run(org_config)
def test_render_task_config_empty_value(self, mock_class): """ The _render_task_config method skips option values of None """ # instantiate a flow with two tasks flow_config = FlowConfig({ "description": "Run a tasks", "steps": { 1: { "task": "name_response", "options": { "response": None } } }, }) flow = BaseFlow(self.project_config, flow_config, self.org_config) flow() task = flow._find_step_by_name("name_response") config = flow._render_task_config(task) self.assertEquals(["Options:"], config)
def test_is_callable(self, mock_class): """ BaseFlow exposes itself as a callable for use """ flow_config = FlowConfig({}) flow = BaseFlow(self.project_config, flow_config, self.org_config) self.assertIsInstance(flow, Callable)
def flow_run(config, flow_name, org, delete_org, debug, o, skip, no_prompt): # Check environment check_keychain(config) # Get necessary configs if org: org_config = config.project_config.get_org(org) else: org, org_config = config.project_config.keychain.get_default_org() if not org_config: raise click.UsageError( '`cci flow run` requires an org.' ' No org was specified and default org is not set.') org_config = check_org_expired(config, org, org_config) if delete_org and not org_config.scratch: raise click.UsageError( '--delete-org can only be used with a scratch org') flow = getattr(config.project_config, 'flows__{}'.format(flow_name)) if not flow: raise FlowNotFoundError('Flow not found: {}'.format(flow_name)) flow_config = FlowConfig(flow) if not flow_config.config: raise click.UsageError( 'No configuration found for flow {}'.format(flow_name)) # Get the class to look up options class_path = flow_config.config.get('class_path', 'cumulusci.core.flows.BaseFlow') flow_class = import_class(class_path) exception = None # Parse command line options and add to task config options = {} if o: for option in o: options[option[0]] = option[1] # Create the flow and handle initialization exceptions try: flow = flow_class(config.project_config, flow_config, org_config, options, skip, name=flow_name) except TaskRequiresSalesforceOrg as e: exception = click.UsageError( 'This flow requires a salesforce org. Use org default <name> to set a default org or pass the org name with the --org option' ) except TaskOptionsError as e: exception = click.UsageError(e.message) handle_exception_debug(config, debug, e, throw_exception=exception) except Exception as e: handle_exception_debug(config, debug, e, no_prompt=no_prompt) if not exception: # Run the flow and handle exceptions try: flow() except TaskOptionsError as e: exception = click.UsageError(e.message) handle_exception_debug(config, debug, e, throw_exception=exception) except ApexTestException as e: exception = click.ClickException('Failed: ApexTestException') handle_exception_debug(config, debug, e, throw_exception=exception) except BrowserTestFailure as e: exception = click.ClickException('Failed: BrowserTestFailure') handle_exception_debug(config, debug, e, throw_exception=exception) except MetadataComponentFailure as e: exception = click.ClickException( 'Failed: MetadataComponentFailure') handle_exception_debug(config, debug, e, throw_exception=exception) except MetadataApiError as e: exception = click.ClickException('Failed: MetadataApiError') handle_exception_debug(config, debug, e, throw_exception=exception) except ScratchOrgException as e: exception = click.ClickException('ScratchOrgException: {}'.format( e.message)) handle_exception_debug(config, debug, e, throw_exception=exception) except Exception as e: handle_exception_debug(config, debug, e, no_prompt=no_prompt) # Delete the scratch org if --delete-org was set if delete_org: try: org_config.delete_org() except Exception as e: click.echo( 'Scratch org deletion failed. Ignoring the error below to complete the flow:' ) click.echo(e.message) if exception: handle_sentry_event(config, no_prompt) raise exception