result_writer.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. #!/usr/bin/env python
  2. # Copyright (c) 2018-2019 Intel Corporation
  3. #
  4. # This work is licensed under the terms of the MIT license.
  5. # For a copy, see <https://opensource.org/licenses/MIT>.
  6. """
  7. This module contains the result gatherer and write for CARLA scenarios.
  8. It shall be used from the ScenarioManager only.
  9. """
  10. from __future__ import print_function
  11. import time
  12. import json
  13. from tabulate import tabulate
  14. class ResultOutputProvider(object):
  15. """
  16. This module contains the _result gatherer and write for CARLA scenarios.
  17. It shall be used from the ScenarioManager only.
  18. """
  19. def __init__(self, data, result, stdout=True, filename=None, junitfile=None, jsonfile=None):
  20. """
  21. Setup all parameters
  22. - _data contains all scenario-related information
  23. - _result is overall pass/fail info
  24. - _stdout (True/False) is used to (de)activate terminal output
  25. - _filename is used to (de)activate file output in tabular form
  26. - _junit is used to (de)activate file output in junit form
  27. - _json is used to (de)activate file output in json form
  28. """
  29. self._data = data
  30. self._result = result
  31. self._stdout = stdout
  32. self._filename = filename
  33. self._junit = junitfile
  34. self._json = jsonfile
  35. self._start_time = time.strftime('%Y-%m-%d %H:%M:%S',
  36. time.localtime(self._data.start_system_time))
  37. self._end_time = time.strftime('%Y-%m-%d %H:%M:%S',
  38. time.localtime(self._data.end_system_time))
  39. def write(self):
  40. """
  41. Public write function
  42. """
  43. if self._junit is not None:
  44. self._write_to_junit()
  45. if self._json is not None:
  46. self._write_to_reportjson()
  47. output = self.create_output_text()
  48. if self._filename is not None:
  49. with open(self._filename, 'w', encoding='utf-8') as fd:
  50. fd.write(output)
  51. if self._stdout:
  52. print(output)
  53. def create_output_text(self):
  54. """
  55. Creates the output message
  56. """
  57. output = "\n"
  58. output += " ======= Results of Scenario: {} ---- {} =======\n".format(
  59. self._data.scenario_tree.name, self._result)
  60. end_line_length = len(output) - 3
  61. output += "\n"
  62. # Lis of all the actors
  63. output += " > Ego vehicles:\n"
  64. for ego_vehicle in self._data.ego_vehicles:
  65. output += "{}; ".format(ego_vehicle)
  66. output += "\n\n"
  67. output += " > Other actors:\n"
  68. for actor in self._data.other_actors:
  69. output += "{}; ".format(actor)
  70. output += "\n\n"
  71. # Simulation part
  72. output += " > Simulation Information\n"
  73. system_time = round(self._data.scenario_duration_system, 2)
  74. game_time = round(self._data.scenario_duration_game, 2)
  75. ratio = round(self._data.scenario_duration_game / self._data.scenario_duration_system, 3)
  76. list_statistics = [["Start Time", "{}".format(self._start_time)]]
  77. list_statistics.extend([["End Time", "{}".format(self._end_time)]])
  78. list_statistics.extend([["Duration (System Time)", "{}s".format(system_time)]])
  79. list_statistics.extend([["Duration (Game Time)", "{}s".format(game_time)]])
  80. list_statistics.extend([["Ratio (System Time / Game Time)", "{}s".format(ratio)]])
  81. output += tabulate(list_statistics, tablefmt='fancy_grid')
  82. output += "\n\n"
  83. # Criteria part
  84. output += " > Criteria Information\n"
  85. header = ['Actor', 'Criterion', 'Result', 'Actual Value', 'Expected Value']
  86. list_statistics = [header]
  87. for criterion in self._data.scenario.get_criteria():
  88. name_string = criterion.name
  89. if criterion.optional:
  90. name_string += " (Opt.)"
  91. else:
  92. name_string += " (Req.)"
  93. actor = "{} (id={})".format(criterion.actor.type_id[8:], criterion.actor.id)
  94. criteria = name_string
  95. result = "FAILURE" if criterion.test_status == "RUNNING" else criterion.test_status
  96. actual_value = criterion.actual_value
  97. expected_value = criterion.expected_value_success
  98. list_statistics.extend([[actor, criteria, result, actual_value, expected_value]])
  99. # Timeout
  100. actor = ""
  101. criteria = "Timeout (Req.)"
  102. result = "SUCCESS" if self._data.scenario_duration_game < self._data.scenario.timeout else "FAILURE"
  103. actual_value = round(self._data.scenario_duration_game, 2)
  104. expected_value = round(self._data.scenario.timeout, 2)
  105. list_statistics.extend([[actor, criteria, result, actual_value, expected_value]])
  106. # Global and final output message
  107. list_statistics.extend([['', 'GLOBAL RESULT', self._result, '', '']])
  108. output += tabulate(list_statistics, tablefmt='fancy_grid')
  109. output += "\n"
  110. output += " " + "=" * end_line_length + "\n"
  111. return output
  112. def _write_to_reportjson(self):
  113. """
  114. Write a machine-readable report to JSON
  115. The resulting report has the following format:
  116. {
  117. criteria: [
  118. {
  119. name: "CheckCollisions",
  120. expected: "0",
  121. actual: "2",
  122. optional: false,
  123. success: false
  124. }, ...
  125. ]
  126. }
  127. """
  128. json_list = []
  129. def result_dict(name, actor, optional, expected, actual, success):
  130. """
  131. Convenience function to convert its arguments into a JSON-ready dict
  132. :param name: Name of the test criterion
  133. :param actor: Actor ID as string
  134. :param optional: If the criterion is optional
  135. :param expected: The expected value of the criterion (eg 0 for collisions)
  136. :param actual: The actual value
  137. :param success: If the test was passed
  138. :return: A dict data structure that will be written to JSON
  139. """
  140. return {
  141. "name": name,
  142. "actor": actor,
  143. "optional": optional,
  144. "expected": expected,
  145. "actual": actual,
  146. "success": success,
  147. }
  148. for criterion in self._data.scenario.get_criteria():
  149. json_list.append(
  150. result_dict(
  151. criterion.name,
  152. "{}-{}".format(criterion.actor.type_id[8:], criterion.actor.id),
  153. criterion.optional,
  154. criterion.expected_value_success,
  155. criterion.actual_value,
  156. criterion.test_status in ["SUCCESS", "ACCEPTABLE"]
  157. )
  158. )
  159. # add one entry for duration
  160. timeout = self._data.scenario.timeout
  161. duration = self._data.scenario_duration_game
  162. json_list.append(
  163. result_dict(
  164. "Duration", "all", False, timeout, duration, duration <= timeout
  165. )
  166. )
  167. result_object = {
  168. "scenario": self._data.scenario_tree.name,
  169. "success": self._result in ["SUCCESS", "ACCEPTABLE"],
  170. "criteria": json_list
  171. }
  172. with open(self._json, "w", encoding='utf-8') as fp:
  173. json.dump(result_object, fp, indent=4)
  174. def _write_to_junit(self):
  175. """
  176. Writing to Junit XML
  177. """
  178. test_count = 0
  179. failure_count = 0
  180. for criterion in self._data.scenario.get_criteria():
  181. test_count += 1
  182. if criterion.test_status != "SUCCESS":
  183. failure_count += 1
  184. # handle timeout
  185. test_count += 1
  186. if self._data.scenario_duration_game >= self._data.scenario.timeout:
  187. failure_count += 1
  188. with open(self._junit, "w", encoding='utf-8') as junit_file:
  189. junit_file.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
  190. test_suites_string = ("<testsuites tests=\"%d\" failures=\"%d\" disabled=\"0\" "
  191. "errors=\"0\" timestamp=\"%s\" time=\"%5.2f\" "
  192. "name=\"Simulation\" package=\"Scenarios\">\n" %
  193. (test_count,
  194. failure_count,
  195. self._start_time,
  196. self._data.scenario_duration_system))
  197. junit_file.write(test_suites_string)
  198. test_suite_string = (" <testsuite name=\"%s\" tests=\"%d\" failures=\"%d\" "
  199. "disabled=\"0\" errors=\"0\" time=\"%5.2f\">\n" %
  200. (self._data.scenario_tree.name,
  201. test_count,
  202. failure_count,
  203. self._data.scenario_duration_system))
  204. junit_file.write(test_suite_string)
  205. for criterion in self._data.scenario.get_criteria():
  206. testcase_name = criterion.name + "_" + \
  207. criterion.actor.type_id[8:] + "_" + str(criterion.actor.id)
  208. result_string = (" <testcase name=\"{}\" status=\"run\" "
  209. "time=\"0\" classname=\"Scenarios.{}\">\n".format(
  210. testcase_name, self._data.scenario_tree.name))
  211. if criterion.test_status != "SUCCESS":
  212. result_string += " <failure message=\"{}\" type=\"\"><![CDATA[\n".format(
  213. criterion.name)
  214. result_string += " Actual: {}\n".format(
  215. criterion.actual_value)
  216. result_string += " Expected: {}\n".format(
  217. criterion.expected_value_success)
  218. result_string += "\n"
  219. result_string += " Exact Value: {} = {}]]></failure>\n".format(
  220. criterion.name, criterion.actual_value)
  221. else:
  222. result_string += " Exact Value: {} = {}\n".format(
  223. criterion.name, criterion.actual_value)
  224. result_string += " </testcase>\n"
  225. junit_file.write(result_string)
  226. # Handle timeout separately
  227. result_string = (" <testcase name=\"Duration\" status=\"run\" time=\"{}\" "
  228. "classname=\"Scenarios.{}\">\n".format(
  229. self._data.scenario_duration_system,
  230. self._data.scenario_tree.name))
  231. if self._data.scenario_duration_game >= self._data.scenario.timeout:
  232. result_string += " <failure message=\"{}\" type=\"\"><![CDATA[\n".format(
  233. "Duration")
  234. result_string += " Actual: {}\n".format(
  235. self._data.scenario_duration_game)
  236. result_string += " Expected: {}\n".format(
  237. self._data.scenario.timeout)
  238. result_string += "\n"
  239. result_string += " Exact Value: {} = {}]]></failure>\n".format(
  240. "Duration", self._data.scenario_duration_game)
  241. else:
  242. result_string += " Exact Value: {} = {}\n".format(
  243. "Duration", self._data.scenario_duration_game)
  244. result_string += " </testcase>\n"
  245. junit_file.write(result_string)
  246. junit_file.write(" </testsuite>\n")
  247. junit_file.write("</testsuites>\n")