run_test.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967
  1. #!/usr/bin/python
  2. # _*_ coding: utf-8 _*_
  3. import traceback
  4. try:
  5. from Tkinter import * # python2
  6. import ttk
  7. import tkMessageBox
  8. import tkFont
  9. except Exception as e:
  10. # Python 2环境下不会走到这里,保留仅做兼容占位
  11. from tkinter import *
  12. from tkinter import ttk
  13. from tkinter import messagebox as tkMessageBox
  14. import tkinter.font as tkFont
  15. import sys
  16. import os
  17. import subprocess
  18. import time
  19. import threading
  20. import csv
  21. from datetime import datetime
  22. import subprocess
  23. sys.path.append("./")
  24. for i in sys.path:
  25. print(i)
  26. from requests_offline import requests
  27. import test_function
  28. # class test_function:
  29. # @staticmethod
  30. # def default_test(context, args=""):
  31. # """默认测试函数,实际使用时替换为真实逻辑"""
  32. # time.sleep(0.1)
  33. # return True, "Test passed (default function)"
  34. #
  35. # sys.modules['test_function'] = test_function
  36. class TestContext:
  37. def __init__(self):
  38. self.script_ver = 'C180-C-C7-v2.6'
  39. self.log_file = './Logs/temp.txt'
  40. self.log1_buffer = ""
  41. self.run_log_content = ""
  42. self.log_file_handle = None
  43. self.current_log_path = None
  44. self.tp_path = "./testplan.csv"
  45. self.device_info = {}
  46. #self.mes_url = "http://60.188.52.164:8079/sfc_response.aspx"
  47. #self.TSID = "2208C_10_Link_PCBA01"
  48. #self.station_id = "2208C_10_Link_PCBA01"
  49. self.mes_url = "http://10.60.64.87/BobcatH/sfc_response.aspx"
  50. self.TSID = "ZT001_Function_Test101"
  51. self.station_id = "ZT001_Function_Test101"
  52. self.params_dict = {
  53. 'path_loss': '25',
  54. 'wave_file': './11b_waveforms/wave11rc_1000.mod',
  55. 'per_limit': '92',
  56. 'SA_RSSI': '-80',
  57. 'BTOn_RSSI': '-30'
  58. }
  59. try:
  60. if sys.version_info >= (3, 0):
  61. # Python 3 直接用 open + encoding
  62. with open(self.tp_path, "r", encoding='utf-8') as f:
  63. reader = csv.DictReader(f)
  64. self.testplan_list = []
  65. for row in reader:
  66. if row.get("Disable", "") != "Y":
  67. self.testplan_list.append(row)
  68. else:
  69. try:
  70. f = open(self.tp_path, 'r')
  71. reader = csv.DictReader(f)
  72. self.testplan_list = []
  73. # 修复Python 2中不等于运算符(~=是错误的,应该是!=)
  74. for row in reader:
  75. if row.get("Disable", "") != "Y":
  76. self.testplan_list.append(row)
  77. except Exception as e:
  78. traceback.print_exc()
  79. finally:
  80. f.close() # Python 2需显式关闭文件
  81. except Exception as e:
  82. print(e)
  83. self.step_status = ['ns'] * len(self.testplan_list)
  84. self.result_msg = [''] * len(self.testplan_list)
  85. self.current_loop = 0 # 当前循环次数
  86. self.total_loops = 1 # 总循环次数
  87. def mes_check_sn(self, sn):
  88. try:
  89. data = {
  90. "C": "QUERY_RECORD",
  91. "SN": sn,
  92. "P": "unit_process_check",
  93. "TSID": self.TSID
  94. }
  95. res = requests.post(self.mes_url, data=data, timeout=10)
  96. text = res.text.strip()
  97. # 判断是否通过
  98. #self.print1("MES Result:" + text) 260409 by joe
  99. if "unit_process_check=OK" in text:
  100. return True, "SN校验通过"
  101. else:
  102. return False, text
  103. except Exception as e:
  104. return False,"网络异常:{}".format(str(e))
  105. def mes_get_value(self, sn, mes_key):
  106. mes_key = mes_key or "PART_NO"
  107. try:
  108. data = {
  109. "C": "QUERY_RECORD",
  110. "SN": sn,
  111. "P": mes_key
  112. }
  113. res = requests.post(self.mes_url, data=data, timeout=10)
  114. text = res.text.strip()
  115. #self.print1("MES Result:" + text) 260409 by joe
  116. if "0 SFC_OK" in text:
  117. return True, text
  118. else:
  119. return False, text
  120. except Exception as e:
  121. return False,"网络异常:{}".format(str(e))
  122. def mes_upload_result(self, sn, status, error_code="", values={}):
  123. try:
  124. data = {
  125. "C": "ADD_RECORD",
  126. "STATUS": status, # PASS / FAIL
  127. "STATION_ID": self.station_id,
  128. "SN": sn,
  129. "ERROR_CODE": error_code
  130. }
  131. if values:
  132. data.update(values)
  133. res = requests.post(self.mes_url, data=data, timeout=10)
  134. text = res.text.strip()
  135. self.print1("MES Result:" + text)
  136. if "0 SFC_OK" in text:
  137. return True, "过站成功"
  138. else:
  139. return False, text
  140. except Exception as e:
  141. return False, "网络异常:{}".format(str(e))
  142. def create_sample_testplan(self):
  143. if not os.path.exists(os.path.dirname(self.tp_path)):
  144. os.makedirs(os.path.dirname(self.tp_path))
  145. with open(self.tp_path, 'w', encoding='utf-8', newline='') as f:
  146. writer = csv.DictWriter(f, fieldnames=['TestName', 'Function', 'Arguments', 'Disable'])
  147. writer.writeheader()
  148. writer.writerow({
  149. 'TestName': 'Sample Test',
  150. 'Function': 'default_test',
  151. 'Arguments': '',
  152. 'Disable': ''
  153. })
  154. with open(self.tp_path, "r", encoding="utf-8") as f:
  155. reader = csv.DictReader(f)
  156. self.testplan_list = []
  157. for row in reader:
  158. if row.get("Disable", "") != "Y":
  159. self.testplan_list.append(row)
  160. def print1(self, string, newline=0):
  161. curr_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
  162. log_str = "[{}] {}".format(curr_time,string)
  163. if newline == 0:
  164. self.log1_buffer += "\n" + log_str
  165. elif newline == 1:
  166. self.log1_buffer += "\n" + log_str + "\n"
  167. # 实时写入文件
  168. if self.log_file_handle is not None:
  169. try:
  170. self.log_file_handle.write(log_str + "\n")
  171. self.log_file_handle.flush() # 强制刷盘
  172. except Exception:
  173. pass
  174. # def write_log_to_file(self):
  175. # log_dir = "./Logs/"
  176. # if not os.path.exists(log_dir):
  177. # os.makedirs(log_dir)
  178. #
  179. # filename = time.strftime("log_%Y-%m-%d_%H-%M-%S.txt", time.localtime())
  180. # log_path = os.path.join(log_dir, filename)
  181. #
  182. # with open(log_path, 'w', encoding='utf-8') as f:
  183. # f.write(self.run_log_content)
  184. #
  185. # self.run_log_content = ""
  186. def start_new_log(self):
  187. # 每次测试新建一个带时间戳的日志文件
  188. log_dir = "./Logs/"
  189. if not os.path.exists(log_dir):
  190. os.makedirs(log_dir)
  191. filename = time.strftime("log_%Y-%m-%d_%H-%M-%S.txt", time.localtime())
  192. self.current_log_path = os.path.join(log_dir, filename)
  193. try:
  194. self.log_file_handle = open(self.current_log_path, 'w', encoding='utf-8')
  195. except:
  196. self.log_file_handle = open(self.current_log_path, 'w')
  197. def close_log(self):
  198. # 关闭文件句柄
  199. if self.log_file_handle is not None:
  200. self.log_file_handle.close()
  201. self.log_file_handle = None
  202. class IO_process:
  203. def __init__(self, log_file_path):
  204. self.log_file = log_file_path
  205. self.line_count = 0
  206. def read_file(self):
  207. try:
  208. f = open(self.log_file, 'r')
  209. flag = 0
  210. line = ""
  211. for i, curr_line in enumerate(f):
  212. if i == self.line_count:
  213. self.line_count += 1
  214. flag = 1
  215. line = curr_line
  216. break
  217. f.close()
  218. if flag == 1:
  219. return line.rstrip('\r')
  220. else:
  221. return ""
  222. except Exception as e:
  223. return ""
  224. def empty_file(self):
  225. f = open(self.log_file, 'w')
  226. f.close()
  227. def reset_counter(self):
  228. self.line_count = 0
  229. class MasterThread(threading.Thread):
  230. def __init__(self, context, selected_steps):
  231. threading.Thread.__init__(self)
  232. self.context = context
  233. self.stop_flag = False
  234. self.selected_steps = selected_steps
  235. def run(self):
  236. for loop_num in range(1, self.context.total_loops + 1):
  237. if self.stop_flag:
  238. break
  239. self.context.start_new_log()
  240. self.context.current_loop = loop_num
  241. self.context.print1("=" * 80, 1)
  242. self.context.print1("Device SN: {}".format(self.context.device_info["ScanSN"]))
  243. self.context.print1("Loop {}/{} - X86 C180 Test Started".format(loop_num, self.context.total_loops), 1)
  244. self.context.print1("Script Version: {}".format(self.context.script_ver), 1)
  245. self.context.print1("Selected Test Steps: {}".format(self.selected_steps), 1)
  246. self.context.print1("=" * 80, 1)
  247. self.context.step_status = ['ns'] * len(self.context.testplan_list)
  248. self.context.result_msg = [''] * len(self.context.testplan_list)
  249. for step_num in self.selected_steps:
  250. if self.stop_flag:
  251. break
  252. test_item = self.context.testplan_list[step_num]
  253. self.context.print1("\n----------------------------------------------------")
  254. self.context.print1("Loop {} - Step {} Started test...".format(loop_num, step_num))
  255. func_name = test_item.get("Function", "")
  256. arguments = test_item.get("Arguments", "")
  257. func = getattr(test_function, func_name, None) if 'test_function' in sys.modules else None
  258. if not func:
  259. self.context.step_status[step_num] = "f"
  260. self.context.result_msg[step_num] = "%s is not defined" % func_name
  261. continue
  262. self.context.step_status[step_num] = 'r'
  263. try:
  264. if arguments:
  265. result, result_msg = func(self.context, arguments)
  266. else:
  267. result, result_msg = func(self.context)
  268. except Exception as e:
  269. result = False
  270. result_msg = "Error: {}".format(str(e))
  271. self.context.step_status[step_num] = "p" if result else "f"
  272. self.context.result_msg[step_num] = result_msg
  273. self.context.print1("Loop {} - Step {} Result: {}".format(loop_num, step_num, result_msg))
  274. self.context.print1("\n----------------------------------------------------")
  275. if not self.stop_flag:
  276. self.context.print1("\nLoop {} Completed!".format(loop_num, 1))
  277. if loop_num < self.context.total_loops:
  278. self.context.print1("Waiting 2 seconds before next loop...", 1)
  279. time.sleep(2)
  280. # self.context.print1("\n" + "=" * 80, 1)
  281. # if self.stop_flag:
  282. # self.context.print1("Test stopped manually!", 1)
  283. # else:
  284. # self.context.print1("All {} loops completed!".format(self.context.total_loops), 1)
  285. # self.context.print1("Test concluded!", 1)
  286. # self.context.print1("=" * 80, 1)
  287. self.context.close_log()
  288. # self.context.close_log()
  289. def stop(self):
  290. """停止测试线程"""
  291. self.stop_flag = True
  292. class GuiThread(object):
  293. def __init__(self):
  294. self.context = TestContext()
  295. self.thread_list = {}
  296. self.counter = 0
  297. self.root = Tk()
  298. self.root.geometry("1800x1200")
  299. small_font = tkFont.Font(family="DejaVu Sans", size=8)
  300. self.start_time = None
  301. self.timer_label = None
  302. self.timer_label = None
  303. self.timer_id = None
  304. self.test_finished = False
  305. self.root.protocol("WM_DELETE_WINDOW", self.destroy)
  306. self.root.title("WiPER: FT Simular Test")
  307. self.sn_frame = Frame(self.root)
  308. self.need_sn_var = BooleanVar() # 存储复选框状态
  309. self.need_sn_var.set(True) # 默认勾选(需要校验SN)
  310. self.need_sn_check = Checkbutton(
  311. self.sn_frame,
  312. text="Need SN",
  313. variable=self.need_sn_var,
  314. font=("Arial", 9),
  315. command=self.toggle_sn_input # 勾选状态变化时触发输入框启用/禁用
  316. )
  317. self.offline_test_var = BooleanVar()
  318. self.offline_test_var.set(False) # 默认不勾选(正常联网上传)
  319. self.sn_label = Label(self.sn_frame, text="Device SN:", fg='darkblue', font=("Arial", 10))
  320. self.sn_entry = Entry(self.sn_frame, width=20, font=("Arial", 10)) # 宽度更大,适配SN长度
  321. self.sn_entry.insert(0, "") # 默认空值
  322. self.sn_entry.bind('<Return>', lambda event: self.get_device_sn())
  323. self.get_sn_btn = Button(self.sn_frame, text="Confirm SN", font=("Arial", 9),
  324. command=self.get_device_sn, width=16)
  325. self.sn_hint = Label(self.sn_frame, text="(Device Serial Number)", fg='gray', font=("Arial", 8))
  326. self.need_sn_check.pack(side=LEFT, padx=5)
  327. self.sn_label.pack(side=LEFT, padx=5)
  328. self.sn_entry.pack(side=LEFT, padx=5)
  329. self.get_sn_btn.pack(side=LEFT, padx=5) # 按钮放在输入框后面
  330. self.sn_hint.pack(side=LEFT, padx=5)
  331. self.offline_check = Checkbutton(
  332. self.sn_frame,
  333. text="离线测试",
  334. variable=self.offline_test_var,
  335. font=("Arial", 9),
  336. fg='red'
  337. )
  338. self.offline_check.pack(side=LEFT, padx=5)
  339. self.sn_frame.pack(pady=5)
  340. self.time_loop_frame = Frame(self.root)
  341. self.time_loop_frame.pack(pady=5)
  342. self.timer_label = Label(self.time_loop_frame, text="Time: 00:00:00", fg='darkred',
  343. font=tkFont.Font(family="Arial", size=10, weight="bold"))
  344. self.loop_status_label = Label(self.time_loop_frame, text="Current Loop: 0/0", fg='purple',
  345. font=tkFont.Font(family="Arial"))
  346. self.loop_label = Label(self.time_loop_frame, text="Loop Count:", fg='darkblue', font=("Arial", 10))
  347. self.loop_entry = Entry(self.time_loop_frame, width=10, font=("Arial", 10))
  348. self.loop_entry.insert(0, "1") # 默认循环1次
  349. self.loop_hint = Label(self.time_loop_frame, text="(test loops)", fg='gray', font=("Arial", 8))
  350. self.timer_label.pack(side=LEFT, padx=5)
  351. self.loop_status_label.pack(side=LEFT, padx=5)
  352. self.loop_label.pack(side=LEFT, padx=5)
  353. self.loop_entry.pack(side=LEFT, padx=5)
  354. self.loop_hint.pack(side=LEFT, padx=5)
  355. self.note1 = Label(self.root, text="Please press 'Start Test' to begin...", fg='blue')
  356. self.start_button = Button(self.root, text="Start Test", command=self.start)
  357. self.stop_button = Button(self.root, text="Stop Test", command=self.stop_test, state=DISABLED)
  358. self.test_status_label = Label(self.root, text="NOT STARTED", width=15, bg='gray')
  359. self.deviceInfo_label = Label(self.root, text="Device SN: ", width=20, bg="#CCCCCC")
  360. self.notebook_box = ttk.Notebook(self.root, name='notebook')
  361. self.status_frame = Frame(self.root)
  362. self.logFrame = Frame(self.root)
  363. self.parametersFrame = Frame(self.root)
  364. self.logText = Text(self.logFrame)
  365. self.logScrollbar = Scrollbar(self.logFrame)
  366. self.canvas = Canvas(self.status_frame, bg="white")
  367. self.scrollbar_y = Scrollbar(self.status_frame, command=self.canvas.yview)
  368. self.lblframe = Frame(self.canvas)
  369. self.canvas_window = self.canvas.create_window((0, 0), window=self.lblframe, anchor="nw")
  370. self.canvas.configure(yscrollcommand=self.scrollbar_y.set)
  371. def on_canvas_configure(event):
  372. self.canvas.itemconfig(self.canvas_window, width=event.width)
  373. self.canvas.configure(scrollregion=self.canvas.bbox("all"))
  374. def on_frame_configure(event):
  375. self.canvas.configure(scrollregion=self.canvas.bbox("all"))
  376. self.canvas.bind("<Configure>", on_canvas_configure)
  377. self.lblframe.bind("<Configure>", on_frame_configure)
  378. def on_mouse_wheel(event):
  379. if event.delta > 0:
  380. self.canvas.yview_scroll(-1, "pages")
  381. else:
  382. self.canvas.yview_scroll(1, "pages")
  383. if sys.platform == 'darwin':
  384. self.canvas.bind('<MouseWheel>', on_mouse_wheel)
  385. else:
  386. self.canvas.bind('<MouseWheel>', on_mouse_wheel)
  387. self.canvas.bind('<Button-4>', lambda e: self.canvas.yview_scroll(-1, "units"))
  388. self.canvas.bind('<Button-5>', lambda e: self.canvas.yview_scroll(1, "units"))
  389. self.canvas.bind("<Enter>", lambda e: self.canvas.focus_set())
  390. self.canvas.pack(side=LEFT, fill=BOTH, expand=True)
  391. self.scrollbar_y.pack(side=RIGHT, fill=Y)
  392. self.index_list = []
  393. self.step_check_vars = [] # 复选框变量
  394. self.step_checkbuttons = [] # 复选框控件
  395. self.step_buttons = []
  396. self.step_name_list = []
  397. self.step_data_list = []
  398. self.step_status_list = []
  399. self.select_all_var = BooleanVar(value=True) # 默认全选
  400. self.select_all_check = Checkbutton(
  401. self.lblframe,
  402. variable=self.select_all_var,
  403. command=self.on_select_all_toggle, # 切换时自动全选/取消全选
  404. anchor='center'
  405. )
  406. self.select_all_check.grid(row=0, column=0, padx=2, pady=1)
  407. title_1 = Label(self.lblframe, text="Index", anchor='center', width=5, font=small_font)
  408. title_1.grid(row=0, column=1, sticky=W, padx=2, pady=1)
  409. title_2 = Label(self.lblframe, text="Run Steps", anchor='center', width=12, font=small_font)
  410. title_2.grid(row=0, column=2, sticky=W, padx=2, pady=1)
  411. title_3 = Label(self.lblframe, text="TestName", anchor='center', width=50, font=small_font)
  412. title_3.grid(row=0, column=3, sticky=W, padx=2, pady=1)
  413. title_4 = Label(self.lblframe, text="TestData", anchor='center', width=50, font=small_font)
  414. title_4.grid(row=0, column=4, sticky=W, padx=2, pady=1)
  415. title_5 = Label(self.lblframe, text="TestStatus", anchor='center', width=20, font=small_font)
  416. title_5.grid(row=0, column=5, sticky=W, padx=2, pady=1)
  417. for index, desc in enumerate(self.context.testplan_list):
  418. # 新增:复选框(默认全选)
  419. check_var = BooleanVar(value=True)
  420. self.step_check_vars.append(check_var)
  421. check_btn = Checkbutton(
  422. self.lblframe,
  423. variable=check_var,
  424. anchor='center',
  425. command=self.update_select_all_state # 点击时自动更新顶部全选框
  426. )
  427. self.step_checkbuttons.append(check_btn)
  428. index_lable = Label(self.lblframe, text=str(index), anchor='center', width=5, font=small_font)
  429. self.index_list.append(index_lable)
  430. btn = Button(self.lblframe, text="Run Step %d" % index,
  431. command=lambda num=index: self.run_single_step(num),
  432. width=12, bg='lightblue', font=small_font)
  433. self.step_buttons.append(btn)
  434. item_label = Label(self.lblframe, text=desc.get("TestName", ""), anchor='w', bg='#CCCCCC', width=50,
  435. font=small_font)
  436. self.step_name_list.append(item_label)
  437. data_label = Label(self.lblframe, text="", anchor='w', bg='#CCCCCC', width=50, font=small_font)
  438. self.step_data_list.append(data_label)
  439. status_label = Label(self.lblframe, text="NOT STARTED", bg='#CCCCCC', width=20, font=small_font)
  440. self.step_status_list.append(status_label)
  441. self.my_io_process = IO_process(self.context.log_file)
  442. self.readLog1()
  443. self.set_param_frame()
  444. def select_all(self):
  445. for var in self.step_check_vars:
  446. var.set(True)
  447. def unselect_all(self):
  448. for var in self.step_check_vars:
  449. var.set(False)
  450. def get_selected_steps(self):
  451. selected = []
  452. for idx, var in enumerate(self.step_check_vars):
  453. if var.get():
  454. selected.append(idx)
  455. return selected
  456. def toggle_sn_input(self):
  457. if self.need_sn_var.get():
  458. self.sn_entry.config(state=NORMAL)
  459. self.get_sn_btn.config(state=NORMAL)
  460. self.sn_label.config(fg='darkblue')
  461. else:
  462. self.sn_entry.config(state=DISABLED)
  463. self.get_sn_btn.config(state=DISABLED)
  464. self.sn_label.config(fg='gray')
  465. self.deviceInfo_label.config(text="Device SN: N/A")
  466. self.context.device_info['ScanSN'] = "N/A"
  467. def get_device_sn(self):
  468. if not self.need_sn_var.get():
  469. return
  470. test_running = 'thread' in self.thread_list and self.thread_list['thread'].is_alive()
  471. if test_running:
  472. return
  473. sn_value = self.sn_entry.get().strip()
  474. if sn_value:
  475. self.context.device_info['ScanSN'] = sn_value
  476. else:
  477. self.context.device_info['ScanSN'] = "Unknown"
  478. tkMessageBox.showerror("Error", "请输入SN!")
  479. return
  480. sn_length = len(sn_value)
  481. if sn_length != 11:
  482. tkMessageBox.showerror("Error", "SN长度不合格!应该为11位。")
  483. return
  484. string = "Device: " + sn_value
  485. self.deviceInfo_label.config(text=string)
  486. self.start()
  487. def update_timer(self):
  488. if self.start_time and not self.test_finished:
  489. elapsed_seconds = int(time.time() - self.start_time)
  490. hours = elapsed_seconds // 3600
  491. minutes = (elapsed_seconds % 3600) // 60
  492. seconds = elapsed_seconds % 60
  493. timer_text = "Time: %02d:%02d:%02d" % (hours, minutes, seconds)
  494. self.timer_label.config(text=timer_text)
  495. self.loop_status_label.config(
  496. text="Current Loop: {}/{}".format(self.context.current_loop, self.context.total_loops)
  497. )
  498. self.timer_id = self.root.after(1000, self.update_timer)
  499. def stop_timer(self):
  500. if self.timer_id:
  501. self.root.after_cancel(self.timer_id)
  502. self.timer_id = None
  503. self.test_finished = True
  504. self.loop_status_label.config(
  505. text="Loop: {}/{}".format(self.context.current_loop, self.context.total_loops)
  506. )
  507. if self.start_time:
  508. total_seconds = int(time.time() - self.start_time)
  509. hours = total_seconds // 3600
  510. minutes = (total_seconds % 3600) // 60
  511. seconds = total_seconds % 60
  512. final_text = "Total Time: %02d:%02d:%02d" % (hours, minutes, seconds)
  513. self.timer_label.config(text=final_text, fg='darkgreen')
  514. def stop_test(self):
  515. if tkMessageBox.askokcancel("Stop Test?", "Are you sure you want to stop the test?"):
  516. if 'thread' in self.thread_list and self.thread_list['thread'].is_alive():
  517. self.thread_list['thread'].stop()
  518. self.stop_timer()
  519. self.test_status_label.config(text='STOPPED', bg='orange')
  520. self.start_button.config(state=NORMAL)
  521. self.stop_button.config(state=DISABLED)
  522. self.get_sn_btn.config(state=NORMAL if self.need_sn_var.get() else DISABLED)
  523. self.sn_entry.config(state=NORMAL if self.need_sn_var.get() else DISABLED)
  524. self.loop_entry.config(state=NORMAL)
  525. self.select_all_check.config(state=NORMAL)
  526. self.offline_check.config(state=NORMAL)
  527. for button in self.step_buttons:
  528. button.config(state=NORMAL)
  529. for check_obj in self.step_checkbuttons:
  530. check_obj.config(state=NORMAL)
  531. self.context.close_log()
  532. def destroy(self):
  533. if tkMessageBox.askokcancel("Quit?", "Are you sure you want to quit?"):
  534. if 'thread' in self.thread_list and self.thread_list['thread'].is_alive():
  535. self.thread_list['thread'].stop()
  536. self.stop_timer()
  537. self.root.quit()
  538. self.context.close_log()
  539. def run(self):
  540. self.note1.pack()
  541. btn_frame = Frame(self.root)
  542. btn_frame.pack()
  543. self.start_button = Button(btn_frame, text="Start Test", command=self.start)
  544. self.stop_button = Button(btn_frame, text="Stop Test", command=self.stop_test, state=DISABLED)
  545. self.start_button.pack(side=LEFT, padx=5)
  546. self.stop_button.pack(side=LEFT, padx=5)
  547. self.test_status_label.pack()
  548. self.deviceInfo_label.pack(fill=X, expand=False, padx=50, pady=20)
  549. self.logScrollbar.config(command=self.logText.yview)
  550. self.logText.config(yscrollcommand=self.logScrollbar.set)
  551. self.logText.pack(side=LEFT, fill=Y)
  552. self.logScrollbar.pack(side=RIGHT, fill=Y)
  553. # 布局步骤列表
  554. for index in range(len(self.context.testplan_list)):
  555. self.step_checkbuttons[index].grid(row=index + 1, column=0, sticky=W, padx=2, pady=0)
  556. self.index_list[index].grid(row=index + 1, column=1, sticky=W, padx=2, pady=0)
  557. self.step_buttons[index].grid(row=index + 1, column=2, sticky=W, padx=2, pady=0)
  558. self.step_name_list[index].grid(row=index + 1, column=3, sticky=W, padx=2, pady=0)
  559. self.step_data_list[index].grid(row=index + 1, column=4, sticky=W, padx=2, pady=0)
  560. self.step_status_list[index].grid(row=index + 1, column=5, sticky=W, padx=2, pady=0)
  561. self.notebook_box.add(self.status_frame, text='Status')
  562. self.notebook_box.add(self.logFrame, text='Log')
  563. self.notebook_box.add(self.parametersFrame, text='Parameters')
  564. self.notebook_box.enable_traversal()
  565. self.notebook_box.pack(fill=BOTH, expand="true", padx=5, pady=1)
  566. self.root.mainloop()
  567. def readLog1(self):
  568. if self.context.log1_buffer != "":
  569. self.logText.insert(END, self.context.log1_buffer)
  570. text = self.my_io_process.read_file()
  571. if text != "":
  572. self.logText.insert(END, text)
  573. if self.context.log1_buffer != "" or text != "":
  574. self.logText.yview_pickplace("end")
  575. self.context.log1_buffer = ""
  576. self.root.update()
  577. self.root.after(100, self.readLog1)
  578. def set_param_frame(self):
  579. self.L = []
  580. self.E = []
  581. count = 1
  582. for key, value in self.context.params_dict.items():
  583. lbl = Label(self.parametersFrame, text=key)
  584. enty = Entry(self.parametersFrame, bd=4)
  585. enty.delete(0, END)
  586. enty.insert(0, value)
  587. enty.config(state=DISABLED)
  588. lbl.grid(row=count, sticky=W)
  589. enty.grid(row=count, column=1)
  590. self.L.append(lbl)
  591. self.E.append(enty)
  592. count += 1
  593. def run_single_step(self, step_num):
  594. self.context.print1("\n--------------------single_step---------------------")
  595. self.context.print1("Step %d Started test..." % step_num)
  596. self.context.step_status[step_num] = 'r'
  597. item_info = self.context.testplan_list[step_num]
  598. func_name = item_info.get("Function", "")
  599. arguments = item_info.get("Arguments", "")
  600. func = getattr(test_function, func_name, None) if 'test_function' in sys.modules else None
  601. if not func:
  602. self.context.step_status[step_num] = "f"
  603. self.context.result_msg[step_num] = "%s is not defined" % func_name
  604. self.root.after(0, self.update_step_status)
  605. else:
  606. self.context.step_status[step_num] = 'r'
  607. self.root.after(0, self.update_step_status)
  608. def run_step():
  609. result, result_msg = False, "FAIL"
  610. if arguments:
  611. result, result_msg = func(self.context, arguments)
  612. else:
  613. result, result_msg = func(self.context)
  614. self.context.step_status[step_num] = "p" if result else "f"
  615. self.context.result_msg[step_num] = result_msg
  616. self.context.print1("\n----------------------------------------------------")
  617. self.context.print1(result_msg)
  618. self.root.after(0, self.update_step_status)
  619. thread = threading.Thread(target=run_step)
  620. thread.start()
  621. def update_step_status(self):
  622. total_no_of_steps = len(self.context.step_status)
  623. for index in range(total_no_of_steps):
  624. status = self.context.step_status[index]
  625. if status == 'r':
  626. self.step_status_list[index]["text"] = "RUNNING..."
  627. self.step_status_list[index]["bg"] = 'yellow'
  628. elif status == 'p':
  629. self.step_status_list[index]["text"] = "PASS"
  630. self.step_data_list[index]["text"] = self.context.result_msg[index]
  631. self.step_status_list[index]["bg"] = 'green'
  632. elif status == 'f':
  633. self.step_status_list[index]["text"] = "ERROR!"
  634. self.step_status_list[index]["bg"] = 'red'
  635. self.step_data_list[index]["text"] = self.context.result_msg[index]
  636. self.root.after(200, self.update_step_status)
  637. def start(self):
  638. # 是否离线模式
  639. is_offline = self.offline_test_var.get()
  640. self.context.device_info['offline_test'] = is_offline
  641. # 获取勾选的测试步骤
  642. selected_steps = self.get_selected_steps()
  643. if not selected_steps:
  644. tkMessageBox.showerror("Error", "请至少选择一个测试项!")
  645. return
  646. # 获取循环次数
  647. try:
  648. loop_count = int(self.loop_entry.get())
  649. if loop_count < 1:
  650. tkMessageBox.showerror("Error", "Loop count must be at least 1!")
  651. return
  652. self.context.total_loops = loop_count
  653. self.context.current_loop = 0
  654. except ValueError:
  655. tkMessageBox.showerror("Error", "Please enter a valid number for loop count!")
  656. return
  657. # SN校验
  658. if self.need_sn_var.get():
  659. sn_value = self.sn_entry.get().strip()
  660. if not sn_value:
  661. tkMessageBox.showerror("Error", "请输入SN!")
  662. return
  663. if len(sn_value) != 11:
  664. tkMessageBox.showerror("Error", "SN长度不合格!应该为11位。")
  665. return
  666. self.context.device_info['ScanSN'] = sn_value
  667. self.deviceInfo_label.config(text="Device: " + sn_value)
  668. # writeSN 260328 by joe
  669. try:
  670. proc = subprocess.Popen( #amidelnx_64
  671. ["sh","scansn.sh","/SS",sn_value],
  672. stdin=subprocess.PIPE,
  673. stdout=subprocess.PIPE,
  674. stderr=subprocess.PIPE
  675. )
  676. stdout,stderr = proc.communicate(input="111111\n")
  677. result = proc.returncode
  678. #a = result.stdout
  679. except subprocess.CalledProcessError as e:
  680. tkMessageBox.showerror("Error", "Command failed: {e.stderr}")
  681. return
  682. else:
  683. sn_value = "N/A"
  684. self.context.device_info['ScanSN'] = "N/A"
  685. self.deviceInfo_label.config(text="Device SN: N/A")
  686. # MES 校验
  687. if is_offline:
  688. self.context.print1("离线模式,跳过MES检查!")
  689. else:
  690. result, msg = self.context.mes_check_sn(sn_value)
  691. if not result:
  692. tkMessageBox.showerror("Error", "SN:%s MES check error!!\n%s" % (sn_value, msg))
  693. return
  694. self.context.print1(msg)
  695. # 启动计时器
  696. self.start_time = time.time()
  697. self.test_finished = False
  698. self.update_timer()
  699. # 界面状态
  700. self.context.step_status = ['x'] + ['ns'] * len(self.context.testplan_list)
  701. self.start_button.config(state=DISABLED)
  702. self.stop_button.config(state=NORMAL)
  703. self.need_sn_check.config(state=DISABLED)
  704. self.get_sn_btn.config(state=DISABLED)
  705. self.sn_entry.config(state=DISABLED)
  706. self.loop_entry.config(state=DISABLED)
  707. self.select_all_check.config(state=DISABLED)
  708. self.offline_check.config(state=DISABLED)
  709. for check_obj in self.step_checkbuttons:
  710. check_obj.config(state=DISABLED)
  711. for button in self.step_buttons:
  712. button.config(state=DISABLED)
  713. for data_lable in self.step_data_list:
  714. data_lable["text"] = ""
  715. self.logText.delete('1.0', 'end')
  716. self.my_io_process.reset_counter()
  717. self.thread_list['thread'] = MasterThread(self.context, selected_steps)
  718. self.thread_list['thread'].start()
  719. self.update_test_status()
  720. def update_test_status(self):
  721. total_no_of_steps = len(self.context.testplan_list)
  722. try:
  723. ScanSN = self.context.device_info['ScanSN']
  724. string = "Device: " + ScanSN
  725. self.deviceInfo_label.config(text=string)
  726. except KeyError:
  727. string = "Device: "
  728. self.deviceInfo_label.config(text=string)
  729. flag = 0
  730. count1 = 0
  731. count2 = 0
  732. count3 = 0
  733. for i in range(total_no_of_steps):
  734. status = self.context.step_status[i]
  735. if status == 'r':
  736. self.step_status_list[i]["text"] = "RUNNING..."
  737. self.step_status_list[i]["bg"] = 'yellow'
  738. self.test_status_label.config(bg='yellow')
  739. self.test_status_label.config(text='RUNNING...')
  740. self.step_data_list[i]["text"] = ""
  741. elif status == 'p':
  742. self.step_status_list[i]["text"] = "PASS"
  743. self.step_status_list[i]["bg"] = 'green'
  744. self.step_data_list[i]["text"] = self.context.result_msg[i]
  745. count2 += 1
  746. elif status == 'f':
  747. self.step_status_list[i]["text"] = "ERROR!"
  748. self.step_status_list[i]["bg"] = 'red'
  749. self.test_status_label.config(text='ERROR!')
  750. self.test_status_label.config(bg='red')
  751. self.step_data_list[i]["text"] = self.context.result_msg[i]
  752. count3 += 1
  753. elif status == 'ns':
  754. self.step_status_list[i]["text"] = "NOT STARTED"
  755. self.step_status_list[i]["bg"] = 'gray'
  756. count1 += 1
  757. if count1 == total_no_of_steps:
  758. self.test_status_label.config(bg='gray')
  759. self.test_status_label.config(text='NOT STARTED')
  760. test_running = 'thread' in self.thread_list and self.thread_list['thread'].is_alive()
  761. if not test_running and not self.test_finished:
  762. if count2 == len(self.get_selected_steps()):
  763. self.test_status_label.config(bg='green')
  764. self.test_status_label.config(text='DONE!')
  765. test_result = "PASS"
  766. else:
  767. test_result = "FAIL"
  768. # MES过站上传
  769. is_offline = self.offline_test_var.get()
  770. if not is_offline:
  771. test_result_values = {}
  772. error_code = ""
  773. for i in range(total_no_of_steps):
  774. status = self.context.step_status[i]
  775. msg = self.context.result_msg[i]
  776. item = self.context.testplan_list[i]
  777. if status == 'f':
  778. test_result_values[item["TestName"]] =msg
  779. elif status == "p":
  780. test_result_values[item["TestName"]] ="PASS"
  781. if status == 'f' and "串口" in item["TestName"]:
  782. error_code = "S102"
  783. uplaload_result, msg = self.context.mes_upload_result(ScanSN, test_result, error_code, test_result_values)
  784. if not uplaload_result:
  785. tkMessageBox.showerror("Error", "SN:%s\n%s" % (ScanSN, msg))
  786. else:
  787. msg = "离线模式,跳过上传MES"
  788. self.context.print1(msg)
  789. self.stop_timer()
  790. self.start_button.config(state=NORMAL)
  791. self.stop_button.config(state=DISABLED)
  792. self.need_sn_check.config(state=NORMAL)
  793. self.get_sn_btn.config(state=NORMAL if self.need_sn_var.get() else DISABLED)
  794. self.sn_entry.config(state=NORMAL if self.need_sn_var.get() else DISABLED)
  795. self.loop_entry.config(state=NORMAL)
  796. self.select_all_check.config(state=NORMAL)
  797. self.offline_check.config(state=NORMAL)
  798. for check_obj in self.step_checkbuttons:
  799. check_obj.config(state=NORMAL)
  800. for button in self.step_buttons:
  801. button.config(state=NORMAL)
  802. flag = 1
  803. if flag == 0:
  804. self.root.after(100, self.update_test_status)
  805. else:
  806. self.context.device_info['ScanSN'] = ""
  807. self.context.close_log()
  808. return 0
  809. def on_select_all_toggle(self):
  810. is_select_all = self.select_all_var.get()
  811. for var in self.step_check_vars:
  812. var.set(is_select_all)
  813. def update_select_all_state(self):
  814. all_checked = all(var.get() for var in self.step_check_vars)
  815. self.select_all_var.set(all_checked)
  816. if __name__ == "__main__":
  817. if not os.path.exists("./Logs"):
  818. os.makedirs("./Logs")
  819. g = GuiThread()
  820. g.run()