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() 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.