4
|
1 <tool name="toolfactory_tester" id="toolfactory_tester" version="1">
|
|
2 <!--Source in git at: https://github.com/fubar2/toolfactory-->
|
|
3 <!--Created by admin@galaxy.org at 23/04/2021 10:25:58 using the Galaxy Tool Factory.-->
|
|
4 <description>Test an untested tool and update it</description>
|
|
5 <requirements>
|
|
6 <requirement type="package" version="2.30.2">git</requirement>
|
|
7 <requirement type="package" version="0.74.3">planemo</requirement> </requirements>
|
|
8 <stdio>
|
|
9 <exit_code range="1:" level="fatal"/>
|
|
10 </stdio>
|
|
11 <version_command><![CDATA[echo "1"]]></version_command>
|
|
12 <command><![CDATA[
|
|
13 python
|
|
14 $runme
|
|
15 --in_tool_archive
|
|
16 $in_tool_archive
|
|
17 --new_tested_tool_archive
|
|
18 $new_tested_tool_archive
|
|
19 --galaxy_root
|
|
20 "$galaxyroot"
|
|
21 >
|
|
22 $tf_archive_tester_log;
|
|
23 ]]></command>
|
|
24 <configfiles>
|
|
25 <configfile name="runme"><![CDATA[#raw
|
|
26
|
|
27 # see https://github.com/fubar2/toolfactory
|
|
28 #
|
|
29 # copyright ross lazarus (ross stop lazarus at gmail stop com) May 2012
|
|
30 #
|
|
31 # all rights reserved
|
|
32 # Licensed under the LGPL
|
|
33 # suggestions for improvement and bug fixes welcome at
|
|
34 # https://github.com/fubar2/toolfactory
|
|
35 #
|
|
36 # July 2020: BCC was fun and I feel like rip van winkle after 5 years.
|
|
37 # Decided to
|
|
38 # 1. Fix the toolfactory so it works - done for simplest case
|
|
39 # 2. Fix planemo so the toolfactory function works
|
|
40 # 3. Rewrite bits using galaxyxml functions where that makes sense - done
|
|
41
|
|
42 import argparse
|
|
43 import copy
|
|
44 import os
|
|
45 import subprocess
|
|
46 import shutil
|
|
47 import sys
|
|
48 import tarfile
|
|
49 import tempfile
|
|
50 import time
|
|
51 import xml.etree.ElementTree as ET
|
|
52
|
|
53
|
|
54 myversion = "V2.2 April 2021"
|
|
55 verbose = True
|
|
56 debug = True
|
|
57 toolFactoryURL = "https://github.com/fubar2/toolfactory"
|
|
58
|
|
59 def timenow():
|
|
60 """return current time as a string"""
|
|
61 return time.strftime("%d/%m/%Y %H:%M:%S", time.localtime(time.time()))
|
|
62
|
|
63 class ToolTester():
|
|
64 # requires highly insecure docker settings - like write to tool_conf.xml and to tools !
|
|
65 # if in a container possibly not so courageous.
|
|
66 # Fine on your own laptop but security red flag for most production instances
|
|
67 # uncompress passed tar, run planemo and rebuild a new tarball with tests
|
|
68
|
|
69 def __init__(self, args=None, in_tool_archive='/galaxy-central/tools/newtool/newtool_toolshed.gz', new_tool_archive=None):
|
|
70 self.args = args
|
|
71 self.new_tool_archive = new_tool_archive
|
|
72 assert tarfile.is_tarfile(in_tool_archive)
|
|
73 # this is not going to go well with arbitrary names.
|
|
74 tff = tarfile.open(in_tool_archive, "r:*")
|
|
75 flist = tff.getnames()
|
|
76 ourdir = os.path.commonpath(flist) # eg pyrevpos
|
|
77 self.tool_name = ourdir
|
|
78 ourxmls = [x for x in flist if x.lower().endswith('.xml') and os.path.split(x)[0] == ourdir]
|
|
79 # planemo_test/planemo_test.xml
|
|
80 assert len(ourxmls) > 0
|
|
81 self.ourxmls = ourxmls # [os.path.join(tool_path,x) for x in ourxmls]
|
|
82 res = tff.extractall()
|
|
83 self.update_tests(ourdir)
|
|
84 tff.close()
|
|
85 self.tooloutdir = "./tfout"
|
|
86 self.repdir = "./TF_run_report"
|
|
87 self.testdir = os.path.join(self.tooloutdir, "test-data")
|
|
88 if not os.path.exists(self.tooloutdir):
|
|
89 os.mkdir(self.tooloutdir)
|
|
90 if not os.path.exists(self.testdir):
|
|
91 os.mkdir(self.testdir)
|
|
92 if not os.path.exists(self.repdir):
|
|
93 os.mkdir(self.repdir)
|
|
94 self.makeTool()
|
|
95 self.moveRunOutputs()
|
|
96 self.makeToolTar()
|
|
97
|
|
98 def call_planemo(self,xmlpath,ourdir):
|
|
99 penv = os.environ
|
|
100 #penv['HOME'] = os.path.join(self.args.galaxy_root,'planemo')
|
|
101 #penv["GALAXY_VIRTUAL_ENV"] = os.path.join(penv['HOME'],'.planemo','gx_venv_3.9')
|
|
102 penv["PIP_CACHE_DIR"] = os.path.join(self.args.galaxy_root,'pipcache')
|
|
103 toolfile = os.path.split(xmlpath)[1]
|
|
104 tool_name = self.tool_name
|
|
105 tool_test_output = f"{tool_name}_planemo_test_report.html"
|
|
106 cll = [
|
|
107 "planemo",
|
|
108 "test",
|
|
109 "--biocontainers",
|
|
110 "--test_output",
|
|
111 os.path.abspath(tool_test_output),
|
|
112 "--galaxy_root",
|
|
113 self.args.galaxy_root,
|
|
114 "--update_test_data",
|
|
115 os.path.abspath(xmlpath),
|
|
116 ]
|
|
117 print(cll)
|
|
118 p = subprocess.run(
|
|
119 cll,
|
|
120 #capture_output=True,
|
|
121 encoding='utf8',
|
|
122 env = penv,
|
|
123 shell=False,
|
|
124 )
|
|
125 return p
|
|
126
|
|
127 def makeTool(self):
|
|
128 """write xmls and input samples into place"""
|
|
129 for xreal in self.ourxmls:
|
|
130 x = os.path.split(xreal)[1]
|
|
131 xout = os.path.join(self.tooloutdir,x)
|
|
132 shutil.copyfile(xreal, xout)
|
|
133 # for p in self.infiles:
|
|
134 # pth = p["name"]
|
|
135 # dest = os.path.join(self.testdir, "%s_sample" % p["infilename"])
|
|
136 # shutil.copyfile(pth, dest)
|
|
137 # dest = os.path.join(self.repdir, "%s_sample" % p["infilename"])
|
|
138 # shutil.copyfile(pth, dest)
|
|
139
|
|
140 def makeToolTar(self):
|
|
141 """move outputs into test-data and prepare the tarball"""
|
|
142 excludeme = "_planemo_test_report.html"
|
|
143
|
|
144 def exclude_function(tarinfo):
|
|
145 filename = tarinfo.name
|
|
146 return None if filename.endswith(excludeme) else tarinfo
|
|
147
|
|
148 newtar = 'new_%s_toolshed.gz' % self.tool_name
|
|
149 ttf = tarfile.open(newtar, "w:gz")
|
|
150 ttf.add(name=self.tool_name,
|
|
151 arcname=self.tool_name,
|
|
152 filter=exclude_function)
|
|
153 ttf.close()
|
|
154 shutil.copyfile(newtar, self.new_tool_archive)
|
|
155
|
|
156 def moveRunOutputs(self):
|
|
157 """need to move planemo or run outputs into toolfactory collection"""
|
|
158 with os.scandir(self.tooloutdir) as outs:
|
|
159 for entry in outs:
|
|
160 if not entry.is_file():
|
|
161 continue
|
|
162 if "." in entry.name:
|
|
163 _, ext = os.path.splitext(entry.name)
|
|
164 if ext in [".tgz", ".json"]:
|
|
165 continue
|
|
166 if ext in [".yml", ".xml", ".yaml"]:
|
|
167 newname = f"{entry.name.replace('.','_')}.txt"
|
|
168 else:
|
|
169 newname = entry.name
|
|
170 else:
|
|
171 newname = f"{entry.name}.txt"
|
|
172 dest = os.path.join(self.repdir, newname)
|
|
173 src = os.path.join(self.tooloutdir, entry.name)
|
|
174 shutil.copyfile(src, dest)
|
|
175 with os.scandir('.') as outs:
|
|
176 for entry in outs:
|
|
177 if not entry.is_file():
|
|
178 continue
|
|
179 if "." in entry.name:
|
|
180 _, ext = os.path.splitext(entry.name)
|
|
181 if ext in [".yml", ".xml", ".yaml"]:
|
|
182 newname = f"{entry.name.replace('.','_')}.txt"
|
|
183 else:
|
|
184 newname = entry.name
|
|
185 else:
|
|
186 newname = f"{entry.name}.txt"
|
|
187 dest = os.path.join(self.repdir, newname)
|
|
188 src =entry.name
|
|
189 shutil.copyfile(src, dest)
|
|
190 if True or self.args.include_tests:
|
|
191 with os.scandir(self.testdir) as outs:
|
|
192 for entry in outs:
|
|
193 if (not entry.is_file()) or entry.name.endswith(
|
|
194 "_planemo_test_report.html"
|
|
195 ):
|
|
196 continue
|
|
197 if "." in entry.name:
|
|
198 _, ext = os.path.splitext(entry.name)
|
|
199 if ext in [".tgz", ".json"]:
|
|
200 continue
|
|
201 if ext in [".yml", ".xml", ".yaml"]:
|
|
202 newname = f"{entry.name.replace('.','_')}.txt"
|
|
203 else:
|
|
204 newname = entry.name
|
|
205 else:
|
|
206 newname = f"{entry.name}.txt"
|
|
207 dest = os.path.join(self.repdir, newname)
|
|
208 src = os.path.join(self.testdir, entry.name)
|
|
209 shutil.copyfile(src, dest)
|
|
210
|
|
211
|
|
212 def update_tests(self,ourdir):
|
|
213 for xmlf in self.ourxmls:
|
|
214 capture = self.call_planemo(xmlf,ourdir)
|
|
215 #sys.stderr.write('%s, stdout=%s, stderr=%s' % (xmlf, capture.stdout, capture.stdout))
|
|
216 #print('%s, stdout=%s, stderr=%s' % (capture.stdout, capture.stdout,xmlf))
|
|
217
|
|
218 def main():
|
|
219 """
|
|
220 This is a Galaxy wrapper.
|
|
221 It expects to be called by a special purpose tool.xml
|
|
222
|
|
223 """
|
|
224 parser = argparse.ArgumentParser()
|
|
225 a = parser.add_argument
|
|
226 a("--in_tool_archive", default=None)
|
|
227 a("--new_tested_tool_archive", default=None)
|
|
228 a("--galaxy_root", default="/home/ross/gal21/")
|
|
229 args = parser.parse_args()
|
|
230 print('Hello from',os.getcwd())
|
|
231 tt = ToolTester(args=args, in_tool_archive=args.in_tool_archive, new_tool_archive=args.new_tested_tool_archive)
|
|
232
|
|
233 if __name__ == "__main__":
|
|
234 main()
|
|
235
|
|
236
|
|
237 #end raw]]></configfile>
|
|
238 </configfiles>
|
|
239 <inputs>
|
|
240 <param name="new_tool_name" value="" type="hidden"/>
|
|
241 <param name="in_tool_archive" type="data" optional="false" label="Select a no_test tarfile to test and update for a toolshed" help="" format="toolshed.gz" multiple="false"/>
|
|
242 <param name="galaxyroot" type="text" value="/home/ross/gal21" label="Galaxy root for planemo to use - MUST be made available in the Galaxy job runner configuration" help=""/>
|
|
243 </inputs>
|
|
244 <outputs>
|
|
245 <data name="new_tested_tool_archive" format="toolshed.gz" label="${in_tool_archive.name.split('_')[0]}_tested_toolshed.gz" hidden="false"/>
|
|
246 <data name="tf_archive_tester_log" format="txt" label="${in_tool_archive.name}_test_log" hidden="false"/>
|
|
247 <collection name="TF_run_report" type="list" label="${in_tool_archive.name} test Run reports">
|
|
248 <discover_datasets pattern="__name_and_ext__" directory="TF_run_report" visible="false"/>
|
|
249 </collection>
|
|
250 </outputs>
|
|
251 <tests>
|
|
252 <test>
|
|
253 <output name="new_tested_tool_archive" value="new_tested_tool_archive_sample" compare="sim_size" delta_frac="0.5"/>
|
|
254 <output name="tf_archive_tester_log" value="tf_archive_tester_log_sample" compare="sim_size" delta_frac="0.1"/>
|
|
255 <param name="in_tool_archive" value="in_tool_archive_sample"/>
|
|
256 <param name="galaxyroot" value="/home/ross/gal21"/>
|
|
257 <output_collection name="TF_run_report"/>
|
|
258 </test>
|
|
259 </tests>
|
|
260 <help><![CDATA[
|
|
261
|
|
262 **What it Does**
|
|
263
|
|
264 ------
|
|
265
|
|
266 Script::
|
|
267
|
|
268 import argparse
|
|
269 import copy
|
|
270 import os
|
|
271 import subprocess
|
|
272 import shutil
|
|
273 import sys
|
|
274 import tarfile
|
|
275 import tempfile
|
|
276 import time
|
|
277 import xml.etree.ElementTree as ET
|
|
278 myversion = "V2.2 April 2021"
|
|
279 verbose = True
|
|
280 debug = True
|
|
281 toolFactoryURL = "https://github.com/fubar2/toolfactory"
|
|
282 def timenow():
|
|
283 """return current time as a string"""
|
|
284 return time.strftime("%d/%m/%Y %H:%M:%S", time.localtime(time.time()))
|
|
285
|
|
286 class ToolTester():
|
|
287 # requires highly insecure docker settings - like write to tool_conf.xml and to tools !
|
|
288 # if in a container possibly not so courageous.
|
|
289 # Fine on your own laptop but security red flag for most production instances
|
|
290 # uncompress passed tar, run planemo and rebuild a new tarball with tests
|
|
291 def __init__(self, args=None, in_tool_archive='/galaxy-central/tools/newtool/newtool_toolshed.gz', new_tool_archive=None):
|
|
292 self.args = args
|
|
293 self.new_tool_archive = new_tool_archive
|
|
294 assert tarfile.is_tarfile(in_tool_archive)
|
|
295 # this is not going to go well with arbitrary names.
|
|
296 tff = tarfile.open(in_tool_archive, "r:*")
|
|
297 flist = tff.getnames()
|
|
298 ourdir = os.path.commonpath(flist) # eg pyrevpos
|
|
299 self.tool_name = ourdir
|
|
300 ourxmls = [x for x in flist if x.lower().endswith('.xml') and os.path.split(x)[0] == ourdir]
|
|
301 # planemo_test/planemo_test.xml
|
|
302 assert len(ourxmls) > 0
|
|
303 self.ourxmls = ourxmls # [os.path.join(tool_path,x) for x in ourxmls]
|
|
304 res = tff.extractall()
|
|
305 self.update_tests(ourdir)
|
|
306 tff.close()
|
|
307 self.tooloutdir = "./tfout"
|
|
308 self.repdir = "./TF_run_report"
|
|
309 self.testdir = os.path.join(self.tooloutdir, "test-data")
|
|
310 if not os.path.exists(self.tooloutdir):
|
|
311 os.mkdir(self.tooloutdir)
|
|
312 if not os.path.exists(self.testdir):
|
|
313 os.mkdir(self.testdir)
|
|
314 if not os.path.exists(self.repdir):
|
|
315 os.mkdir(self.repdir)
|
|
316 self.makeTool()
|
|
317 self.moveRunOutputs()
|
|
318 self.makeToolTar()
|
|
319
|
|
320 def call_planemo(self,xmlpath,ourdir):
|
|
321 penv = os.environ
|
|
322 penv['HOME'] = '/home/ross/galaxy-release_21.01'
|
|
323 toolfile = os.path.split(xmlpath)[1]
|
|
324 tool_name = self.tool_name
|
|
325 tool_test_output = f"{tool_name}_planemo_test_report.html"
|
|
326 cll = [
|
|
327 "planemo",
|
|
328 "test",
|
|
329 "--test_output",
|
|
330 os.path.abspath(tool_test_output),
|
|
331 "--galaxy_root",
|
|
332 self.args.galaxy_root,
|
|
333 "--update_test_data",
|
|
334 os.path.abspath(xmlpath),
|
|
335 ]
|
|
336 print(cll)
|
|
337 p = subprocess.run(
|
|
338 cll,
|
|
339 capture_output=True,
|
|
340 encoding='utf8',
|
|
341 env = penv,
|
|
342 shell=False,
|
|
343 )
|
|
344 return p
|
|
345
|
|
346 def makeTool(self):
|
|
347 """write xmls and input samples into place"""
|
|
348 for xreal in self.ourxmls:
|
|
349 x = os.path.split(xreal)[1]
|
|
350 xout = os.path.join(self.tooloutdir,x)
|
|
351 shutil.copyfile(xreal, xout)
|
|
352 # for p in self.infiles:
|
|
353 # pth = p["name"]
|
|
354 # dest = os.path.join(self.testdir, "%s_sample" % p["infilename"])
|
|
355 # shutil.copyfile(pth, dest)
|
|
356 # dest = os.path.join(self.repdir, "%s_sample" % p["infilename"])
|
|
357 # shutil.copyfile(pth, dest)
|
|
358
|
|
359 def makeToolTar(self):
|
|
360 """move outputs into test-data and prepare the tarball"""
|
|
361 excludeme = "_planemo_test_report.html"
|
|
362 def exclude_function(tarinfo):
|
|
363 filename = tarinfo.name
|
|
364 return None if filename.endswith(excludeme) else tarinfo
|
|
365 newtar = 'new_%s_toolshed.gz' % self.tool_name
|
|
366 ttf = tarfile.open(newtar, "w:gz")
|
|
367 ttf.add(name=self.tooloutdir,
|
|
368 arcname=self.tool_name,
|
|
369 filter=exclude_function)
|
|
370 ttf.close()
|
|
371 shutil.copyfile(newtar, self.new_tool_archive)
|
|
372
|
|
373 def moveRunOutputs(self):
|
|
374 """need to move planemo or run outputs into toolfactory collection"""
|
|
375 with os.scandir(self.tooloutdir) as outs:
|
|
376 for entry in outs:
|
|
377 if not entry.is_file():
|
|
378 continue
|
|
379 if "." in entry.name:
|
|
380 _, ext = os.path.splitext(entry.name)
|
|
381 if ext in [".tgz", ".json"]:
|
|
382 continue
|
|
383 if ext in [".yml", ".xml", ".yaml"]:
|
|
384 newname = f"{entry.name.replace('.','_')}.txt"
|
|
385 else:
|
|
386 newname = entry.name
|
|
387 else:
|
|
388 newname = f"{entry.name}.txt"
|
|
389 dest = os.path.join(self.repdir, newname)
|
|
390 src = os.path.join(self.tooloutdir, entry.name)
|
|
391 shutil.copyfile(src, dest)
|
|
392 with os.scandir('.') as outs:
|
|
393 for entry in outs:
|
|
394 if not entry.is_file():
|
|
395 continue
|
|
396 if "." in entry.name:
|
|
397 _, ext = os.path.splitext(entry.name)
|
|
398 if ext in [".yml", ".xml", ".yaml"]:
|
|
399 newname = f"{entry.name.replace('.','_')}.txt"
|
|
400 else:
|
|
401 newname = entry.name
|
|
402 else:
|
|
403 newname = f"{entry.name}.txt"
|
|
404 dest = os.path.join(self.repdir, newname)
|
|
405 src =entry.name
|
|
406 shutil.copyfile(src, dest)
|
|
407 if True or self.args.include_tests:
|
|
408 with os.scandir(self.testdir) as outs:
|
|
409 for entry in outs:
|
|
410 if (not entry.is_file()) or entry.name.endswith(
|
|
411 "_planemo_test_report.html"
|
|
412 ):
|
|
413 continue
|
|
414 if "." in entry.name:
|
|
415 _, ext = os.path.splitext(entry.name)
|
|
416 if ext in [".tgz", ".json"]:
|
|
417 continue
|
|
418 if ext in [".yml", ".xml", ".yaml"]:
|
|
419 newname = f"{entry.name.replace('.','_')}.txt"
|
|
420 else:
|
|
421 newname = entry.name
|
|
422 else:
|
|
423 newname = f"{entry.name}.txt"
|
|
424 dest = os.path.join(self.repdir, newname)
|
|
425 src = os.path.join(self.testdir, entry.name)
|
|
426 shutil.copyfile(src, dest)
|
|
427
|
|
428 def update_tests(self,ourdir):
|
|
429 for xmlf in self.ourxmls:
|
|
430 capture = self.call_planemo(xmlf,ourdir)
|
|
431 #sys.stderr.write('%s, stdout=%s, stderr=%s' % (xmlf, capture.stdout, capture.stdout))
|
|
432 print('%s, stdout=%s, stderr=%s' % (capture.stdout, capture.stdout,xmlf))
|
|
433
|
|
434 def main():
|
|
435 """
|
|
436 This is a Galaxy wrapper.
|
|
437 It expects to be called by a special purpose tool.xml
|
|
438 """
|
|
439 parser = argparse.ArgumentParser()
|
|
440 a = parser.add_argument
|
|
441 a("--in_tool_archive", default=None)
|
|
442 a("--new_tested_tool_archive", default=None)
|
|
443 a("--galaxy_root", default="/home/ross/gal21/")
|
|
444 args = parser.parse_args()
|
|
445 print('Hello from',os.getcwd())
|
|
446 tt = ToolTester(args=args, in_tool_archive=args.in_tool_archive, new_tool_archive=args.new_tested_tool_archive)
|
|
447 if __name__ == "__main__":
|
|
448 main()
|
|
449
|
|
450 ]]></help>
|
|
451 <citations>
|
|
452 <citation type="doi">10.1093/bioinformatics/bts573</citation>
|
|
453 </citations>
|
|
454 </tool>
|
|
455
|