├── .gitignore ├── .lvimrc ├── README.rst ├── build.py ├── ftplugin └── python_pyunit.vim ├── src ├── base.vim └── python_unittests.py └── tests ├── mocks ├── __init__.py └── vim.py └── test_python_unittests.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /.lvimrc: -------------------------------------------------------------------------------- 1 | " Only consider source files under src 2 | let PyUnitSourceRoot = "src" 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | vim-pyunit 2 | ========== 3 | 4 | Installation 5 | ------------ 6 | 1. Install the following packages from PyPI: 7 | 8 | - nose_: the unit test runner; 9 | - nose_machineout_: The ``machineout`` plugin formats the ``nose`` output 10 | so that Vim can parse it more easily; 11 | - vim_bridge_: This is required for the vim plugin scripts, to call 12 | directly into Python functions. 13 | 14 | To run the unit tests for this project, you also need ``mock``: 15 | 16 | - mock_: Required to mock out the ``vim`` object in our test cases. 17 | 18 | 2. Clone the git repository:: 19 | 20 | git clone git://github.com/nvie/vim-pyunit.git 21 | cd vim-pyunit 22 | 23 | 3. Copy the file ``ftplugin/python_pyunit.vim`` to your ``~/.vim/ftplugin`` 24 | directory 25 | 26 | .. _nose: http://pypi.python.org/pypi/nose 27 | .. _nose_machineout: http://pypi.python.org/pypi/nose_machineout 28 | .. _vim_bridge: http://pypi.python.org/pypi/vim_bridge 29 | .. _mock: http://pypi.python.org/pypi/mock 30 | 31 | 32 | Usage 33 | ----- 34 | 1. Open a Python file (or its corresponding unit test file named 35 | ``test_.py``) 36 | 2. Press ```` to run ``nosetests`` on it 37 | 38 | It shows the errors inside a quickfix window, which will allow your to 39 | quickly jump to the error locations by simply pressing ``[Enter]``. 40 | 41 | 42 | Source files vs. test files 43 | --------------------------- 44 | ``vim-pyunit`` assumes that you have a single test file for each Python 45 | source file. The settings ``PyUnitTestPrefix``, ``PyUnitSourceRoot``, 46 | ``PyUnitTestsRoot``, and ``PyUnitTestsStructure`` determine how the plugin 47 | finds which test files belong to which source files. 48 | 49 | The ``PyUnitTestsStructure`` setting is the most important one, because it 50 | determines where the PyUnit plugin searches for source and test files. 51 | There are three options: 52 | 53 | * **flat**: Put all test files in a single test directory. File names are 54 | composed by resembling the source's module structure, using underscores 55 | as separators. For example, the test file for the source file 56 | ``foo/bar.py`` is called ``tests/test_foo_bar.py``. 57 | * **side-by-side**: Put all the test files in the same directory as the 58 | source files. Test files are prefixed with ``test_``. For example, the 59 | test file for the source file ``foo/bar.py`` is called 60 | ``foo/test_bar.py``. Use this setting when testing Django apps. 61 | * **follow-hierarchy**: Put all the test files in a separate test 62 | directory (specified by ``PyUnitTestsRoot``), but keep the same 63 | directory hierarchy as used in the source directory. 64 | For example, the test file for the source file ``foo/bar.py`` is called 65 | ``tests/test_foo/test_bar.py``. 66 | * **nose**: Put all the test files in a separate test 67 | directory (specified by ``PyUnitTestsRoot``), but keep the same 68 | directory hierarchy as used in the source directory. As opposed to the 69 | ``follow-hierarchy`` layout, the intermediate directories are not 70 | prefixed with ``test_``, only the last (file part) component is. 71 | For example, the test file for the source file ``foo/bar.py`` is called 72 | ``tests/foo/test_bar.py``. 73 | 74 | 75 | Keyboard mappings 76 | ----------------- 77 | By default, the ``vim-pyunit`` plugin defines the following keyboard 78 | mappings: 79 | 80 | +----------+------------------------------------------------------------+ 81 | | Keymap | Description | 82 | +==========+============================================================+ 83 | | F8 | Run ``nosetests`` for the current file. This mapping can | 84 | | | be used on both the source file, and on its corresponding | 85 | | | test file. Calls ``PyUnitRunTests()`` | 86 | +----------+------------------------------------------------------------+ 87 | | Shift+F8 | Run ``nosetests`` for all test files in the project, this | 88 | | | is equivalent to running ``nosetests`` in the root of your | 89 | | | project. Calls ``PyUnitRunAllTests()`` | 90 | +----------+------------------------------------------------------------+ 91 | | F9 | Switch between the source and the corresponding test file. | 92 | | | If the source or test file is not yet open, it is opened. | 93 | | | The setting ``tests_split_window`` is used to determine | 94 | | | where the file needs to be opened screen-wise. Calls | 95 | | | ``PyUnitSwitchToCounterpart()`` | 96 | +----------+------------------------------------------------------------+ 97 | 98 | The plugin autodetects whether you have remapped the functions to custom 99 | keyboard mappings. If so, if does not register the default mappings. So 100 | to pick your own shortcut key mappings, simply add lines like this to your 101 | ``.vimrc``:: 102 | 103 | noremap ,t :call PyUnitRunTests() 104 | noremap! ,t :call PyUnitRunTests() 105 | 106 | (Which would map the test runner to comma-T. ```` then remains what it 107 | was.) 108 | 109 | If you wish to disable any automatic keyboard mapping, simply set:: 110 | 111 | let no_pyunit_maps = 1 112 | 113 | 114 | Configuration 115 | ------------- 116 | The plugin supports setting of the following variables: 117 | 118 | +-------------------------------+------------------------------------------------+---------------------------+-----------------------------------+ 119 | | Variable | Description | Values | Default | 120 | +===============================+================================================+===========================+===================================+ 121 | | ``PyUnitShowTests`` | Shows the tests. | 0 or 1 | 1 | 122 | +-------------------------------+------------------------------------------------+---------------------------+-----------------------------------+ 123 | | ``PyUnitCmd`` | The command to run the unit test. | any string | "nosetests -q --with-machineout" | 124 | +-------------------------------+------------------------------------------------+---------------------------+-----------------------------------+ 125 | | ``ProjRootIndicators`` | List of filenames indicating the project root. | list of file names | [".git", "setup.py", "setup.cfg"] | 126 | +-------------------------------+------------------------------------------------+---------------------------+-----------------------------------+ 127 | | ``ProjRootStopAtHomeDir`` | Stop the search for the project root at the | 0 or 1 | 1 | 128 | | | user's home dir. | | | 129 | +-------------------------------+------------------------------------------------+---------------------------+-----------------------------------+ 130 | | ``PyUnitTestPrefix`` | The filename prefix to use for test files. | any string | "test\_" | 131 | +-------------------------------+------------------------------------------------+---------------------------+-----------------------------------+ 132 | | ``PyUnitTestSuffix`` | *Not implemented yet* | 0 or 1 | n/a | 133 | +-------------------------------+------------------------------------------------+---------------------------+-----------------------------------+ 134 | | ``PyUnitSourceRoot`` | The relative location where all source files | directory spec, or empty | "" | 135 | | | live. | | | 136 | +-------------------------------+------------------------------------------------+---------------------------+-----------------------------------+ 137 | | ``PyUnitTestsRoot`` | The relative location where all tests live. | directory spec | "tests" | 138 | +-------------------------------+------------------------------------------------+---------------------------+-----------------------------------+ 139 | | ``PyUnitTestsStructure`` | Specifies how you wish to organise your tests. | flat, follow-hierarchy, | "follow-hierarchy" | 140 | | | | side-by-side | | 141 | +-------------------------------+------------------------------------------------+---------------------------+-----------------------------------+ 142 | | ``PyUnitTestsSplitWindow`` | Specifies where test files should be opened, | left, right, top, bottom, | "right" | 143 | | | when oopened next to the source file. When set | no | | 144 | | | to ``no``, doesn't open a new window at all, | | | 145 | | | but reuses the current buffer. | | | 146 | +-------------------------------+------------------------------------------------+---------------------------+-----------------------------------+ 147 | | ``PyUnitConfirmTestCreation`` | Ask to confirm creation of new test files. | 0 or 1 | 1 | 148 | +-------------------------------+------------------------------------------------+---------------------------+-----------------------------------+ 149 | 150 | 151 | Tips 152 | ---- 153 | This plugin goes well together with the following plugin: 154 | 155 | - flake8_ (Python static syntax checker under ````) 156 | 157 | .. _flake8: http://github.com/nvie/vim-flake8 158 | -------------------------------------------------------------------------------- /build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | 4 | source_dir = 'src' 5 | output_dir = 'ftplugin' 6 | 7 | 8 | def build(): 9 | py_src = file(os.path.join(source_dir, 'python_unittests.py')).read() 10 | vim_src = file(os.path.join(source_dir, 'base.vim')).read() 11 | combined_src = vim_src.replace('__PYTHON_SOURCE__', py_src) 12 | if not os.path.exists(output_dir): 13 | os.mkdir(output_dir) 14 | output_path = os.path.join(output_dir, 'python_pyunit.vim') 15 | file(output_path, 'w').write(combined_src) 16 | 17 | if __name__ == '__main__': 18 | build() 19 | -------------------------------------------------------------------------------- /ftplugin/python_pyunit.vim: -------------------------------------------------------------------------------- 1 | " 2 | " Python filetype plugin for unit testing (currently with nose) 3 | " Language: Python (ft=python) 4 | " Maintainer: Vincent Driessen 5 | " Version: Vim 7 (may work with lower Vim versions, but not tested) 6 | " URL: http://github.com/nvie/vim-pyunit 7 | " 8 | " Very inspired by Gary Bernhart's work: 9 | " http://bitbucket.org/garybernhardt/dotfiles/src/tip/.vimrc 10 | " 11 | " Based on Mike Crute's Vim plugin: 12 | " http://code.crute.org/mcrute_dotfiles/file/a19ddffcabe6/.vim/plugin/python_testing.vim 13 | " 14 | 15 | " Only do this when not done yet for this buffer 16 | if exists("g:loaded_python_unittests_ftplugin") 17 | finish 18 | endif 19 | let loaded_python_unittests_ftplugin = 1 20 | 21 | " Configuration of the test tool {{{ 22 | " Set the PyUnitCmd to whatever is your testing tool (default: nosetests) 23 | if !exists("g:PyUnitCmd") 24 | let PyUnitCmd = "nosetests -q --with-machineout" 25 | endif 26 | 27 | " Set PyUnitShowTests to 1 if you want to show the tests (default: 1) 28 | if !exists("g:PyUnitShowTests") " TODO: Use this one! 29 | let PyUnitShowTests = 1 30 | endif 31 | 32 | "}}} 33 | " Configuration for autodetecting project root {{{ 34 | " Configure what files indicate a project root 35 | if !exists("g:ProjRootIndicators") 36 | let ProjRootIndicators = [ ".git", "setup.py", "setup.cfg" ] 37 | endif 38 | 39 | " Scan from the current working directory up until the home dir, instead of 40 | " the filesystem root. This has no effect on projects that reside outside the 41 | " user's home directory. In those cases there will be scanned up until the 42 | " filesystem root directory. 43 | if !exists("g:ProjRootStopAtHomeDir") 44 | let ProjRootStopAtHomeDir = 1 45 | endif 46 | " }}} 47 | " Configuration for tests organisation {{{ 48 | " Prefix used for all path components of the test file 49 | if !exists("g:PyUnitTestPrefix") 50 | " nosetests scans all files/directories starting with "test_", so this is 51 | " a sane default value. There should not be a need to change this if you 52 | " want to use nose. 53 | let PyUnitTestPrefix = "test_" 54 | endif 55 | 56 | " Location where the source files live. When set, this must be the relative 57 | " directory under the project root. For example, if you have your project 58 | " root as follows: 59 | " - foo/ 60 | " - bar/ 61 | " - tests/ 62 | " 63 | " And you want the test structure: 64 | " - tests/test_foo/ 65 | " - tests/test_bar/ 66 | " 67 | " Then you can leave this at the default value (""). 68 | " 69 | " But if you have: 70 | " - src/ 71 | " - foo/ 72 | " - bar/ 73 | " - tests/ 74 | " 75 | " And you DON'T want: 76 | " - tests/test_src/test_foo/ 77 | " - tests/test_src/test_bar/ 78 | " but: 79 | " - tests/test_foo/ 80 | " - tests/test_bar/ 81 | " 82 | " Then you need to set this value to "src" 83 | if !exists("g:PyUnitSourceRoot") 84 | let PyUnitSourceRoot = "" 85 | endif 86 | 87 | " Relative location under the project root where to look for the test files. 88 | if !exists("g:PyUnitTestsRoot") 89 | let PyUnitTestsRoot = "tests" 90 | endif 91 | 92 | " Tests structure can be one of: flat, follow-hierarchy 93 | "let PyUnitTestsStructure = "flat" 94 | if !exists("g:PyUnitTestsStructure") 95 | let PyUnitTestsStructure = "follow-hierarchy" 96 | endif 97 | " }}} 98 | " Configuration for editing preferences {{{ 99 | if !exists("g:PyUnitConfirmTestCreation") 100 | " Set this to 0 if you want to silently create new test files 101 | let PyUnitConfirmTestCreation = 1 102 | endif 103 | if !exists("g:PyUnitTestsSplitWindow") 104 | " Specifies how the test window should open, relative to the source file 105 | " window. Takes one of the following values: top, bottom, left, right 106 | let PyUnitTestsSplitWindow = "right" 107 | endif 108 | " }}} 109 | 110 | python << endpython 111 | import vim 112 | import os 113 | import os.path 114 | from vim_bridge import bridged 115 | 116 | 117 | # 118 | # General helper functions 119 | # 120 | 121 | def _relpath(path, start='.', try_stdlib=True): 122 | """Returns the relative version of the path. This is a backport of 123 | Python's stdlib routine os.path.relpath(), which is not yet available in 124 | Python 2.4. 125 | 126 | """ 127 | # Fall back onto stdlib version of it, if available 128 | if try_stdlib: 129 | try: 130 | return os.path.relpath(path, start) 131 | except AttributeError: 132 | # Python versions below 2.6 don't have the relpath function 133 | # It's ok, we fall back onto our own implementation 134 | pass 135 | 136 | fullp = os.path.abspath(path) 137 | fulls = os.path.abspath(start) 138 | matchs = os.path.normpath(start) 139 | if not matchs.endswith(os.sep): 140 | matchs += os.sep 141 | 142 | if fullp == fulls: 143 | return '.' 144 | elif fullp.startswith(matchs): 145 | return fullp[len(matchs):] 146 | else: 147 | # Strip dirs off of fulls until it is a prefix of fullp 148 | path_prefixes = [] 149 | while True: 150 | path_prefixes.append(os.path.pardir) 151 | fulls = os.path.dirname(fulls) 152 | if fullp.startswith(fulls): 153 | break 154 | remainder = fullp[len(fulls):] 155 | if remainder.startswith(os.sep): 156 | remainder = remainder[len(os.sep):] 157 | path_prefix = os.sep.join(path_prefixes) 158 | return os.path.join(path_prefix, remainder) 159 | 160 | 161 | def strip_prefix(s, prefix): 162 | if prefix != "" and s.startswith(prefix): 163 | return s[len(prefix):] 164 | else: 165 | return s 166 | 167 | 168 | def is_home_dir(path): 169 | return os.path.realpath(path) == os.path.expandvars("$HOME") 170 | 171 | 172 | def is_fs_root(path): 173 | return os.path.realpath(path) == "/" or \ 174 | (int(vim.eval("g:ProjRootStopAtHomeDir")) and is_home_dir(path)) 175 | 176 | 177 | def find_project_root(path='.'): 178 | if not os.path.isdir(path): 179 | return find_project_root(os.path.dirname(os.path.realpath(path))) 180 | 181 | indicators = vim.eval("g:ProjRootIndicators") 182 | while not is_fs_root(path): 183 | for i in indicators: 184 | if os.path.exists(os.path.join(path, i)): 185 | return os.path.realpath(path) 186 | path = os.path.join(path, os.path.pardir) 187 | raise Exception("Could not find project root") 188 | 189 | 190 | # 191 | # Classes that implement TestLayouts 192 | # 193 | 194 | class BaseTestLayout(object): 195 | def __init__(self): 196 | self.source_root = vim.eval('g:PyUnitSourceRoot') 197 | self.test_root = vim.eval('g:PyUnitTestsRoot') 198 | self.prefix = vim.eval('g:PyUnitTestPrefix') 199 | 200 | 201 | # Helper methods, to be used in subclasses 202 | def break_down(self, path): 203 | parts = path.split(os.sep) 204 | if len(parts) > 0: 205 | if parts[-1] == '__init__.py': 206 | del parts[-1] 207 | elif parts[-1].endswith(".py"): 208 | parts[-1] = parts[-1][:-len(".py")] 209 | return parts 210 | 211 | def glue_parts(self, parts, use_under_under_init=False): 212 | if use_under_under_init: 213 | parts = parts + ['__init__.py'] 214 | else: 215 | parts = parts[:-1] + [parts[-1] + '.py'] 216 | return os.sep.join(parts) 217 | 218 | def relatize(self, path): 219 | return _relpath(path, find_project_root()) 220 | 221 | def absolutify(self, path): 222 | if os.path.isabs(path): 223 | return path 224 | return os.sep.join([find_project_root(), path]) 225 | 226 | 227 | # The actual BaseTestLayout methods that need implementation 228 | def is_test_file(self, some_file): 229 | raise NotImplemented("Implement this method in a subclass.") 230 | 231 | def get_test_file(self, source_file): 232 | raise NotImplemented("Implement this method in a subclass.") 233 | 234 | def get_source_candidates(self, test_file): 235 | raise NotImplemented("Implement this method in a subclass.") 236 | 237 | def get_source_file(self, test_file): 238 | for candidate in self.get_source_candidates(test_file): 239 | if os.path.exists(candidate): 240 | return candidate 241 | raise RuntimeError("Source file not found.") 242 | 243 | 244 | class SideBySideLayout(BaseTestLayout): 245 | def is_test_file(self, some_file): 246 | some_file = self.relatize(some_file) 247 | parts = self.break_down(some_file) 248 | filepart = parts[-1] 249 | return filepart.startswith(self.prefix) 250 | 251 | def get_test_file(self, source_file): 252 | source_file = self.relatize(source_file) 253 | parts = self.break_down(source_file) 254 | parts[-1] = self.prefix + parts[-1] 255 | return self.glue_parts(parts) 256 | 257 | def get_source_candidates(self, test_file): 258 | test_file = self.relatize(test_file) 259 | parts = self.break_down(test_file) 260 | filepart = parts[-1] 261 | if not filepart.startswith(self.prefix): 262 | raise RuntimeError("Not a test file.") 263 | parts[-1] = filepart[len(self.prefix):] 264 | return [self.glue_parts(parts)] 265 | 266 | 267 | class FlatLayout(BaseTestLayout): 268 | def is_test_file(self, some_file): 269 | some_file = self.relatize(some_file) 270 | if not some_file.startswith(self.test_root): 271 | return False 272 | 273 | some_file = _relpath(some_file, self.test_root) 274 | 275 | parts = self.break_down(some_file) 276 | if len(parts) != 1: 277 | return False 278 | return parts[0].startswith(self.prefix) 279 | 280 | def get_test_file(self, source_file): 281 | source_file = self.relatize(source_file) 282 | if not source_file.startswith(self.source_root): 283 | raise RuntimeError("File %s is not under the source root." % source_file) 284 | 285 | source_file = _relpath(source_file, self.source_root) 286 | parts = self.break_down(source_file) 287 | flat_file_name = "_".join(parts) 288 | parts = [self.test_root] + [self.prefix + flat_file_name] 289 | return self.glue_parts(parts) 290 | 291 | def get_source_candidates(self, test_file): 292 | test_file = self.relatize(test_file) 293 | if not test_file.startswith(self.test_root): 294 | raise RuntimeError("File %s is not under the test root." % test_file) 295 | 296 | test_file = _relpath(test_file, self.test_root) 297 | parts = self.break_down(test_file) 298 | if len(parts) != 1: 299 | raise RuntimeError("Flat tests layout does not allow tests to be more than one directory deep.") 300 | file_name = strip_prefix(parts[0], self.prefix) 301 | parts = file_name.split("_") 302 | parts = [self.source_root] + parts 303 | return [self.glue_parts(parts, x) for x in (False, True)] 304 | 305 | 306 | class FollowHierarchyLayout(BaseTestLayout): 307 | def is_test_file(self, some_file): 308 | some_file = self.relatize(some_file) 309 | if not some_file.startswith(self.test_root): 310 | return False 311 | 312 | some_file = _relpath(some_file, self.test_root) 313 | 314 | parts = self.break_down(some_file) 315 | for p in parts: 316 | if not p.startswith(self.prefix): 317 | return False 318 | return True 319 | 320 | def get_test_file(self, source_file): 321 | source_file = self.relatize(source_file) 322 | if not source_file.startswith(self.source_root): 323 | raise RuntimeError("File %s is not under the source root." % source_file) 324 | 325 | source_file = _relpath(source_file, self.source_root) 326 | parts = self.break_down(source_file) 327 | parts = map(lambda p: self.prefix + p, parts) 328 | parts = [self.test_root] + parts 329 | return self.glue_parts(parts) 330 | 331 | def get_source_candidates(self, test_file): 332 | test_file = self.relatize(test_file) 333 | if not test_file.startswith(self.test_root): 334 | raise RuntimeError("File %s is not under the test root." % test_file) 335 | 336 | test_file = _relpath(test_file, self.test_root) 337 | parts = self.break_down(test_file) 338 | parts = [strip_prefix(p, self.prefix) for p in parts] 339 | if self.source_root: 340 | parts = [self.source_root] + parts 341 | result = [self.glue_parts(parts, x) for x in (False, True)] 342 | return result 343 | 344 | 345 | class NoseLayout(BaseTestLayout): 346 | def is_test_file(self, some_file): 347 | some_file = self.relatize(some_file) 348 | if not some_file.startswith(self.test_root): 349 | return False 350 | 351 | some_file = _relpath(some_file, self.test_root) 352 | 353 | parts = self.break_down(some_file) 354 | return parts[-1].startswith(self.prefix) 355 | 356 | def get_test_file(self, source_file): 357 | source_file = self.relatize(source_file) 358 | if not source_file.startswith(self.source_root): 359 | raise RuntimeError("File %s is not under the source root." % source_file) 360 | 361 | source_file = _relpath(source_file, self.source_root) 362 | parts = self.break_down(source_file) 363 | parts[-1] = self.prefix + parts[-1] 364 | parts = [self.test_root] + parts 365 | return self.glue_parts(parts) 366 | 367 | def get_source_candidates(self, test_file): 368 | test_file = self.relatize(test_file) 369 | if not test_file.startswith(self.test_root): 370 | raise RuntimeError("File %s is not under the test root." % test_file) 371 | 372 | test_file = _relpath(test_file, self.test_root) 373 | parts = self.break_down(test_file) 374 | parts = [strip_prefix(p, self.prefix) for p in parts] 375 | if self.source_root: 376 | parts = [self.source_root] + parts 377 | result = [self.glue_parts(parts, x) for x in (False, True)] 378 | return result 379 | 380 | 381 | def get_implementing_class(): 382 | implementations = { 383 | 'flat': FlatLayout, 384 | 'follow-hierarchy': FollowHierarchyLayout, 385 | 'side-by-side': SideBySideLayout, 386 | 'nose': NoseLayout, 387 | } 388 | test_layout = vim.eval('g:PyUnitTestsStructure') 389 | try: 390 | return implementations[test_layout] 391 | except KeyError: 392 | raise RuntimeError('No such test layout: %s' % test_layout) 393 | 394 | 395 | # 396 | # The main functions 397 | # 398 | 399 | def get_test_file_for_source_file(path): 400 | impl = get_implementing_class()() 401 | return impl.get_test_file(path) 402 | 403 | 404 | def find_source_file_for_test_file(path): 405 | impl = get_implementing_class()() 406 | for f in impl.get_source_candidates(path): 407 | if os.path.exists(f): 408 | return f 409 | raise Exception("Source file not found.") 410 | 411 | 412 | def is_test_file(path): 413 | impl = get_implementing_class()() 414 | return impl.is_test_file(path) 415 | 416 | 417 | def _vim_split_cmd(inverted=False): 418 | invert = {'top': 'bottom', 'left': 'right', 419 | 'right': 'left', 'bottom': 'top', 'no': 'no'} 420 | mapping = {'top': 'lefta', 'left': 'vert lefta', 421 | 'right': 'vert rightb', 'bottom': 'rightb', 'no': ''} 422 | splitoff_direction = vim.eval("g:PyUnitTestsSplitWindow") 423 | if inverted: 424 | return mapping[invert[splitoff_direction]] 425 | else: 426 | return mapping[splitoff_direction] 427 | 428 | 429 | def _open_buffer_cmd(path, opposite=False): 430 | splitopts = _vim_split_cmd(opposite) 431 | if not splitopts: 432 | splitcmd = 'edit' 433 | elif int(vim.eval('bufexists("%s")' % path)): 434 | splitcmd = splitopts + ' sbuffer' 435 | else: 436 | splitcmd = splitopts + ' split' 437 | command = "%s %s" % (splitcmd, path) 438 | return command 439 | 440 | 441 | def lcd_to_project_root(path): 442 | vim.command("lcd %s" % find_project_root(path)) 443 | 444 | 445 | def switch_to_test_file_for_source_file(path): 446 | testfile = get_test_file_for_source_file(path) 447 | testdir = os.path.dirname(testfile) 448 | if not os.path.isfile(testfile): 449 | if int(vim.eval('g:PyUnitConfirmTestCreation')): 450 | # Ask the user for confirmation 451 | rel_testfile = _relpath(testfile, find_project_root(path)) 452 | msg = 'confirm("Test file does not exist yet. Create %s now?", "&Yes\n&No")' % rel_testfile 453 | if int(vim.eval(msg)) != 1: 454 | return 455 | 456 | # Create the directory up until the file (if it doesn't exist yet) 457 | if not os.path.exists(testdir): 458 | os.makedirs(testdir) 459 | 460 | vim.command(_open_buffer_cmd(testfile)) 461 | lcd_to_project_root(path) 462 | 463 | 464 | def switch_to_source_file_for_test_file(path): 465 | sourcefile = find_source_file_for_test_file(path) 466 | vim.command(_open_buffer_cmd(sourcefile, opposite=True)) 467 | lcd_to_project_root(path) 468 | 469 | 470 | @bridged 471 | def PyUnitSwitchToCounterpartOfFile(path): 472 | if is_test_file(path): 473 | switch_to_source_file_for_test_file(path) 474 | else: 475 | switch_to_test_file_for_source_file(path) 476 | 477 | 478 | @bridged 479 | def PyUnitRunTestsForFile(path): 480 | if not is_test_file(path): 481 | path = get_test_file_for_source_file(path) 482 | relpath = _relpath(path, '.') 483 | vim.command('call PyUnitRunTestsForTestFile("%s")' % relpath) 484 | 485 | endpython 486 | 487 | fun! PyUnitRunTestsForTestFile(path) " {{{ 488 | silent write 489 | call PyUnitRunNose(a:path) 490 | endf " }}} 491 | 492 | fun! PyUnitRunNose(path) " {{{ 493 | " TODO: fix this hard-coded "nosetests" string! 494 | if !executable("nosetests") 495 | echoerr "File " . "nosetests" . " not found. Please install it first." 496 | return 497 | endif 498 | 499 | set lazyredraw " delay redrawing 500 | cclose " close any existing cwindows 501 | 502 | " store old grep settings (to restore later) 503 | let old_gfm=&grepformat 504 | let old_gp=&grepprg 505 | 506 | " write any changes before continuing 507 | if !&readonly 508 | update 509 | endif 510 | 511 | " perform the grep itself 512 | let &grepformat = "%f:%l: fail: %m,%f:%l: error: %m" 513 | if g:PyUnitSourceRoot != "" 514 | let &grepprg = "PYTHONPATH=".g:PyUnitSourceRoot." ".g:PyUnitCmd 515 | else 516 | let &grepprg = g:PyUnitCmd 517 | endif 518 | execute "silent! grep! ".a:path 519 | 520 | " restore grep settings 521 | let &grepformat=old_gfm 522 | let &grepprg=old_gp 523 | 524 | " open cwindow 525 | let has_errors=getqflist() != [] 526 | if has_errors 527 | " first, open the alternate window, too 528 | call PyUnitSwitchToCounterpart() 529 | execute 'belowright copen' 530 | setlocal wrap 531 | nnoremap c :cclose 532 | nnoremap q :cclose 533 | endif 534 | 535 | set nolazyredraw 536 | redraw! 537 | 538 | if !has_errors 539 | " Show OK status 540 | call s:GreenBar() 541 | echo "" 542 | hi Green ctermfg=green 543 | echohl Green 544 | echon "All tests passed." 545 | echohl 546 | else 547 | call s:RedBar() 548 | echo "" 549 | hi Red ctermfg=red 550 | echohl Red 551 | let numfail = len(getqflist()) 552 | if numfail == 1 553 | echon "1 test failed." 554 | else 555 | echon numfail." tests failed." 556 | endif 557 | endif 558 | endf # }}} 559 | 560 | fun! s:RedBar() " {{{ 561 | hi RedBar ctermfg=white ctermbg=red guibg=red 562 | echohl RedBar 563 | echon repeat(" ", &columns - 1) 564 | echohl 565 | endf " }}} 566 | 567 | fun! s:GreenBar() " {{{ 568 | hi GreenBar ctermfg=white ctermbg=green guibg=green 569 | echohl GreenBar 570 | echon repeat(" ", &columns - 1) 571 | echohl 572 | endf " }}} 573 | 574 | fun! PyUnitSwitchToCounterpart() " {{{ 575 | call PyUnitSwitchToCounterpartOfFile(@%) 576 | endf " }}} 577 | 578 | fun! PyUnitRunTests() " {{{ 579 | call PyUnitRunTestsForFile(@%) 580 | endf " }}} 581 | 582 | fun! PyUnitRunAllTests() " {{{ 583 | silent w 584 | call PyUnitRunNose('') 585 | endf " }}} 586 | 587 | " Keyboard mappings {{{ 588 | 589 | " Add mappings, unless the user didn't want this. 590 | " The default mapping is registered under to by default, unless the user 591 | " remapped it already (or a mapping exists already for ) 592 | if !exists("no_plugin_maps") && !exists("no_pyunit_maps") 593 | if !hasmapto('PyUnitRunTests(') 594 | noremap :call PyUnitRunTests() 595 | noremap! :call PyUnitRunTests() 596 | endif 597 | if !hasmapto('PyUnitRunAllTests(') 598 | noremap :call PyUnitRunAllTests() 599 | noremap! :call PyUnitRunAllTests() 600 | endif 601 | if !hasmapto('PyUnitSwitchToCounterpart(') 602 | noremap :call PyUnitSwitchToCounterpart() 603 | noremap! :call PyUnitSwitchToCounterpart() 604 | endif 605 | endif 606 | 607 | " }}} 608 | -------------------------------------------------------------------------------- /src/base.vim: -------------------------------------------------------------------------------- 1 | " 2 | " Python filetype plugin for unit testing (currently with nose) 3 | " Language: Python (ft=python) 4 | " Maintainer: Vincent Driessen 5 | " Version: Vim 7 (may work with lower Vim versions, but not tested) 6 | " URL: http://github.com/nvie/vim-pyunit 7 | " 8 | " Very inspired by Gary Bernhart's work: 9 | " http://bitbucket.org/garybernhardt/dotfiles/src/tip/.vimrc 10 | " 11 | " Based on Mike Crute's Vim plugin: 12 | " http://code.crute.org/mcrute_dotfiles/file/a19ddffcabe6/.vim/plugin/python_testing.vim 13 | " 14 | 15 | " Only do this when not done yet for this buffer 16 | if exists("g:loaded_python_unittests_ftplugin") 17 | finish 18 | endif 19 | let loaded_python_unittests_ftplugin = 1 20 | 21 | " Configuration of the test tool {{{ 22 | " Set the PyUnitCmd to whatever is your testing tool (default: nosetests) 23 | if !exists("g:PyUnitCmd") 24 | let PyUnitCmd = "nosetests -q --with-machineout" 25 | endif 26 | 27 | " Set PyUnitShowTests to 1 if you want to show the tests (default: 1) 28 | if !exists("g:PyUnitShowTests") " TODO: Use this one! 29 | let PyUnitShowTests = 1 30 | endif 31 | 32 | "}}} 33 | " Configuration for autodetecting project root {{{ 34 | " Configure what files indicate a project root 35 | if !exists("g:ProjRootIndicators") 36 | let ProjRootIndicators = [ ".git", "setup.py", "setup.cfg" ] 37 | endif 38 | 39 | " Scan from the current working directory up until the home dir, instead of 40 | " the filesystem root. This has no effect on projects that reside outside the 41 | " user's home directory. In those cases there will be scanned up until the 42 | " filesystem root directory. 43 | if !exists("g:ProjRootStopAtHomeDir") 44 | let ProjRootStopAtHomeDir = 1 45 | endif 46 | " }}} 47 | " Configuration for tests organisation {{{ 48 | " Prefix used for all path components of the test file 49 | if !exists("g:PyUnitTestPrefix") 50 | " nosetests scans all files/directories starting with "test_", so this is 51 | " a sane default value. There should not be a need to change this if you 52 | " want to use nose. 53 | let PyUnitTestPrefix = "test_" 54 | endif 55 | 56 | " Location where the source files live. When set, this must be the relative 57 | " directory under the project root. For example, if you have your project 58 | " root as follows: 59 | " - foo/ 60 | " - bar/ 61 | " - tests/ 62 | " 63 | " And you want the test structure: 64 | " - tests/test_foo/ 65 | " - tests/test_bar/ 66 | " 67 | " Then you can leave this at the default value (""). 68 | " 69 | " But if you have: 70 | " - src/ 71 | " - foo/ 72 | " - bar/ 73 | " - tests/ 74 | " 75 | " And you DON'T want: 76 | " - tests/test_src/test_foo/ 77 | " - tests/test_src/test_bar/ 78 | " but: 79 | " - tests/test_foo/ 80 | " - tests/test_bar/ 81 | " 82 | " Then you need to set this value to "src" 83 | if !exists("g:PyUnitSourceRoot") 84 | let PyUnitSourceRoot = "" 85 | endif 86 | 87 | " Relative location under the project root where to look for the test files. 88 | if !exists("g:PyUnitTestsRoot") 89 | let PyUnitTestsRoot = "tests" 90 | endif 91 | 92 | " Tests structure can be one of: flat, follow-hierarchy 93 | "let PyUnitTestsStructure = "flat" 94 | if !exists("g:PyUnitTestsStructure") 95 | let PyUnitTestsStructure = "follow-hierarchy" 96 | endif 97 | " }}} 98 | " Configuration for editing preferences {{{ 99 | if !exists("g:PyUnitConfirmTestCreation") 100 | " Set this to 0 if you want to silently create new test files 101 | let PyUnitConfirmTestCreation = 1 102 | endif 103 | if !exists("g:PyUnitTestsSplitWindow") 104 | " Specifies how the test window should open, relative to the source file 105 | " window. Takes one of the following values: top, bottom, left, right 106 | let PyUnitTestsSplitWindow = "right" 107 | endif 108 | " }}} 109 | 110 | python << endpython 111 | __PYTHON_SOURCE__ 112 | endpython 113 | 114 | fun! PyUnitRunTestsForTestFile(path) " {{{ 115 | silent write 116 | call PyUnitRunNose(a:path) 117 | endf " }}} 118 | 119 | fun! PyUnitRunNose(path) " {{{ 120 | " TODO: fix this hard-coded "nosetests" string! 121 | if !executable("nosetests") 122 | echoerr "File " . "nosetests" . " not found. Please install it first." 123 | return 124 | endif 125 | 126 | set lazyredraw " delay redrawing 127 | cclose " close any existing cwindows 128 | 129 | " store old grep settings (to restore later) 130 | let old_gfm=&grepformat 131 | let old_gp=&grepprg 132 | 133 | " write any changes before continuing 134 | if !&readonly 135 | update 136 | endif 137 | 138 | " perform the grep itself 139 | let &grepformat = "%f:%l: fail: %m,%f:%l: error: %m" 140 | if g:PyUnitSourceRoot != "" 141 | let &grepprg = "PYTHONPATH=".g:PyUnitSourceRoot." ".g:PyUnitCmd 142 | else 143 | let &grepprg = g:PyUnitCmd 144 | endif 145 | execute "silent! grep! ".a:path 146 | 147 | " restore grep settings 148 | let &grepformat=old_gfm 149 | let &grepprg=old_gp 150 | 151 | " open cwindow 152 | let has_errors=getqflist() != [] 153 | if has_errors 154 | " first, open the alternate window, too 155 | call PyUnitSwitchToCounterpart() 156 | execute 'belowright copen' 157 | setlocal wrap 158 | nnoremap c :cclose 159 | nnoremap q :cclose 160 | endif 161 | 162 | set nolazyredraw 163 | redraw! 164 | 165 | if !has_errors 166 | " Show OK status 167 | call s:GreenBar() 168 | echo "" 169 | hi Green ctermfg=green 170 | echohl Green 171 | echon "All tests passed." 172 | echohl 173 | else 174 | call s:RedBar() 175 | echo "" 176 | hi Red ctermfg=red 177 | echohl Red 178 | let numfail = len(getqflist()) 179 | if numfail == 1 180 | echon "1 test failed." 181 | else 182 | echon numfail." tests failed." 183 | endif 184 | endif 185 | endf # }}} 186 | 187 | fun! s:RedBar() " {{{ 188 | hi RedBar ctermfg=white ctermbg=red guibg=red 189 | echohl RedBar 190 | echon repeat(" ", &columns - 1) 191 | echohl 192 | endf " }}} 193 | 194 | fun! s:GreenBar() " {{{ 195 | hi GreenBar ctermfg=white ctermbg=green guibg=green 196 | echohl GreenBar 197 | echon repeat(" ", &columns - 1) 198 | echohl 199 | endf " }}} 200 | 201 | fun! PyUnitSwitchToCounterpart() " {{{ 202 | call PyUnitSwitchToCounterpartOfFile(@%) 203 | endf " }}} 204 | 205 | fun! PyUnitRunTests() " {{{ 206 | call PyUnitRunTestsForFile(@%) 207 | endf " }}} 208 | 209 | fun! PyUnitRunAllTests() " {{{ 210 | silent w 211 | call PyUnitRunNose('') 212 | endf " }}} 213 | 214 | " Keyboard mappings {{{ 215 | 216 | " Add mappings, unless the user didn't want this. 217 | " The default mapping is registered under to by default, unless the user 218 | " remapped it already (or a mapping exists already for ) 219 | if !exists("no_plugin_maps") && !exists("no_pyunit_maps") 220 | if !hasmapto('PyUnitRunTests(') 221 | noremap :call PyUnitRunTests() 222 | noremap! :call PyUnitRunTests() 223 | endif 224 | if !hasmapto('PyUnitRunAllTests(') 225 | noremap :call PyUnitRunAllTests() 226 | noremap! :call PyUnitRunAllTests() 227 | endif 228 | if !hasmapto('PyUnitSwitchToCounterpart(') 229 | noremap :call PyUnitSwitchToCounterpart() 230 | noremap! :call PyUnitSwitchToCounterpart() 231 | endif 232 | endif 233 | 234 | " }}} 235 | -------------------------------------------------------------------------------- /src/python_unittests.py: -------------------------------------------------------------------------------- 1 | import vim 2 | import os 3 | import os.path 4 | from vim_bridge import bridged 5 | 6 | 7 | # 8 | # General helper functions 9 | # 10 | 11 | def _relpath(path, start='.', try_stdlib=True): 12 | """Returns the relative version of the path. This is a backport of 13 | Python's stdlib routine os.path.relpath(), which is not yet available in 14 | Python 2.4. 15 | 16 | """ 17 | # Fall back onto stdlib version of it, if available 18 | if try_stdlib: 19 | try: 20 | return os.path.relpath(path, start) 21 | except AttributeError: 22 | # Python versions below 2.6 don't have the relpath function 23 | # It's ok, we fall back onto our own implementation 24 | pass 25 | 26 | fullp = os.path.abspath(path) 27 | fulls = os.path.abspath(start) 28 | matchs = os.path.normpath(start) 29 | if not matchs.endswith(os.sep): 30 | matchs += os.sep 31 | 32 | if fullp == fulls: 33 | return '.' 34 | elif fullp.startswith(matchs): 35 | return fullp[len(matchs):] 36 | else: 37 | # Strip dirs off of fulls until it is a prefix of fullp 38 | path_prefixes = [] 39 | while True: 40 | path_prefixes.append(os.path.pardir) 41 | fulls = os.path.dirname(fulls) 42 | if fullp.startswith(fulls): 43 | break 44 | remainder = fullp[len(fulls):] 45 | if remainder.startswith(os.sep): 46 | remainder = remainder[len(os.sep):] 47 | path_prefix = os.sep.join(path_prefixes) 48 | return os.path.join(path_prefix, remainder) 49 | 50 | 51 | def strip_prefix(s, prefix): 52 | if prefix != "" and s.startswith(prefix): 53 | return s[len(prefix):] 54 | else: 55 | return s 56 | 57 | 58 | def is_home_dir(path): 59 | return os.path.realpath(path) == os.path.expandvars("$HOME") 60 | 61 | 62 | def is_fs_root(path): 63 | return os.path.realpath(path) == "/" or \ 64 | (int(vim.eval("g:ProjRootStopAtHomeDir")) and is_home_dir(path)) 65 | 66 | 67 | def find_project_root(path='.'): 68 | if not os.path.isdir(path): 69 | return find_project_root(os.path.dirname(os.path.realpath(path))) 70 | 71 | indicators = vim.eval("g:ProjRootIndicators") 72 | while not is_fs_root(path): 73 | for i in indicators: 74 | if os.path.exists(os.path.join(path, i)): 75 | return os.path.realpath(path) 76 | path = os.path.join(path, os.path.pardir) 77 | raise Exception("Could not find project root") 78 | 79 | 80 | # 81 | # Classes that implement TestLayouts 82 | # 83 | 84 | class BaseTestLayout(object): 85 | def __init__(self): 86 | self.source_root = vim.eval('g:PyUnitSourceRoot') 87 | self.test_root = vim.eval('g:PyUnitTestsRoot') 88 | self.prefix = vim.eval('g:PyUnitTestPrefix') 89 | 90 | 91 | # Helper methods, to be used in subclasses 92 | def break_down(self, path): 93 | parts = path.split(os.sep) 94 | if len(parts) > 0: 95 | if parts[-1] == '__init__.py': 96 | del parts[-1] 97 | elif parts[-1].endswith(".py"): 98 | parts[-1] = parts[-1][:-len(".py")] 99 | return parts 100 | 101 | def glue_parts(self, parts, use_under_under_init=False): 102 | if use_under_under_init: 103 | parts = parts + ['__init__.py'] 104 | else: 105 | parts = parts[:-1] + [parts[-1] + '.py'] 106 | return os.sep.join(parts) 107 | 108 | def relatize(self, path): 109 | return _relpath(path, find_project_root()) 110 | 111 | def absolutify(self, path): 112 | if os.path.isabs(path): 113 | return path 114 | return os.sep.join([find_project_root(), path]) 115 | 116 | 117 | # The actual BaseTestLayout methods that need implementation 118 | def is_test_file(self, some_file): 119 | raise NotImplemented("Implement this method in a subclass.") 120 | 121 | def get_test_file(self, source_file): 122 | raise NotImplemented("Implement this method in a subclass.") 123 | 124 | def get_source_candidates(self, test_file): 125 | raise NotImplemented("Implement this method in a subclass.") 126 | 127 | def get_source_file(self, test_file): 128 | for candidate in self.get_source_candidates(test_file): 129 | if os.path.exists(candidate): 130 | return candidate 131 | raise RuntimeError("Source file not found.") 132 | 133 | 134 | class SideBySideLayout(BaseTestLayout): 135 | def is_test_file(self, some_file): 136 | some_file = self.relatize(some_file) 137 | parts = self.break_down(some_file) 138 | filepart = parts[-1] 139 | return filepart.startswith(self.prefix) 140 | 141 | def get_test_file(self, source_file): 142 | source_file = self.relatize(source_file) 143 | parts = self.break_down(source_file) 144 | parts[-1] = self.prefix + parts[-1] 145 | return self.glue_parts(parts) 146 | 147 | def get_source_candidates(self, test_file): 148 | test_file = self.relatize(test_file) 149 | parts = self.break_down(test_file) 150 | filepart = parts[-1] 151 | if not filepart.startswith(self.prefix): 152 | raise RuntimeError("Not a test file.") 153 | parts[-1] = filepart[len(self.prefix):] 154 | return [self.glue_parts(parts)] 155 | 156 | 157 | class FlatLayout(BaseTestLayout): 158 | def is_test_file(self, some_file): 159 | some_file = self.relatize(some_file) 160 | if not some_file.startswith(self.test_root): 161 | return False 162 | 163 | some_file = _relpath(some_file, self.test_root) 164 | 165 | parts = self.break_down(some_file) 166 | if len(parts) != 1: 167 | return False 168 | return parts[0].startswith(self.prefix) 169 | 170 | def get_test_file(self, source_file): 171 | source_file = self.relatize(source_file) 172 | if not source_file.startswith(self.source_root): 173 | raise RuntimeError("File %s is not under the source root." % source_file) 174 | 175 | source_file = _relpath(source_file, self.source_root) 176 | parts = self.break_down(source_file) 177 | flat_file_name = "_".join(parts) 178 | parts = [self.test_root] + [self.prefix + flat_file_name] 179 | return self.glue_parts(parts) 180 | 181 | def get_source_candidates(self, test_file): 182 | test_file = self.relatize(test_file) 183 | if not test_file.startswith(self.test_root): 184 | raise RuntimeError("File %s is not under the test root." % test_file) 185 | 186 | test_file = _relpath(test_file, self.test_root) 187 | parts = self.break_down(test_file) 188 | if len(parts) != 1: 189 | raise RuntimeError("Flat tests layout does not allow tests to be more than one directory deep.") 190 | file_name = strip_prefix(parts[0], self.prefix) 191 | parts = file_name.split("_") 192 | parts = [self.source_root] + parts 193 | return [self.glue_parts(parts, x) for x in (False, True)] 194 | 195 | 196 | class FollowHierarchyLayout(BaseTestLayout): 197 | def is_test_file(self, some_file): 198 | some_file = self.relatize(some_file) 199 | if not some_file.startswith(self.test_root): 200 | return False 201 | 202 | some_file = _relpath(some_file, self.test_root) 203 | 204 | parts = self.break_down(some_file) 205 | for p in parts: 206 | if not p.startswith(self.prefix): 207 | return False 208 | return True 209 | 210 | def get_test_file(self, source_file): 211 | source_file = self.relatize(source_file) 212 | if not source_file.startswith(self.source_root): 213 | raise RuntimeError("File %s is not under the source root." % source_file) 214 | 215 | source_file = _relpath(source_file, self.source_root) 216 | parts = self.break_down(source_file) 217 | parts = map(lambda p: self.prefix + p, parts) 218 | parts = [self.test_root] + parts 219 | return self.glue_parts(parts) 220 | 221 | def get_source_candidates(self, test_file): 222 | test_file = self.relatize(test_file) 223 | if not test_file.startswith(self.test_root): 224 | raise RuntimeError("File %s is not under the test root." % test_file) 225 | 226 | test_file = _relpath(test_file, self.test_root) 227 | parts = self.break_down(test_file) 228 | parts = [strip_prefix(p, self.prefix) for p in parts] 229 | if self.source_root: 230 | parts = [self.source_root] + parts 231 | result = [self.glue_parts(parts, x) for x in (False, True)] 232 | return result 233 | 234 | 235 | class NoseLayout(BaseTestLayout): 236 | def is_test_file(self, some_file): 237 | some_file = self.relatize(some_file) 238 | if not some_file.startswith(self.test_root): 239 | return False 240 | 241 | some_file = _relpath(some_file, self.test_root) 242 | 243 | parts = self.break_down(some_file) 244 | return parts[-1].startswith(self.prefix) 245 | 246 | def get_test_file(self, source_file): 247 | source_file = self.relatize(source_file) 248 | if not source_file.startswith(self.source_root): 249 | raise RuntimeError("File %s is not under the source root." % source_file) 250 | 251 | source_file = _relpath(source_file, self.source_root) 252 | parts = self.break_down(source_file) 253 | parts[-1] = self.prefix + parts[-1] 254 | parts = [self.test_root] + parts 255 | return self.glue_parts(parts) 256 | 257 | def get_source_candidates(self, test_file): 258 | test_file = self.relatize(test_file) 259 | if not test_file.startswith(self.test_root): 260 | raise RuntimeError("File %s is not under the test root." % test_file) 261 | 262 | test_file = _relpath(test_file, self.test_root) 263 | parts = self.break_down(test_file) 264 | parts = [strip_prefix(p, self.prefix) for p in parts] 265 | if self.source_root: 266 | parts = [self.source_root] + parts 267 | result = [self.glue_parts(parts, x) for x in (False, True)] 268 | return result 269 | 270 | 271 | def get_implementing_class(): 272 | implementations = { 273 | 'flat': FlatLayout, 274 | 'follow-hierarchy': FollowHierarchyLayout, 275 | 'side-by-side': SideBySideLayout, 276 | 'nose': NoseLayout, 277 | } 278 | test_layout = vim.eval('g:PyUnitTestsStructure') 279 | try: 280 | return implementations[test_layout] 281 | except KeyError: 282 | raise RuntimeError('No such test layout: %s' % test_layout) 283 | 284 | 285 | # 286 | # The main functions 287 | # 288 | 289 | def get_test_file_for_source_file(path): 290 | impl = get_implementing_class()() 291 | return impl.get_test_file(path) 292 | 293 | 294 | def find_source_file_for_test_file(path): 295 | impl = get_implementing_class()() 296 | for f in impl.get_source_candidates(path): 297 | if os.path.exists(f): 298 | return f 299 | raise Exception("Source file not found.") 300 | 301 | 302 | def is_test_file(path): 303 | impl = get_implementing_class()() 304 | return impl.is_test_file(path) 305 | 306 | 307 | def _vim_split_cmd(inverted=False): 308 | invert = {'top': 'bottom', 'left': 'right', 309 | 'right': 'left', 'bottom': 'top', 'no': 'no'} 310 | mapping = {'top': 'lefta', 'left': 'vert lefta', 311 | 'right': 'vert rightb', 'bottom': 'rightb', 'no': ''} 312 | splitoff_direction = vim.eval("g:PyUnitTestsSplitWindow") 313 | if inverted: 314 | return mapping[invert[splitoff_direction]] 315 | else: 316 | return mapping[splitoff_direction] 317 | 318 | 319 | def _open_buffer_cmd(path, opposite=False): 320 | splitopts = _vim_split_cmd(opposite) 321 | if not splitopts: 322 | splitcmd = 'edit' 323 | elif int(vim.eval('bufexists("%s")' % path)): 324 | splitcmd = splitopts + ' sbuffer' 325 | else: 326 | splitcmd = splitopts + ' split' 327 | command = "%s %s" % (splitcmd, path) 328 | return command 329 | 330 | 331 | def lcd_to_project_root(path): 332 | vim.command("lcd %s" % find_project_root(path)) 333 | 334 | 335 | def switch_to_test_file_for_source_file(path): 336 | testfile = get_test_file_for_source_file(path) 337 | testdir = os.path.dirname(testfile) 338 | if not os.path.isfile(testfile): 339 | if int(vim.eval('g:PyUnitConfirmTestCreation')): 340 | # Ask the user for confirmation 341 | rel_testfile = _relpath(testfile, find_project_root(path)) 342 | msg = 'confirm("Test file does not exist yet. Create %s now?", "&Yes\n&No")' % rel_testfile 343 | if int(vim.eval(msg)) != 1: 344 | return 345 | 346 | # Create the directory up until the file (if it doesn't exist yet) 347 | if not os.path.exists(testdir): 348 | os.makedirs(testdir) 349 | 350 | vim.command(_open_buffer_cmd(testfile)) 351 | lcd_to_project_root(path) 352 | 353 | 354 | def switch_to_source_file_for_test_file(path): 355 | sourcefile = find_source_file_for_test_file(path) 356 | vim.command(_open_buffer_cmd(sourcefile, opposite=True)) 357 | lcd_to_project_root(path) 358 | 359 | 360 | @bridged 361 | def PyUnitSwitchToCounterpartOfFile(path): 362 | if is_test_file(path): 363 | switch_to_source_file_for_test_file(path) 364 | else: 365 | switch_to_test_file_for_source_file(path) 366 | 367 | 368 | @bridged 369 | def PyUnitRunTestsForFile(path): 370 | if not is_test_file(path): 371 | path = get_test_file_for_source_file(path) 372 | relpath = _relpath(path, '.') 373 | vim.command('call PyUnitRunTestsForTestFile("%s")' % relpath) 374 | -------------------------------------------------------------------------------- /tests/mocks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nvie/vim-pyunit/800b3c8799b64606614c791980d0ce291de8b2a0/tests/mocks/__init__.py -------------------------------------------------------------------------------- /tests/mocks/vim.py: -------------------------------------------------------------------------------- 1 | from mock import Mock 2 | 3 | eval = Mock() 4 | command = Mock() 5 | -------------------------------------------------------------------------------- /tests/test_python_unittests.py: -------------------------------------------------------------------------------- 1 | # Mock out the vim library 2 | import sys 3 | sys.path = ['tests/mocks'] + sys.path 4 | import vim 5 | 6 | vimvar = {} 7 | 8 | 9 | def fake_eval(x): 10 | global vimvar 11 | return vimvar[x] 12 | 13 | vim.eval = fake_eval 14 | vimvar['foo'] = 'bar' 15 | 16 | # Now start loading normally 17 | import unittest 18 | import os 19 | import python_unittests as mod 20 | 21 | 22 | # Calculate the *real* project root for this test scenario 23 | # I should probably mock this out, but for the current state of affairs, this is 24 | # too much overkill 25 | proj_root = os.getcwd() 26 | currfile = __file__.replace('.pyc', '.py') 27 | 28 | 29 | def setUpVimEnvironment(): 30 | vimvar.clear() 31 | vimvar.update({ 32 | 'g:PyUnitShowTests': '1', 33 | 'g:PyUnitCmd': 'nosetests -q --with-machineout', 34 | 'g:PyUnitTestPrefix': 'test_', 35 | 'g:ProjRootIndicators': ['.git', 'setup.py', 'setup.cfg'], 36 | 'g:ProjRootStopAtHomeDir': '1', 37 | 'g:PyUnitTestsStructure': 'follow-hierarchy', 38 | 'g:PyUnitTestsRoot': 'tests', 39 | 'g:PyUnitSourceRoot': '', 40 | 'g:PyUnitTestsSplitWindow': 'right', 41 | }) 42 | 43 | class FileAwareTestCase(unittest.TestCase): 44 | def assertSameFile(self, x, y): 45 | self.assertEquals(os.path.realpath(x), os.path.realpath(y)) 46 | 47 | 48 | class TestTestLayout(FileAwareTestCase): 49 | def testBreakDownSimple(self): 50 | layout = mod.BaseTestLayout() 51 | self.assertEquals(layout.break_down('foo.py'), ['foo']) 52 | self.assertEquals(layout.break_down('foo/bar.py'), ['foo', 'bar']) 53 | self.assertEquals(layout.break_down('foo/bar/baz.py'), ['foo', 'bar', 'baz']) 54 | 55 | def testBreakDownWithUnderUnderInits(self): 56 | layout = mod.BaseTestLayout() 57 | self.assertEquals(layout.break_down('__init__.py'), []) 58 | self.assertEquals(layout.break_down('foo/__init__.py'), ['foo']) 59 | self.assertEquals(layout.break_down('foo/bar/baz/__init__.py'), ['foo', 'bar', 'baz']) 60 | 61 | def testGlueSimple(self): 62 | layout = mod.BaseTestLayout() 63 | self.assertEquals(layout.glue_parts(['foo']), 'foo.py') 64 | self.assertEquals(layout.glue_parts(['foo', 'bar', 'baz']), 'foo/bar/baz.py') 65 | self.assertRaises(IndexError, layout.glue_parts, []) 66 | 67 | def testGlueWithUnderUnderInits(self): 68 | layout = mod.BaseTestLayout() 69 | self.assertEquals(layout.glue_parts(['foo'], True), 'foo/__init__.py') 70 | self.assertEquals(layout.glue_parts(['foo', 'bar', 'baz'], True), 'foo/bar/baz/__init__.py') 71 | self.assertEquals(layout.glue_parts([], True), '__init__.py') 72 | 73 | def testRelatize(self): 74 | layout = mod.BaseTestLayout() 75 | self.assertEquals(layout.relatize("%s/foo/bar.py" % proj_root), "foo/bar.py") 76 | self.assertEquals(layout.relatize("foo/bar.py"), "foo/bar.py") 77 | 78 | def testAbsolutify(self): 79 | layout = mod.BaseTestLayout() 80 | self.assertEquals(layout.absolutify("foo/bar.py"), "%s/foo/bar.py" % proj_root) 81 | self.assertEquals(layout.absolutify("/tmp/foo/bar.py"), "/tmp/foo/bar.py") 82 | 83 | 84 | class TestSideBySideLayout(FileAwareTestCase): 85 | def setUp(self): 86 | setUpVimEnvironment() 87 | 88 | def testDetectTestFile(self): 89 | vimvar['g:PyUnitSourceRoot'] = 'src' 90 | layout = mod.SideBySideLayout() 91 | self.assertTrue(layout.is_test_file('test_foo.py')) 92 | self.assertTrue(layout.is_test_file('foo/test_bar.py')) 93 | self.assertTrue(layout.is_test_file('tests/foo/test_bar.py')) 94 | self.assertTrue(layout.is_test_file('test_foo/test_bar.py')) 95 | self.assertFalse(layout.is_test_file('foo.py')) 96 | self.assertFalse(layout.is_test_file('src/foo.py')) 97 | self.assertFalse(layout.is_test_file('src/foo/bar.py')) 98 | 99 | def testDetectTestFileWithAlternatePrefix(self): 100 | vimvar['g:PyUnitTestPrefix'] = '_' 101 | vimvar['g:PyUnitSourceRoot'] = 'src' 102 | layout = mod.SideBySideLayout() 103 | self.assertTrue(layout.is_test_file('_foo.py')) 104 | self.assertTrue(layout.is_test_file('foo/_bar.py')) 105 | self.assertTrue(layout.is_test_file('tests/foo/_bar.py')) 106 | self.assertTrue(layout.is_test_file('test_foo/_bar.py')) 107 | self.assertFalse(layout.is_test_file('foo.py')) 108 | self.assertFalse(layout.is_test_file('src/foo.py')) 109 | self.assertFalse(layout.is_test_file('src/foo/bar.py')) 110 | 111 | def testDetectAbsoluteTestFile(self): 112 | vimvar['g:PyUnitTestPrefix'] = '_' 113 | vimvar['g:PyUnitSourceRoot'] = 'src' 114 | absdir = os.path.realpath(proj_root) 115 | layout = mod.SideBySideLayout() 116 | self.assertTrue(layout.is_test_file('%s/_foo.py' % absdir)) 117 | self.assertTrue(layout.is_test_file('%s/foo/_bar.py' % absdir)) 118 | self.assertTrue(layout.is_test_file('%s/tests/foo/_bar.py' % absdir)) 119 | self.assertTrue(layout.is_test_file('%s/test_foo/_bar.py' % absdir)) 120 | self.assertFalse(layout.is_test_file('%s/foo.py' % absdir)) 121 | self.assertFalse(layout.is_test_file('%s/src/foo.py' % absdir)) 122 | self.assertFalse(layout.is_test_file('%s/src/foo/bar.py' % absdir)) 123 | 124 | def testSourceToTest(self): 125 | vimvar['g:PyUnitSourceRoot'] = 'src' 126 | layout = mod.SideBySideLayout() 127 | self.assertEquals(layout.get_test_file('src/foo.py'), 'src/test_foo.py') 128 | self.assertEquals(layout.get_test_file('src/bar.py'), 'src/test_bar.py') 129 | self.assertEquals(layout.get_test_file('src/bar/baz.py'), 'src/bar/test_baz.py') 130 | self.assertEquals(layout.get_test_file('foo.py'), 'test_foo.py') 131 | 132 | def testSourceToTestWithAlternatePrefix(self): 133 | vimvar['g:PyUnitTestPrefix'] = '_' 134 | vimvar['g:PyUnitSourceRoot'] = 'src' 135 | layout = mod.SideBySideLayout() 136 | self.assertEquals(layout.get_test_file('src/foo.py'), 'src/_foo.py') 137 | self.assertEquals(layout.get_test_file('src/bar.py'), 'src/_bar.py') 138 | self.assertEquals(layout.get_test_file('src/bar/baz.py'), 'src/bar/_baz.py') 139 | self.assertEquals(layout.get_test_file('foo.py'), '_foo.py') 140 | 141 | def testTestToSource(self): 142 | vimvar['g:PyUnitSourceRoot'] = 'src' 143 | layout = mod.SideBySideLayout() 144 | self.assertEquals(layout.get_source_candidates('src/test_foo.py'), ['src/foo.py']) 145 | self.assertEquals(layout.get_source_candidates('src/test_bar.py'), ['src/bar.py']) 146 | self.assertEquals(layout.get_source_candidates('src/bar/test_baz.py'), ['src/bar/baz.py']) 147 | self.assertEquals(layout.get_source_candidates('test_foo.py'), ['foo.py']) 148 | 149 | def testTestToSourceWithAlternatePrefix(self): 150 | vimvar['g:PyUnitTestPrefix'] = '_' 151 | vimvar['g:PyUnitSourceRoot'] = 'src' 152 | layout = mod.SideBySideLayout() 153 | self.assertEquals(layout.get_source_candidates('src/_foo.py'), ['src/foo.py']) 154 | self.assertEquals(layout.get_source_candidates('src/_bar.py'), ['src/bar.py']) 155 | self.assertEquals(layout.get_source_candidates('src/bar/_baz.py'), ['src/bar/baz.py']) 156 | self.assertEquals(layout.get_source_candidates('_foo.py'), ['foo.py']) 157 | 158 | 159 | class TestFlatLayout(FileAwareTestCase): 160 | def setUp(self): 161 | setUpVimEnvironment() 162 | 163 | def testDetectTestFile(self): 164 | vimvar['g:PyUnitSourceRoot'] = 'src' 165 | layout = mod.FlatLayout() 166 | self.assertTrue(layout.is_test_file('tests/test_foo.py')) 167 | self.assertTrue(layout.is_test_file('tests/test_foo_bar.py')) 168 | self.assertTrue(layout.is_test_file('tests/test_foo_bar_baz.py')) 169 | self.assertFalse(layout.is_test_file('tests/test_foo/test_bar.py')) 170 | self.assertFalse(layout.is_test_file('foo.py')) 171 | self.assertFalse(layout.is_test_file('test_foo/test_bar.py')) 172 | self.assertFalse(layout.is_test_file('tests/foo/test_bar.py')) 173 | self.assertFalse(layout.is_test_file('src/foo.py')) 174 | self.assertFalse(layout.is_test_file('src/foo/bar.py')) 175 | 176 | def testDetectTestFileWithAlternatePrefix(self): 177 | vimvar['g:PyUnitTestPrefix'] = '_' 178 | vimvar['g:PyUnitSourceRoot'] = 'src' 179 | vimvar['g:PyUnitTestsRoots'] = 'tests' 180 | layout = mod.FlatLayout() 181 | self.assertTrue(layout.is_test_file('tests/_foo.py')) 182 | self.assertTrue(layout.is_test_file('tests/_foo_bar.py')) 183 | self.assertTrue(layout.is_test_file('tests/_foo_bar_baz.py')) 184 | self.assertFalse(layout.is_test_file('tests/_foo/_bar.py')) 185 | self.assertFalse(layout.is_test_file('foo.py')) 186 | self.assertFalse(layout.is_test_file('_foo/_bar.py')) 187 | self.assertFalse(layout.is_test_file('tests/foo/_bar.py')) 188 | self.assertFalse(layout.is_test_file('src/foo.py')) 189 | self.assertFalse(layout.is_test_file('src/foo/bar.py')) 190 | 191 | def testDetectAbsoluteTestFile(self): 192 | vimvar['g:PyUnitTestPrefix'] = '_' 193 | vimvar['g:PyUnitSourceRoot'] = 'src' 194 | vimvar['g:PyUnitTestsRoots'] = 'tests' 195 | absdir = os.path.realpath(proj_root) 196 | layout = mod.FlatLayout() 197 | self.assertTrue(layout.is_test_file('%s/tests/_foo.py' % absdir)) 198 | self.assertTrue(layout.is_test_file('%s/tests/_foo_bar.py' % absdir)) 199 | self.assertFalse(layout.is_test_file('%s/tests/_foo/_bar.py' % absdir)) 200 | self.assertFalse(layout.is_test_file('%s/foo.py' % absdir)) 201 | self.assertFalse(layout.is_test_file('%s/_foo/_bar.py' % absdir)) 202 | self.assertFalse(layout.is_test_file('%s/tests/foo/_bar.py' % absdir)) 203 | self.assertFalse(layout.is_test_file('%s/src/foo.py' % absdir)) 204 | self.assertFalse(layout.is_test_file('%s/src/foo/bar.py' % absdir)) 205 | 206 | def testSourceToTestFailsForNonSourceFiles(self): 207 | vimvar['g:PyUnitSourceRoot'] = 'src' 208 | layout = mod.FlatLayout() 209 | self.assertRaises(RuntimeError, layout.get_test_file, 'nonsrc/foo.py') 210 | self.assertRaises(RuntimeError, layout.get_test_file, 'foo.py') 211 | #self.assertRaises(RuntimeError, layout.get_test_file, 'src.py') 212 | 213 | def testSourceToTest(self): 214 | vimvar['g:PyUnitSourceRoot'] = 'src' 215 | layout = mod.FlatLayout() 216 | self.assertEquals(layout.get_test_file('src/foo.py'), 'tests/test_foo.py') 217 | self.assertEquals(layout.get_test_file('src/bar.py'), 'tests/test_bar.py') 218 | self.assertEquals(layout.get_test_file('src/bar/baz.py'), 'tests/test_bar_baz.py') 219 | 220 | def testSourceToTestWithAlternatePrefix(self): 221 | vimvar['g:PyUnitTestPrefix'] = '_' 222 | vimvar['g:PyUnitSourceRoot'] = 'src' 223 | layout = mod.FlatLayout() 224 | self.assertEquals(layout.get_test_file('src/foo.py'), 'tests/_foo.py') 225 | self.assertEquals(layout.get_test_file('src/bar.py'), 'tests/_bar.py') 226 | self.assertEquals(layout.get_test_file('src/bar/baz.py'), 'tests/_bar_baz.py') 227 | 228 | def testTestToSource(self): 229 | vimvar['g:PyUnitSourceRoot'] = 'src' 230 | layout = mod.FlatLayout() 231 | self.assertEquals(layout.get_source_candidates('tests/test_foo.py'), ['src/foo.py', 'src/foo/__init__.py']) 232 | self.assertEquals(layout.get_source_candidates('tests/test_bar.py'), ['src/bar.py', 'src/bar/__init__.py']) 233 | self.assertEquals(layout.get_source_candidates('tests/test_foo_bar.py'), ['src/foo/bar.py', 'src/foo/bar/__init__.py']) 234 | self.assertRaises(RuntimeError, layout.get_source_candidates, 'tests/foo/test_bar.py') 235 | self.assertRaises(RuntimeError, layout.get_source_candidates, 'test_foo.py') 236 | 237 | def testTestToSourceWithAlternatePrefix(self): 238 | vimvar['g:PyUnitSourceRoot'] = 'src' 239 | vimvar['g:PyUnitTestPrefix'] = '_' 240 | layout = mod.FlatLayout() 241 | self.assertEquals(layout.get_source_candidates('tests/_foo.py'), ['src/foo.py', 'src/foo/__init__.py']) 242 | self.assertEquals(layout.get_source_candidates('tests/_bar.py'), ['src/bar.py', 'src/bar/__init__.py']) 243 | self.assertEquals(layout.get_source_candidates('tests/_foo_bar.py'), ['src/foo/bar.py', 'src/foo/bar/__init__.py']) 244 | 245 | 246 | class TestFollowHierarcyLayout(FileAwareTestCase): 247 | def setUp(self): 248 | setUpVimEnvironment() 249 | 250 | def testDetectTestFile(self): 251 | vimvar['g:PyUnitSourceRoot'] = 'src' 252 | vimvar['g:PyUnitTestsRoot'] = 'tests' 253 | layout = mod.FollowHierarchyLayout() 254 | self.assertTrue(layout.is_test_file('tests/test_foo.py')) 255 | self.assertTrue(layout.is_test_file('tests/test_foo/test_bar.py')) 256 | self.assertFalse(layout.is_test_file('foo.py')) 257 | self.assertFalse(layout.is_test_file('test_foo/test_bar.py')) 258 | self.assertFalse(layout.is_test_file('tests/foo/test_bar.py')) 259 | self.assertFalse(layout.is_test_file('src/foo.py')) 260 | self.assertFalse(layout.is_test_file('src/foo/bar.py')) 261 | 262 | def testDetectTestFileWithAlternatePrefix(self): 263 | vimvar['g:PyUnitTestPrefix'] = '_' 264 | vimvar['g:PyUnitSourceRoot'] = 'src' 265 | vimvar['g:PyUnitTestsRoots'] = 'tests' 266 | layout = mod.FollowHierarchyLayout() 267 | self.assertTrue(layout.is_test_file('tests/_foo.py')) 268 | self.assertTrue(layout.is_test_file('tests/_foo/_bar.py')) 269 | self.assertFalse(layout.is_test_file('foo.py')) 270 | self.assertFalse(layout.is_test_file('_foo/_bar.py')) 271 | self.assertFalse(layout.is_test_file('tests/foo/_bar.py')) 272 | self.assertFalse(layout.is_test_file('src/foo.py')) 273 | self.assertFalse(layout.is_test_file('src/foo/bar.py')) 274 | 275 | def testDetectAbsoluteTestFile(self): 276 | vimvar['g:PyUnitSourceRoot'] = 'src' 277 | vimvar['g:PyUnitTestsRoots'] = 'tests' 278 | absdir = os.path.realpath(proj_root) 279 | layout = mod.FollowHierarchyLayout() 280 | self.assertTrue(layout.is_test_file('%s/tests/test_foo.py' % absdir)) 281 | self.assertTrue(layout.is_test_file('%s/tests/test_foo/test_bar.py' % absdir)) 282 | self.assertFalse(layout.is_test_file('%s/foo.py' % absdir)) 283 | self.assertFalse(layout.is_test_file('%s/test_foo/test_bar.py' % absdir)) 284 | self.assertFalse(layout.is_test_file('%s/tests/foo/test_bar.py' % absdir)) 285 | self.assertFalse(layout.is_test_file('%s/src/foo.py' % absdir)) 286 | self.assertFalse(layout.is_test_file('%s/src/foo/bar.py' % absdir)) 287 | 288 | def testSourceToTestFailsForNonSourceFiles(self): 289 | vimvar['g:PyUnitSourceRoot'] = 'src' 290 | layout = mod.FollowHierarchyLayout() 291 | self.assertRaises(RuntimeError, layout.get_test_file, 'nonsrc/foo.py') 292 | self.assertRaises(RuntimeError, layout.get_test_file, 'foo.py') 293 | #self.assertRaises(RuntimeError, layout.get_test_file, 'src.py') 294 | 295 | def testSourceToTest(self): 296 | vimvar['g:PyUnitSourceRoot'] = 'src' 297 | layout = mod.FollowHierarchyLayout() 298 | self.assertEquals(layout.get_test_file('src/foo.py'), 'tests/test_foo.py') 299 | self.assertEquals(layout.get_test_file('src/bar.py'), 'tests/test_bar.py') 300 | self.assertEquals(layout.get_test_file('src/bar/baz.py'), 'tests/test_bar/test_baz.py') 301 | 302 | def testSourceToTestWithAlternatePrefix(self): 303 | vimvar['g:PyUnitSourceRoot'] = 'src' 304 | vimvar['g:PyUnitTestPrefix'] = '_' 305 | layout = mod.FollowHierarchyLayout() 306 | self.assertEquals(layout.get_test_file('src/foo.py'), 'tests/_foo.py') 307 | self.assertEquals(layout.get_test_file('src/bar.py'), 'tests/_bar.py') 308 | self.assertEquals(layout.get_test_file('src/bar/baz.py'), 'tests/_bar/_baz.py') 309 | 310 | def testTestToSource(self): 311 | vimvar['g:PyUnitSourceRoot'] = '' 312 | layout = mod.FollowHierarchyLayout() 313 | self.assertEquals(layout.get_source_candidates('tests/test_foo.py'), ['foo.py', 'foo/__init__.py']) 314 | self.assertEquals(layout.get_source_candidates('tests/test_bar.py'), ['bar.py', 'bar/__init__.py']) 315 | self.assertEquals(layout.get_source_candidates('tests/bar/test_baz.py'), ['bar/baz.py', 'bar/baz/__init__.py']) 316 | self.assertRaises(RuntimeError, layout.get_source_candidates, 'test_foo.py') 317 | 318 | def testTestToCustomSource(self): 319 | vimvar['g:PyUnitSourceRoot'] = 'src' 320 | layout = mod.FollowHierarchyLayout() 321 | self.assertEquals(layout.get_source_candidates('tests/test_foo.py'), ['src/foo.py', 'src/foo/__init__.py']) 322 | self.assertEquals(layout.get_source_candidates('tests/test_bar.py'), ['src/bar.py', 'src/bar/__init__.py']) 323 | self.assertEquals(layout.get_source_candidates('tests/bar/test_baz.py'), ['src/bar/baz.py', 'src/bar/baz/__init__.py']) 324 | self.assertRaises(RuntimeError, layout.get_source_candidates, 'test_foo.py') 325 | 326 | def testTestToSourceWithAlternatePrefix(self): 327 | vimvar['g:PyUnitSourceRoot'] = 'src' 328 | vimvar['g:PyUnitTestPrefix'] = '_' 329 | layout = mod.FollowHierarchyLayout() 330 | self.assertEquals(layout.get_source_candidates('tests/_foo.py'), ['src/foo.py', 'src/foo/__init__.py']) 331 | self.assertEquals(layout.get_source_candidates('tests/_bar.py'), ['src/bar.py', 'src/bar/__init__.py']) 332 | self.assertEquals(layout.get_source_candidates('tests/bar/_baz.py'), ['src/bar/baz.py', 'src/bar/baz/__init__.py']) 333 | self.assertRaises(RuntimeError, layout.get_source_candidates, '_foo.py') 334 | 335 | 336 | class TestNoseLayout(FileAwareTestCase): 337 | def setUp(self): 338 | setUpVimEnvironment() 339 | 340 | def testDetectTestFile(self): 341 | vimvar['g:PyUnitSourceRoot'] = 'src' 342 | vimvar['g:PyUnitTestsRoot'] = 'tests' 343 | layout = mod.NoseLayout() 344 | self.assertTrue(layout.is_test_file('tests/test_foo.py')) 345 | self.assertTrue(layout.is_test_file('tests/foo/test_bar.py')) 346 | self.assertFalse(layout.is_test_file('foo.py')) 347 | self.assertFalse(layout.is_test_file('test_foo/test_bar.py')) 348 | self.assertFalse(layout.is_test_file('src/foo.py')) 349 | self.assertFalse(layout.is_test_file('src/foo/bar.py')) 350 | 351 | def testDetectTestFileWithAlternatePrefix(self): 352 | vimvar['g:PyUnitTestPrefix'] = '_' 353 | vimvar['g:PyUnitSourceRoot'] = 'src' 354 | vimvar['g:PyUnitTestsRoots'] = 'tests' 355 | layout = mod.NoseLayout() 356 | self.assertTrue(layout.is_test_file('tests/_foo.py')) 357 | self.assertTrue(layout.is_test_file('tests/foo/_bar.py')) 358 | self.assertFalse(layout.is_test_file('foo.py')) 359 | self.assertFalse(layout.is_test_file('_foo/_bar.py')) 360 | self.assertFalse(layout.is_test_file('src/foo.py')) 361 | self.assertFalse(layout.is_test_file('src/foo/bar.py')) 362 | 363 | def testDetectAbsoluteTestFile(self): 364 | vimvar['g:PyUnitSourceRoot'] = 'src' 365 | vimvar['g:PyUnitTestsRoots'] = 'tests' 366 | absdir = os.path.realpath(proj_root) 367 | layout = mod.NoseLayout() 368 | self.assertTrue(layout.is_test_file('%s/tests/test_foo.py' % absdir)) 369 | self.assertTrue(layout.is_test_file('%s/tests/foo/test_bar.py' % absdir)) 370 | self.assertFalse(layout.is_test_file('%s/foo.py' % absdir)) 371 | self.assertFalse(layout.is_test_file('%s/test_foo/test_bar.py' % absdir)) 372 | self.assertFalse(layout.is_test_file('%s/src/foo.py' % absdir)) 373 | self.assertFalse(layout.is_test_file('%s/src/foo/bar.py' % absdir)) 374 | 375 | def testSourceToTestFailsForNonSourceFiles(self): 376 | vimvar['g:PyUnitSourceRoot'] = 'src' 377 | layout = mod.NoseLayout() 378 | self.assertRaises(RuntimeError, layout.get_test_file, 'nonsrc/foo.py') 379 | self.assertRaises(RuntimeError, layout.get_test_file, 'foo.py') 380 | #self.assertRaises(RuntimeError, layout.get_test_file, 'src.py') 381 | 382 | def testSourceToTest(self): 383 | vimvar['g:PyUnitSourceRoot'] = 'src' 384 | layout = mod.NoseLayout() 385 | self.assertEquals(layout.get_test_file('src/foo.py'), 'tests/test_foo.py') 386 | self.assertEquals(layout.get_test_file('src/bar.py'), 'tests/test_bar.py') 387 | self.assertEquals(layout.get_test_file('src/bar/baz.py'), 'tests/bar/test_baz.py') 388 | 389 | def testSourceToTestWithAlternatePrefix(self): 390 | vimvar['g:PyUnitSourceRoot'] = 'src' 391 | vimvar['g:PyUnitTestPrefix'] = '_' 392 | layout = mod.NoseLayout() 393 | self.assertEquals(layout.get_test_file('src/foo.py'), 'tests/_foo.py') 394 | self.assertEquals(layout.get_test_file('src/bar.py'), 'tests/_bar.py') 395 | self.assertEquals(layout.get_test_file('src/bar/baz.py'), 'tests/bar/_baz.py') 396 | 397 | def testTestToSource(self): 398 | vimvar['g:PyUnitSourceRoot'] = '' 399 | layout = mod.NoseLayout() 400 | self.assertEquals(layout.get_source_candidates('tests/test_foo.py'), 401 | ['foo.py', 'foo/__init__.py']) 402 | self.assertEquals(layout.get_source_candidates('tests/test_bar.py'), 403 | ['bar.py', 'bar/__init__.py']) 404 | self.assertEquals(layout.get_source_candidates('tests/bar/test_baz.py'), 405 | ['bar/baz.py', 'bar/baz/__init__.py']) 406 | self.assertRaises(RuntimeError, layout.get_source_candidates, 407 | 'test_foo.py') 408 | 409 | def testTestToCustomSource(self): 410 | vimvar['g:PyUnitSourceRoot'] = 'src' 411 | layout = mod.NoseLayout() 412 | self.assertEquals(layout.get_source_candidates('tests/test_foo.py'), 413 | ['src/foo.py', 'src/foo/__init__.py']) 414 | self.assertEquals(layout.get_source_candidates('tests/test_bar.py'), 415 | ['src/bar.py', 'src/bar/__init__.py']) 416 | self.assertEquals(layout.get_source_candidates('tests/bar/test_baz.py'), 417 | ['src/bar/baz.py', 'src/bar/baz/__init__.py']) 418 | self.assertRaises(RuntimeError, layout.get_source_candidates, 'test_foo.py') 419 | 420 | def testTestToSourceWithAlternatePrefix(self): 421 | vimvar['g:PyUnitSourceRoot'] = 'src' 422 | vimvar['g:PyUnitTestPrefix'] = '_' 423 | layout = mod.NoseLayout() 424 | self.assertEquals(layout.get_source_candidates('tests/_foo.py'), 425 | ['src/foo.py', 'src/foo/__init__.py']) 426 | self.assertEquals(layout.get_source_candidates('tests/_bar.py'), 427 | ['src/bar.py', 'src/bar/__init__.py']) 428 | self.assertEquals(layout.get_source_candidates('tests/bar/_baz.py'), 429 | ['src/bar/baz.py', 'src/bar/baz/__init__.py']) 430 | self.assertRaises(RuntimeError, layout.get_source_candidates, '_foo.py') 431 | 432 | 433 | class TestPlugin(FileAwareTestCase): 434 | def setUp(self): 435 | setUpVimEnvironment() 436 | 437 | def test_patch(self): 438 | self.assertEquals(vim.eval('g:PyUnitTestPrefix'), 'test_') 439 | self.assertEquals(vim.eval('g:PyUnitShowTests'), '1') 440 | 441 | 442 | def test_is_fs_root(self): 443 | self.assertTrue(mod.is_fs_root('/')) 444 | self.assertFalse(mod.is_fs_root('')) 445 | self.assertTrue(mod.is_fs_root(os.path.expandvars('$HOME'))) 446 | vimvar['g:ProjRootStopAtHomeDir'] = '0' 447 | self.assertFalse(mod.is_fs_root(os.path.expandvars('$HOME'))) 448 | 449 | def test_find_project_root(self): 450 | self.assertEquals(mod.find_project_root(currfile), proj_root) 451 | 452 | def test_relpath(self): 453 | # Nice and simple 454 | self.assertEquals( 455 | mod._relpath('/tmp/foo/bar', '/tmp', try_stdlib=False), 456 | 'foo/bar') 457 | self.assertEquals( 458 | mod._relpath('/etc/passwd', '/', try_stdlib=False), 459 | 'etc/passwd') 460 | 461 | # Walking backward 462 | self.assertEquals( 463 | mod._relpath('.././foo/bar.py', '.', try_stdlib=False), 464 | '../foo/bar.py') 465 | self.assertEquals( 466 | mod._relpath('/a/b', '/c', try_stdlib=False), 467 | '../a/b') 468 | self.assertEquals( 469 | mod._relpath('/a/b/c', '/d/e', try_stdlib=False), 470 | '../../a/b/c') 471 | self.assertEquals( 472 | mod._relpath('/', '/a/b', try_stdlib=False), 473 | '../../') 474 | 475 | # Directory signs shouldn't matter 476 | self.assertEquals( 477 | mod._relpath('foo/', 'foo', try_stdlib=False), '.') 478 | self.assertEquals( 479 | mod._relpath('foo', 'foo/', try_stdlib=False), '.') 480 | self.assertEquals( 481 | mod._relpath('foo', 'foo', try_stdlib=False), '.') 482 | 483 | 484 | def test_is_test_file(self): 485 | self.assertTrue(mod.is_test_file('tests/test_foo.py')) 486 | 487 | vimvar['g:PyUnitTestsRoot'] = 'my/test/dir' 488 | self.assertFalse(mod.is_test_file('tests/test_foo.py')) 489 | 490 | def test_get_test_file_for_normal_source_file(self): 491 | self.assertSameFile( 492 | mod.get_test_file_for_source_file('foo/bar/qux.py'), 493 | 'tests/test_foo/test_bar/test_qux.py') 494 | 495 | vimvar['g:PyUnitTestsRoot'] = 'misc/mytests' 496 | self.assertSameFile( 497 | mod.get_test_file_for_source_file('foo/bar/qux.py'), 498 | 'misc/mytests/test_foo/test_bar/test_qux.py') 499 | 500 | vimvar['g:PyUnitTestsStructure'] = 'flat' 501 | self.assertSameFile( 502 | mod.get_test_file_for_source_file('foo/bar/qux.py'), 503 | 'misc/mytests/test_foo_bar_qux.py') 504 | 505 | def test_get_test_file_for_init_source_file(self): 506 | self.assertSameFile( 507 | mod.get_test_file_for_source_file('foo/bar/__init__.py'), 508 | 'tests/test_foo/test_bar.py') 509 | 510 | vimvar['g:PyUnitTestsRoot'] = 'misc/mytests' 511 | self.assertSameFile( 512 | mod.get_test_file_for_source_file('foo/bar/__init__.py'), 513 | 'misc/mytests/test_foo/test_bar.py') 514 | 515 | vimvar['g:PyUnitTestsStructure'] = 'flat' 516 | self.assertSameFile( 517 | mod.get_test_file_for_source_file('foo/bar/__init__.py'), 518 | 'misc/mytests/test_foo_bar.py') 519 | 520 | def test_get_source_file_for_test_file(self): 521 | self.assertRaises(Exception, 522 | mod.find_source_file_for_test_file, currfile) 523 | 524 | vimvar['g:PyUnitSourceRoot'] = 'src' 525 | self.assertSameFile( 526 | mod.find_source_file_for_test_file('tests/test_python_unittests.py'), 527 | os.path.realpath('src/python_unittests.py')) 528 | 529 | 530 | def test_vim_split_cmd(self): 531 | self.assertEquals(mod._vim_split_cmd(), 'vert rightb') 532 | self.assertEquals(mod._vim_split_cmd(True), 'vert lefta') 533 | 534 | vimvar['g:PyUnitTestsSplitWindow'] = 'left' 535 | self.assertEquals(mod._vim_split_cmd(), 'vert lefta') 536 | self.assertEquals(mod._vim_split_cmd(True), 'vert rightb') 537 | 538 | vimvar['g:PyUnitTestsSplitWindow'] = 'top' 539 | self.assertEquals(mod._vim_split_cmd(), 'lefta') 540 | self.assertEquals(mod._vim_split_cmd(True), 'rightb') 541 | 542 | vimvar['g:PyUnitTestsSplitWindow'] = 'bottom' 543 | self.assertEquals(mod._vim_split_cmd(True), 'lefta') 544 | self.assertEquals(mod._vim_split_cmd(), 'rightb') 545 | 546 | def test_open_buffer_cmd(self): 547 | vimvar['bufexists("foo")'] = '1' 548 | self.assertEquals(mod._open_buffer_cmd('foo'), 549 | 'vert rightb sbuffer foo') 550 | self.assertEquals(mod._open_buffer_cmd('foo', opposite=True), 551 | 'vert lefta sbuffer foo') 552 | 553 | vimvar['bufexists("foo")'] = '0' 554 | self.assertEquals(mod._open_buffer_cmd('foo'), 555 | 'vert rightb split foo') 556 | 557 | vimvar['g:PyUnitTestsSplitWindow'] = 'no' 558 | self.assertEquals(mod._open_buffer_cmd('foo'), 559 | 'edit foo') 560 | 561 | 562 | --------------------------------------------------------------------------------