def test_serialization(self): """Test serialization of workflow templates.""" template = TemplateHandle( identifier='ABC', base_dir='XYZ', workflow_spec=dict(), parameters=[ TemplateParameter(pd.parameter_declaration('A')), TemplateParameter( pd.parameter_declaration('B', data_type=pd.DT_LIST)), TemplateParameter(pd.parameter_declaration('C', parent='B')) ]) doc = DefaultTemplateLoader().to_dict(template) parameters = DefaultTemplateLoader().from_dict(doc).parameters assert len(parameters) == 3 assert 'A' in parameters assert 'B' in parameters assert len(parameters['B'].children) == 1 template = DefaultTemplateLoader().from_dict(doc) assert template.identifier == 'ABC' # The base directory is not materialized assert template.base_dir is None # Invalid resource descriptor serializations with pytest.raises(err.InvalidTemplateError): ResourceDescriptor.from_dict(dict()) with pytest.raises(err.InvalidTemplateError): ResourceDescriptor.from_dict({LABEL_ID: 'A', 'noname': 'B'})
def test_invalid_identifier(self): """Assert that an error is raised if a package declaration with an invalid identifier is given. """ # Ensure that it works with a valid identifier pd.parameter_declaration(identifier='ABC', data_type=pd.DT_BOOL) # Error is raised if identifier is None with pytest.raises(err.InvalidParameterError): pd.parameter_declaration(identifier=None, data_type=pd.DT_BOOL)
def test_validate_error(self): """Assert that errors are raised if an invalid parameter declaration is given to the validate_parameter function. """ p = pd.parameter_declaration(identifier='ABC') # Ensure that creating a dictionary from a valid parameter declaration # is still valid pd.validate_parameter(dict(p)) # Invalid data type for parameter identifier p_invalid = dict(p) p_invalid[pd.LABEL_ID] = 123 with pytest.raises(err.InvalidParameterError): pd.validate_parameter(p_invalid) # Invalid data type for parameter name p_invalid = dict(p) p_invalid[pd.LABEL_NAME] = 123 with pytest.raises(err.InvalidParameterError): pd.validate_parameter(p_invalid) # Invalid data type for parameter data type p_invalid = dict(p) p_invalid[pd.LABEL_DATATYPE] = 12.3 with pytest.raises(err.InvalidParameterError): pd.validate_parameter(p_invalid) # Invalid data type for parameter index p_invalid = dict(p) p_invalid[pd.LABEL_INDEX] = '12' with pytest.raises(err.InvalidParameterError): pd.validate_parameter(p_invalid) # Invalid data type for parameter required p_invalid = dict(p) p_invalid[pd.LABEL_REQUIRED] = '12' with pytest.raises(err.InvalidParameterError): pd.validate_parameter(p_invalid)
def test_validate(self): """Test error cases for argument validation.""" para_list = TemplateParameter( pd.parameter_declaration('E', data_type=pd.DT_LIST)) with pytest.raises(ValueError): values.TemplateArgument(parameter=para_list, value=1) para_record = TemplateParameter( pd.parameter_declaration('E', data_type=pd.DT_RECORD)) with pytest.raises(ValueError): values.TemplateArgument(parameter=para_record, value=list()) arg = values.TemplateArgument(parameter=TemplateParameter( pd.parameter_declaration('E', data_type=pd.DT_INTEGER)), value=1, validate=True) arg.data_type = 'unknown' with pytest.raises(ValueError): arg.validate()
def test_invalid_datatype(self): """Assert that an error is raised if a package declaration with an invalid data type is given. """ # Ensure that it works with a valid data type p = pd.parameter_declaration(identifier='ABC', data_type=pd.DT_RECORD) with pytest.raises(err.InvalidParameterError): p = pd.parameter_declaration(identifier='ABC', data_type='XYZ') # Specifying a non-string value should also raise an error with pytest.raises(err.InvalidParameterError): pd.parameter_declaration(identifier='ABC', data_type=123) # Ensure that validation fails if data type is manipulated p = pd.parameter_declaration(identifier='ABC', data_type=pd.DT_RECORD) pd.validate_parameter(p) p[pd.LABEL_DATATYPE] = 'Something unknown' with pytest.raises(err.InvalidParameterError): pd.validate_parameter(p)
def test_init(self): """Test initialization of attribuets and error cases when creating template instances. """ th = TemplateHandle( workflow_spec=dict(), parameters=[ TemplateParameter(pd.parameter_declaration('A')), TemplateParameter(pd.parameter_declaration('B')) ]) assert not th.identifier is None assert th.base_dir is None th = TemplateHandle( identifier='ABC', base_dir='XYZ', workflow_spec=dict(), parameters=[ TemplateParameter(pd.parameter_declaration('A')), TemplateParameter(pd.parameter_declaration('B')) ]) assert th.identifier == 'ABC' assert th.base_dir == 'XYZ' with pytest.raises(err.InvalidTemplateError): TemplateHandle( workflow_spec=dict(), parameters=[ TemplateParameter(pd.parameter_declaration('A')), TemplateParameter(pd.parameter_declaration('B')), TemplateParameter(pd.parameter_declaration('A')) ])
def test_duplicate_id(self): """Ensure that exception is raised if parameter identifier are not unique. """ with pytest.raises(err.InvalidTemplateError): DefaultTemplateLoader().from_dict( { loader.LABEL_WORKFLOW: dict(), loader.LABEL_PARAMETERS: [ pd.parameter_declaration('A', index=1), pd.parameter_declaration('B'), pd.parameter_declaration('C'), pd.parameter_declaration('A', index=2), pd.parameter_declaration('E', index=1) ] }, validate=True)
def test_read_with_record(self): """Read argument for a template that contains a parameter of data type DT_RECORD. """ template = TemplateHandle( workflow_spec=dict(), parameters=[ TemplateParameter( pd.parameter_declaration(identifier='codeFile', data_type=pd.DT_FILE, index=0, default_value=None, as_const=AS_INPUT)), TemplateParameter( pd.parameter_declaration(identifier='dataFile', data_type=pd.DT_FILE, index=1, default_value='data/names.txt')), TemplateParameter( pd.parameter_declaration(identifier='resultFile', data_type=pd.DT_FILE, index=2, default_value=None)), TemplateParameter( pd.parameter_declaration(identifier='sleeptime', data_type=pd.DT_INTEGER, index=3, default_value=10)), TemplateParameter( pd.parameter_declaration(identifier='verbose', data_type=pd.DT_BOOL, index=4, default_value=False)), TemplateParameter( pd.parameter_declaration(identifier='frac', data_type=pd.DT_DECIMAL, index=6)), TemplateParameter( pd.parameter_declaration(identifier='outputType', index=5)), ]) sc = Scanner(reader=ListReader([ 'ABC.txt', 'code/abc.py', '', 'result/output.txt', 3, True, 'XYZ', 0.123 ])) arguments = tmpl.read(template.list_parameters(), scanner=sc) assert arguments['codeFile'].name == 'ABC.txt' assert arguments['codeFile'].target_path == 'code/abc.py' assert arguments['dataFile'].name == 'names.txt' assert arguments['dataFile'].target_path is None assert arguments['resultFile'].name == 'output.txt' assert arguments['resultFile'].target_path is None assert arguments['sleeptime'] == 3 assert arguments['verbose'] assert arguments['outputType'] == 'XYZ' assert arguments['frac'] == 0.123
def test_sort(self): """Test the sort functionality of the template list_parameters method. """ # Create a new TemplateHandle with an empty workflow specification and # a list of five parameters template = DefaultTemplateLoader().from_dict( { loader.LABEL_WORKFLOW: dict(), loader.LABEL_PARAMETERS: [ pd.parameter_declaration('A', index=1), pd.parameter_declaration('B'), pd.parameter_declaration('C'), pd.parameter_declaration('D', index=2), pd.parameter_declaration('E', index=1) ] }, validate=True) # Get list of sorted parameter identifier from listing keys = [p.identifier for p in template.list_parameters()] assert keys == ['B', 'C', 'A', 'E', 'D']
def test_nested_parameters(self): """Test proper nesting of parameters for DT_LIST and DT_RECORD.""" # Create a new TemplateHandle with an empty workflow specification and # a list of six parameters (one record and one list) template = DefaultTemplateLoader().from_dict( { loader.LABEL_WORKFLOW: dict(), loader.LABEL_PARAMETERS: [ pd.parameter_declaration('A'), pd.parameter_declaration('B', data_type=pd.DT_RECORD), pd.parameter_declaration('C', parent='B'), pd.parameter_declaration('D', parent='B'), pd.parameter_declaration('E', data_type=pd.DT_LIST), pd.parameter_declaration('F', parent='E'), ] }, validate=True) # Parameters 'A', 'C', 'D', and 'F' have no children for key in ['A', 'C', 'D', 'F']: assert not template.get_parameter(key).has_children() # Parameter 'B' has two children 'C' and 'D' b = template.get_parameter('B') assert b.has_children() assert len(b.children) == 2 assert 'C' in [p.identifier for p in b.children] assert 'D' in [p.identifier for p in b.children] # Parameter 'E' has one childr 'F' e = template.get_parameter('E') assert e.has_children() assert len(e.children) == 1 assert 'F' in [p.identifier for p in e.children]
def test_propmpt(self): """Test generated prompts when reading parameter values from standard input. """ # BOOL p = TemplateParameter( pd.set_defaults( pd.parameter_declaration(identifier='ABC', data_type=pd.DT_BOOL))).prompt() assert p == 'ABC (bool): ' # FILE p = TemplateParameter( pd.set_defaults( pd.parameter_declaration(identifier='ABC', data_type=pd.DT_FILE))).prompt() assert p == 'ABC (file): ' # FLOAT p = TemplateParameter( pd.set_defaults( pd.parameter_declaration(identifier='ABC', data_type=pd.DT_DECIMAL))).prompt() assert p == 'ABC (decimal): ' # INTEGER p = TemplateParameter( pd.set_defaults( pd.parameter_declaration(identifier='ABC', data_type=pd.DT_INTEGER))).prompt() assert p == 'ABC (integer): ' # STRING p = TemplateParameter( pd.set_defaults( pd.parameter_declaration(identifier='ABC', data_type=pd.DT_STRING))).prompt() assert p == 'ABC (string): ' # Default values in prompts p = TemplateParameter( pd.set_defaults( pd.parameter_declaration(identifier='ABC', data_type=pd.DT_INTEGER, default_value=100))).prompt() assert p == 'ABC (integer) [default 100]: ' p = TemplateParameter( pd.set_defaults( pd.parameter_declaration(identifier='ABC', data_type=pd.DT_STRING, default_value=100))).prompt() assert p == 'ABC (string) [default \'100\']: '
def test_parameter_declaration(self): """Test methods of the parameter declaration object.""" obj = pd.set_defaults(pd.parameter_declaration(identifier='ABC')) obj[pd.LABEL_AS] = 'XYZ' p = TemplateParameter(obj) # Type flags assert not p.is_bool() assert not p.is_file() assert not p.is_float() assert not p.is_int() assert not p.is_list() assert not p.is_record() assert p.is_string() # Constant values assert p.has_constant() assert p.get_constant() == 'XYZ' # Error for invalid data type with pytest.raises(err.InvalidParameterError): TemplateParameter({pd.LABEL_ID: 'A', pd.LABEL_DATATYPE: 'XYZ'})
def test_minimal_declaration(self): """Test parameter declarations that only provide the required arguments. """ # Expect to get a dictionary that contains the identifier, name (both # equal to 'ABC'), a data type DT_STRING, an index of 0. The required # flag is True. p = pd.parameter_declaration(identifier='ABC') assert isinstance(p, dict) assert p.get(pd.LABEL_ID) == 'ABC' assert p.get(pd.LABEL_NAME) == 'ABC' assert p.get(pd.LABEL_DESCRIPTION) == 'ABC' assert p.get(pd.LABEL_DATATYPE) == pd.DT_STRING assert p.get(pd.LABEL_INDEX) == 0 assert p.get(pd.LABEL_REQUIRED) # All other optional elements of the declaration are missing assert pd.LABEL_DEFAULT not in p assert pd.LABEL_PARENT not in p assert pd.LABEL_VALUES not in p # Ensure that the returned dictionary is valid with respect to the # parameter schema declaration. pd.validate_parameter(p)
def test_maximal_declaration(self): """Test parameter declarations that provide values for all arguments. """ # Set all parameter elements to values that are different from their # default value p = pd.parameter_declaration(identifier='ABC', name='XYZ', description='ABC to XYZ', data_type=pd.DT_INTEGER, index=10, required=False, values=[ pd.enum_value(value=1), pd.enum_value(value=2, text='Two'), pd.enum_value(value=3, text='THREE', is_default=True) ], parent='DEF', default_value=5, as_const='data/names.txt') assert isinstance(p, dict) assert p.get(pd.LABEL_ID) == 'ABC' assert p.get(pd.LABEL_NAME) == 'XYZ' assert p.get(pd.LABEL_DESCRIPTION) == 'ABC to XYZ' assert p.get(pd.LABEL_DATATYPE) == pd.DT_INTEGER assert p.get(pd.LABEL_INDEX) == 10 assert not p.get(pd.LABEL_REQUIRED) assert p.get(pd.LABEL_PARENT) == 'DEF' assert p.get(pd.LABEL_DEFAULT) == 5 assert p.get(pd.LABEL_AS) == 'data/names.txt' # Valudate value enumeration values = p.get(pd.LABEL_VALUES, []) assert len(values) == 3 self.validate_value(values[0], 1, '1', False) self.validate_value(values[1], 2, 'Two', False) self.validate_value(values[2], 3, 'THREE', True) # Ensure that the returned dictionary is valid with respect to the # parameter schema declaration. pd.validate_parameter(p)
def test_flat_parse(self): """Test parsing arguments for a flat (un-nested) parameter declaration. """ template = TemplateHandle( workflow_spec=dict(), parameters=[ TemplateParameter( pd.parameter_declaration('A', data_type=pd.DT_INTEGER)), TemplateParameter( pd.parameter_declaration('B', data_type=pd.DT_BOOL)), TemplateParameter( pd.parameter_declaration('C', data_type=pd.DT_DECIMAL)), TemplateParameter( pd.parameter_declaration('D', data_type=pd.DT_FILE, required=False)), TemplateParameter( pd.parameter_declaration('E', data_type=pd.DT_STRING, required=False)) ]) params = template.parameters fh = InputFile(f_handle=FileHandle(filepath=LOCAL_FILE)) # Valid argument set args = values.parse_arguments(arguments={ 'A': 10, 'B': True, 'C': 12.5, 'D': fh, 'E': 'ABC' }, parameters=params, validate=True) assert len(args) == 5 for key in params.keys(): assert key in args values.parse_arguments(arguments=args, parameters=params, validate=False) # Error cases with pytest.raises(ValueError): values.parse_arguments(arguments={ 'A': 10, 'Z': 0 }, parameters=params) with pytest.raises(ValueError): values.parse_arguments(arguments={ 'A': 10, 'B': True }, parameters=params) # Validate data type with pytest.raises(ValueError): values.parse_arguments(arguments={ 'A': '10', 'B': True, 'C': 12.3, 'D': fh, 'E': 'ABC' }, parameters=params, validate=True) with pytest.raises(ValueError): values.parse_arguments(arguments={ 'A': 10, 'B': 23, 'C': 12.3, 'D': fh, 'E': 'ABC' }, parameters=params, validate=True) with pytest.raises(ValueError): values.parse_arguments(arguments={ 'A': 10, 'B': True, 'C': '12.3', 'D': fh, 'E': 'ABC' }, parameters=params, validate=True) with pytest.raises(ValueError): values.parse_arguments(arguments={ 'A': 10, 'B': True, 'C': 12.3, 'D': 'fh', 'E': 'ABC' }, parameters=params, validate=True) with pytest.raises(ValueError): values.parse_arguments(arguments={ 'A': 10, 'B': True, 'C': 12.3, 'D': fh, 'E': 12 }, parameters=params, validate=True)
def test_nested_parse(self): """Test parsing arguments for a nested parameter declaration.""" template = DefaultTemplateLoader().from_dict( { tmpl.LABEL_WORKFLOW: dict(), tmpl.LABEL_PARAMETERS: [ pd.parameter_declaration('A', data_type=pd.DT_INTEGER), pd.parameter_declaration('B', data_type=pd.DT_RECORD), pd.parameter_declaration( 'C', data_type=pd.DT_DECIMAL, parent='B'), pd.parameter_declaration('D', data_type=pd.DT_STRING, parent='B', required=False), pd.parameter_declaration( 'E', data_type=pd.DT_LIST, required=False), pd.parameter_declaration( 'F', data_type=pd.DT_INTEGER, parent='E'), pd.parameter_declaration('G', data_type=pd.DT_DECIMAL, parent='E', required=False) ] }, validate=True) params = template.parameters # Without values for list parameters args = values.parse_arguments(arguments={ 'A': 10, 'B': { 'C': 12.3 } }, parameters=params, validate=True) assert len(args) == 2 assert not args['B'].value.get('C') is None assert args['B'].value.get('D') is None assert len(args['B'].value) == 1 assert args['B'].value.get('C').value == 12.3 # With list arguments args = values.parse_arguments(arguments={ 'A': 10, 'B': { 'C': 12.3, 'D': 'ABC' }, 'E': [{ 'F': 1 }, { 'F': 2 }, { 'F': 3, 'G': 0.9 }] }, parameters=params, validate=True) assert len(args) == 3 assert not args['B'].value.get('C') is None assert not args['B'].value.get('D') is None assert len(args['B'].value) == 2 assert len(args['E'].value) == 3 for arg in args['E'].value: if arg.get('F').value < 3: assert len(arg) == 1 else: assert len(arg) == 2 # Error cases with pytest.raises(ValueError): values.parse_arguments(arguments={ 'A': 10, 'B': [{ 'C': 12.3 }] }, parameters=params, validate=True)