Source code for pychangelog.tests

#! /usr/bin/env python
# vim: set fileencoding=utf-8: 
"""
The ``pychangelog.tests`` module provides some helper functions and classes for
testing your changelog, as well as standard ``version`` modules.

A standard version module is simply a module which you distribute as part of your
package (usually called ``package_name.version``) which includes as public members
a certain set of standard attributes providing information about the version number
of the package. Specifically, it provides the following attributes, which you
can read more about in *this* package's `~pychangelog.version` module:
    * `~pychangelog.version.RELEASE`
    * `~pychangelog.version.MAJOR`
    * `~pychangelog.version.MINOR`
    * `~pychangelog.version.PATCH`
    * `~pychangelog.version.SEMANTIC`
    * `~pychangelog.version.SUFFIX`
    * `~pychangelog.version.YEAR`
    * `~pychangelog.version.MONTH`
    * `~pychangelog.version.DAY`


Nosetests
-------------

Some of the code in this module is intended to work with the nose_ test utility
for python. While none of it strictly depends on nose to function, the ``node``
python package will be imported and used to provide some additional convenience
if it is available.
"""

try:
    from nose.plugins.attrib import attr
except ImportError:
    def attr(*args, **kwargs):
        def wrapper(func):
            return func
        return wrapper

import pychangelog
import unittest

__all__ = []

def public(obj):
    __all__.append(obj.__name__)
    return obj

def raise_(err, *msg):
    if len(msg):
        err = str(msg[0]) + ' '  + err
    raise AssertionError(err)

def ok_(test, *msg):
    if test:
        pass
    else:
        raise_("%r does not evaluate as true." % (test,), *msg)

def none_(test, *msg):
    if test is None:
        pass
    else:
        raise_("%r is not None." % (test,), *msg)

def eq_(a, b, *msg):
    if a == b:
        pass
    else:
        raise_('%r != %r' % (a, b,), *msg)

@public
[docs]def verify_version_module(changelog, version): """ Generically tests the contents of a standard ``version`` module against the contents of the given `~pychangelog.ChangeLog` object, without any pre-existing assumptions about whether or not this is for a release or a development version. The latest full release (i.e., not a pre-release) in the changelog should correspond to the version and date information in the version module. If there is not full release in the change log, then the version module should list major version 0 and release 0. Additionally, if the change-log has no pre-release, then we should not be in development mode, meaning version.SUFFIX should be None. Otherwise, it should *not* be None. """ last_release = changelog.get_last_release() #Contents of the version module should always match the latest full-release in the changelog. if last_release is not None: eq_(last_release.release_num, version.RELEASE, "Release number is incorrect. Expected latest release in change log to match version.RELEASE.") eq_(last_release.version[0], version.MAJOR, "Major version number is incorrect. Expected latest release in change log to match version.MAJOR.") eq_(last_release.version[1], version.MINOR, "Minor version number is incorrect. Expected latest release in change log to match version.MINOR.") test_version = last_release.version[2] if (len(last_release.version) > 2) else 0 eq_(test_version, version.PATCH, "Patch version number is incorrect. Expected latest release in change log to match version.PATCH.") test_version = last_release.version[3] if (len(last_release.version) > 3) else 0 eq_(test_version, version.SEMANTIC, "Semantic version number is incorrect. Expected latest release in change log to match version.SEMANTIC.") eq_(last_release.year, version.YEAR, "Year is incorrect. Expected latest release in change log to match version.YEAR.") eq_(last_release.month, version.MONTH, "Month is incorrect. Expected latest release in change log to match version.MONTH.") eq_(last_release.day, version.DAY, "Day is incorrect. Expected latest release in change log to match version.DAY.") else: #If there's no release in the changelog, then the version module should be at major version 0. eq_(version.RELEASE, 0, "No releases in changelog, version.RELEASE should be 0.") eq_(version.MAJOR, 0, "No releases in changelog, version.MAJOR should be 0.") if changelog.last_release() == len(changelog) - 1: #The latest entry in the change log is a full release, so we should not have suffix none_(version.SUFFIX, "Change log has release ready, version.SUFFIX should be None.") else: #The latest entry in the change log if a pre-release, so we should have a development suffix. ok_(version.SUFFIX is not None, "Change log has pre-release, version.SUFFIX should not be None.")
@public
[docs]def verify_for_release(changelog, version): """ Tests a version module and change log for a release version. This calls `verify_version_module` to do the generic tests validating the version module against the changelog, and also tests that the version module and changelog are both correct for a release version. """ none_(version.SUFFIX, "version.SUFFIX should be None for a release version.") eq_(changelog.last_release(), len(changelog)-1, "Changelog should not have a pre-release listing for a release version.") verify_version_module(changelog, version)
@public
[docs]def verify_for_development(changelog, version): """ Tests a version module and change log for a release version. This calls `verify_version_module` to do the generic tests validating the version module against the changelog, and also tests that the version module and changelog are both correct for a release version. """ ok_(version.SUFFIX is not None, "version.SUFFIX should not be None for a development version.") ok_(changelog.last_release() != len(changelog)-1, "Changelog should have a pre-release listing for a development version.") verify_version_module(changelog, version)
@public
[docs]class StandardVersionTests(unittest.TestCase): """ This is a simple `~python:unittest.TestCase` class that can be easily extended for unittesting to validate your changelog and version module. All you need to do is subclass this class and set the `version_mod` attribute to the module which contains your project's standard version attributes. Alternatively, you can use the `create` factory method to create automatically create a new subclass with the specified version module. .. seealso:: * `get_path_to_changelog` can be overridden to change the path from which the changelog will be read. * `get_changelog` can be overridden to change the way in which the changelog is actually loaded and parsed. * `get_version_module` can be overridden to change the way the version module is fetched, instead of just getting it form the `version_mod` attribute. """ @classmethod
[docs] def create(cls, version_mod): """ Create and return a new subclass of `cls` which sets the `version_mod` attribute to the given version module. This is an alternative to statically subclassing if for some reason that's easier for you. """ return type('StandardVersionTests_', (cls,), dict(version_mod = version_mod))
version_mod = None """ The `version_mod` attribute should be set on a subclass of `StandardVersionTests` to the module which implememnts your package's standard version attributes. .. seealso:: `get_version_module` """
[docs] def get_path_to_changelog(self): """ Returns the filesystem path from which the changelog will be read by `get_changelog`. """ return 'CHANGES.txt'
[docs] def get_changelog(self): """ Called from `setUp` to get a `~pychangelog.ChangeLog` object to test with. The default implementation opens the path indicates by `get_path_to_changelog` and parses it using `~pychangelog.parse_plain_text`. """ with open(self.get_path_to_changelog(), 'r') as change_log_file: return pychangelog.parse_plain_text(change_log_file)
[docs] def get_version_module(self): """ Should returns a module which implements your projects standard version attributes. The default implementation returns the value of the `version_mod` attribute, which subclasses can easily set statically in the class definition. If this attribute value is ``None``, will raise a `NotImplementedError`. This is what will happen if you actually try to run an instance of this class directly, instead of subclassing it to override the value of the version_mod attribute. """ if self.version_mod is None: raise NotImplementedError('The version_mod attribute must be set in order to run the tests.') return self.version_mod
[docs] def setUp(self): """ Test setup sets a ``change_log`` attribute on the instance to the value returned by `get_changelog`. """ self.change_log = self.get_changelog()
[docs] def test_changelog(self): """ Just does the `setUp` to make sure that the changelog can be parsed and constructed. """ pass
[docs] def test_version_module(self): """ Invokes `verify_version_module`. """ verify_version_module(self.change_log, self.get_version_module())
@attr('release')
[docs] def test_for_release(self): """ Invokes `verify_for_release`. If nose_ is installed, this is tagged with the attribute ``release`` using the `~nose:nose.plugins.attrib` plugin. To omit this test, you can invoke :program:`nosetests` using the :std:option:`--attr` parameter, for instance in BASH as: .. code-block:: bash $ nosetest --attr '!release' or in DOS as: .. code-block:: dos > nosetests --attr !release """ verify_for_release(self.change_log, self.get_version_module())
@attr('dev')
[docs] def test_for_dev(self): """ Invokes `verify_for_development`. If nose_ is installed, this is tagged with the attribute ``dev`` using the `~nose:nose.plugins.attrib` plugin. To omit this test, you can invoke :program:`nosetests` using the :std:option:`--attr` parameter, for instance in BASH as: .. code-block:: bash $ nosetest --attr '!dev' or in DOS as: .. code-block:: dos > nosetests --attr !dev """ verify_for_development(self.change_log, self.get_version_module())