0%

compiler-lab-3-testscript

本文将贴出用于编译实验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.pyauto_run_irsim.py的代码将在稍后贴出。接下来将会解释inputstextsexpects文件夹中的文件应如何准备(即如何准备测试样例)。

准备测试样例

inputs文件夹:C–语言源程序

无需多说,C–语言源程序符合实验手册要求即可。需要注意,inputs文件夹中的文件名(不含后缀)需要与textsexpects文件夹中对应的文件相同,正如上文中的文件结构图所示。

下面给出一个示例: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后有一个换行符。

1
2
100

expects文件夹:执行IR程序期望获得的输出

脚本会自动执行你的C–编译器,并将生成的IR程序存于irs文件夹中;之后,脚本自动运行irsim.qyc虚拟机小程序,使用其执行刚刚生成的IR程序,并将输出结果写入outputs文件夹中。

你要做的,是将你所期望的输出结果写入expects文件夹中以.expect为结尾的文件中。

注意:文件的结束没有换行符,文件的后缀必须.expect

下面给出一个示例:doc.1.1.expect,这个示例恰对应于上文中的源程序和stdin。根据源程序的语义,应期望输出整数101,注意数字101后没有换行符。

1
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
#!/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

这个脚本无需修改任何内容。

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
#!/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

脚本使用

首先赋予脚本执行权限:

1
2
chmod +x ./run.py
chmod +x ./auto_run_irsim.py

要自动化执行测试样例,在当前目录下,执行以下命令:(注意,脚本运行期间,不要使用鼠标、不要使用键盘)

1
./run.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)

另外,脚本需要安装包括但不限于以下依赖:

pyAutoGUIWnck

关于pyAutoGUI,有人并不建议在 Ubuntu 16.04 上使用,但限于实验要求,不得不作出让步。我确实注意到,在安装pyAutoGUI时,有些其依赖没有安装成功;不过,本着“能用就是好用”的原则,经过实践,这并没有影响我实现这个自动化测试脚本。

结语

写这个脚本的主要目的是自用,而不是面向用户体验,所以用起来有些繁琐在所难免。

祝我和大家实验愉快。