forked from jeffh/sniffer
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Jeff Hui
committed
Jul 14, 2010
1 parent
4d4b4a9
commit 635f3b9
Showing
35 changed files
with
1,455 additions
and
166 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
ChangeLog | ||
========= | ||
|
||
0.1.1 | ||
----- | ||
- Added traceback for exceptions caught. | ||
- Fixed misnamed methods due to refactoring. | ||
|
||
0.1.0 | ||
----- | ||
- Initial Release |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
Copyright (c) 2010 Jeff Hui | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
LICENSE.txt | ||
README.rst | ||
setup.py | ||
sniffer/__init__.py | ||
sniffer/main.py | ||
sniffer/modules_restore_point.py | ||
sniffer/runner.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
include README.rst LICENSE.txt CHANGES.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
Overview | ||
======== | ||
|
||
``sniffer`` is a autotest tool for Python_ using the nosetest_ library. | ||
|
||
Sniffer will automatically re-run tests if your code changes. And with another third-party | ||
library (see below), the CPU usage of file system monitoring is reduced in comparison | ||
to pure-python solutions. However, sniffer will still work without any of those libraries. | ||
|
||
.. _Python: http://python.org/ | ||
.. _nosetest: http://code.google.com/p/python-nose/ | ||
|
||
Usage | ||
----- | ||
|
||
To install:: | ||
|
||
pip install nose | ||
pip install sniffer | ||
|
||
Simply run ``sniffer`` in your project directory. | ||
|
||
You can use ``sniffer --help`` for options And like autonose_, you can pass the nose | ||
arguments: ``-x--with-doctest`` or ``-x--config``. | ||
|
||
The problem with autonose_, is that the autodetect can be slow to detect changes. This is due | ||
to the pure python implementation - manually walking through the file system to see what's | ||
changed. Although the default install of sniffer shares the same problem, installing a | ||
third-party library can help fix the problem. The library is dependent on your operating system: | ||
|
||
- If you use **Linux**, you'll need to install pyinotify_. | ||
- If you use **Windows**, you'll need to install pywin32_. | ||
- If you use **Mac OS X** 10.5+ (Leopard), you'll need to install MacFSEvents_. | ||
|
||
**As a word of warning**, Windows and OSX libraries are *untested* as of now. This is because I | ||
haven't gotten around to testing in Windows, and I don't have a Mac :(. | ||
|
||
.. _nose: http://code.google.com/p/python-nose/ | ||
.. _easy_install: http://pypi.python.org/pypi/setuptools | ||
.. _pip: http://pypi.python.org/pypi/pip | ||
.. _autonose: http://github.com/gfxmonk/autonose | ||
.. _pyinotify: http://trac.dbzteam.org/pyinotify | ||
.. _pywin32: http://sourceforge.net/projects/pywin32/ | ||
.. _MacFSEvents: http://pypi.python.org/pypi/MacFSEvents/0.2.1 | ||
|
||
|
||
Other Uses | ||
========== | ||
|
||
Running with Other Test Frameworks | ||
---------------------------------- | ||
|
||
If you want to run another unit testing framework, you can do so by overriding ``sniffer.Sniffer``, | ||
which is the class that handles running tests, or whatever you want. Specifically, you'll want to | ||
override the ``run``, method to configure what you need to be done. | ||
|
||
The property, ``test_args``, are arguments gathered through ``--config=blah`` and ``-x.*`` | ||
configuration options. You should perform you imports inside the function instead of outside, | ||
to let the class reload the test framework (and reduce possibilities of multiple-run bugs). | ||
|
||
After subclassing, set sniffer_cls parameter to your custom class when calling run or main. | ||
|
||
Using the FileSystem monitoring code | ||
------------------------------------ | ||
|
||
If you simply want to use the file system monitor code, ``import sniffer.Scanner``. Behind | ||
the scenes, the library will figure out what libraries are available to use and which | ||
monitor technique to use. | ||
|
||
Right now, this is lacking some documentation, but here's a small example. | ||
|
||
Creating the scanner is simple:: | ||
|
||
from sniffer import Scanner | ||
|
||
paths = ('/path/to/watch/', '/another/path') | ||
scanner = Scanner(paths) | ||
|
||
Here we pass a tuple of paths to monitor. Now we need to get notification when events occur:: | ||
|
||
# when file is created | ||
scanner.observe('created', lambda path: print "Created", path) | ||
|
||
# when file is modified | ||
scanner.observe('modified', lambda path: print "Modified", path) | ||
|
||
# when file is deleted | ||
scanner.observe('deleted', lambda path: print "Deleted", path) | ||
|
||
# when scanner.loop() is called | ||
scanner.observe('init', lambda: print "Scanner started listening.") | ||
|
||
In addition, we can use the same function to listen to multiple events:: | ||
|
||
# listen to multiple events | ||
scanner.observe(('created', 'modified', 'deleted'), lambda path: "Triggered:", path) | ||
|
||
Finally, we start our blocking loop:: | ||
|
||
# blocks | ||
scanner.loop() | ||
|
||
Current Issues | ||
============== | ||
|
||
Being relatively new, there are bound to be bugs. Most notable is third-party libraries. | ||
Only the Linux install was tested (being in Ubuntu), so Windows & OSX implementations were | ||
done *without testing*. Patches are welcomed though :) | ||
|
||
For linux, there is an exception that is sometimes thrown when terminating. | ||
|
||
Currently the program only looks for changes in the current working directory. This isn't the | ||
best solution: it doesn't understand how changes to your source code affects it. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
""" | ||
Main runners. Bootloads Sniffer class. | ||
""" | ||
from optparse import OptionParser | ||
from scanner import Scanner | ||
from runner import Sniffer | ||
from metadata import __version__ | ||
import os | ||
import sys | ||
|
||
__all__ = ['run', 'main'] | ||
|
||
def run(sniffer_cls=Sniffer, wait_time=0.5, clear=True, args=(), debug=False): | ||
""" | ||
Runs the auto tester loop. Internally, the runner instanciates the sniffer_cls and | ||
scanner class. | ||
``sniffer_cls`` The class to run. Usually this is set to but a subclass of scanner. | ||
Defaults to Sniffer. Sniffer class documentation for more information. | ||
``wait_time`` The time, in seconds, to wait between polls. This is dependent on | ||
the underlying scanner implementation. OS-specific libraries may choose | ||
to ignore this parameter. Defaults to 0.5 seconds. | ||
``clear`` Boolean. Set to True to clear the terminal before running the sniffer, | ||
(alias, the unit tests). Defaults to True. | ||
``args`` The arguments to pass to the sniffer/test runner. Defaults to (). | ||
``debug`` Boolean. Sets the scanner and sniffer in debug mode, printing more internal | ||
information. Defaults to False (and should usually be False). | ||
""" | ||
if debug: | ||
scanner = Scanner(('.',), logger=sys.stdout) | ||
else: | ||
scanner = Scanner(('.',)) | ||
sniffer = sniffer_cls(tuple(args), clear, debug) | ||
sniffer.observe_scanner(scanner) | ||
scanner.loop(wait_time) | ||
|
||
def main(sniffer_cls=Sniffer, test_args=(), progname=sys.argv[0], args=sys.argv[1:]): | ||
""" | ||
Runs the program. This is used when you want to run this program standalone. | ||
``sniffer_cls`` A class (usually subclassed of Sniffer) that hooks into the scanner and | ||
handles running the test framework. Defaults to Sniffer class. | ||
``test_args`` This function normally extracts args from ``--test-arg ARG`` command. A | ||
preset argument list can be passed. Defaults to an empty tuple. | ||
``program`` Program name. Defaults to sys.argv[0]. | ||
``args`` Command line arguments. Defaults to sys.argv[1:] | ||
""" | ||
parser = OptionParser(version="%prog " + __version__) | ||
parser.add_option('-w', '--wait', dest="wait_time", metavar="TIME", default=0.5, type="float", | ||
help="Wait time, in seconds, before possibly rerunning tests. " | ||
"(default: %default)") | ||
parser.add_option('--no-clear', dest="clear_on_run", default=True, action="store_false", | ||
help="Disable the clearing of screen") | ||
parser.add_option('--debug', dest="debug", default=False, action="store_true", | ||
help="Enabled debugging output. (default: %default)") | ||
parser.add_option('-x', '--test-arg', dest="test_args", default=[], action="append", | ||
help="Arguments to pass to nose (use multiple times to pass multiple " | ||
"arguments.)") | ||
(options, args) = parser.parse_args(args) | ||
test_args = test_args + tuple(options.test_args) | ||
if options.debug: | ||
print "Options:", options | ||
print "Test Args:", test_args | ||
try: | ||
print "Starting watch..." | ||
run(sniffer_cls, options.wait_time, options.clear_on_run, test_args, options.debug) | ||
except KeyboardInterrupt: | ||
print "Good bye." | ||
except Exception: | ||
return sys.exit(1) | ||
return sys.exit(0) | ||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
""" | ||
Sniffer - An automatic test runner for Nose | ||
========== | ||
This is a simple program that runs nose anytime the current working | ||
directory (or its subdirectories) have changed (added, modified, or | ||
delete files). | ||
*Note:* By default, sniffer polls the directory tree to determine what changed. | ||
This method was used because of its: | ||
- simplicity: It's pretty easy to write. | ||
- cross-platform: Unlike third-party libs, which only work on one OS. | ||
- standard: Uses only standard modules. | ||
Alternatively, third party modules can be installed to increase performance. | ||
The library to install is dependent on your operating system: | ||
- Linux: install pyinotify | ||
- Windows: install pywin32 | ||
- OSX: install MacFSEvents | ||
""" | ||
from metadata import * | ||
from scanner import Scanner | ||
from runner import Sniffer | ||
from main import main, run | ||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
""" | ||
Main runners. Bootloads Sniffer class. | ||
""" | ||
from optparse import OptionParser | ||
from scanner import Scanner | ||
from runner import Sniffer | ||
from metadata import __version__ | ||
import os | ||
import sys | ||
|
||
__all__ = ['run', 'main'] | ||
|
||
def run(sniffer_cls=Sniffer, wait_time=0.5, clear=True, args=(), debug=False): | ||
""" | ||
Runs the auto tester loop. Internally, the runner instanciates the sniffer_cls and | ||
scanner class. | ||
``sniffer_cls`` The class to run. Usually this is set to but a subclass of scanner. | ||
Defaults to Sniffer. Sniffer class documentation for more information. | ||
``wait_time`` The time, in seconds, to wait between polls. This is dependent on | ||
the underlying scanner implementation. OS-specific libraries may choose | ||
to ignore this parameter. Defaults to 0.5 seconds. | ||
``clear`` Boolean. Set to True to clear the terminal before running the sniffer, | ||
(alias, the unit tests). Defaults to True. | ||
``args`` The arguments to pass to the sniffer/test runner. Defaults to (). | ||
``debug`` Boolean. Sets the scanner and sniffer in debug mode, printing more internal | ||
information. Defaults to False (and should usually be False). | ||
""" | ||
if debug: | ||
scanner = Scanner(('.',), logger=sys.stdout) | ||
else: | ||
scanner = Scanner(('.',)) | ||
sniffer = sniffer_cls(tuple(args), clear, debug) | ||
sniffer.observe_scanner(scanner) | ||
scanner.loop(wait_time) | ||
|
||
def main(sniffer_cls=Sniffer, test_args=(), progname=sys.argv[0], args=sys.argv[1:]): | ||
""" | ||
Runs the program. This is used when you want to run this program standalone. | ||
``sniffer_cls`` A class (usually subclassed of Sniffer) that hooks into the scanner and | ||
handles running the test framework. Defaults to Sniffer class. | ||
``test_args`` This function normally extracts args from ``--test-arg ARG`` command. A | ||
preset argument list can be passed. Defaults to an empty tuple. | ||
``program`` Program name. Defaults to sys.argv[0]. | ||
``args`` Command line arguments. Defaults to sys.argv[1:] | ||
""" | ||
parser = OptionParser(version="%prog " + __version__) | ||
parser.add_option('-w', '--wait', dest="wait_time", metavar="TIME", default=0.5, type="float", | ||
help="Wait time, in seconds, before possibly rerunning tests. " | ||
"(default: %default)") | ||
parser.add_option('--no-clear', dest="clear_on_run", default=True, action="store_false", | ||
help="Disable the clearing of screen") | ||
parser.add_option('--debug', dest="debug", default=False, action="store_true", | ||
help="Enabled debugging output. (default: %default)") | ||
parser.add_option('-x', '--test-arg', dest="test_args", default=[], action="append", | ||
help="Arguments to pass to nose (use multiple times to pass multiple " | ||
"arguments.)") | ||
(options, args) = parser.parse_args(args) | ||
test_args = test_args + tuple(options.test_args) | ||
if options.debug: | ||
print "Options:", options | ||
print "Test Args:", test_args | ||
try: | ||
print "Starting watch..." | ||
run(sniffer_cls, options.wait_time, options.clear_on_run, test_args, options.debug) | ||
except KeyboardInterrupt: | ||
print "Good bye." | ||
except Exception: | ||
return sys.exit(1) | ||
return sys.exit(0) | ||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
__all__ = [ | ||
'__author__', '__author_email__', '__copyright__', '__credits__', | ||
'__license__', '__version__' | ||
] | ||
|
||
|
||
__author__ = "Jeff Hui" | ||
__author_email__ = '[email protected]' | ||
__copyright__ = "Copyright 2010, Jeff Hui" | ||
__credits__ = ["Jeff Hui"] | ||
|
||
__license__ = "MIT" | ||
__version__ = "0.1.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import sys | ||
|
||
__all__ = ['ModulesRestorePoint'] | ||
|
||
class ModulesRestorePoint(object): | ||
def __init__(self, sys_modules=sys.modules): | ||
self._saved_modules = None | ||
self._sys_modules = sys_modules | ||
self.save() | ||
|
||
def save(self): | ||
"""Saves the currently loaded modules for restore.""" | ||
self._saved_modules = set(self._sys_modules.keys()) | ||
|
||
def restore(self): | ||
"""Unloads all modules that weren't loaded when save_modules was called.""" | ||
sys = set(self._sys_modules.keys()) | ||
for mod_name in sys.difference(self._saved_modules): | ||
del self._sys_modules[mod_name] |
Oops, something went wrong.