本文将贴出用于编译实验3的自动测试脚本源码。
涉及版权,本文将不会提供 irsim.pyc 虚拟机小程序和任何官方测试样例。
脚本的运行需要配置python3等环境,相信对于大家来说不是问题,后文也会对环境配置给予讨论。
OK,进入正题。首先明确,本脚本判断测试是否正确的标准 是 :执行你的编译器编译得到的IR程序,是否能产生预期的输出 。
本地文件结构 使用脚本自动测试,需要按照如下所示组织本地文件结构:
1 2 3 4 5 6 7 8 9 10 11 12 . ├── 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文件夹中的文件应如何准备(即如何准备测试样例)。
准备测试样例 无需多说,C–语言源程序符合实验手册要求即可。需要注意,inputs文件夹中的文件名(不含后缀)需要与texts和expects文件夹中对应的文件相同 ,正如上文中的文件结构图所示。
下面给出一个示例:doc.1.1.cmm。
1 2 3 4 5 6 7 8 9 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后有一个换行符。
expects文件夹:执行IR程序期望获得的输出脚本会自动执行你的C–编译器,并将生成的IR程序存于irs文件夹中;之后,脚本自动运行irsim.qyc虚拟机小程序,使用其执行刚刚生成的IR程序,并将输出结果写入outputs文件夹中。
你要做的,是将你所期望的输出结果写入expects文件夹中以.expect为结尾的文件中。
注意:文件的结束没有 换行符,文件的后缀必须 为.expect。
下面给出一个示例:doc.1.1.expect,这个示例恰对应于上文中的源程序和stdin。根据源程序的语义,应期望输出整数101,注意数字101后没有换行符。
脚本源代码 下面的脚本由python3书写。
脚本run.py 注意:对于脚本,有唯一一处必要 的修改,在第42行,详见注释。
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 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 import timeimport osimport reimport sysimport auto_run_irsimESC_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 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" 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) for root_dir, dirs, files in os.walk(input_dir_name): for input_file_name in files: num_total_files += 1 file_pass = True 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 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 : 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." ) f = open (output_file_full_name) my_lines = f.readlines() f.close() f = open (expect_file_full_name) official_lines = f.readlines() f.close if my_lines == official_lines: file_pass = True else : file_pass = False 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 ("" ) 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 这个脚本无需修改任何内容。
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 import pyautoguiimport osimport timeimport gigi.require_version('Wnck' , '3.0' ) from gi.repository import Wnckdef 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 ) pyautogui.press('f5' ) for line in input_text_lines: pyautogui.typewrite(line) pyautogui.press('enter' ) time.sleep(0.1 ) 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 : pyautogui.moveTo(836 , 533 ) pyautogui.dragTo(836 , 350 ) 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
脚本使用 首先赋予脚本执行权限:
1 2 chmod +x ./run.py chmod +x ./auto_run_irsim.py
要自动化执行测试样例,在当前目录下,执行以下命令:(注意,脚本运行期间,不要 使用鼠标、不要 使用键盘)
在我的本地环境,得到以下输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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,我的本地环境是:
1 2 3 4 5 6 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时,有些其依赖没有安装成功;不过,本着“能用就是好用”的原则,经过实践,这并没有影响我实现这个自动化测试脚本。
结语 写这个脚本的主要目的是自用,而不是面向用户体验,所以用起来有些繁琐在所难免。
祝我和大家实验愉快。