blob: c5521d81bd1f84e7ba090f906aff2670b215bcfe [file] [log] [blame]
Brad Bishop40320b12019-03-26 16:08:25 -04001# resulttool - common library/utility functions
2#
3# Copyright (c) 2019, Intel Corporation.
4# Copyright (c) 2019, Linux Foundation
5#
Brad Bishopc342db32019-05-15 21:57:59 -04006# SPDX-License-Identifier: GPL-2.0-only
Brad Bishop40320b12019-03-26 16:08:25 -04007#
Brad Bishopc342db32019-05-15 21:57:59 -04008
Brad Bishop40320b12019-03-26 16:08:25 -04009import os
Brad Bishopa34c0302019-09-23 22:34:48 -040010import base64
11import zlib
Brad Bishop40320b12019-03-26 16:08:25 -040012import json
13import scriptpath
Brad Bishop19323692019-04-05 15:28:33 -040014import copy
Brad Bishopc342db32019-05-15 21:57:59 -040015import urllib.request
16import posixpath
Brad Bishop40320b12019-03-26 16:08:25 -040017scriptpath.add_oe_lib_path()
18
19flatten_map = {
20 "oeselftest": [],
21 "runtime": [],
22 "sdk": [],
23 "sdkext": [],
24 "manual": []
25}
26regression_map = {
27 "oeselftest": ['TEST_TYPE', 'MACHINE'],
28 "runtime": ['TESTSERIES', 'TEST_TYPE', 'IMAGE_BASENAME', 'MACHINE', 'IMAGE_PKGTYPE', 'DISTRO'],
29 "sdk": ['TESTSERIES', 'TEST_TYPE', 'IMAGE_BASENAME', 'MACHINE', 'SDKMACHINE'],
30 "sdkext": ['TESTSERIES', 'TEST_TYPE', 'IMAGE_BASENAME', 'MACHINE', 'SDKMACHINE'],
31 "manual": ['TEST_TYPE', 'TEST_MODULE', 'IMAGE_BASENAME', 'MACHINE']
32}
33store_map = {
34 "oeselftest": ['TEST_TYPE'],
35 "runtime": ['TEST_TYPE', 'DISTRO', 'MACHINE', 'IMAGE_BASENAME'],
36 "sdk": ['TEST_TYPE', 'MACHINE', 'SDKMACHINE', 'IMAGE_BASENAME'],
37 "sdkext": ['TEST_TYPE', 'MACHINE', 'SDKMACHINE', 'IMAGE_BASENAME'],
38 "manual": ['TEST_TYPE', 'TEST_MODULE', 'MACHINE', 'IMAGE_BASENAME']
39}
40
Brad Bishopc342db32019-05-15 21:57:59 -040041def is_url(p):
42 """
43 Helper for determining if the given path is a URL
44 """
45 return p.startswith('http://') or p.startswith('https://')
46
Brad Bishop15ae2502019-06-18 21:44:24 -040047extra_configvars = {'TESTSERIES': ''}
48
Brad Bishop40320b12019-03-26 16:08:25 -040049#
50# Load the json file and append the results data into the provided results dict
51#
Brad Bishop15ae2502019-06-18 21:44:24 -040052def append_resultsdata(results, f, configmap=store_map, configvars=extra_configvars):
Brad Bishop40320b12019-03-26 16:08:25 -040053 if type(f) is str:
Brad Bishopc342db32019-05-15 21:57:59 -040054 if is_url(f):
55 with urllib.request.urlopen(f) as response:
56 data = json.loads(response.read().decode('utf-8'))
57 url = urllib.parse.urlparse(f)
58 testseries = posixpath.basename(posixpath.dirname(url.path))
59 else:
60 with open(f, "r") as filedata:
Andrew Geissler8f840682023-07-21 09:09:43 -050061 try:
62 data = json.load(filedata)
63 except json.decoder.JSONDecodeError:
64 print("Cannot decode {}. Possible corruption. Skipping.".format(f))
65 data = ""
Brad Bishopc342db32019-05-15 21:57:59 -040066 testseries = os.path.basename(os.path.dirname(f))
Brad Bishop40320b12019-03-26 16:08:25 -040067 else:
68 data = f
69 for res in data:
70 if "configuration" not in data[res] or "result" not in data[res]:
71 raise ValueError("Test results data without configuration or result section?")
Brad Bishop15ae2502019-06-18 21:44:24 -040072 for config in configvars:
73 if config == "TESTSERIES" and "TESTSERIES" not in data[res]["configuration"]:
74 data[res]["configuration"]["TESTSERIES"] = testseries
75 continue
76 if config not in data[res]["configuration"]:
77 data[res]["configuration"][config] = configvars[config]
Brad Bishop40320b12019-03-26 16:08:25 -040078 testtype = data[res]["configuration"].get("TEST_TYPE")
79 if testtype not in configmap:
80 raise ValueError("Unknown test type %s" % testtype)
Brad Bishop40320b12019-03-26 16:08:25 -040081 testpath = "/".join(data[res]["configuration"].get(i) for i in configmap[testtype])
82 if testpath not in results:
83 results[testpath] = {}
Brad Bishop40320b12019-03-26 16:08:25 -040084 results[testpath][res] = data[res]
85
86#
87# Walk a directory and find/load results data
88# or load directly from a file
89#
Brad Bishop15ae2502019-06-18 21:44:24 -040090def load_resultsdata(source, configmap=store_map, configvars=extra_configvars):
Brad Bishop40320b12019-03-26 16:08:25 -040091 results = {}
Brad Bishopc342db32019-05-15 21:57:59 -040092 if is_url(source) or os.path.isfile(source):
Brad Bishop15ae2502019-06-18 21:44:24 -040093 append_resultsdata(results, source, configmap, configvars)
Brad Bishop40320b12019-03-26 16:08:25 -040094 return results
95 for root, dirs, files in os.walk(source):
96 for name in files:
97 f = os.path.join(root, name)
98 if name == "testresults.json":
Brad Bishop15ae2502019-06-18 21:44:24 -040099 append_resultsdata(results, f, configmap, configvars)
Brad Bishop40320b12019-03-26 16:08:25 -0400100 return results
101
102def filter_resultsdata(results, resultid):
103 newresults = {}
104 for r in results:
105 for i in results[r]:
106 if i == resultsid:
107 newresults[r] = {}
108 newresults[r][i] = results[r][i]
109 return newresults
110
Brad Bishop19323692019-04-05 15:28:33 -0400111def strip_ptestresults(results):
112 newresults = copy.deepcopy(results)
113 #for a in newresults2:
114 # newresults = newresults2[a]
115 for res in newresults:
116 if 'result' not in newresults[res]:
117 continue
118 if 'ptestresult.rawlogs' in newresults[res]['result']:
119 del newresults[res]['result']['ptestresult.rawlogs']
120 if 'ptestresult.sections' in newresults[res]['result']:
121 for i in newresults[res]['result']['ptestresult.sections']:
122 if 'log' in newresults[res]['result']['ptestresult.sections'][i]:
123 del newresults[res]['result']['ptestresult.sections'][i]['log']
124 return newresults
125
Brad Bishopa34c0302019-09-23 22:34:48 -0400126def decode_log(logdata):
127 if isinstance(logdata, str):
128 return logdata
129 elif isinstance(logdata, dict):
130 if "compressed" in logdata:
131 data = logdata.get("compressed")
132 data = base64.b64decode(data.encode("utf-8"))
Brad Bishop00e122a2019-10-05 11:10:57 -0400133 data = zlib.decompress(data)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500134 return data.decode("utf-8", errors='ignore')
Brad Bishopa34c0302019-09-23 22:34:48 -0400135 return None
136
Andrew Geissler1e34c2d2020-05-29 16:02:59 -0500137def generic_get_log(sectionname, results, section):
138 if sectionname not in results:
Brad Bishopa34c0302019-09-23 22:34:48 -0400139 return None
Andrew Geissler1e34c2d2020-05-29 16:02:59 -0500140 if section not in results[sectionname]:
Brad Bishopa34c0302019-09-23 22:34:48 -0400141 return None
142
Andrew Geissler1e34c2d2020-05-29 16:02:59 -0500143 ptest = results[sectionname][section]
Brad Bishopa34c0302019-09-23 22:34:48 -0400144 if 'log' not in ptest:
145 return None
146 return decode_log(ptest['log'])
147
Andrew Geissler1e34c2d2020-05-29 16:02:59 -0500148def ptestresult_get_log(results, section):
Andrew Geisslerfc113ea2023-03-31 09:59:46 -0500149 return generic_get_log('ptestresult.sections', results, section)
Andrew Geissler1e34c2d2020-05-29 16:02:59 -0500150
151def generic_get_rawlogs(sectname, results):
152 if sectname not in results:
153 return None
154 if 'log' not in results[sectname]:
155 return None
156 return decode_log(results[sectname]['log'])
157
Brad Bishopa34c0302019-09-23 22:34:48 -0400158def ptestresult_get_rawlogs(results):
Andrew Geissler1e34c2d2020-05-29 16:02:59 -0500159 return generic_get_rawlogs('ptestresult.rawlogs', results)
Brad Bishopa34c0302019-09-23 22:34:48 -0400160
Brad Bishop19323692019-04-05 15:28:33 -0400161def save_resultsdata(results, destdir, fn="testresults.json", ptestjson=False, ptestlogs=False):
Brad Bishop40320b12019-03-26 16:08:25 -0400162 for res in results:
163 if res:
164 dst = destdir + "/" + res + "/" + fn
165 else:
166 dst = destdir + "/" + fn
167 os.makedirs(os.path.dirname(dst), exist_ok=True)
Brad Bishop19323692019-04-05 15:28:33 -0400168 resultsout = results[res]
169 if not ptestjson:
170 resultsout = strip_ptestresults(results[res])
Brad Bishop40320b12019-03-26 16:08:25 -0400171 with open(dst, 'w') as f:
Brad Bishop19323692019-04-05 15:28:33 -0400172 f.write(json.dumps(resultsout, sort_keys=True, indent=4))
173 for res2 in results[res]:
174 if ptestlogs and 'result' in results[res][res2]:
Brad Bishopa34c0302019-09-23 22:34:48 -0400175 seriesresults = results[res][res2]['result']
176 rawlogs = ptestresult_get_rawlogs(seriesresults)
177 if rawlogs is not None:
Brad Bishop19323692019-04-05 15:28:33 -0400178 with open(dst.replace(fn, "ptest-raw.log"), "w+") as f:
Brad Bishopa34c0302019-09-23 22:34:48 -0400179 f.write(rawlogs)
180 if 'ptestresult.sections' in seriesresults:
181 for i in seriesresults['ptestresult.sections']:
182 sectionlog = ptestresult_get_log(seriesresults, i)
183 if sectionlog is not None:
Brad Bishop19323692019-04-05 15:28:33 -0400184 with open(dst.replace(fn, "ptest-%s.log" % i), "w+") as f:
Brad Bishopa34c0302019-09-23 22:34:48 -0400185 f.write(sectionlog)
Brad Bishop40320b12019-03-26 16:08:25 -0400186
Brad Bishop1d80a2e2019-11-15 16:35:03 -0500187def git_get_result(repo, tags, configmap=store_map):
Brad Bishop40320b12019-03-26 16:08:25 -0400188 git_objs = []
189 for tag in tags:
190 files = repo.run_cmd(['ls-tree', "--name-only", "-r", tag]).splitlines()
191 git_objs.extend([tag + ':' + f for f in files if f.endswith("testresults.json")])
192
193 def parse_json_stream(data):
194 """Parse multiple concatenated JSON objects"""
195 objs = []
196 json_d = ""
197 for line in data.splitlines():
198 if line == '}{':
199 json_d += '}'
200 objs.append(json.loads(json_d))
201 json_d = '{'
202 else:
203 json_d += line
204 objs.append(json.loads(json_d))
205 return objs
206
207 # Optimize by reading all data with one git command
208 results = {}
209 for obj in parse_json_stream(repo.run_cmd(['show'] + git_objs + ['--'])):
Brad Bishop1d80a2e2019-11-15 16:35:03 -0500210 append_resultsdata(results, obj, configmap=configmap)
Brad Bishop40320b12019-03-26 16:08:25 -0400211
212 return results
Brad Bishopc342db32019-05-15 21:57:59 -0400213
214def test_run_results(results):
215 """
216 Convenient generator function that iterates over all test runs that have a
217 result section.
218
219 Generates a tuple of:
220 (result json file path, test run name, test run (dict), test run "results" (dict))
221 for each test run that has a "result" section
222 """
223 for path in results:
224 for run_name, test_run in results[path].items():
225 if not 'result' in test_run:
226 continue
227 yield path, run_name, test_run, test_run['result']
228