diff --git a/easybuild/framework/easyconfig/easyconfig.py b/easybuild/framework/easyconfig/easyconfig.py index 73182da7ac..2b6aaa751a 100644 --- a/easybuild/framework/easyconfig/easyconfig.py +++ b/easybuild/framework/easyconfig/easyconfig.py @@ -2026,12 +2026,14 @@ def resolve_template(value, tmpl_dict): # '%(name)s' -> '%(name)s' # '%%(name)s' -> '%%(name)s' if '%' in value: + orig_value = value value = re.sub(re.compile(r'(%)(?!%*\(\w+\)s)'), r'\1\1', value) try: value = value % tmpl_dict except KeyError: _log.warning("Unable to resolve template value %s with dict %s", value, tmpl_dict) + value = orig_value # Undo "%"-escaping else: # this block deals with references to objects and returns other references # for reading this is ok, but for self['x'] = {} diff --git a/test/framework/easyconfig.py b/test/framework/easyconfig.py index 45e2852221..4b63dc605b 100644 --- a/test/framework/easyconfig.py +++ b/test/framework/easyconfig.py @@ -3654,6 +3654,30 @@ def test_resolve_template(self): # '%(name)' is not a correct template spec (missing trailing 's') self.assertEqual(resolve_template('%(name)', tmpl_dict), '%(name)') + # Correct (un)escaping + values = ( + ('10%', '10%'), + ('%of', '%of'), + ('10%of', '10%of'), + ('%s', '%s'), + ('%%(name)s', '%(name)s'), + ('%%%(name)s', '%FooBar'), + ('%%%%(name)s', '%%(name)s'), + # It doesn't matter what is resolved + ('%%(invalid)s', '%(invalid)s'), + ('%%%%(invalid)s', '%%(invalid)s'), + ) + for value, expected in values: + self.assertEqual(resolve_template(value, tmpl_dict), expected) + # Templates are resolved + value += ' %(name)s' + expected += ' FooBar' + self.assertEqual(resolve_template(value, tmpl_dict), expected) + + # On unknown values the value is returned unchanged + for value in ('%(invalid)s', '%(name)s %(invalid)s', '%%%(invalid)s', '% %(invalid)s', '%s %(invalid)s'): + self.assertEqual(resolve_template(value, tmpl_dict), value) + def test_det_subtoolchain_version(self): """Test det_subtoolchain_version function""" _, all_tc_classes = search_toolchain('')