├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── pyvows ├── __init__.py ├── __main__.py ├── async_topic.py ├── cli.py ├── color.py ├── commands.py ├── core.py ├── decorators.py ├── errors.py ├── reporting │ ├── __init__.py │ ├── common.py │ ├── coverage.py │ ├── profile.py │ ├── test.py │ └── xunit.py ├── result.py ├── runner │ ├── __init__.py │ ├── abc.py │ ├── executionplan.py │ ├── gevent.py │ ├── sequential.py │ └── utils.py ├── utils.py └── version.py ├── requirements.txt ├── setup.py ├── tests ├── __init__.py ├── assertions │ ├── __init__.py │ ├── assertion_vows.py │ ├── emptiness_vows.py │ ├── equality_vows.py │ ├── inclusion_vows.py │ ├── length_vows.py │ ├── like_vows.py │ └── types │ │ ├── __init__.py │ │ ├── boolean_vows.py │ │ ├── classes_vows.py │ │ ├── errors_vows.py │ │ ├── file_vows.py │ │ ├── function_vows.py │ │ ├── nullable_vows.py │ │ ├── numeric_vows.py │ │ └── regexp_vows.py ├── async_vows.py ├── bugs │ ├── 64_vows.py │ └── __init__.py ├── captured_output_vows.py ├── cli_vows.py ├── context_inheritance_vows.py ├── division_vows.py ├── docstring_vows.py ├── errors_in_topic_vows.py ├── filter_vows_to_run_vows.py ├── fruits_vows.py ├── generator_vows.py ├── multiple_topic_vows.py ├── no_subcontext_extension_vows.py ├── prune_execution_vows.py ├── reporting │ ├── __init__.py │ ├── error_reporting_vows.py │ ├── reporting_vows.py │ └── xunit_reporter_vows.py ├── setup_teardown_vows.py ├── skipping_vows.py ├── utils_vows.py └── version_vows.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | build 4 | dist 5 | pyVows.egg-info 6 | .tox 7 | 8 | # jetbrains 9 | .idea/ 10 | pyvows.iml 11 | 12 | # rope 13 | .ropeproject/ 14 | 15 | # virtualenv 16 | .Python 17 | bin/ 18 | include/ 19 | lib/ 20 | 21 | /coverage.xml 22 | /pyvows.xml 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: xenial 3 | language: python 4 | python: 5 | - 2.7 6 | - 3.6 7 | - 3.8 8 | script: 9 | - make coverage 10 | after_success: 11 | - coveralls 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This file is an summary of major changes. Check out [the commit history on GitHub](https://github.com/heynemann/pyvows/commits/master) 4 | if you need an exhaustive list of changes. 5 | 6 | 7 | ## 2.0.0 (2013-05-22) 8 | 9 | * Numerous minor improvements: 10 | - code cleanup 11 | - added comments 12 | - reorganization 13 | - renamed variables, methods, etc. to better describe their purpose 14 | 15 | 16 | ### Features 17 | 18 | * Close #86: New option (`--template`): generate new PyVows files 19 | * Close #73: Vows filtering [nathan dotz] 20 | Exclude tests based on a string pattern 21 | 22 | 23 | ### Bugfixes 24 | 25 | * Fix broken test `file_vows.py` 26 | * Fixed bug in `_get_topic_times()` 27 | * Fix #91: `ImportError` exception on Windows [Doug McCall] 28 | Module names were not being properly constructed on Windows; 29 | code used the `/` character instead of `os.path.sep` 30 | * Fixed build-breaking use of set comprehensions… 31 | P.S.: Python 2.6 is dumb 32 | * Fix #81, #48: Now catching internal errors when printing tracebacks 33 | * Fix #6, #78: from Zearin/bug/67_error_on_reorganizing_vows 34 | * Fix #67: A simple `__init__.py` fixed things ;) 35 | * Fixed build error: `aptitude` → `apt-get` (for Travis) 36 | Getting an "`aptitude not found`" error. Odd… Advised by support 37 | from Travis IRC channel to try `apt-get` instead 38 | * `reporting.test`: commented out problematic `except:` block 39 | * Fix #89: Handle generative topics like simple topics if they produce an exception [Telofy] 40 | If an exception occures in a topic, the value remains iterable since 41 | expections, somehow, are iterable. The result is that the entries 42 | (i.e. the error message string) are handed down to the vows where they 43 | cause strange errors that are hard to debug. By resetting 44 | `is_generator` to False, these errors are handed to the vows unchanged, 45 | so that they can actually see the error type and topic.error. Since 46 | most of my topics are not supposed to produce exceptions, it would be 47 | nice to have something like `Vows.NotErrorContext` that instead of just 48 | adding a vow, does not execute any vows in case of an error in the 49 | topic, and also prints a complete traceback of the error. The API 50 | might be a function decorator for `topic()` that sets a function 51 | attribute heeded by the runner 52 | 53 | 54 | ### Refactoring 55 | 56 | * New modules: 57 | - `decorators`: now contains all decorators in PyVows 58 | Decorators existing elsewhere should import them from here 59 | - `errors`: now contains all custom errors in PyVows 60 | - `utils`: now contains all utility functions previously scattered throughout PyVows 61 | * Moved `Vows.NotErrorContext` and `Vows.NotEmptyContext` to 62 | `tests/assertions/assertion_vows.py` (The only place they were ever used…) 63 | * `pyvows.console` → `pyvows.cli` 64 | NEW!!! Module name now 57% shorter! ☺ 65 | 66 | #### Preggy 67 | **Introducing…preggy!** 68 | 69 | Preggy is an assertions library extracted from PyVows! 70 | Preggy now exists as a standalone project. 71 | PyVows now lists preggy as a requirement. 72 | 73 | * Removed: 74 | - `__init__`: Removed `import` for `VowsAssertionError` 75 | - Removed `pyvows.assertions` 76 | - Removed `_AssertionNotFoundError` class 77 | Functionality now covered by `preggy` 78 | - Removed `utils.VowsAssertion` class 79 | Functionality now covered by `preggy` 80 | - Replaced references to `expect`, `decorators._assertion`, and 81 | `decorators._create_assertions` to `preggy` counterparts 82 | * Updated requirements in project metadata: 83 | - Makefile 84 | - REQUIREMENTS 85 | - setup.py 86 | - test_requirements.txt 87 | - tox.ini 88 | * `tests.*`: Updated test string checking to match updated preggy 89 | 90 | 91 | #### pyvows.runner 92 | 93 | Although preggy was the focus of this release, we’ve also began 94 | a refactoring `pyvows.runner`. Work is ongoing... 95 | 96 | * Close #83: Semi-refactoring of `pyvows.runner` 97 | * Made `pool` a class-level attribute 98 | * Extracted `VowsParallelRunner` methods to module functions (for now…) 99 | * Removed superfluous variable in `VowsParallelRunner.run()` 100 | * Organized code in `VowsParallelRunner.run_context_async()` 101 | * Runner: Predetermine vows & contexts 102 | - …only iterate over the vows or contexts (rather than over all context members, 103 | testing whether each is a vow/subcontext) 104 | - …iterate over sets, which (since they’re unordered) iterate much faster than lists 105 | * Runner: determine excluded items *first* 106 | * Runner: Better variable names 107 | * Runner: Precompile regexes. use `set`s (since we don’t care about the order of the patterns) 108 | 109 | 110 | ### Misc 111 | 112 | Tons of tweaks, cleaning, and changes to make the source code more human-friendly 113 | 114 | * `reporting.common`: better checking to format tracebacks 115 | * `Vows` now tracks test files with `Vows.suites` 116 | 117 | * Patch up missing flag [nathan dotz] 118 | * Update tests to reflect upstream changes [nathan dotz] 119 | * Add `prune` function (currently doesn’t do anything) [nathan dotz] 120 | * Make the exclusions regex matches [nathan dotz] 121 | * Update exclusion notes in help [nathan dotz] 122 | * Make test for matching contexts a smidge nicer [nathan dotz] 123 | * Rename to be appropriate to the feature under test [nathan dotz] 124 | * Passing exclusion patterns to test runner [nathan dotz] 125 | 126 | * Close #79: PEP8 compliance 127 | * Bring `docstring_vows.py` up to date with refactorings 128 | * Add tests to improve coverage 129 | * `xunit.py`: Use `with...` to write report file 130 | * Add message to `VowsInternalError` encouraging bug reports 131 | * Add `#FIXME` for a needed comment 132 | * Import `VowsInternalError` for problems at runtime 133 | * Add `VowsInternalError` to `raise` for any illegal behavior internal to PyVows 134 | * Removed redundant `FunctionWrapper` declaration 135 | * Decoupled `result` and `reporter` in `pyvows.cli:run()` 136 | * Clean up call to `async_topic()` 137 | * Travis requirements 138 | * Fix PEP8 issues 139 | * Fix import in `__init__.py` 140 | * Underscore-prefixed `_get_topic_times()` in `VowsResult` 141 | * `Vows.ensure()` → `Vows.run()` 142 | * `Vows.gather()` → `Vows.collect()` 143 | * Moved pseudo-private methods to top (`VowsParallelRunner`) 144 | * Underscored internal methods in `VowsParallelRunner` 145 | * Tidied up `VowsTestReporter` printing 146 | This also required a few edits to the base class `VowsReporter` 147 | This, in turn, required a minor edit to a test 148 | (`tests/bugs/64_vows.py`). (In the test, the lambdas arguments count 149 | must equal the corresponding methods in `VowsReporter`.) 150 | * Don’t `pprint` results 151 | * Expanded docstring of `VowsResult` 152 | * Move class-level attributes to top 153 | * Close #72: `--long_cli_option` → `--long-cli-option` (underscores are annoying) 154 | * Tidied profile results list code 155 | * `README.mkd` → `README.md` 156 | * Replaced uses of `map()` with list comprehensions 157 | * Minor improvements to `VowsReporter.print_traceback()` (in `reporting.common`) 158 | - Renamed variables for readability 159 | - This method was manually indenting its output; changed to use the `indent_msg()` method -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to PyVows 2 | 3 | If you want to contribute to pyvows, please verify your pull request with [flake8](http://pypi.python.org/pypi/flake8/) before sending it. It’s quite simple to use. 4 | 5 | We’re only using one different setting from the default: instead of a maximum line length of 80, we’re using 130. 6 | 7 | Changing flake8 to respect this is as easy as: 8 | 9 | $ cat ~/.config/pep8 10 | 11 | [pep8] 12 | max-line-length = 130 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011, Bernardo Heynemann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include tests *.py 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | vows test: 2 | @env PYTHONPATH=. python pyvows/cli.py --cover --cover-package=pyvows --cover-threshold=80.0 --profile tests/ 3 | 4 | setup: 5 | @pip install --upgrade --editable .\[tests\] 6 | 7 | coverage: 8 | @env PYTHONPATH=. python pyvows/cli.py --cover --cover-package=pyvows --cover-threshold=80.0 --cover-report=coverage.xml -x tests/ 9 | 10 | publish: 11 | python setup.py sdist upload 12 | 13 | .PHONY: vows test setup coverage publish 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://secure.travis-ci.org/heynemann/pyvows.png?branch=master)](http://travis-ci.org/heynemann/pyvows) 2 | [![Coverage Status](https://coveralls.io/repos/heynemann/pyvows/badge.png)](https://coveralls.io/r/heynemann/pyvows) 3 | [![PyPI Version](https://pypip.in/v/pyVows/badge.png)](https://crate.io/packages/pyVows) 4 | [![PyPI Downloads](https://pypip.in/d/pyVows/badge.png)](https://crate.io/packages/pyVows) 5 | 6 | PyVows is the Python version of the [Vows.JS](http://vowsjs.org) testing framework. 7 | 8 | For more info, go to [pyVows website](http://pyvows.org). 9 | 10 | If you are using [tornado](http://www.tornadoweb.org), check out the 11 | [Tornado_pyVows project](https://github.com/rafaelcaricio/tornado_pyvows) 12 | by Rafael Carício. 13 | 14 | ---- 15 | 16 | **Want to contribute?** Awesome! Please be sure to read 17 | [Contributing](./CONTRIBUTING.md) first. 18 | -------------------------------------------------------------------------------- /pyvows/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''PyVows is a Behavior-Driven Development framework for Python. (And and it 3 | is **fast**!) 4 | 5 | --- 6 | 7 | PyVows runs tests asynchronously. This makes tests which target I/O 8 | run much faster, by running them concurrently. A faster test suite gets 9 | run more often, thus improving the feedback cycle. 10 | 11 | PyVows is inspired by Vows, a BDD framework for Node.js. 12 | 13 | ---- 14 | 15 | You typically shouldn't need to import any specific modules from the `pyvows` 16 | package. Normal use is: 17 | 18 | from pyvows import Vows, expect 19 | 20 | ---- 21 | 22 | To learn more, check out: http://pyvows.org 23 | 24 | ''' 25 | 26 | 27 | # pyVows testing engine 28 | # https://github.com/heynemann/pyvows 29 | # 30 | # Licensed under the MIT license: 31 | # http://www.opensource.org/licenses/mit-license 32 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 33 | 34 | # flake8: noqa 35 | 36 | #------------------------------------------------------------------------------------------------- 37 | 38 | try: 39 | from preggy import expect 40 | except: 41 | pass 42 | 43 | try: 44 | from pyvows.core import Vows, expect 45 | except ImportError: 46 | pass 47 | -------------------------------------------------------------------------------- /pyvows/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | '''Allows the use of `python -m pyvows`.''' 4 | 5 | import sys 6 | 7 | from pyvows.cli import main 8 | 9 | #------------------------------------------------------------------------------------------------- 10 | 11 | if __name__ == '__main__': 12 | sys.exit(main()) 13 | -------------------------------------------------------------------------------- /pyvows/async_topic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''Implementation for `Vows.async_topic` decorator. (See `core` 3 | module). 4 | 5 | ''' 6 | 7 | 8 | # pyVows testing engine 9 | # https://github.com/heynemann/pyvows 10 | 11 | # Licensed under the MIT license: 12 | # http://www.opensource.org/licenses/mit-license 13 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 14 | 15 | import sys 16 | 17 | #------------------------------------------------------------------------------------------------- 18 | 19 | 20 | class VowsAsyncTopic(object): 21 | # FIXME: Add Docstring 22 | def __init__(self, func, args, kw): 23 | self.func = func 24 | self.args = args 25 | self.kw = kw 26 | 27 | def __call__(self, callback): 28 | args = (self.args[0], callback,) + self.args[1:] 29 | try: 30 | self.func(*args, **self.kw) 31 | except: 32 | exc_type, exc_value, exc_traceback = sys.exc_info() 33 | callback(exc_type, exc_value, exc_traceback) 34 | 35 | 36 | class VowsAsyncTopicValue(object): 37 | # FIXME: Add Docstring 38 | def __init__(self, args, kw): 39 | self.args = args 40 | self.kw = kw 41 | self.error = None 42 | if len(self.args) >= 1 and isinstance(self.args[0], Exception): 43 | self.error = self.args 44 | 45 | def __getitem__(self, attr): 46 | if type(attr) is int: 47 | return self.args[attr] 48 | 49 | if attr in self.kw: 50 | return self.kw[attr] 51 | 52 | raise AttributeError 53 | 54 | def __getattr__(self, attr): 55 | if attr in self.kw: 56 | return self.kw[attr] 57 | 58 | if hasattr(self, attr): 59 | return self.attr 60 | 61 | raise AttributeError 62 | -------------------------------------------------------------------------------- /pyvows/cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | '''PyVows' main entry point. Contains code for command-line I/O, 4 | running tests, and the almighty `if __name__ == '__main__': main()`. 5 | 6 | ''' 7 | 8 | # pyVows testing engine 9 | # https://github.com/heynemann/pyvows 10 | 11 | # Licensed under the MIT license: 12 | # http://www.opensource.org/licenses/mit-license 13 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 14 | from __future__ import division, print_function 15 | 16 | import argparse 17 | import inspect 18 | import os 19 | from os.path import isfile, split 20 | import sys 21 | import tempfile 22 | 23 | try: 24 | from coverage import coverage 25 | COVERAGE_AVAILABLE = True 26 | except ImportError: 27 | COVERAGE_AVAILABLE = False 28 | 29 | from pyvows.color import yellow, Style, Fore 30 | from pyvows.reporting import VowsDefaultReporter 31 | from pyvows.reporting.xunit import XUnitReporter 32 | from pyvows import version 33 | 34 | #------------------------------------------------------------------------------------------------- 35 | 36 | 37 | class Messages(object): # pragma: no cover 38 | '''A simple container for command-line interface strings.''' 39 | 40 | summary = 'Run PyVows tests.' 41 | 42 | path = 'Directory to look for vows recursively. If a file is passed,' + \ 43 | 'the file will be the target for vows. (default: %(default)r).' 44 | 45 | pattern = 'Pattern of vows files. (default: %(default)r)' 46 | verbosity = 'Verbosity. May be specified many times to increase verbosity (default: -vv)' 47 | cover = 'Show the code coverage of tests. (default: %(default)s)' 48 | cover_package = 'Verify coverage of %(metavar)s. May be specified many times. (default: all packages)' 49 | cover_omit = 'Exclude %(metavar)s from coverage. May be specified many times. (default: no files)' 50 | cover_threshold = 'Coverage below %(metavar)s is considered a failure. (default: %(default)s)' 51 | cover_report = 'Store coverage report as %(metavar)s. (default: %(default)r)' 52 | xunit_output = 'Enable XUnit output. (default: %(default)s)' 53 | xunit_file = 'Store XUnit output as %(metavar)s. (default: %(default)r)' 54 | exclude = 'Exclude tests and contexts that match regex-pattern %(metavar)s [Mutually exclusive with --include]' 55 | include = 'Include only tests and contexts that match regex-pattern %(metavar)s [Mutually exclusive with --exclude]' 56 | profile = 'Prints the 10 slowest topics. (default: %(default)s)' 57 | profile_threshold = 'Tests taking longer than %(metavar)s seconds are considered slow. (default: %(default)s)' 58 | no_color = 'Turn off colorized output. (default: %(default)s)' 59 | progress = 'Show progress ticks during testing. (default: %(default)s)' 60 | template = 'Print a PyVows test file template. (Disables testing)' 61 | capture_output = 'Capture stdout and stderr during test execution (default: %(default)s)' 62 | 63 | 64 | class Parser(argparse.ArgumentParser): 65 | def __init__(self, description=Messages.summary, **kwargs): 66 | super(Parser, self).__init__( 67 | description=description, 68 | **kwargs) 69 | 70 | #Easy underlining, if we ever need it in the future 71 | #uline = lambda text: '\033[4m{0}\033[24m'.format(text) 72 | metavar = lambda metavar: '{0}{metavar}{0}'.format(Style.RESET_ALL, metavar=metavar.upper()) 73 | 74 | self.add_argument('-p', '--pattern', default='*_vows.py', help=Messages.pattern, metavar=metavar('pattern')) 75 | 76 | ### Filtering 77 | self.add_argument('-e', '--exclude', action='append', default=[], help=Messages.exclude, metavar=metavar('exclude')) 78 | self.add_argument('-i', '--include', action='append', default=[], help=Messages.include, metavar=metavar('include')) 79 | 80 | ### Coverage 81 | cover_group = self.add_argument_group('Test Coverage') 82 | cover_group.add_argument('-c', '--cover', action='store_true', default=False, help=Messages.cover) 83 | cover_group.add_argument( 84 | '-l', '--cover-package', action='append', default=[], 85 | help=Messages.cover_package, metavar=metavar('package') 86 | ) 87 | cover_group.add_argument( 88 | '-o', '--cover-omit', action='append', default=[], 89 | help=Messages.cover_omit, metavar=metavar('file') 90 | ) 91 | cover_group.add_argument( 92 | '-t', '--cover-threshold', type=float, default=80.0, 93 | help=Messages.cover_threshold, metavar=metavar('number') 94 | ) 95 | cover_group.add_argument( 96 | '-r', '--cover-report', action='store', default=None, 97 | help=Messages.cover_report, metavar=metavar('file') 98 | ) 99 | 100 | ### XUnit 101 | xunit_group = self.add_argument_group('XUnit') 102 | xunit_group.add_argument('-x', '--xunit-output', action='store_true', default=False, help=Messages.xunit_output) 103 | xunit_group.add_argument( 104 | '-f', '--xunit-file', action='store', default='pyvows.xml', 105 | help=Messages.xunit_file, metavar=metavar('file') 106 | ) 107 | 108 | ### Profiling 109 | profile_group = self.add_argument_group('Profiling') 110 | profile_group.add_argument('--profile', action='store_true', dest='profile', default=False, help=Messages.profile) 111 | profile_group.add_argument( 112 | '--profile-threshold', type=float, default=0.1, 113 | help=Messages.profile_threshold, metavar=metavar('num') 114 | ) 115 | 116 | ### Aux/Unconventional 117 | aux_group = self.add_argument_group('Utility') 118 | aux_group.add_argument('--template', action='store_true', dest='template', default=False, help=Messages.template) 119 | 120 | ### Misc 121 | self.add_argument('--no-color', action='store_true', default=False, help=Messages.no_color) 122 | self.add_argument('--progress', action='store_true', dest='progress', default=False, help=Messages.progress) 123 | self.add_argument('--version', action='version', version='%(prog)s {0}'.format(version.to_str())) 124 | self.add_argument('--capture-output', action='store_true', default=False, help=Messages.capture_output) 125 | self.add_argument('-v', action='append_const', dest='verbosity', const=1, help=Messages.verbosity) 126 | 127 | self.add_argument('path', nargs='?', default=os.curdir, help=Messages.path) 128 | 129 | 130 | def run(path, pattern, verbosity, show_progress, exclusion_patterns=None, inclusion_patterns=None, capture_output=False): 131 | # FIXME: Add Docstring 132 | 133 | # This calls Vows.run(), which then calls VowsRunner.run() 134 | 135 | # needs to be imported here, else the no-color option won't work 136 | from pyvows.core import Vows 137 | 138 | if exclusion_patterns: 139 | Vows.exclude(exclusion_patterns) 140 | if inclusion_patterns: 141 | Vows.include(inclusion_patterns) 142 | 143 | Vows.collect(path, pattern) 144 | 145 | on_success = show_progress and VowsDefaultReporter.on_vow_success or None 146 | on_error = show_progress and VowsDefaultReporter.on_vow_error or None 147 | result = Vows.run(on_success, on_error, capture_output) 148 | 149 | return result 150 | 151 | 152 | def main(): 153 | '''PyVows' runtime implementation. 154 | ''' 155 | # needs to be imported here, else the no-color option won't work 156 | from pyvows.reporting import VowsDefaultReporter 157 | 158 | arguments = Parser().parse_args() 159 | 160 | if arguments.template: 161 | from pyvows.utils import template 162 | template() 163 | sys.exit() # Exit after printing template, since it's 164 | # supposed to be redirected from STDOUT by the user 165 | 166 | path, pattern = arguments.path, arguments.pattern 167 | if path and isfile(path): 168 | path, pattern = split(path) 169 | if not path: 170 | path = os.curdir 171 | 172 | if arguments.no_color: 173 | for color_name, value in inspect.getmembers(Fore): 174 | if not color_name.startswith('_'): 175 | setattr(Fore, color_name, '') 176 | 177 | if arguments.cover and COVERAGE_AVAILABLE: 178 | cov = coverage(source=arguments.cover_package, 179 | omit=arguments.cover_omit) 180 | cov.erase() 181 | cov.start() 182 | 183 | verbosity = len(arguments.verbosity) if arguments.verbosity else 2 184 | result = run( 185 | path, 186 | pattern, 187 | verbosity, 188 | arguments.progress, 189 | exclusion_patterns=arguments.exclude, 190 | inclusion_patterns=arguments.include, 191 | capture_output=arguments.capture_output 192 | ) 193 | reporter = VowsDefaultReporter(result, verbosity) 194 | 195 | # Print test results first 196 | reporter.pretty_print() 197 | 198 | # Print profile if necessary 199 | if arguments.profile: 200 | reporter.print_profile(arguments.profile_threshold) 201 | 202 | # Print coverage if necessary 203 | if result.successful and arguments.cover: 204 | # if coverage was requested, but unavailable, warn the user 205 | if not COVERAGE_AVAILABLE: 206 | print() 207 | print(yellow('WARNING: Cover disabled because coverage could not be found.')) 208 | print(yellow('Make sure it is installed and accessible.')) 209 | print() 210 | 211 | # otherwise, we're good 212 | else: 213 | cov.stop() 214 | xml = '' 215 | 216 | try: 217 | with tempfile.NamedTemporaryFile() as tmp: 218 | cov.xml_report(outfile=tmp.name) 219 | tmp.seek(0) 220 | xml = tmp.read() 221 | except Exception: 222 | err = sys.exc_info()[1] 223 | print("Could not run coverage. Error: %s" % err) 224 | 225 | if xml: 226 | if arguments.cover_report: 227 | with open(arguments.cover_report, 'wb') as report: 228 | report.write(xml) 229 | 230 | arguments.cover_threshold /= 100.0 231 | reporter.print_coverage(xml, arguments.cover_threshold) 232 | 233 | # Write XUnit if necessary 234 | if arguments.xunit_output: 235 | xunit = XUnitReporter(result) 236 | xunit.write_report(arguments.xunit_file) 237 | 238 | sys.exit(result.errored_tests) 239 | 240 | if __name__ == '__main__': 241 | main() 242 | -------------------------------------------------------------------------------- /pyvows/color.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''PyVows' support for color-printing to the terminal. 3 | 4 | Currently, just a thin wrapper around the (3rd-party) `colorama` 5 | module. 6 | 7 | ''' 8 | 9 | # pyvows testing engine 10 | # https://github.com/heynemann/pyvows 11 | 12 | # Licensed under the MIT license: 13 | # http://www.opensource.org/licenses/mit-license 14 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 15 | 16 | try: 17 | from colorama import init, Fore, Style 18 | init(autoreset=True) 19 | except ImportError: 20 | class NoColor(object): 21 | '''When Python can't import `colorama`, this stand-in class prevents 22 | other parts of PyVows from throwing errors when attempting to print 23 | in color. 24 | 25 | ''' 26 | def __getattr__(self, *args, **kwargs): 27 | return '' 28 | 29 | Fore = NoColor() 30 | Style = NoColor() 31 | 32 | #------------------------------------------------------------------------------------------------- 33 | 34 | __all__ = [ 35 | 'Fore', 'Style', 36 | 'BLACK', 'BLUE', 'CYAN', 'GREEN', 'RED', 'YELLOW', 'WHITE', 'RESET', 'RESET_ALL', 37 | 'black', 'blue', 'cyan', 'green', 'red', 'yellow', 'white', 'bold', 'dim' 38 | ] 39 | 40 | 41 | #------------------------------------------------------------------------------------------------- 42 | # Color Constants 43 | #------------------------------------------------------------------------------------------------- 44 | BLACK = Fore.BLACK 45 | BLUE = Fore.BLUE 46 | CYAN = Fore.CYAN 47 | GREEN = Fore.GREEN 48 | RED = Fore.RED 49 | YELLOW = Fore.YELLOW 50 | WHITE = Fore.WHITE 51 | # 52 | BOLD = Style.BRIGHT 53 | DIM = Style.DIM 54 | # 55 | RESET = Fore.RESET 56 | RESET_ALL = Style.RESET_ALL 57 | 58 | #------------------------------------------------------------------------------------------------- 59 | # Functions 60 | #------------------------------------------------------------------------------------------------- 61 | def _colorize(msg, color, reset=True): 62 | reset = RESET if reset else '' 63 | return '{COLOR}{0!s}{RESET}'.format(msg, COLOR=color, RESET=reset) 64 | 65 | 66 | def _bold(msg): 67 | return '{BOLD}{0!s}{RESET_ALL}'.format(msg, BOLD=BOLD, RESET_ALL=RESET_ALL) 68 | 69 | 70 | def _dim(msg): 71 | return '{DIM}{0!s}{RESET_ALL}'.format(msg, DIM=DIM, RESET_ALL=RESET_ALL) 72 | 73 | 74 | black = lambda msg: _colorize(msg, BLACK) 75 | blue = lambda msg: _colorize(msg, BLUE) 76 | cyan = lambda msg: _colorize(msg, CYAN) 77 | green = lambda msg: _colorize(msg, GREEN) 78 | red = lambda msg: _colorize(msg, RED) 79 | yellow = lambda msg: _colorize(msg, YELLOW) 80 | white = lambda msg: _colorize(msg, WHITE) 81 | 82 | bold = lambda msg: _bold(msg) 83 | dim = lambda msg: _dim(msg) 84 | -------------------------------------------------------------------------------- /pyvows/commands.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import distutils.cmd 3 | import distutils.log 4 | 5 | 6 | class VowsCommand(distutils.cmd.Command): 7 | """Custom command to run pyvows vows""" 8 | 9 | description = 'Run pyvows tests' 10 | user_options = [ 11 | ('pyvows-path=', None, 'Directory or file to search for vows.'), 12 | ('pyvows-pattern=', None, 'Pattern for filtering vows files'), 13 | ] 14 | 15 | def initialize_options(self): 16 | """Set default values for options.""" 17 | self.pyvows_pattern = '*_vows.py' 18 | self.pyvows_path = 'tests/' 19 | 20 | def finalize_options(self): 21 | pass 22 | 23 | def run(self): 24 | cmd = 'pyvows -p "{pattern}" {path}'.format( 25 | path=self.pyvows_path, 26 | pattern=self.pyvows_pattern 27 | ) 28 | self.announce('Executing ' + cmd, level=distutils.log.INFO) 29 | subprocess.call(cmd, shell=True) 30 | -------------------------------------------------------------------------------- /pyvows/core.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''This module is the foundation that allows users to write PyVows-style tests. 3 | ''' 4 | 5 | # pyVows testing engine 6 | # https://github.com/heynemann/pyvows 7 | 8 | # Licensed under the MIT license: 9 | # http://www.opensource.org/licenses/mit-license 10 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 11 | 12 | import os 13 | import sys 14 | import warnings 15 | import copy 16 | 17 | import preggy 18 | 19 | from pyvows import utils 20 | from pyvows.decorators import _batch, async_topic, capture_error, skip_if 21 | from pyvows.runner import VowsRunner 22 | from pyvows.runner.executionplan import ExecutionPlanner 23 | 24 | #------------------------------------------------------------------------------------------------- 25 | 26 | expect = preggy.expect 27 | 28 | 29 | class Vows(object): 30 | '''This class contains almost the entire interface for using PyVows. (The 31 | `expect` class usually being the only other necessary import.) 32 | 33 | * Mark test batches with the `Vows.batch` decorator 34 | * Build test hierarchies with classes that extend `Vows.Context` 35 | * For those who need it, topics with asynchronous code can use the 36 | `Vows.async_topic` decorator 37 | 38 | Other attributes and methods here are for PyVows' internal use. They 39 | aren't necessary for writing tests. 40 | 41 | ''' 42 | suites = dict() 43 | exclusion_patterns = set() 44 | inclusion_patterns = set() 45 | 46 | class Context(object): 47 | '''Extend this class to create your test classes. (The convention is to 48 | write `from pyvows import Vows, expect` in your test module, then extend 49 | `Vows.Context` in your test classes. If you really wanted, you could 50 | also import `Context` directly. But don't do that.) 51 | 52 | * `Vows.Context` subclasses expect one method named `topic`. 53 | It should be the first method in any `Vows.Context` subclass, 54 | by convention. 55 | * Sibling `Context`s run in parallel. 56 | * Nested `Context`s run sequentially. 57 | 58 | The `setup` and `teardown` methods aren't typically needed. But 59 | they are available if your test suite has extra pre- and 60 | post-testing work to be done in any given `Context`. 61 | ''' 62 | 63 | ignored_members = set(['topic', 'setup', 'teardown', 'ignore']) 64 | 65 | def __init__(self, parent=None): 66 | self.parent = parent 67 | self.topic_value = None 68 | self.index = -1 69 | self.generated_topic = False 70 | self.ignored_members = copy.copy(self.ignored_members) 71 | 72 | def _get_first_available_topic(self, index=-1): 73 | if self.topic_value: 74 | if index > -1 and isinstance(self.topic_value, (list, set, tuple)): 75 | return self.topic_value[index] 76 | else: 77 | return self.topic_value 78 | elif self.parent: 79 | return self.parent._get_first_available_topic(index) 80 | return None 81 | 82 | def ignore(self, *args): 83 | '''Appends `*args` to `ignored_members`. (Methods listed in 84 | `ignored_members` are considered "not a test method" by PyVows.) 85 | ''' 86 | for arg in args: 87 | self.ignored_members.add(arg) 88 | 89 | def setup(self): 90 | pass 91 | 92 | def teardown(self): 93 | pass 94 | 95 | setup.__doc__ = teardown.__doc__ = \ 96 | '''For use in your PyVows tests. Define in your `Vows.Context` 97 | subclass to define what should happen before that Context's testing begins. 98 | 99 | Remember: 100 | * sibling Contexts are executed in parallel 101 | * nested Contexts are executed sequentially 102 | ''' 103 | 104 | @staticmethod 105 | def assertion(func): 106 | return preggy.assertion(func) 107 | 108 | @staticmethod 109 | def create_assertions(func): 110 | return preggy.create_assertions(func) 111 | 112 | @staticmethod 113 | def async_topic(topic): 114 | return async_topic(topic) 115 | 116 | @staticmethod 117 | def asyncTopic(topic): 118 | # FIXME: Add Comment 119 | warnings.warn('The asyncTopic decorator is deprecated. ' 120 | 'Please use Vows.async_topic instead.', 121 | DeprecationWarning, 122 | stacklevel=2) 123 | return async_topic(topic) 124 | 125 | @staticmethod 126 | def capture_error(topic_func): 127 | return capture_error(topic_func) 128 | 129 | @staticmethod 130 | def skip_if(condition, reason): 131 | return skip_if(condition, reason) 132 | 133 | @staticmethod 134 | def skip(reason): 135 | return skip_if(True, reason) 136 | 137 | @staticmethod 138 | def batch(ctx_class): 139 | '''Class decorator. Use on subclasses of `Vows.Context`. 140 | 141 | Test batches in PyVows are the largest unit of tests. The convention 142 | is to have one test batch per file, and have the batch’s class match 143 | the file name. 144 | 145 | ''' 146 | suite = ctx_class.__module__.replace('.', os.path.sep) 147 | suite = os.path.abspath(suite) 148 | suite += '.py' 149 | if suite not in Vows.suites: 150 | Vows.suites[suite] = set() 151 | Vows.suites[suite].add(ctx_class) 152 | return _batch(ctx_class) 153 | 154 | @classmethod 155 | def collect(cls, path, pattern): 156 | # FIXME: Add Docstring 157 | # 158 | # * Only used in `cli.py` 159 | path = os.path.abspath(path) 160 | sys.path.insert(0, path) 161 | files = utils.locate(pattern, path) 162 | for module_path in files: 163 | module_name = os.path.splitext( 164 | module_path.replace(path, '').replace(os.sep, '.').lstrip('.') 165 | )[0] 166 | __import__(module_name) 167 | 168 | @classmethod 169 | def exclude(cls, test_name_pattern): 170 | cls.exclusion_patterns = test_name_pattern 171 | 172 | @classmethod 173 | def include(cls, test_name_pattern): 174 | cls.inclusion_patterns = test_name_pattern 175 | 176 | @classmethod 177 | def run(cls, on_vow_success, on_vow_error, capture_error=False): 178 | # FIXME: Add Docstring 179 | # 180 | # * Used by `run()` in `cli.py` 181 | # * Please add a useful description if you wrote this! :) 182 | 183 | execution_plan = ExecutionPlanner(cls.suites, set(cls.exclusion_patterns), set(cls.inclusion_patterns)).plan() 184 | 185 | runner = VowsRunner( 186 | cls.suites, 187 | cls.Context, 188 | on_vow_success, 189 | on_vow_error, 190 | execution_plan, 191 | capture_error 192 | ) 193 | return runner.run() 194 | -------------------------------------------------------------------------------- /pyvows/decorators.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''This module is the foundation that allows users to write PyVows-style tests. 3 | ''' 4 | 5 | # pyVows testing engine 6 | # https://github.com/heynemann/pyvows 7 | 8 | # Licensed under the MIT license: 9 | # http://www.opensource.org/licenses/mit-license 10 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 11 | 12 | from functools import wraps 13 | 14 | from pyvows.async_topic import VowsAsyncTopic 15 | from pyvows.runner import SkipTest 16 | 17 | #------------------------------------------------------------------------------------------------- 18 | 19 | 20 | def _batch(klass): 21 | # This is underscored-prefixed because the only intended use (via 22 | # `@Vows.batch`) expands on this core functionality 23 | def klass_name(*args, **kw): 24 | klass(*args, **kw) 25 | return klass_name 26 | 27 | 28 | def async_topic(topic): 29 | '''Topic decorator. Allows PyVows testing of asynchronous topics. 30 | 31 | Use `@Vows.async_topic` on your `topic` method to mark it as 32 | asynchronous. This allows PyVows to test topics which use callbacks 33 | instead of return values. 34 | 35 | ''' 36 | def wrapper(*args, **kw): 37 | return VowsAsyncTopic(topic, args, kw) 38 | wrapper._original = topic 39 | wrapper._wrapper_type = 'async_topic' 40 | wrapper.__name__ = topic.__name__ 41 | return wrapper 42 | 43 | 44 | def capture_error(topic_func): 45 | '''Topic decorator. Allows any errors raised to become the topic value. 46 | 47 | By default, errors raised in topic functions are reported as 48 | errors. But sometimes you want the error to be the topic value, in 49 | which case decorate the topic function with this decorator.''' 50 | def wrapper(*args, **kw): 51 | try: 52 | return topic_func(*args, **kw) 53 | except Exception as e: 54 | return e 55 | wrapper._original = topic_func 56 | wrapper._wrapper_type = 'capture_error' 57 | wrapper.__name__ = topic_func.__name__ 58 | return wrapper 59 | 60 | 61 | def skip_if(condition, reason): 62 | '''Topic or vow or context decorator. Causes a topic or vow to be skipped if `condition` is True 63 | 64 | This is equivilent to `if condition: raise SkipTest(reason)` 65 | ''' 66 | from pyvows.core import Vows 67 | 68 | def real_decorator(topic_or_vow_or_context): 69 | if not condition: 70 | return topic_or_vow_or_context 71 | 72 | if type(topic_or_vow_or_context) == type(Vows.Context): 73 | class klass_wrapper(topic_or_vow_or_context): 74 | def topic(self): 75 | raise SkipTest(reason) 76 | klass_wrapper.__name__ = topic_or_vow_or_context.__name__ 77 | return klass_wrapper 78 | else: 79 | def wrapper(*args, **kwargs): 80 | raise SkipTest(reason) 81 | wrapper.__name__ = topic_or_vow_or_context.__name__ 82 | return wrapper 83 | return real_decorator 84 | 85 | #------------------------------------------------------------------------------------------------- 86 | 87 | 88 | class FunctionWrapper(object): 89 | 90 | '''Function decorator. Simply calls the decorated function when all 91 | the wrapped functions have been called. 92 | 93 | ''' 94 | def __init__(self, func): 95 | self.waiting = 0 96 | self.func = func 97 | 98 | def wrap(self, method): 99 | self.waiting += 1 100 | 101 | @wraps(method) 102 | def wrapper(*args, **kw): 103 | try: 104 | ret = method(*args, **kw) 105 | return ret 106 | finally: 107 | self.waiting -= 1 108 | self() 109 | 110 | wrapper._original = method 111 | return wrapper 112 | 113 | def __call__(self): 114 | if self.waiting == 0: 115 | self.func() 116 | -------------------------------------------------------------------------------- /pyvows/errors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''This module is the foundation that allows users to write PyVows-style tests. 3 | ''' 4 | 5 | # pyVows testing engine 6 | # https://github.com/heynemann/pyvows 7 | 8 | # Licensed under the MIT license: 9 | # http://www.opensource.org/licenses/mit-license 10 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 11 | 12 | 13 | class VowsInternalError(Exception): 14 | '''Raised whenever PyVows internal code does something unexpected.''' 15 | 16 | def __init__(self, *args): 17 | if not isinstance(args[0], str): 18 | raise TypeError('VowsInternalError must be instantiated with a string as the first argument') 19 | if not len(args) >= 2: 20 | raise IndexError('VowsInternalError must receive at least 2 arguments') 21 | self.raw_msg = args[0] 22 | self.args = args[1:] 23 | 24 | def __str__(self): 25 | msg = self.raw_msg.format(*self.args) 26 | msg += ''' 27 | 28 | Help PyVows fix this issue! Tell us what happened: 29 | 30 | https://github.com/heynemann/pyvows/issues/new 31 | 32 | ''' 33 | return msg 34 | -------------------------------------------------------------------------------- /pyvows/reporting/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # pyvows testing engine 4 | # https://github.com/heynemann/pyvows 5 | 6 | # Licensed under the MIT license: 7 | # http://www.opensource.org/licenses/mit-license 8 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 9 | 10 | 11 | from pyvows.reporting.common import VowsReporter 12 | from pyvows.reporting.coverage import VowsCoverageReporter 13 | from pyvows.reporting.profile import VowsProfileReporter 14 | from pyvows.reporting.test import VowsTestReporter 15 | 16 | 17 | __all__ = [ 18 | 'VowsDefaultReporter', 19 | 'VowsTestReporter', 20 | 'VowsProfileReporter', 21 | 'VowsCoverageReporter', 22 | 'VowsReporter', 23 | ] 24 | 25 | 26 | class VowsDefaultReporter(VowsTestReporter, 27 | VowsCoverageReporter, 28 | VowsProfileReporter): 29 | '''The all-in-one reporter used by other parts of PyVows.''' 30 | pass 31 | -------------------------------------------------------------------------------- /pyvows/reporting/common.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''Contains the `VowsDefaultReporter` class, which handles output after tests 3 | have been run. 4 | ''' 5 | 6 | # pyvows testing engine 7 | # https://github.com/heynemann/pyvows 8 | 9 | # Licensed under the MIT license: 10 | # http://www.opensource.org/licenses/mit-license 11 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 12 | from __future__ import division, print_function 13 | 14 | import re 15 | import traceback 16 | import sys 17 | 18 | from pyvows.color import yellow, green, red, bold 19 | 20 | __all__ = [ 21 | 'PROGRESS_SIZE', 22 | 'V_EXTRA_VERBOSE', 23 | 'V_VERBOSE', 24 | 'V_NORMAL', 25 | 'V_SILENT', 26 | 'ensure_encoded', 27 | 'VowsReporter', 28 | ] 29 | 30 | PROGRESS_SIZE = 50 31 | 32 | # verbosity levels 33 | V_EXTRA_VERBOSE = 4 34 | V_VERBOSE = 3 35 | V_NORMAL = 2 36 | V_SILENT = 1 37 | 38 | 39 | def ensure_encoded(thing, encoding='utf-8'): 40 | '''Ensures proper encoding for unicode characters. 41 | 42 | Currently used only for characters `✓` and `✗`. 43 | 44 | ''' 45 | if isinstance(thing, bytes) or not isinstance(thing, str): 46 | return thing 47 | else: 48 | return thing.encode(encoding) 49 | 50 | 51 | class VowsReporter(object): 52 | '''Base class for other Reporters to extend. Contains common attributes 53 | and methods. 54 | ''' 55 | # Should *only* contain attributes and methods that aren't specific 56 | # to a particular type of report. 57 | 58 | HONORED = green('✓') 59 | BROKEN = red('✗') 60 | SKIPPED = '?' 61 | TAB = ' ' 62 | 63 | def __init__(self, result, verbosity): 64 | self.result = result 65 | self.verbosity = verbosity 66 | self.indent = 1 67 | 68 | #------------------------------------------------------------------------- 69 | # String Formatting 70 | #------------------------------------------------------------------------- 71 | def camel_split(self, string): 72 | '''Splits camel-case `string` into separate words. 73 | 74 | Example: 75 | 76 | self.camel_split('SomeCamelCaseString') 77 | 78 | Returns: 79 | 80 | 'Some camel case string' 81 | 82 | ''' 83 | return re.sub('((?=[A-Z][a-z])|(?<=[a-z])(?=[A-Z])|(?=[0-9]\b))', ' ', string).strip() 84 | 85 | def under_split(self, string): 86 | '''Replaces all underscores in `string` with spaces.''' 87 | return ' '.join(string.split('_')) 88 | 89 | def format_traceback(self, traceback_list): 90 | '''Adds the current level of indentation to a traceback (so it matches 91 | the current context's indentation). 92 | 93 | ''' 94 | 95 | # TODO: 96 | # ...Is this a decorator? If so, please add a comment or docstring 97 | # to make it explicit. 98 | def _indent(msg): 99 | if msg.strip().startswith('File'): 100 | return self.indent_msg(msg) 101 | return msg 102 | 103 | tb_list = [_indent(tb) for tb in traceback_list] 104 | return ''.join(tb_list) 105 | 106 | def format_python_constants(self, msg): 107 | '''Fixes capitalization of Python constants. 108 | 109 | Since developers are used to reading `True`, `False`, and `None` 110 | as capitalized words, it makes sense to match that capitalization 111 | in reports. 112 | 113 | ''' 114 | msg = msg.replace('true', 'True') 115 | msg = msg.replace('false', 'False') 116 | msg = msg.replace('none', 'None') 117 | return msg 118 | 119 | def header(self, msg, ruler_character='='): 120 | '''Returns the string `msg` with a text "ruler". Also colorizes as 121 | bright green (when color is available). 122 | 123 | ''' 124 | ruler = ' {0}'.format(len(msg) * ruler_character) 125 | 126 | msg = ' {0}'.format(msg) 127 | msg = '{0}{ruler}{0}{msg}{0}{ruler}{0}'.format( 128 | '\n', 129 | ruler=ruler, 130 | msg=msg) 131 | 132 | msg = green(bold(msg)) 133 | 134 | return msg 135 | 136 | def indent_msg(self, msg, indentation=None): 137 | '''Returns `msg` with the indentation specified by `indentation`. 138 | 139 | ''' 140 | if indentation is not None: 141 | indent = self.TAB * indentation 142 | else: 143 | indent = self.TAB * self.indent 144 | 145 | return '{indent}{msg}'.format( 146 | indent=indent, 147 | msg=msg 148 | ) 149 | 150 | #------------------------------------------------------------------------- 151 | # Printing Methods 152 | #------------------------------------------------------------------------- 153 | def humanized_print(self, msg, indentation=None, file=sys.stdout): 154 | '''Passes `msg` through multiple text filters to make the output 155 | appear more like normal text, then prints it (indented by 156 | `indentation`). 157 | 158 | ''' 159 | msg = self.under_split(msg) 160 | msg = self.camel_split(msg) 161 | msg = msg.replace(' ', ' ') # normalize spaces if inserted by 162 | # both of the above 163 | msg = msg.capitalize() 164 | msg = self.format_python_constants(msg) 165 | 166 | print(self.indent_msg(msg, indentation), file=file) 167 | 168 | def print_traceback(self, err_type, err_obj, err_traceback, file=sys.stdout): 169 | '''Prints a color-formatted traceback with appropriate indentation.''' 170 | if isinstance(err_obj, AssertionError): 171 | error_msg = err_obj 172 | elif isinstance(err_obj, bytes): 173 | error_msg = err_obj.decode('utf8') 174 | else: 175 | error_msg = err_obj 176 | 177 | print(self.indent_msg(red(error_msg)), file=file) 178 | 179 | if self.verbosity >= V_NORMAL: 180 | traceback_msg = traceback.format_exception(err_type, err_obj, err_traceback) 181 | traceback_msg = self.format_traceback(traceback_msg) 182 | traceback_msg = '\n{traceback}'.format(traceback=traceback_msg) 183 | traceback_msg = self.indent_msg(yellow(traceback_msg)) 184 | print(traceback_msg, file=file) 185 | -------------------------------------------------------------------------------- /pyvows/reporting/coverage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''Contains the `VowsDefaultReporter` class, which handles output after tests 3 | have been run. 4 | ''' 5 | 6 | # pyvows testing engine 7 | # https://github.com/heynemann/pyvows 8 | 9 | # Licensed under the MIT license: 10 | # http://www.opensource.org/licenses/mit-license 11 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 12 | from __future__ import division, print_function 13 | 14 | from xml.etree import ElementTree as etree 15 | 16 | from pyvows.color import yellow, blue, dim, white, bold 17 | from pyvows.reporting.common import ( 18 | PROGRESS_SIZE, 19 | VowsReporter,) 20 | 21 | 22 | class VowsCoverageReporter(VowsReporter): 23 | '''A VowsReporter which prints the code coverage of tests.''' 24 | 25 | def get_uncovered_lines(self, uncovered_lines, max_num=3): 26 | '''Searches for untested lines of code. Returns a string 27 | listing the line numbers. 28 | 29 | If the number of uncovered lines is greater than `max_num`, this will 30 | only explicitly list the first `max_num` uncovered lines, followed 31 | by ' and ## more' (where '##' is the total number of additional 32 | uncovered lines. 33 | 34 | ''' 35 | if len(uncovered_lines) > max_num: 36 | template_str = [] 37 | for i in range(max_num): 38 | line_num = uncovered_lines[i] 39 | template_str.append(line_num) 40 | if i is not (max_num - 1): 41 | template_str.append(', ') 42 | 43 | template_str.append( 44 | ', and {num_more_uncovered:d} more'.format( 45 | num_more_uncovered=len(uncovered_lines) - max_num 46 | )) 47 | 48 | return yellow(''.join(template_str)) 49 | 50 | return yellow(', '.join(uncovered_lines)) 51 | 52 | def parse_coverage_xml(self, xml): 53 | '''Reads `xml` for code coverage statistics, and returns the 54 | dict `result`. 55 | ''' 56 | _coverage = lambda x: float(x.attrib['line-rate']) 57 | 58 | result = {} 59 | root = etree.fromstring(xml) 60 | result['overall'] = _coverage(root) 61 | result['classes'] = [] 62 | 63 | for package in root.findall('.//package'): 64 | package_name = package.attrib['name'] 65 | for klass in package.findall('.//class'): 66 | result['classes'].append({ 67 | 'name': '.'.join([package_name, klass.attrib['name']]), 68 | 'line_rate': _coverage(klass), 69 | 'uncovered_lines': [line.attrib['number'] 70 | for line in klass.find('lines') 71 | if line.attrib['hits'] == '0'] 72 | }) 73 | 74 | return result 75 | 76 | #------------------------------------------------------------------------- 77 | # Printing (Coverage) 78 | #------------------------------------------------------------------------- 79 | def print_coverage(self, xml, cover_threshold): 80 | '''Prints code coverage statistics for your tests.''' 81 | print(self.header('Code Coverage')) 82 | 83 | root = self.parse_coverage_xml(xml) 84 | klasses = sorted(root['classes'], key=lambda klass: klass['line_rate']) 85 | max_length = max([len(klass['name']) for klass in root['classes']]) 86 | max_coverage = 0 87 | 88 | for klass in klasses: 89 | coverage = klass['line_rate'] 90 | 91 | if coverage < cover_threshold: 92 | cover_character = VowsReporter.BROKEN 93 | else: 94 | cover_character = VowsReporter.HONORED 95 | 96 | if 100.0 < max_coverage < coverage: 97 | max_coverage = coverage 98 | if max_coverage == 100.0: 99 | print() 100 | 101 | coverage = coverage 102 | progress = int(coverage * PROGRESS_SIZE) 103 | offset = None 104 | 105 | if coverage == 0.000: 106 | offset = 2 107 | elif 0.000 < coverage < 0.1000: 108 | offset = 1 109 | else: 110 | offset = 0 111 | 112 | if coverage == 0.000 and not klass['uncovered_lines']: 113 | continue 114 | 115 | print(self.format_class_coverage( 116 | cover_character=cover_character, 117 | klass=klass['name'], 118 | space1=' ' * (max_length - len(klass['name'])), 119 | progress=progress, 120 | coverage=coverage, 121 | space2=' ' * (PROGRESS_SIZE - progress + offset), 122 | lines=self.get_uncovered_lines(klass['uncovered_lines']), 123 | cover_threshold=cover_threshold)) 124 | 125 | print() 126 | 127 | total_coverage = root['overall'] 128 | cover_character = VowsReporter.HONORED if (total_coverage >= cover_threshold) else VowsReporter.BROKEN 129 | progress = int(total_coverage * PROGRESS_SIZE) 130 | 131 | print(self.format_overall_coverage(cover_character, max_length, progress, total_coverage)) 132 | print() 133 | 134 | def format_class_coverage(self, cover_character, klass, space1, progress, coverage, space2, lines, cover_threshold): 135 | '''Accepts coverage data for a class and returns a formatted string (intended for 136 | humans). 137 | ''' 138 | # FIXME: 139 | # Doesn't this *actually* print coverage for a module, and not a class? 140 | 141 | # preprocess raw data... 142 | klass = klass.lstrip('.') 143 | klass = blue(klass) 144 | 145 | MET_THRESHOLD = coverage >= cover_threshold 146 | 147 | coverage = '{prefix}{coverage:.1%}'.format( 148 | prefix=' ' if (coverage > 0.000) else '', 149 | coverage=coverage 150 | ) 151 | 152 | if MET_THRESHOLD: 153 | coverage = bold(coverage) 154 | 155 | coverage = white(coverage) 156 | 157 | # ...then format 158 | return ' {0} {klass}{space1}\t{progress}{coverage}{space2} {lines}'.format( 159 | # TODO: 160 | # * remove manual spacing, use .format() alignment 161 | cover_character, 162 | klass=klass, 163 | space1=space1, 164 | progress=dim('•' * progress), 165 | coverage=coverage, 166 | space2=space2, 167 | lines=lines 168 | ) 169 | 170 | def format_overall_coverage(self, cover_character, max_length, progress, total_coverage): 171 | '''Accepts overall coverage data and returns a formatted string (intended for 172 | humans). 173 | ''' 174 | 175 | # preprocess raw data 176 | overall = blue('OVERALL') 177 | overall = bold(overall) 178 | space = ' ' * (max_length - len('OVERALL')) 179 | total = '{total_coverage:.1%}'.format(total_coverage=total_coverage) 180 | total = white(bold(total)) 181 | 182 | # then format 183 | return ' {0} {overall}{space}\t{progress} {total}'.format( 184 | cover_character, 185 | overall=overall, 186 | space=space, 187 | progress='•' * progress, 188 | total=total) 189 | -------------------------------------------------------------------------------- /pyvows/reporting/profile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''Contains the `VowsDefaultReporter` class, which handles output after tests 3 | have been run. 4 | ''' 5 | # pyvows testing engine 6 | # https://github.com/heynemann/pyvows 7 | 8 | # Licensed under the MIT license: 9 | # http://www.opensource.org/licenses/mit-license 10 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 11 | from __future__ import division 12 | 13 | import os 14 | 15 | from pyvows.color import yellow, blue, dim, green, white 16 | from pyvows.reporting.common import ( 17 | VowsReporter,) 18 | 19 | 20 | class VowsProfileReporter(VowsReporter): 21 | '''A VowsReporter which prints a profile of the 10 slowest topics.''' 22 | 23 | def print_profile(self, threshold): 24 | '''Prints the 10 slowest topics that took longer than `threshold` 25 | to test. 26 | ''' 27 | 28 | '''Prints the 10 slowest topics that took longer than 29 | `threshold` to test. 30 | 31 | ''' 32 | 33 | MAX_PATH_SIZE = 40 34 | topics = self.result.get_worst_topics(number=10, threshold=threshold) 35 | 36 | if topics: 37 | print(self.header('Slowest Topics')) 38 | 39 | table_header = yellow(' {0}'.format(dim('#'))) 40 | table_header += yellow(' Elapsed Context File Path ') 41 | table_header += yellow(' Context Name') 42 | print(table_header) 43 | 44 | for index, topic in enumerate(topics): 45 | name = self.under_split(topic['context']) 46 | name = self.camel_split(name) 47 | 48 | topic['path'] = os.path.realpath(topic['path']) 49 | topic['path'] = '{0!s}'.format(topic['path']) 50 | topic['path'] = os.path.relpath(topic['path'], os.path.abspath(os.curdir)) 51 | 52 | data = { 53 | 'number': '{number:#2}'.format(number=index + 1), 54 | 'time': '{time:.05f}s'.format(time=topic['elapsed']), 55 | 'path': '{path:<{width}}'.format( 56 | path=topic['path'][-MAX_PATH_SIZE:], 57 | width=MAX_PATH_SIZE), 58 | 'name': '{name}'.format(name=name), 59 | } 60 | 61 | for k, v in data.items(): 62 | if k == 'number': 63 | colorized = blue 64 | if k == 'time': 65 | colorized = green 66 | if k == 'path': 67 | colorized = lambda x: dim(white(x)) 68 | if k == 'name': 69 | colorized = green 70 | 71 | data[k] = colorized(v) 72 | 73 | print( 74 | ' {number} {time}{0}{path}{0}{name}'.format( 75 | 4 * ' ', 76 | **data) 77 | ) 78 | 79 | print() 80 | -------------------------------------------------------------------------------- /pyvows/reporting/test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''Contains the `VowsDefaultReporter` class, which handles output after tests 3 | have been run. 4 | ''' 5 | # pyvows testing engine 6 | # https://github.com/heynemann/pyvows 7 | 8 | # Licensed under the MIT license: 9 | # http://www.opensource.org/licenses/mit-license 10 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 11 | from __future__ import division, print_function 12 | 13 | import sys 14 | try: 15 | from StringIO import StringIO 16 | except: 17 | from io import StringIO 18 | 19 | from pyvows.color import yellow, red, blue 20 | from pyvows.reporting.common import ( 21 | ensure_encoded, 22 | V_EXTRA_VERBOSE, 23 | V_VERBOSE, 24 | VowsReporter,) 25 | from pyvows.result import VowsResult 26 | 27 | 28 | class VowsTestReporter(VowsReporter): 29 | '''A VowsReporter which prints test results.''' 30 | 31 | def __init__(self, result, verbosity): 32 | super(VowsTestReporter, self).__init__(result, verbosity) 33 | 34 | @property 35 | def status_symbol(self): 36 | '''Returns the symbol indicating whether all tests passed.''' 37 | if self.result.successful: 38 | return VowsReporter.HONORED 39 | else: 40 | return VowsReporter.BROKEN 41 | 42 | #------------------------------------------------------------------------- 43 | # Class Methods 44 | #------------------------------------------------------------------------- 45 | @classmethod 46 | def on_vow_success(cls, vow): 47 | # FIXME: Add Docstring / Comment description 48 | # 49 | # * Why is `vow` unused? 50 | sys.stdout.write(VowsReporter.HONORED) 51 | 52 | @classmethod 53 | def on_vow_error(cls, vow): 54 | # FIXME: Add Docstring / Comment description 55 | # 56 | # * Why is `vow` unused? 57 | sys.stdout.write(VowsReporter.BROKEN) 58 | 59 | #------------------------------------------------------------------------- 60 | # Printing Methods 61 | #------------------------------------------------------------------------- 62 | def pretty_print(self, file=sys.stdout): 63 | '''Prints PyVows test results.''' 64 | print(self.header('Vows Results'), file=file) 65 | 66 | if not self.result.contexts: 67 | # FIXME: 68 | # If no vows are found, how could any be broken? 69 | summary = '{indent}{broken} No vows found! » 0 honored • 0 broken • 0 skipped (0.0s)'.format( 70 | indent=self.TAB * self.indent, 71 | broken=VowsReporter.BROKEN) 72 | print(summary, file=file) 73 | return 74 | 75 | if self.verbosity >= V_VERBOSE or self.result.errored_tests: 76 | print(file=file) 77 | 78 | for context in self.result.contexts: 79 | self.print_context(context['name'], context, file=file) 80 | 81 | summary = '{0}{1} OK » {honored:d} honored • {broken:d} broken • {skipped:d} skipped ({time:.6f}s)'.format( 82 | self.TAB * self.indent, 83 | self.status_symbol, 84 | honored=self.result.successful_tests, 85 | broken=self.result.errored_tests, 86 | skipped=self.result.skipped_tests, 87 | time=self.result.elapsed_time 88 | ) 89 | print(summary, file=file) 90 | print(file=file) 91 | 92 | def print_context(self, name, context, file=sys.stdout): 93 | # FIXME: Add Docstring 94 | # 95 | # * Is this only used in certain cases? 96 | # * If so, which? 97 | self.indent += 1 98 | 99 | if (self.verbosity >= V_VERBOSE or not self.result.eval_context(context)): 100 | contextName = StringIO() 101 | self.humanized_print(name, file=contextName) 102 | contextName = contextName.getvalue().replace('\n', '') 103 | if context.get('skip', None): 104 | contextName += ' (SKIPPED: {0})'.format(str(context['skip'])) 105 | print(contextName, file=file) 106 | 107 | def _print_successful_test(): 108 | honored = ensure_encoded(VowsReporter.HONORED) 109 | topic = ensure_encoded(test['topic']) 110 | name = ensure_encoded(test['name']) 111 | 112 | if self.verbosity == V_VERBOSE: 113 | self.humanized_print('{0} {1}'.format(honored, name), file=file) 114 | elif self.verbosity >= V_EXTRA_VERBOSE: 115 | if test['enumerated']: 116 | self.humanized_print('{0} {1} - {2}'.format(honored, topic, name), file=file) 117 | else: 118 | self.humanized_print('{0} {1}'.format(honored, name), file=file) 119 | 120 | def _print_skipped_test(): 121 | if self.verbosity >= V_VERBOSE: 122 | message = StringIO() 123 | self.humanized_print('{0} {1}'.format(VowsReporter.SKIPPED, test['name']), file=message) 124 | message = message.getvalue().replace('\n', '') 125 | if test['skip'] != context['skip']: 126 | message = '{0} (SKIPPED: {1})'.format(message, str(test['skip'])) 127 | print(message, file=file) 128 | 129 | def _print_failed_test(): 130 | ctx = test['context_instance'] 131 | 132 | def _print_traceback(): 133 | self.indent += 2 134 | 135 | ### NOTE: 136 | ### Commented out try/except; potential debugging hinderance 137 | 138 | #try: 139 | 140 | traceback_args = (test['error']['type'], 141 | test['error']['value'], 142 | test['error']['traceback']) 143 | self.print_traceback(*traceback_args, file=file) 144 | 145 | # except Exception: 146 | # # should never occur! 147 | # err_msg = '''Unexpected error in PyVows! 148 | # PyVows error occurred in: ({0!s}) 149 | # Context was: {1!r} 150 | # 151 | # ''' 152 | # # from os.path import abspath 153 | # raise VowsInternalError(err_msg, 'pyvows.reporting.test', ctx) 154 | 155 | # print file and line number 156 | if 'file' in test: 157 | file_msg = 'found in {test[file]} at line {test[lineno]}'.format(test=test) 158 | print('\n', self.indent_msg(red(file_msg)), '\n', file=file) 159 | 160 | self.indent -= 2 161 | 162 | self.humanized_print('{0} {test}'.format(VowsReporter.BROKEN, test=test['name']), file=file) 163 | 164 | # print generated topic (if applicable) 165 | if ctx.generated_topic: 166 | value = yellow(test['topic']) 167 | self.humanized_print('', file=file) 168 | self.humanized_print('\tTopic value:', file=file) 169 | self.humanized_print('\t{value}'.format(value=value), file=file) 170 | self.humanized_print('\n' * 2, file=file) 171 | 172 | # print traceback 173 | _print_traceback() 174 | 175 | # Show any error raised by the setup, topic or teardown functions 176 | if context.get('error', None): 177 | e = context['error'] 178 | print('\n', self.indent_msg(blue("Error in {0!s}:".format(e.source))), file=file) 179 | self.print_traceback(*e.exc_info, file=file) 180 | 181 | else: 182 | for test in context['tests']: 183 | if VowsResult.test_is_successful(test): 184 | _print_successful_test() 185 | elif test['skip']: 186 | _print_skipped_test() 187 | else: 188 | _print_failed_test() 189 | 190 | # I hereby (re)curse you...! 191 | for context in context['contexts']: 192 | self.print_context(context['name'], context, file=file) 193 | 194 | self.indent -= 1 195 | -------------------------------------------------------------------------------- /pyvows/reporting/xunit.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''Provides the `XUnitReporter` class, which creates XML reports after testing. 3 | ''' 4 | 5 | 6 | # pyVows testing engine 7 | # https://github.com/{heynemann,truemped}/pyvows 8 | 9 | # Licensed under the MIT license: 10 | # http://www.opensource.org/licenses/mit-license 11 | # Copyright (c) 2011 Daniel Truemper truemped@googlemail.com 12 | import codecs 13 | from datetime import datetime 14 | import socket 15 | import traceback 16 | from xml.dom.minidom import Document 17 | import re 18 | 19 | INVALID_CHARACTERS = re.compile(r"[\000-\010\013\014\016-\037]") 20 | INVALID_CHARACTERS = re.compile(r"[\000]") 21 | 22 | 23 | class XUnitReporter(object): 24 | '''Turns `VowsResult` objects into XUnit-style reports.''' 25 | 26 | def __init__(self, result): 27 | self.result_summary = self.summarize_results(result) 28 | 29 | def write_report(self, filename, encoding=None): 30 | # FIXME: Add Docstring 31 | with codecs.open(filename, 'w', encoding, 'replace') as output_file: 32 | output_file.write(self.to_xml(encoding)) 33 | 34 | def to_xml(self, encoding='utf-8'): 35 | # FIXME: Add Docstring 36 | document = self.create_report_document() 37 | return document.toxml(encoding=encoding) 38 | 39 | def summarize_results(self, result): 40 | # FIXME: Add Docstring 41 | result_summary = { 42 | 'total': result.total_test_count, 43 | 'errors': 0, 44 | 'skip': result.skipped_tests, 45 | 'failures': result.errored_tests, 46 | 'ts': datetime.now().strftime("%Y-%m-%dT%H:%M:%S"), 47 | 'hostname': socket.gethostname(), 48 | 'elapsed': result.elapsed_time, 49 | 'contexts': result.contexts 50 | } 51 | return result_summary 52 | 53 | def create_report_document(self): 54 | # FIXME: Add Docstring 55 | result_summary = self.result_summary 56 | 57 | document = Document() 58 | testsuite_node = document.createElement('testsuite') 59 | testsuite_node.setAttribute('name', 'pyvows') 60 | testsuite_node.setAttribute('tests', str(result_summary['total'])) 61 | testsuite_node.setAttribute('errors', str(result_summary['errors'])) 62 | testsuite_node.setAttribute('failures', str(result_summary['failures'])) 63 | testsuite_node.setAttribute('skip', str(result_summary['skip'])) 64 | testsuite_node.setAttribute('timestamp', str(result_summary['ts'])) 65 | testsuite_node.setAttribute('hostname', str(result_summary['hostname'])) 66 | testsuite_node.setAttribute('time', '{elapsed:.3f}'.format(elapsed=result_summary['elapsed'])) 67 | 68 | document.appendChild(testsuite_node) 69 | 70 | for context in result_summary['contexts']: 71 | self.create_test_case_elements(document, testsuite_node, context) 72 | 73 | return document 74 | 75 | def _safe_cdata(self, str): 76 | return INVALID_CHARACTERS.sub('', str) 77 | 78 | def create_test_case_elements(self, document, parent_node, context): 79 | # FIXME: Add Docstring 80 | 81 | topic_node = document.createElement('testcase') 82 | topic_node.setAttribute('classname', context['name']) 83 | topic_node.setAttribute('name', 'topic') 84 | topic_node.setAttribute('time', '0.0') 85 | stdOutNode = document.createElement('system-out') 86 | stdOutText = document.createCDATASection(self._safe_cdata(context['stdout'])) 87 | stdOutNode.appendChild(stdOutText) 88 | stdErrNode = document.createElement('system-err') 89 | stdErrText = document.createCDATASection(self._safe_cdata(context['stderr'])) 90 | stdErrNode.appendChild(stdErrText) 91 | 92 | topic_node.appendChild(stdOutNode) 93 | topic_node.appendChild(stdErrNode) 94 | if context.get('error', None): 95 | e = context['error'] 96 | error_msg = 'Error in {0!s}: {1!s}'.format(e.source, e.exc_info[1]) 97 | error_tb = traceback.format_exception(*e.exc_info) 98 | 99 | failure_node = document.createElement('failure') 100 | failure_node.setAttribute('type', e.exc_info[0].__name__) 101 | failure_node.setAttribute('message', error_msg) 102 | failure_text = document.createTextNode(''.join(error_tb)) 103 | failure_node.appendChild(failure_text) 104 | topic_node.appendChild(failure_node) 105 | if context.get('skip', None): 106 | skip_node = document.createElement('skipped') 107 | skip_node.setAttribute('message', str(context['skip'])) 108 | topic_node.appendChild(skip_node) 109 | 110 | parent_node.appendChild(topic_node) 111 | 112 | for test in context['tests']: 113 | test_stats = { 114 | 'context': context['name'], 115 | 'name': test['name'], 116 | 'taken': 0.0 117 | } 118 | 119 | testcase_node = document.createElement('testcase') 120 | testcase_node.setAttribute('classname', str(test_stats['context'])) 121 | testcase_node.setAttribute('name', str(test_stats['name'])) 122 | testcase_node.setAttribute('time', '{time:.3f}'.format(time=test_stats['taken'])) 123 | 124 | stdOutNode = document.createElement('system-out') 125 | stdOutText = document.createCDATASection(self._safe_cdata(test['stdout'])) 126 | stdOutNode.appendChild(stdOutText) 127 | stdErrNode = document.createElement('system-err') 128 | stdErrText = document.createCDATASection(self._safe_cdata(test['stderr'])) 129 | stdErrNode.appendChild(stdErrText) 130 | 131 | testcase_node.appendChild(stdOutNode) 132 | testcase_node.appendChild(stdErrNode) 133 | parent_node.appendChild(testcase_node) 134 | 135 | if test.get('error', None): 136 | error = test['error'] 137 | error_msg = traceback.format_exception( 138 | error['type'], 139 | error['value'], 140 | error['traceback'] 141 | ) 142 | 143 | error_data = { 144 | 'errtype': error['type'].__name__, 145 | 'msg': error['value'], 146 | 'tb': ''.join(error_msg) 147 | } 148 | 149 | failure_node = document.createElement('failure') 150 | failure_node.setAttribute('type', str(error_data['errtype'])) 151 | failure_node.setAttribute('message', str(error_data['msg'])) 152 | failure_text = document.createTextNode(str(error_data['tb'])) 153 | failure_node.appendChild(failure_text) 154 | testcase_node.appendChild(failure_node) 155 | if test.get('skip', None): 156 | skip_node = document.createElement('skipped') 157 | skip_node.setAttribute('message', str(test['skip'])) 158 | testcase_node.appendChild(skip_node) 159 | 160 | for ctx in context['contexts']: 161 | self.create_test_case_elements(document, parent_node, ctx) 162 | -------------------------------------------------------------------------------- /pyvows/result.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''Contains `VowsResult` class, which collects the results of 3 | each vow. 4 | 5 | ''' 6 | 7 | # pyvows testing engine 8 | # https://github.com/heynemann/pyvows 9 | 10 | # Licensed under the MIT license: 11 | # http://www.opensource.org/licenses/mit-license 12 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 13 | 14 | #------------------------------------------------------------------------------- 15 | 16 | 17 | class VowsResult(object): 18 | '''Collects success/failure/total statistics (as well as elapsed 19 | time) for the outcomes of tests. 20 | 21 | Only one instance of this class is created when PyVows is run. 22 | 23 | ''' 24 | 25 | def __init__(self): 26 | self.contexts = [] 27 | self.elapsed_time = 0.0 28 | 29 | def _count_contexts(self, contexts=None, count_func=lambda context: 1): 30 | '''Used interally for class properties 31 | `total_test_count`, `successful_tests`, and `errored_tests`. 32 | 33 | ''' 34 | # TODO 35 | # Reevaluate whether `count_func` should have a default value 36 | # (AFAICT the default is never used. It makes more sense 37 | # to me if it had no default, or defaulted to `None`. 38 | context_count = 0 39 | 40 | for context in contexts: 41 | context_count += count_func(context) 42 | context_count += self._count_contexts( 43 | contexts=context['contexts'], 44 | count_func=count_func 45 | ) 46 | 47 | return context_count 48 | 49 | def _get_topic_times(self, contexts=None): 50 | '''Returns a dict describing how long testing took for 51 | each topic in `contexts`. 52 | 53 | ''' 54 | topic_times = [] 55 | 56 | if contexts is None: 57 | contexts = self.contexts 58 | 59 | for context in contexts: 60 | topic_times.append({ 61 | 'context': context['name'], 62 | 'path': context['filename'], 63 | 'elapsed': context['topic_elapsed'] 64 | }) 65 | ctx_topic_times = self._get_topic_times(context['contexts']) 66 | topic_times.extend(ctx_topic_times) 67 | 68 | return topic_times 69 | 70 | @property 71 | def successful(self): 72 | '''Returns a boolean, indicating whether the current 73 | `VowsResult` was 100% successful. 74 | 75 | ''' 76 | return self.successful_tests + self.skipped_tests == self.total_test_count 77 | 78 | @property 79 | def total_test_count(self): 80 | '''Returns the total number of tests.''' 81 | return self.successful_tests + self.errored_tests + self.skipped_tests 82 | 83 | @staticmethod 84 | def test_is_successful(test): 85 | return not (test['error'] or test['skip']) 86 | 87 | @property 88 | def successful_tests(self): 89 | '''Returns the number of tests that passed.''' 90 | return self._count_contexts( 91 | contexts=self.contexts, 92 | count_func=lambda context: ( 93 | len([t for t in context['tests'] if self.test_is_successful(t)]) 94 | + (0 if (context['error'] or context['skip']) else 1) 95 | ) 96 | ) 97 | 98 | @property 99 | def errored_tests(self): 100 | '''Returns the number of tests that failed.''' 101 | return self._count_contexts( 102 | contexts=self.contexts, 103 | count_func=lambda context: ( 104 | len([t for t in context['tests'] if t['error']]) 105 | + (1 if context['error'] else 0) 106 | ) 107 | ) 108 | 109 | @property 110 | def skipped_tests(self): 111 | '''Returns the number of tests that were skipped''' 112 | return self._count_contexts( 113 | contexts=self.contexts, 114 | count_func=lambda context: ( 115 | len([t for t in context['tests'] if t['skip']]) 116 | + (1 if context['skip'] else 0) 117 | ) 118 | ) 119 | 120 | def eval_context(self, context): 121 | '''Returns a boolean indicating whether `context` tested 122 | successfully. 123 | 124 | ''' 125 | succeeded = True 126 | 127 | # Success only if there wasn't an error in setup, topic or teardown 128 | succeeded = succeeded and (not context.get('error', None)) 129 | 130 | # Success only if all subcontexts succeeded 131 | for context in context['contexts']: 132 | succeeded = succeeded and self.eval_context(context) 133 | 134 | # Success only if all tests succeeded 135 | for test in context['tests']: 136 | succeeded = succeeded and not test['error'] 137 | 138 | return succeeded 139 | 140 | def get_worst_topics(self, number=10, threshold=0.1): 141 | '''Returns the top `number` slowest topics which took longer 142 | than `threshold` to test. 143 | 144 | ''' 145 | times = [ 146 | time for time in self._get_topic_times() 147 | if time['elapsed'] > 0 and time['elapsed'] >= threshold 148 | ] 149 | times.sort(key=lambda x: x['elapsed'], reverse=True) 150 | return times[:number] 151 | -------------------------------------------------------------------------------- /pyvows/runner/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''This package contains different runtime implementations for PyVows. PyVows will 3 | select the fastest possible runner, using fallbacks if unavailable. 4 | ''' 5 | 6 | 7 | class SkipTest(Exception): 8 | pass 9 | 10 | from pyvows.runner.gevent import VowsParallelRunner as VowsRunner 11 | 12 | __all__ = ('VowsRunner', 'SkipTest') 13 | -------------------------------------------------------------------------------- /pyvows/runner/abc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''Abstract base class for all PyVows Runner implementations.''' 3 | 4 | 5 | # pyvows testing engine 6 | # https://github.com/heynemann/pyvows 7 | 8 | # Licensed under the MIT license: 9 | # http://www.opensource.org/licenses/mit-license 10 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 11 | 12 | import sys 13 | import time 14 | 15 | from pyvows.runner.utils import get_file_info_for 16 | from pyvows.utils import elapsed 17 | from pyvows.runner import SkipTest 18 | 19 | #------------------------------------------------------------------------------------------------- 20 | 21 | 22 | class VowsRunnerABC(object): 23 | 24 | def __init__(self, suites, context_class, on_vow_success, on_vow_error, execution_plan, capture_output=False): 25 | self.suites = suites # a suite is a file with pyvows tests 26 | self.context_class = context_class 27 | self.on_vow_success = on_vow_success 28 | self.on_vow_error = on_vow_error 29 | self.execution_plan = execution_plan 30 | self.capture_output = capture_output 31 | 32 | def run(self): 33 | pass 34 | 35 | def run_context(self): 36 | pass 37 | 38 | def get_vow_result(self, vow, topic, ctx_obj, vow_name, enumerated): 39 | filename, lineno = get_file_info_for(vow) 40 | 41 | vow_result = { 42 | 'context_instance': ctx_obj, 43 | 'name': vow_name, 44 | 'enumerated': enumerated, 45 | 'result': None, 46 | 'topic': topic, 47 | 'error': None, 48 | 'skip': None, 49 | 'succeeded': False, 50 | 'file': filename, 51 | 'lineno': lineno, 52 | 'elapsed': 0, 53 | 'stdout': '', 54 | 'stderr': '' 55 | } 56 | return vow_result 57 | 58 | def run_vow(self, tests_collection, topic, ctx_obj, vow, vow_name, enumerated): 59 | # FIXME: Add Docstring 60 | 61 | start_time = time.time() 62 | vow_result = self.get_vow_result(vow, topic, ctx_obj, vow_name, enumerated) 63 | 64 | try: 65 | result = vow(ctx_obj, topic) 66 | vow_result['result'] = result 67 | vow_result['succeeded'] = True 68 | if self.on_vow_success: 69 | self.on_vow_success(vow_result) 70 | except SkipTest as se: 71 | vow_result['skip'] = se 72 | except: 73 | err_type, err_value, err_traceback = sys.exc_info() 74 | vow_result['error'] = { 75 | 'type': err_type, 76 | 'value': err_value, 77 | 'traceback': err_traceback 78 | } 79 | if self.on_vow_error: 80 | self.on_vow_error(vow_result) 81 | 82 | vow_result['elapsed'] = elapsed(start_time) 83 | tests_collection.append(vow_result) 84 | 85 | return vow_result 86 | 87 | 88 | class VowsTopicError(Exception): 89 | """Wraps an error in the setup or topic functions.""" 90 | def __init__(self, source, exc_info): 91 | self.source = source 92 | self.exc_info = exc_info 93 | -------------------------------------------------------------------------------- /pyvows/runner/executionplan.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''The logic PyVows uses to discover contexts and vows''' 3 | 4 | import inspect 5 | import re 6 | 7 | 8 | class ExecutionPlanner(object): 9 | def __init__(self, suites, exclusion_patterns, inclusion_patterns): 10 | self.suites = suites 11 | if exclusion_patterns and inclusion_patterns: 12 | raise Exception('Using both exclusion_patterns and inclusion_patterns is not allowed') 13 | self.exclusion_patterns = set([re.compile(x) for x in exclusion_patterns]) 14 | self.inclusion_patterns = set([re.compile(x) for x in inclusion_patterns]) 15 | 16 | def plan(self): 17 | plan = {} 18 | for suiteName, contextClasses in self.suites.items(): 19 | plan[suiteName] = { 20 | 'contexts': {} 21 | } 22 | for contextClass in contextClasses: 23 | contextPlan, isRequired = self.plan_context(contextClass, '') 24 | if isRequired and not self.is_excluded(contextPlan['name']): 25 | plan[suiteName]['contexts'][contextClass.__name__] = contextPlan 26 | return plan 27 | 28 | def is_excluded(self, name): 29 | '''Return whether `name` is in `self.exclusion_patterns`.''' 30 | 31 | for pattern in self.exclusion_patterns: 32 | if pattern.search(name): 33 | return True 34 | return False 35 | 36 | def is_included(self, name): 37 | '''Return whether `name` is in `self.inclusion_patterns`.''' 38 | 39 | if not self.inclusion_patterns: 40 | return True 41 | 42 | for pattern in self.inclusion_patterns: 43 | if pattern.search(name): 44 | return True 45 | return False 46 | 47 | def plan_context(self, contextClass, idBase): 48 | context = { 49 | 'name': contextClass.__name__, 50 | 'id': idBase + ('.' if idBase else '') + contextClass.__name__, 51 | 'contexts': {}, 52 | 'vows': [] 53 | } 54 | 55 | special_names = set(['setup', 'teardown', 'topic']) 56 | if hasattr(contextClass, 'ignored_members'): 57 | special_names.update(contextClass.ignored_members) 58 | 59 | # remove any special methods 60 | contextMembers = [ 61 | (name, value) for name, value in inspect.getmembers(contextClass) 62 | if name not in special_names and not name.startswith('_') 63 | ] 64 | 65 | context['vows'] = [ 66 | name for name, vow in contextMembers 67 | if (inspect.ismethod(vow) or inspect.isfunction(vow)) 68 | and self.is_included(context['id'] + '.' + name) 69 | and not self.is_excluded(name) 70 | ] 71 | 72 | subcontexts = [ 73 | (name, subcontext) for name, subcontext in contextMembers 74 | if inspect.isclass(subcontext) and not self.is_excluded(name) 75 | ] 76 | 77 | for name, subcontext in subcontexts: 78 | subcontextPlan, subcontextContainsIncludedSubcontexts = self.plan_context(subcontext, context['id']) 79 | if self.is_included(subcontextPlan['id']) or subcontextContainsIncludedSubcontexts: 80 | context['contexts'][name] = subcontextPlan 81 | 82 | if self.inclusion_patterns: 83 | contextRequiredBecauseItContainsVowsOrSubcontexts = bool(context['contexts']) or bool(context['vows']) 84 | else: 85 | contextRequiredBecauseItContainsVowsOrSubcontexts = True 86 | 87 | return context, contextRequiredBecauseItContainsVowsOrSubcontexts 88 | -------------------------------------------------------------------------------- /pyvows/runner/gevent.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''The GEvent implementation of PyVows runner.''' 3 | 4 | 5 | # pyvows testing engine 6 | # https://github.com/heynemann/pyvows 7 | 8 | # Licensed under the MIT license: 9 | # http://www.opensource.org/licenses/mit-license 10 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 11 | 12 | from __future__ import absolute_import 13 | 14 | import inspect 15 | import sys 16 | import time 17 | try: 18 | from StringIO import StringIO 19 | except: 20 | from io import StringIO 21 | try: 22 | from colorama.ansitowin32 import AnsiToWin32 23 | except ImportError: 24 | def AnsiToWin32(*args, **kwargs): 25 | return args[0] 26 | 27 | from gevent.pool import Pool 28 | import gevent.local 29 | 30 | from pyvows.async_topic import VowsAsyncTopic, VowsAsyncTopicValue 31 | from pyvows.runner.utils import get_topics_for 32 | from pyvows.result import VowsResult 33 | from pyvows.utils import elapsed 34 | from pyvows.runner.abc import VowsRunnerABC, VowsTopicError 35 | from pyvows.runner import SkipTest 36 | 37 | #----------------------------------------------------------------------------- 38 | 39 | 40 | class _LocalOutput(gevent.local.local): 41 | def __init__(self): 42 | self.__dict__['stdout'] = StringIO() 43 | self.__dict__['stderr'] = StringIO() 44 | 45 | 46 | class _StreamCapture(object): 47 | def __init__(self, streamName): 48 | self.__streamName = streamName 49 | 50 | def __getattr__(self, name): 51 | return getattr(getattr(VowsParallelRunner.output, self.__streamName), name) 52 | 53 | 54 | class VowsParallelRunner(VowsRunnerABC): 55 | # FIXME: Add Docstring 56 | 57 | # Class is called from `pyvows.core:Vows.run()`, 58 | # which is called from `pyvows.cli.run()` 59 | 60 | output = _LocalOutput() 61 | orig_stdout = sys.stdout 62 | orig_stderr = sys.stderr 63 | 64 | def __init__(self, *args, **kwargs): 65 | super(VowsParallelRunner, self).__init__(*args, **kwargs) 66 | self.pool = Pool(1000) 67 | 68 | def run(self): 69 | # FIXME: Add Docstring 70 | 71 | # called from `pyvows.core:Vows.run()`, 72 | # which is called from `pyvows.cli.run()` 73 | 74 | start_time = time.time() 75 | result = VowsResult() 76 | if self.capture_output: 77 | self._capture_streams(True) 78 | try: 79 | for suiteName, suitePlan in self.execution_plan.items(): 80 | batches = [batch for batch in self.suites[suiteName] if batch.__name__ in suitePlan['contexts']] 81 | for batch in batches: 82 | self.pool.spawn( 83 | self.run_context, 84 | result.contexts, 85 | batch.__name__, 86 | batch(None), 87 | suitePlan['contexts'][batch.__name__], 88 | index=-1, 89 | suite=suiteName 90 | ) 91 | 92 | self.pool.join() 93 | finally: 94 | if self.capture_output: 95 | self._capture_streams(False) 96 | 97 | result.elapsed_time = elapsed(start_time) 98 | return result 99 | 100 | def run_context(self, ctx_collection, ctx_name, ctx_obj, execution_plan, index=-1, suite=None, skipReason=None): 101 | # FIXME: Add Docstring 102 | 103 | #----------------------------------------------------------------------- 104 | # Local variables and defs 105 | #----------------------------------------------------------------------- 106 | ctx_result = { 107 | 'filename': suite or inspect.getsourcefile(ctx_obj.__class__), 108 | 'name': ctx_name, 109 | 'tests': [], 110 | 'contexts': [], 111 | 'topic_elapsed': 0, 112 | 'error': None, 113 | 'skip': skipReason 114 | } 115 | 116 | ctx_collection.append(ctx_result) 117 | ctx_obj.index = index 118 | ctx_obj.pool = self.pool 119 | teardown_blockers = [] 120 | 121 | def _run_setup_and_topic(ctx_obj, index): 122 | # If we're already mid-skip, don't run anything 123 | if skipReason: 124 | raise skipReason 125 | 126 | # Run setup function 127 | try: 128 | ctx_obj.setup() 129 | except Exception: 130 | raise VowsTopicError('setup', sys.exc_info()) 131 | 132 | try: 133 | # Find & run topic function 134 | if not hasattr(ctx_obj, 'topic'): # ctx_obj has no topic 135 | return ctx_obj._get_first_available_topic(index) 136 | 137 | topic_func = ctx_obj.topic 138 | topic_list = get_topics_for(topic_func, ctx_obj) 139 | 140 | start_time = time.time() 141 | 142 | if topic_func is None: 143 | return None 144 | 145 | topic = topic_func(*topic_list) 146 | ctx_result['topic_elapsed'] = elapsed(start_time) 147 | return topic 148 | except SkipTest: 149 | raise 150 | except Exception: 151 | raise VowsTopicError('topic', sys.exc_info()) 152 | 153 | def _run_tests(topic): 154 | def _run_with_topic(topic): 155 | def _run_vows_and_subcontexts(topic, index=-1, enumerated=False): 156 | # methods 157 | for vow_name, vow in vows: 158 | if skipReason: 159 | skipped_result = self.get_vow_result(vow, topic, ctx_obj, vow_name, enumerated) 160 | skipped_result['skip'] = skipReason 161 | ctx_result['tests'].append(skipped_result) 162 | else: 163 | vow_greenlet = self._run_vow( 164 | ctx_result['tests'], 165 | topic, 166 | ctx_obj, 167 | vow, 168 | vow_name, 169 | enumerated=enumerated) 170 | teardown_blockers.append(vow_greenlet) 171 | 172 | # classes 173 | for subctx_name, subctx in subcontexts: 174 | # resolve user-defined Context classes 175 | if not issubclass(subctx, self.context_class): 176 | subctx = type(ctx_name, (subctx, self.context_class), {}) 177 | 178 | subctx_obj = subctx(ctx_obj) 179 | subctx_obj.pool = self.pool 180 | 181 | subctx_greenlet = self.pool.spawn( 182 | self.run_context, 183 | ctx_result['contexts'], 184 | subctx_name, 185 | subctx_obj, 186 | execution_plan['contexts'][subctx_name], 187 | index=index, 188 | suite=suite or ctx_result['filename'], 189 | skipReason=skipReason 190 | ) 191 | teardown_blockers.append(subctx_greenlet) 192 | 193 | # setup generated topics if needed 194 | is_generator = inspect.isgenerator(topic) 195 | if is_generator: 196 | try: 197 | ctx_obj.generated_topic = True 198 | topic = ctx_obj.topic_value = list(topic) 199 | except Exception: 200 | # Actually getting the values from the generator may raise exception 201 | raise VowsTopicError('topic', sys.exc_info()) 202 | else: 203 | ctx_obj.topic_value = topic 204 | 205 | if is_generator: 206 | for index, topic_value in enumerate(topic): 207 | _run_vows_and_subcontexts(topic_value, index=index, enumerated=True) 208 | else: 209 | _run_vows_and_subcontexts(topic) 210 | 211 | vows = set((vow_name, getattr(type(ctx_obj), vow_name)) for vow_name in execution_plan['vows']) 212 | subcontexts = set((subctx_name, getattr(type(ctx_obj), subctx_name)) for subctx_name in execution_plan['contexts']) 213 | 214 | if not isinstance(topic, VowsAsyncTopic): 215 | _run_with_topic(topic) 216 | else: 217 | def handle_callback(*args, **kw): 218 | _run_with_topic(VowsAsyncTopicValue(args, kw)) 219 | topic(handle_callback) 220 | 221 | def _run_teardown(): 222 | try: 223 | for blocker in teardown_blockers: 224 | blocker.join() 225 | ctx_obj.teardown() 226 | except Exception: 227 | raise VowsTopicError('teardown', sys.exc_info()) 228 | 229 | def _update_execution_plan(): 230 | '''Since Context.ignore can modify the ignored_members during setup or topic, 231 | update the execution_plan to reflect the new ignored_members''' 232 | 233 | for name in ctx_obj.ignored_members: 234 | if name in execution_plan['vows']: 235 | execution_plan['vows'].remove(name) 236 | if name in execution_plan['contexts']: 237 | del execution_plan['contexts'][name] 238 | 239 | #----------------------------------------------------------------------- 240 | # Begin 241 | #----------------------------------------------------------------------- 242 | try: 243 | try: 244 | topic = _run_setup_and_topic(ctx_obj, index) 245 | _update_execution_plan() 246 | except SkipTest as se: 247 | ctx_result['skip'] = se 248 | skipReason = se 249 | topic = None 250 | except VowsTopicError as e: 251 | ctx_result['error'] = e 252 | skipReason = SkipTest('topic dependency failed') 253 | topic = None 254 | _run_tests(topic) 255 | if not ctx_result['error']: 256 | try: 257 | _run_teardown() 258 | except Exception as e: 259 | ctx_result['error'] = e 260 | finally: 261 | ctx_result['stdout'] = VowsParallelRunner.output.stdout.getvalue() 262 | ctx_result['stderr'] = VowsParallelRunner.output.stderr.getvalue() 263 | 264 | def _capture_streams(self, capture): 265 | if capture: 266 | sys.stdout = AnsiToWin32(_StreamCapture('stdout'), convert=False, strip=True) 267 | sys.stderr = AnsiToWin32(_StreamCapture('stderr'), convert=False, strip=True) 268 | else: 269 | sys.stdout = VowsParallelRunner.orig_stdout 270 | sys.stderr = VowsParallelRunner.orig_stderr 271 | 272 | def _run_vow(self, tests_collection, topic, ctx_obj, vow, vow_name, enumerated=False): 273 | # FIXME: Add Docstring 274 | return self.pool.spawn(self.run_vow, tests_collection, topic, ctx_obj, vow, vow_name, enumerated) 275 | 276 | def run_vow(self, tests_collection, topic, ctx_obj, vow, vow_name, enumerated=False): 277 | results = super(VowsParallelRunner, self).run_vow(tests_collection, topic, ctx_obj, vow, vow_name, enumerated=enumerated) 278 | results['stdout'] = VowsParallelRunner.output.stdout.getvalue() 279 | results['stderr'] = VowsParallelRunner.output.stderr.getvalue() 280 | -------------------------------------------------------------------------------- /pyvows/runner/sequential.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''This is the slowest of PyVows' runner implementations. But it's also dependency-free; thus, 3 | it's a universal fallback. 4 | 5 | ''' 6 | 7 | from pyvows.runner.abc import VowsRunnerABC 8 | from pyvows.runner.utils import get_code_for, get_file_info_for, get_topics_for 9 | 10 | #------------------------------------------------------------------------------------------------- 11 | 12 | class VowsSequentialRunner(object): 13 | 14 | def run(self): 15 | pass 16 | #for suite, batches in self.suites.items(): 17 | # for batch in batches: 18 | # self.run_context(batch.__name__, batch(None)) 19 | 20 | def run_context(self, ctx_name, ctx_instance): 21 | pass 22 | # setup 23 | # teardown 24 | # topic 25 | # vows 26 | # subcontexts 27 | # teardown -------------------------------------------------------------------------------- /pyvows/runner/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''Utility functions for all implementations of pyvows.runner. 3 | 4 | ''' 5 | import os.path as path 6 | 7 | #------------------------------------------------------------------------------------------------- 8 | 9 | def get_code_for(obj): 10 | # FIXME: Add Comment description 11 | code = None 12 | if hasattr(obj, '__code__'): 13 | code = obj.__code__ 14 | elif hasattr(obj, '__func__'): 15 | code = obj.__func__.__code__ 16 | return code 17 | 18 | 19 | def get_file_info_for(member): 20 | # FIXME: Add Docstring 21 | code = get_code_for(member) 22 | 23 | filename = code.co_filename 24 | lineno = code.co_firstlineno 25 | 26 | return filename, lineno 27 | 28 | 29 | def get_topics_for(topic_function, ctx_obj): 30 | # FIXME: Add Docstring 31 | if not ctx_obj.parent: 32 | return [] 33 | 34 | # check for decorated topic function 35 | if hasattr(topic_function, '_original'): 36 | # _wrapper_type is 'async_topic' or 'capture_error' 37 | _async = (getattr(topic_function, '_wrapper_type', None) == 'async_topic') 38 | topic_function = topic_function._original 39 | else: 40 | _async = False 41 | 42 | code = get_code_for(topic_function) 43 | 44 | if not code: 45 | raise RuntimeError( 46 | 'Function %s does not have a code property' % topic_function) 47 | 48 | expected_args = code.co_argcount - 1 49 | 50 | # taking the callback argument into consideration 51 | if _async: 52 | expected_args -= 1 53 | 54 | # prepare to create `topics` list 55 | topics = [] 56 | child = ctx_obj 57 | context = ctx_obj.parent 58 | 59 | # populate `topics` list 60 | for i in range(expected_args): 61 | topic = context.topic_value 62 | 63 | if context.generated_topic: 64 | topic = topic[child.index] 65 | 66 | topics.append(topic) 67 | 68 | if not context.parent: 69 | break 70 | 71 | context = context.parent 72 | child = child.parent 73 | 74 | return topics 75 | -------------------------------------------------------------------------------- /pyvows/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''This module is the foundation that allows users to write PyVows-style tests. 3 | ''' 4 | 5 | # pyVows testing engine 6 | # https://github.com/heynemann/pyvows 7 | 8 | # Licensed under the MIT license: 9 | # http://www.opensource.org/licenses/mit-license 10 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 11 | 12 | import fnmatch 13 | import glob 14 | import os 15 | import time 16 | 17 | #------------------------------------------------------------------------------------------------- 18 | 19 | elapsed = lambda start_time: float(round(time.time() - start_time, 6)) 20 | 21 | 22 | def locate(pattern, root=os.curdir, recursive=True): 23 | '''Recursively locates test files when `pyvows` is run from the 24 | command line. 25 | 26 | ''' 27 | root_path = os.path.abspath(root) 28 | 29 | if recursive: 30 | return_files = [] 31 | for path, dirs, files in os.walk(root_path): 32 | for filename in fnmatch.filter(files, pattern): 33 | return_files.append(os.path.join(path, filename)) 34 | return return_files 35 | else: 36 | return glob.glob(os.path.join(root_path, pattern)) 37 | 38 | 39 | def template(): 40 | '''Provides a template containing boilerplate code for new PyVows test 41 | files. Output is sent to STDOUT, allowing you to redirect it on 42 | the command line as you wish. 43 | 44 | ''' 45 | from datetime import date 46 | import sys 47 | from textwrap import dedent 48 | 49 | from pyvows import version 50 | 51 | TEST_FILE_TEMPLATE = '''\ 52 | # -*- coding: utf-8 -*- 53 | ## Generated by PyVows v{version} ({date}) 54 | ## http://pyvows.org 55 | 56 | ## IMPORTS ## 57 | ## 58 | ## Standard Library 59 | # 60 | ## Third Party 61 | # 62 | ## PyVows Testing 63 | from pyvows import Vows, expect 64 | 65 | ## Local Imports 66 | import 67 | 68 | 69 | ## TESTS ## 70 | @Vows.batch 71 | class PleaseGiveMeAGoodName(Vows.Context): 72 | 73 | def topic(self): 74 | return # return what you're going to test here 75 | 76 | ## Now, write some vows for your topic! :) 77 | def should_do_something(self, topic): 78 | expect(topic)# 79 | 80 | '''.format( 81 | version = version.to_str(), 82 | date = '{0:%Y/%m/%d}'.format(date.today()) 83 | ) 84 | 85 | sys.stdout.write(dedent(TEST_FILE_TEMPLATE)) 86 | -------------------------------------------------------------------------------- /pyvows/version.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | '''PyVows' version number. 3 | ''' 4 | 5 | 6 | # pyVows testing engine 7 | # https://github.com/heynemann/pyvows 8 | 9 | # Licensed under the MIT license: 10 | # http://www.opensource.org/licenses/mit-license 11 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 12 | 13 | #------------------------------------------------------------------------------------------------- 14 | 15 | __version__ = (3, 0, 0) 16 | 17 | #------------------------------------------------------------------------------------------------- 18 | 19 | 20 | def to_str(): 21 | '''Returns a string containing PyVows' version number.''' 22 | return '.'.join([str(item) for item in __version__]) 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | gevent>=1.2.2 2 | preggy>=1.4.4 3 | 4 | argparse>=1.4.0 5 | colorama>=0.3.7 6 | coverage>=4.1.1 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyVows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | # stdlib 12 | import sys 13 | from textwrap import dedent 14 | # external 15 | from setuptools import setup, find_packages 16 | # local 17 | from pyvows import version 18 | 19 | 20 | _test_requires = [ 21 | 'argparse>=1.4.0', 22 | 'colorama>=0.3.7', 23 | 'coverage>=4.1.1' 24 | 25 | ] 26 | _install_requires = [ 27 | 'gevent>=1.2.2', 28 | 'preggy>=1.3.0', 29 | ] 30 | if sys.version_info < (2, 7): 31 | _install_requires.append('argparse >= 1.1') 32 | 33 | 34 | setup( 35 | ### OVERVIEW 36 | name='pyVows', 37 | description='pyVows is a BDD test engine based on Vows.js .', 38 | long_description_content_type='text/x-rst', 39 | long_description=dedent( 40 | '''pyVows is a test engine based on Vows.js. It features topic-based testing, 41 | (*fast*) parallel running of tests, code coverage reports, test profiling, and 42 | more: 43 | 44 | http://pyvows.org 45 | 46 | '''), 47 | 48 | 49 | ### URLs 50 | url='https://github.com/heynemann/pyvows', 51 | 52 | 53 | ### TECHNICAL INFO 54 | version=version.to_str(), 55 | install_requires=_install_requires, 56 | extras_require={ 57 | 'tests': _test_requires, 58 | }, 59 | packages=find_packages(exclude=['tests', 'tests.*']), 60 | package_dir={'pyvows': 'pyvows'}, 61 | entry_points={ 62 | 'console_scripts': [ 63 | 'pyvows = pyvows.cli:main' 64 | ], 65 | 'distutils.commands': [ 66 | ' vows = pyvows.commands:VowsCommand', 67 | ], 68 | }, 69 | 70 | 71 | ### PEOPLE & LICENSE 72 | author='Bernardo Heynemann', 73 | author_email='heynemann@gmail.com', 74 | #maintainer = 'Rafael Carício', 75 | #maintainer_email = 'rafael@caricio.com', 76 | maintainer='Zearin', 77 | license='MIT', 78 | 79 | 80 | ### CATEGORIZATION 81 | keywords='test testing vows tdd bdd development coverage profile profiling', 82 | classifiers=[ 83 | 'Development Status :: 4 - Beta', 84 | 'Intended Audience :: Developers', 85 | 'License :: OSI Approved :: MIT License', 86 | 'Natural Language :: English', 87 | 'Operating System :: MacOS', 88 | 'Operating System :: POSIX', 89 | 'Operating System :: Unix', 90 | 'Operating System :: OS Independent', 91 | 'Programming Language :: Python :: 2.6', 92 | 'Programming Language :: Python :: 2.7', 93 | 'Topic :: Software Development :: Testing' 94 | ], 95 | ) 96 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # thumbor imaging service 3 | # https://github.com/globocom/thumbor/wiki 4 | 5 | # Licensed under the MIT license: 6 | # http://www.opensource.org/licenses/mit-license 7 | # Copyright (c) 2011 globo.com timehome@corp.globo.com 8 | -------------------------------------------------------------------------------- /tests/assertions/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # pyvows testing engine 4 | # https://github.com/heynemann/pyvows 5 | 6 | # Licensed under the MIT license: 7 | # http://www.opensource.org/licenses/mit-license 8 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 9 | -------------------------------------------------------------------------------- /tests/assertions/assertion_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | 13 | #----------------------------------------------------------------------------- 14 | 15 | class NotEmptyContext(Vows.Context): 16 | def should_not_be_empty(self, topic): 17 | expect(topic).not_to_be_empty() 18 | 19 | class NotErrorContext(Vows.Context): 20 | def should_not_be_an_error(self, topic): 21 | expect(topic).not_to_be_an_error() 22 | 23 | Vows.NotEmptyContext = NotEmptyContext 24 | Vows.NotErrorContext = NotErrorContext 25 | 26 | #----------------------------------------------------------------------------- 27 | 28 | @Vows.batch 29 | class Assertion(Vows.Context): 30 | 31 | class WhenNotHaveTopic(Vows.Context): 32 | 33 | def we_can_see_topic_as_none(self, topic): 34 | expect(topic).to_be_null() 35 | 36 | class WhenUTF8Topic(Vows.Context): 37 | def topic(self): 38 | return u"some á é í ó ç" 39 | 40 | def should_not_fail(self, topic): 41 | expect(topic).to_equal(u'some á é í ó ç') 42 | 43 | class NonErrorContext(Vows.NotErrorContext): 44 | def topic(self): 45 | return 42 46 | 47 | class NotEmptyContext(Vows.NotEmptyContext): 48 | def topic(self): 49 | return "harmless" 50 | -------------------------------------------------------------------------------- /tests/assertions/emptiness_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | 13 | 14 | @Vows.batch 15 | class AssertionIsEmpty(Vows.Context): 16 | class WhenEmpty(Vows.Context): 17 | class WhenString(Vows.Context): 18 | def topic(self): 19 | return '' 20 | 21 | def we_get_an_empty_string(self, topic): 22 | expect(topic).to_be_empty() 23 | 24 | class WhenList(Vows.Context): 25 | def topic(self): 26 | return [] 27 | 28 | def we_get_an_empty_list(self, topic): 29 | expect(topic).to_be_empty() 30 | 31 | class WhenTuple(Vows.Context): 32 | def topic(self): 33 | return tuple([]) 34 | 35 | def we_get_an_empty_tuple(self, topic): 36 | expect(topic).to_be_empty() 37 | 38 | class WhenDict(Vows.Context): 39 | def topic(self): 40 | return {} 41 | 42 | def we_get_an_empty_dict(self, topic): 43 | expect(topic).to_be_empty() 44 | 45 | class WhenWeGetAnError(Vows.Context): 46 | 47 | @Vows.capture_error 48 | def topic(self, last): 49 | expect([1]).to_be_empty() 50 | 51 | def we_get_an_understandable_message(self, topic): 52 | expect(topic).to_have_an_error_message_of("Expected topic([1]) to be empty") 53 | 54 | class WhenNotEmpty(Vows.Context): 55 | class WhenString(Vows.Context): 56 | def topic(self): 57 | return 'whatever' 58 | 59 | def we_get_a_not_empty_string(self, topic): 60 | expect(topic).Not.to_be_empty() 61 | 62 | class WhenList(Vows.Context): 63 | def topic(self): 64 | return ['something'] 65 | 66 | def we_get_a_not_empty_list(self, topic): 67 | expect(topic).Not.to_be_empty() 68 | 69 | class WhenTuple(Vows.Context): 70 | def topic(self): 71 | return tuple(['something']) 72 | 73 | def we_get_a_not_empty_tuple(self, topic): 74 | expect(topic).Not.to_be_empty() 75 | 76 | class WhenDict(Vows.Context): 77 | def topic(self): 78 | return {"key": "value"} 79 | 80 | def we_get_a_not_empty_dict(self, topic): 81 | expect(topic).Not.to_be_empty() 82 | 83 | class WhenWeGetAnError(Vows.Context): 84 | 85 | @Vows.capture_error 86 | def topic(self, last): 87 | expect([]).not_to_be_empty() 88 | 89 | def we_get_an_understandable_message(self, topic): 90 | expect(topic).to_have_an_error_message_of("Expected topic([]) not to be empty") 91 | -------------------------------------------------------------------------------- /tests/assertions/equality_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | 13 | 14 | @Vows.batch 15 | class AssertionEquality(Vows.Context): 16 | def topic(self): 17 | return "test" 18 | 19 | class WhenIsEqual(Vows.Context): 20 | 21 | def we_get_test(self, topic): 22 | expect(topic).to_equal('test') 23 | 24 | class WhenWeGetAnError(Vows.Context): 25 | 26 | @Vows.capture_error 27 | def topic(self, last): 28 | expect(1).to_equal(2) 29 | 30 | def we_get_an_understandable_message(self, topic): 31 | expect(topic).to_have_an_error_message_of("Expected topic(1) to equal 2") 32 | 33 | class WhenIsNotEqual(Vows.Context): 34 | 35 | def we_do_not_get_else(self, topic): 36 | expect(topic).Not.to_equal('else') 37 | 38 | class WhenWeGetAnError(Vows.Context): 39 | 40 | @Vows.capture_error 41 | def topic(self, last): 42 | expect(1).not_to_equal(1) 43 | 44 | def we_get_an_understandable_message(self, topic): 45 | expect(topic).to_have_an_error_message_of("Expected topic(1) not to equal 1") 46 | 47 | class WhenHaveASubClassThatHaveAExtraParamInTopic(Vows.Context): 48 | def topic(self, last): 49 | return last 50 | 51 | def we_get_the_last_topic_value_without_modifications(self, topic): 52 | expect(topic).to_equal('test') 53 | 54 | class WhenSubContextNotHaveTopic(Vows.Context): 55 | 56 | def we_get_the_last_topic(self, topic): 57 | expect(topic).to_equal('test') 58 | -------------------------------------------------------------------------------- /tests/assertions/inclusion_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | 13 | 14 | @Vows.batch 15 | class AssertionInclude(Vows.Context): 16 | 17 | class WhenItIsAString(Vows.Context): 18 | def topic(self): 19 | return "some big string" 20 | 21 | def we_can_find_some(self, topic): 22 | expect(topic).to_include('some') 23 | 24 | def we_can_find_big(self, topic): 25 | expect(topic).to_include('big') 26 | 27 | def we_can_find_string(self, topic): 28 | expect(topic).to_include('string') 29 | 30 | def we_cant_find_else(self, topic): 31 | expect(topic).Not.to_include('else') 32 | 33 | class WhenItIsAList(Vows.Context): 34 | def topic(self): 35 | return ["some", "big", "string"] 36 | 37 | def we_can_find_some(self, topic): 38 | expect(topic).to_include('some') 39 | 40 | def we_can_find_big(self, topic): 41 | expect(topic).to_include('big') 42 | 43 | def we_can_find_string(self, topic): 44 | expect(topic).to_include('string') 45 | 46 | def we_cant_find_else(self, topic): 47 | expect(topic).Not.to_include('else') 48 | 49 | class WhenItIsATuple(Vows.Context): 50 | def topic(self): 51 | return tuple(["some", "big", "string"]) 52 | 53 | def we_can_find_some(self, topic): 54 | expect(topic).to_include('some') 55 | 56 | def we_can_find_big(self, topic): 57 | expect(topic).to_include('big') 58 | 59 | def we_can_find_string(self, topic): 60 | expect(topic).to_include('string') 61 | 62 | def we_cant_find_else(self, topic): 63 | expect(topic).Not.to_include('else') 64 | 65 | class WhenItIsADict(Vows.Context): 66 | def topic(self): 67 | return {"some": 1, "big": 2, "string": 3} 68 | 69 | def we_can_find_some(self, topic): 70 | expect(topic).to_include('some') 71 | 72 | def we_can_find_big(self, topic): 73 | expect(topic).to_include('big') 74 | 75 | def we_can_find_string(self, topic): 76 | expect(topic).to_include('string') 77 | 78 | def we_cant_find_else(self, topic): 79 | expect(topic).Not.to_include('else') 80 | 81 | class WhenWeGetAnError(Vows.Context): 82 | @Vows.capture_error 83 | def topic(self, last): 84 | expect('a').to_include('b') 85 | 86 | def we_get_an_understandable_message(self, topic): 87 | expect(topic).to_have_an_error_message_of("Expected topic('a') to include 'b'") 88 | 89 | class WhenWeGetAnErrorOnNot(Vows.Context): 90 | @Vows.capture_error 91 | def topic(self, last): 92 | expect('a').not_to_include('a') 93 | 94 | def we_get_an_understandable_message(self, topic): 95 | expect(topic).to_have_an_error_message_of("Expected topic('a') not to include 'a'") 96 | -------------------------------------------------------------------------------- /tests/assertions/length_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | 13 | 14 | @Vows.batch 15 | class AssertionLength(Vows.Context): 16 | class ToLength(Vows.Context): 17 | class WithString(Vows.Context): 18 | def topic(self): 19 | return "some string" 20 | 21 | def we_can_see_it_has_11_characters(self, topic): 22 | expect(topic).to_length(11) 23 | 24 | class WithList(Vows.Context): 25 | def topic(self): 26 | return ["some", "list"] 27 | 28 | def we_can_see_it_has_2_items(self, topic): 29 | expect(topic).to_length(2) 30 | 31 | class WithTuple(Vows.Context): 32 | def topic(self): 33 | return tuple(["some", "list"]) 34 | 35 | def we_can_see_it_has_2_items(self, topic): 36 | expect(topic).to_length(2) 37 | 38 | class WithDict(Vows.Context): 39 | def topic(self): 40 | return {"some": "item", "other": "item"} 41 | 42 | def we_can_see_it_has_2_items(self, topic): 43 | expect(topic).to_length(2) 44 | 45 | class WhenWeGetAnError(Vows.Context): 46 | 47 | @Vows.capture_error 48 | def topic(self, last): 49 | expect('a').to_length(2) 50 | 51 | def we_get_an_understandable_message(self, topic): 52 | expect(topic).to_have_an_error_message_of("Expected topic('a') to have 2 of length, but it has 1") 53 | 54 | class NotToLength(Vows.Context): 55 | class WhenWeGetAnError(Vows.Context): 56 | @Vows.capture_error 57 | def topic(self, last): 58 | expect('a').not_to_length(1) 59 | 60 | def we_get_an_understandable_message(self, topic): 61 | expect(topic).to_have_an_error_message_of("Expected topic('a') not to have 1 of length") 62 | -------------------------------------------------------------------------------- /tests/assertions/like_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | 13 | 14 | @Vows.batch 15 | class AssertionIsLike(Vows.Context): 16 | 17 | class WhenItIsAString(Vows.Context): 18 | def topic(self): 19 | return " some StRinG with RanDoM CaSe And Weird SpACING " 20 | 21 | def we_assert_it_is_like_other_string(self, topic): 22 | expect(topic).to_be_like('some string with random case and weird spacing') 23 | 24 | def we_assert_it_is_not_like_other_string(self, topic): 25 | expect(topic).Not.to_be_like('some other string') 26 | 27 | class WhenItIsAMultilineString(Vows.Context): 28 | def topic(self): 29 | return " some StRinG \nwith RanDoM \nCaSe And \nWeird \nSpACING " 30 | 31 | def we_assert_it_is_like_other_string(self, topic): 32 | expect(topic).to_be_like('some string with random case and weird spacing') 33 | 34 | def we_assert_it_is_not_like_other_string(self, topic): 35 | expect(topic).Not.to_be_like('some other string') 36 | 37 | class WhenItIsANumber(Vows.Context): 38 | def topic(self): 39 | return 42 40 | 41 | def we_assert_it_is_not_like_a_string(self, topic): 42 | expect(topic).Not.to_be_like('42') 43 | 44 | def we_assert_it_is_like_42(self, topic): 45 | expect(topic).to_be_like(42) 46 | 47 | def we_assert_it_is_like_42_float(self, topic): 48 | expect(topic).to_be_like(42.0) 49 | 50 | def we_assert_it_is_not_like_41(self, topic): 51 | expect(topic).Not.to_be_like(41) 52 | 53 | class WhenItIsAList(Vows.Context): 54 | 55 | class OfNumbers(Vows.Context): 56 | def topic(self): 57 | return [1, 2, 3] 58 | 59 | def we_can_compare_to_other_list(self, topic): 60 | expect(topic).to_be_like([1, 2, 3]) 61 | 62 | def we_can_compare_to_a_list_in_different_order(self, topic): 63 | expect(topic).to_be_like([3, 2, 1]) 64 | 65 | def we_can_compare_to_a_tuple_in_different_order(self, topic): 66 | expect(topic).to_be_like((3, 2, 1)) 67 | 68 | class OfStrings(Vows.Context): 69 | def topic(self): 70 | return ["some", "string", "list"] 71 | 72 | def we_can_compare_to_other_list_in_different_order(self, topic): 73 | expect(topic).to_be_like(["list", "some", "string"]) 74 | 75 | class OfLists(Vows.Context): 76 | 77 | class WithinList(Vows.Context): 78 | def topic(self): 79 | return [["my", "list"], ["of", "lists"]] 80 | 81 | def we_can_compare_to_other_list_of_lists(self, topic): 82 | expect(topic).to_be_like((['lists', 'of'], ['list', 'my'])) 83 | 84 | class WithinTuple(Vows.Context): 85 | def topic(self): 86 | return (["my", "list"], ["of", "lists"]) 87 | 88 | def we_can_compare_to_other_list_of_lists(self, topic): 89 | expect(topic).to_be_like((['lists', 'of'], ['list', 'my'])) 90 | 91 | class OfDicts(Vows.Context): 92 | 93 | def topic(self): 94 | return [{'some': 'key', 'other': 'key'}] 95 | 96 | def we_can_compare_to_other_list_of_dicts(self, topic): 97 | expect(topic).to_be_like([{'some': 'key', 'other': 'key'}]) 98 | 99 | def we_can_compare_to_other_list_of_dicts_out_of_order(self, topic): 100 | expect(topic).to_be_like([{'other': 'key', 'some': 'key'}]) 101 | 102 | class WhenItIsATuple(Vows.Context): 103 | 104 | class OfNumbers(Vows.Context): 105 | def topic(self): 106 | return (1, 2, 3) 107 | 108 | def we_can_compare_to_other_tuple(self, topic): 109 | expect(topic).to_be_like((1, 2, 3)) 110 | 111 | def we_can_compare_to_a_tuple_in_different_order(self, topic): 112 | expect(topic).to_be_like((3, 2, 1)) 113 | 114 | def we_can_compare_to_a_list_in_different_order(self, topic): 115 | expect(topic).to_be_like([3, 2, 1]) 116 | 117 | class WhenItIsADict(Vows.Context): 118 | 119 | def topic(self): 120 | return {'some': 'key', 'other': 'value'} 121 | 122 | def we_can_compare_to_other_dict(self, topic): 123 | expect(topic).to_be_like({'some': 'key', 'other': 'value'}) 124 | 125 | def we_can_compare_to_a_dict_in_other_order(self, topic): 126 | expect(topic).to_be_like({'other': 'value', 'some': 'key'}) 127 | 128 | def we_can_compare_to_a_dict_with_a_key_that_has_value_none(self, topic): 129 | expect(topic).not_to_be_like({'other': 'value', 'some': None}) 130 | 131 | class OfDicts(Vows.Context): 132 | 133 | def topic(self): 134 | return { 135 | 'some': { 136 | 'key': 'value', 137 | 'key2': 'value2' 138 | } 139 | } 140 | 141 | def we_can_compare_to_nested_dicts(self, topic): 142 | expect(topic).to_be_like({ 143 | 'some': { 144 | 'key2': 'value2', 145 | 'key': 'value' 146 | } 147 | }) 148 | 149 | class WhenWeGetAnError(Vows.Context): 150 | @Vows.capture_error 151 | def topic(self, last): 152 | expect('a').to_be_like('b') 153 | 154 | def we_get_an_understandable_message(self, topic): 155 | expect(topic).to_have_an_error_message_of("Expected topic('a') to be like 'b'") 156 | 157 | class WhenWeGetAnErrorOnNot(Vows.Context): 158 | @Vows.capture_error 159 | def topic(self, last): 160 | expect('a').not_to_be_like('a') 161 | 162 | def we_get_an_understandable_message(self, topic): 163 | expect(topic).to_have_an_error_message_of("Expected topic('a') not to be like 'a'") 164 | -------------------------------------------------------------------------------- /tests/assertions/types/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heynemann/pyvows/431abffebf44144411e5b9049f1289bd93b1004b/tests/assertions/types/__init__.py -------------------------------------------------------------------------------- /tests/assertions/types/boolean_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | 13 | 14 | @Vows.batch 15 | class AssertionIsTrue(Vows.Context): 16 | 17 | class WhenBoolean(Vows.Context): 18 | def topic(self): 19 | return True 20 | 21 | def we_can_assert_it_is_true(self, topic): 22 | expect(topic).to_be_true() 23 | 24 | class WhenNumber(Vows.Context): 25 | def topic(self): 26 | return 1 27 | 28 | def we_can_assert_number_is_true(self, topic): 29 | expect(topic).to_be_true() 30 | 31 | class WhenString(Vows.Context): 32 | def topic(self): 33 | return 'some' 34 | 35 | def we_can_assert_string_is_true(self, topic): 36 | expect(topic).to_be_true() 37 | 38 | class WhenList(Vows.Context): 39 | def topic(self): 40 | return ['some'] 41 | 42 | def we_can_assert_list_is_true(self, topic): 43 | expect(topic).to_be_true() 44 | 45 | class WhenDict(Vows.Context): 46 | def topic(self): 47 | return {'some': 'key'} 48 | 49 | def we_can_assert_dict_is_true(self, topic): 50 | expect(topic).to_be_true() 51 | 52 | class WhenWeGetAnError(Vows.Context): 53 | 54 | @Vows.capture_error 55 | def topic(self, last): 56 | expect(False).to_be_true() 57 | 58 | def we_get_an_understandable_message(self, topic): 59 | expect(topic).to_have_an_error_message_of("Expected topic(False) to be truthy") 60 | 61 | 62 | @Vows.batch 63 | class AssertionIsFalse(Vows.Context): 64 | 65 | class WhenBoolean(Vows.Context): 66 | def topic(self): 67 | return False 68 | 69 | def we_can_assert_it_is_false(self, topic): 70 | expect(topic).to_be_false() 71 | 72 | class WhenNumber(Vows.Context): 73 | def topic(self): 74 | return 0 75 | 76 | def we_can_assert_zero_is_false(self, topic): 77 | expect(topic).to_be_false() 78 | 79 | class WhenNone(Vows.Context): 80 | def topic(self): 81 | return None 82 | 83 | def we_can_assert_none_is_false(self, topic): 84 | expect(topic).to_be_false() 85 | 86 | class WhenString(Vows.Context): 87 | def topic(self): 88 | return '' 89 | 90 | def we_can_assert_empty_string_is_false(self, topic): 91 | expect(topic).to_be_false() 92 | 93 | class WhenList(Vows.Context): 94 | def topic(self): 95 | return [] 96 | 97 | def we_can_assert_empty_list_is_false(self, topic): 98 | expect(topic).to_be_false() 99 | 100 | class WhenDict(Vows.Context): 101 | def topic(self): 102 | return {} 103 | 104 | def we_can_assert_empty_dict_is_false(self, topic): 105 | expect(topic).to_be_false() 106 | 107 | class WhenWeGetAnError(Vows.Context): 108 | 109 | @Vows.capture_error 110 | def topic(self): 111 | expect(True).to_be_false() 112 | 113 | def we_get_an_understandable_message(self, topic): 114 | expect(topic).to_have_an_error_message_of("Expected topic(True) to be falsy") 115 | -------------------------------------------------------------------------------- /tests/assertions/types/classes_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | 13 | 14 | class SomeClass(object): 15 | pass 16 | 17 | 18 | class OtherClass(object): 19 | pass 20 | 21 | 22 | @Vows.batch 23 | class AssertionIsInstance(Vows.Context): 24 | def topic(self): 25 | return SomeClass() 26 | 27 | class WhenIsInstance(Vows.Context): 28 | 29 | def we_get_an_instance_of_someclass(self, topic): 30 | expect(topic).to_be_instance_of(SomeClass) 31 | 32 | class WhenWeGetAnError(Vows.Context): 33 | 34 | @Vows.capture_error 35 | def topic(self, last): 36 | expect(2).to_be_instance_of(str) 37 | 38 | def we_get_an_understandable_message(self, topic): 39 | msg = 'Expected topic(2) to be an instance of {0!r}, but it was a {1!r}'.format(str, int) 40 | expect(topic).to_have_an_error_message_of(msg) 41 | 42 | class WhenIsNotInstance(Vows.Context): 43 | 44 | def we_do_not_get_an_instance_of_otherclass(self, topic): 45 | expect(topic).Not.to_be_instance_of(OtherClass) 46 | 47 | class WhenWeGetAnError(Vows.Context): 48 | 49 | @Vows.capture_error 50 | def topic(self, last): 51 | expect(2).not_to_be_instance_of(int) 52 | 53 | def we_get_an_understandable_message(self, topic): 54 | expect(topic).to_have_an_error_message_of( 55 | 'Expected topic(2) not to be an instance of {0!s}'.format(int)) 56 | -------------------------------------------------------------------------------- /tests/assertions/types/errors_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | from preggy import utils 13 | 14 | 15 | @Vows.batch 16 | class AssertionErrors(Vows.Context): 17 | class NonErrors(Vows.Context): 18 | def topic(self): 19 | return 0 20 | 21 | def we_can_see_that_is_not_an_error(self, topic): 22 | expect(topic).Not.to_be_an_error() 23 | 24 | class Errors(Vows.Context): 25 | def topic(self, error): 26 | return ValueError('some bogus error') 27 | 28 | def we_can_see_that_is_an_error_class(self, topic): 29 | expect(topic).to_be_an_error() 30 | 31 | def we_can_see_it_was_a_value_error(self, topic): 32 | expect(topic).to_be_an_error_like(ValueError) 33 | 34 | def we_can_see_that_is_has_error_message_of(self, topic): 35 | expect(topic).to_have_an_error_message_of('some bogus error') 36 | 37 | class ErrorMessages(Vows.Context): 38 | @Vows.capture_error 39 | def topic(self, last): 40 | raise Exception('1 does not equal 2') 41 | 42 | def we_get_an_understandable_message(self, topic): 43 | expect(topic).to_have_an_error_message_of('1 does not equal 2') 44 | 45 | class WhenErrorMessagesDoNotMatch(Vows.Context): 46 | def topic(self, last): 47 | try: 48 | expect(last).to_have_an_error_message_of('some bogus') 49 | except AssertionError as e: 50 | return e 51 | 52 | def we_get_an_understandable_message(self, topic): 53 | expected_message = "Expected topic({0!r}) to be an error with message {1!r}".format( 54 | utils.text_type(ValueError('some bogus error')), 55 | 'some bogus' 56 | ) 57 | expect(topic).to_have_an_error_message_of(expected_message) 58 | 59 | 60 | class ToBeAnError(Vows.Context): 61 | def we_can_see_that_is_an_error_instance(self, topic): 62 | expect(topic).to_be_an_error() 63 | 64 | class WhenWeGetAnError(Vows.Context): 65 | @Vows.capture_error 66 | def topic(self, last): 67 | expect(2).to_be_an_error() 68 | 69 | def we_get_an_understandable_message(self, topic): 70 | expect(topic).to_have_an_error_message_of("Expected topic(2) to be an error") 71 | 72 | 73 | class NotToBeAnError(Vows.Context): 74 | def topic(self): 75 | return 2 76 | 77 | def we_can_see_that_is_not_an_error_instance(self, topic): 78 | expect(topic).not_to_be_an_error() 79 | 80 | 81 | class WhenWeGetAnError(Vows.Context): 82 | def topic(self, last): 83 | try: 84 | expect(last).to_be_an_error() 85 | except AssertionError as e: 86 | return e, last 87 | 88 | def we_get_an_understandable_message(self, topic): 89 | expect(topic[0]).to_have_an_error_message_of("Expected topic({0}) to be an error".format(topic[1])) 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /tests/assertions/types/file_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | 13 | 14 | # TEST DATA 15 | STRINGS = { 16 | 'that_are_files': ( 17 | __file__, 18 | (__file__.decode('utf8') 19 | if isinstance(__file__, bytes) \ 20 | else __file__), 21 | ), 22 | 23 | 'that_are_not_files': ( 24 | __doc__, 25 | ) 26 | } 27 | 28 | 29 | # HELPERS 30 | isafile = lambda topic: expect(topic).to_be_a_file() 31 | isnotafile = lambda topic: expect(topic).not_to_be_a_file() 32 | 33 | 34 | # NOW, MAKE YOUR VOWS. 35 | 36 | @Vows.batch 37 | class WhenMakingFileAssertions(Vows.Context): 38 | # @TODO: Clean up this repetitive test code 39 | # 40 | # Preferable one of the following: 41 | # 42 | # - context inheritance 43 | # http://pyvows.org/#-context-inheritance 44 | # 45 | # - generative testing 46 | # http://pyvows.org/#-using-generative-testing 47 | 48 | class OnFilesThatDoNotExist(Vows.Context): 49 | def topic(self): 50 | for item in STRINGS['that_are_not_files']: 51 | yield item 52 | 53 | class AssertingThatTheyDo(Vows.Context): 54 | @Vows.capture_error 55 | def topic(self, parent_topic): 56 | return isafile(parent_topic) 57 | 58 | def should_raise_an_error(self, topic): 59 | expect(topic).to_be_an_error_like(AssertionError) 60 | 61 | class AssertingThatTheyDoNot(Vows.Context): 62 | @Vows.capture_error 63 | def topic(self, parent_topic): 64 | return isnotafile(parent_topic) 65 | 66 | def should_raise_no_errors(self, topic): 67 | expect(topic).Not.to_be_an_error() 68 | 69 | class OnFilesThatDoExist(Vows.Context): 70 | def topic(self): 71 | for item in STRINGS['that_are_files']: 72 | yield item 73 | 74 | class AssertingTheyAreFiles(Vows.Context): 75 | @Vows.capture_error 76 | def topic(self, parent_topic): 77 | return isafile(parent_topic) 78 | 79 | def should_not_raise_errors(self, topic): 80 | expect(topic).not_to_be_an_error() 81 | 82 | class AssertingTheyAreNotFiles(Vows.Context): 83 | @Vows.capture_error 84 | def topic(self, parent_topic): 85 | return isnotafile(parent_topic) 86 | 87 | def should_raise_an_error(self, topic): 88 | expect(topic).to_be_an_error() 89 | 90 | class WhenWeInstantiateThemAsFileObjects(Vows.Context): 91 | def topic(self, parent_topic): 92 | f = open(parent_topic) 93 | return f 94 | 95 | class AssertingTheyAreFiles(Vows.Context): 96 | @Vows.capture_error 97 | def topic(self, parent_topic): 98 | return isafile(parent_topic) 99 | 100 | def should_not_raise_errors(self, topic): 101 | expect(topic).not_to_be_an_error() 102 | 103 | class AssertingTheyAreNotFiles(Vows.Context): 104 | @Vows.capture_error 105 | def topic(self, parent_topic): 106 | return isnotafile(parent_topic) 107 | 108 | def should_raise_an_error(self, topic): 109 | expect(topic).to_be_an_error() 110 | -------------------------------------------------------------------------------- /tests/assertions/types/function_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | 13 | 14 | def a_function(): 15 | pass 16 | 17 | 18 | @Vows.batch 19 | class AssertionIsFunction(Vows.Context): 20 | 21 | class WhenItIsAFunction(Vows.Context): 22 | def topic(self): 23 | def my_func(): 24 | pass 25 | return my_func 26 | 27 | def we_assert_it_is_a_function(self, topic): 28 | expect(topic).to_be_a_function() 29 | 30 | class WhenWeGetAnError(Vows.Context): 31 | 32 | @Vows.capture_error 33 | def topic(self): 34 | expect(4).to_be_a_function() 35 | 36 | def we_get_an_understandable_message(self, topic): 37 | msg = 'Expected topic(4) to be a function or a method, but it was a {0!s}'.format(int) 38 | expect(topic).to_have_an_error_message_of(msg) 39 | 40 | class WhenItNotAFunction(Vows.Context): 41 | def topic(self): 42 | return 42 43 | 44 | def we_assert_it_is_not_a_function(self, topic): 45 | expect(topic).Not.to_be_a_function() 46 | 47 | class WhenWeGetAnError(Vows.Context): 48 | 49 | @Vows.capture_error 50 | def topic(self): 51 | expect(a_function).not_to_be_a_function() 52 | 53 | def we_get_an_understandable_message(self, topic): 54 | msg = 'Expected topic({0!s}) not to be a function or a method'.format(a_function) 55 | expect(topic).to_have_an_error_message_of(msg) 56 | -------------------------------------------------------------------------------- /tests/assertions/types/nullable_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | 13 | 14 | @Vows.batch 15 | class AssertionIsNull(Vows.Context): 16 | 17 | class WhenItIsNull(Vows.Context): 18 | def topic(self): 19 | return None 20 | 21 | def we_get_to_check_for_nullability_in_None(self, topic): 22 | expect(topic).to_be_null() 23 | 24 | class WhenWeGetAnError(Vows.Context): 25 | @Vows.capture_error 26 | def topic(self, last): 27 | expect(1).to_be_null() 28 | 29 | def we_get_an_understandable_message(self, topic): 30 | expect(topic).to_have_an_error_message_of("Expected topic(1) to be None") 31 | 32 | class WhenItIsNotNull(Vows.Context): 33 | def topic(self): 34 | return "something" 35 | 36 | def we_see_string_is_not_null(self, topic): 37 | expect(topic).not_to_be_null() 38 | 39 | class WhenWeGetAnError(Vows.Context): 40 | @Vows.capture_error 41 | def topic(self, last): 42 | expect(None).not_to_be_null() 43 | 44 | def we_get_an_understandable_message(self, topic): 45 | expect(topic).to_have_an_error_message_of("Expected topic(None) not to be None") 46 | -------------------------------------------------------------------------------- /tests/assertions/types/numeric_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | 13 | 14 | @Vows.batch 15 | class AssertionIsNumeric(Vows.Context): 16 | 17 | class WhenItIsANumber(Vows.Context): 18 | def topic(self): 19 | return 42 20 | 21 | def we_assert_it_is_numeric(self, topic): 22 | expect(topic).to_be_numeric() 23 | 24 | class WhenWeGetAnError(Vows.Context): 25 | 26 | @Vows.capture_error 27 | def topic(self): 28 | expect('s').to_be_numeric() 29 | 30 | def we_get_an_understandable_message(self, topic): 31 | expect(topic).to_have_an_error_message_of("Expected topic('s') to be numeric") 32 | 33 | class WhenItIsNotANumber(Vows.Context): 34 | def topic(self): 35 | return 'test' 36 | 37 | def we_assert_it_is_not_numeric(self, topic): 38 | expect(topic).Not.to_be_numeric() 39 | 40 | class WhenWeGetAnError(Vows.Context): 41 | 42 | @Vows.capture_error 43 | def topic(self): 44 | expect(2).not_to_be_numeric() 45 | 46 | def we_get_an_understandable_message(self, topic): 47 | expect(topic).to_have_an_error_message_of("Expected topic(2) not to be numeric") 48 | -------------------------------------------------------------------------------- /tests/assertions/types/regexp_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | 13 | 14 | @Vows.batch 15 | class AssertionRegexp(Vows.Context): 16 | def topic(self): 17 | return "some string" 18 | 19 | class WhenItMatches(Vows.Context): 20 | 21 | def we_assert_it_matches_regexp(self, topic): 22 | expect(topic).to_match(r'^some.+$') 23 | 24 | class WhenWeGetAnError(Vows.Context): 25 | 26 | @Vows.capture_error 27 | def topic(self, last): 28 | expect(last).to_match(r'^other.+$') 29 | 30 | def we_get_an_understandable_message(self, topic): 31 | expect(topic).to_have_an_error_message_of( 32 | "Expected topic('some string') to match the regular expression '^other.+$'") 33 | 34 | class WhenItDoesntMatches(Vows.Context): 35 | 36 | def we_assert_it_does_not_match_regexp(self, topic): 37 | expect(topic).Not.to_match(r'^other.+$') 38 | 39 | class WhenWeGetAnError(Vows.Context): 40 | 41 | @Vows.capture_error 42 | def topic(self, last): 43 | expect(last).not_to_match(r'^some.+$') 44 | 45 | def we_get_an_understandable_message(self, topic): 46 | expect(topic).to_have_an_error_message_of( 47 | "Expected topic('some string') not to match the regular expression '^some.+$'") 48 | -------------------------------------------------------------------------------- /tests/async_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | import time 12 | 13 | from pyvows import Vows, expect 14 | 15 | #------------------------------------------------------------------------------------------------- 16 | 17 | def asyncFunc(pool, callback): 18 | def _async(): 19 | time.sleep(0.1) 20 | return 10 21 | 22 | def get_value(value): 23 | callback(value, 20, kwarg=30, kw2=40) 24 | pool.apply_async(_async, callback=get_value) 25 | 26 | #------------------------------------------------------------------------------------------------- 27 | 28 | @Vows.batch 29 | class AsyncTopic(Vows.Context): 30 | @Vows.async_topic 31 | def topic(self, callback): 32 | asyncFunc(self.pool, callback) 33 | 34 | def should_check_the_first_parameter(self, topic): 35 | expect(topic[0]).to_equal(10) 36 | 37 | def should_check_the_second_parameter(self, topic): 38 | expect(topic.args[1]).to_equal(20) 39 | 40 | def should_check_the_kwarg_parameter(self, topic): 41 | expect(topic.kwarg).to_equal(30) 42 | 43 | def should_check_the_kwarg_parameter_accesing_from_topic_as_dict(self, topic): 44 | expect(topic['kwarg']).to_equal(30) 45 | 46 | def should_check_the_kw2_parameter(self, topic): 47 | expect(topic.kw['kw2']).to_equal(40) 48 | 49 | class SyncTopic(Vows.Context): 50 | def topic(self): 51 | return 1 52 | 53 | def should_be_1(self, topic): 54 | expect(topic).to_equal(1) 55 | 56 | class NestedAsyncTest(Vows.Context): 57 | @Vows.async_topic 58 | def topic(self, callback, old_topic): 59 | def cb(*args, **kw): 60 | args = (old_topic,) + args 61 | return callback(*args, **kw) 62 | asyncFunc(self.pool, cb) 63 | 64 | def should_be_the_value_of_the_old_topic(self, topic): 65 | expect(topic.args[0]).to_equal(1) 66 | 67 | class NestedSyncTopic(Vows.Context): 68 | def topic(self): 69 | return 1 70 | 71 | def should_be_1(self, topic): 72 | expect(topic).to_equal(1) 73 | -------------------------------------------------------------------------------- /tests/bugs/64_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # unbreaking some pyvows 5 | 6 | # This file is MIT licensed 7 | # http://www.opensource.org/licenses/mit-license 8 | # Copyright (c) 2013 nathan dotz 9 | 10 | from pyvows import Vows, expect 11 | from pyvows.result import VowsResult 12 | from pyvows.reporting import VowsTestReporter # , VowsDefaultReporter 13 | 14 | try: 15 | from StringIO import StringIO 16 | except: 17 | from io import StringIO 18 | 19 | 20 | @Vows.batch 21 | class VowsTestReporterExceptions(Vows.Context): 22 | 23 | def topic(self): 24 | v = VowsTestReporter(VowsResult(), 0) 25 | return v 26 | 27 | def should_not_raise_TypeError_on_tests_without_a_topic(self, topic): 28 | try: 29 | # Notice that the test dict here has no 'topic' key. 30 | test = {'name': 'Mock Test Result', 31 | 'succeeded': False, 32 | 'context_instance': Vows.Context(), 33 | 'error': {'type': '', 34 | 'value': '', 35 | 'traceback': ''}, 36 | 'skip': None 37 | } 38 | context = {'tests': [test], 39 | 'contexts': [] 40 | } 41 | output = StringIO() 42 | topic.print_context('Derp', context, file=output) 43 | except AssertionError as e: 44 | expect(e).to_be_an_error_like(AssertionError) 45 | expect(e).Not.to_be_an_error_like(TypeError) 46 | -------------------------------------------------------------------------------- /tests/bugs/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # pyvows testing engine 4 | # https://github.com/heynemann/pyvows 5 | 6 | # Licensed under the MIT license: 7 | # http://www.opensource.org/licenses/mit-license 8 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 9 | -------------------------------------------------------------------------------- /tests/captured_output_vows.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys 3 | 4 | from pyvows import Vows, expect 5 | from pyvows.runner.gevent import VowsParallelRunner 6 | from pyvows.runner.executionplan import ExecutionPlanner 7 | from pyvows.runner import VowsRunner 8 | 9 | 10 | @Vows.batch 11 | class CapturedOutputVows(Vows.Context): 12 | 13 | class ResultsFromContextThatExplicitlyCapturesOutput(Vows.Context): 14 | def topic(self): 15 | dummySuite = {'dummySuite': set([OutputSomeStuff])} 16 | execution_plan = ExecutionPlanner(dummySuite, set(), set()).plan() 17 | runner = VowsRunner(dummySuite, Vows.Context, None, None, execution_plan, False) 18 | return runner.run() 19 | 20 | def results_are_successful(self, topic): 21 | expect(topic.successful).to_equal(True) 22 | 23 | class TopContextStdout(Vows.Context): 24 | def topic(self, results): 25 | return results.contexts[0]['stdout'] 26 | 27 | def has_setup_topic_teardown(self, topic): 28 | expect(topic).to_equal('setup\ntopic\nteardown\n') 29 | 30 | class TopContextStderr(Vows.Context): 31 | def topic(self, results): 32 | return results.contexts[0]['stderr'] 33 | 34 | def has_setup_topic_teardown_err(self, topic): 35 | expect(topic).to_equal('setup-err\ntopic-err\nteardown-err\n') 36 | 37 | class SubcontextStdout(Vows.Context): 38 | def topic(self, results): 39 | return results.contexts[0]['contexts'][0]['stdout'] 40 | 41 | def has_subcontext_topic(self, topic): 42 | expect(topic).to_equal('subcontext-topic\n') 43 | 44 | class SubcontextStderr(Vows.Context): 45 | def topic(self, results): 46 | return results.contexts[0]['contexts'][0]['stderr'] 47 | 48 | def has_subcontext_topic_err(self, topic): 49 | expect(topic).to_equal('subcontext-topic-err\n') 50 | 51 | class TopContextVowStdout(Vows.Context): 52 | def topic(self, results): 53 | return results.contexts[0]['tests'][0]['stdout'] 54 | 55 | def has_vow(self, topic): 56 | expect(topic).to_equal('vow\n') 57 | 58 | class TopContextVowStderr(Vows.Context): 59 | def topic(self, results): 60 | return results.contexts[0]['tests'][0]['stderr'] 61 | 62 | def has_vow_err(self, topic): 63 | expect(topic).to_equal('vow-err\n') 64 | 65 | class ResultsFromContextThatPrintsWhenSysStreamsArePatched(ResultsFromContextThatExplicitlyCapturesOutput): 66 | def topic(self): 67 | dummySuite = {'dummySuite': set([PrintSomeStuff])} 68 | execution_plan = ExecutionPlanner(dummySuite, set(), set()).plan() 69 | runner = VowsRunner(dummySuite, Vows.Context, None, None, execution_plan, True) 70 | return runner.run() 71 | 72 | 73 | class OutputSomeStuff(Vows.Context): 74 | def setup(self): 75 | VowsParallelRunner.output.stdout.write('setup\n') 76 | VowsParallelRunner.output.stderr.write('setup-err\n') 77 | 78 | def topic(self): 79 | VowsParallelRunner.output.stdout.write('topic\n') 80 | VowsParallelRunner.output.stderr.write('topic-err\n') 81 | 82 | def teardown(self): 83 | VowsParallelRunner.output.stdout.write('teardown\n') 84 | VowsParallelRunner.output.stderr.write('teardown-err\n') 85 | 86 | def vow(self, topic): 87 | VowsParallelRunner.output.stdout.write('vow\n') 88 | VowsParallelRunner.output.stderr.write('vow-err\n') 89 | 90 | class OutputFromSubcontext(Vows.Context): 91 | def topic(self): 92 | VowsParallelRunner.output.stdout.write('subcontext-topic\n') 93 | VowsParallelRunner.output.stderr.write('subcontext-topic-err\n') 94 | 95 | 96 | class PrintSomeStuff(Vows.Context): 97 | def setup(self): 98 | print('setup') 99 | print('setup-err', file=sys.stderr) 100 | 101 | def topic(self): 102 | print('topic') 103 | print('topic-err', file=sys.stderr) 104 | 105 | def teardown(self): 106 | print('teardown') 107 | print('teardown-err', file=sys.stderr) 108 | 109 | def vow(self, topic): 110 | print('vow') 111 | print('vow-err', file=sys.stderr) 112 | 113 | class PrintFromSubcontext(Vows.Context): 114 | def topic(self): 115 | print('subcontext-topic') 116 | print('subcontext-topic-err', file=sys.stderr) 117 | -------------------------------------------------------------------------------- /tests/cli_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | import argparse 11 | 12 | from pyvows import Vows, expect 13 | from pyvows.cli import Parser 14 | 15 | 16 | mock_args = ( 17 | '--cover', 18 | '--profile', 19 | ) 20 | 21 | 22 | @Vows.batch 23 | class PyVowsCommandLineInterface(Vows.Context): 24 | 25 | class ArgumentParser(Vows.Context): 26 | def topic(self): 27 | # suppress the defaults, or the test breaks :/ 28 | parser = Parser(argument_default=argparse.SUPPRESS) 29 | return parser 30 | 31 | def we_have_a_parser(self, topic): 32 | expect(topic).to_be_instance_of(argparse.ArgumentParser) 33 | 34 | def we_dont_get_an_error(self, topic): 35 | expect(topic).not_to_be_an_error() 36 | 37 | class ParsesCorrectly(Vows.Context): 38 | def topic(self, parser): 39 | return parser.parse_args(mock_args) 40 | 41 | def should_contain_cover(self, topic): 42 | expect('cover' in topic).to_be_true() 43 | 44 | def cover_should_be_true(self, topic): 45 | expect(topic.cover).to_be_true() 46 | 47 | def profile_should_be_true(self, topic): 48 | expect(topic.profile).to_be_true() 49 | -------------------------------------------------------------------------------- /tests/context_inheritance_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | 12 | from pyvows import Vows, expect 13 | 14 | 15 | class NotAContextThingy(object): 16 | alicorns = (u'Celestia', u'Luna', u'Nyx', u'Pluto', u'Lauren Faust') 17 | 18 | def get_alicorns(self): 19 | return self.alicorns 20 | 21 | 22 | class BaseContext(Vows.Context): 23 | 24 | # First case: Thingy should be ignored. 25 | Thingy = None 26 | 27 | def topic(self, ponies): 28 | self.ignore('Thingy', 'BaseSubcontext') 29 | return (self.Thingy, ponies) 30 | 31 | # Second case: BaseSubcontext should be ignored. 32 | class BaseSubcontext(Vows.Context): 33 | 34 | def topic(self, v): 35 | (Thingy, ponies) = v 36 | self.ignore('prepare') 37 | for pony in ponies: 38 | yield (Thingy, self.prepare(pony)) 39 | 40 | def prepare(self, something): 41 | raise NotImplementedError 42 | 43 | def pony_has_name(self, topic): 44 | expect(topic).to_be_true() 45 | 46 | 47 | @Vows.batch 48 | class PonyVows(Vows.Context): 49 | 50 | def topic(self): 51 | return ('Nyx', 'Pluto') 52 | 53 | class ActualContext(BaseContext): 54 | 55 | Thingy = NotAContextThingy 56 | 57 | class ActualSubcontext(BaseContext.BaseSubcontext): 58 | 59 | def prepare(self, something): 60 | return something.decode('utf8') if isinstance(something, bytes) else something 61 | 62 | def pony_is_alicorn(self, v): 63 | (Thingy, pony) = v 64 | expect(Thingy.alicorns).to_include(pony) 65 | -------------------------------------------------------------------------------- /tests/division_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from __future__ import division # Python 2 division is deprecated 12 | from pyvows import Vows, expect 13 | 14 | 15 | @Vows.batch 16 | class DivisionTests(Vows.Context): 17 | 18 | class WhenDividing42Per1(Vows.Context): 19 | 20 | def topic(self): 21 | return 42 / 1 22 | 23 | def WeGetANumber(self, topic): 24 | expect(topic).to_be_numeric() 25 | 26 | def WeGet42(self, topic): 27 | expect(topic).to_equal(42) 28 | -------------------------------------------------------------------------------- /tests/docstring_vows.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import types 4 | 5 | from pyvows import Vows, expect 6 | 7 | 8 | from pyvows import ( 9 | __init__ as pyvows_init, 10 | __main__, 11 | async_topic, 12 | color, 13 | cli, 14 | core, 15 | runner, 16 | version) 17 | from pyvows.reporting import ( 18 | __init__ as reporting_init, 19 | common as reporting_common, 20 | coverage as reporting_coverage, 21 | profile as reporting_profile, 22 | test as reporting_test, 23 | xunit as reporting_xunit) 24 | 25 | PYVOWS_MODULES = ( 26 | # general modules 27 | pyvows_init, 28 | __main__, 29 | async_topic, 30 | color, 31 | cli, 32 | core, 33 | runner, 34 | version, 35 | # reporting 36 | reporting_init, 37 | reporting_common, 38 | reporting_coverage, 39 | reporting_profile, 40 | reporting_test, 41 | reporting_xunit,) 42 | 43 | 44 | @Vows.assertion 45 | def to_have_a_docstring(topic): 46 | '''Custom assertion. Raises a AssertionError if `topic` has no 47 | docstring. 48 | 49 | ''' 50 | if not hasattr(topic, '__doc__'): 51 | raise AssertionError('Expected topic({0}) to have a docstring', topic) 52 | 53 | 54 | @Vows.batch 55 | class EachPyvowsModule(Vows.Context): 56 | def topic(self): 57 | for mod in PYVOWS_MODULES: 58 | if isinstance(mod, types.ModuleType): 59 | yield mod 60 | 61 | def should_have_a_docstring(self, topic): 62 | expect(topic).to_have_a_docstring() 63 | -------------------------------------------------------------------------------- /tests/errors_in_topic_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2013 Richard Lupton r.lupton@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | from pyvows.runner import VowsRunner, SkipTest 13 | from pyvows.runner.executionplan import ExecutionPlanner 14 | 15 | 16 | # These tests demonstrate what happens when the topic function raises 17 | # or returns an exception. 18 | 19 | @Vows.batch 20 | class ErrorsInTopicFunction(Vows.Context): 21 | 22 | class WhenTopicRaisesAnExceptionWithCaptureErrorDecorator: 23 | @Vows.capture_error 24 | def topic(self): 25 | return 42 / 0 26 | 27 | def it_is_passed_to_tests_as_normal(self, topic): 28 | expect(topic).to_be_an_error_like(ZeroDivisionError) 29 | 30 | class WhenTopicReturnsAnException(Vows.Context): 31 | def topic(self): 32 | try: 33 | return 42 / 0 34 | except Exception as e: 35 | return e 36 | 37 | def it_is_passed_to_tests_as_normal(self, topic): 38 | expect(topic).to_be_an_error_like(ZeroDivisionError) 39 | 40 | class WhenTopicRaisesAnUnexpectedException(Vows.Context): 41 | def topic(self): 42 | dummySuite = {'dummySuite': set([WhenTopicRaisesAnException])} 43 | execution_plan = ExecutionPlanner(dummySuite, set(), set()).plan() 44 | runner = VowsRunner(dummySuite, Vows.Context, None, None, execution_plan, False) 45 | return runner.run() 46 | 47 | def results_are_not_successful(self, topic): 48 | expect(topic.successful).to_equal(False) 49 | 50 | class ResultForTopContextVows(Vows.Context): 51 | def topic(self, results): 52 | return results.contexts[0]['tests'][0] 53 | 54 | def skip_is_set_to_a_skiptest_exception(self, topic): 55 | expect(topic['skip']).to_be_an_error_like(SkipTest) 56 | 57 | def skip_message_relates_reason_as_topic_failure(self, topic): 58 | expect(str(topic['skip'])).to_include('topic dependency failed') 59 | 60 | class ResultForSubContext(Vows.Context): 61 | def topic(self, results): 62 | return results.contexts[0]['contexts'][0] 63 | 64 | def skip_is_set_to_a_skiptest_exception(self, topic): 65 | expect(topic['skip']).to_be_an_error_like(SkipTest) 66 | 67 | def skip_message_relates_reason_as_topic_failure(self, topic): 68 | expect(str(topic['skip'])).to_include('topic dependency failed') 69 | 70 | def vows_and_subcontexts_and_teardown_are_not_called(self, topic): 71 | expect(WhenTopicRaisesAnException.functionsCalled).to_equal(0) 72 | 73 | class WhenSubcontextTopicRaisesAnException(Vows.Context): 74 | def topic(self): 75 | dummySuite = {'dummySuite': set([WhenTeardownIsDefined])} 76 | execution_plan = ExecutionPlanner(dummySuite, set(), set(['excluded_vows_do_not_block'])).plan() 77 | runner = VowsRunner(dummySuite, Vows.Context, None, None, execution_plan, False) 78 | return runner.run() 79 | 80 | def results_are_not_successful(self, topic): 81 | expect(topic.successful).to_equal(False) 82 | 83 | def ancestor_teardowns_are_all_called(self, topic): 84 | expect(WhenTeardownIsDefined.teardownCalled).to_equal(True) 85 | expect( 86 | WhenTeardownIsDefined.WhenSubcontextTeardownIsDefined. 87 | teardownCalled 88 | ).to_equal(True) 89 | 90 | class WhenTeardownRaisesAnException(Vows.Context): 91 | def topic(self): 92 | dummySuite = {'dummySuite': set([WhenTeardownRaisesException])} 93 | execution_plan = ExecutionPlanner(dummySuite, set(), set()).plan() 94 | runner = VowsRunner(dummySuite, Vows.Context, None, None, execution_plan, False) 95 | return runner.run() 96 | 97 | def results_are_not_successful(self, topic): 98 | expect(topic.successful).to_equal(False) 99 | 100 | 101 | class WhenTopicRaisesAnException(Vows.Context): 102 | functionsCalled = 0 103 | 104 | def topic(self): 105 | return 42 / 0 106 | 107 | def teardown(self): 108 | WhenTopicRaisesAnException.functionsCalled += 1 109 | 110 | def tests_should_not_run(self, topic): 111 | WhenTopicRaisesAnException.functionsCalled += 1 112 | 113 | class SubContext(Vows.Context): 114 | def subcontexts_should_also_not_run(self, topic): 115 | WhenTopicRaisesAnException.functionsCalled += 1 116 | 117 | 118 | class WhenTeardownIsDefined(Vows.Context): 119 | teardownCalled = False 120 | 121 | def teardown(self): 122 | WhenTeardownIsDefined.teardownCalled = True 123 | 124 | class WhenSubcontextTeardownIsDefined(Vows.Context): 125 | teardownCalled = False 126 | 127 | def teardown(self): 128 | WhenTeardownIsDefined.\ 129 | WhenSubcontextTeardownIsDefined.teardownCalled = True 130 | 131 | def excluded_vows_do_not_block(self, topic): 132 | raise Exception('This will never pass') 133 | 134 | class WhenTopicRaisesAnException(Vows.Context): 135 | def topic(self): 136 | return 42 / 0 137 | 138 | 139 | class WhenTeardownRaisesException(Vows.Context): 140 | def teardown(self): 141 | raise Exception('omg') 142 | -------------------------------------------------------------------------------- /tests/filter_vows_to_run_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2013 Nathan Dotz nathan.dotz@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | from pyvows import cli 13 | from pyvows.runner import VowsRunner 14 | from pyvows.runner.executionplan import ExecutionPlanner 15 | 16 | 17 | class VowsTestCopy1(Vows): 18 | exclusion_patterns = set() 19 | inclusion_patterns = set() 20 | suites = dict() 21 | 22 | 23 | class VowsTestCopy2(Vows): 24 | exclusion_patterns = set() 25 | inclusion_patterns = set() 26 | suites = dict() 27 | 28 | 29 | @Vows.batch 30 | class FilterOutVowsFromCommandLine(Vows.Context): 31 | 32 | class Console(Vows.Context): 33 | def topic(self): 34 | return cli 35 | 36 | def should_hand_off_exclusions_to_Vows_class(self, topic): 37 | 38 | patterns = ['foo', 'bar', 'baz'] 39 | try: 40 | topic.run(None, '*_vows.py', 2, False, patterns) 41 | except Exception: 42 | expect(Vows.exclusion_patterns).to_equal(patterns) 43 | 44 | def should_hand_off_inclusions_to_Vows_class(self, topic): 45 | 46 | patterns = ['foo', 'bar', 'baz'] 47 | try: 48 | topic.run(None, '*_vows.py', 2, False, None, patterns) 49 | except Exception: 50 | expect(Vows.inclusion_patterns).to_equal(patterns) 51 | 52 | # TODO: add vow checking that there is a message about vow matching 53 | 54 | class Core(Vows.Context): 55 | 56 | def topic(self): 57 | return VowsTestCopy1 58 | 59 | class RunMethod(Vows.Context): 60 | 61 | class UsingAnExclusionPattern(Vows.Context): 62 | def topic(self): 63 | VowsTestCopy1.exclusion_patterns = ['asdf'] 64 | VowsTestCopy1.suites = { 65 | 'dummySuite': [TestContext] 66 | } 67 | return VowsTestCopy1.run(None, None) 68 | 69 | def should_not_run_the_excluded_vow(self, topic): 70 | expect(topic.contexts[0]['tests']).to_equal([]) 71 | 72 | class UsingAnInclusionPattern(Vows.Context): 73 | def topic(self): 74 | VowsTestCopy2.inclusion_patterns = ['asdf'] 75 | VowsTestCopy2.suites = { 76 | 'dummySuite': [TestContext2] 77 | } 78 | return VowsTestCopy2.run(None, None) 79 | 80 | def should_only_run_asdf(self, topic): 81 | expect([t['name'] for t in topic.contexts[0]['tests']]).to_equal(['asdf']) 82 | 83 | class ResultsOfRunningTwoContextsWithConflictingIgnores(Vows.Context): 84 | 85 | def topic(self): 86 | dummySuite = {'dummySuite': set([OverlappingIgnores1, OverlappingIgnores2])} 87 | execution_plan = ExecutionPlanner(dummySuite, set(), set()).plan() 88 | runner = VowsRunner(dummySuite, Vows.Context, None, None, execution_plan) 89 | return runner.run() 90 | 91 | def context1_ran_only_test2(self, topic): 92 | testsRan = [context for context in topic.contexts if context['name'] == 'OverlappingIgnores1'][0]['tests'] 93 | testNames = [t['name'] for t in testsRan] 94 | expect(testNames).to_equal(['test2']) 95 | 96 | def context2_ran_only_test1(self, topic): 97 | testsRan = [context for context in topic.contexts if context['name'] == 'OverlappingIgnores2'][0]['tests'] 98 | testNames = [t['name'] for t in testsRan] 99 | expect(testNames).to_equal(['test1']) 100 | 101 | 102 | class TestContext(Vows.Context): 103 | def topic(self): 104 | return 'test' 105 | 106 | def asdf(self, topic): 107 | expect(topic).to_equal('test') 108 | 109 | 110 | class TestContext2(Vows.Context): 111 | def topic(self): 112 | return 'test' 113 | 114 | def asdf(self, topic): 115 | expect(topic).to_equal('test') 116 | 117 | def fdsa(self, topic): 118 | expect(topic).to_equal('test') 119 | 120 | 121 | class OverlappingIgnores1(Vows.Context): 122 | def topic(self): 123 | self.ignore('test1') 124 | return 1 125 | 126 | def test1(self, topic): 127 | expect(topic).to_equal(1) 128 | 129 | def test2(self, topic): 130 | expect(topic).to_equal(1) 131 | 132 | 133 | class OverlappingIgnores2(Vows.Context): 134 | def topic(self): 135 | self.ignore('test2') 136 | return 1 137 | 138 | def test1(self, topic): 139 | expect(topic).to_equal(1) 140 | 141 | def test2(self, topic): 142 | expect(topic).to_equal(1) 143 | -------------------------------------------------------------------------------- /tests/fruits_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | 13 | 14 | class Strawberry(object): 15 | def __init__(self): 16 | self.color = '#ff0000' 17 | 18 | def isTasty(self): 19 | return True 20 | 21 | 22 | class PeeledBanana(object): 23 | pass 24 | 25 | 26 | class Banana(object): 27 | def __init__(self): 28 | self.color = '#fff333' 29 | 30 | def peel(self): 31 | return PeeledBanana() 32 | 33 | 34 | @Vows.batch 35 | class TheGoodThings(Vows.Context): 36 | class AStrawberry(Vows.Context): 37 | def topic(self): 38 | return Strawberry() 39 | 40 | def is_red(self, topic): 41 | expect(topic.color).to_equal('#ff0000') 42 | 43 | def and_tasty(self, topic): 44 | expect(topic.isTasty()).to_be_true() 45 | 46 | class ABanana(Vows.Context): 47 | def topic(self): 48 | return Banana() 49 | 50 | class WhenPeeled(Vows.Context): 51 | def topic(self, banana): 52 | return banana.peel() 53 | 54 | def returns_a_peeled_banana(self, topic): 55 | expect(topic).to_be_instance_of(PeeledBanana) 56 | -------------------------------------------------------------------------------- /tests/generator_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | 13 | 14 | def get_test_data(): 15 | for i in [1] * 10: 16 | yield i 17 | 18 | 19 | @Vows.batch 20 | class GeneratorTests(Vows.Context): 21 | def topic(self): 22 | return get_test_data() 23 | 24 | def should_be_numeric(self, topic): 25 | expect(topic).to_equal(1) 26 | 27 | class SubContext(Vows.Context): 28 | def topic(self, parent_topic): 29 | return parent_topic 30 | 31 | def should_be_executed_many_times(self, topic): 32 | expect(topic).to_equal(1) 33 | 34 | class SubSubContext(Vows.Context): 35 | def topic(self, parent_topic, outer_topic): 36 | return outer_topic 37 | 38 | def should_be_executed_many_times(self, topic): 39 | expect(topic).to_equal(1) 40 | 41 | class GeneratorAgainContext(Vows.Context): 42 | def topic(self, topic): 43 | for i in range(10): 44 | yield topic * 2 45 | 46 | def should_return_topic_times_two(self, topic): 47 | expect(topic).to_equal(2) 48 | 49 | 50 | def add(a, b): 51 | return a + b 52 | 53 | a_samples = range(10) 54 | b_samples = range(10) 55 | 56 | 57 | @Vows.batch 58 | class Add(Vows.Context): 59 | class ATopic(Vows.Context): 60 | def topic(self): 61 | for a in a_samples: 62 | yield a 63 | 64 | class BTopic(Vows.Context): 65 | def topic(self, a): 66 | for b in b_samples: 67 | yield b 68 | 69 | class Sum(Vows.Context): 70 | def topic(self, b, a): 71 | yield (add(a, b), a + b) 72 | 73 | def should_be_numeric(self, topic): 74 | value, expected = topic 75 | expect(value).to_be_numeric() 76 | 77 | def should_equal_to_expected(self, topic): 78 | value, expected = topic 79 | expect(value).to_equal(expected) 80 | -------------------------------------------------------------------------------- /tests/multiple_topic_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | 13 | 14 | @Vows.batch 15 | class MultipleTopics(Vows.Context): 16 | class FirstLevel(Vows.Context): 17 | def topic(self): 18 | return 'a' 19 | 20 | def is_a(self, topic): 21 | expect(topic).to_equal('a') 22 | 23 | class SecondLevel(Vows.Context): 24 | def topic(self, first): 25 | return (first, 'b') 26 | 27 | def still_a(self, topic): 28 | expect(topic[0]).to_equal('a') 29 | 30 | def is_b(self, topic): 31 | expect(topic[1]).to_equal('b') 32 | 33 | class ThirdLevel(Vows.Context): 34 | def topic(self, second, first): 35 | return (first, second[1], 'c') 36 | 37 | def still_a(self, topic): 38 | expect(topic[0]).to_equal('a') 39 | 40 | def still_b(self, topic): 41 | expect(topic[1]).to_equal('b') 42 | 43 | def is_c(self, topic): 44 | expect(topic[2]).to_equal('c') 45 | -------------------------------------------------------------------------------- /tests/no_subcontext_extension_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | 13 | 14 | @Vows.batch 15 | class ContextClass(Vows.Context): 16 | entered = False 17 | 18 | def topic(self): 19 | return 1 20 | 21 | def should_be_working_fine(self, topic): 22 | expect(topic).to_equal(1) 23 | 24 | def teardown(self): 25 | # note to readers: 'expect's are not recommended on teardown methods 26 | expect(self.entered).to_equal(True) 27 | 28 | class SubcontextThatDoesntNeedToExtendAgainFromContext: 29 | entered = False 30 | 31 | def topic(self): 32 | return 2 33 | 34 | def should_be_working_fine_too(self, topic): 35 | self.parent.entered = True 36 | expect(topic).to_equal(2) 37 | 38 | def teardown(self): 39 | # note to readers: 'expect's are not recommended on teardown methods 40 | expect(self.entered).to_equal(True) 41 | 42 | class SubcontextThatDoesntNeedToExtendAgainFromContext: 43 | def topic(self): 44 | return 3 45 | 46 | def should_be_working_fine_too(self, topic): 47 | self.parent.entered = True 48 | expect(topic).to_equal(3) 49 | -------------------------------------------------------------------------------- /tests/prune_execution_vows.py: -------------------------------------------------------------------------------- 1 | from pyvows import Vows, expect 2 | from pyvows.runner.executionplan import ExecutionPlanner 3 | 4 | 5 | @Vows.batch 6 | class DiscoveringExecutionTree(Vows.Context): 7 | 8 | class NormalBatch(Vows.Context): 9 | def topic(self): 10 | planner = ExecutionPlanner( 11 | {'dummySuite': set([UnrunnableBatch])}, 12 | set([]), 13 | set([]) 14 | ) 15 | return planner.plan() 16 | 17 | def the_tree_matches(self, topic): 18 | baseline = { 19 | 'dummySuite': { 20 | 'contexts': { 21 | 'UnrunnableBatch': { 22 | 'name': 'UnrunnableBatch', 23 | 'id': 'UnrunnableBatch', 24 | 'vows': [], 25 | 'contexts': { 26 | 'SubA': { 27 | 'name': 'SubA', 28 | 'id': 'UnrunnableBatch.SubA', 29 | 'vows': [], 30 | 'contexts': {} 31 | }, 32 | 'SubB': { 33 | 'name': 'SubB', 34 | 'id': 'UnrunnableBatch.SubB', 35 | 'vows': ['testB_0'], 36 | 'contexts': { 37 | 'SubC': { 38 | 'name': 'SubC', 39 | 'id': 'UnrunnableBatch.SubB.SubC', 40 | 'vows': ['testB1_0', 'testB1_1'], 41 | 'contexts': {} 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | expect(topic).to_equal(baseline) 51 | 52 | class WithExclusionPatternForContext(Vows.Context): 53 | def topic(self): 54 | planner = ExecutionPlanner( 55 | {'dummySuite': set([UnrunnableBatch])}, 56 | set([r'S[bu]{2}B']), 57 | set([]) 58 | ) 59 | return planner.plan() 60 | 61 | def the_excluded_context_is_not_included(self, topic): 62 | baseline = { 63 | 'dummySuite': { 64 | 'contexts': { 65 | 'UnrunnableBatch': { 66 | 'name': 'UnrunnableBatch', 67 | 'id': 'UnrunnableBatch', 68 | 'vows': [], 69 | 'contexts': { 70 | 'SubA': { 71 | 'name': 'SubA', 72 | 'id': 'UnrunnableBatch.SubA', 73 | 'vows': [], 74 | 'contexts': {} 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } 81 | expect(topic).to_equal(baseline) 82 | 83 | class WithExclusionPatternForBatch(Vows.Context): 84 | def topic(self): 85 | planner = ExecutionPlanner( 86 | {'dummySuite': set([UnrunnableBatch])}, 87 | set([r'UnrunnableBatch']), 88 | set([]) 89 | ) 90 | return planner.plan() 91 | 92 | def the_excluded_context_is_not_included(self, topic): 93 | baseline = { 94 | 'dummySuite': { 95 | 'contexts': {} 96 | } 97 | } 98 | expect(topic).to_equal(baseline) 99 | 100 | class WithExclusionPatternForVow(Vows.Context): 101 | def topic(self): 102 | planner = ExecutionPlanner( 103 | {'dummySuite': set([UnrunnableBatch])}, 104 | set([r'testB1.*']), 105 | set([]) 106 | ) 107 | return planner.plan() 108 | 109 | def the_excluded_context_is_not_included(self, topic): 110 | baseline = { 111 | 'dummySuite': { 112 | 'contexts': { 113 | 'UnrunnableBatch': { 114 | 'name': 'UnrunnableBatch', 115 | 'id': 'UnrunnableBatch', 116 | 'vows': [], 117 | 'contexts': { 118 | 'SubA': { 119 | 'name': 'SubA', 120 | 'id': 'UnrunnableBatch.SubA', 121 | 'vows': [], 122 | 'contexts': {} 123 | }, 124 | 'SubB': { 125 | 'name': 'SubB', 126 | 'id': 'UnrunnableBatch.SubB', 127 | 'vows': ['testB_0'], 128 | 'contexts': { 129 | 'SubC': { 130 | 'name': 'SubC', 131 | 'id': 'UnrunnableBatch.SubB.SubC', 132 | 'vows': [], 133 | 'contexts': {} 134 | } 135 | } 136 | } 137 | } 138 | } 139 | } 140 | } 141 | } 142 | expect(topic).to_equal(baseline) 143 | 144 | class WithBothInclusionAndExclusion(Vows.Context): 145 | @Vows.capture_error 146 | def topic(self): 147 | planner = ExecutionPlanner( 148 | {'dummySuite': set([UnrunnableBatch])}, 149 | set([r'testB1.*']), 150 | set([r'asdf']) 151 | ) 152 | return planner.plan() 153 | 154 | def exception_is_raised(self, topic): 155 | expect(topic).to_be_an_error() 156 | 157 | class WithInclusionPatternForOneContext(Vows.Context): 158 | def topic(self): 159 | planner = ExecutionPlanner( 160 | {'dummySuite': set([UnrunnableBatch, SomeLoneBatch])}, 161 | set([]), 162 | set([r'SubB$']) 163 | ) 164 | return planner.plan() 165 | 166 | def target_context_topic_and_ancestors_topics_are_included(self, topic): 167 | baseline = { 168 | 'dummySuite': { 169 | 'contexts': { 170 | 'UnrunnableBatch': { 171 | 'name': 'UnrunnableBatch', 172 | 'id': 'UnrunnableBatch', 173 | 'vows': [], 174 | 'contexts': { 175 | 'SubB': { 176 | 'name': 'SubB', 177 | 'id': 'UnrunnableBatch.SubB', 178 | 'vows': [], 179 | 'contexts': {} 180 | } 181 | } 182 | } 183 | } 184 | } 185 | } 186 | expect(topic).to_equal(baseline) 187 | 188 | class WithInclusionPatternForOneVow(Vows.Context): 189 | def topic(self): 190 | planner = ExecutionPlanner( 191 | {'dummySuite': set([UnrunnableBatch, SomeLoneBatch])}, 192 | set([]), 193 | set([r'testB1_0']) 194 | ) 195 | return planner.plan() 196 | 197 | def the_only_the_targeted_vow_is_run_on_the_context(self, topic): 198 | baseline = { 199 | 'dummySuite': { 200 | 'contexts': { 201 | 'UnrunnableBatch': { 202 | 'name': 'UnrunnableBatch', 203 | 'id': 'UnrunnableBatch', 204 | 'vows': [], 205 | 'contexts': { 206 | 'SubB': { 207 | 'name': 'SubB', 208 | 'id': 'UnrunnableBatch.SubB', 209 | 'vows': [], 210 | 'contexts': { 211 | 'SubC': { 212 | 'name': 'SubC', 213 | 'id': 'UnrunnableBatch.SubB.SubC', 214 | 'vows': ['testB1_0'], 215 | 'contexts': {} 216 | } 217 | } 218 | } 219 | } 220 | } 221 | } 222 | } 223 | } 224 | expect(topic).to_equal(baseline) 225 | 226 | class WithInclusionPatternHittingOnlyOneBatch(Vows.Context): 227 | def topic(self): 228 | planner = ExecutionPlanner( 229 | {'dummySuite': set([UnrunnableBatch, SomeOtherBatch])}, 230 | set([]), 231 | set([r'UnrunnableBatch']) 232 | ) 233 | return planner.plan() 234 | 235 | def the_other_batch_is_not_included(self, topic): 236 | baseline = { 237 | 'dummySuite': { 238 | 'contexts': { 239 | 'UnrunnableBatch': { 240 | 'name': 'UnrunnableBatch', 241 | 'id': 'UnrunnableBatch', 242 | 'vows': [], 243 | 'contexts': { 244 | 'SubA': { 245 | 'name': 'SubA', 246 | 'id': 'UnrunnableBatch.SubA', 247 | 'vows': [], 248 | 'contexts': {} 249 | }, 250 | 'SubB': { 251 | 'name': 'SubB', 252 | 'id': 'UnrunnableBatch.SubB', 253 | 'vows': ['testB_0'], 254 | 'contexts': { 255 | 'SubC': { 256 | 'name': 'SubC', 257 | 'id': 'UnrunnableBatch.SubB.SubC', 258 | 'vows': ['testB1_0', 'testB1_1'], 259 | 'contexts': {} 260 | } 261 | } 262 | } 263 | } 264 | } 265 | } 266 | } 267 | } 268 | expect(topic).to_equal(baseline) 269 | 270 | 271 | class UnrunnableBatch(Vows.Context): 272 | def topic(self): 273 | raise Exception('Never Run Me') 274 | 275 | class SubA(Vows.Context): 276 | pass 277 | 278 | class SubB(Vows.Context): 279 | def testB_0(self, topic): 280 | pass 281 | 282 | class SubC(Vows.Context): 283 | def testB1_0(self, topic): 284 | pass 285 | 286 | def testB1_1(self, topic): 287 | pass 288 | 289 | 290 | class SomeOtherBatch(UnrunnableBatch): 291 | pass 292 | 293 | 294 | class SomeLoneBatch(Vows.Context): 295 | pass 296 | -------------------------------------------------------------------------------- /tests/reporting/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heynemann/pyvows/431abffebf44144411e5b9049f1289bd93b1004b/tests/reporting/__init__.py -------------------------------------------------------------------------------- /tests/reporting/error_reporting_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2013 Richard Lupton r.lupton@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | from pyvows.reporting import VowsDefaultReporter 13 | from pyvows.runner.abc import VowsTopicError 14 | 15 | try: 16 | from StringIO import StringIO 17 | except: 18 | from io import StringIO 19 | 20 | # These tests check that the reporting, which happens after all tests 21 | # have run, correctly shows the errors raised in topic functions. 22 | 23 | @Vows.batch 24 | class ErrorReporting(Vows.Context): 25 | 26 | class TracebackOfTopicError: 27 | 28 | def setup(self): 29 | # The eval_context() method of the result object is called by 30 | # the reporter to decide if a context was successful or 31 | # not. Here we are testing the reporting of errors, so provide 32 | # a mock result which always says it has failed. 33 | class MockResult: 34 | def eval_context(self, context): 35 | return False 36 | self.reporter = VowsDefaultReporter(MockResult(), 0) 37 | 38 | # Patch the print_traceback() method to just record its 39 | # arguments. 40 | self.print_traceback_args = None 41 | 42 | def print_traceback(*args, **kwargs): 43 | self.print_traceback_args = args 44 | self.reporter.print_traceback = print_traceback 45 | 46 | class AContextWithATopicError: 47 | def topic(self): 48 | # Simulate a context whose topic() function raised an error 49 | mock_exc_info = ('type', 'value', 'traceback') 50 | context = { 51 | 'contexts': [], 52 | 'error': VowsTopicError('topic', mock_exc_info), 53 | 'filename': '/path/to/vows.py', 54 | 'name': 'TestContext', 55 | 'tests': [], 56 | 'topic_elapsed': 0 57 | } 58 | return context 59 | 60 | def reporter_should_call_print_traceback_with_the_exception(self, context): 61 | self.parent.print_traceback_args = None 62 | self.parent.reporter.print_context('TestContext', context, file=StringIO()) 63 | expect(self.parent.print_traceback_args).to_equal(('type', 'value', 'traceback')) 64 | 65 | class ASuccessfulContext: 66 | def topic(self): 67 | # Simulate a context whose topic() didn't raise an error 68 | context = { 69 | 'contexts': [], 70 | 'error': None, 71 | 'filename': '/path/to/vows.py', 72 | 'name': 'TestContext', 73 | 'tests': [], 74 | 'topic_elapsed': 0 75 | } 76 | return context 77 | 78 | def reporter_should_not_call_print_traceback(self, context): 79 | self.parent.print_traceback_args = None 80 | self.parent.reporter.print_context('TestContext', context, file=StringIO()) 81 | expect(self.parent.print_traceback_args).to_equal(None) 82 | -------------------------------------------------------------------------------- /tests/reporting/reporting_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | from pyvows.reporting import VowsDefaultReporter 13 | 14 | 15 | @Vows.batch 16 | class CoverageXMLParser(Vows.Context): 17 | 18 | def topic(self): 19 | return VowsDefaultReporter(None, 0) 20 | 21 | def should_be_an_instance_of_class(self, inst): 22 | expect(inst).to_be_instance_of(VowsDefaultReporter) 23 | 24 | class WhenParseCoverageXMLResult: 25 | """ 26 | {'overall': 99.0, 'classes': [ {'name': 'pyvows.cli', 'line_rate': 0.0568, 'uncovered_lines':[ 12, 13 ] }, ] } 27 | """ 28 | 29 | def topic(self, default_reporter): 30 | return default_reporter.parse_coverage_xml(''' 31 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | ''') 61 | 62 | def should_return_a_dict(self, result): 63 | expect(result).to_be_instance_of(dict) 64 | 65 | def should_contain_only_one_package(self, result): 66 | expect(len(result['classes'])).to_equal(2) 67 | 68 | def should_be_overall_99(self, result): 69 | expect(result['overall']).to_equal(0.99) 70 | 71 | class TheFirstClass(Vows.Context): 72 | 73 | def topic(self, result): 74 | return result['classes'][0] 75 | 76 | def should_be_pyvows_cli(self, klass): 77 | expect(klass['name']).to_equal('pyvows.cli') 78 | 79 | def should_contain_linehate(self, klass): 80 | expect(klass['line_rate']).to_equal(0.568) 81 | 82 | def should_contain_lines_uncovered(self, klass): 83 | expect(klass['uncovered_lines']).to_equal(['12', '14']) 84 | 85 | class TheSecondClass(Vows.Context): 86 | 87 | def topic(self, result): 88 | return result['classes'][1] 89 | 90 | def should_be_pyvowsconsole(self, klass): 91 | expect(klass['name']).to_equal('tests.bla') 92 | 93 | def should_contain_linehate(self, klass): 94 | expect(klass['line_rate']).to_equal(0.88) 95 | 96 | def should_contain_lines_uncovered(self, klass): 97 | expect(klass['uncovered_lines']).to_equal(['2', '3']) 98 | -------------------------------------------------------------------------------- /tests/reporting/xunit_reporter_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | from pyvows.reporting.xunit import XUnitReporter 13 | from pyvows.runner.abc import VowsTopicError 14 | import sys 15 | 16 | 17 | class ResultMock(): 18 | pass 19 | 20 | 21 | @Vows.batch 22 | class XunitReporterVows(Vows.Context): 23 | 24 | class WhenShowingZeroTests(Vows.Context): 25 | def topic(self): 26 | result = ResultMock() 27 | result.successful_tests = 0 28 | result.errored_tests = 0 29 | result.skipped_tests = 0 30 | result.total_test_count = 0 31 | result.elapsed_time = 0 32 | result.contexts = [] 33 | reporter = XUnitReporter(result) 34 | return reporter 35 | 36 | def should_create_xml_header(self, topic): 37 | expect(topic.to_xml().find(b'')).to_equal(0) 38 | 39 | def should_have_a_testsuite_node(self, topic): 40 | expect(topic.to_xml()).to_match(br'.*') 41 | 42 | class WithDocument(Vows.Context): 43 | def topic(self, topic): 44 | return topic.create_report_document() 45 | 46 | def should_have_a_testsuite_node(self, topic): 47 | expect(topic.firstChild.nodeName).to_equal('testsuite') 48 | 49 | class WhenShowingASuccessfulResult(Vows.Context): 50 | def topic(self): 51 | result = ResultMock() 52 | result.successful_tests = 1 53 | result.errored_tests = 0 54 | result.skipped_tests = 0 55 | result.total_test_count = 1 56 | result.elapsed_time = 0 57 | result.contexts = [ 58 | { 59 | 'name': 'Context1', 60 | 'tests': [ 61 | { 62 | 'name': 'Test1', 63 | 'succeeded': True, 64 | 'stdout': 'outline', 65 | 'stderr': 'errline' 66 | } 67 | ], 68 | 'contexts': [], 69 | 'stdout': 'outline', 70 | 'stderr': 'errline' 71 | } 72 | ] 73 | reporter = XUnitReporter(result) 74 | return reporter.create_report_document().firstChild 75 | 76 | def should_create_topic_and_test_node(self, topic): 77 | expect(len(topic.childNodes)).to_equal(2) 78 | 79 | class TopicTestcase(Vows.Context): 80 | def topic(self, suiteNode): 81 | return [node for node in suiteNode.childNodes if node.getAttribute('name') == 'topic'][0] 82 | 83 | def node_name_is_testcase(self, topic): 84 | expect(topic.nodeName).to_equal('testcase') 85 | 86 | def classname_attribute_is_context1(self, topic): 87 | expect(topic.getAttribute('classname')).to_equal('Context1') 88 | 89 | class OutputNode(Vows.Context): 90 | def topic(self, testcaseNode): 91 | return [node for node in testcaseNode.childNodes if node.nodeName == 'system-out'][0] 92 | 93 | def has_text_outline(self, topic): 94 | expect(topic.firstChild.data).to_equal('outline') 95 | 96 | class ErrorNode(Vows.Context): 97 | def topic(self, testcaseNode): 98 | return [node for node in testcaseNode.childNodes if node.nodeName == 'system-err'][0] 99 | 100 | def has_text_errline(self, topic): 101 | expect(topic.firstChild.data).to_equal('errline') 102 | 103 | class Test1Testcase(Vows.Context): 104 | def topic(self, suiteNode): 105 | return [node for node in suiteNode.childNodes if node.getAttribute('name') == 'Test1'][0] 106 | 107 | def node_name_is_testcase(self, topic): 108 | expect(topic.nodeName).to_equal('testcase') 109 | 110 | def classname_attribute_is_context1(self, topic): 111 | expect(topic.getAttribute('classname')).to_equal('Context1') 112 | 113 | class OutputNode(Vows.Context): 114 | def topic(self, testcaseNode): 115 | return [node for node in testcaseNode.childNodes if node.nodeName == 'system-out'][0] 116 | 117 | def has_text_outline(self, topic): 118 | expect(topic.firstChild.data).to_equal('outline') 119 | 120 | class ErrorNode(Vows.Context): 121 | def topic(self, testcaseNode): 122 | return [node for node in testcaseNode.childNodes if node.nodeName == 'system-err'][0] 123 | 124 | def has_text_errline(self, topic): 125 | expect(topic.firstChild.data).to_equal('errline') 126 | 127 | class WhenShowingATopicError(Vows.Context): 128 | def topic(self): 129 | try: 130 | raise Exception('asdf') 131 | except: 132 | test_exc_info = sys.exc_info() 133 | 134 | result = ResultMock() 135 | result.successful_tests = 1 136 | result.errored_tests = 0 137 | result.skipped_tests = 0 138 | result.total_test_count = 1 139 | result.elapsed_time = 0 140 | result.contexts = [ 141 | { 142 | 'name': 'Context1', 143 | 'tests': [], 144 | 'error': VowsTopicError('topic', test_exc_info), 145 | 'contexts': [], 146 | 'stdout': '', 147 | 'stderr': '' 148 | } 149 | ] 150 | reporter = XUnitReporter(result) 151 | return reporter.create_report_document().firstChild.firstChild 152 | 153 | class FailureNodeForTopic(Vows.Context): 154 | def topic(self, testcaseNode): 155 | return [node for node in testcaseNode.childNodes if node.nodeName == 'failure'][0] 156 | 157 | def should_have_original_exception_type(self, topic): 158 | expect(topic.getAttribute('type')).to_equal('Exception') 159 | 160 | def should_have_original_exception_message(self, topic): 161 | expect(topic.getAttribute('message')).to_equal('Error in topic: asdf') 162 | 163 | class WhenATopicIsACapturedExceptionAndAVowFails(Vows.Context): 164 | def topic(self): 165 | try: 166 | raise Exception('fdsa') 167 | except: 168 | test_exc_info = sys.exc_info() 169 | 170 | result = ResultMock() 171 | result.successful_tests = 1 172 | result.errored_tests = 1 173 | result.skipped_tests = 0 174 | result.total_test_count = 2 175 | result.elapsed_time = 0 176 | result.contexts = [ 177 | { 178 | 'name': 'ContextWithCapturedError', 179 | 'error': None, 180 | 'contexts': [], 181 | 'stdout': '', 182 | 'stderr': '', 183 | 'tests': [{ 184 | 'context_instance': Vows.Context(), 185 | 'name': 'failedCheckOnException', 186 | 'enumerated': False, 187 | 'result': None, 188 | 'topic': Exception('fdsa'), 189 | 'error': dict(zip(['type', 'value', 'traceback'], test_exc_info)), 190 | 'succeeded': False, 191 | 'file': 'asdf.py', 192 | 'lineno': 1, 193 | 'elapsed': 0, 194 | 'stdout': '', 195 | 'stderr': '' 196 | }] 197 | } 198 | ] 199 | reporter = XUnitReporter(result) 200 | return reporter.create_report_document().firstChild 201 | 202 | class TestcaseForTest(Vows.Context): 203 | def topic(self, suiteNode): 204 | return [node for node in suiteNode.childNodes if node.getAttribute('name') == 'failedCheckOnException'][0] 205 | 206 | class FailureNodeForTopic(Vows.Context): 207 | def topic(self, testcaseNode): 208 | return [node for node in testcaseNode.childNodes if node.nodeName == 'failure'][0] 209 | 210 | def should_have_original_exception_type(self, topic): 211 | expect(topic.getAttribute('type')).to_equal('Exception') 212 | 213 | def should_have_original_exception_message(self, topic): 214 | expect(topic.getAttribute('message')).to_equal('fdsa') 215 | -------------------------------------------------------------------------------- /tests/setup_teardown_vows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # pyvows testing engine 5 | # https://github.com/heynemann/pyvows 6 | 7 | # Licensed under the MIT license: 8 | # http://www.opensource.org/licenses/mit-license 9 | # Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com 10 | 11 | from pyvows import Vows, expect 12 | 13 | 14 | @Vows.batch 15 | class SetupTeardownSpecs(Vows.Context): 16 | exec_order = [] 17 | 18 | def teardown(self): 19 | expect(self.exec_order).to_equal([1, 2, 3, -2, -1]) 20 | 21 | class OrderOfExecution(Vows.Context): 22 | def setup(self): 23 | self.parent.exec_order.append(1) 24 | 25 | def topic(self): 26 | self.parent.exec_order.append(2) 27 | return 20 28 | 29 | def teardown(self): 30 | self.parent.exec_order.append(-1) # last 31 | expect(self.parent.exec_order).to_equal([1, 2, 3, -2, -1]) 32 | 33 | def check_order(self, topic): 34 | expect(self.parent.exec_order).to_equal([1, 2]) 35 | 36 | class TeardownOrderOfExecution(Vows.Context): 37 | def setup(self): 38 | self.parent.parent.exec_order.append(3) 39 | 40 | def teardown(self): 41 | self.parent.parent.exec_order.append(-2) # right before the parent's teardown 42 | expect(self.parent.parent.exec_order).to_equal([1, 2, 3, -2]) 43 | 44 | def check_order(self, topic): 45 | expect(self.parent.parent.exec_order).to_equal([1, 2, 3]) 46 | -------------------------------------------------------------------------------- /tests/utils_vows.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ## Generated by PyVows v2.0.0 (2013/05/25) 3 | ## http://pyvows.org 4 | 5 | ## IMPORTS ## 6 | ## 7 | ## Standard Library 8 | # 9 | ## Third Party 10 | # 11 | ## PyVows Testing 12 | from pyvows import Vows, expect 13 | 14 | ## Local Imports 15 | #import 16 | 17 | 18 | ## TESTS ## 19 | @Vows.batch 20 | class Utils(Vows.Context): 21 | 22 | def topic(self): 23 | return # return what you're going to test here 24 | 25 | ## Now, write some vows for your topic! :) 26 | def should_do_something(self, topic): 27 | expect(topic)# 28 | 29 | -------------------------------------------------------------------------------- /tests/version_vows.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from pyvows import Vows, expect 4 | import pyvows.version as pyvows_version 5 | 6 | 7 | @Vows.batch 8 | class PyvowsVersionModule(Vows.Context): 9 | def topic(self): 10 | return pyvows_version 11 | 12 | def has_a_docstring(self, topic): 13 | expect(hasattr(topic, '__doc__')).to_be_true() 14 | 15 | class VersionNumber(Vows.Context): 16 | def topic(self, topic): 17 | return topic.__version__ 18 | 19 | def should_be_a_tuple(self, topic): 20 | expect(topic).to_be_instance_of(tuple) 21 | 22 | def should_have_length_of_3(self, topic): 23 | expect(topic).to_length(3) 24 | 25 | def shoud_not_be_empty(self, topic): 26 | expect(topic).Not.to_be_empty() 27 | 28 | def should_not_be_None(self, topic): 29 | expect(topic).Not.to_be_null() 30 | 31 | class VersionString(Vows.Context): 32 | def topic(self, topic): 33 | return topic.to_str() 34 | 35 | def should_not_be_empty(self, topic): 36 | expect(topic).Not.to_be_empty() 37 | 38 | def should_not_be_None(self, topic): 39 | expect(topic).Not.to_be_null() 40 | 41 | def should_be_a_string(self, topic): 42 | expect(topic).to_be_instance_of(str) 43 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox (http://tox.testrun.org/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | envlist = py26, py27 8 | 9 | [testenv] 10 | commands = make ci_test 11 | --------------------------------------------------------------------------------