def test_process_startcp( startcp: Optional[str], starttask: Optional[str], expected: str, expected_err: Optional[Tuple[Type[Exception], str]], monkeypatch: pytest.MonkeyPatch, set_cycling_type: Fixture ) -> None: """Test WorkflowConfig.process_start_cycle_point(). An icp of 1899-05-01T00+0530 is assumed, and "now" is assumed to be 2005-01-02T06:15+0530 Params: startcp: The start cycle point given by cli option. expected: The expected startcp value that gets set. expected_err: Expected exception. """ set_cycling_type(ISO8601_CYCLING_TYPE, time_zone="+0530") mocked_config = Mock(initial_point='18990501T0000+0530') mocked_config.options.startcp = startcp mocked_config.options.starttask = starttask monkeypatch.setattr('cylc.flow.config.get_current_time_string', lambda: '20050102T0615+0530') if expected_err is not None: err, msg = expected_err with pytest.raises(err) as exc: WorkflowConfig.process_start_cycle_point(mocked_config) assert msg in str(exc.value) else: WorkflowConfig.process_start_cycle_point(mocked_config) assert str(mocked_config.start_point) == expected
def test_process_fcp(cycling_type: str, scheduling_cfg: dict, options_fcp: Optional[str], expected_fcp: Optional[str], expected_err: Optional[Tuple[Type[Exception], str]], set_cycling_type: Fixture) -> None: """Test WorkflowConfig.process_final_cycle_point(). Params: cycling_type: Workflow cycling type. scheduling_cfg: 'scheduling' section of workflow config. options_fcp: The fcp set by cli option. expected_fcp: The expected fcp value that gets set. expected_err: Exception class expected to be raised plus the message. """ set_cycling_type(cycling_type, time_zone='+0530') mocked_config = Mock(cycling_type=cycling_type) mocked_config.cfg = {'scheduling': scheduling_cfg} mocked_config.initial_point = loader.get_point( scheduling_cfg['initial cycle point']).standardise() mocked_config.final_point = None mocked_config.options.fcp = options_fcp if expected_err: err, msg = expected_err with pytest.raises(err) as exc: WorkflowConfig.process_final_cycle_point(mocked_config) assert msg in str(exc.value) else: WorkflowConfig.process_final_cycle_point(mocked_config) assert mocked_config.cfg['scheduling'][ 'final cycle point'] == expected_fcp assert str(mocked_config.final_point) == str(expected_fcp)
def main(_, options: 'Values', *ids) -> None: workflow_id, _, flow_file = parse_id( *ids, src=True, constraint='workflows', ) # extract task host platforms from the workflow_id config = WorkflowConfig( workflow_id, flow_file, options, load_template_vars(options.templatevars, options.templatevars_file)) platforms = { config.get_config(['runtime', name, 'platform']) for name in config.get_namespace_list('all tasks') } - {None, 'localhost'} # When "workflow run hosts" are formalised as "flow platforms" # we can substitute `localhost` for this, in the mean time # we will have to assume that flow hosts are configured correctly. if not platforms: sys.exit(0) verbose = cylc.flow.flags.verbosity > 0 # get the cylc version on each platform versions = {} for platform_name in sorted(platforms): platform = get_platform(platform_name) host = get_host_from_platform(platform, bad_hosts=None) cmd = construct_ssh_cmd(['version'], platform, host) if verbose: print(cmd) proc = procopen(cmd, stdin=DEVNULL, stdout=PIPE, stderr=PIPE) out, err = proc.communicate() out = out.decode() err = err.decode() if proc.wait() == 0: if verbose: print(" %s" % out) versions[platform_name] = out.strip() else: versions[platform_name] = f'ERROR: {err.strip()}' # report results max_len = max((len(platform_name) for platform_name in platforms)) print(f'{"platform".rjust(max_len)}: cylc version') print('-' * (max_len + 14)) for platform_name, result in versions.items(): print(f'{platform_name.rjust(max_len)}: {result}') if all((version == CYLC_VERSION for version in versions.values())): ret_code = 0 elif options.error: ret_code = 1 else: ret_code = 0 sys.exit(ret_code)
def test_implicit_tasks( allow_implicit_tasks: Optional[bool], cylc7_compat: bool, rose_suite_conf: bool, expected_exc: Optional[Type[Exception]], expected_log_level: Optional[int], caplog: pytest.LogCaptureFixture, log_filter: Callable, monkeypatch: pytest.MonkeyPatch, tmp_flow_config: Callable ): """Test that the prescence of implicit tasks in the config is handled correctly. Params: allow_implicit_tasks: Value of "[scheduler]allow implicit tasks". cylc7_compat: Whether Cylc 7 backwards compatibility is turned on. rose_suite_conf: Whether a rose-suite.conf file is present in run dir. expected_exc: Exception expected to be raised only when "[scheduler]allow implicit tasks" is not set. expected_log_level: Expected logging severity level for the "implicit tasks detected" message only when "[scheduler]allow implicit tasks" is not set. """ # Setup reg = 'rincewind' flow_file: Path = tmp_flow_config(reg, f""" [scheduler] { f'allow implicit tasks = {allow_implicit_tasks}' if allow_implicit_tasks is not None else '' } [scheduling] [[graph]] R1 = foo """) monkeypatch.setattr('cylc.flow.flags.cylc7_back_compat', cylc7_compat) if rose_suite_conf: (flow_file.parent / 'rose-suite.conf').touch() caplog.set_level(logging.DEBUG, CYLC_LOG) if allow_implicit_tasks is True: expected_exc = None expected_log_level = logging.DEBUG elif allow_implicit_tasks is False: expected_exc = WorkflowConfigError # Test args: dict = {'workflow': reg, 'fpath': flow_file, 'options': None} expected_msg = "implicit tasks detected" if expected_exc: with pytest.raises(expected_exc) as exc: WorkflowConfig(**args) assert expected_msg in str(exc.value) else: WorkflowConfig(**args) assert log_filter( caplog, level=expected_log_level, contains=expected_msg )
def main(parser: COP, options: 'Values', workflow_id1: str, workflow_id2: str): workflow_id_1, _, workflow_file_1_ = parse_id( workflow_id1, src=True, constraint='workflows', ) workflow_id_2, _, workflow_file_2_ = parse_id( workflow_id2, src=True, constraint='workflows', ) if workflow_file_1_ == workflow_file_2_: parser.error("You can't diff a single workflow.") print(f"Parsing {workflow_id_1} ({workflow_file_1_})") template_vars = load_template_vars( options.templatevars, options.templatevars_file ) config1 = WorkflowConfig( workflow_id_1, workflow_file_1_, options, template_vars ).cfg print(f"Parsing {workflow_id_2} ({workflow_file_2_})") config2 = WorkflowConfig( workflow_id_2, workflow_file_2_, options, template_vars, is_reload=True ).cfg if config1 == config2: print( f"Workflow definitions {workflow_id_1} and {workflow_id_2} are " f"identical" ) sys.exit(0) print(f"Workflow definitions {workflow_id_1} and {workflow_id_2} differ") workflow1_only = {} # type: ignore workflow2_only = {} # type: ignore diff_1_2 = {} # type: ignore # TODO: this whole file could do wih refactoring at some point diffdict(config1, config2, workflow1_only, workflow2_only, diff_1_2) if n_oone > 0: print(f'\n{n_oone} items only in {workflow_id_1} (<)') prdict(workflow1_only, '<', nested=options.nested) if n_otwo > 0: print(f'\n{n_otwo} items only in {workflow_id_2} (>)') prdict(workflow2_only, '>', nested=options.nested) if n_diff > 0: print(f'\n{n_diff} common items differ {workflow_id_1}(<) ' f'{workflow_id_2}(>)') prdict(diff_1_2, '', diff=True, nested=options.nested)
def test_process_runahead_limit(cycling_type: str, runahead_limit: str, valid: bool, set_cycling_type: Callable) -> None: set_cycling_type(cycling_type) mock_config = Mock(cycling_type=cycling_type) mock_config.cfg = {'scheduling': {'runahead limit': runahead_limit}} if valid: WorkflowConfig.process_runahead_limit(mock_config) else: with pytest.raises(WorkflowConfigError) as exc: WorkflowConfig.process_runahead_limit(mock_config) assert "bad runahead limit" in str(exc.value).lower()
def _test(utc_mode, expected, expected_warnings=0): mock_glbl_cfg( 'cylc.flow.config.glbl_cfg', f''' [scheduler] UTC mode = {utc_mode['glbl']} ''') mock_config = Mock() mock_config.cfg = {'scheduler': {'UTC mode': utc_mode['workflow']}} mock_config.options.utc_mode = utc_mode['stored'] WorkflowConfig.process_utc_mode(mock_config) assert mock_config.cfg['scheduler']['UTC mode'] is expected assert get_utc_mode() is expected assert len(caplog.record_tuples) == expected_warnings caplog.clear()
def _test(cp_tz, utc_mode, expected, expected_warnings=0): set_utc_mode(utc_mode) mock_config = Mock() mock_config.cfg = { 'scheduler': { 'cycle point time zone': cp_tz['workflow'] } } mock_config.options.cycle_point_tz = cp_tz['stored'] WorkflowConfig.process_cycle_point_tz(mock_config) assert mock_config.cfg['scheduler'][ 'cycle point time zone'] == expected assert len(caplog.record_tuples) == expected_warnings caplog.clear()
def test_xfunction_not_callable(self, mock_glbl_cfg, tmp_path): """Test for error when a xtrigger function is not callable.""" mock_glbl_cfg( 'cylc.flow.platforms.glbl_cfg', ''' [platforms] [[localhost]] hosts = localhost ''' ) python_dir = tmp_path / "lib" / "python" python_dir.mkdir(parents=True) not_callable_file = python_dir / "not_callable.py" # NB: we are not returning a lambda, instead we have a scalar not_callable_file.write_text("""not_callable = 42""") flow_file = tmp_path / WorkflowFiles.FLOW_FILE flow_config = """ [scheduling] initial cycle point = 2018-01-01 [[xtriggers]] oopsie = not_callable() [[graph]] R1 = '@oopsie => qux' """ flow_file.write_text(flow_config) with pytest.raises(ValueError) as excinfo: WorkflowConfig( workflow="workflow_with_not_callable", fpath=flow_file, options=Mock(spec=[]) ) assert "callable" in str(excinfo.value)
def test_c7_back_compat_optional_outputs(tmp_flow_config, monkeypatch, caplog): """Test optional and required outputs Cylc 7 back compat mode. Success outputs should be required, others optional. Tested here because success is set to required after graph parsing, in taskdef processing. """ caplog.set_level(logging.WARNING, CYLC_LOG) monkeypatch.setattr('cylc.flow.flags.cylc7_back_compat', True) reg = 'custom_out2' flow_file = tmp_flow_config( reg, ''' [scheduling] [[graph]] R1 = """ foo:x => bar foo:fail = oops foo => spoo """ [runtime] [[bar, oops, spoo]] [[foo]] [[[outputs]]] x = x ''') cfg = WorkflowConfig(workflow=reg, fpath=flow_file, options=None) assert WorkflowConfig.CYLC7_GRAPH_COMPAT_MSG in caplog.text for taskdef in cfg.taskdefs.values(): for output, (_, required) in taskdef.outputs.items(): if output in [TASK_OUTPUT_SUBMITTED, TASK_OUTPUT_SUCCEEDED]: assert required else: assert not required
def test_xfunction_imports( self, mock_glbl_cfg: Fixture, tmp_path: Path, xtrigger_mgr: XtriggerManager): """Test for a workflow configuration with valid xtriggers""" mock_glbl_cfg( 'cylc.flow.platforms.glbl_cfg', ''' [platforms] [[localhost]] hosts = localhost ''' ) python_dir = tmp_path / "lib" / "python" python_dir.mkdir(parents=True) name_a_tree_file = python_dir / "name_a_tree.py" # NB: we are not returning a lambda, instead we have a scalar name_a_tree_file.write_text("""name_a_tree = lambda: 'jacaranda'""") flow_file = tmp_path / WorkflowFiles.FLOW_FILE flow_config = """ [scheduler] allow implicit tasks = True [scheduling] initial cycle point = 2018-01-01 [[xtriggers]] tree = name_a_tree() [[graph]] R1 = '@tree => qux' """ flow_file.write_text(flow_config) workflow_config = WorkflowConfig( workflow="name_a_tree", fpath=flow_file, options=Mock(spec=[]), xtrigger_mgr=xtrigger_mgr ) assert 'tree' in workflow_config.xtrigger_mgr.functx_map
def get_platform_from_task_def(flow: str, task: str) -> Dict[str, Any]: """Return the platform dictionary for a particular task. Uses the flow definition - designed to be used with tasks with unsubmitted jobs. Evaluates platform/host defined as subshell. Args: flow: The name of the Cylc flow to be queried. task: The name of the task to be queried. Returns: Platform Dictionary. """ _, _, flow_file = parse_id(flow, constraint='workflows', src=True) config = WorkflowConfig(flow, flow_file, Values()) # Get entire task spec to allow Cylc 7 platform from host guessing. task_spec = config.pcfg.get(['runtime', task]) # check for subshell and evaluate if (task_spec.get('platform') and is_platform_definition_subshell(task_spec['platform'])): task_spec['platform'] = eval_subshell(task_spec['platform']) elif (task_spec.get('remote', {}).get('host') and HOST_REC_COMMAND.match(task_spec['remote']['host'])): task_spec['remote']['host'] = eval_subshell( task_spec['remote']['host']) platform = get_platform(task_spec) return platform
def test_xfunction_import_error(self, mock_glbl_cfg, tmp_path): """Test for error when a xtrigger function cannot be imported.""" mock_glbl_cfg( 'cylc.flow.platforms.glbl_cfg', ''' [platforms] [[localhost]] hosts = localhost ''' ) python_dir = tmp_path / "lib" / "python" python_dir.mkdir(parents=True) caiman_file = python_dir / "caiman.py" # NB: we are not returning a lambda, instead we have a scalar caiman_file.write_text("""caiman = lambda: True""") flow_file = tmp_path / WorkflowFiles.FLOW_FILE flow_config = """ [scheduling] initial cycle point = 2018-01-01 [[xtriggers]] oopsie = piranha() [[graph]] R1 = '@oopsie => qux' """ flow_file.write_text(flow_config) with pytest.raises(ImportError) as excinfo: WorkflowConfig( workflow="caiman_workflow", fpath=flow_file, options=Mock(spec=[]) ) assert "not found" in str(excinfo.value)
def get_config(workflow: str, opts: 'Values') -> WorkflowConfig: """Return a WorkflowConfig object for the provided reg / path.""" workflow, flow_file = parse_reg(workflow, src=True) template_vars = get_template_vars(opts) return WorkflowConfig(workflow, flow_file, opts, template_vars=template_vars)
def test_valid_rsync_includes_returns_correct_list(tmp_flow_config): """Test that the rsync includes in the correct """ reg = 'rsynctest' flow_file = tmp_flow_config( reg, """ [scheduling] initial cycle point = 2020-01-01 [[dependencies]] graph = "blah => deeblah" [scheduler] install = dir/, dir2/, file1, file2 allow implicit tasks = True """) config = WorkflowConfig(workflow=reg, fpath=flow_file, options=Values()) rsync_includes = WorkflowConfig.get_validated_rsync_includes(config) assert rsync_includes == ['dir/', 'dir2/', 'file1', 'file2']
def test_process_icp( cycling_type: str, scheduling_cfg: Dict[str, Any], expected_icp: Optional[str], expected_opt_icp: Optional[str], expected_err: Optional[Tuple[Type[Exception], str]], monkeypatch: pytest.MonkeyPatch, set_cycling_type: Fixture ) -> None: """Test WorkflowConfig.process_initial_cycle_point(). "now" is assumed to be 2005-01-02T06:15+0530 Params: cycling_type: Workflow cycling type. scheduling_cfg: 'scheduling' section of workflow config. expected_icp: The expected icp value that gets set. expected_opt_icp: The expected value of options.icp that gets set (this gets stored in the workflow DB). expected_err: Exception class expected to be raised plus the message. """ set_cycling_type(cycling_type, time_zone="+0530") mocked_config = Mock(cycling_type=cycling_type) mocked_config.cfg = { 'scheduling': scheduling_cfg } mocked_config.options.icp = None monkeypatch.setattr('cylc.flow.config.get_current_time_string', lambda: '20050102T0615+0530') if expected_err: err, msg = expected_err with pytest.raises(err) as exc: WorkflowConfig.process_initial_cycle_point(mocked_config) assert msg in str(exc.value) else: WorkflowConfig.process_initial_cycle_point(mocked_config) assert mocked_config.cfg[ 'scheduling']['initial cycle point'] == expected_icp assert str(mocked_config.initial_point) == expected_icp opt_icp = mocked_config.options.icp if opt_icp is not None: opt_icp = str(loader.get_point(opt_icp).standardise()) assert opt_icp == expected_opt_icp
def main(parser, options, *args): workflow1_name, workflow1_config = parse_workflow_arg(options, args[0]) workflow2_name, workflow2_config = parse_workflow_arg(options, args[1]) if workflow1_name == workflow2_name: parser.error("You can't diff a single workflow.") print(f"Parsing {workflow1_name} ({workflow1_config})") template_vars = load_template_vars( options.templatevars, options.templatevars_file) config1 = WorkflowConfig( workflow1_name, workflow1_config, options, template_vars).cfg print(f"Parsing {workflow2_name} ({workflow2_config})") config2 = WorkflowConfig( workflow2_name, workflow2_config, options, template_vars, is_reload=True).cfg if config1 == config2: print( f"Workflow definitions {workflow1_name} and {workflow2_name} are " f"identical" ) sys.exit(0) print(f"Workflow definitions {workflow1_name} and {workflow2_name} differ") workflow1_only = {} workflow2_only = {} diff_1_2 = {} diffdict(config1, config2, workflow1_only, workflow2_only, diff_1_2) if n_oone > 0: print(f'\n{n_oone} items only in {workflow1_name} (<)') prdict(workflow1_only, '<', nested=options.nested) if n_otwo > 0: print(f'\n{n_otwo} items only in {workflow2_name} (>)') prdict(workflow2_only, '>', nested=options.nested) if n_diff > 0: print(f'\n{n_diff} common items differ {workflow1_name}(<) ' f'{workflow2_name}(>)') prdict(diff_1_2, '', diff=True, nested=options.nested)
def test_valid_rsync_includes_returns_correct_list(tmp_path): """Test that the rsync includes in the correct """ flow_cylc_content = """ [scheduling] initial cycle point = 2020-01-01 [[dependencies]] graph = "blah => deeblah" [scheduler] install = dir/, dir2/, file1, file2 allow implicit tasks = True """ flow_cylc = tmp_path.joinpath(WorkflowFiles.FLOW_FILE) flow_cylc.write_text(flow_cylc_content) config = WorkflowConfig(workflow="rsynctest", fpath=flow_cylc, options=Mock(spec=[])) rsync_includes = WorkflowConfig.get_validated_rsync_includes(config) assert rsync_includes == ['dir/', 'dir2/', 'file1', 'file2']
def test_process_startcp(startcp: Optional[str], expected: str, monkeypatch: pytest.MonkeyPatch, set_cycling_type: Fixture) -> None: """Test WorkflowConfig.process_start_cycle_point(). An icp of 1899-05-01T00+0530 is assumed, and "now" is assumed to be 2005-01-02T06:15+0530 Params: startcp: The start cycle point given by cli option. expected: The expected startcp value that gets set. """ set_cycling_type(ISO8601_CYCLING_TYPE, time_zone="+0530") mocked_config = Mock(initial_point='18990501T0000+0530') mocked_config.options.startcp = startcp monkeypatch.setattr('cylc.flow.config.get_current_time_string', lambda: '20050102T0615+0530') WorkflowConfig.process_start_cycle_point(mocked_config) assert str(mocked_config.start_point) == expected
def get_config(workflow_id: str, opts: 'Values') -> WorkflowConfig: """Return a WorkflowConfig object for the provided reg / path.""" workflow_id, _, flow_file = parse_id( workflow_id, src=True, constraint='workflows', ) template_vars = get_template_vars(opts) return WorkflowConfig(workflow_id, flow_file, opts, template_vars=template_vars)
def test_prelim_process_graph(scheduling_cfg: Dict[str, Any], scheduling_expected: Optional[Dict[str, Any]], expected_err: Optional[Tuple[Type[Exception], str]]): """Test WorkflowConfig.prelim_process_graph(). Params: scheduling_cfg: 'scheduling' section of workflow config. scheduling_expected: The expected scheduling section after preliminary processing. expected_err: Exception class expected to be raised plus the message. """ mock_config = Mock(cfg={'scheduling': scheduling_cfg}) if expected_err: err, msg = expected_err with pytest.raises(err) as exc: WorkflowConfig.prelim_process_graph(mock_config) assert msg in str(exc.value) else: WorkflowConfig.prelim_process_graph(mock_config) assert mock_config.cfg['scheduling'] == scheduling_expected
def test_success_after_optional_submit(tmp_flow_config, graph): """Check foo:succeed is not required if foo:submit is optional.""" reg = 'blargh' flow_file = tmp_flow_config(reg, f""" [scheduling] [[graph]] R1 = {graph} [runtime] [[bar]] [[foo]] """) cfg = WorkflowConfig(workflow=reg, fpath=flow_file, options=None) assert not cfg.taskdefs['foo'].outputs[TASK_OUTPUT_SUCCEEDED][1]
def test_family_inheritance_and_quotes( fam_txt: str, mock_glbl_cfg: Callable, tmp_path: Path ) -> None: """Test that inheritance does not ignore items, if not all quoted. For example: inherit = 'MAINFAM<major, minor>', SOMEFAM inherit = 'BIGFAM', SOMEFAM See bug #2700 for more/ """ mock_glbl_cfg( 'cylc.flow.platforms.glbl_cfg', ''' [platforms] [[localhost]] hosts = localhost ''' ) cfg = f''' [scheduler] allow implicit tasks = True [task parameters] major = 1..5 minor = 10..20 [scheduling] [[graph]] R1 = """hello => MAINFAM<major, minor> hello => SOMEFAM""" [runtime] [[root]] script = true [[MAINFAM<major, minor>]] [[SOMEFAM]] [[ goodbye_0<major, minor> ]] inherit = 'MAINFAM<major, minor>', {fam_txt} ''' file_path = tmp_path / 'thing.cylc' file_path.write_text(cfg) config = WorkflowConfig( 'test', str(file_path), template_vars={}, options=Mock(spec=[]) ) assert ('goodbye_0_major1_minor10' in config.runtime['descendants']['MAINFAM_major1_minor10']) assert ('goodbye_0_major1_minor10' in config.runtime['descendants']['SOMEFAM'])
def test_undefined_custom_output(tmp_flow_config: Callable): """Test error on undefined custom output referenced in graph.""" reg = 'custom_out1' flow_file = tmp_flow_config(reg, """ [scheduling] [[graph]] R1 = "foo:x => bar" [runtime] [[foo, bar]] """) with pytest.raises(WorkflowConfigError) as cm: WorkflowConfig(workflow=reg, fpath=flow_file, options=Values()) assert "Undefined custom output" in str(cm.value)
def test_success_after_optional_submit(tmp_path, graph): """Check foo:succeed is not required if foo:submit is optional.""" flow_config = f""" [scheduling] [[graph]] R1 = {graph} [runtime] [[bar]] [[foo]] """ flow_file = tmp_path.joinpath(WorkflowFiles.FLOW_FILE) flow_file.write_text(flow_config) cfg = WorkflowConfig(workflow='blargh', fpath=flow_file, options=None) assert not cfg.taskdefs['foo'].outputs[TASK_OUTPUT_SUCCEEDED][1]
def test_implicit_success_required(tmp_flow_config, graph): """Check foo:succeed is required if success/fail not used in the graph.""" reg = 'blargh' flow_file = tmp_flow_config(reg, f""" [scheduling] [[graph]] R1 = {graph} [runtime] [[bar]] [[foo]] [[[outputs]]] x = "the quick brown fox" """) cfg = WorkflowConfig(workflow=reg, fpath=flow_file, options=None) assert cfg.taskdefs['foo'].outputs[TASK_OUTPUT_SUCCEEDED][1]
def test_rsync_includes_will_not_accept_sub_directories(tmp_flow_config): reg = 'rsynctest' flow_file = tmp_flow_config( reg, """ [scheduling] initial cycle point = 2020-01-01 [[dependencies]] graph = "blah => deeblah" [scheduler] install = dir/, dir2/subdir2/, file1, file2 """) with pytest.raises(WorkflowConfigError) as exc: WorkflowConfig(workflow=reg, fpath=flow_file, options=Values()) assert "Directories can only be from the top level" in str(exc.value)
def test_undefined_custom_output(tmp_path): """Test error on undefined custom output referenced in graph.""" flow_config = """ [scheduling] [[graph]] R1 = "foo:x => bar" [runtime] [[foo, bar]] """ flow_file = tmp_path.joinpath(WorkflowFiles.FLOW_FILE) flow_file.write_text(flow_config) with pytest.raises(WorkflowConfigError) as cm: WorkflowConfig( workflow='custom_out1', fpath=flow_file, options=None) assert "Undefined custom output" in str(cm.value)
def test_implicit_success_required(tmp_path, graph): """Check foo:succeed is required if success/fail not used in the graph.""" flow_config = f""" [scheduling] [[graph]] R1 = {graph} [runtime] [[bar]] [[foo]] [[[outputs]]] x = "the quick brown fox" """ flow_file = tmp_path.joinpath(WorkflowFiles.FLOW_FILE) flow_file.write_text(flow_config) cfg = WorkflowConfig(workflow='blargh', fpath=flow_file, options=None) assert cfg.taskdefs['foo'].outputs[TASK_OUTPUT_SUCCEEDED][1]
def main( parser: COP, options: 'Values', *ids, ) -> None: if options.print_platform_names and options.print_platforms: options.print_platform_names = False if options.print_platform_names or options.print_platforms: # Get platform information: if ids: raise UserInputError( "Workflow IDs are incompatible with --platform options.") glbl_cfg().platform_dump(options.print_platform_names, options.print_platforms) return if not ids: if options.print_hierarchy: print("\n".join(get_config_file_hierarchy())) return glbl_cfg().idump(options.item, not options.defaults, oneline=options.oneline, none_str=options.none_str) return workflow_id, _, flow_file = parse_id( *ids, src=True, constraint='workflows', ) if options.print_hierarchy: print("\n".join(get_config_file_hierarchy(workflow_id))) return config = WorkflowConfig(workflow_id, flow_file, options, get_template_vars(options)) config.pcfg.idump(options.item, not options.defaults, oneline=options.oneline, none_str=options.none_str)