From d514eaec15424f6d290f9243c398fd51ea25fa1d Mon Sep 17 00:00:00 2001 From: Noah Levitt Date: Tue, 16 May 2017 14:00:10 -0700 Subject: [PATCH] even more, better failing tests for thread_raise --- setup.py | 2 +- tests/test_units.py | 117 +++++++++++++++++++++++++++----------------- 2 files changed, 73 insertions(+), 46 deletions(-) diff --git a/setup.py b/setup.py index f2f42fc..34e5087 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ def find_package_data(package): setuptools.setup( name='brozzler', - version='1.1b11.dev246', + version='1.1b11.dev247', description='Distributed web crawling with browsers', url='https://github.com/internetarchive/brozzler', author='Noah Levitt', diff --git a/tests/test_units.py b/tests/test_units.py index 9046d91..3f40863 100644 --- a/tests/test_units.py +++ b/tests/test_units.py @@ -192,31 +192,12 @@ def test_start_stop_backwards_compat(): assert not 'started' in job assert not 'finished' in job -def test_thread_raise(): - thread_caught_exception = None - - class Exception1(Exception): - pass - class Exception2(Exception): - pass - - def accept_immediately(): - try: - with brozzler.thread_accept_exceptions(): - brozzler.sleep(2) - except Exception as e: - nonlocal thread_caught_exception - thread_caught_exception = e - - def accept_eventually(): - try: - brozzler.sleep(2) - with brozzler.thread_accept_exceptions(): - pass - except Exception as e: - nonlocal thread_caught_exception - thread_caught_exception = e +class Exception1(Exception): + pass +class Exception2(Exception): + pass +def test_thread_raise_not_accept(): def never_accept(): try: brozzler.sleep(2) @@ -224,42 +205,50 @@ def test_thread_raise(): nonlocal thread_caught_exception thread_caught_exception = e - def delay_context_exit(): - try: - with brozzler.thread_accept_exceptions() as gate: - logging.info('gate=%s', gate) - orig_exit = gate.__exit__ - import traceback - gate.__exit__ = lambda et, ev, t: ( - logging.info('fake exit'), traceback.print_stack(), - brozzler.sleep(2), orig_exit(et, ev, t)) - try: - brozzler.sleep(2) - except Exception as e: - raise - except Exception as e: - nonlocal thread_caught_exception - thread_caught_exception = e - # test that thread_raise does not raise exception in a thread that has no # `with thread_exception_gate()` block thread_caught_exception = None th = threading.Thread(target=never_accept) th.start() - brozzler.thread_raise(th, Exception) + brozzler.thread_raise(th, Exception1) th.join() assert thread_caught_exception is None +def test_thread_raise_immediate(): + def accept_immediately(): + try: + with brozzler.thread_accept_exceptions(): + brozzler.sleep(2) + except Exception as e: + nonlocal thread_caught_exception + thread_caught_exception = e + # test immediate exception raise thread_caught_exception = None th = threading.Thread(target=accept_immediately) th.start() - brozzler.thread_raise(th, Exception) + brozzler.thread_raise(th, Exception1) start = time.time() th.join() assert thread_caught_exception + assert isinstance(thread_caught_exception, Exception1) assert time.time() - start < 1.0 +def test_thread_raise_safe_exit(): + def delay_context_exit(): + gate = brozzler.thread_accept_exceptions() + orig_exit = type(gate).__exit__ + try: + type(gate).__exit__ = lambda self, et, ev, t: ( + brozzler.sleep(2), orig_exit(self, et, ev, t), False)[-1] + with brozzler.thread_accept_exceptions() as gate: + brozzler.sleep(2) + except Exception as e: + nonlocal thread_caught_exception + thread_caught_exception = e + finally: + type(gate).__exit__ = orig_exit + # test that a second thread_raise() doesn't result in an exception in # ThreadExceptionGate.__exit__ thread_caught_exception = None @@ -273,13 +262,51 @@ def test_thread_raise(): assert thread_caught_exception assert isinstance(thread_caught_exception, Exception1) +def test_thread_raise_pending_exception(): + def accept_eventually(): + try: + brozzler.sleep(2) + with brozzler.thread_accept_exceptions(): + pass + except Exception as e: + nonlocal thread_caught_exception + thread_caught_exception = e + # test exception that has to wait for `with thread_exception_gate()` block thread_caught_exception = None th = threading.Thread(target=accept_eventually) th.start() - brozzler.thread_raise(th, Exception) + brozzler.thread_raise(th, Exception1) start = time.time() th.join() - assert thread_caught_exception + assert isinstance(thread_caught_exception, Exception1) assert time.time() - start > 1.0 +def test_thread_raise_second_with_block(): + def two_with_blocks(): + try: + with brozzler.thread_accept_exceptions(): + time.sleep(2) + return # test fails + except Exception1 as e: + pass + except: + return # fail test + + try: + with brozzler.thread_accept_exceptions(): + brozzler.sleep(2) + except Exception as e: + nonlocal thread_caught_exception + thread_caught_exception = e + + # test that second `with` block gets second exception raised during first + # `with` block + thread_caught_exception = None + th = threading.Thread(target=two_with_blocks) + th.start() + brozzler.thread_raise(th, Exception1) + brozzler.thread_raise(th, Exception2) + th.join() + assert isinstance(thread_caught_exception, Exception2) +