Autotest for IronPython
Continuing my explorations of IronPython, I decided I wanted a continuous test setup, which would automatically run my unit tests every time I saved a .py file, which was something I had seen on various “katacasts”. After a bit of investigation, I found the promising looking modipyd, which seemed to be Windows friendly. Unfortunately, GitHub won’t let me download the files, so I set about creating my own basic continuous test tool for IronPython.
One advantage of using IronPython is that it immediately gives me access to the .NET framework’s FileSystemWatcher, which meant I didn’t have to worry about learning threading in Python. I did however have to work around one quirk which meant that the file changed event could get triggered multiple times, despite a single save command in my code editor.
Another challenge was working out how to load or reload a module given its name. This is done with the __import__
function, and using the sys.modules
dictionary for the reload.
It took slightly longer than I hoped to get it fully working. Occasionally I get a spurious ValueError
thrown when it attempts a reload. I’m not sure what that is all about. It should also be improved to rerun tests on all loaded modules not just the one that changed if you are not working entirely within a single file.
Again, any Python gurus feel free to suggest improvements.
import unittest
import clr
import sys
from System.IO import FileSystemWatcher
from System.IO import NotifyFilters
from System import DateTime
def changed(sender, args):
global lastFileTimeWatcherEventRaised
if DateTime.Now.Subtract(lastFileTimeWatcherEventRaised).TotalMilliseconds < 500:
return
moduleName = args.Name[:-3]
if reloadModule(moduleName):
runTests(moduleName)
lastFileTimeWatcherEventRaised = DateTime.Now
def reloadModule(moduleName):
loaded = False
try:
if(sys.modules.has_key(moduleName)):
print 'Reloading ' + moduleName
reload(sys.modules[moduleName])
else:
print 'Importing ' + moduleName
__import__(moduleName)
loaded = True
except SyntaxError, s:
print 'Syntax error loading ' + s.filename, 'line', s.lineno, 'offset', s.offset
print s.text
except:
#sometimes get a ValueError here, not sure why
error = sys.exc_info()[0]
print error
return loaded
def runTests(moduleName):
loader = unittest.TestLoader()
suite = loader.loadTestsFromModule(sys.modules[moduleName])
if suite.countTestCases() > 0:
runner = unittest.TextTestRunner()
runner.run(suite)
else:
print 'No tests in module'
def watch(path):
watcher = FileSystemWatcher()
watcher.Filter = '*.py'
watcher.Changed += changed
watcher.Path = path
watcher.NotifyFilter = NotifyFilters.LastWrite
watcher.EnableRaisingEvents = 1
lastFileTimeWatcherEventRaised = DateTime.Now
if __name__ == '__main__':
print 'Watching current folder for changes...'
watch('.')
input('press Enter to exit')
If I get a chance I’ll record my own “katacast” showing the autotest python module in action.
Update: I've made the katacast. I've also made a slight improvement to the autotest code, moving the setting of lastFileTimeWatcherEventRaised
further down to stop long-running tests thwarting the multiple-event filtering.