import os import re from typing import ( Any, List, Optional, Tuple, ) from galaxy.tool_util.model_factory import parse_tool from galaxy.tool_util.parameters import ( DataCollectionRequest, DataRequestHda, encode_test, input_models_for_tool_source, ) from galaxy.tool_util.parameters.case import ( test_case_state as case_state, TestCaseStateAndWarnings, TestCaseStateValidationResult, validate_test_cases_for_tool_source, ) from galaxy.tool_util.parser.factory import get_tool_source from galaxy.tool_util.parser.interface import ( ToolSource, ToolSourceTest, ) from galaxy.tool_util.unittest_utils import ( functional_test_tool_directory, functional_test_tool_source, ) from galaxy.tool_util.verify.parse import parse_tool_test_descriptions from galaxy.tool_util_models.tool_source import ( JsonTestCollectionDefDict, JsonTestDatasetDefDict, ) from .util import dict_verify_each # legacy tools allows specifying parameter and repeat parameters without # qualification. This was problematic and could result in ambigious specifications. TOOLS_THAT_USE_UNQUALIFIED_PARAMETER_ACCESS = [ "boolean_conditional.xml", "simple_constructs.xml", "section.xml", "collection_paired_conditional_structured_like.xml", "output_action_change_format.xml", "top_level_data.xml", "disambiguate_cond.xml", "multi_repeats.xml", "implicit_default_conds.xml", "min_repeat.xml", ] # tools that use truevalue/falsevalue in parameter setting, I think we're going to # forbid this for a future tool profile version. Potential ambigouity could result. TOOLS_THAT_USE_TRUE_FALSE_VALUE_BOOLEAN_SPECIFICATION = [ "inputs_as_json_profile.xml", "inputs_as_json_with_paths.xml", "inputs_as_json.xml", ] TOOLS_THAT_USE_SELECT_BY_VALUE = [ "multi_select.xml", ] # Figure out the problem and resolve. TOOLS_THAT_ARE_OUTSTANDING_ISSUES = [ "gx_conditional_boolean_optional.xml", "gx_conditional_boolean_discriminate_on_string_value.xml", ] TEST_TOOL_THAT_DO_NOT_VALIDATE = ( TOOLS_THAT_USE_UNQUALIFIED_PARAMETER_ACCESS + TOOLS_THAT_USE_TRUE_FALSE_VALUE_BOOLEAN_SPECIFICATION + TOOLS_THAT_USE_SELECT_BY_VALUE + TOOLS_THAT_ARE_OUTSTANDING_ISSUES + [ # will never handle upload_dataset "upload.xml", ] ) MOCK_ID = "thisisafakeid" def test_parameter_test_cases_validate(): validation_result = validate_test_cases_for("column_param") assert len(validation_result[0].warnings) == 0 assert len(validation_result[1].warnings) == 0 assert len(validation_result[2].warnings) == 1 validation_result = validate_test_cases_for("column_param", use_latest_profile=True) assert validation_result[2].validation_error def test_legacy_features_fail_validation_with_24_2(tmp_path): for filename in TOOLS_THAT_USE_UNQUALIFIED_PARAMETER_ACCESS + TOOLS_THAT_USE_TRUE_FALSE_VALUE_BOOLEAN_SPECIFICATION: _assert_tool_test_parsing_only_fails_with_newer_profile(tmp_path, filename, index=None) # column parameters need to be indexes _assert_tool_test_parsing_only_fails_with_newer_profile(tmp_path, "column_param.xml", index=2) # selection by value only _assert_tool_test_parsing_only_fails_with_newer_profile(tmp_path, "multi_select.xml", index=1) def _assert_tool_test_parsing_only_fails_with_newer_profile(tmp_path, filename: str, index: Optional[int] = 0): test_tool_directory = functional_test_tool_directory() original_path = os.path.join(test_tool_directory, filename) new_path = tmp_path / filename with open(original_path) as rf: tool_contents = rf.read() tool_contents = re.sub(r'profile="[\d\.]*"', r"", tool_contents) new_profile_contents = tool_contents.replace(" DataRequestHda: return DataRequestHda(src="hda", id=MOCK_ID) def mock_adapt_collections(input: JsonTestCollectionDefDict) -> DataCollectionRequest: return DataCollectionRequest(src="hdca", id=MOCK_ID) for test_case in test_cases: if test_case.get("expect_failure"): continue test_case_state_and_warnings = case_state(test_case, parsed_tool.inputs, profile) test_case_state = test_case_state_and_warnings.tool_state encode_test(test_case_state, parameters, mock_adapt_datasets, mock_adapt_collections) def _validate_path(tool_path: str): tool_source = get_tool_source(tool_path) tool_source_class = type(tool_source).__name__ raw_tool_source = tool_source.to_string() tool_source = get_tool_source(tool_source_class=tool_source_class, raw_tool_source=raw_tool_source) tool_id = tool_source.parse_id() model_name = f"{tool_id} (test case model)" parsed_tool = parse_tool(tool_source) profile = tool_source.parse_profile() test_cases: List[ToolSourceTest] = tool_source.parse_tests_to_dict()["tests"] for test_case in test_cases: if test_case.get("expect_failure"): continue test_case_state_and_warnings = case_state(test_case, parsed_tool.inputs, profile, name=model_name) tool_state = test_case_state_and_warnings.tool_state assert tool_state.state_representation == "test_case_xml" def validate_test_cases_for(tool_name: str, **kwd) -> List[TestCaseStateValidationResult]: return validate_test_cases_for_tool_source(tool_source_for(tool_name), **kwd) def case_state_for(tool_source: ToolSource, test_case: ToolSourceTest) -> TestCaseStateAndWarnings: parsed_tool = parse_tool(tool_source) profile = tool_source.parse_profile() return case_state(test_case, parsed_tool.inputs, profile) tool_source_for = functional_test_tool_source