resultutils.py 7.8 KB

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