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