ResultsBrowser.py 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182
  1. from sqlalchemy import create_engine, desc, and_
  2. from sqlalchemy.orm import sessionmaker
  3. from baangt.base.DataBaseORM import DATABASE_URL, TestrunLog, GlobalAttribute, TestCaseLog, TestCaseSequenceLog, TestCaseField
  4. from baangt.base.ExportResults.ExportResults import ExcelSheetHelperFunctions
  5. from baangt.base.PathManagement import ManagedPaths
  6. import baangt.base.GlobalConstants as GC
  7. import uuid
  8. from datetime import datetime
  9. from xlsxwriter import Workbook
  10. import logging
  11. import os
  12. import json
  13. import re
  14. import uuid
  15. import time # time tracker
  16. logger = logging.getLogger("pyC")
  17. class QuerySet:
  18. # flags
  19. SIZE_TCS = 0
  20. SIZE_TC = 1
  21. def __init__(self):
  22. self.data = None
  23. def __del__(self):
  24. del self.data
  25. @property
  26. def length(self):
  27. return len(self.data)
  28. def set(self, array):
  29. self.data = array
  30. def names(self):
  31. return {tr.testrunName for tr in self.data}
  32. def all(self):
  33. return (tr for tr in self.data)
  34. def filter(self, tr_name, tcs_index=None, tc_index=None):
  35. #
  36. # returns generator of filtered query_set
  37. #
  38. if tcs_index is None:
  39. return filter(lambda tr: tr.testrunName == tr_name, self.data)
  40. if tc_index is None:
  41. return filter(lambda tr: tr.testrunName == tr_name and len(tr.testcase_sequences) > tcs_index, self.data)
  42. return filter(
  43. lambda tr: tr.testrunName == tr_name and len(tr.testcase_sequences) > tcs_index \
  44. and len(tr.testcase_sequences[tcs_index].testcases) > tc_index,
  45. self.data,
  46. )
  47. def max_size(self, tr_name, tcs_index=None):
  48. #
  49. # returns the max number of
  50. # tcs_index is None: testcase sequences within the testruns in query_set
  51. # tcs_index is a number: testcases within specified testcase sequence of all testruns in db
  52. #
  53. # test case sequences
  54. if tcs_index is None:
  55. return max(map(lambda tr: len(tr.testcase_sequences), self.filter(tr_name=tr_name)))
  56. # test cases
  57. return max(map(lambda tr: len(tr.testcase_sequences[tcs_index].testcases), self.filter(tr_name=tr_name, tcs_index=tcs_index)))
  58. def tr_avg_duration(self, tr_name):
  59. duration = 0
  60. quantity = 0
  61. for d in (tr.duration for tr in self.filter(tr_name) if tr.duration):
  62. duration += d
  63. quantity += 1
  64. if quantity > 0:
  65. return round(duration/quantity, 2)
  66. return None
  67. def tc_avg_duration(self, tr_name, indexes):
  68. durations = map(
  69. lambda tr: tr.testcase_sequences[indexes[0]].testcases[indexes[1]].duration,
  70. self.filter(tr_name, tcs_index=indexes[0], tc_index=indexes[1]),
  71. )
  72. duration = 0
  73. quantity = 0
  74. for d in durations:
  75. if d:
  76. duration += d
  77. quantity += 1
  78. if quantity > 0:
  79. return round(duration/quantity, 2)
  80. return None
  81. class ExportSheet:
  82. def __init__(self, sheet, header_format):
  83. self.sheet = sheet
  84. self.line = 1 # current line
  85. self.column = 0
  86. self.header_map = {}
  87. self.header_format = header_format
  88. def set_header_format(self, header_format):
  89. self.header_format = header_format
  90. def hr(self):
  91. self.line += 1
  92. def header(self, headers):
  93. for header in headers:
  94. self.sheet.write(0, self.column, header, self.header_format)
  95. self.header_map[header] = self.column
  96. self.column += 1
  97. def add_header(self, header):
  98. if not header in self.header_map:
  99. self.sheet.write(0, self.column, header, self.header_format)
  100. self.header_map[header] = self.column
  101. self.column += 1
  102. def row(self, row_data):
  103. for col, data in enumerate(row_data):
  104. self.sheet.write(self.line, col, data.get('value'), data.get('format'))
  105. self.line += 1
  106. def new_row(self, row_data):
  107. self.line += 1
  108. for col, data in enumerate(row_data):
  109. self.sheet.write(self.line, col, data.get('value'), data.get('format'))
  110. def by_header(self, header, value):
  111. self.add_header(header)
  112. self.sheet.write(self.line, self.header_map[header], value)
  113. class ResultsBrowser:
  114. def __init__(self, db_url=None):
  115. # setup db engine
  116. if db_url:
  117. engine = create_engine(db_url)
  118. else:
  119. engine = create_engine(DATABASE_URL)
  120. self.db = sessionmaker(bind=engine)()
  121. # result query set
  122. self.query_set = QuerySet()
  123. # tag of the current query set
  124. self.tags = None
  125. # set of stages
  126. self.stages = None
  127. # path management
  128. self.managedPaths = ManagedPaths()
  129. logger.info(f'Initiated with DATABASE_URL: {db_url if db_url else DATABASE_URL}')
  130. def __del__(self):
  131. self.db.close()
  132. def name_list(self):
  133. names = self.db.query(TestrunLog.testrunName).group_by(TestrunLog.testrunName).order_by(TestrunLog.testrunName).all()
  134. return [x[0] for x in names]
  135. def stage_list(self):
  136. stages = self.db.query(GlobalAttribute.value).filter_by(name=GC.EXECUTION_STAGE)\
  137. .group_by(GlobalAttribute.value).order_by(GlobalAttribute.value).all()
  138. return [x[0] for x in stages]
  139. def query(self, name=None, stage=None, start_date=None, end_date=None):
  140. #
  141. # get TestrunLogs by name, stage and dates
  142. #
  143. # format date
  144. format_date = lambda date: date.strftime('%Y-%m-%d') if date else None
  145. # get records
  146. records = []
  147. logger.info(f'Quering: name={name}, stage={stage}, dates=[{format_date(start_date)} - {format_date(end_date)}]')
  148. # filter by name and stage
  149. if name and stage:
  150. self.stages = {stage}
  151. records = self.db.query(TestrunLog).order_by(TestrunLog.startTime).filter_by(testrunName=name)\
  152. .filter(TestrunLog.globalVars.any(and_(GlobalAttribute.name==GC.EXECUTION_STAGE, GlobalAttribute.value==stage))).all()
  153. # filter by name
  154. elif name:
  155. # get Testrun stages
  156. stages = self.db.query(GlobalAttribute.value).filter(GlobalAttribute.testrun.has(TestrunLog.testrunName==name))\
  157. .filter_by(name=GC.EXECUTION_STAGE).group_by(GlobalAttribute.value).order_by(GlobalAttribute.value).all()
  158. self.stages = {x[0] for x in stages}
  159. for s in self.stages:
  160. logs = self.db.query(TestrunLog).order_by(TestrunLog.startTime).filter_by(testrunName=name)\
  161. .filter(TestrunLog.globalVars.any(and_(GlobalAttribute.name==GC.EXECUTION_STAGE, GlobalAttribute.value==s))).all()
  162. records.extend(logs)
  163. # filter by stage
  164. elif stage:
  165. self.stages = {stage}
  166. # get Testrun names
  167. names = self.db.query(TestrunLog.testrunName)\
  168. .filter(TestrunLog.globalVars.any(and_(GlobalAttribute.name==GC.EXECUTION_STAGE, GlobalAttribute.value==stage)))\
  169. .group_by(TestrunLog.testrunName).order_by(TestrunLog.testrunName).all()
  170. names = [x[0] for x in names]
  171. for n in names:
  172. logs = self.db.query(TestrunLog).order_by(TestrunLog.startTime).filter_by(testrunName=n)\
  173. .filter(TestrunLog.globalVars.any(and_(GlobalAttribute.name==GC.EXECUTION_STAGE, GlobalAttribute.value==stage))).all()
  174. records.extend(logs)
  175. # get all testruns ordered by name and stage
  176. else:
  177. # get Testrun names
  178. names = self.db.query(TestrunLog.testrunName).group_by(TestrunLog.testrunName).order_by(TestrunLog.testrunName).all()
  179. names = [x[0] for x in names]
  180. self.stages = set()
  181. for n in names:
  182. # get Testrun stages
  183. stages = self.db.query(GlobalAttribute.value).filter(GlobalAttribute.testrun.has(TestrunLog.testrunName==n))\
  184. .filter_by(name=GC.EXECUTION_STAGE).group_by(GlobalAttribute.value).order_by(GlobalAttribute.value).all()
  185. stages = [x[0] for x in stages]
  186. self.stages.update(stages)
  187. for s in stages:
  188. logs = self.db.query(TestrunLog).order_by(TestrunLog.startTime).filter_by(testrunName=n)\
  189. .filter(TestrunLog.globalVars.any(and_(GlobalAttribute.name==GC.EXECUTION_STAGE, GlobalAttribute.value==s))).all()
  190. records.extend(logs)
  191. # filter by dates
  192. if start_date and end_date:
  193. self.query_set.set([log for log in records if log.startTime > start_date and log.startTime < end_date])
  194. elif start_date:
  195. self.query_set.set([log for log in records if log.startTime >= start_date])
  196. elif end_date:
  197. self.query_set.set([log for log in records if log.startTime <= end_date])
  198. else:
  199. self.query_set.set(records)
  200. # set the tags
  201. self.tags = {
  202. 'Name': name or 'All',
  203. 'Stage': stage or 'All',
  204. 'Date from': format_date(start_date) or 'None',
  205. 'Date to': format_date(end_date or datetime.now()),
  206. }
  207. logger.info(f'Number of found records: {self.query_set.length}')
  208. def export_old(self):
  209. #
  210. # export the query set to xlsx
  211. #
  212. # set labels
  213. labelTetsrun = 'TestRun'
  214. labelTestCaseSequence = 'Test Case Sequence'
  215. labelTestCase = 'Test Cases'
  216. labelStage = 'Stage'
  217. labelAvgDuration = 'Avg. Duration'
  218. # initialize workbook
  219. path_to_file = self.managedPaths.getOrSetDBExportPath().joinpath(f'TestrunLogs_{"_".join(list(map(str, self.tag.values())))}.xlsx')
  220. workbook = Workbook(str(path_to_file))#, {'constant_memory': True})
  221. # define cell formats
  222. # green background
  223. cellFormatGreen = workbook.add_format({'bg_color': 'green'})
  224. # red background
  225. cellFormatRed = workbook.add_format({'bg_color': 'red'})
  226. # yellow background
  227. cellFormatYellow = workbook.add_format({'bg_color': 'yellow'})
  228. # bold font
  229. cellFormatBold = workbook.add_format({'bold': True})
  230. # bold and italic font
  231. cellFormatBoldItalic = workbook.add_format({'bold': True, 'italic': True})
  232. # summary tab
  233. print('*** Summary Sheet')
  234. sheet = workbook.add_worksheet('Summary')
  235. sheet.set_column(first_col=0, last_col=0, width=18)
  236. # title
  237. sheet.write(0, 0, f'{labelTetsrun}s Summary', cellFormatBold)
  238. # parameters
  239. line = 1
  240. for key, value in self.tag.items():
  241. line += 1
  242. sheet.write(line, 0, key)#, cellFormatBold)
  243. sheet.write(line, 1, value)
  244. # average duration
  245. line += 2
  246. sheet.write(line, 0, labelAvgDuration, cellFormatBold)
  247. sheet.write(line, 1, self.average_duration())
  248. # testcases
  249. line += 2
  250. sheet.write(line, 0, f'{labelTestCase}s', cellFormatBold)
  251. status_style = {
  252. GC.TESTCASESTATUS_SUCCESS: cellFormatGreen,
  253. GC.TESTCASESTATUS_ERROR: cellFormatRed,
  254. GC.TESTCASESTATUS_WAITING: None,
  255. }
  256. for tcs_index in range(self.size()):
  257. print(f'**** TestCaseSequence-{tcs_index}')
  258. # testcase sequence
  259. line += 1
  260. sheet.write(line, 0, labelTestCaseSequence, cellFormatYellow)
  261. sheet.write(line, 1, tcs_index, cellFormatYellow)
  262. #line += 1
  263. #sheet.write(line, 0, labelAvgDuration)
  264. #sheet.write(line, 1, self.average_duration(testcase_sequence=tcs_index)) #-----------> high resource consumption
  265. # test cases
  266. # header
  267. line += 2
  268. sheet.write(line, 0, f'{labelTetsrun} Date', cellFormatBoldItalic)
  269. sheet.write(line, 1, labelTestCase, cellFormatBoldItalic)
  270. line += 1
  271. for i in range(self.size(testcase_sequence=tcs_index)):
  272. sheet.write(line, 1 + i, i)
  273. stage_col = i + 2
  274. sheet.write(line-1, stage_col, labelStage, cellFormatBoldItalic)
  275. sheet.write(line-1, stage_col+1, f'{labelTetsrun} ID', cellFormatBoldItalic)
  276. # status
  277. tr_counter_base = line
  278. for tr in filter(lambda tr: len(tr.testcase_sequences)>tcs_index, self.query_set):
  279. print(f'***** TestRun {line-tr_counter_base}: {len(tr.testcase_sequences[tcs_index].testcases)} tcases')
  280. line += 1
  281. sheet.write(line, 0, tr.startTime.strftime('%Y-%m-%d %H:%M:%S'))
  282. #col = 1
  283. for col, tc in enumerate(tr.testcase_sequences[tcs_index].testcases, 1):
  284. sheet.write(line, col, tc.status, status_style.get(tc.status))
  285. #sheet.write(line, col, tc.duration, status_style.get(tc.status))
  286. #col += 1
  287. #sheet.write(line, col, tr.duration)
  288. #sheet.write(line, col+1, tr.testcase_sequences[0].duration)
  289. sheet.write(line, stage_col, tr.stage)
  290. sheet.write(line, stage_col+1, str(tr))
  291. line += 1
  292. sheet.write(line, 0, labelAvgDuration, cellFormatBoldItalic)
  293. for tc_index in range(self.size(testcase_sequence=tcs_index)):
  294. sheet.write(line, tc_index+1, self.average_duration(testcase_sequence=tcs_index, testcase=tc_index))
  295. # empty line separator
  296. line += 1
  297. '''
  298. # test case tabs
  299. for stage in self.stages:
  300. sheet = workbook.add_worksheet(f'{stage}_JSON')
  301. # write headers
  302. headers = [
  303. 'Stage',
  304. f'{labelTetsrun} ID',
  305. f'{labelTestCase} ID',
  306. 'Attribute',
  307. 'Value',
  308. ]
  309. for index, label in enumerate(headers):
  310. sheet.write(0, index, label, cellFormatBold)
  311. # write data
  312. line = 1
  313. for tr in self.query_set:
  314. # check the stage
  315. if tr.stage == stage:
  316. for tcs in tr.testcase_sequences:
  317. for tc in tcs.testcases:
  318. for field in tc.fields:
  319. sheet.write(line, 0, stage)
  320. sheet.write(line, 1, str(tr))
  321. sheet.write(line, 2, str(tc))
  322. sheet.write(line, 3, field.name)
  323. sheet.write(line, 4, field.value)
  324. line += 1
  325. # autowidth
  326. for i in range(len(headers)):
  327. ExcelSheetHelperFunctions.set_column_autowidth(sheet, i)
  328. '''
  329. # output tab
  330. print('\n*** Output Sheet')
  331. sheet = workbook.add_worksheet('Output')
  332. # write headers
  333. base_headers = [
  334. 'Testrun ID',
  335. 'TestCase ID',
  336. 'TestCase Number',
  337. ]
  338. base_fields = [
  339. GC.EXECUTION_STAGE,
  340. GC.TESTCASESTATUS,
  341. GC.TIMING_DURATION,
  342. ]
  343. field_header_dict = {field: index for index, field in enumerate(base_fields, len(base_headers))}
  344. field_header_set = set(field_header_dict.keys())
  345. field_counter = len(base_headers) + len(field_header_set)
  346. for index, label in enumerate(base_headers):
  347. sheet.write(0, index, label, cellFormatBold)
  348. for label, index in field_header_dict.items():
  349. sheet.write(0, index, label, cellFormatBold)
  350. # write data
  351. line = 1
  352. for tr in self.query_set:
  353. for tcs in tr.testcase_sequences:
  354. for tc_index, tc in enumerate(tcs.testcases):
  355. sheet.write(line, 0, str(tr))
  356. sheet.write(line, 1, str(tc))
  357. sheet.write(line, 2, tc_index)
  358. #sheet.write(line, 2, tr.stage)
  359. #sheet.write(line, 3, tc.status)
  360. #sheet.write(line, 4, tc.duration)
  361. #sheet.write(line, 5, json.dumps(tc.fields_as_dict()))
  362. for field in tc.fields:
  363. if not field.name in field_header_set:
  364. sheet.write(0, field_counter, field.name, cellFormatBold)
  365. field_header_set.add(field.name)
  366. field_header_dict[field.name] = field_counter
  367. field_counter += 1
  368. sheet.write(line, field_header_dict[field.name], field.value)
  369. line += 1
  370. # autowidth
  371. for col in range(6):#len(base_headers)+len(field_header_set)):
  372. ExcelSheetHelperFunctions.set_column_autowidth(sheet, col)
  373. workbook.close()
  374. logger.info(f'Query successfully exported to {path_to_file}')
  375. return path_to_file
  376. def export_sep(self):
  377. #
  378. # exports the query set to xlsx
  379. #
  380. # initialize workbook
  381. path_to_file = self.managedPaths.getOrSetDBExportPath().joinpath(f'TestrunLogs_{"_".join(list(map(str, self.tags.values())))}.xlsx')
  382. self.workbook = Workbook(str(path_to_file))#, {'constant_memory': True})
  383. self.make_summary_sheet()
  384. self.make_output_sheet()
  385. self.workbook.close()
  386. logger.info(f'Query successfully exported to {path_to_file}')
  387. return path_to_file
  388. def make_summary_sheet(self):
  389. #
  390. # makes Summary sheet of XLSX export file
  391. #
  392. # set labels
  393. labels = {
  394. 'testrun': 'TestRun',
  395. 'testcase_sequence': 'Test Case Sequence',
  396. 'testcase': 'Test Case',
  397. 'stage': 'Stage',
  398. 'duration': 'Avg. Duration',
  399. }
  400. # define cell formats
  401. cformats = {
  402. 'bg_red': self.workbook.add_format({'bg_color': 'red'}),
  403. 'bg_green': self.workbook.add_format({'bg_color': 'green'}),
  404. 'bg_yellow': self.workbook.add_format({'bg_color': 'yellow'}),
  405. 'font_bold': self.workbook.add_format({'bold': True}),
  406. 'font_bold_italic': self.workbook.add_format({'bold': True, 'italic': True}),
  407. }
  408. # map status styles
  409. status_style = {
  410. GC.TESTCASESTATUS_SUCCESS: cformats.get('bg_green'),
  411. GC.TESTCASESTATUS_ERROR: cformats.get('bg_red'),
  412. GC.TESTCASESTATUS_WAITING: None,
  413. }
  414. # write data
  415. sheet = self.workbook.add_worksheet('Summary')
  416. sheet.set_column(first_col=0, last_col=0, width=18)
  417. # title
  418. time_start = time.time() # -------------------------> time tracker
  419. sheet.write(0, 0, f'{labels.get("testrun")}s Summary', cformats.get('font_bold'))
  420. # parameters
  421. line = 1
  422. for key, value in self.tags.items():
  423. line += 1
  424. sheet.write(line, 0, key, cformats.get('font_bold'))
  425. sheet.write(line, 1, value)
  426. # testruns
  427. for tr_name in self.query_set.names():
  428. print(f'*** Tetsrun "{tr_name}"')
  429. # testrun name
  430. line += 2
  431. sheet.write(line, 0, labels.get('testrun'), cformats.get('font_bold'))
  432. sheet.write(line, 1, tr_name)
  433. # average duration
  434. line += 1
  435. sheet.write(line, 0, labels.get('duration'), cformats.get('font_bold'))
  436. sheet.write(line, 1, self.query_set.tr_avg_duration(tr_name))
  437. # testcase sequences
  438. for tcs_index in range(self.query_set.max_size(tr_name)):
  439. print(f'**** TestCaseSequence-{tcs_index}')
  440. # testcase sequence
  441. line += 2
  442. sheet.write(line, 0, labels.get('testcase_sequence'), cformats.get('font_bold'))
  443. sheet.write(line, 1, tcs_index)
  444. # test cases
  445. # header
  446. line += 2
  447. sheet.write(line, 0, 'Run Date', cformats.get('font_bold_italic'))
  448. sheet.write(line, 1, labels.get('testcase'), cformats.get('font_bold_italic'))
  449. line += 1
  450. for tc_index in range(self.query_set.max_size(tr_name, tcs_index=tcs_index)):
  451. sheet.write(line, tc_index+1, tc_index)
  452. # store stage column
  453. stage_col = tc_index + 2
  454. sheet.write(line-1, stage_col, labels.get('stage'), cformats.get('font_bold_italic'))
  455. sheet.write(line-1, stage_col+1, f'{labels.get("testcase")} ID', cformats.get('font_bold_italic'))
  456. # testcase status
  457. tr_counter_base = line
  458. for tr in self.query_set.filter(tr_name, tcs_index):
  459. print(f'***** TestRun {line-tr_counter_base}: {len(tr.testcase_sequences[tcs_index].testcases)} tcases')
  460. line += 1
  461. sheet.write(line, 0, tr.startTime.strftime('%Y-%m-%d %H:%M:%S'))
  462. #for col, tc in enumerate(tr.testcase_sequences[tcs_index].testcases, 1):
  463. # sheet.write(line, col, tc.status, status_style.get(tc.status))
  464. sheet.write(line, stage_col, tr.stage)
  465. sheet.write(line, stage_col+1, str(tr))
  466. # avg durations
  467. line += 1
  468. sheet.write(line, 0, labels.get('duration'), cformats.get('font_bold_italic'))
  469. #for tc_index in range(self.query_set.max_size(tr_name, tcs_index=tcs_index)):
  470. # sheet.write(line, tc_index+1, self.query_set.tc_avg_duration(tr_name, (tcs_index, tc_index)))
  471. print(f'*** EXECUTION TIME: {time.time()-time_start} seconds') # ------------------> time tracker
  472. def make_output_sheet(self):
  473. #
  474. # makes output sheet
  475. #
  476. # output tab
  477. print('\n*** Output Sheet')
  478. sheet = self.workbook.add_worksheet('Output')
  479. # format
  480. cellFormatBold = self.workbook.add_format({'bold': True})
  481. # write headers
  482. base_headers = [
  483. 'Testrun ID',
  484. 'TestCase ID',
  485. 'TestCase Number',
  486. ]
  487. base_fields = [
  488. GC.EXECUTION_STAGE,
  489. GC.TESTCASESTATUS,
  490. GC.TIMING_DURATION,
  491. ]
  492. field_header_dict = {field: index for index, field in enumerate(base_fields, len(base_headers))}
  493. field_header_set = set(field_header_dict.keys())
  494. field_counter = len(base_headers) + len(field_header_set)
  495. time_start = time.time() # -------------------------> time tracker
  496. for index, label in enumerate(base_headers):
  497. sheet.write(0, index, label, cellFormatBold)
  498. for label, index in field_header_dict.items():
  499. sheet.write(0, index, label, cellFormatBold)
  500. # write data
  501. line = 1
  502. for tr_num, tr in enumerate(self.query_set.all()):
  503. print(f'**** TR-{tr_num}')
  504. for tcs in tr.testcase_sequences:
  505. for tc_index, tc in enumerate(tcs.testcases):
  506. sheet.write(line, 0, str(tr))
  507. sheet.write(line, 1, str(tc))
  508. sheet.write(line, 2, tc_index)
  509. #sheet.write(line, 2, tr.stage)
  510. #sheet.write(line, 3, tc.status)
  511. #sheet.write(line, 4, tc.duration)
  512. #sheet.write(line, 5, json.dumps(tc.fields_as_dict()))
  513. for field in tc.fields:
  514. if not field.name in field_header_set:
  515. sheet.write(0, field_counter, field.name, cellFormatBold)
  516. field_header_set.add(field.name)
  517. field_header_dict[field.name] = field_counter
  518. field_counter += 1
  519. sheet.write(line, field_header_dict[field.name], field.value)
  520. line += 1
  521. # autowidth first columns
  522. for col in range(6):#len(base_headers)+len(field_header_set)):
  523. ExcelSheetHelperFunctions.set_column_autowidth(sheet, col)
  524. print(f'*** EXECUTION TIME: {time.time()-time_start} seconds') # ------------------> time tracker
  525. # Execution time of export
  526. #
  527. # lazy: select immediate joined subquery | query_fields | query_field_data
  528. # name[0]: 34.24 33.81 59.68 | 33.52 | 91.46
  529. # name[1]: 65.35 65.37 35.85 | 63.06 | 57.58
  530. # name[2]: | 871.05 | 293.13
  531. # all names: | 7200 | 966
  532. def export_nonumber(self):
  533. #
  534. # exports the query set to xlsx
  535. #
  536. # initialize workbook
  537. path_to_file = self.managedPaths.getOrSetDBExportPath().joinpath(f'TestrunLogs_{"_".join(list(map(str, self.tags.values())))}.xlsx')
  538. workbook = Workbook(str(path_to_file))
  539. # set labels
  540. labels = {
  541. 'testrun': 'TestRun',
  542. 'testcase_sequence': 'Test Case Sequence',
  543. 'testcase': 'Test Case',
  544. 'stage': 'Stage',
  545. 'duration': 'Avg. Duration',
  546. }
  547. # set output headers
  548. base_headers = [
  549. 'Testrun ID',
  550. 'TestCase ID',
  551. 'TestCase Number',
  552. ]
  553. base_fields = [
  554. GC.EXECUTION_STAGE,
  555. GC.TESTCASESTATUS,
  556. GC.TIMING_DURATION,
  557. ]
  558. # define cell formats
  559. cformats = {
  560. 'bg_red': workbook.add_format({'bg_color': 'red'}),
  561. 'bg_green': workbook.add_format({'bg_color': 'green'}),
  562. 'bg_yellow': workbook.add_format({'bg_color': 'yellow'}),
  563. 'font_bold': workbook.add_format({'bold': True}),
  564. 'font_bold_italic': workbook.add_format({'bold': True, 'italic': True}),
  565. }
  566. # map status styles
  567. status_style = {
  568. GC.TESTCASESTATUS_SUCCESS: cformats.get('bg_green'),
  569. GC.TESTCASESTATUS_ERROR: cformats.get('bg_red'),
  570. GC.TESTCASESTATUS_WAITING: None,
  571. }
  572. # create sheets
  573. sheets = {
  574. 'summary': ExportSheet(workbook.add_worksheet('Summary'), cformats.get('font_bold')),
  575. 'output': ExportSheet(workbook.add_worksheet('Output'), cformats.get('font_bold')),
  576. }
  577. # write summary titles
  578. time_start = time.time() # -------------------------> time tracker
  579. # title
  580. sheets['summary'].sheet.set_column(first_col=0, last_col=0, width=18)
  581. sheets['summary'].header([f'{labels.get("testrun")}s Summary'])
  582. # parameters
  583. for key, value in self.tags.items():
  584. sheets['summary'].row([
  585. {
  586. 'value': key,
  587. 'format': cformats.get('font_bold'),
  588. },
  589. {
  590. 'value': value,
  591. }
  592. ])
  593. # write output titles
  594. sheets['output'].header(base_headers + base_fields)
  595. # write items
  596. # testruns
  597. for tr_name in self.query_set.names():
  598. print(f'\n*** Tetsrun "{tr_name}": {len(list(self.query_set.filter(tr_name)))} records')
  599. # testrun name
  600. sheets['summary'].hr()
  601. sheets['summary'].row([
  602. {
  603. 'value': labels.get('testrun'),
  604. 'format': cformats.get('font_bold'),
  605. },
  606. {
  607. 'value': tr_name,
  608. }
  609. ])
  610. # average duration
  611. sheets['summary'].row([
  612. {
  613. 'value': labels.get('duration'),
  614. 'format': cformats.get('font_bold'),
  615. },
  616. {
  617. 'value': self.query_set.tr_avg_duration(tr_name),
  618. }
  619. ])
  620. # testcase sequences
  621. for tcs_index in range(self.query_set.max_size(tr_name)):
  622. print(f'**** TestCaseSequence-{tcs_index}')
  623. # testcase sequence
  624. sheets['summary'].hr()
  625. sheets['summary'].row([
  626. {
  627. 'value': labels.get('testcase_sequence'),
  628. 'format': cformats.get('font_bold'),
  629. },
  630. {
  631. 'value': tcs_index,
  632. }
  633. ])
  634. # test cases
  635. # header
  636. tc_num_max = self.query_set.max_size(tr_name, tcs_index=tcs_index)
  637. sheets['summary'].hr()
  638. sheets['summary'].row(
  639. [
  640. {
  641. 'value': 'Run Date',
  642. 'format': cformats.get('font_bold_italic'),
  643. },
  644. {
  645. 'value': labels.get('testcase'),
  646. 'format': cformats.get('font_bold_italic'),
  647. },
  648. ] + [{} for i in range(1, tc_num_max)] +[
  649. {
  650. 'value': labels.get('stage'),
  651. 'format': cformats.get('font_bold_italic'),
  652. },
  653. {
  654. 'value': f'{labels.get("testcase")} ID',
  655. 'format': cformats.get('font_bold_italic'),
  656. },
  657. ]
  658. )
  659. sheets['summary'].row(
  660. [{}] + [{'value': i} for i in range(tc_num_max)]
  661. )
  662. durations = [[.0,0] for i in range(tc_num_max)]
  663. for tr_index, tr in enumerate(self.query_set.filter(tr_name, tcs_index)):
  664. tc_num = len(tr.testcase_sequences[tcs_index].testcases)
  665. print(f'***** TestRun Log {tr_index}: {tc_num} test cases')
  666. status_row = [{'value': tr.startTime.strftime('%Y-%m-%d %H:%M:%S')}]
  667. # query testcases
  668. tcs_id = tr.testcase_sequences[tcs_index].id
  669. tc_query = self.db.query(TestCaseLog.id).filter_by(testcase_sequence_id=tcs_id)
  670. #for tc_index, tc in enumerate(tr.testcase_sequences[tcs_index].testcases):
  671. for tc_index, tc in enumerate(tc_query.yield_per(200)):
  672. #for field in tc.fields:
  673. field_query = self.db.query(TestCaseField.name, TestCaseField.value).filter_by(testcase_id=tc[0])
  674. for name, value in field_query.yield_per(200):
  675. if name == GC.TESTCASESTATUS:
  676. status_row.append({
  677. 'value': value,
  678. 'format': status_style.get(value),
  679. })
  680. elif name == GC.EXECUTION_STAGE:
  681. tr_stage = value
  682. elif name == GC.TIMING_DURATION:
  683. m = re.search(r'(?P<hours>\d+):(?P<minutes>\d+):(?P<seconds>\d[\.\d+]*)', value)
  684. if m:
  685. factors = {
  686. 'hours': 3600,
  687. 'minutes': 60,
  688. 'seconds': 1,
  689. }
  690. durations[tc_index][0] += sum([factors[key]*float(value) for key, value in m.groupdict().items()])
  691. durations[tc_index][1] += 1
  692. # write to output
  693. sheets['output'].by_header(name, value)
  694. # write testcase info to output sheet
  695. sheets['output'].row([
  696. {
  697. 'value': str(tr),
  698. },
  699. {
  700. 'value': str(uuid.UUID(bytes=tc[0])),
  701. },
  702. {
  703. 'value': tc_index,
  704. },
  705. ])
  706. # write state row to summary sheet
  707. sheets['summary'].row(
  708. status_row + [{} for i in range(tc_num, tc_num_max)] + [
  709. {
  710. 'value': tr_stage,
  711. },
  712. {
  713. 'value': str(tr),
  714. },
  715. ]
  716. )
  717. # avg durations
  718. #print('*** DURATIONS:')
  719. #print(durations)
  720. #print(list(map(lambda d: round(d[0]/d[1], 2) if d[1] > 0 else .0, durations)))
  721. sheets['summary'].row(
  722. [
  723. {
  724. 'value': labels.get('duration'),
  725. 'format': cformats.get('font_bold_italic'),
  726. },
  727. ] + [{'value': d} for d in map(lambda d: round(d[0]/d[1], 2) if d[1] > 0 else .0, durations)]
  728. )
  729. # autowidth output columns
  730. for col in range(len(base_headers)+len(base_fields)):
  731. ExcelSheetHelperFunctions.set_column_autowidth(sheets['output'].sheet, col)
  732. print(f'\n*** EXECUTION TIME: {time.time()-time_start} seconds\n') # ------------------> time tracker
  733. workbook.close()
  734. logger.info(f'Query successfully exported to {path_to_file}')
  735. return path_to_file
  736. def export(self):
  737. #
  738. # exports the query set to xlsx
  739. #
  740. # initialize workbook
  741. path_to_file = self.managedPaths.getOrSetDBExportPath().joinpath(f'TestrunLogs_{"_".join(list(map(str, self.tags.values())))}.xlsx')
  742. workbook = Workbook(str(path_to_file))
  743. # set labels
  744. labels = {
  745. 'testrun': 'TestRun',
  746. 'testcase_sequence': 'Test Case Sequence',
  747. 'testcase': 'Test Case',
  748. 'stage': 'Stage',
  749. 'duration': 'Avg. Duration',
  750. }
  751. # set output headers
  752. base_headers = [
  753. 'Testrun ID',
  754. 'TestCase ID',
  755. 'TestCase Number',
  756. ]
  757. base_fields = [
  758. GC.EXECUTION_STAGE,
  759. GC.TESTCASESTATUS,
  760. GC.TIMING_DURATION,
  761. ]
  762. # define cell formats
  763. cformats = {
  764. 'bg_red': workbook.add_format({'bg_color': 'red'}),
  765. 'bg_green': workbook.add_format({'bg_color': 'green'}),
  766. 'bg_yellow': workbook.add_format({'bg_color': 'yellow'}),
  767. 'font_bold': workbook.add_format({'bold': True}),
  768. 'font_bold_italic': workbook.add_format({'bold': True, 'italic': True}),
  769. }
  770. # map status styles
  771. status_style = {
  772. GC.TESTCASESTATUS_SUCCESS: cformats.get('bg_green'),
  773. GC.TESTCASESTATUS_ERROR: cformats.get('bg_red'),
  774. GC.TESTCASESTATUS_WAITING: None,
  775. }
  776. # create sheets
  777. sheets = {
  778. 'summary': ExportSheet(workbook.add_worksheet('Summary'), cformats.get('font_bold')),
  779. 'output': ExportSheet(workbook.add_worksheet('Output'), cformats.get('font_bold')),
  780. }
  781. # write summary titles
  782. time_start = time.time() # -------------------------> time tracker
  783. # title
  784. sheets['summary'].sheet.set_column(first_col=0, last_col=0, width=18)
  785. sheets['summary'].header([f'{labels.get("testrun")}s Summary'])
  786. # parameters
  787. for key, value in self.tags.items():
  788. sheets['summary'].row([
  789. {
  790. 'value': key,
  791. 'format': cformats.get('font_bold'),
  792. },
  793. {
  794. 'value': value,
  795. }
  796. ])
  797. # write output titles
  798. sheets['output'].header(base_headers + base_fields)
  799. # write items
  800. # testruns
  801. for tr_name in self.query_set.names():
  802. print(f'\n*** Tetsrun "{tr_name}": {len(list(self.query_set.filter(tr_name)))} records')
  803. # testrun name
  804. sheets['summary'].hr()
  805. sheets['summary'].row([
  806. {
  807. 'value': labels.get('testrun'),
  808. 'format': cformats.get('font_bold'),
  809. },
  810. {
  811. 'value': tr_name,
  812. }
  813. ])
  814. # average duration
  815. sheets['summary'].row([
  816. {
  817. 'value': labels.get('duration'),
  818. 'format': cformats.get('font_bold'),
  819. },
  820. {
  821. 'value': self.query_set.tr_avg_duration(tr_name),
  822. }
  823. ])
  824. #
  825. # testcase sequences
  826. for tcs_index in range(self.query_set.max_size(tr_name)):
  827. tcs_number = tcs_index+1
  828. print(f'**** TestCaseSequence-{tcs_number}')
  829. # testcase sequence
  830. sheets['summary'].hr()
  831. sheets['summary'].row([
  832. {
  833. 'value': labels.get('testcase_sequence'),
  834. 'format': cformats.get('font_bold'),
  835. },
  836. {
  837. 'value': tcs_number,
  838. }
  839. ])
  840. # test cases
  841. # header
  842. tc_num_max = self.query_set.max_size(tr_name, tcs_index=tcs_index)
  843. sheets['summary'].hr()
  844. sheets['summary'].row(
  845. [
  846. {
  847. 'value': 'Run Date',
  848. 'format': cformats.get('font_bold_italic'),
  849. },
  850. {
  851. 'value': labels.get('testcase'),
  852. 'format': cformats.get('font_bold_italic'),
  853. },
  854. ] + [{} for i in range(1, tc_num_max)] +[
  855. {
  856. 'value': labels.get('stage'),
  857. 'format': cformats.get('font_bold_italic'),
  858. },
  859. {
  860. 'value': f'{labels.get("testcase")} ID',
  861. 'format': cformats.get('font_bold_italic'),
  862. },
  863. ]
  864. )
  865. sheets['summary'].row(
  866. [{}] + [{'value': i} for i in range(tc_num_max)]
  867. )
  868. durations = [[.0,0] for i in range(tc_num_max)]
  869. for tr_index, tr in enumerate(self.query_set.filter(tr_name, tcs_index)):
  870. tc_num = len(tr.testcase_sequences[tcs_index].testcases)
  871. print(f'***** TestRun Log {tr_index}: {tc_num} test cases')
  872. status_row = [{'value': tr.startTime.strftime('%Y-%m-%d %H:%M:%S')}]
  873. # query fields
  874. data = self.db.query(
  875. TestCaseField.name,
  876. TestCaseField.value,
  877. TestCaseLog.number,
  878. TestCaseLog.id,
  879. ).join(TestCaseField.testcase).join(TestCaseLog.testcase_sequence).join(TestCaseSequenceLog.testrun)\
  880. .filter(and_(TestrunLog.id == tr.id, TestCaseSequenceLog.number == tcs_number)).order_by(TestCaseLog.number)
  881. tc_id_cur = None
  882. for name, value, tc_index, tc_id in data.yield_per(500):
  883. # summary data
  884. if name == GC.TESTCASESTATUS:
  885. status_row.append({
  886. 'value': value,
  887. 'format': status_style.get(value),
  888. })
  889. elif name == GC.EXECUTION_STAGE:
  890. tr_stage = value
  891. elif name == GC.TIMING_DURATION:
  892. m = re.search(r'(?P<hours>\d+):(?P<minutes>\d+):(?P<seconds>\d[\.\d+]*)', value)
  893. if m:
  894. factors = {
  895. 'hours': 3600,
  896. 'minutes': 60,
  897. 'seconds': 1,
  898. }
  899. durations[tc_index-1][0] += sum([factors[key]*float(value) for key, value in m.groupdict().items()])
  900. durations[tc_index-1][1] += 1
  901. # write to output
  902. # write testcase
  903. if tc_id != tc_id_cur:
  904. tc_id_cur = tc_id
  905. sheets['output'].new_row([
  906. {
  907. 'value': str(tr),
  908. },
  909. {
  910. 'value': str(uuid.UUID(bytes=tc_id)),
  911. },
  912. {
  913. 'value': tc_index,
  914. },
  915. ])
  916. #field
  917. sheets['output'].by_header(name, value)
  918. # write state row to summary sheet
  919. sheets['summary'].row(
  920. status_row + [{} for i in range(tc_num, tc_num_max)] + [
  921. {
  922. 'value': tr_stage,
  923. },
  924. {
  925. 'value': str(tr),
  926. },
  927. ]
  928. )
  929. # avg durations
  930. sheets['summary'].row(
  931. [
  932. {
  933. 'value': labels.get('duration'),
  934. 'format': cformats.get('font_bold_italic'),
  935. },
  936. ] + [{'value': d} for d in map(lambda d: round(d[0]/d[1], 2) if d[1] > 0 else .0, durations)]
  937. )
  938. # autowidth output columns
  939. for col in range(len(base_headers)+len(base_fields)):
  940. ExcelSheetHelperFunctions.set_column_autowidth(sheets['output'].sheet, col)
  941. print(f'\n*** EXECUTION TIME: {time.time()-time_start} seconds\n') # ------------------> time tracker
  942. workbook.close()
  943. logger.info(f'Query successfully exported to {path_to_file}')
  944. return path_to_file
  945. def export_txt(self):
  946. #
  947. # export to txt
  948. #
  949. path_to_file = self.managedPaths.getOrSetDBExportPath().joinpath(f'TestrunLogs_{"_".join(list(map(str, self.tags.values())))}.txt')
  950. # set labels
  951. labels = {
  952. 'testrun': 'TestRun',
  953. 'testcase_sequence': 'Test Case Sequence',
  954. 'testcase': 'Test Case',
  955. 'stage': 'Stage',
  956. 'duration': 'Avg. Duration',
  957. }
  958. # write data
  959. with open(path_to_file, 'w') as f:
  960. # title
  961. f.write(f'{labels.get("testrun")}s Summary\n\n')
  962. # parameters
  963. for key, value in self.tags.items():
  964. f.write(f'{key}\t{value}\n')
  965. # testruns
  966. for tr_name in self.query_set.names():
  967. print(f'*** Tetsrun "{tr_name}"')
  968. # testrun name
  969. f.write(f'\n{labels.get("testrun")}: {tr_name}\n')
  970. # average duration
  971. f.write(f'{labels.get("duration")}: {self.query_set.tr_avg_duration(tr_name)}\n')
  972. # testcase sequences
  973. for tcs_index in range(self.query_set.max_size(tr_name)):
  974. print(f'**** TestCaseSequence-{tcs_index}')
  975. # testcase sequence
  976. f.write(f'\n{labels.get("testcase_sequence")}: {tcs_index}\n\n')
  977. # test cases
  978. # header
  979. tc_num = self.query_set.max_size(tr_name, tcs_index=tcs_index)
  980. f.write(f'{"Run Date":20}{labels.get("testcase"):8}')
  981. f.write(' '*7)
  982. f.write((' '*8)*(tc_num-2))
  983. f.write(f'{labels.get("stage"):8}{labels.get("testcase")} ID\n')
  984. f.write(' '*20)
  985. for tc_index in range(tc_num):
  986. f.write(f'{tc_index:<8}')
  987. f.write('\n')
  988. # testcase status
  989. tr_counter = 1
  990. for tr in self.query_set.filter(tr_name, tcs_index):
  991. print(f'***** TestRun {tr_counter}: {len(tr.testcase_sequences[tcs_index].testcases)} testcases')
  992. tr_counter += 1
  993. f.write(f'{tr.startTime.strftime("%Y-%m-%d %H:%M:%S"):20}')
  994. for tc in tr.testcase_sequences[tcs_index].testcases:
  995. f.write(f'{tc.status:8}')
  996. # tail
  997. print(f'{tc_num} - {len(tr.testcase_sequences[tcs_index].testcases)} = {(tc_num-len(tr.testcase_sequences[tcs_index].testcases))}')
  998. f.write((' '*8)*(tc_num-len(tr.testcase_sequences[tcs_index].testcases)))
  999. f.write(f'{tr.stage:8}{tr}\n')
  1000. # average durations
  1001. f.write(f'{labels.get("duration"):20}')
  1002. for tc_index in range(tc_num):
  1003. f.write(f'{self.query_set.tc_avg_duration(tr_name, (tcs_index, tc_index)):8}')
  1004. logger.info(f'Query successfully exported to {path_to_file}')
  1005. return path_to_file