Initial pass at timeout for subprocessing pytest
pytest-dev/pytest#4073
This commit is contained in:
		
							parent
							
								
									29d3faed66
								
							
						
					
					
						commit
						96b2ae6654
					
				
							
								
								
									
										1
									
								
								setup.py
								
								
								
								
							
							
						
						
									
										1
									
								
								setup.py
								
								
								
								
							| 
						 | 
				
			
			@ -65,6 +65,7 @@ def main():
 | 
			
		|||
        "attrs>=17.4.0",
 | 
			
		||||
        "more-itertools>=4.0.0",
 | 
			
		||||
        "atomicwrites>=1.0",
 | 
			
		||||
        "monotonic",
 | 
			
		||||
    ]
 | 
			
		||||
    # if _PYTEST_SETUP_SKIP_PLUGGY_DEP is set, skip installing pluggy;
 | 
			
		||||
    # used by tox.ini to test with pluggy master
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@ from __future__ import absolute_import, division, print_function
 | 
			
		|||
 | 
			
		||||
import codecs
 | 
			
		||||
import gc
 | 
			
		||||
import monotonic
 | 
			
		||||
import os
 | 
			
		||||
import platform
 | 
			
		||||
import re
 | 
			
		||||
| 
						 | 
				
			
			@ -482,6 +483,9 @@ class Testdir(object):
 | 
			
		|||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    class TimeoutExpired(Exception):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def __init__(self, request, tmpdir_factory):
 | 
			
		||||
        self.request = request
 | 
			
		||||
        self._mod_collections = WeakKeyDictionary()
 | 
			
		||||
| 
						 | 
				
			
			@ -1039,7 +1043,7 @@ class Testdir(object):
 | 
			
		|||
 | 
			
		||||
        return popen
 | 
			
		||||
 | 
			
		||||
    def run(self, *cmdargs):
 | 
			
		||||
    def run(self, *cmdargs, **kwargs):
 | 
			
		||||
        """Run a command with arguments.
 | 
			
		||||
 | 
			
		||||
        Run a process using subprocess.Popen saving the stdout and stderr.
 | 
			
		||||
| 
						 | 
				
			
			@ -1061,7 +1065,27 @@ class Testdir(object):
 | 
			
		|||
            popen = self.popen(
 | 
			
		||||
                cmdargs, stdout=f1, stderr=f2, close_fds=(sys.platform != "win32")
 | 
			
		||||
            )
 | 
			
		||||
            ret = popen.wait()
 | 
			
		||||
            timeout = kwargs.get('timeout')
 | 
			
		||||
            if timeout is None:
 | 
			
		||||
                ret = popen.wait()
 | 
			
		||||
            elif six.PY3:
 | 
			
		||||
                try:
 | 
			
		||||
                    ret = popen.wait(timeout)
 | 
			
		||||
                except subprocess.TimeoutExpired:
 | 
			
		||||
                    raise self.TimeoutExpired
 | 
			
		||||
            else:
 | 
			
		||||
                end = monotonic.monotonic() + timeout
 | 
			
		||||
 | 
			
		||||
                while True:
 | 
			
		||||
                    ret = popen.poll()
 | 
			
		||||
                    if ret is not None:
 | 
			
		||||
                        break
 | 
			
		||||
 | 
			
		||||
                    remaining = end - monotonic.monotonic()
 | 
			
		||||
                    if remaining <= 0:
 | 
			
		||||
                        raise self.TimeoutExpired()
 | 
			
		||||
 | 
			
		||||
                    time.sleep(remaining * 0.9)
 | 
			
		||||
        finally:
 | 
			
		||||
            f1.close()
 | 
			
		||||
            f2.close()
 | 
			
		||||
| 
						 | 
				
			
			@ -1119,7 +1143,13 @@ class Testdir(object):
 | 
			
		|||
        if plugins:
 | 
			
		||||
            args = ("-p", plugins[0]) + args
 | 
			
		||||
        args = self._getpytestargs() + args
 | 
			
		||||
        return self.run(*args)
 | 
			
		||||
 | 
			
		||||
        if "timeout" in kwargs:
 | 
			
		||||
            timeout = {"timeout": kwargs["timeout"]}
 | 
			
		||||
        else:
 | 
			
		||||
            timeout = {}
 | 
			
		||||
 | 
			
		||||
        return self.run(*args, **timeout)
 | 
			
		||||
 | 
			
		||||
    def spawn_pytest(self, string, expect_timeout=10.0):
 | 
			
		||||
        """Run pytest using pexpect.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@ from __future__ import absolute_import, division, print_function
 | 
			
		|||
import os
 | 
			
		||||
import py.path
 | 
			
		||||
import pytest
 | 
			
		||||
import subprocess
 | 
			
		||||
import sys
 | 
			
		||||
import _pytest.pytester as pytester
 | 
			
		||||
from _pytest.pytester import HookRecorder
 | 
			
		||||
| 
						 | 
				
			
			@ -401,3 +402,20 @@ def test_testdir_subprocess(testdir):
 | 
			
		|||
def test_unicode_args(testdir):
 | 
			
		||||
    result = testdir.runpytest("-k", u"💩")
 | 
			
		||||
    assert result.ret == EXIT_NOTESTSCOLLECTED
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_testdir_run_no_timeout(testdir):
 | 
			
		||||
    testfile = testdir.makepyfile("def test_no_timeout(): pass")
 | 
			
		||||
    assert testdir.runpytest_subprocess(testfile).ret == EXIT_OK
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_testdir_run_timeout_expires(testdir):
 | 
			
		||||
    testfile = testdir.makepyfile(
 | 
			
		||||
        """
 | 
			
		||||
        import time
 | 
			
		||||
        
 | 
			
		||||
        def test_timeout():
 | 
			
		||||
            time.sleep(10)"""
 | 
			
		||||
    )
 | 
			
		||||
    with pytest.raises(testdir.TimeoutExpired):
 | 
			
		||||
        testdir.runpytest_subprocess(testfile, timeout=0.5)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue