xref: /OK3568_Linux_fs/yocto/poky/scripts/lib/resulttool/report.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun# test result tool - report text based test results
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 glob
11*4882a593Smuzhiyunimport json
12*4882a593Smuzhiyunimport resulttool.resultutils as resultutils
13*4882a593Smuzhiyunfrom oeqa.utils.git import GitRepo
14*4882a593Smuzhiyunimport oeqa.utils.gitarchive as gitarchive
15*4882a593Smuzhiyun
16*4882a593Smuzhiyun
17*4882a593Smuzhiyunclass ResultsTextReport(object):
18*4882a593Smuzhiyun    def __init__(self):
19*4882a593Smuzhiyun        self.ptests = {}
20*4882a593Smuzhiyun        self.ltptests = {}
21*4882a593Smuzhiyun        self.ltpposixtests = {}
22*4882a593Smuzhiyun        self.result_types = {'passed': ['PASSED', 'passed', 'PASS', 'XFAIL'],
23*4882a593Smuzhiyun                             'failed': ['FAILED', 'failed', 'FAIL', 'ERROR', 'error', 'UNKNOWN', 'XPASS'],
24*4882a593Smuzhiyun                             'skipped': ['SKIPPED', 'skipped', 'UNSUPPORTED', 'UNTESTED', 'UNRESOLVED']}
25*4882a593Smuzhiyun
26*4882a593Smuzhiyun
27*4882a593Smuzhiyun    def handle_ptest_result(self, k, status, result, machine):
28*4882a593Smuzhiyun        if machine not in self.ptests:
29*4882a593Smuzhiyun            self.ptests[machine] = {}
30*4882a593Smuzhiyun
31*4882a593Smuzhiyun        if k == 'ptestresult.sections':
32*4882a593Smuzhiyun            # Ensure tests without any test results still show up on the report
33*4882a593Smuzhiyun            for suite in result['ptestresult.sections']:
34*4882a593Smuzhiyun                if suite not in self.ptests[machine]:
35*4882a593Smuzhiyun                    self.ptests[machine][suite] = {
36*4882a593Smuzhiyun                            'passed': 0, 'failed': 0, 'skipped': 0, 'duration' : '-',
37*4882a593Smuzhiyun                            'failed_testcases': [], "testcases": set(),
38*4882a593Smuzhiyun                            }
39*4882a593Smuzhiyun                if 'duration' in result['ptestresult.sections'][suite]:
40*4882a593Smuzhiyun                    self.ptests[machine][suite]['duration'] = result['ptestresult.sections'][suite]['duration']
41*4882a593Smuzhiyun                if 'timeout' in result['ptestresult.sections'][suite]:
42*4882a593Smuzhiyun                    self.ptests[machine][suite]['duration'] += " T"
43*4882a593Smuzhiyun            return True
44*4882a593Smuzhiyun
45*4882a593Smuzhiyun        # process test result
46*4882a593Smuzhiyun        try:
47*4882a593Smuzhiyun            _, suite, test = k.split(".", 2)
48*4882a593Smuzhiyun        except ValueError:
49*4882a593Smuzhiyun            return True
50*4882a593Smuzhiyun
51*4882a593Smuzhiyun        # Handle 'glib-2.0'
52*4882a593Smuzhiyun        if 'ptestresult.sections' in result and suite not in result['ptestresult.sections']:
53*4882a593Smuzhiyun            try:
54*4882a593Smuzhiyun                _, suite, suite1, test = k.split(".", 3)
55*4882a593Smuzhiyun                if suite + "." + suite1 in result['ptestresult.sections']:
56*4882a593Smuzhiyun                    suite = suite + "." + suite1
57*4882a593Smuzhiyun            except ValueError:
58*4882a593Smuzhiyun                pass
59*4882a593Smuzhiyun
60*4882a593Smuzhiyun        if suite not in self.ptests[machine]:
61*4882a593Smuzhiyun            self.ptests[machine][suite] = {
62*4882a593Smuzhiyun                    'passed': 0, 'failed': 0, 'skipped': 0, 'duration' : '-',
63*4882a593Smuzhiyun                    'failed_testcases': [], "testcases": set(),
64*4882a593Smuzhiyun                    }
65*4882a593Smuzhiyun
66*4882a593Smuzhiyun        # do not process duplicate results
67*4882a593Smuzhiyun        if test in self.ptests[machine][suite]["testcases"]:
68*4882a593Smuzhiyun            print("Warning duplicate ptest result '{}.{}' for {}".format(suite, test, machine))
69*4882a593Smuzhiyun            return False
70*4882a593Smuzhiyun
71*4882a593Smuzhiyun        for tk in self.result_types:
72*4882a593Smuzhiyun            if status in self.result_types[tk]:
73*4882a593Smuzhiyun                self.ptests[machine][suite][tk] += 1
74*4882a593Smuzhiyun        self.ptests[machine][suite]["testcases"].add(test)
75*4882a593Smuzhiyun        return True
76*4882a593Smuzhiyun
77*4882a593Smuzhiyun    def handle_ltptest_result(self, k, status, result, machine):
78*4882a593Smuzhiyun        if machine not in self.ltptests:
79*4882a593Smuzhiyun            self.ltptests[machine] = {}
80*4882a593Smuzhiyun
81*4882a593Smuzhiyun        if k == 'ltpresult.sections':
82*4882a593Smuzhiyun            # Ensure tests without any test results still show up on the report
83*4882a593Smuzhiyun            for suite in result['ltpresult.sections']:
84*4882a593Smuzhiyun                if suite not in self.ltptests[machine]:
85*4882a593Smuzhiyun                    self.ltptests[machine][suite] = {'passed': 0, 'failed': 0, 'skipped': 0, 'duration' : '-', 'failed_testcases': []}
86*4882a593Smuzhiyun                if 'duration' in result['ltpresult.sections'][suite]:
87*4882a593Smuzhiyun                    self.ltptests[machine][suite]['duration'] = result['ltpresult.sections'][suite]['duration']
88*4882a593Smuzhiyun                if 'timeout' in result['ltpresult.sections'][suite]:
89*4882a593Smuzhiyun                    self.ltptests[machine][suite]['duration'] += " T"
90*4882a593Smuzhiyun            return
91*4882a593Smuzhiyun        try:
92*4882a593Smuzhiyun            _, suite, test = k.split(".", 2)
93*4882a593Smuzhiyun        except ValueError:
94*4882a593Smuzhiyun            return
95*4882a593Smuzhiyun        # Handle 'glib-2.0'
96*4882a593Smuzhiyun        if 'ltpresult.sections' in result and suite not in result['ltpresult.sections']:
97*4882a593Smuzhiyun            try:
98*4882a593Smuzhiyun                _, suite, suite1, test = k.split(".", 3)
99*4882a593Smuzhiyun                if suite + "." + suite1 in result['ltpresult.sections']:
100*4882a593Smuzhiyun                    suite = suite + "." + suite1
101*4882a593Smuzhiyun            except ValueError:
102*4882a593Smuzhiyun                pass
103*4882a593Smuzhiyun        if suite not in self.ltptests[machine]:
104*4882a593Smuzhiyun            self.ltptests[machine][suite] = {'passed': 0, 'failed': 0, 'skipped': 0, 'duration' : '-', 'failed_testcases': []}
105*4882a593Smuzhiyun        for tk in self.result_types:
106*4882a593Smuzhiyun            if status in self.result_types[tk]:
107*4882a593Smuzhiyun                self.ltptests[machine][suite][tk] += 1
108*4882a593Smuzhiyun
109*4882a593Smuzhiyun    def handle_ltpposixtest_result(self, k, status, result, machine):
110*4882a593Smuzhiyun        if machine not in self.ltpposixtests:
111*4882a593Smuzhiyun            self.ltpposixtests[machine] = {}
112*4882a593Smuzhiyun
113*4882a593Smuzhiyun        if k == 'ltpposixresult.sections':
114*4882a593Smuzhiyun            # Ensure tests without any test results still show up on the report
115*4882a593Smuzhiyun            for suite in result['ltpposixresult.sections']:
116*4882a593Smuzhiyun                if suite not in self.ltpposixtests[machine]:
117*4882a593Smuzhiyun                    self.ltpposixtests[machine][suite] = {'passed': 0, 'failed': 0, 'skipped': 0, 'duration' : '-', 'failed_testcases': []}
118*4882a593Smuzhiyun                if 'duration' in result['ltpposixresult.sections'][suite]:
119*4882a593Smuzhiyun                    self.ltpposixtests[machine][suite]['duration'] = result['ltpposixresult.sections'][suite]['duration']
120*4882a593Smuzhiyun            return
121*4882a593Smuzhiyun        try:
122*4882a593Smuzhiyun            _, suite, test = k.split(".", 2)
123*4882a593Smuzhiyun        except ValueError:
124*4882a593Smuzhiyun            return
125*4882a593Smuzhiyun        # Handle 'glib-2.0'
126*4882a593Smuzhiyun        if 'ltpposixresult.sections' in result and suite not in result['ltpposixresult.sections']:
127*4882a593Smuzhiyun            try:
128*4882a593Smuzhiyun                _, suite, suite1, test = k.split(".", 3)
129*4882a593Smuzhiyun                if suite + "." + suite1 in result['ltpposixresult.sections']:
130*4882a593Smuzhiyun                    suite = suite + "." + suite1
131*4882a593Smuzhiyun            except ValueError:
132*4882a593Smuzhiyun                pass
133*4882a593Smuzhiyun        if suite not in self.ltpposixtests[machine]:
134*4882a593Smuzhiyun            self.ltpposixtests[machine][suite] = {'passed': 0, 'failed': 0, 'skipped': 0, 'duration' : '-', 'failed_testcases': []}
135*4882a593Smuzhiyun        for tk in self.result_types:
136*4882a593Smuzhiyun            if status in self.result_types[tk]:
137*4882a593Smuzhiyun                self.ltpposixtests[machine][suite][tk] += 1
138*4882a593Smuzhiyun
139*4882a593Smuzhiyun    def get_aggregated_test_result(self, logger, testresult, machine):
140*4882a593Smuzhiyun        test_count_report = {'passed': 0, 'failed': 0, 'skipped': 0, 'failed_testcases': []}
141*4882a593Smuzhiyun        result = testresult.get('result', [])
142*4882a593Smuzhiyun        for k in result:
143*4882a593Smuzhiyun            test_status = result[k].get('status', [])
144*4882a593Smuzhiyun            if k.startswith("ptestresult."):
145*4882a593Smuzhiyun                if not self.handle_ptest_result(k, test_status, result, machine):
146*4882a593Smuzhiyun                    continue
147*4882a593Smuzhiyun            elif k.startswith("ltpresult."):
148*4882a593Smuzhiyun                self.handle_ltptest_result(k, test_status, result, machine)
149*4882a593Smuzhiyun            elif k.startswith("ltpposixresult."):
150*4882a593Smuzhiyun                self.handle_ltpposixtest_result(k, test_status, result, machine)
151*4882a593Smuzhiyun
152*4882a593Smuzhiyun            # process result if it was not skipped by a handler
153*4882a593Smuzhiyun            for tk in self.result_types:
154*4882a593Smuzhiyun                if test_status in self.result_types[tk]:
155*4882a593Smuzhiyun                    test_count_report[tk] += 1
156*4882a593Smuzhiyun            if test_status in self.result_types['failed']:
157*4882a593Smuzhiyun                test_count_report['failed_testcases'].append(k)
158*4882a593Smuzhiyun        return test_count_report
159*4882a593Smuzhiyun
160*4882a593Smuzhiyun    def print_test_report(self, template_file_name, test_count_reports):
161*4882a593Smuzhiyun        from jinja2 import Environment, FileSystemLoader
162*4882a593Smuzhiyun        script_path = os.path.dirname(os.path.realpath(__file__))
163*4882a593Smuzhiyun        file_loader = FileSystemLoader(script_path + '/template')
164*4882a593Smuzhiyun        env = Environment(loader=file_loader, trim_blocks=True)
165*4882a593Smuzhiyun        template = env.get_template(template_file_name)
166*4882a593Smuzhiyun        havefailed = False
167*4882a593Smuzhiyun        reportvalues = []
168*4882a593Smuzhiyun        machines = []
169*4882a593Smuzhiyun        cols = ['passed', 'failed', 'skipped']
170*4882a593Smuzhiyun        maxlen = {'passed' : 0, 'failed' : 0, 'skipped' : 0, 'result_id': 0, 'testseries' : 0, 'ptest' : 0 ,'ltptest': 0, 'ltpposixtest': 0}
171*4882a593Smuzhiyun        for line in test_count_reports:
172*4882a593Smuzhiyun            total_tested = line['passed'] + line['failed'] + line['skipped']
173*4882a593Smuzhiyun            vals = {}
174*4882a593Smuzhiyun            vals['result_id'] = line['result_id']
175*4882a593Smuzhiyun            vals['testseries'] = line['testseries']
176*4882a593Smuzhiyun            vals['sort'] = line['testseries'] + "_" + line['result_id']
177*4882a593Smuzhiyun            vals['failed_testcases'] = line['failed_testcases']
178*4882a593Smuzhiyun            for k in cols:
179*4882a593Smuzhiyun                vals[k] = "%d (%s%%)" % (line[k], format(line[k] / total_tested * 100, '.0f'))
180*4882a593Smuzhiyun            for k in maxlen:
181*4882a593Smuzhiyun                if k in vals and len(vals[k]) > maxlen[k]:
182*4882a593Smuzhiyun                    maxlen[k] = len(vals[k])
183*4882a593Smuzhiyun            reportvalues.append(vals)
184*4882a593Smuzhiyun            if line['failed_testcases']:
185*4882a593Smuzhiyun                havefailed = True
186*4882a593Smuzhiyun            if line['machine'] not in machines:
187*4882a593Smuzhiyun                machines.append(line['machine'])
188*4882a593Smuzhiyun        reporttotalvalues = {}
189*4882a593Smuzhiyun        for k in cols:
190*4882a593Smuzhiyun            reporttotalvalues[k] = '%s' % sum([line[k] for line in test_count_reports])
191*4882a593Smuzhiyun        reporttotalvalues['count'] = '%s' % len(test_count_reports)
192*4882a593Smuzhiyun        for (machine, report) in self.ptests.items():
193*4882a593Smuzhiyun            for ptest in self.ptests[machine]:
194*4882a593Smuzhiyun                if len(ptest) > maxlen['ptest']:
195*4882a593Smuzhiyun                    maxlen['ptest'] = len(ptest)
196*4882a593Smuzhiyun        for (machine, report) in self.ltptests.items():
197*4882a593Smuzhiyun            for ltptest in self.ltptests[machine]:
198*4882a593Smuzhiyun                if len(ltptest) > maxlen['ltptest']:
199*4882a593Smuzhiyun                    maxlen['ltptest'] = len(ltptest)
200*4882a593Smuzhiyun        for (machine, report) in self.ltpposixtests.items():
201*4882a593Smuzhiyun            for ltpposixtest in self.ltpposixtests[machine]:
202*4882a593Smuzhiyun                if len(ltpposixtest) > maxlen['ltpposixtest']:
203*4882a593Smuzhiyun                    maxlen['ltpposixtest'] = len(ltpposixtest)
204*4882a593Smuzhiyun        output = template.render(reportvalues=reportvalues,
205*4882a593Smuzhiyun                                 reporttotalvalues=reporttotalvalues,
206*4882a593Smuzhiyun                                 havefailed=havefailed,
207*4882a593Smuzhiyun                                 machines=machines,
208*4882a593Smuzhiyun                                 ptests=self.ptests,
209*4882a593Smuzhiyun                                 ltptests=self.ltptests,
210*4882a593Smuzhiyun                                 ltpposixtests=self.ltpposixtests,
211*4882a593Smuzhiyun                                 maxlen=maxlen)
212*4882a593Smuzhiyun        print(output)
213*4882a593Smuzhiyun
214*4882a593Smuzhiyun    def view_test_report(self, logger, source_dir, branch, commit, tag, use_regression_map, raw_test, selected_test_case_only):
215*4882a593Smuzhiyun        def print_selected_testcase_result(testresults, selected_test_case_only):
216*4882a593Smuzhiyun            for testsuite in testresults:
217*4882a593Smuzhiyun                for resultid in testresults[testsuite]:
218*4882a593Smuzhiyun                    result = testresults[testsuite][resultid]['result']
219*4882a593Smuzhiyun                    test_case_result = result.get(selected_test_case_only, {})
220*4882a593Smuzhiyun                    if test_case_result.get('status'):
221*4882a593Smuzhiyun                        print('Found selected test case result for %s from %s' % (selected_test_case_only,
222*4882a593Smuzhiyun                                                                                           resultid))
223*4882a593Smuzhiyun                        print(test_case_result['status'])
224*4882a593Smuzhiyun                    else:
225*4882a593Smuzhiyun                        print('Could not find selected test case result for %s from %s' % (selected_test_case_only,
226*4882a593Smuzhiyun                                                                                           resultid))
227*4882a593Smuzhiyun                    if test_case_result.get('log'):
228*4882a593Smuzhiyun                        print(test_case_result['log'])
229*4882a593Smuzhiyun        test_count_reports = []
230*4882a593Smuzhiyun        configmap = resultutils.store_map
231*4882a593Smuzhiyun        if use_regression_map:
232*4882a593Smuzhiyun            configmap = resultutils.regression_map
233*4882a593Smuzhiyun        if commit:
234*4882a593Smuzhiyun            if tag:
235*4882a593Smuzhiyun                logger.warning("Ignoring --tag as --commit was specified")
236*4882a593Smuzhiyun            tag_name = "{branch}/{commit_number}-g{commit}/{tag_number}"
237*4882a593Smuzhiyun            repo = GitRepo(source_dir)
238*4882a593Smuzhiyun            revs = gitarchive.get_test_revs(logger, repo, tag_name, branch=branch)
239*4882a593Smuzhiyun            rev_index = gitarchive.rev_find(revs, 'commit', commit)
240*4882a593Smuzhiyun            testresults = resultutils.git_get_result(repo, revs[rev_index][2], configmap=configmap)
241*4882a593Smuzhiyun        elif tag:
242*4882a593Smuzhiyun            repo = GitRepo(source_dir)
243*4882a593Smuzhiyun            testresults = resultutils.git_get_result(repo, [tag], configmap=configmap)
244*4882a593Smuzhiyun        else:
245*4882a593Smuzhiyun            testresults = resultutils.load_resultsdata(source_dir, configmap=configmap)
246*4882a593Smuzhiyun        if raw_test:
247*4882a593Smuzhiyun            raw_results = {}
248*4882a593Smuzhiyun            for testsuite in testresults:
249*4882a593Smuzhiyun                result = testresults[testsuite].get(raw_test, {})
250*4882a593Smuzhiyun                if result:
251*4882a593Smuzhiyun                    raw_results[testsuite] = {raw_test: result}
252*4882a593Smuzhiyun            if raw_results:
253*4882a593Smuzhiyun                if selected_test_case_only:
254*4882a593Smuzhiyun                    print_selected_testcase_result(raw_results, selected_test_case_only)
255*4882a593Smuzhiyun                else:
256*4882a593Smuzhiyun                    print(json.dumps(raw_results, sort_keys=True, indent=4))
257*4882a593Smuzhiyun            else:
258*4882a593Smuzhiyun                print('Could not find raw test result for %s' % raw_test)
259*4882a593Smuzhiyun            return 0
260*4882a593Smuzhiyun        if selected_test_case_only:
261*4882a593Smuzhiyun            print_selected_testcase_result(testresults, selected_test_case_only)
262*4882a593Smuzhiyun            return 0
263*4882a593Smuzhiyun        for testsuite in testresults:
264*4882a593Smuzhiyun            for resultid in testresults[testsuite]:
265*4882a593Smuzhiyun                skip = False
266*4882a593Smuzhiyun                result = testresults[testsuite][resultid]
267*4882a593Smuzhiyun                machine = result['configuration']['MACHINE']
268*4882a593Smuzhiyun
269*4882a593Smuzhiyun                # Check to see if there is already results for these kinds of tests for the machine
270*4882a593Smuzhiyun                for key in result['result'].keys():
271*4882a593Smuzhiyun                    testtype = str(key).split('.')[0]
272*4882a593Smuzhiyun                    if ((machine in self.ltptests and testtype == "ltpiresult" and self.ltptests[machine]) or
273*4882a593Smuzhiyun                        (machine in self.ltpposixtests and testtype == "ltpposixresult" and self.ltpposixtests[machine])):
274*4882a593Smuzhiyun                        print("Already have test results for %s on %s, skipping %s" %(str(key).split('.')[0], machine, resultid))
275*4882a593Smuzhiyun                        skip = True
276*4882a593Smuzhiyun                        break
277*4882a593Smuzhiyun                if skip:
278*4882a593Smuzhiyun                    break
279*4882a593Smuzhiyun
280*4882a593Smuzhiyun                test_count_report = self.get_aggregated_test_result(logger, result, machine)
281*4882a593Smuzhiyun                test_count_report['machine'] = machine
282*4882a593Smuzhiyun                test_count_report['testseries'] = result['configuration']['TESTSERIES']
283*4882a593Smuzhiyun                test_count_report['result_id'] = resultid
284*4882a593Smuzhiyun                test_count_reports.append(test_count_report)
285*4882a593Smuzhiyun        self.print_test_report('test_report_full_text.txt', test_count_reports)
286*4882a593Smuzhiyun
287*4882a593Smuzhiyundef report(args, logger):
288*4882a593Smuzhiyun    report = ResultsTextReport()
289*4882a593Smuzhiyun    report.view_test_report(logger, args.source_dir, args.branch, args.commit, args.tag, args.use_regression_map,
290*4882a593Smuzhiyun                            args.raw_test_only, args.selected_test_case_only)
291*4882a593Smuzhiyun    return 0
292*4882a593Smuzhiyun
293*4882a593Smuzhiyundef register_commands(subparsers):
294*4882a593Smuzhiyun    """Register subcommands from this plugin"""
295*4882a593Smuzhiyun    parser_build = subparsers.add_parser('report', help='summarise test results',
296*4882a593Smuzhiyun                                         description='print a text-based summary of the test results',
297*4882a593Smuzhiyun                                         group='analysis')
298*4882a593Smuzhiyun    parser_build.set_defaults(func=report)
299*4882a593Smuzhiyun    parser_build.add_argument('source_dir',
300*4882a593Smuzhiyun                              help='source file/directory/URL that contain the test result files to summarise')
301*4882a593Smuzhiyun    parser_build.add_argument('--branch', '-B', default='master', help="Branch to find commit in")
302*4882a593Smuzhiyun    parser_build.add_argument('--commit', help="Revision to report")
303*4882a593Smuzhiyun    parser_build.add_argument('-t', '--tag', default='',
304*4882a593Smuzhiyun                              help='source_dir is a git repository, report on the tag specified from that repository')
305*4882a593Smuzhiyun    parser_build.add_argument('-m', '--use_regression_map', action='store_true',
306*4882a593Smuzhiyun                              help='instead of the default "store_map", use the "regression_map" for report')
307*4882a593Smuzhiyun    parser_build.add_argument('-r', '--raw_test_only', default='',
308*4882a593Smuzhiyun                              help='output raw test result only for the user provided test result id')
309*4882a593Smuzhiyun    parser_build.add_argument('-s', '--selected_test_case_only', default='',
310*4882a593Smuzhiyun                              help='output selected test case result for the user provided test case id, if both test '
311*4882a593Smuzhiyun                                   'result id and test case id are provided then output the selected test case result '
312*4882a593Smuzhiyun                                   'from the provided test result id')
313