Python Library Unittest
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

run.py 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. import fstools
  5. from unittest import jsonlog
  6. from unittest import output
  7. import report
  8. import reqif
  9. import json
  10. import os
  11. import sys
  12. import platform
  13. try:
  14. from platform import dist as dist
  15. except ImportError:
  16. from distro import linux_distribution as dist
  17. import getpass
  18. import subprocess
  19. import imp
  20. import xml.dom.minidom
  21. try:
  22. import jinja2
  23. except ImportError:
  24. jinja2 = None
  25. import shutil
  26. ARG_CLEAN = 'clean'
  27. ARG_RUN = 'run'
  28. ARG_FINALISE = 'finalise'
  29. ARG_TEX = 'tex'
  30. ARG_STATUS = 'status'
  31. ARG_COPY = 'copy'
  32. ARG_RELEASE = 'release'
  33. FN_DATA_COLLECTION = 'unittest.json'
  34. FN_TEX_REPORT = 'unittest.tex'
  35. FN_PDF_REPORT = 'unittest.pdf'
  36. FN_COVERAGE = 'coverage.xml'
  37. REPORT_FILES = [FN_DATA_COLLECTION, FN_COVERAGE, FN_PDF_REPORT]
  38. def testresults_filename(ut_folder, filename):
  39. return os.path.join(jsonlog.get_ut_testresult_folder(ut_folder), filename)
  40. def remove_file(filename):
  41. if os.path.exists(filename) and not filename.endswith('.gitkeep'):
  42. try:
  43. output.print_info('Removing %s' % filename)
  44. os.remove(filename)
  45. except OSError:
  46. pass
  47. class coverage_info(list):
  48. KEY_FRAGMENTS = 'fragments'
  49. KEY_START_LINE = 'start'
  50. KEY_END_LINE = 'end'
  51. KEY_COVERAGE_STATE = 'coverage_state'
  52. COVERED = 'covered'
  53. UNCOVERED = 'uncovered'
  54. CLEAN = 'clean'
  55. PARTIALLY_COVERED = 'partially-covered'
  56. def __init__(self, xml_filename, module_basepath):
  57. list.__init__(self)
  58. xmldoc = xml.dom.minidom.parse(xml_filename)
  59. itemlist = xmldoc.getElementsByTagName('package')
  60. for p in itemlist:
  61. module = {}
  62. module[jsonlog.COVI_KEY_NAME] = p.attributes['name'].value[len(module_basepath) + 1:]
  63. module[jsonlog.COVI_KEY_FILEPATH] = p.attributes['name'].value.replace('.', os.path.sep)
  64. module[jsonlog.COVI_KEY_LINE_COVERAGE] = float(p.attributes['line-rate'].value) * 100.
  65. try:
  66. module[jsonlog.COVI_KEY_BRANCH_COVERAGE] = float(p.attributes['branch-rate'].value) * 100.
  67. except AttributeError:
  68. module[jsonlog.COVI_KEY_BRANCH_COVERAGE] = None
  69. module[jsonlog.COVI_KEY_FILES] = []
  70. for c in p.getElementsByTagName('class'):
  71. f = {}
  72. f[jsonlog.COVI_KEY_NAME] = c.attributes['filename'].value[len(module_basepath) + 1:].replace(os.path.sep, '.')
  73. f[jsonlog.COVI_KEY_FILEPATH] = c.attributes['filename'].value
  74. f[jsonlog.COVI_KEY_LINE_COVERAGE] = float(c.attributes['line-rate'].value) * 100.
  75. try:
  76. f[jsonlog.COVI_KEY_BRANCH_COVERAGE] = float(p.attributes['branch-rate'].value) * 100.
  77. except Exception:
  78. f[jsonlog.COVI_KEY_BRANCH_COVERAGE] = None
  79. f[self.KEY_FRAGMENTS] = []
  80. last_hit = None
  81. start_line = 1
  82. end_line = 1
  83. for line in c.getElementsByTagName('line'):
  84. line_no = int(line.attributes['number'].value)
  85. hit = bool(int(line.attributes['hits'].value))
  86. if hit:
  87. cc = line.attributes.get('condition-coverage')
  88. if cc is not None and not cc.value.startswith('100%'):
  89. hit = self.PARTIALLY_COVERED
  90. else:
  91. hit = self.COVERED
  92. else:
  93. hit = self.UNCOVERED
  94. if line_no == 1:
  95. last_hit = hit
  96. elif last_hit != hit or line_no > end_line + 1:
  97. if last_hit is not None:
  98. line = {}
  99. line[self.KEY_START_LINE] = start_line
  100. line[self.KEY_END_LINE] = end_line
  101. line[self.KEY_COVERAGE_STATE] = last_hit
  102. f[self.KEY_FRAGMENTS].append(line)
  103. if line_no > end_line + 1:
  104. line = {}
  105. if last_hit is not None:
  106. line[self.KEY_START_LINE] = end_line + 1
  107. else:
  108. line[self.KEY_START_LINE] = start_line
  109. line[self.KEY_END_LINE] = line_no - 1
  110. line[self.KEY_COVERAGE_STATE] = self.CLEAN
  111. f[self.KEY_FRAGMENTS].append(line)
  112. start_line = line_no
  113. end_line = line_no
  114. last_hit = hit
  115. elif line_no == end_line + 1:
  116. end_line = line_no
  117. if last_hit is not None:
  118. line = {}
  119. line[self.KEY_START_LINE] = start_line
  120. line[self.KEY_END_LINE] = end_line
  121. line[self.KEY_COVERAGE_STATE] = last_hit
  122. f[self.KEY_FRAGMENTS].append(line)
  123. line = {}
  124. if last_hit is not None:
  125. line[self.KEY_START_LINE] = end_line + 1
  126. else:
  127. line[self.KEY_START_LINE] = start_line
  128. line[self.KEY_END_LINE] = None
  129. line[self.KEY_COVERAGE_STATE] = self.CLEAN
  130. f[self.KEY_FRAGMENTS].append(line)
  131. module[jsonlog.COVI_KEY_FILES].append(f)
  132. self.append(module)
  133. def __str__(self):
  134. rv = ''
  135. for module in self:
  136. rv += '%s (%.1f%% - %s)\n' % (module.get(jsonlog.COVI_KEY_NAME), module.get(jsonlog.COVI_KEY_LINE_COVERAGE), module.get(jsonlog.COVI_KEY_FILEPATH))
  137. for py_file in module.get(jsonlog.COVI_KEY_FILES):
  138. rv += ' %s (%.1f%% - %s)\n' % (py_file.get(jsonlog.COVI_KEY_NAME), py_file.get(jsonlog.COVI_KEY_LINE_COVERAGE), py_file.get(jsonlog.COVI_KEY_FILEPATH))
  139. for fragment in py_file.get(self.KEY_FRAGMENTS):
  140. if fragment.get(self.KEY_END_LINE) is not None:
  141. rv += ' %d - %d: %s\n' % (fragment.get(self.KEY_START_LINE), fragment.get(self.KEY_END_LINE), repr(fragment.get(self.KEY_COVERAGE_STATE)))
  142. else:
  143. rv += ' %d - : %s\n' % (fragment.get(self.KEY_START_LINE), repr(fragment.get(self.COVERAGE_STATE)))
  144. return rv
  145. def unittest(options, args, unittest_folder):
  146. if 'release_testcases' in args:
  147. unittest_release_testcases(unittest_folder)
  148. elif 'clean' in args:
  149. unittest_clean(unittest_folder)
  150. elif 'prepare' in args:
  151. unittest_prepare(unittest_folder)
  152. elif 'testrun' in args:
  153. unittest_testrun(unittest_folder, options)
  154. elif 'finalise' in args:
  155. unittest_finalise(unittest_folder)
  156. elif 'status' in args:
  157. unittest_status(unittest_folder)
  158. elif 'publish' in args:
  159. unittest_publish(unittest_folder)
  160. def unittest_release_testcases(ut_folder):
  161. unittest_uid = jsonlog.module_uid(jsonlog.get_ut_testcase_folder(ut_folder))
  162. output.print_header('Releasing unittest')
  163. config_file = jsonlog.get_ut_config(ut_folder)
  164. with open(config_file, 'r') as fh:
  165. conf_file = fh.read()
  166. output.print_action('Setting release_unittest_version = %s in %s' % (unittest_uid, config_file))
  167. with open(config_file, 'w') as fh:
  168. for line in conf_file.splitlines():
  169. if line.startswith('release_unittest_version'):
  170. fh.write("release_unittest_version = '%s'\n" % unittest_uid)
  171. else:
  172. fh.write(line + '\n')
  173. def unittest_clean(ut_folder):
  174. output.print_header('Cleaning up...')
  175. output.print_action('Testresults from last testrun')
  176. for fn in os.listdir(testresults_filename(ut_folder, '')):
  177. remove_file(testresults_filename(ut_folder, fn))
  178. def unittest_prepare(ut_folder):
  179. config = imp.load_source('', jsonlog.get_ut_config(ut_folder))
  180. #
  181. output.print_header("Initiating unittest for first testrun...")
  182. if not os.path.exists(testresults_filename(ut_folder, '')):
  183. output.print_action('Creating outpout folder %s' % testresults_filename(ut_folder, ''))
  184. fstools.mkdir(testresults_filename(ut_folder, ''))
  185. #
  186. output.print_action('Creating unittest data-collection: %s' % testresults_filename(ut_folder, FN_DATA_COLLECTION))
  187. #
  188. system_info = {}
  189. system_info[jsonlog.SYSI_ARCHITECTURE] = platform.architecture()[0]
  190. system_info[jsonlog.SYSI_MACHINE] = platform.machine()
  191. system_info[jsonlog.SYSI_HOSTNAME] = platform.node()
  192. system_info[jsonlog.SYSI_DISTRIBUTION] = ' '.join(dist())
  193. system_info[jsonlog.SYSI_SYSTEM] = platform.system()
  194. system_info[jsonlog.SYSI_KERNEL] = platform.release() + ' (%s)' % platform.version()
  195. system_info[jsonlog.SYSI_USERNAME] = getpass.getuser()
  196. system_info[jsonlog.SYSI_PATH] = ut_folder
  197. #
  198. unittest_info = {}
  199. unittest_info[jsonlog.UTEI_VERSION] = jsonlog.module_uid(jsonlog.get_ut_testcase_folder(ut_folder))
  200. #
  201. testobject_info = {}
  202. testobject_info[jsonlog.TOBI_NAME] = config.lib.__name__
  203. testobject_info[jsonlog.TOBI_VERSION] = jsonlog.module_uid(config.lib.__path__[0])
  204. testobject_info[jsonlog.TOBI_DESCRIPTION] = config.lib.__DESCRIPTION__
  205. testobject_info[jsonlog.TOBI_SUPP_INTERP] = ', '.join(['python%d' % vers for vers in config.lib.__INTERPRETER__])
  206. testobject_info[jsonlog.TOBI_STATE] = jsonlog.TOBI_STATE_RELEASED if config.release_unittest_version == unittest_info[jsonlog.UTEI_VERSION] else jsonlog.TOBI_STATE_IN_DEVELOPMENT
  207. testobject_info[jsonlog.TOBI_DEPENDENCIES] = []
  208. for dependency in config.lib.__DEPENDENCIES__:
  209. testobject_info[jsonlog.TOBI_DEPENDENCIES].append((dependency, jsonlog.module_uid(os.path.join(jsonlog.get_ut_src_folder(ut_folder), dependency))))
  210. #
  211. spec_filename = os.path.join(ut_folder, 'requirements', 'specification.reqif')
  212. output.print_action("Adding Requirement Specification from %s" % spec_filename)
  213. try:
  214. spec = reqif.reqif_dict(spec_filename, 'Heading', 'Software Specification')
  215. except FileNotFoundError:
  216. output.print_info(output.STATUS_FAILED)
  217. spec = {}
  218. else:
  219. output.print_info(output.STATUS_SUCCESS)
  220. #
  221. data_collection = {
  222. jsonlog.MAIN_KEY_SYSTEM_INFO: system_info,
  223. jsonlog.MAIN_KEY_UNITTEST_INFO: unittest_info,
  224. jsonlog.MAIN_KEY_TESTOBJECT_INFO: testobject_info,
  225. jsonlog.MAIN_KEY_SPECIFICATION: spec,
  226. jsonlog.MAIN_KEY_TESTRUNS: [],
  227. }
  228. with open(testresults_filename(ut_folder, FN_DATA_COLLECTION), 'w') as fh:
  229. fh.write(json.dumps(data_collection, indent=4, sort_keys=True))
  230. def unittest_testrun(ut_folder, options):
  231. tests = imp.load_source('', os.path.join(jsonlog.get_ut_testcase_folder(ut_folder), '__init__.py'))
  232. config = imp.load_source('', jsonlog.get_ut_config(ut_folder))
  233. #
  234. interpreter_version = 'python ' + '.'.join(['%d' % n for n in sys.version_info[:3]]) + ' (%s)' % sys.version_info[3]
  235. #
  236. execution_level = report.TCEL_REVERSE_NAMED.get(options.execution_level, report.TCEL_FULL)
  237. #
  238. if sys.version_info.major in config.lib.__INTERPRETER__:
  239. output.print_header("Running \"%s\" Unittest with %s" % (options.execution_level, interpreter_version))
  240. with open(testresults_filename(ut_folder, FN_DATA_COLLECTION), 'r') as fh:
  241. data_collection = json.loads(fh.read())
  242. output.print_action('Executing Testcases')
  243. heading_dict = {}
  244. for key in data_collection[jsonlog.MAIN_KEY_SPECIFICATION].get(jsonlog.SPEC_ITEM_DICT, {}):
  245. heading_dict[key] = data_collection[jsonlog.MAIN_KEY_SPECIFICATION][jsonlog.SPEC_ITEM_DICT][key]['Heading']
  246. test_session = report.testSession(
  247. ['__unittest__', 'root'],
  248. interpreter=interpreter_version,
  249. testcase_execution_level=execution_level,
  250. testrun_id='p%d' % sys.version_info[0],
  251. heading_dict=heading_dict
  252. )
  253. tests.testrun(test_session)
  254. #
  255. output.print_action('Adding Testrun data to %s' % testresults_filename(ut_folder, FN_DATA_COLLECTION))
  256. data_collection[jsonlog.MAIN_KEY_TESTRUNS].append(test_session)
  257. with open(testresults_filename(ut_folder, FN_DATA_COLLECTION), 'w') as fh:
  258. fh.write(json.dumps(data_collection, indent=4, sort_keys=True))
  259. else:
  260. output.print_header("Library does not support %s." % interpreter_version)
  261. def unittest_finalise(ut_folder):
  262. config = imp.load_source('', jsonlog.get_ut_config(ut_folder))
  263. #
  264. output.print_header("Adding Requirement information")
  265. #
  266. with open(testresults_filename(ut_folder, FN_DATA_COLLECTION), 'r') as fh:
  267. data_collection = json.loads(fh.read())
  268. #
  269. data_collection[jsonlog.MAIN_KEY_LOST_SOULS] = {}
  270. #
  271. output.print_action("Adding Lost Requirement Soul")
  272. data_collection[jsonlog.MAIN_KEY_LOST_SOULS][jsonlog.LOST_ITEMLIST] = []
  273. for req_id in data_collection[jsonlog.MAIN_KEY_SPECIFICATION].get(jsonlog.SPEC_ITEM_DICT, {}):
  274. item = data_collection[jsonlog.MAIN_KEY_SPECIFICATION][jsonlog.SPEC_ITEM_DICT][req_id]
  275. if item['system_type_uid'] == '_MR7eNHYYEem_kd-7nxt1sg':
  276. testcase_available = False
  277. for testrun in data_collection[jsonlog.MAIN_KEY_TESTRUNS]:
  278. if req_id in testrun[jsonlog.TRUN_TESTCASES]:
  279. testcase_available = True
  280. break
  281. if not testcase_available:
  282. data_collection[jsonlog.MAIN_KEY_LOST_SOULS][jsonlog.LOST_ITEMLIST].append(req_id)
  283. output.print_info('%s - "%s" has no corresponding testcase' % (item['system_uid'], item['Heading']), output.termcolors.FAIL)
  284. #
  285. output.print_action("Adding Lost Testcase Soul")
  286. data_collection[jsonlog.MAIN_KEY_LOST_SOULS][jsonlog.LOST_TESTCASELIST] = []
  287. for testrun in data_collection[jsonlog.MAIN_KEY_TESTRUNS]:
  288. for tc_id in testrun.get(jsonlog.TRUN_TESTCASES, {}):
  289. if tc_id not in data_collection[jsonlog.MAIN_KEY_SPECIFICATION].get(jsonlog.SPEC_ITEM_DICT, {}) and tc_id not in data_collection[jsonlog.MAIN_KEY_LOST_SOULS][jsonlog.LOST_TESTCASELIST]:
  290. data_collection[jsonlog.MAIN_KEY_LOST_SOULS][jsonlog.LOST_TESTCASELIST].append(tc_id)
  291. output.print_info('"%s" has no corresponding testcase' % tc_id, output.termcolors.FAIL)
  292. #
  293. output.print_header("Adding Coverage information")
  294. output.print_action('Adding Coverage Information to %s' % testresults_filename(ut_folder, FN_DATA_COLLECTION))
  295. data_collection[jsonlog.MAIN_KEY_COVERAGE_INFO] = coverage_info(testresults_filename(ut_folder, FN_COVERAGE), os.path.dirname(config.lib_path))
  296. with open(testresults_filename(ut_folder, FN_DATA_COLLECTION), 'w') as fh:
  297. fh.write(json.dumps(data_collection, indent=4, sort_keys=True))
  298. #
  299. output.print_header("Creating LaTeX-Report of Unittest")
  300. with open(testresults_filename(ut_folder, FN_DATA_COLLECTION), 'r') as fh:
  301. data_collection = json.loads(fh.read())
  302. if jinja2 is None:
  303. output.print_action('You need to install jinja2 to create a LaTeX-Report!', output.termcolors.FAIL)
  304. else:
  305. fn = testresults_filename(ut_folder, FN_TEX_REPORT)
  306. output.print_action('Creating LaTeX-File %s' % fn)
  307. with open(fn, 'w') as fh:
  308. #
  309. template_path = os.path.join(os.path.dirname(__file__), 'templates')
  310. template_filename = 'unittest.tex'
  311. jenv = jinja2.Environment(loader=jinja2.FileSystemLoader(template_path))
  312. template = jenv.get_template(template_filename)
  313. fh.write(template.render(data=data_collection))
  314. def unittest_publish(ut_folder):
  315. config = imp.load_source('', jsonlog.get_ut_config(ut_folder))
  316. #
  317. output.print_header('Copy unittest files to library')
  318. target_folder = os.path.join(config.lib_path, '_testresults_')
  319. output.print_action('Copying Unittest Files to %s' % target_folder)
  320. if not os.path.exists(target_folder):
  321. output.print_info('Creating folder %s' % target_folder)
  322. fstools.mkdir(target_folder)
  323. else:
  324. for fn in os.listdir(target_folder):
  325. remove_file(os.path.join(target_folder, fn))
  326. for fn in REPORT_FILES:
  327. src = testresults_filename(ut_folder, fn)
  328. dst = os.path.join(target_folder, fn)
  329. output.print_info('copying %s -> %s' % (src, dst))
  330. shutil.copyfile(src, dst)
  331. def unittest_status(ut_folder):
  332. #
  333. # GIT STATUS
  334. #
  335. output.print_header('Checking GIT repository status')
  336. # GIT FETCH
  337. output.print_action('Fetching repository from server...')
  338. process = subprocess.Popen("LANGUAGE='en_US.UTF-8 git' git submodule foreach git fetch", cwd=ut_folder, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  339. stderroutput = process.communicate()[1]
  340. if stderroutput == b'':
  341. output.print_info(output.STATUS_SUCCESS)
  342. else:
  343. output.print_info(output.STATUS_FAILED)
  344. # GIT_REPO
  345. output.print_action('Analysing repository status...')
  346. output.print_info(jsonlog.status_git(ut_folder))
  347. # SUBMODULES
  348. output.print_action('Analysing submodule status...')
  349. process = subprocess.Popen("LANGUAGE='en_US.UTF-8 git' git submodule foreach git status", cwd=ut_folder, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  350. stdoutput, stderroutput = process.communicate()
  351. if stderroutput == b'':
  352. module = None
  353. data = {}
  354. for line in stdoutput.splitlines():
  355. line = str(line)
  356. if 'Entering' in line:
  357. m = line[line.index("'") + 1:]
  358. m = str(m[:m.index("'")])
  359. if m != module:
  360. data[m] = ''
  361. module = m
  362. else:
  363. data[m] += line
  364. for key in data:
  365. if "working tree clean" not in data[key] and "working directory clean" not in data[key]:
  366. data[key] = ("LOCAL CHANGES", output.termcolors.WARNING)
  367. elif "Your branch is behind" in data[key]:
  368. data[key] = ("OUTDATED (try git pull)", output.termcolors.WARNING)
  369. elif "HEAD detached at" in data[key]:
  370. data[key] = ("OUTDATED (try git checkout master)", output.termcolors.WARNING)
  371. elif "Your branch is ahead of" in data[key]:
  372. data[key] = ("CHANGED (try git push)", output.termcolors.WARNING)
  373. elif "nothing to commit" in data[key]:
  374. data[key] = ("CLEAN", output.termcolors.OKGREEN)
  375. else:
  376. data[key] = ("UNKNOWN", output.termcolors.FAIL)
  377. output.print_info('Submodule %s... %s' % (key, data[key][1] + data[key][0]))
  378. else:
  379. output.print_info(output.STATUS_FAILED)
  380. #
  381. # TESTRUN STATUS
  382. #
  383. output.print_header('Checking status of unittest in the library')
  384. for txt, fcn in (
  385. ('Checking release state... ', jsonlog.get_lib_release_state),
  386. ('Checking testcase integrity... ', jsonlog.get_lib_testcase_integrity),
  387. ('Checking source integrity... ', jsonlog.get_lib_src_integrity)
  388. ):
  389. output.print_action(txt)
  390. output.print_info(fcn(ut_folder))
  391. output.print_action('Checking code coverage... ')
  392. output.print_coverage(*jsonlog.lib_coverage(ut_folder))
  393. #
  394. output.print_header('Checking status of unittest for this testrun')
  395. for txt, fcn in (
  396. ('Checking release state... ', jsonlog.get_ut_release_state),
  397. ('Checking testcase integrity... ', jsonlog.get_ut_testcase_integrity),
  398. ('Checking source integrity... ', jsonlog.get_ut_src_integrity)
  399. ):
  400. output.print_action(txt)
  401. output.print_info(fcn(ut_folder))
  402. output.print_action('Checking code coverage... ')
  403. output.print_coverage(*jsonlog.ut_coverage(ut_folder))