前两篇文章讲解了Suite的构造,文件到Robot的数据格式的转化。这篇文章继续讲解
回到入口函数Robotframework().main()
8行之前已经讲过了,8行为对suite的配置。9行为重点
1 def main(self, datasources, **options): 2 settings = RobotSettings(options) 3 LOGGER.register_console_logger(**settings.console_logger_config) 4 LOGGER.info('Settings: %s' % unicode(settings)) 5 suite = TestSuiteBuilder(settings['SuiteNames'], 6 settings['WarnOnSkipped'], 7 settings['RunEmptySuite']).build(*datasources) 8 suite.configure(**settings.suite_config) 9 result = suite.run(settings) 10 LOGGER.info("Tests execution ended. Statistics: %s" 11 % result.suite.stat_message) 12 if settings.log or settings.report or settings.xunit: 13 writer = ResultWriter(settings.output if settings.log else result) 14 writer.write_results(settings.get_rebot_settings()) 15 return result.return_code
suite.run()是robot unningmodel.py下TestSuite的方法,具体构造已经在上篇文章中简单讲述了
1 def run(self, settings=None, **options): 2 with STOP_SIGNAL_MONITOR: 3 IMPORTER.reset() 4 pyloggingconf.initialize(settings['LogLevel']) 5 init_global_variables(settings) 6 output = Output(settings) 7 runner = Runner(output, settings) 8 self.visit(runner) 9 output.close(runner.result) 10 return runner.result
这里出现了with STOP_SIGNAL_MONITOR 这个也算是python中的魔法实现了。这篇文章先讲一下这块,with的原理如下
1、紧跟with后面的语句被执行后后,对象的__enter__()方法被调用,这个方法的返回值将被赋值给as后面的变量
2、当with后面的代码块全部被执行完之后,将调用前面返回对象的__exit__()方法
示例代码和输出结果如下
1 class Test: 2 def __enter__(self): 3 print "__enter__" 4 return self 5 def __exit__(self, *exc_info): 6 print "__exit__" 7 def output(self, message): 8 print message 9 with Test() as test: 10 test.output('test')
1 C:Python27python.exe E:/test/testwith.py 2 __enter__ 3 test 4 __exit__
返回来看STOP_SIGNAL_MONITOR这个类
1 class _StopSignalMonitor(object): 2 3 def __init__(self): 4 self._signal_count = 0 5 self._running_keyword = False 6 self._orig_sigint = None 7 self._orig_sigterm = None 8 9 def __call__(self, signum, frame): 10 self._signal_count += 1 11 LOGGER.info('Received signal: %s.' % signum) 12 if self._signal_count > 1: 13 sys.__stderr__.write('Execution forcefully stopped. ') 14 raise SystemExit() 15 sys.__stderr__.write('Second signal will force exit. ') 16 if self._running_keyword and not sys.platform.startswith('java'): 17 self._stop_execution_gracefully() 18 19 def _stop_execution_gracefully(self): 20 raise ExecutionFailed('Execution terminated by signal', exit=True) 21 22 def start(self): 23 # TODO: Remove start() in favor of __enter__ in RF 2.9. Refactoring 24 # the whole signal handler at that point would be a good idea. 25 self.__enter__() 26 27 def __enter__(self): 28 if signal: 29 self._orig_sigint = signal.getsignal(signal.SIGINT) 30 self._orig_sigterm = signal.getsignal(signal.SIGTERM) 31 for signum in signal.SIGINT, signal.SIGTERM: 32 self._register_signal_handler(signum) 33 return self 34 35 def __exit__(self, *exc_info): 36 if signal: 37 signal.signal(signal.SIGINT, self._orig_sigint) 38 signal.signal(signal.SIGTERM, self._orig_sigterm) 39 40 def _register_signal_handler(self, signum): 41 try: 42 signal.signal(signum, self) 43 except (ValueError, IllegalArgumentException), err: 44 # ValueError occurs e.g. if Robot doesn't run on main thread. 45 # IllegalArgumentException is http://bugs.jython.org/issue1729 46 if currentThread().getName() == 'MainThread': 47 self._warn_about_registeration_error(signum, err) 48 49 def _warn_about_registeration_error(self, signum, err): 50 name, ctrlc = {signal.SIGINT: ('INT', 'or with Ctrl-C '), 51 signal.SIGTERM: ('TERM', '')}[signum] 52 LOGGER.warn('Registering signal %s failed. Stopping execution ' 53 'gracefully with this signal %sis not possible. ' 54 'Original error was: %s' % (name, ctrlc, err)) 55 56 def start_running_keyword(self, in_teardown): 57 self._running_keyword = True 58 if self._signal_count and not in_teardown: 59 self._stop_execution_gracefully() 60 61 def stop_running_keyword(self): 62 self._running_keyword = False
程序开始运行时执行顺序为:
1、__enter__():
查看signal是否导入成功,导入成功,则获取一下执行signal.getsignal(),对signal.SIGINT和signal.SIGTERM,这两种信号,一个是Crtl+C触发,一个是进程自身要求中断引起,当程序初始运行时,getsignal返回的都是0
调用_register_signal_handler 对两种信号进行注册,以备将来捕获。在注册时候,使用了signal.signal(signum, self)。请注意这块,signale的第二个参数为捕获到对应信号后的处理函数,此时把self传进去,也就是说处理函数为self()也就是_StopSignalMonito()这块,一会再讲
此时由于信号只能在主线程设置,所以又判断了一下,给出了相应的告警
2、退出
分为两种,接收到SIGINIT or SIGTERM
此时会调用_StopSignalMonito()进行信号处理,这块又用到Python的魔法,__call__()
其中的逻辑是,接到一次信号,判断自身状态,如果测试在跑,则后续不再进行,执行teardown后正常退出;如果再次接到信号,直接调用系统的SystemExit()。也就是说在程序运行过程中,我们要中断测试,需要按两次Ctrl+C
然后再执行__exit__(),此时又执行了signal.signal,传入的是Stop_SignalMonito的两个变量,在执行__enter__时被初始化,此时应该都是0,也就是singal.SIG_DFL,进程采用默认操作。退出with
无故障,直接调用__exit__(),和上述接到中断信号调用__exit__()的流程是一样的