Hacking on Jeff Winkler's Nosy

I discovered Jason Pellerin's nose test discovery and execution tool when I started using TurboGears. My first forays into test driven development and automated Python testing weren't all that successful though. Then talks by Jonathan Hartley, and by Titus Brown at PyCon 2008, and a lobby chat with Jonathan just before I left Chicago got me thinking differently, and more seriously about automated testing. A few days later, the penny dropped, and I'm a convert!

Then I came across a little script called nosy that Jeff Winkler described in a (now vanished) blog post in April of 2006. nosy runs nose whenever a source file has changed. I'd thought about doing something like that but was thinking I'd have to use some kind of continuous integration system like buildbot, and that seemed like a whole lot of trouble for some little personal projects. Jeff's trick for calculating a checksum for the source files makes it all very, very easy. His original nosy.py script was less than 20 lines long!

I liked nosy on sight but after playing with it for a short time wanted to add features and flexibility. I like to keep my test code in a tests/ directory, separate from my project code. I wanted nose to run whenever I changed either the project code, or the test code. I wanted to be able to change the way nose ran, turning options like -s and -v on or off, and specifying particular parts of the test suite to run via command line arguments.

So, I decided that nosy needed the option of having a configuration file. Of course that meant that it needed a command line processor. ConfigParser and optparse from the Python standard library to the rescue, and we have my version of nosy:

$ nosy -h
Usage: nosy [options]

Automatically run test runner (nosetests, by default) whenever
source files change.

Options:
  -h, --help            show this help message and exit
  -c CONFIG_FILE, --config=CONFIG_FILE
                        configuration file path and name; defaults to
                        setup.cfg

In version 1.2 nosy

You should be able to install nosy with:

$ pip install -U nosy

Throw a section something like this into your setup.cfg file:

# Sample config file section for nosy

# Including this file in the paths to check allows you to change
# nose's behaviour on the fly.

[nosy]
# Test runner command to use; defaults to nosetests
# Other possibilites:
#test_runner = python manage.py test         # Django
#test_runner = python -m unittest discover   # Python 2.7 unittest
# Paths to check for changed files; changes cause test runner to be run
base_path = ./
glob_patterns = *.py
exclude_patterns = *_flymake.*
extra_paths = setup.cfg
# Command line options to pass to test runner
options = -x
# Command line arguments to pass to test runner;
# e.g. part of test suite to run
tests = tests/unit_tests.py

launch nosy:

$ nosy

and away you go. The specified test runner will run whenever any of the *.py files in your project, or setup.cfg change.

If you prefer, you can use a separate config file just for nosy, say tests/nosy.cfg, and launch nosy with:

$ nosy -c tests/nosy.cfg

As of version 1.1 nosy has new configuration options to make it easier to use in projects with deep directory structures. The checksum calculator now uses os.walk() starting from base_path, including files given by a list of glob_patterns, excluding those given by a list of exclude_patterns. Files given by the extra_paths list are also glob-ed in to handle anything that the first 3 options don't easily catch. Thanks to Jussi Rasinmäki for the suggestion.

Putting the config file itself in the list of files to watch (typically via the extra_paths option) allows you to change the behaviour of the test runner without having to stop and restart nosy.

options are the command line options to pass to the test runner. Use -v to turn on verbose output, or -a foo with nosetests to run only the tests with attribute foo.

tests are the command line arguments to pass to the test runner, used to select specific tests to run.

Changelog

v1.2 6-Dec-2015

  • Change exception handling to preserve backward compatibility with Python 2.5.
  • Add Vagrant and tox configurations for automation of testing against multiple Python versions.
  • Add support for Python 3.2, 3.3, 3.4, and 3.5. Thanks to @ronnix and @StefanWiechula for pull requests, and to @jacebrowning for in-use testing.
  • Fix test suite issues re: multiple Python versions.
  • Add test_runner option in the config file to specify the test runner command to use; defaults to nosetests.

v1.1.2 15-Sep-2011

  • Fix packaging errors. CHANGELOG was missing from MANIFEST.in, and setup.py failed under Python 2.5 due to the use of context managers.

v1.1.1 13-Sep-2011

  • Refactored checksum calculations to flatten nested loops and improve testability.
  • Added test suite.
  • Fixed a bug that caused nose not to run when exclude_patterns was empty. Thanks to Greg Haskins for the patch, and an anonymous issue reporter on Bitbucket.
  • Fixed a bug that caused files in the base_path not to be monitored. Thanks to Greg Haskins for the patch.

v1.1 21-Mar-2011

  • Configuration file now defaults to setup.cfg.
  • New configuration options to make it easier to use nosy in projects with deep directory structures. The checksum calculator now uses os.walk() starting from base_path, including files given by a list of glob_patterns, excluding those given by a list of exclude_patterns. Files given by the extra_paths list are also glob-ed in to handle anything that the first 3 options don't easily catch. Thanks to Jussi Rasinmäki for the suggestion.
  • The paths configuration option is retained for backward compatibility only.
  • Changed from setuptools to distribute for packaging.
  • Source code and issue tracker now on bitbucket.org: https://bitbucket.org/douglatornell/nosy
  • Released under New BSD License.
  • Released on PyPI: http://pypi.python.org/pypi/nosy

v1.0 13-Jun-2008

  • Original release.