Skip to content

Commit d3f7cce

Browse files
authored
Merge pull request #47 from common-workflow-language/default-timeout
Add default timeout
2 parents f962c94 + a196df3 commit d3f7cce

File tree

5 files changed

+111
-43
lines changed

5 files changed

+111
-43
lines changed

cwltest/__init__.py

Lines changed: 27 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,66 +3,46 @@
33
from __future__ import absolute_import
44
from __future__ import print_function
55

6-
from six.moves import range
7-
from six.moves import zip
8-
96
import argparse
107
import json
8+
import logging
119
import os
12-
import subprocess
13-
import sys
10+
import pipes
1411
import shutil
12+
import sys
1513
import tempfile
14+
import threading
15+
import time
16+
1617
import junit_xml
1718
import ruamel.yaml as yaml
1819
import ruamel.yaml.scanner as yamlscanner
19-
import pipes
20-
import logging
2120
import schema_salad.ref_resolver
22-
import time
23-
import threading
2421
from concurrent.futures import ThreadPoolExecutor
25-
from typing import Any, Dict, List, Text
22+
from six.moves import range
23+
from six.moves import zip
24+
from typing import Any, Dict, List
2625

27-
from cwltest.utils import compare, CompareFail
26+
from cwltest.utils import compare, CompareFail, TestResult
2827

2928
_logger = logging.getLogger("cwltest")
3029
_logger.addHandler(logging.StreamHandler())
3130
_logger.setLevel(logging.INFO)
3231

3332
UNSUPPORTED_FEATURE = 33
34-
RUNTIME = sys.version_info.major
35-
36-
37-
class TestResult(object):
38-
39-
"""Encapsulate relevant test result data."""
40-
41-
def __init__(self, return_code, standard_output, error_output, duration, classname, message=''):
42-
# type: (int, Text, Text, float, Text, str) -> None
43-
self.return_code = return_code
44-
self.standard_output = standard_output
45-
self.error_output = error_output
46-
self.duration = duration
47-
self.message = message
48-
self.classname = classname
49-
50-
def create_test_case(self, test):
51-
# type: (Dict[Text, Any]) -> junit_xml.TestCase
52-
doc = test.get(u'doc', 'N/A').strip()
53-
case = junit_xml.TestCase(
54-
doc, elapsed_sec=self.duration, classname=self.classname,
55-
stdout=self.standard_output, stderr=self.error_output,
56-
)
57-
if self.return_code > 0:
58-
case.failure_message = self.message
59-
return case
33+
DEFAULT_TIMEOUT = 900 # 15 minutes
6034

35+
if sys.version_info < (3, 0):
36+
import subprocess32 as subprocess
37+
else:
38+
import subprocess
6139

6240
templock = threading.Lock()
6341

6442

65-
def run_test(args, i, tests): # type: (argparse.Namespace, int, List[Dict[str, str]]) -> TestResult
43+
def run_test(args, i, tests, timeout):
44+
# type: (argparse.Namespace, int, List[Dict[str, str]], int) -> TestResult
45+
6646
global templock
6747

6848
out = {} # type: Dict[str,Any]
@@ -105,7 +85,7 @@ def run_test(args, i, tests): # type: (argparse.Namespace, int, List[Dict[str,
10585
start_time = time.time()
10686
stderr = subprocess.PIPE if not args.verbose else None
10787
process = subprocess.Popen(test_command, stdout=subprocess.PIPE, stderr=stderr)
108-
outstr, outerr = [var.decode('utf-8') for var in process.communicate()]
88+
outstr, outerr = [var.decode('utf-8') for var in process.communicate(timeout=timeout)]
10989
return_code = process.poll()
11090
duration = time.time() - start_time
11191
if return_code:
@@ -135,6 +115,10 @@ def run_test(args, i, tests): # type: (argparse.Namespace, int, List[Dict[str,
135115
except KeyboardInterrupt:
136116
_logger.error(u"""Test interrupted: %s""", " ".join([pipes.quote(tc) for tc in test_command]))
137117
raise
118+
except subprocess.TimeoutExpired:
119+
_logger.error(u"""Test timed out: %s""", " ".join([pipes.quote(tc) for tc in test_command]))
120+
_logger.error(t.get("doc"))
121+
return TestResult(2, outstr, outerr, timeout, args.classname, "Test timed out")
138122

139123
fail_message = ''
140124

@@ -177,6 +161,9 @@ def main(): # type: () -> int
177161
"(defaults to one).")
178162
parser.add_argument("--verbose", action="store_true", help="More verbose output during test run.")
179163
parser.add_argument("--classname", type=str, default="", help="Specify classname for the Test Suite.")
164+
parser.add_argument("--timeout", type=int, default=DEFAULT_TIMEOUT, help="Time of execution in seconds after "
165+
"which the test will be skipped."
166+
"Defaults to 900 sec (15 minutes)")
180167

181168
args = parser.parse_args()
182169
if '--' in args.args:
@@ -229,7 +216,7 @@ def main(): # type: () -> int
229216

230217
total = 0
231218
with ThreadPoolExecutor(max_workers=args.j) as executor:
232-
jobs = [executor.submit(run_test, args, i, tests)
219+
jobs = [executor.submit(run_test, args, i, tests, args.timeout)
233220
for i in ntest]
234221
try:
235222
for i, job in zip(ntest, jobs):

cwltest/utils.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,36 @@
11
import json
2-
from typing import Any, Dict, Set
2+
3+
import junit_xml
4+
from typing import Any, Dict, Set, Text
35

46
from six.moves import range
57

68

9+
class TestResult(object):
10+
11+
"""Encapsulate relevant test result data."""
12+
13+
def __init__(self, return_code, standard_output, error_output, duration, classname, message=''):
14+
# type: (int, Text, Text, float, Text, str) -> None
15+
self.return_code = return_code
16+
self.standard_output = standard_output
17+
self.error_output = error_output
18+
self.duration = duration
19+
self.message = message
20+
self.classname = classname
21+
22+
def create_test_case(self, test):
23+
# type: (Dict[Text, Any]) -> junit_xml.TestCase
24+
doc = test.get(u'doc', 'N/A').strip()
25+
case = junit_xml.TestCase(
26+
doc, elapsed_sec=self.duration, classname=self.classname,
27+
stdout=self.standard_output, stderr=self.error_output,
28+
)
29+
if self.return_code > 0:
30+
case.failure_message = self.message
31+
return case
32+
33+
734
class CompareFail(Exception):
835

936
@classmethod

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
schema-salad >= 1.14
22
typing>=3.6,<3.7; python_version < '3.5'
33
futures >= 3.0.5; python_version == '2.7'
4+
subprocess32; python_version < '3'
45
junit-xml >= 1.7
6+

setup.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,11 @@
2323
]
2424

2525
if sys.version_info.major == 2:
26-
install_requires.append('futures >= 3.0.5')
26+
install_requires.extend(['futures >= 3.0.5', 'subprocess32'])
2727

2828
if sys.version_info[:2] < (3, 5):
2929
install_requires.append('typing >= 3.5.2')
3030

31-
3231
setup(name='cwltest',
3332
version='1.0',
3433
description='Common workflow language testing framework',

typeshed/2.7/subprocess32.pyi

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Stubs for subprocess32 (Python 2)
2+
#
3+
# NOTE: This dynamically typed stub was automatically generated by stubgen.
4+
5+
from typing import Any, Optional
6+
7+
class CalledProcessError(Exception):
8+
returncode: Any = ...
9+
cmd: Any = ...
10+
output: Any = ...
11+
def __init__(self, returncode, cmd, output: Optional[Any] = ...) -> None: ...
12+
13+
class TimeoutExpired(Exception):
14+
cmd: Any = ...
15+
timeout: Any = ...
16+
output: Any = ...
17+
def __init__(self, cmd, timeout, output: Optional[Any] = ...) -> None: ...
18+
19+
class STARTUPINFO:
20+
dwFlags: int = ...
21+
hStdInput: Any = ...
22+
hStdOutput: Any = ...
23+
hStdError: Any = ...
24+
wShowWindow: int = ...
25+
26+
class pywintypes:
27+
error: Any = ...
28+
29+
PIPE: int
30+
STDOUT: int
31+
32+
def call(*popenargs, **kwargs): ...
33+
def check_call(*popenargs, **kwargs): ...
34+
def check_output(*popenargs, **kwargs): ...
35+
36+
class Popen:
37+
args: Any = ...
38+
stdin: Any = ...
39+
stdout: Any = ...
40+
stderr: Any = ...
41+
pid: Any = ...
42+
returncode: Any = ...
43+
universal_newlines: Any = ...
44+
def __init__(self, args, bufsize: int = ..., executable: Optional[Any] = ..., stdin: Optional[Any] = ..., stdout: Optional[Any] = ..., stderr: Optional[Any] = ..., preexec_fn: Optional[Any] = ..., close_fds: Any = ..., shell: bool = ..., cwd: Optional[Any] = ..., env: Optional[Any] = ..., universal_newlines: bool = ..., startupinfo: Optional[Any] = ..., creationflags: int = ..., restore_signals: bool = ..., start_new_session: bool = ..., pass_fds: Any = ...) -> None: ...
45+
def __enter__(self): ...
46+
def __exit__(self, type, value, traceback): ...
47+
def __del__(self, _maxint: Any = ..., _active: Any = ...): ...
48+
def communicate(self, input: Optional[Any] = ..., timeout: Optional[Any] = ...): ...
49+
def poll(self) -> int: ...
50+
def wait(self, timeout: Optional[Any] = ..., endtime: Optional[Any] = ...): ...
51+
def send_signal(self, sig): ...
52+
def terminate(self): ...
53+
def kill(self): ...

0 commit comments

Comments
 (0)