def test_started_context_manager(request, tempfiles, start_timeout): script = tempfiles.makepyfile( r""" # coding=utf-8 import sys import time import multiprocessing def main(): time.sleep(3) sys.stdout.write("Done!\n") sys.stdout.flush() sys.exit(0) # Support for windows test runs if __name__ == '__main__': multiprocessing.freeze_support() main() """, executable=True, ) daemon = DaemonFactory( cli_script_name=sys.executable, base_script_args=[script], start_timeout=2, max_start_attempts=1, check_ports=[12345], ) # Make sure the daemon is terminated no matter what request.addfinalizer(daemon.terminate) with pytest.raises(FactoryNotStarted) as exc: daemon.start(start_timeout=start_timeout) match = re.search(r"which took (?P<seconds>.*) seconds", str(exc.value)) assert match # XXX: Revisit logic # seconds = float(match.group("seconds")) ## Must take at least start_timeout to start # assert seconds > start_timeout ## Should not take more than start_timeout + 0.3 to start and fail # assert seconds < start_timeout + 0.3 # And using a context manager? with pytest.raises(FactoryNotStarted) as exc: started = None with daemon.started(start_timeout=start_timeout): # We should not even be able to set the following variable started = False # pragma: no cover assert started is None match = re.search(r"which took (?P<seconds>.*) seconds", str(exc.value)) assert match
def test_stopped_context_manager_raises_FactoryNotRunning(request, factory_stopped_script): daemon = DaemonFactory( cli_script_name=sys.executable, base_script_args=[factory_stopped_script], start_timeout=3, max_start_attempts=1, check_ports=[12345], ) # Make sure the daemon is terminated no matter what request.addfinalizer(daemon.terminate) with pytest.raises(FactoryNotRunning): with daemon.stopped(): pass
def test_exact_max_start_attempts(tempfiles, caplog, max_start_attempts): """ This test asserts that we properly report max_start_attempts """ script = tempfiles.makepyfile( r""" # coding=utf-8 import sys import time import multiprocessing def main(): time.sleep(0.125) sys.exit(1) # Support for windows test runs if __name__ == '__main__': multiprocessing.freeze_support() main() """, executable=True, ) daemon = DaemonFactory( cli_script_name=sys.executable, base_script_args=[script], start_timeout=0.1, max_start_attempts=max_start_attempts, check_ports=[12345], ) with caplog.at_level(logging.INFO): with pytest.raises(FactoryNotStarted) as exc: daemon.start() assert "confirm running status after {} attempts".format(max_start_attempts) in str( exc.value ) start_attempts = [ "Attempt: {} of {}".format(n, max_start_attempts) for n in range(1, max_start_attempts + 1) ] for record in caplog.records: if not record.message.startswith("Starting DaemonFactory"): continue for idx, start_attempt in enumerate(list(start_attempts)): if start_attempt in record.message: start_attempts.pop(idx) assert not start_attempts
def test_stopped_context_manager(request, factory_stopped_script): daemon = DaemonFactory( cli_script_name=sys.executable, base_script_args=[factory_stopped_script], start_timeout=3, max_start_attempts=1, check_ports=[12345], ) # Make sure the daemon is terminated no matter what request.addfinalizer(daemon.terminate) with daemon.started(): assert daemon.is_running() with daemon.stopped(): assert daemon.is_running() is False assert daemon.is_running()
def test_daemon_process_termination(request, tempfiles): primary_childrend_count = 5 secondary_children_count = 3 script = tempfiles.makepyfile( """ #!{shebang} # coding=utf-8 import time import multiprocessing def spin(): while True: try: time.sleep(0.25) except KeyboardInterrupt: break def spin_children(): procs = [] for idx in range({secondary_children_count}): proc = multiprocessing.Process(target=spin) proc.daemon = True proc.start() procs.append(proc) while True: try: time.sleep(0.25) except KeyboardInterrupt: break def main(): procs = [] for idx in range({primary_childrend_count}): proc = multiprocessing.Process(target=spin_children) procs.append(proc) proc.start() while True: try: time.sleep(0.25) except KeyboardInterrupt: break # We're not terminating child processes on purpose. Our code should handle it. # Support for windows test runs if __name__ == '__main__': multiprocessing.freeze_support() main() """.format( shebang=sys.executable, primary_childrend_count=primary_childrend_count, secondary_children_count=secondary_children_count, ), executable=True, ) if not platform.is_windows(): factory_kwargs = dict(cli_script_name=script) else: # Windows don't know how to handle python scripts directly factory_kwargs = dict(cli_script_name=sys.executable, base_script_args=[script]) daemon = DaemonFactory(**factory_kwargs) daemon.start() daemon_pid = daemon.pid # Make sure the daemon is terminated no matter what request.addfinalizer(daemon.terminate) # Allow the script to start time.sleep(PROCESS_START_TIMEOUT) assert psutil.pid_exists(daemon_pid) proc = psutil.Process(daemon_pid) children = proc.children(recursive=True) request.addfinalizer(functools.partial(kill_children, children)) assert len(children) == primary_childrend_count + ( primary_childrend_count * secondary_children_count ) daemon.terminate() assert psutil.pid_exists(daemon_pid) is False for child in list(children): if psutil.pid_exists(child.pid): continue children.remove(child) assert not children, "len(children)=={} != 0\n{}".format( len(children), pprint.pformat(children) )
def test_context_manager_returns_class_instance(tempfiles): script = tempfiles.makepyfile( r""" # coding=utf-8 import sys import time import multiprocessing def main(): while True: try: time.sleep(0.1) except KeyboardInterrupt: break sys.stdout.write("Done!\n") sys.stdout.flush() sys.exit(0) # Support for windows test runs if __name__ == '__main__': multiprocessing.freeze_support() main() """, executable=True, ) daemon = DaemonFactory( cli_script_name=sys.executable, base_script_args=[script], start_timeout=1, max_start_attempts=1, ) # Without starting the factory started = d = None with pytest.raises(RuntimeError): with daemon as d: # We should not even be able to set the following variable started = d.is_running() # pragma: no cover assert d is None assert started is None # After starting the factory started = False daemon.start() with daemon as d: # We should not even be able to set the following variable started = d.is_running() assert d.is_running() is False assert started is True # By starting the factory and passing timeout directly started = False with daemon.started(start_timeout=1) as d: # We should not even be able to set the following variable started = d.is_running() assert d.is_running() is False assert started is True # By starting the factory without any keyword arguments started = False with daemon.started() as d: # We should not even be able to set the following variable started = d.is_running() assert d.is_running() is False assert started is True