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