resultutils.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. # resulttool - common library/utility functions
  2. #
  3. # Copyright (c) 2019, Intel Corporation.
  4. # Copyright (c) 2019, Linux Foundation
  5. #
  6. # This program is free software; you can redistribute it and/or modify it
  7. # under the terms and conditions of the GNU General Public License,
  8. # version 2, as published by the Free Software Foundation.
  9. #
  10. # This program is distributed in the hope it will be useful, but WITHOUT
  11. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  12. # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  13. # more details.
  14. #
  15. import os
  16. import json
  17. import scriptpath
  18. import copy
  19. import urllib.request
  20. import posixpath
  21. scriptpath.add_oe_lib_path()
  22. flatten_map = {
  23. "oeselftest": [],
  24. "runtime": [],
  25. "sdk": [],
  26. "sdkext": [],
  27. "manual": []
  28. }
  29. regression_map = {
  30. "oeselftest": ['TEST_TYPE', 'MACHINE'],
  31. "runtime": ['TESTSERIES', 'TEST_TYPE', 'IMAGE_BASENAME', 'MACHINE', 'IMAGE_PKGTYPE', 'DISTRO'],
  32. "sdk": ['TESTSERIES', 'TEST_TYPE', 'IMAGE_BASENAME', 'MACHINE', 'SDKMACHINE'],
  33. "sdkext": ['TESTSERIES', 'TEST_TYPE', 'IMAGE_BASENAME', 'MACHINE', 'SDKMACHINE'],
  34. "manual": ['TEST_TYPE', 'TEST_MODULE', 'IMAGE_BASENAME', 'MACHINE']
  35. }
  36. store_map = {
  37. "oeselftest": ['TEST_TYPE'],
  38. "runtime": ['TEST_TYPE', 'DISTRO', 'MACHINE', 'IMAGE_BASENAME'],
  39. "sdk": ['TEST_TYPE', 'MACHINE', 'SDKMACHINE', 'IMAGE_BASENAME'],
  40. "sdkext": ['TEST_TYPE', 'MACHINE', 'SDKMACHINE', 'IMAGE_BASENAME'],
  41. "manual": ['TEST_TYPE', 'TEST_MODULE', 'MACHINE', 'IMAGE_BASENAME']
  42. }
  43. def is_url(p):
  44. """
  45. Helper for determining if the given path is a URL
  46. """
  47. return p.startswith('http://') or p.startswith('https://')
  48. #
  49. # Load the json file and append the results data into the provided results dict
  50. #
  51. def append_resultsdata(results, f, configmap=store_map):
  52. if type(f) is str:
  53. if is_url(f):
  54. with urllib.request.urlopen(f) as response:
  55. data = json.loads(response.read().decode('utf-8'))
  56. url = urllib.parse.urlparse(f)
  57. testseries = posixpath.basename(posixpath.dirname(url.path))
  58. else:
  59. with open(f, "r") as filedata:
  60. data = json.load(filedata)
  61. testseries = os.path.basename(os.path.dirname(f))
  62. else:
  63. data = f
  64. for res in data:
  65. if "configuration" not in data[res] or "result" not in data[res]:
  66. raise ValueError("Test results data without configuration or result section?")
  67. if "TESTSERIES" not in data[res]["configuration"]:
  68. data[res]["configuration"]["TESTSERIES"] = testseries
  69. testtype = data[res]["configuration"].get("TEST_TYPE")
  70. if testtype not in configmap:
  71. raise ValueError("Unknown test type %s" % testtype)
  72. configvars = configmap[testtype]
  73. testpath = "/".join(data[res]["configuration"].get(i) for i in configmap[testtype])
  74. if testpath not in results:
  75. results[testpath] = {}
  76. results[testpath][res] = data[res]
  77. #
  78. # Walk a directory and find/load results data
  79. # or load directly from a file
  80. #
  81. def load_resultsdata(source, configmap=store_map):
  82. results = {}
  83. if is_url(source) or os.path.isfile(source):
  84. append_resultsdata(results, source, configmap)
  85. return results
  86. for root, dirs, files in os.walk(source):
  87. for name in files:
  88. f = os.path.join(root, name)
  89. if name == "testresults.json":
  90. append_resultsdata(results, f, configmap)
  91. return results
  92. def filter_resultsdata(results, resultid):
  93. newresults = {}
  94. for r in results:
  95. for i in results[r]:
  96. if i == resultsid:
  97. newresults[r] = {}
  98. newresults[r][i] = results[r][i]
  99. return newresults
  100. def strip_ptestresults(results):
  101. newresults = copy.deepcopy(results)
  102. #for a in newresults2:
  103. # newresults = newresults2[a]
  104. for res in newresults:
  105. if 'result' not in newresults[res]:
  106. continue
  107. if 'ptestresult.rawlogs' in newresults[res]['result']:
  108. del newresults[res]['result']['ptestresult.rawlogs']
  109. if 'ptestresult.sections' in newresults[res]['result']:
  110. for i in newresults[res]['result']['ptestresult.sections']:
  111. if 'log' in newresults[res]['result']['ptestresult.sections'][i]:
  112. del newresults[res]['result']['ptestresult.sections'][i]['log']
  113. return newresults
  114. def save_resultsdata(results, destdir, fn="testresults.json", ptestjson=False, ptestlogs=False):
  115. for res in results:
  116. if res:
  117. dst = destdir + "/" + res + "/" + fn
  118. else:
  119. dst = destdir + "/" + fn
  120. os.makedirs(os.path.dirname(dst), exist_ok=True)
  121. resultsout = results[res]
  122. if not ptestjson:
  123. resultsout = strip_ptestresults(results[res])
  124. with open(dst, 'w') as f:
  125. f.write(json.dumps(resultsout, sort_keys=True, indent=4))
  126. for res2 in results[res]:
  127. if ptestlogs and 'result' in results[res][res2]:
  128. if 'ptestresult.rawlogs' in results[res][res2]['result']:
  129. with open(dst.replace(fn, "ptest-raw.log"), "w+") as f:
  130. f.write(results[res][res2]['result']['ptestresult.rawlogs']['log'])
  131. if 'ptestresult.sections' in results[res][res2]['result']:
  132. for i in results[res][res2]['result']['ptestresult.sections']:
  133. if 'log' in results[res][res2]['result']['ptestresult.sections'][i]:
  134. with open(dst.replace(fn, "ptest-%s.log" % i), "w+") as f:
  135. f.write(results[res][res2]['result']['ptestresult.sections'][i]['log'])
  136. def git_get_result(repo, tags):
  137. git_objs = []
  138. for tag in tags:
  139. files = repo.run_cmd(['ls-tree', "--name-only", "-r", tag]).splitlines()
  140. git_objs.extend([tag + ':' + f for f in files if f.endswith("testresults.json")])
  141. def parse_json_stream(data):
  142. """Parse multiple concatenated JSON objects"""
  143. objs = []
  144. json_d = ""
  145. for line in data.splitlines():
  146. if line == '}{':
  147. json_d += '}'
  148. objs.append(json.loads(json_d))
  149. json_d = '{'
  150. else:
  151. json_d += line
  152. objs.append(json.loads(json_d))
  153. return objs
  154. # Optimize by reading all data with one git command
  155. results = {}
  156. for obj in parse_json_stream(repo.run_cmd(['show'] + git_objs + ['--'])):
  157. append_resultsdata(results, obj)
  158. return results
  159. def test_run_results(results):
  160. """
  161. Convenient generator function that iterates over all test runs that have a
  162. result section.
  163. Generates a tuple of:
  164. (result json file path, test run name, test run (dict), test run "results" (dict))
  165. for each test run that has a "result" section
  166. """
  167. for path in results:
  168. for run_name, test_run in results[path].items():
  169. if not 'result' in test_run:
  170. continue
  171. yield path, run_name, test_run, test_run['result']