Loading .gitignore 0 → 100644 +2 −0 Original line number Diff line number Diff line # python generated *.py[cod] unittest_extra/__init__.py 0 → 100644 +22 −0 Original line number Diff line number Diff line # # This file is part of 'py-unittest-extra' # Copyright (C) 2015 Dom Sekotill # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # unittest_extra/coverage.py 0 → 100644 +137 −0 Original line number Diff line number Diff line # # This file is part of 'py-unittest-extra' # Copyright (C) 2015 Dom Sekotill # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # """ A module to produce coverage reports for testing. """ from six import string_types from importlib import import_module from pkgutil import walk_packages from itertools import product from functools import wraps import contextlib import itertools import logging import sys @contextlib.contextmanager def coverage(modules, include=None, exclude=None, file=None): """ Return a context manager that generates a coverage report for 'modules' 'include', 'exclude' and 'file' are as the arguments to coverage.coverage.report(). """ try: from coverage import coverage as Coverage except ImportError: logging.warning( "The code coverage module 'coverage' was not found in the " "search paths; there will be no coverage report." ) yield return old_modules = sys.modules.copy() modules = {import_module(m) if isinstance(m, string_types) else m for m in modules} for module in list(modules): try: walker = walk_packages(module.__path__, module.__name__+'.') except (AttributeError): modules.add(module) else: modules.update(import_module(n) for l,n,p in walker) for module in modules: del sys.modules[module.__name__] cover = Coverage() cover.start() try: yield finally: sys.modules.clear() sys.modules.update(old_modules) cover.stop() cover.save() with (file or sys.stdout) as file: if file.isatty(): file.write("\nCoverage Report:\n================\n") cover.report(list(modules), include=include, omit=exclude, file=file) def test_coverage_command(superclass, packages=None): """ Generate a distutils command class which is a subclass of 'superclass'. 'superclass' should be a testing command (eg. setuptools.commands.test.test) for which a coverage report will be generated at the end of a call to the run() method. 'packages' should be a list of packages to generate the report for; it defaults to the value of 'packages' passed to setup(). Run 'python setup.py <command> --help' to see the added command line arguments. """ class test(superclass): __doc__ = superclass.__doc__ user_options = superclass.user_options + [ ('with-coverage', None, "Include a coverage report (if the coverage module is installed)"), ('coverage-report=', None, "Write the coverage report to FILE (implies --with-coverage)"), ] @wraps(superclass.initialize_options) def initialize_options(self): super(test, self).initialize_options() self.with_coverage = False self.coverage_report = None @wraps(superclass.finalize_options) def finalize_options(self): super(test, self).finalize_options() if self.coverage_report: self.with_coverage = True self.coverage_report = open(self.coverage_report, 'w') @wraps(superclass.run) def run(self): if not self.with_coverage: return super(test, self).run() with coverage(packages or self.distribution.packages, file=self.coverage_report): super(test, self).run() return test unittest_extra/generator.py 0 → 100644 +151 −0 Original line number Diff line number Diff line # # This file is part of 'py-unittest-extra' # Copyright (C) 2015 Dom Sekotill # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # """ A module to increase test re-use, by running tests with multiple argument sets. """ import unittest from collections import Iterable from functools import wraps from six import string_types class test_generator(object): """ A class which wraps a test and calls it with different arguments. Used as a decorator on a test function, an instance of this class will be used by the TestGeneratorMeta class to generate multiple wrapped tests, each being called with a set of arguments passed to the constructor. The constructor takes one argument, which is either an iterable of arguments or a string giving the attribute name of an iterable of arguments in the class. The arguments themselves must be either: 1) An iterable of positional arguments, or 2) A mapping of keyword arguments, or 3) A 2-item object where item 0 matches (1) and item 1 matches (2) Examples: ``` class TestCase(unittest.TestCase, metaclass=TestGeneratorMeta): @test_generator([('foo', 'bar'), (1, 2)]) def some_test(self, a, b): # run test with a & b... ``` ``` class TestCase(unittest.TestCase, metaclass=TestGeneratorMeta): some_test_args = [ ('foo', 'bar'), (1, 2), ] @test_generator('some_test_args') def some_test(self, a, b): # run test with a & b... ``` ``` class TestCase(unittest.TestCase, metaclass=TestGeneratorMeta): some_test_args = [ (('foo', 'bar'), {'type': str}), (1, 2), ] @test_generator('some_test_args') def some_test(self, a, b, type=None): # run test with a & b... ``` """ def __init__(self, values): self.values = values def __call__(self, func): self.func = func return self def wrap(self, attr): """ Generate a test-wrapper with each set of arguments and add it to 'attr'. """ if isinstance(self.values, string_types): values = attr[self.values] else: values = self.values for name, func in self._wrap(values): attr[name] = func def _wrap(self, value_list): for number, values in enumerate(value_list): if self._is_args_kwds(values): args, kwds = values elif self._is_args(values): args = values kwds = {} elif self._is_kwds(values): kwds = values args = [] else: args = values, kwds = {} wrapper = self._make_wrap(args, kwds) yield 'test_{0}_{1}'.format(wrapper.__name__, number), wrapper yield self.func.__name__, self.func yield '_test_generator_' + self.func.__name__, self def _make_wrap(self, args, kwds): @wraps(self.func) def wrapper(*a): return self.func(*a + args, **kwds) return wrapper @staticmethod def _is_args(obj): return isinstance(obj, Iterable) and not isinstance(obj, string_types) @staticmethod def _is_kwds(obj): return isinstance(obj, dict) @classmethod def _is_args_kwds(cls, obj): try: return len(obj) == 2 and cls._is_args(obj[0]) and cls._is_args(obj[1]) except (TypeError): return False class TestGeneratorMeta(type): def __new__(cls, clsname, bases, attr): for name, obj in list(attr.items()): if isinstance(obj, test_generator): obj.wrap(attr) return super(TestGeneratorMeta).__new__(cls, clsname, bases, attr) Loading
.gitignore 0 → 100644 +2 −0 Original line number Diff line number Diff line # python generated *.py[cod]
unittest_extra/__init__.py 0 → 100644 +22 −0 Original line number Diff line number Diff line # # This file is part of 'py-unittest-extra' # Copyright (C) 2015 Dom Sekotill # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. #
unittest_extra/coverage.py 0 → 100644 +137 −0 Original line number Diff line number Diff line # # This file is part of 'py-unittest-extra' # Copyright (C) 2015 Dom Sekotill # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # """ A module to produce coverage reports for testing. """ from six import string_types from importlib import import_module from pkgutil import walk_packages from itertools import product from functools import wraps import contextlib import itertools import logging import sys @contextlib.contextmanager def coverage(modules, include=None, exclude=None, file=None): """ Return a context manager that generates a coverage report for 'modules' 'include', 'exclude' and 'file' are as the arguments to coverage.coverage.report(). """ try: from coverage import coverage as Coverage except ImportError: logging.warning( "The code coverage module 'coverage' was not found in the " "search paths; there will be no coverage report." ) yield return old_modules = sys.modules.copy() modules = {import_module(m) if isinstance(m, string_types) else m for m in modules} for module in list(modules): try: walker = walk_packages(module.__path__, module.__name__+'.') except (AttributeError): modules.add(module) else: modules.update(import_module(n) for l,n,p in walker) for module in modules: del sys.modules[module.__name__] cover = Coverage() cover.start() try: yield finally: sys.modules.clear() sys.modules.update(old_modules) cover.stop() cover.save() with (file or sys.stdout) as file: if file.isatty(): file.write("\nCoverage Report:\n================\n") cover.report(list(modules), include=include, omit=exclude, file=file) def test_coverage_command(superclass, packages=None): """ Generate a distutils command class which is a subclass of 'superclass'. 'superclass' should be a testing command (eg. setuptools.commands.test.test) for which a coverage report will be generated at the end of a call to the run() method. 'packages' should be a list of packages to generate the report for; it defaults to the value of 'packages' passed to setup(). Run 'python setup.py <command> --help' to see the added command line arguments. """ class test(superclass): __doc__ = superclass.__doc__ user_options = superclass.user_options + [ ('with-coverage', None, "Include a coverage report (if the coverage module is installed)"), ('coverage-report=', None, "Write the coverage report to FILE (implies --with-coverage)"), ] @wraps(superclass.initialize_options) def initialize_options(self): super(test, self).initialize_options() self.with_coverage = False self.coverage_report = None @wraps(superclass.finalize_options) def finalize_options(self): super(test, self).finalize_options() if self.coverage_report: self.with_coverage = True self.coverage_report = open(self.coverage_report, 'w') @wraps(superclass.run) def run(self): if not self.with_coverage: return super(test, self).run() with coverage(packages or self.distribution.packages, file=self.coverage_report): super(test, self).run() return test
unittest_extra/generator.py 0 → 100644 +151 −0 Original line number Diff line number Diff line # # This file is part of 'py-unittest-extra' # Copyright (C) 2015 Dom Sekotill # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # """ A module to increase test re-use, by running tests with multiple argument sets. """ import unittest from collections import Iterable from functools import wraps from six import string_types class test_generator(object): """ A class which wraps a test and calls it with different arguments. Used as a decorator on a test function, an instance of this class will be used by the TestGeneratorMeta class to generate multiple wrapped tests, each being called with a set of arguments passed to the constructor. The constructor takes one argument, which is either an iterable of arguments or a string giving the attribute name of an iterable of arguments in the class. The arguments themselves must be either: 1) An iterable of positional arguments, or 2) A mapping of keyword arguments, or 3) A 2-item object where item 0 matches (1) and item 1 matches (2) Examples: ``` class TestCase(unittest.TestCase, metaclass=TestGeneratorMeta): @test_generator([('foo', 'bar'), (1, 2)]) def some_test(self, a, b): # run test with a & b... ``` ``` class TestCase(unittest.TestCase, metaclass=TestGeneratorMeta): some_test_args = [ ('foo', 'bar'), (1, 2), ] @test_generator('some_test_args') def some_test(self, a, b): # run test with a & b... ``` ``` class TestCase(unittest.TestCase, metaclass=TestGeneratorMeta): some_test_args = [ (('foo', 'bar'), {'type': str}), (1, 2), ] @test_generator('some_test_args') def some_test(self, a, b, type=None): # run test with a & b... ``` """ def __init__(self, values): self.values = values def __call__(self, func): self.func = func return self def wrap(self, attr): """ Generate a test-wrapper with each set of arguments and add it to 'attr'. """ if isinstance(self.values, string_types): values = attr[self.values] else: values = self.values for name, func in self._wrap(values): attr[name] = func def _wrap(self, value_list): for number, values in enumerate(value_list): if self._is_args_kwds(values): args, kwds = values elif self._is_args(values): args = values kwds = {} elif self._is_kwds(values): kwds = values args = [] else: args = values, kwds = {} wrapper = self._make_wrap(args, kwds) yield 'test_{0}_{1}'.format(wrapper.__name__, number), wrapper yield self.func.__name__, self.func yield '_test_generator_' + self.func.__name__, self def _make_wrap(self, args, kwds): @wraps(self.func) def wrapper(*a): return self.func(*a + args, **kwds) return wrapper @staticmethod def _is_args(obj): return isinstance(obj, Iterable) and not isinstance(obj, string_types) @staticmethod def _is_kwds(obj): return isinstance(obj, dict) @classmethod def _is_args_kwds(cls, obj): try: return len(obj) == 2 and cls._is_args(obj[0]) and cls._is_args(obj[1]) except (TypeError): return False class TestGeneratorMeta(type): def __new__(cls, clsname, bases, attr): for name, obj in list(attr.items()): if isinstance(obj, test_generator): obj.wrap(attr) return super(TestGeneratorMeta).__new__(cls, clsname, bases, attr)