|
3 | 3 | from __future__ import absolute_import
|
4 | 4 | from __future__ import print_function
|
5 | 5 |
|
6 |
| -from six.moves import range |
7 |
| -from six.moves import zip |
8 |
| - |
9 | 6 | import argparse
|
10 | 7 | import json
|
| 8 | +import logging |
11 | 9 | import os
|
12 |
| -import subprocess |
13 |
| -import sys |
| 10 | +import pipes |
14 | 11 | import shutil
|
| 12 | +import sys |
15 | 13 | import tempfile
|
| 14 | +import threading |
| 15 | +import time |
| 16 | + |
16 | 17 | import junit_xml
|
17 | 18 | import ruamel.yaml as yaml
|
18 | 19 | import ruamel.yaml.scanner as yamlscanner
|
19 |
| -import pipes |
20 |
| -import logging |
21 | 20 | import schema_salad.ref_resolver
|
22 |
| -import time |
23 |
| -import threading |
24 | 21 | 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 |
26 | 25 |
|
27 |
| -from cwltest.utils import compare, CompareFail |
| 26 | +from cwltest.utils import compare, CompareFail, TestResult |
28 | 27 |
|
29 | 28 | _logger = logging.getLogger("cwltest")
|
30 | 29 | _logger.addHandler(logging.StreamHandler())
|
31 | 30 | _logger.setLevel(logging.INFO)
|
32 | 31 |
|
33 | 32 | 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 |
60 | 34 |
|
| 35 | +if sys.version_info < (3, 0): |
| 36 | + import subprocess32 as subprocess |
| 37 | +else: |
| 38 | + import subprocess |
61 | 39 |
|
62 | 40 | templock = threading.Lock()
|
63 | 41 |
|
64 | 42 |
|
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 | + |
66 | 46 | global templock
|
67 | 47 |
|
68 | 48 | out = {} # type: Dict[str,Any]
|
@@ -105,7 +85,7 @@ def run_test(args, i, tests): # type: (argparse.Namespace, int, List[Dict[str,
|
105 | 85 | start_time = time.time()
|
106 | 86 | stderr = subprocess.PIPE if not args.verbose else None
|
107 | 87 | 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)] |
109 | 89 | return_code = process.poll()
|
110 | 90 | duration = time.time() - start_time
|
111 | 91 | if return_code:
|
@@ -135,6 +115,10 @@ def run_test(args, i, tests): # type: (argparse.Namespace, int, List[Dict[str,
|
135 | 115 | except KeyboardInterrupt:
|
136 | 116 | _logger.error(u"""Test interrupted: %s""", " ".join([pipes.quote(tc) for tc in test_command]))
|
137 | 117 | 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") |
138 | 122 |
|
139 | 123 | fail_message = ''
|
140 | 124 |
|
@@ -177,6 +161,9 @@ def main(): # type: () -> int
|
177 | 161 | "(defaults to one).")
|
178 | 162 | parser.add_argument("--verbose", action="store_true", help="More verbose output during test run.")
|
179 | 163 | 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)") |
180 | 167 |
|
181 | 168 | args = parser.parse_args()
|
182 | 169 | if '--' in args.args:
|
@@ -229,7 +216,7 @@ def main(): # type: () -> int
|
229 | 216 |
|
230 | 217 | total = 0
|
231 | 218 | 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) |
233 | 220 | for i in ntest]
|
234 | 221 | try:
|
235 | 222 | for i, job in zip(ntest, jobs):
|
|
0 commit comments