def setUp(self): self.cmd_manager = RemoteCommandManager()
class RemoteCommandManagerTester(with_metaclass(Py2TestMeta, TestCase)): """ Tests the various behaviors that the RemoteCommandManager should conform to. Some tests rely on how the command manager affects the tracer that it is managing. """ @skip_py3 @classmethod def setUpClass(cls): """ Start up a tracer server for the remote command managers to connect to. """ cls.setup_server() def setUp(self): self.cmd_manager = RemoteCommandManager() @classmethod def setup_server(cls): """ Sets up the server to run on a random yet valid port. """ cls.bad_auth_msg = 'BAD-AUTH' cls.tracer_host = cls.client_host = 'localhost' cls.server = QdbServer( tracer_host=cls.tracer_host, tracer_port=0, client_host=cls.client_host, client_port=0, tracer_auth_fn=lambda a: a != cls.bad_auth_msg, attach_timeout=0, ) cls.server.start() cls.tracer_port = cls.server.tracer_server.server_port @classmethod def tearDownClass(cls): """ Stop the test server. """ cls.server.stop() def tearDown(self): if Qdb._instance: Qdb._instance.disable() def MockTracer(self): """ Construct a mock tracer. """ tracer = MagicMock() tracer.address = self.tracer_host, self.tracer_port tracer.pause_signal = signal.SIGUSR2 tracer.retry_attempts = 1 tracer.local = 0, 0 tracer.uuid = 'mock' tracer.watchlist = {} tracer.curframe = sys._getframe() tracer.stack = [(sys._getframe(), 1)] * 3 tracer.skip_fn = lambda _: False tracer.cmd_manager = self.cmd_manager return tracer def test_connect(self): """ Tests that the remote command manager can connect to the server. """ tracer = self.MockTracer() # If we fail to connect, an error is raised and we fail the test. self.cmd_manager.start(tracer) def test_fail_to_connect(self): """ Assert that the correct error is raised if we cannot connect. """ tracer = self.MockTracer() tracer.address = 'not' + self.tracer_host, self.tracer_port with self.assertRaises(QdbFailedToConnect): self.cmd_manager.start(tracer, '') def test_fail_auth(self): """ Asserts that failing auth gives us the proper error. """ tracer = self.MockTracer() with self.assertRaises(QdbAuthenticationError): cmd_manager = self.cmd_manager cmd_manager.start(tracer, self.bad_auth_msg) cmd_manager.next_command(tracer) @parameterized.expand([ ({'file': 'test.py', 'line': 2},), ({'line': 2},), ({'line': 2, 'cond': '2 + 2 == 4'},), ({'line': 2, 'func': 'f'},), ({'line': 2, 'file': 't.py', 'cond': 'f()', 'func': 'g'},) ]) def test_fmt_breakpoint_dict(self, arg_dict): tracer = self.MockTracer() tracer.default_file = 'd.py' cmd_manager = self.cmd_manager cpy = dict(arg_dict) self.assertEqual( cmd_manager.fmt_breakpoint_dict(tracer, cpy), set_break_params(tracer, **cpy) ) @parameterized.expand([ (lambda t: t.set_step, 'step'), (lambda t: t.set_next, 'next'), (lambda t: t.set_continue, 'continue'), (lambda t: t.set_return, 'return'), (lambda t: t.set_break, 'set_break', { 'file': fix_filename(__file__), 'line': 1 }), (lambda t: t.clear_break, 'clear_break', { 'file': fix_filename(__file__), 'line': 1 }), (lambda t: t.set_watch, 'set_watch', ['2 + 2']), (lambda t: t.clear_watch, 'clear_watch', ['2 + 2']), (lambda t: t.get_file, 'list'), (lambda t: t.eval_fn, 'eval' '2 + 2'), (lambda t: t.disable, 'disable', 'hard'), (lambda t: t.disable, 'disable', 'soft'), ]) def test_commands(self, attrgetter_, event, payload=None): """ Tests various commands with or without payloads. """ tracer = self.MockTracer() cmd_manager = self.cmd_manager cmd_manager.start(tracer, '') self.server.session_store.send_to_tracer( uuid=tracer.uuid, event=fmt_msg(event, payload) ) with gevent.Timeout(0.1, False): cmd_manager.next_command(tracer) tracer.start.assert_called() # Start always gets called. attrgetter_(tracer).assert_called() # Kill the session we just created self.server.session_store.slaughter(tracer.uuid) def test_locals(self): """ Tests accessing the locals. """ tracer = self.MockTracer() tracer.curframe_locals = {'a': 'a'} cmd_manager = self.cmd_manager cmd_manager.start(tracer, '') gyield() self.server.session_store.send_to_tracer( uuid=tracer.uuid, event=fmt_msg('locals') ) command_locals_called = NonLocal(False) def test_command_locals(cmd_manager, tracer, payload): command_locals_called.value = True type(self.cmd_manager).command_locals(cmd_manager, tracer, payload) cmd_locals = partial(test_command_locals, cmd_manager) with gevent.Timeout(0.1, False), \ patch.object(cmd_manager, 'command_locals', cmd_locals): cmd_manager.next_command(tracer) self.assertTrue(command_locals_called.value) tracer.start.assert_called() # Start always gets called. self.server.session_store.slaughter(tracer.uuid) @parameterized.expand([('up',), ('down',)]) def test_stack_transpose_no_skip(self, direction): """ Tests moving up the stack. """ events = [] def capture_event(self, event, payload): events.append(fmt_msg(event, payload)) class cmd_manager(type(self.cmd_manager)): """ Wrap send_stack by just capturing the output to make assertions on it. """ def send_stack(self, tracer): with patch.object(cmd_manager, 'send_event', capture_event): super(cmd_manager, self).send_stack(tracer) db = Qdb( uuid='test_' + direction, cmd_manager=cmd_manager(), host=self.tracer_host, port=self.tracer_port, redirect_output=False, green=True, ) gyield() if direction == 'down': # We are already located in the bottom frame, let's go up one # so that we may try going down. self.server.session_store.send_to_tracer( uuid=db.uuid, event=fmt_msg('up') ) self.server.session_store.send_to_tracer( uuid=db.uuid, event=fmt_msg(direction) ) self.server.session_store.send_to_tracer( uuid=db.uuid, event=fmt_msg('disable', 'soft') ) gyield() db.set_trace() start_ind = events[-2]['p']['index'] shift_ind = events[-1]['p']['index'] if direction == 'up': self.assertEqual(start_ind - shift_ind, 1) elif direction == 'down': self.assertEqual(shift_ind - start_ind, 1) else: self.fail("direction is not 'up' or 'down'") # wut did u do? def test_pause(self): """ Asserts that sending a pause to the process will raise the pause signal in the tracer process. """ pause_called = [False] def pause_handler(signal, stackframe): """ Pause handler that marks that we made it into this function. """ pause_called[0] = True db = Qdb( cmd_manager=self.cmd_manager, host=self.tracer_host, port=self.tracer_port, green=True, ) signal.signal(db.pause_signal, pause_handler) self.server.session_store.send_to_tracer( uuid=db.uuid, event=fmt_msg('pause') ) self.assertTrue(pause_called) @parameterized.expand([ ('2 + 2', None, '4'), ('print "test"', None, 'test'), ('ValueError("test")', None, "ValueError('test',)"), ('raise ValueError("test")', 'ValueError', 'ValueError: test'), ('[][10]', 'IndexError', 'IndexError: list index out of range'), ('{}["test"]', 'KeyError', "KeyError: 'test'"), ]) def test_eval_results(self, input_, exc, output): """ Tests that evaling code returns the proper results. """ prints = [] class cmd_manager(type(self.cmd_manager)): """ Captures print commands to make assertions on them. """ def send_print(self, input_, exc, output): prints.append({ 'input': input_, 'exc': exc, 'output': output }) db = Qdb( uuid='eval_test', cmd_manager=cmd_manager(), host=self.tracer_host, port=self.tracer_port, redirect_output=False, green=True, ) gyield() self.server.session_store.send_to_tracer( uuid=db.uuid, event=fmt_msg('eval', input_) ) self.server.session_store.send_to_tracer( uuid=db.uuid, event=fmt_msg('continue') ) db.set_trace(stop=True) self.server.session_store.slaughter(db.uuid) self.assertTrue(prints) print_ = prints[0] self.assertEqual(print_['input'], input_) self.assertEqual(print_['exc'], exc) self.assertEqual(print_['output'], output) @parameterized.expand([ ('2 + 2', None, '4'), ('print "test"', None, 'test'), ('ValueError("test")', None, "ValueError('test',)"), ('raise ValueError("test")', 'ValueError', 'ValueError: test'), ('[][10]', 'IndexError', 'IndexError: list index out of range'), ('{}["test"]', 'KeyError', "KeyError: 'test'"), ('(1,) * 30', None, pformat((1,) * 30)), ('set(range(30))', None, pformat(set(range(30)))), ]) def test_eval_pprint(self, input_, exc, output): """ Tests that evaling code returns the proper results. """ prints = [] class cmd_manager(type(self.cmd_manager)): """ Captures print commands to make assertions on them. """ def send_print(self, input_, exc, output): prints.append({ 'input': input_, 'exc': exc, 'output': output }) db = Qdb( uuid='eval_test', cmd_manager=cmd_manager(), host=self.tracer_host, port=self.tracer_port, redirect_output=False, ) gyield() self.server.session_store.send_to_tracer( uuid=db.uuid, event=fmt_msg('pprint', input_) ) self.server.session_store.send_to_tracer( uuid=db.uuid, event=fmt_msg('continue') ) db.set_trace(stop=True) self.server.session_store.slaughter(db.uuid) self.assertTrue(prints) print_ = prints[0] self.assertEqual(print_['input'], input_) self.assertEqual(print_['exc'], exc) self.assertEqual(print_['output'], output) def test_eval_state_update(self): """ Tests that eval may update the state of the program. """ # We will try to corrupt this variable with a stateful operation. test_var = 'pure' # NOQA db = Qdb( uuid='eval_test', cmd_manager=self.cmd_manager, host=self.tracer_host, port=self.tracer_port, redirect_output=False, green=True, ) gyield() self.server.session_store.send_to_tracer( uuid=db.uuid, event=fmt_msg('eval', "test_var = 'mutated'") ) self.server.session_store.send_to_tracer( uuid=db.uuid, event=fmt_msg('continue') ) db.set_trace(stop=True) self.server.session_store.slaughter(db.uuid) self.assertEqual(test_var, 'mutated') def test_eval_timeout(self): """ Tests that evaluating user repl commands will raise Timeouts. """ def g(): while True: pass prints = [] class cmd_manager(type(self.cmd_manager)): """ Captures print commands to make assertions on them. """ def send_print(self, input_, exc, output): prints.append({ 'input': input_, 'exc': exc, 'output': output }) to_eval = 'g()' db = Qdb( uuid='timeout_test', cmd_manager=cmd_manager(), host=self.tracer_host, port=self.tracer_port, redirect_output=False, execution_timeout=1, green=True, ) gyield() self.server.session_store.send_to_tracer( uuid=db.uuid, event=fmt_msg('eval', to_eval) ) self.server.session_store.send_to_tracer( uuid=db.uuid, event=fmt_msg('continue') ) db.set_trace(stop=True) self.server.session_store.slaughter(db.uuid) self.assertTrue(prints) print_ = prints[0] self.assertEqual(print_['input'], to_eval) self.assertTrue(print_['exc']) self.assertEqual( print_['output'], db.exception_serializer(QdbExecutionTimeout(to_eval, 1)) ) def test_send_disabled(self): """ Tests that disabling sends a 'disabled' message back to the server. """ class cmd_manager(type(self.cmd_manager)): disabled = False def send_disabled(self): self.disabled = True db = Qdb( uuid='send_disabled_test', cmd_manager=cmd_manager(), host=self.tracer_host, port=self.tracer_port, redirect_output=False, green=True, ) gyield() db.set_trace(stop=False) db.disable() self.assertTrue(db.cmd_manager.disabled) self.server.session_store.slaughter(db.uuid) @parameterized.expand([(False,), (True,)]) def test_send_stack_results(self, use_skip_fn): """ Tests that the results from sending the stack are accurate. WARNING: This test uses lines of it's own source as string literals, be sure to edit the source and the string if you make any changes. """ def skip_fn(filename): return not fix_filename(__file__) in filename events = [] def capture_event(self, event, payload): events.append(fmt_msg(event, payload)) class cmd_manager(type(self.cmd_manager)): """ Wrap send_stack by just capturing the output to make assertions on it. """ def send_stack(self, tracer): with patch.object(cmd_manager, 'send_event', capture_event): super(cmd_manager, self).send_stack(tracer) db = Qdb( uuid='send_stack_test', cmd_manager=cmd_manager(), host=self.tracer_host, port=self.tracer_port, redirect_output=False, skip_fn=skip_fn if use_skip_fn else None, green=True, ) gyield() self.server.session_store.send_to_tracer( uuid=db.uuid, event=fmt_msg('continue') ) db.set_trace(stop=True) self.assertTrue(events) # EDIT IN BOTH PLACES event = events[0] if use_skip_fn: # Assert that we actually suppressed some frames. self.assertTrue(len(event['p']['stack']) < len(db.stack)) self.assertEqual( # I love dictionaries so much! event['p']['stack'][event['p']['index']]['code'], ' self.assertTrue(events) # EDIT IN BOTH PLACES', ) self.server.session_store.slaughter(db.uuid) def test_why_are_you_executing_all_these_commands(self): db = Qdb( uuid='send_stack_test', cmd_manager=self.cmd_manager, host=self.tracer_host, port=self.tracer_port, redirect_output=False, green=True, ) gyield() for n in range(sys.getrecursionlimit()): self.server.session_store.send_to_tracer( uuid=db.uuid, event=fmt_msg('eval', 'None') ) self.server.session_store.send_to_tracer( uuid=db.uuid, event=fmt_msg('continue') ) with gevent.Timeout(1): db.set_trace(stop=True)