├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.rst ├── pytest_pdb.py ├── setup.cfg ├── setup.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | /.cache/ 2 | /.tox/ 3 | /bin/ 4 | /include/ 5 | /lib/ 6 | /pip-selfcheck.json 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Permission is hereby granted, free of charge, to any person obtaining a copy 3 | of this software and associated documentation files (the "Software"), to deal 4 | in the Software without restriction, including without limitation the rights 5 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 6 | copies of the Software, and to permit persons to whom the Software is 7 | furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all 10 | copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 18 | SOFTWARE. 19 | 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include tox.ini 3 | include LICENSE 4 | include test*.py 5 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | pytest-pdb 2 | ========== 3 | 4 | py.test plugin pytest plugin which adds pdb helper commands related to pytest. 5 | 6 | Usage 7 | ----- 8 | 9 | install via:: 10 | 11 | pip install pytest-pdb 12 | 13 | Commands 14 | -------- 15 | 16 | ``gototest | gt`` 17 | When debugging with pdb, type ``gototest`` which brings you directly to 18 | the frame of the test function. 19 | 20 | 21 | ``whichtest | wt`` 22 | When debugging with pdb, type ``whichtest`` which should print something like:: 23 | 24 | Currently in test_pdb.py:13: TestClasses.test_class_pdb 25 | 26 | It works by walking the frame stack until it finds a test item in the locals. 27 | This depends on how ``pytest_pyfunc_call`` is implemented. 28 | If that changes or is replaced by a plugin, the whichtest command may fail. 29 | 30 | 31 | ``top`` 32 | Move to top (oldest) frame. 33 | 34 | 35 | ``bottom`` 36 | Move to bottom (newest) frame. 37 | 38 | 39 | Changes 40 | ======= 41 | 42 | 0.3.2 - Unreleased 43 | ------------------ 44 | 45 | 46 | 47 | 0.3.1 - 2018-07-31 48 | ------------------ 49 | 50 | - Remove debug print output. 51 | [fschulze] 52 | 53 | 54 | 0.3.0 - 2018-07-28 55 | ------------------ 56 | 57 | - Show offset of current frame to test frame. 58 | [blueyed, fschulze] 59 | 60 | - Add ``top`` and ``bottom`` commands. 61 | [blueyed] 62 | 63 | - Add ``wt``/``gt`` shortcuts. 64 | [blueyed] 65 | 66 | - Add ``gototest`` command. 67 | [blueyed] 68 | 69 | - Print location upon entering pdb. 70 | [blueyed, fschulze] 71 | 72 | 73 | 0.2.0 - 2017-01-17 74 | ------------------ 75 | 76 | - Fix/improve location reporting. 77 | [blueyed (Daniel Hahler)] 78 | 79 | 80 | 0.1.0 - 2016-07-09 81 | ------------------ 82 | 83 | - Initial release. 84 | [fschulze (Florian Schulze)] 85 | -------------------------------------------------------------------------------- /pytest_pdb.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import pdb 3 | import pytest 4 | import sys 5 | 6 | 7 | def find_test_by_frame(currentframe): 8 | frame = currentframe 9 | prev = frame 10 | while frame: 11 | for value in frame.f_locals.values(): 12 | if isinstance(value, pytest.Item): 13 | return (value, prev) 14 | prev = frame 15 | frame = frame.f_back 16 | return (None, currentframe) 17 | 18 | 19 | def find_test_by_stack(stack): 20 | for index, (frame, lineno) in reversed(list(enumerate(stack))): 21 | for value in frame.f_locals.values(): 22 | if isinstance(value, pytest.Item): 23 | return (value, stack[index + 1][0], index + 1) 24 | return (None, stack[0], 0) 25 | 26 | 27 | def find_settrace_frame(curframe): 28 | frame = curframe 29 | while frame: 30 | if frame.f_code.co_name == 'set_trace': 31 | if frame.f_back: 32 | return frame.f_back 33 | frame = frame.f_back 34 | 35 | 36 | def offset_between_frames(currentframe, destinationframe): 37 | # search from current 38 | index = 0 39 | frame = currentframe 40 | while frame: 41 | if frame == destinationframe: 42 | return index 43 | index -= 1 44 | frame = frame.f_back 45 | # search from destination 46 | index = 0 47 | frame = destinationframe 48 | while frame: 49 | if frame == currentframe: 50 | return index 51 | index += 1 52 | frame = frame.f_back 53 | 54 | 55 | def offset_description(offset): 56 | if offset == 0: 57 | return 'at current frame' 58 | elif offset == 1: 59 | return '1 frame above' 60 | elif offset > 1: 61 | return '%s frames above' % offset 62 | elif offset == -1: 63 | return '1 frame below' 64 | else: 65 | return '%s frames below' % -offset 66 | 67 | 68 | class PdbExtension: 69 | def do_whichtest(self, arg): 70 | """whichtest | wt 71 | Show which test we are currently in. 72 | """ 73 | (test, frame, index) = find_test_by_stack(self.stack) 74 | if test is None: 75 | print("Couldn't determine current test", file=self.stdout) 76 | return 77 | 78 | offset = index - self.curindex 79 | 80 | print("Currently in {} ({}:{}) on line {} ({})".format( 81 | test.location[2], test.location[0], test.location[1] + 1, 82 | frame.f_lineno, offset_description(offset)), file=self.stdout) 83 | do_wt = do_whichtest 84 | 85 | def do_gototest(self, arg): 86 | """gototest | gt 87 | Go to frame containing the current test. 88 | """ 89 | (test, frame, index) = find_test_by_stack(self.stack) 90 | if test is None: 91 | print("Couldn't determine current test.", file=self.stdout) 92 | return 93 | 94 | self._select_frame(index) 95 | do_gt = do_gototest 96 | 97 | def do_top(self, arg): 98 | """top 99 | Move to top (oldest) frame. 100 | """ 101 | if self.curindex == 0: 102 | self.error('Oldest frame') 103 | return 104 | self._select_frame(0) 105 | 106 | def do_bottom(self, arg): 107 | """bottom 108 | Move to bottom (newest) frame. 109 | """ 110 | if self.curindex + 1 == len(self.stack): 111 | self.error('Newest frame') 112 | return 113 | self._select_frame(len(self.stack) - 1) 114 | 115 | 116 | def pytest_configure(config): 117 | cmds = {x[3:] for x in dir(PdbExtension) if x.startswith('do_')} 118 | 119 | prefixes = {'do', 'help'} 120 | for prefix in prefixes: 121 | for cmd in cmds: 122 | attr = '%s_%s' % (prefix, cmd) 123 | if hasattr(pdb.Pdb, attr): 124 | raise ValueError 125 | for prefix in prefixes: 126 | for cmd in cmds: 127 | attr = '%s_%s' % (prefix, cmd) 128 | if hasattr(PdbExtension, attr): 129 | setattr(pdb.Pdb, attr, getattr(PdbExtension, attr)) 130 | 131 | 132 | def pytest_enter_pdb(config): 133 | import _pytest.config 134 | tw = _pytest.config.create_terminal_writer(config) 135 | curframe = sys._getframe().f_back 136 | (test, frame) = find_test_by_frame(curframe) 137 | if test is None: 138 | tw.sep(">", "Couldn't determine current test") 139 | return 140 | 141 | offset = offset_between_frames(find_settrace_frame(curframe), frame) 142 | desc = '' 143 | if offset is not None: 144 | desc = ' (%s)' % offset_description(offset) 145 | 146 | tw.sep(">", "Currently in {} ({}:{}) on line {}{}".format( 147 | test.location[2], test.location[0], test.location[1] + 1, 148 | frame.f_lineno, desc)) 149 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | 4 | [devpi:upload] 5 | formats = sdist.tgz,bdist_wheel 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | setup( 5 | name="pytest-pdb", 6 | description='pytest plugin which adds pdb helper commands related to pytest.', 7 | long_description=open("README.rst").read(), 8 | license="MIT license", 9 | version='0.3.2.dev0', 10 | author='Florian Schulze', 11 | author_email='florian.schulze@gmx.net', 12 | url='https://github.com/fschulze/pytest-pdb', 13 | py_modules=["pytest_pdb"], 14 | entry_points={'pytest11': ['pytest_pdb = pytest_pdb']}, 15 | install_requires=['pytest'], 16 | classifiers=[ 17 | "Framework :: Pytest"]) 18 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,py33,py34,py35 3 | 4 | [testenv] 5 | usedevelop = true 6 | deps = 7 | pytest 8 | pytest-flake8 9 | coverage 10 | commands = 11 | {envbindir}/py.test --junitxml={envlogdir}/junit-{envname}.xml {posargs} 12 | 13 | [pytest] 14 | addopts = --flake8 15 | flake8-ignore = E501 16 | norecursedirs = bin lib include Scripts .* 17 | --------------------------------------------------------------------------------