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