本文将贴出用于编译实验3的自动测试脚本源码。
涉及版权,本文将不会提供 irsim.pyc
虚拟机小程序和任何官方测试样例。
脚本的运行需要配置python3
等环境,相信对于大家来说不是问题,后文也会对环境配置给予讨论。
OK,进入正题。首先明确,本脚本判断测试是否正确的标准 是:执行你的编译器编译得到的IR程序,是否能产生预期的输出。
使用脚本自动测试,需要按照如下所示组织本地文件结构:
. ├── auto_run_irsim.py # 与运行irsim.pyc相关的脚本 ├── run.py # 自动化测试脚本,即你要执行的脚本 ├── irsim.pyc -> /home/me/Compiler/irsim/irsim.pyc # 链接到irsim.pyc的软链接 ├── expects # 你期望的输出(即执行C--编译器输出的IR文件所期望的输出) │ └── doc.1.1.expect ├── inputs # 测试样例(用C--语言编写) │ └── doc.1.1.cmm ├── irs # 编译测试样例得到的IR文件(该文件夹无需预先新建) ├── outputs # 执行IR程序的实际输出(该文件夹无需预先新建) └── texts # 执行IR程序所对应的stdin(即执行IR程序时本需要手工输入的数据) └── doc.1.1.text
run.py
和auto_run_irsim.py
的代码将在稍后贴出。接下来将会解释inputs
,texts
,expects
文件夹中的文件应如何准备(即如何准备测试样例)。
需要准备好inputs
,texts
,expects
文件夹。
inputs
文件夹:C--语言源程序无需多说,C--语言源程序符合实验手册要求即可。需要注意,inputs
文件夹中的文件名(不含后缀)需要与texts
和expects
文件夹中对应的文件相同,正如上文中的文件结构图所示。
下面给出一个示例:doc.1.1.cmm
。
int main() { int a; int b; read(a); b = a + 1; write(b); return 0; }
texts
文件夹:执行IR程序需要的stdin内容(即本需手工输入的数据)C--语言源程序中如果调用了read
函数,那么,在执行其对应的IR时,需要手工输入数据,这里将本应手工输入的数据写在texts
文件夹中后缀为.text
的文件中。
注意:每个数据均以换行符结束,文件的后缀必须为.text
。
下面给出一个示例:doc.1.1.text
,这个示例恰对应于上文中的doc.1.1.cmm
,注意数字100后有一个换行符。
100
expects
文件夹:执行IR程序期望获得的输出脚本会自动执行你的C--编译器,并将生成的IR程序存于irs
文件夹中;之后,脚本自动运行irsim.qyc
虚拟机小程序,使用其执行刚刚生成的IR程序,并将输出结果写入outputs
文件夹中。
你要做的,是将你所期望的输出结果写入expects
文件夹中以.expect
为结尾的文件中。
注意:文件的结束没有换行符,文件的后缀必须为.expect
。
下面给出一个示例:doc.1.1.expect
,这个示例恰对应于上文中的源程序和stdin。根据源程序的语义,应期望输出整数101,注意数字101后没有换行符。
101
下面的脚本由python3书写。
run.py
注意:对于脚本,有唯一一处必要的修改,在第42行,详见注释。
#!/usr/bin/python3 import time import os import re import sys import auto_run_irsim ESC_RED="\033[31m" ESC_GREEN="\033[32m" ESC_CYAN="\033[36m" ESC_END="\033[0m" def RunParser(executable, input_file_full_name, ir_file_full_name, output_file_full_name): os.system("touch " + output_file_full_name) os.system(executable + " " + input_file_full_name + " " + ir_file_full_name + " > " + output_file_full_name) f = open(output_file_full_name) output_text = f.read() f.close() if output_text == '': return 0 else: return -1 def main(): try: print(ESC_CYAN + "script running..." + ESC_END) start_time = time.asctime(time.localtime(time.time())) num_total_files=0 num_pass_files=0 # mission start # output_dir_name = "./outputs/" ir_dir_name = "./irs/" input_text_dir_name = "./texts/" input_dir_name = "./inputs/" expect_dir_name = "./expects/" executable = "../cc" # 请将双引号内修改为你的编译器的绝对地址 或 相对于本文件夹的相对地址 irsim = "./irsim.pyc" output_file_suffix = ".output" ir_file_suffix = ".ir" input_text_suffix = ".text" expect_file_suffix = ".expect" # make ir/output dir if not os.path.exists(ir_dir_name): os.makedirs(ir_dir_name) if not os.path.exists(output_dir_name): os.makedirs(output_dir_name) # traverse input files for root_dir, dirs, files in os.walk(input_dir_name): for input_file_name in files: num_total_files += 1 # === process an input file === file_pass = True # is this file pass input_file_full_name = root_dir + input_file_name input_file_pure_name = (re.findall(r"(.+)\.", input_file_name))[0] output_file_full_name = output_dir_name + input_file_pure_name + output_file_suffix ir_file_full_name = ir_dir_name + input_file_pure_name + ir_file_suffix input_text_file_full_name = input_text_dir_name + input_file_pure_name + input_text_suffix expect_file_full_name = expect_dir_name + input_file_pure_name + expect_file_suffix # get output ### ====== STEP I ====== run_rst = 0 compile_rst = 0 compile_rst = RunParser(executable, input_file_full_name, ir_file_full_name, output_file_full_name) if compile_rst == 0: ### == STEP II ====== auto_run_irsim.RunIRSim() f = open(input_text_file_full_name) input_text = f.read() f.close() input_text_lines = input_text.split("\n") input_text_lines.remove('') run_rst = auto_run_irsim.RunTestCase(ir_file_full_name, output_file_full_name, input_text_lines) auto_run_irsim.StopIRSim() ### ==================== if run_rst != 0: file_pass = False else: if compile_rst != 0: print(input_file_full_name, "Compile error, ignore.") # read output file f = open(output_file_full_name) my_lines = f.readlines() f.close() # read expect file f = open(expect_file_full_name) official_lines = f.readlines() f.close # compare if my_lines == official_lines: file_pass = True else: file_pass = False # print result if file_pass == True: num_pass_files += 1 print(ESC_GREEN + "PASS " + input_file_full_name + ESC_END) else: print(ESC_RED + "FAIL " + input_file_full_name + ESC_END, file=sys.stderr) print(ESC_CYAN + ">>>" + expect_file_full_name + ">>>" + ESC_END) os.system("cat " + expect_file_full_name) print("") print(ESC_CYAN + ">>>" + output_file_full_name + ">>>" + ESC_END) os.system("cat " + output_file_full_name) print("") # # mission complete end_time = time.asctime(time.localtime(time.time())) print("------") print("number of total files: ", num_total_files ) print("number of pass files: ", num_pass_files ) print("------") print(ESC_CYAN + "start at " + start_time + ESC_END) print(ESC_CYAN + "end at " + end_time + ESC_END) if num_total_files == num_pass_files: sys.exit(0) else: sys.exit(-1) except Exception as excep: print(excep, file=sys.stderr) sys.exit(-2) if __name__ == "__main__": main()
auto_run_irsim.py
这个脚本无需修改任何内容。
#!/usr/bin/python3 import pyautogui import os import time import gi gi.require_version('Wnck', '3.0') from gi.repository import Wnck def RunIRSim(): os.system("python ./irsim.pyc &") time.sleep(0.75) def StopIRSim(): pyautogui.hotkey('alt', 'f4') def RunTestCase(testcase_name, output_name, input_text_lines): pyautogui.hotkey('ctrl', 'o') time.sleep(0.1) pyautogui.typewrite(testcase_name) pyautogui.press('enter') time.sleep(0.05) # run pyautogui.press('f5') for line in input_text_lines: # enter input pyautogui.typewrite(line) pyautogui.press('enter') time.sleep(0.1) # check if successful src = Wnck.Screen.get_default() src.force_update() window_title = src.get_active_window().get_name() src = None Wnck.shutdown() pyautogui.press('enter') if window_title != "Finish": return -1 else: # scroll pyautogui.moveTo(836, 533) pyautogui.dragTo(836, 350) # select pyautogui.moveTo(492, 400) pyautogui.mouseDown() pyautogui.moveTo(855, 626) time.sleep(1.2) pyautogui.mouseUp() pyautogui.hotkey('ctrl', 'c') os.system("xclip -o > " + output_name) return 0
首先赋予脚本执行权限:
chmod +x ./run.py chmod +x ./auto_run_irsim.py
要自动化执行测试样例,在当前目录下,执行以下命令:(注意,脚本运行期间,不要使用鼠标、不要使用键盘)
./run.py
在我的本地环境,得到以下输出:
script running... PASS ./inputs/doc.2.2.cmm PASS ./inputs/doc.1.1.cmm PASS ./inputs/doc.3.1.cmm PASS ./inputs/doc.2.1.cmm PASS ./inputs/doc.1.3.cmm PASS ./inputs/doc.2.3.cmm PASS ./inputs/doc.4.1.cmm PASS ./inputs/doc.1.2.cmm ------ number of total files: 8 number of pass files: 8 ------ start at Thu May 5 18:24:56 2022 end at Thu May 5 18:25:28 2022
最后,如你所见,脚本中使用了如pyAutoGUI
等第三方包。虽然安装它们并不难,但在这里多说几句也不是坏事。
再次强调,本脚本依赖于 Python 3,我的本地环境是:
me@ubuntu:~$ uname -a Linux ubuntu 4.15.0-142-generic #146~16.04.1-Ubuntu SMP Tue Apr 13 09:27:15 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux me@ubuntu:~$ python3 --version Python 3.5.2 me@ubuntu:~$ python3 -m pip --version pip 20.3.4 from /home/me/.local/lib/python3.5/site-packages/pip (python 3.5)
另外,脚本需要安装包括但不限于以下依赖:
pyAutoGUI
,Wnck
。
关于pyAutoGUI
,有人并不建议在 Ubuntu 16.04 上使用,但限于实验要求,不得不作出让步。我确实注意到,在安装pyAutoGUI
时,有些其依赖没有安装成功;不过,本着“能用就是好用”的原则,经过实践,这并没有影响我实现这个自动化测试脚本。
写这个脚本的主要目的是自用,而不是面向用户体验,所以用起来有些繁琐在所难免。
祝我和大家实验愉快。