multireport.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. # -*- coding: utf-8 -*-
  2. import sys
  3. import os
  4. import argparse
  5. import itertools
  6. import datetime
  7. import operator
  8. from collections import defaultdict
  9. # TODO: we should be able to work without matplotlib
  10. import matplotlib
  11. matplotlib.use('Agg')
  12. import matplotlib.pyplot as plt
  13. from matplotlib.dates import date2num
  14. from grass.gunittest.checkers import text_to_keyvalue
  15. from grass.gunittest.utils import ensure_dir
  16. from grass.gunittest.reporters import success_to_html_percent
  17. class TestResultSummary(object):
  18. def __init__(self):
  19. self.timestamp = None
  20. self.svn_revision = None
  21. self.location = None
  22. self.location_type = None
  23. self.total = None
  24. self.successes = None
  25. self.failures = None
  26. self.errors = None
  27. self.skipped = []
  28. self.expected_failures = []
  29. self.unexpected_successes = []
  30. self.files_total = None
  31. self.files_successes = None
  32. self.files_failures = None
  33. self.tested_modules = []
  34. self.tested_dirs = []
  35. self.test_files_authors = []
  36. self.time = []
  37. self.names = []
  38. self.report = None
  39. def tests_plot(x, xlabels, results, filename):
  40. total = [result.total for result in results]
  41. successes = [result.successes for result in results]
  42. # TODO: document: counting errors and failures together
  43. failures = [result.failures + result.errors for result in results]
  44. fig = plt.figure()
  45. graph = fig.add_subplot(111)
  46. # Plot the data as a red line with round markers
  47. graph.plot(x, total, 'b-o')
  48. graph.plot(x, successes, 'g-o')
  49. graph.plot(x, failures, 'r-o')
  50. fig.autofmt_xdate()
  51. # Set the xtick locations to correspond to just the dates you entered.
  52. graph.set_xticks(x)
  53. # Set the xtick labels to correspond to just the dates you entered.
  54. graph.set_xticklabels(xlabels)
  55. fig.savefig(filename)
  56. def files_plot(x, xlabels, results, filename):
  57. total = [result.files_total for result in results]
  58. successes = [result.files_successes for result in results]
  59. failures = [result.files_failures for result in results]
  60. fig = plt.figure()
  61. graph = fig.add_subplot(111)
  62. # Plot the data as a red line with round markers
  63. graph.plot(x, total, 'b-o')
  64. graph.plot(x, successes, 'g-o')
  65. graph.plot(x, failures, 'r-o')
  66. #fig.autofmt_xdate(bottom=0.2, rotation=30, ha='left')
  67. fig.autofmt_xdate()
  68. # Set the xtick locations to correspond to just the dates you entered.
  69. graph.set_xticks(x)
  70. # Set the xtick labels to correspond to just the dates you entered.
  71. graph.set_xticklabels(xlabels)
  72. fig.savefig(filename)
  73. def info_plot(x, xlabels, results, filename):
  74. modules = [len(result.tested_modules) for result in results]
  75. names = [len(result.names) for result in results]
  76. authors = [len(result.test_files_authors) for result in results]
  77. fig = plt.figure()
  78. graph = fig.add_subplot(111)
  79. # Plot the data as a red line with round markers
  80. graph.plot(x, names, 'b-o', label="Test files")
  81. graph.plot(x, modules, 'g-o', label="Tested modules")
  82. graph.plot(x, authors, 'r-o', label="Test authors")
  83. fig.autofmt_xdate()
  84. # Now add the legend with some customizations.
  85. graph.legend(loc='upper center', shadow=True)
  86. # Set the xtick locations to correspond to just the dates you entered.
  87. graph.set_xticks(x)
  88. # Set the xtick labels to correspond to just the dates you entered.
  89. graph.set_xticklabels(xlabels)
  90. fig.savefig(filename)
  91. # TODO: solve the directory inconsitencies, implemement None
  92. def main_page(results, filename, images, captions, title='Test reports',
  93. directory=None):
  94. filename = os.path.join(directory, filename)
  95. with open(filename, 'w') as page:
  96. page.write(
  97. '<html><body>'
  98. '<h1>{title}</h1>'
  99. '<table>'
  100. '<thead><tr>'
  101. '<th>Date (timestamp)</th><th>SVN revision</th><th>Name</th>'
  102. '<th>Successful files</th><th>Successful tests</th>'
  103. '</tr></thead>'
  104. '<tbody>'
  105. .format(title=title)
  106. )
  107. for result in results:
  108. # TODO: include name to summary file
  109. # now using location or test report directory as name
  110. if result.location != 'unknown':
  111. name = result.location
  112. else:
  113. name = os.path.basename(result.report)
  114. if not name:
  115. # Python basename returns '' for 'abc/'
  116. for d in reversed(os.path.split(result.report)):
  117. if d:
  118. name = d
  119. break
  120. per_test = success_to_html_percent(
  121. total=result.total, successes=result.successes)
  122. per_file = success_to_html_percent(
  123. total=result.files_total, successes=result.files_successes)
  124. report_path = os.path.relpath(path=result.report, start=directory)
  125. page.write(
  126. '<tr>'
  127. '<td><a href={report_path}/index.html>{result.timestamp}</a></td>'
  128. '<td>{result.svn_revision}</td>'
  129. '<td><a href={report_path}/index.html>{name}</a></td>'
  130. '<td>{pfiles}</td><td>{ptests}</td>'
  131. '</tr>'
  132. .format(result=result, name=name, report_path=report_path,
  133. pfiles=per_file, ptests=per_test))
  134. page.write('</tbody></table>')
  135. for image, caption in itertools.izip(images, captions):
  136. page.write(
  137. '<h3>{caption}<h3>'
  138. '<img src="{image}" alt="{caption}" title="{caption}">'
  139. .format(image=image, caption=caption))
  140. page.write('</body></html>')
  141. def main():
  142. parser = argparse.ArgumentParser(
  143. description='Create overall report from several individual test reports')
  144. parser.add_argument('reports', metavar='report_directory',
  145. type=str, nargs='+',
  146. help='Directories with reports')
  147. parser.add_argument('--output', dest='output', action='store',
  148. default='testreports_summary',
  149. help='Output directory')
  150. parser.add_argument('--timestamps', dest='timestamps', action='store_true',
  151. help='Use file timestamp instead of date in test summary')
  152. args = parser.parse_args()
  153. output = args.output
  154. reports = args.reports
  155. use_timestamps = args.timestamps
  156. ensure_dir(output)
  157. all_results = []
  158. results_in_locations = defaultdict(list)
  159. for report in reports:
  160. try:
  161. summary_file = os.path.join(report, 'test_keyvalue_result.txt')
  162. if not os.path.exists(summary_file):
  163. sys.stderr.write('WARNING: Key-value summary not available in'
  164. ' report <%s>, skipping.\n')
  165. # skipping incomplete reports
  166. # use only results list for further processing
  167. continue
  168. summary = text_to_keyvalue(open(summary_file).read(), sep='=')
  169. if use_timestamps:
  170. test_timestamp = datetime.datetime.fromtimestamp(os.path.getmtime(summary_file))
  171. else:
  172. test_timestamp = datetime.datetime.strptime(summary['timestamp'], "%Y-%m-%d %H:%M:%S")
  173. result = TestResultSummary()
  174. result.timestamp = test_timestamp
  175. result.total = summary['total']
  176. result.successes = summary['successes']
  177. result.failures = summary['failures']
  178. result.errors = summary['errors']
  179. result.files_total = summary['files_total']
  180. result.files_successes = summary['files_successes']
  181. result.files_failures = summary['files_failures']
  182. result.svn_revision = str(summary['svn_revision'])
  183. result.tested_modules = summary['tested_modules']
  184. result.names = summary['names']
  185. result.test_files_authors = summary['test_files_authors']
  186. result.report = report
  187. # let's consider no location as valid state and use 'unknown'
  188. result.location = summary.get('location', 'unknown')
  189. result.location_type = summary.get('location_type', 'unknown')
  190. # grouping according to location types
  191. # this can cause that two actual locations tested at the same time
  192. # will end up together, this is not ideal but testing with
  193. # one location type and different actual locations is not standard
  194. # and although it will not break anything it will not give a nice
  195. # report
  196. results_in_locations[result.location_type].append(result)
  197. all_results.append(result)
  198. del result
  199. except KeyError as e:
  200. print 'File %s does not have right values (%s)' % (report, e.message)
  201. locations_main_page = open(os.path.join(output, 'index.html'), 'w')
  202. locations_main_page.write(
  203. '<html><body>'
  204. '<h1>Test reports grouped by location type</h1>'
  205. '<table>'
  206. '<thead><tr>'
  207. '<th>Location</th>'
  208. '<th>Successful files</th><th>Successful tests</th>'
  209. '</tr></thead>'
  210. '<tbody>'
  211. )
  212. for location_type, results in results_in_locations.iteritems():
  213. results = sorted(results, key=operator.attrgetter('timestamp'))
  214. # TODO: document: location type must be a valid dir name
  215. directory = os.path.join(output, location_type)
  216. ensure_dir(directory)
  217. if location_type == 'unknown':
  218. title = 'Test reports'
  219. else:
  220. title = ('Test reports for &lt;{type}&gt; location type'
  221. .format(type=location_type))
  222. x = [date2num(result.timestamp) for result in results]
  223. xlabels = [result.timestamp.strftime("%Y-%m-%d") + ' (r' + result.svn_revision + ')' for result in results]
  224. tests_plot(x=x, xlabels=xlabels, results=results,
  225. filename=os.path.join(directory, 'tests_plot.png'))
  226. files_plot(x=x, xlabels=xlabels, results=results,
  227. filename=os.path.join(directory, 'files_plot.png'))
  228. info_plot(x=x, xlabels=xlabels, results=results,
  229. filename=os.path.join(directory, 'info_plot.png'))
  230. main_page(results=results, filename='index.html',
  231. images=['tests_plot.png', 'files_plot.png', 'info_plot.png'],
  232. captions=['Success of individual tests', 'Success of test files',
  233. 'Additional information'],
  234. directory=directory,
  235. title=title)
  236. files_successes = sum(result.files_successes for result in results)
  237. files_total = sum(result.files_total for result in results)
  238. successes = sum(result.successes for result in results)
  239. total = sum(result.total for result in results)
  240. per_test = success_to_html_percent(
  241. total=total, successes=successes)
  242. per_file = success_to_html_percent(
  243. total=files_total, successes=files_successes)
  244. locations_main_page.write(
  245. '<tr>'
  246. '<td><a href={location}/index.html>{location}</a></td>'
  247. '<td>{pfiles}</td><td>{ptests}</td>'
  248. '</tr>'
  249. .format(location=location_type,
  250. pfiles=per_file, ptests=per_test))
  251. locations_main_page.write('</tbody></table>')
  252. locations_main_page.write('</body></html>')
  253. locations_main_page.close()
  254. return 0
  255. if __name__ == '__main__':
  256. sys.exit(main())