77import json
88import logging
99import os
10- import pipes
1110import shutil
1211import sys
1312import tempfile
1413import threading
1514import time
15+ from typing import Any , Dict , List , Optional , Text
16+ from concurrent .futures import ThreadPoolExecutor
1617
1718import ruamel .yaml as yaml
1819import ruamel .yaml .scanner as yamlscanner
1920import schema_salad .ref_resolver
20- from concurrent .futures import ThreadPoolExecutor
2121from six .moves import range
2222from six .moves import zip
23- from typing import Any , Dict , List
2423import pkg_resources # part of setuptools
2524
2625import junit_xml
27- from cwltest .utils import compare , CompareFail , TestResult , REQUIRED , get_test_number_by_key
26+ from cwltest .utils import (compare , CompareFail , TestResult , REQUIRED ,
27+ get_test_number_by_key )
2828
2929_logger = logging .getLogger ("cwltest" )
3030_logger .addHandler (logging .StreamHandler ())
3131_logger .setLevel (logging .INFO )
3232
3333UNSUPPORTED_FEATURE = 33
34- DEFAULT_TIMEOUT = 900 # 15 minutes
34+ DEFAULT_TIMEOUT = 600 # 10 minutes
3535
3636if sys .version_info < (3 , 0 ):
3737 import subprocess32 as subprocess
38+ from pipes import quote
3839else :
3940 import subprocess
41+ from shlex import quote
4042
4143templock = threading .Lock ()
4244
4345
44- def prepare_test_command (args , i , tests ):
45- # type: (argparse.Namespace, int, List[Dict[str, str]]) -> List[str]
46- t = tests [i ]
47- test_command = [args .tool ]
48- test_command .extend (args .args )
46+ def prepare_test_command (tool , # type: str
47+ args , # type: List[str]
48+ testargs , # type: Optional[List[str]]
49+ test # type: Dict[str, str]
50+ ): # type: (...) -> List[str]
51+ """ Turn the test into a command line. """
52+ test_command = [tool ]
53+ test_command .extend (args )
4954
5055 # Add additional arguments given in test case
51- if args . testargs is not None :
52- for testarg in args . testargs :
56+ if testargs is not None :
57+ for testarg in testargs :
5358 (test_case_name , prefix ) = testarg .split ('==' )
54- if test_case_name in t :
55- test_command .extend ([prefix , t [test_case_name ]])
59+ if test_case_name in test :
60+ test_command .extend ([prefix , test [test_case_name ]])
5661
5762 # Add prefixes if running on MacOSX so that boot2docker writes to /Users
5863 with templock :
59- if 'darwin' in sys .platform and args . tool == 'cwltool' :
64+ if 'darwin' in sys .platform and tool == 'cwltool' :
6065 outdir = tempfile .mkdtemp (prefix = os .path .abspath (os .path .curdir ))
61- test_command .extend (["--tmp-outdir-prefix={}" .format (outdir ), "--tmpdir-prefix={}" .format (outdir )])
66+ test_command .extend (["--tmp-outdir-prefix={}" .format (outdir ),
67+ "--tmpdir-prefix={}" .format (outdir )])
6268 else :
6369 outdir = tempfile .mkdtemp ()
6470 test_command .extend (["--outdir={}" .format (outdir ),
6571 "--quiet" ,
66- t ["tool" ]])
67- if t .get ("job" ):
68- test_command .append (t ["job" ])
72+ os . path . normcase ( test ["tool" ]) ])
73+ if test .get ("job" ):
74+ test_command .append (os . path . normcase ( test ["job" ]) )
6975 return test_command
7076
7177
72- def run_test (args , i , tests , timeout ):
73- # type: (argparse.Namespace, int, List[Dict[str, str]], int) -> TestResult
78+ def run_test (args , # type: argparse.Namespace
79+ test , # type: Dict[str, str]
80+ test_number , # type: int
81+ total_tests , # type: int
82+ timeout # type: int
83+ ): # type: (...) -> TestResult
7484
7585 global templock
7686
7787 out = {} # type: Dict[str,Any]
78- outdir = outstr = outerr = test_command = None
88+ outdir = outstr = outerr = None
89+ test_command = [] # type: List[str]
7990 duration = 0.0
80- t = tests [i ]
8191 prefix = ""
8292 suffix = ""
8393 if sys .stderr .isatty ():
@@ -86,12 +96,18 @@ def run_test(args, i, tests, timeout):
8696 suffix = "\n "
8797 try :
8898 process = None # type: subprocess.Popen
89- test_command = prepare_test_command (args , i , tests )
90-
91- if t .get ("short_name" ):
92- sys .stderr .write ("%sTest [%i/%i] %s: %s%s\n " % (prefix , i + 1 , len (tests ), t .get ("short_name" ), t .get ("doc" ), suffix ))
99+ test_command = prepare_test_command (
100+ args .tool , args .args , args .testargs , test )
101+
102+ if test .get ("short_name" ):
103+ sys .stderr .write (
104+ "%sTest [%i/%i] %s: %s%s\n "
105+ % (prefix , test_number , total_tests , test .get ("short_name" ),
106+ test .get ("doc" ), suffix ))
93107 else :
94- sys .stderr .write ("%sTest [%i/%i] %s%s\n " % (prefix , i + 1 , len (tests ), t .get ("doc" ), suffix ))
108+ sys .stderr .write (
109+ "%sTest [%i/%i] %s%s\n "
110+ % (prefix , test_number , total_tests , test .get ("doc" ), suffix ))
95111 sys .stderr .flush ()
96112
97113 start_time = time .time ()
@@ -104,38 +120,40 @@ def run_test(args, i, tests, timeout):
104120 raise subprocess .CalledProcessError (return_code , " " .join (test_command ))
105121
106122 out = json .loads (outstr )
107- except ValueError as v :
108- _logger .error (str (v ))
123+ except ValueError as err :
124+ _logger .error (str (err ))
109125 _logger .error (outstr )
110126 _logger .error (outerr )
111127 except subprocess .CalledProcessError as err :
112128 if err .returncode == UNSUPPORTED_FEATURE :
113129 return TestResult (UNSUPPORTED_FEATURE , outstr , outerr , duration , args .classname )
114- elif t .get ("should_fail" , False ):
130+ if test .get ("should_fail" , False ):
115131 return TestResult (0 , outstr , outerr , duration , args .classname )
116- else :
117- _logger .error (u"""Test failed: %s""" , " " . join ([ pipes . quote ( tc ) for tc in test_command ] ))
118- _logger .error (t . get ( "doc" ) )
119- _logger .error ("Returned non-zero" )
120- _logger . error ( outerr )
121- return TestResult ( 1 , outstr , outerr , duration , args . classname , str ( err ))
122- except ( yamlscanner . ScannerError , TypeError ) as e :
123- _logger . error ( u"""Test failed: %s""" , " " .join ([pipes . quote (tc ) for tc in test_command ]))
132+ _logger . error ( u"""Test failed: %s""" , " " . join ([ quote ( tc ) for tc in test_command ]))
133+ _logger .error (test . get ( "doc" ))
134+ _logger .error (u"Returned non-zero" )
135+ _logger .error (outerr )
136+ return TestResult ( 1 , outstr , outerr , duration , args . classname , str ( err ) )
137+ except ( yamlscanner . ScannerError , TypeError ) as err :
138+ _logger . error ( u"""Test failed: %s""" ,
139+ u " " .join ([quote (tc ) for tc in test_command ]))
124140 _logger .error (outstr )
125- _logger .error (u"Parse error %s" , str (e ))
141+ _logger .error (u"Parse error %s" , str (err ))
126142 _logger .error (outerr )
127143 except KeyboardInterrupt :
128- _logger .error (u"""Test interrupted: %s""" , " " .join ([pipes .quote (tc ) for tc in test_command ]))
144+ _logger .error (u"""Test interrupted: %s""" ,
145+ u" " .join ([quote (tc ) for tc in test_command ]))
129146 raise
130147 except subprocess .TimeoutExpired :
131- _logger .error (u"""Test timed out: %s""" , " " .join ([pipes .quote (tc ) for tc in test_command ]))
132- _logger .error (t .get ("doc" ))
148+ _logger .error (u"""Test timed out: %s""" ,
149+ u" " .join ([quote (tc ) for tc in test_command ]))
150+ _logger .error (test .get ("doc" ))
133151 return TestResult (2 , outstr , outerr , timeout , args .classname , "Test timed out" )
134152 finally :
135153 if process is not None and process .returncode is None :
136154 _logger .error (u"""Terminating lingering process""" )
137155 process .terminate ()
138- for a in range (0 , 3 ):
156+ for _ in range (0 , 3 ):
139157 time .sleep (1 )
140158 if process .poll () is not None :
141159 break
@@ -144,24 +162,25 @@ def run_test(args, i, tests, timeout):
144162
145163 fail_message = ''
146164
147- if t .get ("should_fail" , False ):
148- _logger .warning (u"""Test failed: %s""" , " " .join ([pipes . quote (tc ) for tc in test_command ]))
149- _logger .warning (t .get ("doc" ))
165+ if test .get ("should_fail" , False ):
166+ _logger .warning (u"""Test failed: %s""" , u " " .join ([quote (tc ) for tc in test_command ]))
167+ _logger .warning (test .get ("doc" ))
150168 _logger .warning (u"Returned zero but it should be non-zero" )
151169 return TestResult (1 , outstr , outerr , duration , args .classname )
152170
153171 try :
154- compare (t .get ("output" ), out )
172+ compare (test .get ("output" ), out )
155173 except CompareFail as ex :
156- _logger .warning (u"""Test failed: %s""" , " " .join ([pipes . quote (tc ) for tc in test_command ]))
157- _logger .warning (t .get ("doc" ))
174+ _logger .warning (u"""Test failed: %s""" , u " " .join ([quote (tc ) for tc in test_command ]))
175+ _logger .warning (test .get ("doc" ))
158176 _logger .warning (u"Compare failure %s" , ex )
159177 fail_message = str (ex )
160178
161179 if outdir :
162180 shutil .rmtree (outdir , True )
163181
164- return TestResult ((1 if fail_message else 0 ), outstr , outerr , duration , args .classname , fail_message )
182+ return TestResult ((1 if fail_message else 0 ), outstr , outerr , duration ,
183+ args .classname , fail_message )
165184
166185
167186def arg_parser (): # type: () -> argparse.ArgumentParser
@@ -175,17 +194,18 @@ def arg_parser(): # type: () -> argparse.ArgumentParser
175194 help = "CWL runner executable to use (default 'cwl-runner'" )
176195 parser .add_argument ("--only-tools" , action = "store_true" , help = "Only test CommandLineTools" )
177196 parser .add_argument ("--junit-xml" , type = str , default = None , help = "Path to JUnit xml file" )
178- parser .add_argument ("--test-arg" , type = str , help = "Additional argument given in test cases and "
179- " required prefix for tool runner." ,
180- metavar = "cache==--cache-dir" , action = "append" , dest = "testargs" )
197+ parser .add_argument ("--test-arg" , type = str , help = "Additional argument "
198+ "given in test cases and required prefix for tool runner." ,
199+ default = None , metavar = "cache==--cache-dir" , action = "append" , dest = "testargs" )
181200 parser .add_argument ("args" , help = "arguments to pass first to tool runner" , nargs = argparse .REMAINDER )
182201 parser .add_argument ("-j" , type = int , default = 1 , help = "Specifies the number of tests to run simultaneously "
183202 "(defaults to one)." )
184203 parser .add_argument ("--verbose" , action = "store_true" , help = "More verbose output during test run." )
185204 parser .add_argument ("--classname" , type = str , default = "" , help = "Specify classname for the Test Suite." )
186- parser .add_argument ("--timeout" , type = int , default = DEFAULT_TIMEOUT , help = "Time of execution in seconds after "
187- "which the test will be skipped. "
188- "Defaults to 900 sec (15 minutes)" )
205+ parser .add_argument ("--timeout" , type = int , default = DEFAULT_TIMEOUT ,
206+ help = "Time of execution in seconds after which the test will be "
207+ "skipped. Defaults to {} seconds ({} minutes)." .format (
208+ DEFAULT_TIMEOUT , DEFAULT_TIMEOUT / 60 ))
189209
190210 pkg = pkg_resources .require ("cwltest" )
191211 if pkg :
@@ -256,14 +276,14 @@ def main(): # type: () -> int
256276 if test_number :
257277 ntest .append (test_number )
258278 else :
259- _logger .error ('Test with short name "%s" not found ' % s )
279+ _logger .error ('Test with short name "%s" not found ' , s )
260280 return 1
261281 else :
262282 ntest = list (range (0 , len (tests )))
263283
264284 total = 0
265285 with ThreadPoolExecutor (max_workers = args .j ) as executor :
266- jobs = [executor .submit (run_test , args , i , tests , args .timeout )
286+ jobs = [executor .submit (run_test , args , tests [ i ], i + 1 , len ( tests ) , args .timeout )
267287 for i in ntest ]
268288 try :
269289 for i , job in zip (ntest , jobs ):
@@ -294,18 +314,19 @@ def main(): # type: () -> int
294314 _logger .error ("Tests interrupted" )
295315
296316 if args .junit_xml :
297- with open (args .junit_xml , 'w' ) as fp :
298- junit_xml .TestSuite .to_file (fp , [report ])
317+ with open (args .junit_xml , 'w' ) as xml :
318+ junit_xml .TestSuite .to_file (xml , [report ])
299319
300320 if failures == 0 and unsupported == 0 :
301321 _logger .info ("All tests passed" )
302322 return 0
303- elif failures == 0 and unsupported > 0 :
304- _logger .warning ("%i tests passed, %i unsupported features" , total - unsupported , unsupported )
323+ if failures == 0 and unsupported > 0 :
324+ _logger .warning ("%i tests passed, %i unsupported features" ,
325+ total - unsupported , unsupported )
305326 return 0
306- else :
307- _logger . warning ( "%i tests passed, %i failures, %i unsupported features" , total - (failures + unsupported ), failures , unsupported )
308- return 1
327+ _logger . warning ( "%i tests passed, %i failures, %i unsupported features" ,
328+ total - (failures + unsupported ), failures , unsupported )
329+ return 1
309330
310331
311332if __name__ == "__main__" :
0 commit comments