From 88e0afc7a036dc514a53b32451d8a9947742ca90 Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Mon, 20 Aug 2012 16:55:37 -0400 Subject: [PATCH 1/6] Require zipline tests to end with a DONE packet --- zipline/utils/test_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/zipline/utils/test_utils.py b/zipline/utils/test_utils.py index 99175c63..ae804426 100644 --- a/zipline/utils/test_utils.py +++ b/zipline/utils/test_utils.py @@ -119,6 +119,7 @@ def drain_receiver(receiver, count=None): def assert_single_position(test, zipline, blocking=False): output, transaction_count = drain_zipline(test, zipline, p_blocking=blocking) + test.assertEqual(output[-1]['prefix'], 'DONE') test.assertEqual( test.zipline_test_config['order_count'], From a2376cb87adebc719a045888da2063395d956bc0 Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Mon, 20 Aug 2012 20:56:40 -0400 Subject: [PATCH 2/6] test_finance.py no longer needs drain_zipline --- tests/test_finance.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_finance.py b/tests/test_finance.py index 94a98da9..5562b671 100644 --- a/tests/test_finance.py +++ b/tests/test_finance.py @@ -20,7 +20,6 @@ from zipline.finance.performance import PerformanceTracker from zipline.utils.protocol_utils import ndict from zipline.finance.trading import TransactionSimulator from zipline.utils.test_utils import \ - drain_zipline, \ setup_logger, \ teardown_logger,\ assert_single_position From a7bb4b53d85ee79a6609418fa5a1a65880f8a3ad Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Tue, 21 Aug 2012 13:53:03 -0400 Subject: [PATCH 3/6] Close the results socket to ensure messages are sent --- zipline/lines.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zipline/lines.py b/zipline/lines.py index 1c3a558f..d31c47d6 100644 --- a/zipline/lines.py +++ b/zipline/lines.py @@ -172,6 +172,8 @@ class SimulatedTrading(object): def close(self): log.info("Closing Simulation: {id}".format(id=self.sim_id)) + if self.results_socket: + self.results_socket.close() if self.proc and self.send_sighup: ppid = os.getppid() if self.success: From e43ded840b8d620f4e67efe0440b11f19c1a0ed8 Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Tue, 21 Aug 2012 13:53:59 -0400 Subject: [PATCH 4/6] Add delayed_signals class. --- tests/test_delayed_signals.py | 53 ++++++++++++++++++++++++++++++++ zipline/utils/delayed_signals.py | 42 +++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 tests/test_delayed_signals.py create mode 100644 zipline/utils/delayed_signals.py diff --git a/tests/test_delayed_signals.py b/tests/test_delayed_signals.py new file mode 100644 index 00000000..d840bb0a --- /dev/null +++ b/tests/test_delayed_signals.py @@ -0,0 +1,53 @@ +import os +from signal import signal, SIGHUP, SIGINT +import time +from types import FrameType +import unittest + +from zipline.utils.delayed_signals import delayed_signals + +class DelayedSignals(unittest.TestCase): + def handler(self, signum, frame): + print "Got signal " + str(signum) + self.got[signum] = time.time() + self.assertTrue(isinstance(frame, FrameType)) + + def setUp(self): + signal(SIGHUP, self.handler) + signal(SIGINT, self.handler) + + def reset(self): + self.got = {} + + def test_delayed_signals(self): + self.reset() + with delayed_signals([SIGHUP]): + os.kill(os.getpid(), SIGHUP) + time.sleep(2) + self.assertTrue(self.got[SIGHUP]) + self.assertTrue(time.time() - self.got[SIGHUP] < 2) + + def test_immediate_signals(self): + self.reset() + os.kill(os.getpid(), SIGHUP) + time.sleep(2) + self.assertTrue(self.got[SIGHUP]) + self.assertTrue(time.time() - self.got[SIGHUP] > 1) + + def test_multiple_signals(self): + self.reset() + with delayed_signals([SIGHUP, SIGINT]): + os.kill(os.getpid(), SIGINT) + self.assertFalse(SIGHUP in self.got) + self.assertTrue(SIGINT in self.got) + + @delayed_signals([SIGHUP]) + def kill_and_sleep(self): + os.kill(os.getpid(), SIGHUP) + time.sleep(2) + + def test_decorator(self): + self.reset() + self.kill_and_sleep() + self.assertTrue(SIGHUP in self.got) + self.assertTrue(time.time() - self.got[SIGHUP] < 2) diff --git a/zipline/utils/delayed_signals.py b/zipline/utils/delayed_signals.py new file mode 100644 index 00000000..1341fa3e --- /dev/null +++ b/zipline/utils/delayed_signals.py @@ -0,0 +1,42 @@ +from functools import wraps +from signal import signal + +class delayed_signals(object): + """ + Utility to temporary intercept one or more signals while a function or code + block is executed, restore their signal handlers at the end of execution, + and invoke them if the signals were in fact received during execution. + + Can be used either as a decorator or a context manager. + + Pass in an iterable of signals to intercept. + """ + + def handler(self, signum, frame=None): + self.got.append([self.trapped.index(signum), frame]) + + def __init__(self, signals): + self.trapped = signals + self.orig_handlers = [] + self.got = [] + + def __enter__(self): + for sig in self.trapped: + self.orig_handlers.append(signal(sig, self.handler)) + + def __exit__(self, time, value, traceback): + for i in xrange(len(self.trapped)): + signal(self.trapped[i], self.orig_handlers[i]) + for intercepted in self.got: + i = intercepted[0] + signum = self.trapped[i] + frame = intercepted[1] + self.orig_handlers[i](signum, frame) + + def __call__(self, fn): + @wraps(fn) + def call_fn(*args, **kwargs): + with self: + outval = fn(*args, **kwargs) + return outval + return call_fn From edb7e9bbf5216ef9b0384245665f2ac3fd11debf Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Tue, 21 Aug 2012 16:02:33 -0400 Subject: [PATCH 5/6] Refactor delayed_signals class for readability --- zipline/utils/delayed_signals.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/zipline/utils/delayed_signals.py b/zipline/utils/delayed_signals.py index 1341fa3e..9ca8c811 100644 --- a/zipline/utils/delayed_signals.py +++ b/zipline/utils/delayed_signals.py @@ -13,25 +13,23 @@ class delayed_signals(object): """ def handler(self, signum, frame=None): - self.got.append([self.trapped.index(signum), frame]) + self.got.append({'signum': signum, 'frame': frame}) def __init__(self, signals): - self.trapped = signals - self.orig_handlers = [] + self.signals = signals + self.handlers = {} self.got = [] def __enter__(self): - for sig in self.trapped: - self.orig_handlers.append(signal(sig, self.handler)) + for signum in self.signals: + # signal() returns the old signal handler + self.handlers[signum] = signal(signum, self.handler) def __exit__(self, time, value, traceback): - for i in xrange(len(self.trapped)): - signal(self.trapped[i], self.orig_handlers[i]) - for intercepted in self.got: - i = intercepted[0] - signum = self.trapped[i] - frame = intercepted[1] - self.orig_handlers[i](signum, frame) + for signum, handler in self.handlers.items(): + signal(signum, handler) + for signum, frame in ((i['signum'], i['frame']) for i in self.got): + self.handlers[signum](signum, frame) def __call__(self, fn): @wraps(fn) From ad980e64852bee526bd29ad717545ebb0a4b7604 Mon Sep 17 00:00:00 2001 From: Jonathan Kamens Date: Tue, 21 Aug 2012 22:29:51 -0400 Subject: [PATCH 6/6] Close test log handler at end of test to fix file descriptor leak --- zipline/utils/test_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/zipline/utils/test_utils.py b/zipline/utils/test_utils.py index ae804426..3a9a0906 100644 --- a/zipline/utils/test_utils.py +++ b/zipline/utils/test_utils.py @@ -15,6 +15,7 @@ def setup_logger(test, path='/var/log/zipline/zipline.log'): def teardown_logger(test): test.log_handler.pop_application() + test.log_handler.close() def check_list(test, a, b, label): test.assertTrue(isinstance(a, (list, blist.blist)))