mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
674 lines
17 KiB
Python
674 lines
17 KiB
Python
import os
|
|
from shutil import rmtree
|
|
from tempfile import mkdtemp
|
|
|
|
from galaxy.tool_util.loader import load_tool, template_macro_params
|
|
from galaxy.tool_util.unittest_utils.sample_data import SIMPLE_MACRO, SIMPLE_TOOL_WITH_MACRO
|
|
from galaxy.util import parse_xml, xml_to_string
|
|
|
|
|
|
class TestToolDirectory:
|
|
__test__ = False # Prevent pytest from discovering this class (issue #12071)
|
|
|
|
def __init__(self):
|
|
self.temp_directory = mkdtemp()
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, type, value, tb):
|
|
rmtree(self.temp_directory)
|
|
|
|
def write(self, contents, name="tool.xml"):
|
|
open(os.path.join(self.temp_directory, name), "w").write(contents)
|
|
|
|
def load(self, name="tool.xml", preprocess=True):
|
|
path = os.path.join(self.temp_directory, name)
|
|
if preprocess:
|
|
return load_tool(path)
|
|
else:
|
|
return parse_xml(path)
|
|
|
|
|
|
def test_no_macros():
|
|
"""
|
|
Test tool loaded in absence of a macros node.
|
|
"""
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('<tool/>')
|
|
tool_dir.load(preprocess=True)
|
|
|
|
|
|
def test_loader_simple():
|
|
"""
|
|
Test simple macro replacement.
|
|
"""
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<expand macro="inputs" />
|
|
<macros>
|
|
<macro name="inputs">
|
|
<inputs />
|
|
</macro>
|
|
</macros>
|
|
</tool>''')
|
|
xml = tool_dir.load(preprocess=False)
|
|
assert xml.find("inputs") is None
|
|
xml = tool_dir.load(preprocess=True)
|
|
assert xml.find("inputs") is not None
|
|
|
|
|
|
def test_loader_external():
|
|
"""
|
|
Test importing macros from external files
|
|
"""
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write(SIMPLE_TOOL_WITH_MACRO)
|
|
|
|
tool_dir.write(SIMPLE_MACRO.substitute(tool_version="2.0"), name="external.xml")
|
|
xml = tool_dir.load(preprocess=False)
|
|
assert xml.find("inputs") is None
|
|
xml = tool_dir.load(preprocess=True)
|
|
assert xml.find("inputs") is not None
|
|
|
|
|
|
def test_loader_unnamed_yield():
|
|
# Test macros with unnamed yield statements.
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<expand macro="inputs">
|
|
<input name="first_input" />
|
|
</expand>
|
|
<expand macro="inputs">
|
|
<input name="second_input" />
|
|
</expand>
|
|
<expand macro="inputs">
|
|
<input name="third_input" />
|
|
</expand>
|
|
<macros>
|
|
<macro name="inputs">
|
|
<expand macro="foo">
|
|
<yield />
|
|
</expand>
|
|
</macro>
|
|
<macro name="foo">
|
|
<inputs>
|
|
<yield />
|
|
</inputs>
|
|
</macro>
|
|
</macros>
|
|
</tool>''')
|
|
xml = tool_dir.load()
|
|
assert xml.find("/inputs[1]/input").get("name") == "first_input"
|
|
assert xml.find("/inputs[2]/input").get("name") == "second_input"
|
|
assert xml.find("/inputs[3]/input").get("name") == "third_input"
|
|
|
|
|
|
def test_loader_unnamed_yield_nested():
|
|
"""
|
|
Test nested macro with yield statements
|
|
"""
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write("""
|
|
<tool>
|
|
<macros>
|
|
<macro name="paired_options">
|
|
<when value="paired">
|
|
<yield />
|
|
</when>
|
|
<when value="paired_collection">
|
|
<yield />
|
|
</when>
|
|
</macro>
|
|
<macro name="single_or_paired_general">
|
|
<conditional name="library">
|
|
<expand macro="paired_options">
|
|
<yield />
|
|
</expand>
|
|
</conditional>
|
|
</macro>
|
|
</macros>
|
|
<inputs>
|
|
<expand macro="single_or_paired_general">
|
|
<param name="test"/>
|
|
</expand>
|
|
</inputs>
|
|
</tool>
|
|
""")
|
|
xml = tool_dir.load()
|
|
# assert the both yields in the inner macro (paired_options) are expanded
|
|
assert xml.find('/inputs/conditional[@name="library"]/when[@value="paired"]/param[@name="test"]') is not None
|
|
assert xml.find('/inputs/conditional[@name="library"]/when[@value="paired_collection"]/param[@name="test"]') is not None
|
|
|
|
|
|
def test_loader_recursive():
|
|
"""
|
|
Test recursive macro applications.
|
|
"""
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<expand macro="inputs">
|
|
<input name="first_input" />
|
|
<expand macro="second" />
|
|
</expand>
|
|
<macros>
|
|
<macro name="inputs">
|
|
<inputs>
|
|
<yield />
|
|
<input name="third_input" />
|
|
</inputs>
|
|
</macro>
|
|
<macro name="second">
|
|
<input name="second_input" />
|
|
</macro>
|
|
</macros>
|
|
</tool>''')
|
|
xml = tool_dir.load()
|
|
assert xml.find("/inputs/input[1]").get("name") == "first_input"
|
|
assert xml.find("/inputs/input[2]").get("name") == "second_input"
|
|
assert xml.find("/inputs/input[3]").get("name") == "third_input"
|
|
|
|
|
|
def test_loader_recursive2():
|
|
"""
|
|
Test recursive macro applications.
|
|
"""
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<expand macro="inputs">
|
|
<input name="first_input" />
|
|
<expand macro="second" />
|
|
</expand>
|
|
<macros>
|
|
<macro name="inputs">
|
|
<inputs>
|
|
<yield />
|
|
</inputs>
|
|
</macro>
|
|
<macro name="second">
|
|
<expand macro="second_delegate" />
|
|
<input name="third_input" />
|
|
</macro>
|
|
<macro name="second_delegate">
|
|
<input name="second_input" />
|
|
</macro>
|
|
</macros>
|
|
</tool>''')
|
|
xml = tool_dir.load()
|
|
assert xml.find("/inputs/input[1]").get("name") == "first_input"
|
|
assert xml.find("/inputs/input[2]").get("name") == "second_input"
|
|
assert xml.find("/inputs/input[3]").get("name") == "third_input"
|
|
|
|
|
|
def test_loader_toplevel_yield():
|
|
"""
|
|
test expansion of top level (ie child of <macro>) yields
|
|
"""
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool id="issue_647">
|
|
<macros>
|
|
<macro name="a">
|
|
<param name="a1" type="text" value="a1" label="a1"/>
|
|
<yield />
|
|
</macro>
|
|
</macros>
|
|
<inputs>
|
|
<expand macro="a">
|
|
<param name="b" type="text" value="b" label="b" />
|
|
</expand>
|
|
</inputs>
|
|
</tool>''')
|
|
xml = tool_dir.load()
|
|
assert xml.find("/inputs/param[1]").get("name") == "a1"
|
|
assert xml.find("/inputs/param[2]").get("name") == "b"
|
|
|
|
|
|
def test_loader_shortcut():
|
|
"""
|
|
Test <xml> is shortcut for macro type="xml"
|
|
"""
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<expand macro="inputs" />
|
|
<macros>
|
|
<xml name="inputs">
|
|
<inputs />
|
|
</xml>
|
|
</macros>
|
|
</tool>''')
|
|
xml = tool_dir.load()
|
|
assert xml.find("inputs") is not None
|
|
|
|
|
|
def test_loader_template():
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<command interpreter="python">tool_wrapper.py
|
|
#include source=$tool_params
|
|
</command>
|
|
<macros>
|
|
<template name="tool_params">-a 1 -b 2</template>
|
|
</macros>
|
|
</tool>
|
|
''')
|
|
xml = tool_dir.load()
|
|
params_dict = template_macro_params(xml.getroot())
|
|
assert params_dict['tool_params'] == "-a 1 -b 2"
|
|
|
|
|
|
def test_loader_token_text():
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<macros>
|
|
<token name="@CITATION@">The citation.</token>
|
|
</macros>
|
|
<help>@CITATION@</help>
|
|
<another>
|
|
<tag />
|
|
</another>
|
|
</tool>
|
|
''')
|
|
xml = tool_dir.load()
|
|
help_el = xml.find("help")
|
|
assert help_el.text == "The citation.", help_el.text
|
|
|
|
|
|
def test_loader_token_nested():
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<macros>
|
|
<token name="@WIZARD@">Ab@HELPER@ra</token>
|
|
<token name="@HELPER@">ra@WITCH@ab</token>
|
|
<token name="@WITCH@">cad</token>
|
|
</macros>
|
|
<help>@WIZARD@</help>
|
|
<another>
|
|
<tag />
|
|
</another>
|
|
</tool>
|
|
''')
|
|
xml = tool_dir.load()
|
|
help_el = xml.find("help")
|
|
assert help_el.text == "Abracadabra", help_el.text
|
|
|
|
|
|
def test_loader_token_cycle():
|
|
"""
|
|
test if cycles in nested tokens are detected
|
|
"""
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<macros>
|
|
<token name="@A@">a @ROSE@</token>
|
|
<token name="@ROSE@">rose @IS@</token>
|
|
<token name="@IS@">a @A@</token>
|
|
</macros>
|
|
<help>@A@</help>
|
|
<another>
|
|
<tag />
|
|
</another>
|
|
</tool>
|
|
''')
|
|
try:
|
|
tool_dir.load()
|
|
except Exception as e:
|
|
assert str(e) == "Token '@IS@' cannot contain itself"
|
|
else:
|
|
raise AssertionError("Cycle not detected, but then we are doomed anyway")
|
|
|
|
|
|
def test_loader_token_attribute_name():
|
|
"""
|
|
test the repacement of an attribute name by a token
|
|
"""
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<macros>
|
|
<token name="__ATTR_NAME">name</token>
|
|
</macros>
|
|
<another>
|
|
<tag __ATTR_NAME="blah" />
|
|
</another>
|
|
</tool>
|
|
''')
|
|
xml = tool_dir.load()
|
|
assert xml.find('/another/tag[@name="blah"]') is not None
|
|
|
|
|
|
def test_loader_token_attribute_value():
|
|
"""
|
|
test the repacement of an attribute value by a token
|
|
"""
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<macros>
|
|
<token name="@TAG_VAL@">The value.</token>
|
|
</macros>
|
|
<another>
|
|
<tag value="@TAG_VAL@" />
|
|
</another>
|
|
</tool>
|
|
''')
|
|
xml = tool_dir.load()
|
|
assert xml.find('/another/tag[@value="The value."]') is not None
|
|
|
|
|
|
def test_loader_token_empty():
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<macros>
|
|
<token name="@TAG_VAL@"><![CDATA[]]></token>
|
|
</macros>
|
|
<another>
|
|
<tag value="@TAG_VAL@" />
|
|
</another>
|
|
</tool>
|
|
''')
|
|
xml = tool_dir.load()
|
|
assert xml.find('/another/tag[@value=""]') is not None
|
|
|
|
|
|
def test_loader_macro_token_quote():
|
|
"""
|
|
Test macros XML macros with $$ expansions in attributes
|
|
"""
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<expand macro="inputs" bar="hello" />
|
|
<expand macro="inputs" bar="my awesome" />
|
|
<expand macro="inputs" bar="doggo" />
|
|
<macros>
|
|
<xml name="inputs" tokens="bar" token_quote="$$">
|
|
<inputs type="the type is $$BAR$$" />
|
|
</xml>
|
|
</macros>
|
|
</tool>
|
|
''')
|
|
xml = tool_dir.load()
|
|
input_els = xml.findall("inputs")
|
|
assert len(input_els) == 3
|
|
assert input_els[0].attrib["type"] == "the type is hello"
|
|
assert input_els[1].attrib["type"] == "the type is my awesome"
|
|
assert input_els[2].attrib["type"] == "the type is doggo"
|
|
|
|
|
|
def test_loader_macro_token():
|
|
"""
|
|
Test macros XML macros with @ expansions in text
|
|
"""
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<expand macro="inputs" foo="hello" />
|
|
<expand macro="inputs" foo="world" />
|
|
<expand macro="inputs" />
|
|
<macros>
|
|
<xml name="inputs" token_foo="the_default">
|
|
<inputs>@FOO@</inputs>
|
|
</xml>
|
|
</macros>
|
|
</tool>
|
|
''')
|
|
xml = tool_dir.load()
|
|
input_els = xml.findall("inputs")
|
|
assert len(input_els) == 3
|
|
assert input_els[0].text == "hello"
|
|
assert input_els[1].text == "world"
|
|
assert input_els[2].text == "the_default"
|
|
|
|
|
|
def test_loader_macro_token_recursive():
|
|
"""
|
|
Test macros XML macros with @ expansions and recursive
|
|
"""
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<expand macro="inputs" foo="hello" />
|
|
<expand macro="inputs" foo="world" />
|
|
<expand macro="inputs" />
|
|
<macros>
|
|
<xml name="inputs" token_foo="the_default">
|
|
<expand macro="real_inputs"><cow>@FOO@</cow></expand>
|
|
</xml>
|
|
<xml name="real_inputs">
|
|
<inputs><yield /></inputs>
|
|
</xml>
|
|
</macros>
|
|
</tool>
|
|
''')
|
|
xml = tool_dir.load()
|
|
input_els = xml.findall("inputs")
|
|
assert len(input_els) == 3
|
|
assert input_els[0].find("cow").text == "hello"
|
|
assert input_els[1].find("cow").text == "world"
|
|
assert input_els[2].find("cow").text == "the_default"
|
|
|
|
|
|
def test_loader_macro_named_yield():
|
|
"""
|
|
test expansion of named and unnamed yield
|
|
- named yields are replaced by content of the corresponding token
|
|
- unnamed yields are replaced by all non-token elements of the expand tag
|
|
"""
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<macros>
|
|
<xml name="test">
|
|
<A>
|
|
<yield/>
|
|
</A>
|
|
<yield name="token1"/>
|
|
<B>
|
|
<yield/>
|
|
<yield name="token2"/>
|
|
</B>
|
|
</xml>
|
|
</macros>
|
|
<expand macro="test">
|
|
<token name="token1">
|
|
<content_of_token1/>
|
|
<more_content_of_token1/>
|
|
</token>
|
|
<sub_of_expand_1/>
|
|
<token name="token2">
|
|
<content_of_token2/>
|
|
<more_content_of_token2/>
|
|
</token>
|
|
<sub_of_expand_2/>
|
|
</expand>
|
|
</tool>
|
|
''')
|
|
xml = tool_dir.load()
|
|
assert xml_to_string(xml, pretty=True) == '''<?xml version="1.0" ?>
|
|
<tool>
|
|
<macros/>
|
|
<A>
|
|
<sub_of_expand_1/>
|
|
<sub_of_expand_2/>
|
|
</A>
|
|
<content_of_token1/>
|
|
<more_content_of_token1/>
|
|
<B>
|
|
<sub_of_expand_1/>
|
|
<sub_of_expand_2/>
|
|
<content_of_token2/>
|
|
<more_content_of_token2/>
|
|
</B>
|
|
</tool>'''
|
|
|
|
|
|
def test_loader_macro_multiple_toplevel_yield():
|
|
"""
|
|
test replacement of multiple top level yield
|
|
"""
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<macros>
|
|
<xml name="test">
|
|
<blah/>
|
|
<yield/>
|
|
<blah>
|
|
<yield name="token1"/>
|
|
</blah>
|
|
<yield name="token2"/>
|
|
</xml>
|
|
</macros>
|
|
<expand macro="test">
|
|
<token name="token1">
|
|
<content_of_token1/>
|
|
<more_content_of_token1/>
|
|
</token>
|
|
<sub_of_expand_1/>
|
|
<token name="token2">
|
|
<content_of_token2/>
|
|
<more_content_of_token2/>
|
|
</token>
|
|
<sub_of_expand_2/>
|
|
</expand>
|
|
</tool>
|
|
''')
|
|
xml = tool_dir.load()
|
|
assert xml_to_string(xml, pretty=True) == '''<?xml version="1.0" ?>
|
|
<tool>
|
|
<macros/>
|
|
<blah/>
|
|
<sub_of_expand_1/>
|
|
<sub_of_expand_2/>
|
|
<blah>
|
|
<content_of_token1/>
|
|
<more_content_of_token1/>
|
|
</blah>
|
|
<content_of_token2/>
|
|
<more_content_of_token2/>
|
|
</tool>'''
|
|
|
|
|
|
def test_loader_macro_recursive_named_yield():
|
|
"""
|
|
test 'recursive' replacement with named yields
|
|
since named yields are processed in order of the definition of the
|
|
corresponding tokens:
|
|
- replacing yield for token1 introduces yield for token2
|
|
- replacing yield for token2 introduced unnamed yield
|
|
- replacing unnamed yield gives the only non-token element of the expand
|
|
"""
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<macros>
|
|
<xml name="test">
|
|
<A>
|
|
<yield name="token1"/>
|
|
</A>
|
|
</xml>
|
|
</macros>
|
|
<expand macro="test">
|
|
<token name="token1">
|
|
<T1>
|
|
<yield name="token2"/>
|
|
</T1>
|
|
</token>
|
|
<token name="token2">
|
|
<T2>
|
|
<yield/>
|
|
</T2>
|
|
</token>
|
|
<T/>
|
|
</expand>
|
|
</tool>''')
|
|
|
|
xml = tool_dir.load()
|
|
assert xml_to_string(xml, pretty=True) == '''<?xml version="1.0" ?>
|
|
<tool>
|
|
<macros/>
|
|
<A>
|
|
<T1>
|
|
<T2>
|
|
<T/>
|
|
</T2>
|
|
</T1>
|
|
</A>
|
|
</tool>'''
|
|
|
|
|
|
def test_loader_specify_nested_macro_by_token():
|
|
"""
|
|
test if a nested macro can have a nested
|
|
macro specifying the macro name via a token
|
|
of the outer macro
|
|
"""
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<macros>
|
|
<import>external.xml</import>
|
|
</macros>
|
|
<expand macro="testp" token_name="a"/>
|
|
<expand macro="testp" token_name="b"/>
|
|
</tool>''')
|
|
tool_dir.write('''
|
|
<macros>
|
|
<xml name="a">
|
|
<A/>
|
|
</xml>
|
|
<xml name="b">
|
|
<B/>
|
|
</xml>
|
|
<xml name="testp" tokens="token_name">
|
|
<expand macro="@TOKEN_NAME@"/>
|
|
</xml>
|
|
</macros>
|
|
''', name="external.xml")
|
|
|
|
xml = tool_dir.load()
|
|
assert xml_to_string(xml, pretty=True) == '''<?xml version="1.0" ?>
|
|
<tool>
|
|
<macros/>
|
|
<A/>
|
|
<B/>
|
|
</tool>'''
|
|
|
|
|
|
def test_loader_circular_macros():
|
|
"""
|
|
check the cycles in nested macros are detected
|
|
"""
|
|
with TestToolDirectory() as tool_dir:
|
|
tool_dir.write('''
|
|
<tool>
|
|
<macros>
|
|
<xml name="a">
|
|
<A>
|
|
<expand macro="b"/>
|
|
</A>
|
|
</xml>
|
|
<xml name="b">
|
|
<B>
|
|
<expand macro="a"/>
|
|
</B>
|
|
</xml>
|
|
</macros>
|
|
<expand macro="a"/>
|
|
</tool>''')
|
|
try:
|
|
tool_dir.load()
|
|
except AssertionError as a:
|
|
assert str(a) == "Cycle in nested macros: already expanded ['a', 'b'] can't expand 'a' again"
|