run_test-0310.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. #!/usr/bin/python
  2. # _*_ coding: utf-8 _*_
  3. try:
  4. from Tkinter import * # python2
  5. import ttk
  6. import tkMessageBox
  7. except Exception as e:
  8. from tkinter import * # python3
  9. from tkinter import ttk
  10. from tkinter import messagebox as tkMessageBox
  11. import sys
  12. import os
  13. import subprocess
  14. import time
  15. import threading
  16. import csv
  17. from datetime import datetime
  18. import test_function
  19. class TestContext:
  20. def __init__(self):
  21. self.script_ver = 'C180-C-C7-v1.2'
  22. self.log_file = './Logs/temp.txt'
  23. self.log1_buffer = "" # 日志缓冲区
  24. self.tp_path = "/Users/zhang/Documents/Projects/C180/pythonTest-V2.2-C180/python_test/testplan.csv"
  25. self.device_info = {}
  26. self.params_dict = {
  27. 'path_loss': '25',
  28. 'wave_file': './11b_waveforms/wave11rc_1000.mod',
  29. 'per_limit': '92',
  30. 'SA_RSSI': '-80',
  31. 'BTOn_RSSI': '-30'
  32. }
  33. # 加载Testplan
  34. f = open(self.tp_path, 'r')
  35. reader = csv.DictReader(f)
  36. self.testplan_list = []
  37. # for row in reader:
  38. # if row["Disable"] ~= "Y":
  39. # self.testplan_list.append(row)
  40. self.testplan_list = [row for row in reader if row["Disable"] != "Y"]
  41. # disable = row["Disable"]
  42. # testName = row["TestName"]
  43. # function = row["function"]
  44. # Parameters = row["Parameters"]
  45. self.step_status = ['ns'] * len(self.testplan_list)
  46. self.result_msg = [''] * len(self.testplan_list)
  47. # 可选:为 log1_buffer 添加锁(如果担心线程安全)
  48. # self.buffer_lock = threading.Lock()
  49. def print1(self, string, newline=0):
  50. if newline == 0:
  51. self.log1_buffer += "\n" + str(string)
  52. elif newline == 1:
  53. self.log1_buffer += "\n" + str(string) + "\n"
  54. def write_log_to_file(self, content):
  55. curr_time = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())
  56. log_dir = "./Logs/"
  57. log_file = "log_" + curr_time + ".txt"
  58. log_file_location = log_dir + log_file
  59. file = open(log_file_location, 'a') # open file to append
  60. file.write(str(content) + '\n')
  61. file.close()
  62. class IO_process:
  63. def __init__(self, log_file_path):
  64. # log_file_path is the absolute path of the log file
  65. self.log_file = log_file_path
  66. self.line_count = 0
  67. def read_file(self):
  68. try:
  69. f = open(self.log_file, 'r') # open file to read
  70. flag = 0
  71. for i, line in enumerate(f):
  72. if i == self.line_count: # if reached the correct line
  73. self.line_count += 1
  74. flag = 1
  75. # print self.line_count
  76. break
  77. f.close()
  78. if flag == 1:
  79. return line.rstrip('\r') # get rid of the newline character
  80. else:
  81. return ""
  82. except:
  83. return ""
  84. def empty_file(self):
  85. f = open(self.log_file, 'w')
  86. f.close()
  87. def reset_counter(self):
  88. self.line_count = 0
  89. class MasterThread(threading.Thread):
  90. def __init__(self, context):
  91. super(MasterThread, self).__init__()
  92. self.context = context
  93. def run(self):
  94. self.context.print1("X86 C180####################################################", 1)
  95. self.context.print1(self.context.script_ver)
  96. for step_num, test_item in enumerate(self.context.testplan_list):
  97. self.context.print1("\n----------------------------------------------------")
  98. self.context.print1("Step " + str(step_num) + " Started test...")
  99. func_name = test_item["Function"]
  100. arguments = test_item["Arguments"]
  101. func = getattr(test_function, func_name, None)
  102. if not func:
  103. self.context.step_status[step_num] = "f"
  104. self.context.result_msg[step_num] = "{} is not defined".format(func_name)
  105. continue
  106. self.context.step_status[step_num] = 'r' # running
  107. if arguments:
  108. result, result_msg = func(self.context, arguments)
  109. else:
  110. result, result_msg = func(self.context)
  111. self.context.step_status[step_num] = result and "p" or "f"
  112. self.context.result_msg[step_num] = result_msg
  113. self.context.print1("\n----------------------------------------------------")
  114. self.context.print1(result_msg)
  115. self.context.print1("\n----------------------------------------------------")
  116. self.context.print1("Test concluded!")
  117. class GuiThread(object):
  118. def __init__(self):
  119. self.context = TestContext()
  120. self.thread_list = {}
  121. self.counter = 0
  122. self.root = Tk()
  123. self.root.geometry("1800x1200")
  124. self.root.protocol("WM_DELETE_WINDOW", self.destroy) # when user closes the window
  125. self.root.title("WiPER: FT Simular Test")
  126. self.note1 = Label(self.root, text="Please press 'Start Test' to begin...", fg='blue')
  127. self.start_button = Button(self.root, text="Start Test", command=self.start)
  128. self.test_status_label = Label(self.root, text="NOT STARTED", width=15, bg='gray')
  129. self.deviceInfo_label = Label(self.root, text="Device: ", width=20, bg="#C6EBFF")
  130. self.notebook_box = ttk.Notebook(self.root, name='notebook')
  131. self.status_frame = Frame(self.root)
  132. self.logFrame = Frame(self.root)
  133. self.parametersFrame = Frame(self.root)
  134. self.logText = Text(self.logFrame)
  135. self.logScrollbar = Scrollbar(self.logFrame)
  136. # self.logScrollbar.config(command=self.logText.yview)
  137. # self.logText.config(yscrollcommand=self.logScrollbar.set)
  138. # -------------------------- 核心修改:添加滚动条 --------------------------
  139. # 2. 创建Canvas和Scrollbar
  140. self.canvas = Canvas(self.status_frame, bg="white")
  141. self.scrollbar_y = Scrollbar(self.status_frame, command=self.canvas.yview)
  142. # 3. 创建原来的lblframe作为Canvas的内部窗口
  143. self.lblframe = Frame(self.canvas)
  144. # 4. 将内部Frame绑定到Canvas
  145. self.canvas_window = self.canvas.create_window((0, 0), window=self.lblframe, anchor="nw")
  146. # 5. 配置Canvas的滚动命令
  147. self.canvas.configure(yscrollcommand=self.scrollbar_y.set)
  148. # 6. 绑定事件:更新Canvas滚动区域和内部Frame宽度
  149. def on_canvas_configure(event):
  150. # 让内部Frame宽度和Canvas一致
  151. self.canvas.itemconfig(self.canvas_window, width=event.width)
  152. # 更新滚动区域
  153. self.canvas.configure(scrollregion=self.canvas.bbox("all"))
  154. def on_frame_configure(event):
  155. # 当内部Frame尺寸变化时更新滚动区域
  156. # print("event>>", event, event.height)
  157. self.canvas.configure(scrollregion=self.canvas.bbox("all"))
  158. # self.canvas.bind("<Configure>", on_canvas_configure)
  159. self.lblframe.bind("<Configure>", on_frame_configure)
  160. # -------------------------- 新增:鼠标滚轮支持 --------------------------
  161. # 定义滚轮事件处理函数(兼容Windows/Linux和Mac)
  162. def on_mouse_wheel(event):
  163. # Windows/Linux: event.delta 是120的倍数;Mac: event.delta 是-1/1的倍数
  164. if event.delta > 0:
  165. self.canvas.yview_scroll(-1, "pages") # 向上滚动
  166. else:
  167. self.canvas.yview_scroll(1, "pages") # 向下滚动
  168. # 绑定滚轮事件(兼容不同系统)
  169. if sys.platform == 'darwin': # Mac系统
  170. self.canvas.bind('<MouseWheel>', on_mouse_wheel)
  171. else: # Windows/Linux系统
  172. self.canvas.bind('<MouseWheel>', on_mouse_wheel)
  173. self.canvas.bind('<Button-4>', lambda e: self.canvas.yview_scroll(-1, "units"))
  174. self.canvas.bind('<Button-5>', lambda e: self.canvas.yview_scroll(1, "units"))
  175. # 确保Canvas能接收鼠标事件(解决焦点问题)
  176. self.canvas.bind("<Enter>", lambda e: self.canvas.focus_set())
  177. # ------------------------------------------------------------------------
  178. # 7. 布局Canvas和Scrollbar
  179. self.canvas.pack(side=LEFT, fill=BOTH, expand=True)
  180. self.scrollbar_y.pack(side=RIGHT, fill=Y)
  181. # ------------------------------------------------------------------------
  182. # 创建按钮和标签列表
  183. self.index_list = []
  184. self.step_buttons = []
  185. self.step_name_list = []
  186. self.step_data_list = []
  187. self.step_status_list = []
  188. title_1 = Label(self.lblframe, text="Index", anchor='center', width=5)
  189. title_1.grid(row=0, column=0, sticky=W, padx=5, pady=2)
  190. title_2 = Label(self.lblframe, text="Run Steps", anchor='center', width=12)
  191. title_2.grid(row=0, column=1, sticky=W, padx=5, pady=2)
  192. # title_2 = Button(self.lblframe, text="Run Steps", width=12, bg='lightblue')
  193. # title_2.config(state=DISABLED)
  194. # title_2.grid(row=0, column=1, padx=5, pady=2)
  195. title_3 = Label(self.lblframe, text="TestName", anchor='center', width=30)
  196. title_3.grid(row=0, column=2, sticky=W, padx=5, pady=2)
  197. title_4 = Label(self.lblframe, text="TestData", anchor='center', width=20)
  198. title_4.grid(row=0, column=3, sticky=W, padx=5, pady=2)
  199. title_5 = Label(self.lblframe, text="TestStatus", anchor='center', width=20)
  200. title_5.grid(row=0, column=4, sticky=W, padx=5, pady=2)
  201. # 创建按钮和标签
  202. for index, desc in enumerate(self.context.testplan_list):
  203. # 创建序列
  204. index_lable = Label(self.lblframe, text=str(index), anchor='center', width=5)
  205. self.index_list.append(index_lable)
  206. # 创建按钮
  207. btn = Button(self.lblframe, text=f"Run Step {index}",
  208. command=lambda num=index: self.run_single_step(num),
  209. width=12, bg='lightblue')
  210. self.step_buttons.append(btn)
  211. # 创建步骤描述标签
  212. item_label = Label(self.lblframe, text=desc["TestName"], anchor='w', bg='#CCCCCC', width=30)
  213. self.step_name_list.append(item_label)
  214. # 创数据标签
  215. data_label = Label(self.lblframe, text="", anchor='w', bg='#CCCCCC', width=20)
  216. self.step_data_list.append(data_label)
  217. # 创建状态标签
  218. status_label = Label(self.lblframe, text="NOT STARTED", bg='#CCCCCC', width=20)
  219. self.step_status_list.append(status_label)
  220. self.my_io_process = IO_process(self.context.log_file)
  221. self.readLog1()
  222. self.set_param_frame()
  223. def destroy(self):
  224. if tkMessageBox.askokcancel("Quit?", "Are you sure you want to quit?"):
  225. self.root.quit()
  226. def run(self):
  227. self.note1.pack()
  228. self.start_button.pack(side=TOP, padx=10, pady=3)
  229. self.test_status_label.pack()
  230. # 优化:单行文本标签仅水平填充,不扩展额外空间
  231. self.deviceInfo_label.pack(fill=X, expand=False, padx=50, pady=20)
  232. self.logScrollbar.config(command=self.logText.yview)
  233. self.logText.config(yscrollcommand=self.logScrollbar.set)
  234. self.logText.pack(side=LEFT, fill=Y)
  235. self.logScrollbar.pack(side=RIGHT, fill=Y)
  236. # 布局步骤按钮和标签(放在带滚动的内部Frame中)
  237. for index in range(len(self.context.testplan_list)):
  238. self.index_list[index].grid(row=index + 1, column=0, sticky=W)
  239. self.step_buttons[index].grid(row=index + 1, column=1, sticky=W, padx=5, pady=2)
  240. self.step_name_list[index].grid(row=index + 1, column=2, sticky=W, padx=5, pady=2)
  241. self.step_data_list[index].grid(row=index + 1, column=3, sticky=W, padx=5, pady=2)
  242. self.step_status_list[index].grid(row=index + 1, column=4, sticky=W, padx=5, pady=2)
  243. self.notebook_box.add(self.status_frame, text='Status')
  244. self.notebook_box.add(self.logFrame, text='Log')
  245. self.notebook_box.add(self.parametersFrame, text='Parameters')
  246. self.notebook_box.enable_traversal()
  247. self.notebook_box.pack(fill=BOTH, expand="true", padx=5, pady=1)
  248. self.root.mainloop()
  249. def readLog1(self):
  250. # This function periodically reads and empties the log buffer
  251. if self.context.log1_buffer != "":
  252. self.logText.insert(END, self.context.log1_buffer)
  253. text = self.my_io_process.read_file()
  254. if text != "":
  255. self.logText.insert(END, text)
  256. if self.context.log1_buffer != "" or text != "": # if log buffer was populated
  257. self.logText.yview_pickplace("end") # move the scrollbar to the bottom
  258. self.context.log1_buffer = "" # empty the log buffer
  259. self.root.update() # update the screen
  260. self.root.after(100, self.readLog1)
  261. def set_param_frame(self):
  262. self.L = []
  263. self.E = []
  264. count = 1
  265. for key, value in self.context.params_dict.items():
  266. lbl = Label(self.parametersFrame, text=key)
  267. enty = Entry(self.parametersFrame, bd=4)
  268. enty.delete(0, END)
  269. enty.insert(0, value)
  270. enty.config(state=DISABLED)
  271. lbl.grid(row=count, sticky=W)
  272. enty.grid(row=count, column=1)
  273. self.L.append(lbl)
  274. self.E.append(enty)
  275. count = count + 1
  276. def run_single_step(self, step_num):
  277. """运行单个测试步骤"""
  278. self.context.print1("\n--------------------single_step---------------------")
  279. self.context.print1("Step " + str(step_num) + " Started test...")
  280. # 重置该步骤的状态
  281. self.context.step_status[step_num] = 'r'
  282. item_info = self.context.testplan_list[step_num]
  283. func_name = item_info["Function"]
  284. arguments = item_info["Arguments"]
  285. func = getattr(test_function, func_name, None)
  286. if not func:
  287. self.context.step_status[step_num] = "f"
  288. self.context.result_msg[step_num] = "{} is not defined".format(func_name)
  289. self.root.after(0, self.update_step_status)
  290. else:
  291. self.context.step_status[step_num] = 'r' # running
  292. self.root.after(0, self.update_step_status)
  293. result, result_msg = False, "FAIL"
  294. def run_step():
  295. if arguments:
  296. result, result_msg = func(self.context, arguments)
  297. else:
  298. result, result_msg = func(self.context)
  299. self.context.step_status[step_num] = result and "p" or "f"
  300. self.context.result_msg[step_num] = result_msg
  301. self.context.print1("\n----------------------------------------------------")
  302. self.context.print1(result_msg)
  303. # 更新UI状态
  304. thread = threading.Thread(target=run_step)
  305. thread.start()
  306. # self.root.after(0, self.update_step_status)
  307. def update_step_status(self):
  308. """更新步骤状态显示"""
  309. total_no_of_steps = len(self.context.step_status)
  310. for index in range(total_no_of_steps):
  311. if self.context.step_status[index] == 'r':
  312. self.step_status_list[index]["text"] = "RUNNING..."
  313. self.step_status_list[index]["bg"] = 'yellow'
  314. elif self.context.step_status[index] == 'p':
  315. self.step_status_list[index]["text"] = "PASS"
  316. self.step_data_list[index]["text"] = self.context.result_msg[index]
  317. self.step_status_list[index]["bg"] = 'green'
  318. elif self.context.step_status[index] == 'f':
  319. self.step_status_list[index]["text"] = "ERROR!"
  320. self.step_status_list[index]["bg"] = 'red'
  321. self.step_data_list[index]["text"] = self.context.result_msg[index]
  322. # 继续监控状态变化
  323. self.root.after(200, self.update_step_status)
  324. def start(self):
  325. self.context.step_status = ['x'] + ['ns'] * len(self.context.testplan_list) # 索引从1开始
  326. self.start_button.config(state=DISABLED) # disable the start button
  327. for button in self.step_buttons:
  328. button.config(state=DISABLED)
  329. for data_lable in self.step_data_list:
  330. data_lable["text"] = ""
  331. self.logText.delete('1.0', 'end') # delete the log
  332. self.my_io_process.reset_counter()
  333. self.thread_list['thread'] = MasterThread(self.context)
  334. self.thread_list['thread'].start()
  335. self.update_test_status()
  336. def update_test_status(self):
  337. # manage the frame that displays the step-by-step status
  338. # manage the top-level status Label
  339. total_no_of_steps = len(self.context.testplan_list)
  340. try:
  341. SrNo = self.context.device_info['SrNo']
  342. string = "Device: " + SrNo
  343. self.deviceInfo_label.config(text=string)
  344. except:
  345. string = "Device: "
  346. self.deviceInfo_label.config(text=string)
  347. flag = 0
  348. count1 = 0
  349. count2 = 0
  350. count3 = 0
  351. for i in range(total_no_of_steps):
  352. if self.context.step_status[i] == 'r': # if running
  353. self.step_status_list[i]["text"] = "RUNNING..."
  354. self.step_status_list[i]["bg"] = 'yellow'
  355. self.test_status_label.config(bg='yellow')
  356. self.test_status_label.config(text='RUNNING...')
  357. self.step_data_list[i]["text"] = ""
  358. elif self.context.step_status[i] == 'p': # if passed
  359. self.step_status_list[i]["text"] = "PASS"
  360. self.step_status_list[i]["bg"] = 'green'
  361. self.step_data_list[i]["text"] = self.context.result_msg[i]
  362. count2 = count2 + 1
  363. elif self.context.step_status[i] == 'f': # if failed
  364. self.step_status_list[i]["text"] = "ERROR!"
  365. self.step_status_list[i]["bg"] = 'red'
  366. self.test_status_label.config(text='ERROR!')
  367. self.test_status_label.config(bg='red')
  368. self.step_data_list[i]["text"] = self.context.result_msg[i]
  369. # flag = 1
  370. count3 = count3 + 1
  371. elif self.context.step_status[i] == 'ns': # if not started
  372. self.step_status_list[i]["text"] = "NOT STARTED"
  373. self.step_status_list[i]["bg"] = 'gray'
  374. count1 = count1 + 1
  375. if count1 == total_no_of_steps: # if none of the steps have started
  376. self.test_status_label.config(bg='gray')
  377. self.test_status_label.config(text='NOT STARTED')
  378. if count2 == total_no_of_steps: # if all the steps are pass
  379. self.test_status_label.config(bg='green')
  380. self.test_status_label.config(text='DONE!')
  381. flag = 1
  382. if (count3 + count2 + count1) == total_no_of_steps: # if all the steps are done
  383. self.start_button.config(state=NORMAL) # enable the start button
  384. for button in self.step_buttons:
  385. button.config(state=NORMAL)
  386. if flag == 0: # if there is no error and all the steps aren't done yet
  387. self.root.after(100, self.update_test_status)
  388. else: # if there is an error or if all the steps are done already
  389. self.context.device_info['SrNo'] = ""
  390. return 0
  391. if __name__ == "__main__":
  392. g = GuiThread()
  393. g.run()