Library 'Process' 中有个 keyword 'Start Process'。
(Starts a new process on background)
注:Library 'OperatingSystem' 中也有个 keyword 'Start Process'。但是已被废弃,建议用 Process.Start Process。
=== 这部分是我发现这个“坑”的经过。不感兴趣的,可直接跳到文末 ===
最近遇到个问题。
> 现象:
注:被测系统 (SUT) 是一个 WinForm 程序,用 C# .Net 编写
- 在执行某个 Case 时,手动执行一切正常;
- 但是用 Robot Framework 执行时,总是执行到某个固定步骤时 SUT 卡死;
- (接上一步)此时,如果停掉 Robot Framework,SUT 又能继续执行,正常结束;
- 同一个 Case 在测试以前的版本时没有这个问题。
> 经过:
刚接到这个任务时,我首先怀疑是 Case 的脚本有所不妥,导致被测系统被影响。
(毕竟手动执行没问题)
于是:
- 研究了被测系统卡死时 Robot Framework 正在执行的那个 keyword。无果。该 keyword(我自己实现的):在固定时间内不断检测被测系统是否有弹出消息框,直到消息框弹出或超时。我也考虑过是不是 UIAutomation 底层实现导致:Robot Framework 不断检测 SUT 的 UI;SUT 又要不断更新 UI(刷新各种界面特效)。想了想感觉可能性不大,接着考虑其它原因。
- Review 整条 Case 执行线。无果。
- 然后用排除法(半自动化):手动启动 SUT,自动执行后续步骤。SUT 顺利执行完毕。
于是猜测是脚本中,启动 SUT 的方式导致的。
把启动方式从
Process.Start Process ${SUT.exe}
改为
Evaluate subprocess.Popen(['${SUT.exe}']) subprocess
经测试,SUT 顺利执行完毕。
为了不 delay 测试业务太多时间,先用上这个新方案,具体问题原因自己再慢慢研究。
拿到 SUT 源码,开始调试。
发现如果用 Start Process 启动 SUT,SUT 在某处调用 Console.WriteLine 时会卡死。
这个语句在一个循环中,而且每次都是执行循环固定次数后卡死。
此时隐约感觉是输出被重定向后遇到最大输出数据的限制了。
然后就是后续的一些对比实验验证了该想法。
=== 发现过程结束 ===
Process.Start Process ${SUT.exe}
vs
Evaluate subprocess.Popen(['${SUT.exe}']) subprocess
其实这两者底层都调用了 subprocess.Popen,但是传给 Popen 的参数有所不同。
- Start Process 在内部会对参数赋予一些特定的默认值;
- 其中,子进程的输出被重定向为 subprocess.PIPE;
- 输出被重定向到一个流,而且这个没有任何读者(除非用户主动发起相关操作);
- 如果子进程不断往这个输出流写数据,会导致流中数据越来越多(没有被读出去),并达到限制的最大值;
- 然后子进程就被卡死在下一次写操作上 (流已满,无法写入)。
MSDN 上的一些示例也有对这类死锁的一些说明:Process.StandardOutput Property