multireport.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  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. summary = text_to_keyvalue(open(summary_file).read(), sep='=')
  163. if use_timestamps:
  164. test_timestamp = datetime.datetime.fromtimestamp(os.path.getmtime(summary_file))
  165. else:
  166. test_timestamp = datetime.datetime.strptime(summary['timestamp'], "%Y-%m-%d %H:%M:%S")
  167. result = TestResultSummary()
  168. result.timestamp = test_timestamp
  169. result.total = summary['total']
  170. result.successes = summary['successes']
  171. result.failures = summary['failures']
  172. result.errors = summary['errors']
  173. result.files_total = summary['files_total']
  174. result.files_successes = summary['files_successes']
  175. result.files_failures = summary['files_failures']
  176. result.svn_revision = str(summary['svn_revision'])
  177. result.tested_modules = summary['tested_modules']
  178. result.names = summary['names']
  179. result.test_files_authors = summary['test_files_authors']
  180. result.report = report
  181. # let's consider no location as valid state and use 'unknown'
  182. result.location = summary.get('location', 'unknown')
  183. result.location_type = summary.get('location_type', 'unknown')
  184. # grouping accoring to location types
  185. # this can cause that two actual locations tested at the same time
  186. # will end up together, this is not ideal but testing with
  187. # one location type and different actual locations is not standard
  188. # and although it will not break anything it will not give a nice
  189. # report
  190. results_in_locations[result.location_type].append(result)
  191. all_results.append(result)
  192. del result
  193. except KeyError as e:
  194. print 'File %s does not have right values (%s)' % (report, e.message)
  195. locations_main_page = open(os.path.join(output, 'index.html'), 'w')
  196. locations_main_page.write(
  197. '<html><body>'
  198. '<h1>Test reports accoring to locations</h1>'
  199. '<table>'
  200. '<thead><tr>'
  201. '<th>Location</th>'
  202. '<th>Successful files</th><th>Successful tests</th>'
  203. '</tr></thead>'
  204. '<tbody>'
  205. )
  206. for location_type, results in results_in_locations.iteritems():
  207. results = sorted(results, key=operator.attrgetter('timestamp'))
  208. # TODO: document: location type must be a valid dir name
  209. directory = os.path.join(output, location_type)
  210. ensure_dir(directory)
  211. if location_type == 'unknown':
  212. title = 'Test reports'
  213. else:
  214. title = 'Test reports for ' + location_type + ' location'
  215. x = [date2num(result.timestamp) for result in results]
  216. xlabels = [result.timestamp.strftime("%Y-%m-%d") + ' (r' + result.svn_revision + ')' for result in results]
  217. tests_plot(x=x, xlabels=xlabels, results=results,
  218. filename=os.path.join(directory, 'tests_plot.png'))
  219. files_plot(x=x, xlabels=xlabels, results=results,
  220. filename=os.path.join(directory, 'files_plot.png'))
  221. info_plot(x=x, xlabels=xlabels, results=results,
  222. filename=os.path.join(directory, 'info_plot.png'))
  223. main_page(results=results, filename='index.html',
  224. images=['tests_plot.png', 'files_plot.png', 'info_plot.png'],
  225. captions=['Success of individual tests', 'Success of test files',
  226. 'Additional information'],
  227. directory=directory,
  228. title=title)
  229. files_successes = sum(result.files_successes for result in results)
  230. files_total = sum(result.files_total for result in results)
  231. successes = sum(result.successes for result in results)
  232. total = sum(result.total for result in results)
  233. per_test = success_to_html_percent(
  234. total=total, successes=successes)
  235. per_file = success_to_html_percent(
  236. total=files_total, successes=files_successes)
  237. locations_main_page.write(
  238. '<tr>'
  239. '<td><a href={location}/index.html>{location}</a></td>'
  240. '<td>{pfiles}</td><td>{ptests}</td>'
  241. '</tr>'
  242. .format(location=location_type,
  243. pfiles=per_file, ptests=per_test))
  244. locations_main_page.write('</tbody></table>')
  245. locations_main_page.write('</body></html>')
  246. locations_main_page.close()
  247. return 0
  248. if __name__ == '__main__':
  249. sys.exit(main())