果然还是把复杂问题简单化比较爽啊
Python September 16th, 2009
事情的起因是这样子的,小员在写一个测试工具,主要是管理 Xvfb 和 Selenium Server,启动多个 Server 来并行跑多个测试用例,出错的时候截图生成报告。
然后开发人员的测试代码是这么写的:
1 2 | def setUP(self): self.selenium = selenium(host, port, "*firefox", "http://xxx.yyy.zzz:1234/") |
这里的 host 和 port 在测试用例里是写死的,而在这个测试工具中,由于启动的 Selenium Server 的端口是随机绑定的,所以需要自动的把测试用例里的连接修改成对应的参数。
我俩讨论了下,决定用 decorator 来改掉 selenium 的构造函数,然后有了以下关键部分的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | def change_selenium_params(host, port, browser='', baseURL=''): def inner(func): def wrapper(*kargs, **kwargs): kargs = list(kargs) kargs[1:3] = host, port return func(*kargs, **kwargs) return wrapper return inner @stop_servers def run(self): """ Run testcases """ self.xvfb.start() self.selenium_server = serverctl.SeleniumServer(self.xvfb.display) self.selenium_server.start() for testcase_file in self.testcases_status['waiting']: selenium.selenium._init_ = change_selenium_params('localhost', self.selenium_server.port)(selenium.selenium.__init__) # Make testsuite and run it here ...... self.selenium_server.restart() self.selenium_server.stop() self.xvfb.stop() |
由于 Selenium 关闭 Firefox 有 Bug,所以不得不每跑一个 testsuite 就重启一次 Selenium Server,这时候发现 testsuite 用的还是第一次传过去的端口。结果看起来奇怪,这个问题还和其它混在一起了,而且这玩意比较难跟踪,调试器也不好用,于是乎,我们写了个简化版的原型来分析,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Selenium: def __init__(self, port): print 'Port:', port def change_port(port): def inner(func): def wrapper(*kargs, **kwargs): kargs = list(kargs) kargs[1] = port return func(*kargs, **kwargs) return wrapper return inner for i in xrange(3): Selenium.__init__ = change_port(i)(Selenium.__init__) a = Selenium(9) |
这时候的输出是:
1 2 3 | Port: 0 Port: 0 Port: 0 |
然后在 decorator 里加了条输出语句:
1 2 3 4 5 6 7 8 9 | def change_port(port): def inner(func): def wrapper(*kargs, **kwargs): kargs = list(kargs) print 'Change port:', port # Add a debug output. kargs[1] = port return func(*kargs, **kwargs) return wrapper return inner |
这时候的输出是:
1 2 3 4 5 6 7 8 9 | Change port: 0 Port: 0 Change port: 1 Change port: 0 Port: 0 Change port: 2 Change port: 1 Change port: 0 Port: 0 |
这下终于知道问题所在了,可怜的 A.__init__ 函数每次执行都多被套一层 decorator,到了最后就变成了:
1 | change_port(2)(change_port(1)(change_port(0)(A.__init__))) |
嗯,用个临时变量先把初始的 A.__init__ 保存起来就 OK 了,比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class A: def __init__(self, port): print 'Port:', port orig_func = A.__init__ def change_port(port): def inner(func): def wrapper(*kargs, **kwargs): kargs = list(kargs) print 'Change port:', port # Add a debug output. kargs[1] = port return func(*kargs, **kwargs) return wrapper return inner for i in xrange(3): A.__init__ = orig_func A.__init__ = change_port(i)(A.__init__) a = A(9) |
Tags: Python
Daily Scripts: djangoat.py (for Linux) 
Django, Linux, Python June 19th, 2009
Djangoat is short for Django Auto Tester, but I often pronounce it “djan-goat”… This script does monitor Django project directory by inotify mechanism, run unit tests when file changed, and notify errors through Mumbles if tests failed. It depends on inotify and Mumbles, so runs on Linux only, Mac version comes later…
Python code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | #!/usr/bin/env python # -*- coding: utf-8 -*- import commands import datetime import dbus import dbus.service import os import sys from dbus.mainloop.glib import DBusGMainLoop from pyinotify import WatchManager, ThreadedNotifier, \ ProcessEvent, IN_CLOSE_WRITE, \ ExcludeFilter FIREFOX_DBUS_INTERFACE = 'org.mozilla.firefox.DBus' FIREFOX_DBUS_PATH = '/org/mozilla/firefox/DBus' class FireFoxDBus(dbus.service.Object): def __init__(self, bus_name): dbus.service.Object.__init__(self, bus_name, FIREFOX_DBUS_PATH) @dbus.service.signal(dbus_interface=FIREFOX_DBUS_INTERFACE, signature='ss') def DownloadComplete(self, title, subject): pass # Which type of files' change should be monitor MONITOR_EXTENSIONS = ('.py', '.html') class Watcher(ProcessEvent): def process_IN_CLOSE_WRITE(self, event): global cmd global firefox_dbus for extension in MONITOR_EXTENSIONS: if event.pathname.endswith(extension): start_time = datetime.datetime.now() print start_time output = commands.getoutput(cmd) print output # If test failed, call mumbles for notification if not output.endswith('OK'): firefox_dbus.DownloadComplete(start_time.isoformat(), output) def process_default(self, event): pass if __name__ == '__main__': if len(sys.argv) < 2: print 'Please specify a path for monitoring...' sys.exit() path = sys.argv[1] cmd = "python %s/manage.py test -v 0" % path # Exclude filter object excl_file = os.path.join(os.getcwd(), 'exclude.patterns') excl = ExcludeFilter({excl_file: ('excl_lst',)}) # Add watch wm = WatchManager() notifier = ThreadedNotifier(wm, Watcher()) wm.add_watch(path, IN_CLOSE_WRITE, rec=True, \ auto_add=True, exclude_filter=excl) # Set up an event loop dbus_loop = DBusGMainLoop() name = dbus.service.BusName(FIREFOX_DBUS_INTERFACE, bus=dbus.SessionBus(mainloop=dbus_loop)) firefox_dbus = FireFoxDBus(name) try: notifier.loop() except KeyboardInterrupt: print 'Djangoat shut down...' except Exception, ex: print 'Exception in Djangoat: %s' % (ex) |
1 2 3 4 5 6 | # -*- mode: python; -*- # Exclude pattens excl_lst = ['^\.git/', '^\.svn/', ] |
Screenshoot:
![]()
Code repository:
http://git.lazytech.info/?p=daily-scripts.git
Download: AltSwitch 0.1
Description: A firefox extension which make windows version’s firefox use alt+(1-9) for switching tab instead of original ctrl binding
Tags: firefox extension