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_simple_replace(self): """Replace parameter references in simple template with argument values. """ for filename in [TEMPLATE_YAML_FILE, TEMPLATE_JSON_FILE]: template = DefaultTemplateLoader().load(filename) arguments = { 'code': TemplateArgument(parameter=template.get_parameter('code'), value=FileHandle('code/helloworld.py')), 'names': TemplateArgument(parameter=template.get_parameter('names'), value=FileHandle('data/list-of-names.txt')), 'sleeptime': TemplateArgument(parameter=template.get_parameter('sleeptime'), value=10) } spec = tmpl.replace_args(spec=template.workflow_spec, arguments=arguments, parameters=template.parameters) assert spec['inputs']['files'][0] == 'helloworld.py' assert spec['inputs']['files'][1] == 'data/names.txt' assert spec['inputs']['parameters'][ 'helloworld'] == 'code/helloworld.py' assert spec['inputs']['parameters'][ 'inputfile'] == 'data/names.txt' assert spec['inputs']['parameters']['sleeptime'] == 10 assert spec['inputs']['parameters']['waittime'] == 5
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_add_template(self, tmpdir): """Test creating templates.""" store = TemplateRepository(base_dir=str(tmpdir)) template = store.add_template(src_dir=WORKFLOW_DIR) self.validate_template_handle(template) # Ensure that the template handle has been serialized correctly f = os.path.join(store.base_dir, template.identifier, TEMPLATE_FILE) doc = DefaultTemplateLoader().load(f) d = os.path.join(store.base_dir, template.identifier, STATIC_FILES_DIR) assert os.path.isdir(d) # Get template and repeat tests self.validate_template_handle(store.get_template(template.identifier)) store = TemplateRepository(base_dir=str(tmpdir)) self.validate_template_handle(store.get_template(template.identifier)) # Add template with JSON specification file template = store.add_template(src_dir=WORKFLOW_DIR, template_spec_file=JSON_SPEC) self.validate_template_handle(template) # Unknown template error with pytest.raises(err.UnknownTemplateError): store.get_template('unknown') # Errors when specifying wrong parameter combination with pytest.raises(ValueError): store.add_template() with pytest.raises(ValueError): store.add_template(src_dir=WORKFLOW_DIR, src_repo_url=WORKFLOW_DIR) # Load templates with erroneous specifications with pytest.raises(err.InvalidTemplateError): store.add_template(src_dir=WORKFLOW_DIR, template_spec_file=ERR_SPEC) # Error when cloning invalid repository from GitHub with pytest.raises(err.InvalidTemplateError): store.add_template( src_repo_url='https://github.com/reanahub/reana-demo-helloworld' )
def test_expand_parameters(self): """Test parameter expansion.""" template = DefaultTemplateLoader().load(TEMPLATE_FILE) arguments = { 'code': TemplateArgument( parameter=template.get_parameter('code'), value=FileHandle(filepath='code/runme.py') ), 'names': TemplateArgument( parameter=template.get_parameter('names'), value=FileHandle(filepath='data/myfriends.txt') ), 'sleeptime': TemplateArgument( parameter=template.get_parameter('sleeptime'), value=11 ), 'waittime': TemplateArgument( parameter=template.get_parameter('waittime'), value=22 ) } commands = mp.get_commands(template=template, arguments=arguments) CMDS = [ 'python "runme.py" --inputfile "data/names.txt" --outputfile "results/greetings.txt" --sleeptime 11', 'wait 22', 'python "code/eval.py" --inputfile "results/greetings.txt" --outputfile results.json' ] assert commands == CMDS # Default values arguments = { 'names': TemplateArgument( parameter=template.get_parameter('names'), value=FileHandle(filepath='data/myfriends.txt') ) } commands = mp.get_commands(template=template, arguments=arguments) CMDS = [ 'python "code/helloworld.py" --inputfile "data/names.txt" --outputfile "results/greetings.txt" --sleeptime 10', 'wait 5', 'python "code/eval.py" --inputfile "results/greetings.txt" --outputfile results.json' ] assert commands == CMDS # Error cases del template.workflow_spec['inputs']['parameters']['inputfile'] with pytest.raises(err.InvalidTemplateError): mp.get_commands(template=template, arguments=arguments)
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 __init__(self, base_dir, loader=None, filenames=None, suffixes=None, id_func=None, max_attempts=DEFAULT_MAX_ATTEMPTS): """Initialize the base directory where templates are maintained. The optional identifier function is used to generate unique template identifier. By default, short identifier are used. Uses the cross product of file names and suffixes to look for a template specification file when adding a new template. Parameters ---------- base_dir: string Base directory for templates loader: benchtmpl.workflow.template.loader.TemplateLoader, optional Loader for reading and writing template specification files filenames: list(string) List of file names for templates files suffixes: list(string) List of recognized file suffies for template files id_func: func, optional Function to generate template folder identifier max_attempts: int, optional Maximum number of attempts to create a unique folder for a new workflow template """ self.base_dir = os.path.abspath(base_dir) self.loader = loader if not loader is None else DefaultTemplateLoader() self.filenames = filenames if not filenames is None else [ 'template', 'workflow' ] self.suffixes = suffixes if not suffixes is None else [ '.yml', '.yaml', '.json' ] self.id_func = id_func if not id_func is None else util.get_short_identifier self.max_attempts = max_attempts # Create the base directory if it does not exist util.create_dir(self.base_dir)
def test_get_parameter_references(self): """Test function to get all parameter references in a workflow specification.""" spec = { 'input': ['A', '$[[X]]', { 'B': { 'C': '$[[Y]]', 'D': [123, '$[[Z]]'] } }], 'E': { 'E': 'XYZ', 'F': 23, 'G': '$[[W]]' }, 'F': '$[[U]]', 'G': ['$[[V]]', 123] } refs = tmpl.get_parameter_references(spec) assert refs == set(['U', 'V', 'W', 'X', 'Y', 'Z']) # If given parameter set as argument the elements in that set are part # of the result para = set(['A', 'B', 'X']) refs = tmpl.get_parameter_references(spec, parameters=para) assert refs == set(['A', 'B', 'U', 'V', 'W', 'X', 'Y', 'Z']) # Error if specification contains nested lists with pytest.raises(err.InvalidTemplateError): tmpl.get_parameter_references({ 'input': [ 'A', ['$[[X]]'], { 'B': { 'C': '$[[Y]]', 'D': [123, '$[[Z]]'] } } ] }) # Error when loading specification that references undefined parameter with pytest.raises(err.UnknownParameterError): template = DefaultTemplateLoader().load(TEMPLATE_ERR, validate=True)
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_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)