xref: /aosp_15_r20/external/vulkan-validation-layers/scripts/parse_test_results.py (revision b7893ccf7851cd6a48cc5a1e965257d8a5cdcc70)
1*b7893ccfSSadaf Ebrahimi#!/usr/bin/python3
2*b7893ccfSSadaf Ebrahimi#
3*b7893ccfSSadaf Ebrahimi# Copyright (c) 2018 Google Inc.
4*b7893ccfSSadaf Ebrahimi#
5*b7893ccfSSadaf Ebrahimi# Licensed under the Apache License, Version 2.0 (the "License");
6*b7893ccfSSadaf Ebrahimi# you may not use this file except in compliance with the License.
7*b7893ccfSSadaf Ebrahimi# You may obtain a copy of the License at
8*b7893ccfSSadaf Ebrahimi#
9*b7893ccfSSadaf Ebrahimi#           http://www.apache.org/licenses/LICENSE-2.0
10*b7893ccfSSadaf Ebrahimi#
11*b7893ccfSSadaf Ebrahimi# Unless required by applicable law or agreed to in writing, software
12*b7893ccfSSadaf Ebrahimi# distributed under the License is distributed on an "AS IS" BASIS,
13*b7893ccfSSadaf Ebrahimi# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*b7893ccfSSadaf Ebrahimi# See the License for the specific language governing permissions and
15*b7893ccfSSadaf Ebrahimi# limitations under the License.
16*b7893ccfSSadaf Ebrahimi#
17*b7893ccfSSadaf Ebrahimi# Author: William Henning <[email protected]>
18*b7893ccfSSadaf Ebrahimi#
19*b7893ccfSSadaf Ebrahimi# This script parses the validation layers test continuous integration ouput
20*b7893ccfSSadaf Ebrahimi# and reports the number of tests that passed, failured, ouput unexpected errors,
21*b7893ccfSSadaf Ebrahimi# or were skipped. As such, the script is only designed to parse the ouput
22*b7893ccfSSadaf Ebrahimi# generated by the existing CI implementation.
23*b7893ccfSSadaf Ebrahimi#
24*b7893ccfSSadaf Ebrahimi# usage:
25*b7893ccfSSadaf Ebrahimi#       for profile in tests/device_profiles/*.json; do echo Testing with
26*b7893ccfSSadaf Ebrahimi#       profile $profile; VK_LAYER_PATH=DEVSIM_AND_VALIDATION_PATHS
27*b7893ccfSSadaf Ebrahimi#       VK_DEVSIM_FILE=$profile VK_ICD_FILENAMES=MOCK_ICD_PATH
28*b7893ccfSSadaf Ebrahimi#       ./build/tests/vk_layer_validation_tests --devsim; done
29*b7893ccfSSadaf Ebrahimi#       | python3 parse_test_results.py [--fail_on_skip] [--fail_on_unexpected]
30*b7893ccfSSadaf Ebrahimi#
31*b7893ccfSSadaf Ebrahimi#       --fail_on_skip causes the script to exit with a non-zero exit code if a test
32*b7893ccfSSadaf Ebrahimi#       didn't run on any device profile
33*b7893ccfSSadaf Ebrahimi#
34*b7893ccfSSadaf Ebrahimi#       --fail_on_unexpected causes the script to exit with a non-zero exit code if
35*b7893ccfSSadaf Ebrahimi#       a test printed unexpected errors
36*b7893ccfSSadaf Ebrahimi#
37*b7893ccfSSadaf Ebrahimi
38*b7893ccfSSadaf Ebrahimiimport argparse
39*b7893ccfSSadaf Ebrahimiimport re
40*b7893ccfSSadaf Ebrahimiimport sys
41*b7893ccfSSadaf Ebrahimifrom collections import defaultdict
42*b7893ccfSSadaf Ebrahimi
43*b7893ccfSSadaf Ebrahimiclass OutputStats(object):
44*b7893ccfSSadaf Ebrahimi    def __init__(self):
45*b7893ccfSSadaf Ebrahimi        self.current_profile = ""
46*b7893ccfSSadaf Ebrahimi        self.current_test = ""
47*b7893ccfSSadaf Ebrahimi        self.current_test_output = ""
48*b7893ccfSSadaf Ebrahimi        self.test_results = defaultdict(defaultdict)
49*b7893ccfSSadaf Ebrahimi        self.unexpected_errors = defaultdict(defaultdict)
50*b7893ccfSSadaf Ebrahimi
51*b7893ccfSSadaf Ebrahimi    def match(self, line):
52*b7893ccfSSadaf Ebrahimi        self.new_profile_match(line)
53*b7893ccfSSadaf Ebrahimi        self.test_suite_end_match(line)
54*b7893ccfSSadaf Ebrahimi        self.start_test_match(line)
55*b7893ccfSSadaf Ebrahimi        if self.current_test != "":
56*b7893ccfSSadaf Ebrahimi            self.current_test_output += line
57*b7893ccfSSadaf Ebrahimi        self.skip_test_match(line)
58*b7893ccfSSadaf Ebrahimi        self.pass_test_match(line)
59*b7893ccfSSadaf Ebrahimi        self.fail_test_match(line)
60*b7893ccfSSadaf Ebrahimi        self.unexpected_error_match(line)
61*b7893ccfSSadaf Ebrahimi
62*b7893ccfSSadaf Ebrahimi    def print_summary(self, skip_is_failure, unexpected_is_failure):
63*b7893ccfSSadaf Ebrahimi        if self.current_test != "":
64*b7893ccfSSadaf Ebrahimi            self.test_died()
65*b7893ccfSSadaf Ebrahimi
66*b7893ccfSSadaf Ebrahimi        passed_tests = 0
67*b7893ccfSSadaf Ebrahimi        skipped_tests = 0
68*b7893ccfSSadaf Ebrahimi        failed_tests = 0
69*b7893ccfSSadaf Ebrahimi        unexpected_error_tests = 0
70*b7893ccfSSadaf Ebrahimi        did_fail = False
71*b7893ccfSSadaf Ebrahimi
72*b7893ccfSSadaf Ebrahimi        for test_name, results in self.test_results.items():
73*b7893ccfSSadaf Ebrahimi            skipped_profiles = 0
74*b7893ccfSSadaf Ebrahimi            passed_profiles = 0
75*b7893ccfSSadaf Ebrahimi            failed_profiles = 0
76*b7893ccfSSadaf Ebrahimi            aborted_profiles = 0
77*b7893ccfSSadaf Ebrahimi            unexpected_error_profiles = 0
78*b7893ccfSSadaf Ebrahimi            for profile, result in results.items():
79*b7893ccfSSadaf Ebrahimi                if result == "pass":
80*b7893ccfSSadaf Ebrahimi                    passed_profiles += 1
81*b7893ccfSSadaf Ebrahimi                if result == "fail":
82*b7893ccfSSadaf Ebrahimi                    failed_profiles += 1
83*b7893ccfSSadaf Ebrahimi                if result == "skip":
84*b7893ccfSSadaf Ebrahimi                    skipped_profiles += 1
85*b7893ccfSSadaf Ebrahimi                if self.unexpected_errors.get(test_name, {}).get(profile, "") == "true":
86*b7893ccfSSadaf Ebrahimi                    unexpected_error_profiles += 1
87*b7893ccfSSadaf Ebrahimi            if failed_profiles != 0:
88*b7893ccfSSadaf Ebrahimi                print("TEST FAILED:", test_name)
89*b7893ccfSSadaf Ebrahimi                failed_tests += 1
90*b7893ccfSSadaf Ebrahimi            elif skipped_profiles == len(results):
91*b7893ccfSSadaf Ebrahimi                print("TEST SKIPPED ALL DEVICES:", test_name)
92*b7893ccfSSadaf Ebrahimi                skipped_tests += 1
93*b7893ccfSSadaf Ebrahimi            else:
94*b7893ccfSSadaf Ebrahimi                passed_tests += 1
95*b7893ccfSSadaf Ebrahimi            if unexpected_error_profiles != 0:
96*b7893ccfSSadaf Ebrahimi                print("UNEXPECTED ERRORS:", test_name)
97*b7893ccfSSadaf Ebrahimi                unexpected_error_tests += 1
98*b7893ccfSSadaf Ebrahimi        num_tests = len(self.test_results)
99*b7893ccfSSadaf Ebrahimi        print("PASSED: ", passed_tests, "/", num_tests, " tests")
100*b7893ccfSSadaf Ebrahimi        if skipped_tests != 0:
101*b7893ccfSSadaf Ebrahimi            did_fail |= skip_is_failure
102*b7893ccfSSadaf Ebrahimi            print("NEVER RAN: ", skipped_tests, "/", num_tests, " tests")
103*b7893ccfSSadaf Ebrahimi        if failed_tests != 0:
104*b7893ccfSSadaf Ebrahimi            did_fail = True
105*b7893ccfSSadaf Ebrahimi            print("FAILED: ", failed_tests, "/", num_tests, "tests")
106*b7893ccfSSadaf Ebrahimi        if unexpected_error_tests != 0:
107*b7893ccfSSadaf Ebrahimi            did_fail |= unexpected_is_failure
108*b7893ccfSSadaf Ebrahimi            print("UNEXPECTED OUPUT: ", unexpected_error_tests, "/", num_tests, "tests")
109*b7893ccfSSadaf Ebrahimi        return did_fail
110*b7893ccfSSadaf Ebrahimi
111*b7893ccfSSadaf Ebrahimi    def new_profile_match(self, line):
112*b7893ccfSSadaf Ebrahimi        if re.search(r'Testing with profile .*/(.*)', line) is not None:
113*b7893ccfSSadaf Ebrahimi            self.current_profile = re.search(r'Testing with profile .*/(.*)', line).group(1)
114*b7893ccfSSadaf Ebrahimi
115*b7893ccfSSadaf Ebrahimi    def test_suite_end_match(self, line):
116*b7893ccfSSadaf Ebrahimi        if re.search(r'\[-*\]', line) is not None:
117*b7893ccfSSadaf Ebrahimi            if self.current_test != "":
118*b7893ccfSSadaf Ebrahimi                # Here we see a message that starts [----------] before another test
119*b7893ccfSSadaf Ebrahimi                # finished running. This should mean that that other test died.
120*b7893ccfSSadaf Ebrahimi                self.test_died()
121*b7893ccfSSadaf Ebrahimi
122*b7893ccfSSadaf Ebrahimi    def start_test_match(self, line):
123*b7893ccfSSadaf Ebrahimi        if re.search(r'\[ RUN\s*\]', line) is not None:
124*b7893ccfSSadaf Ebrahimi            # This parser doesn't handle the case where one test's start comes between another
125*b7893ccfSSadaf Ebrahimi            # test's start and result.
126*b7893ccfSSadaf Ebrahimi            assert self.current_test == ""
127*b7893ccfSSadaf Ebrahimi            self.current_test = re.search(r'] (.*)', line).group(1)
128*b7893ccfSSadaf Ebrahimi            self.current_test_output = ""
129*b7893ccfSSadaf Ebrahimi
130*b7893ccfSSadaf Ebrahimi    def skip_test_match(self, line):
131*b7893ccfSSadaf Ebrahimi        if re.search(r'TEST SKIPPED', line) is not None:
132*b7893ccfSSadaf Ebrahimi            self.test_results[self.current_test][self.current_profile] = "skip"
133*b7893ccfSSadaf Ebrahimi
134*b7893ccfSSadaf Ebrahimi    def pass_test_match(self, line):
135*b7893ccfSSadaf Ebrahimi        if re.search(r'\[\s*OK \]', line) is not None:
136*b7893ccfSSadaf Ebrahimi            # If gtest says the test passed, check if it was skipped before marking it passed
137*b7893ccfSSadaf Ebrahimi            if self.test_results.get(self.current_test, {}).get(self.current_profile, "") != "skip":
138*b7893ccfSSadaf Ebrahimi                    self.test_results[self.current_test][self.current_profile] = "pass"
139*b7893ccfSSadaf Ebrahimi            self.current_test = ""
140*b7893ccfSSadaf Ebrahimi
141*b7893ccfSSadaf Ebrahimi    def fail_test_match(self, line):
142*b7893ccfSSadaf Ebrahimi        if re.search(r'\[\s*FAILED\s*\]', line) is not None and self.current_test != "":
143*b7893ccfSSadaf Ebrahimi            self.test_results[self.current_test][self.current_profile] = "fail"
144*b7893ccfSSadaf Ebrahimi            self.current_test = ""
145*b7893ccfSSadaf Ebrahimi
146*b7893ccfSSadaf Ebrahimi    def unexpected_error_match(self, line):
147*b7893ccfSSadaf Ebrahimi        if re.search(r'^Unexpected: ', line) is not None:
148*b7893ccfSSadaf Ebrahimi            self.unexpected_errors[self.current_test][self.current_profile] = "true"
149*b7893ccfSSadaf Ebrahimi
150*b7893ccfSSadaf Ebrahimi    def test_died(self):
151*b7893ccfSSadaf Ebrahimi        print("A test likely crashed. Testing is being aborted.")
152*b7893ccfSSadaf Ebrahimi        print("Final test output: ")
153*b7893ccfSSadaf Ebrahimi        print(self.current_test_output)
154*b7893ccfSSadaf Ebrahimi        sys.exit(1)
155*b7893ccfSSadaf Ebrahimi
156*b7893ccfSSadaf Ebrahimidef main():
157*b7893ccfSSadaf Ebrahimi    parser = argparse.ArgumentParser(description='Parse the output from validation layer tests.')
158*b7893ccfSSadaf Ebrahimi    parser.add_argument('--fail_on_skip', action='store_true', help="Makes the script exit with a "
159*b7893ccfSSadaf Ebrahimi                        "non-zero exit code if a test didn't run on any device profile.")
160*b7893ccfSSadaf Ebrahimi    parser.add_argument('--fail_on_unexpected', action='store_true', help="Makes the script exit "
161*b7893ccfSSadaf Ebrahimi                        "with a non-zero exit code if a test causes unexpected errors.")
162*b7893ccfSSadaf Ebrahimi    args = parser.parse_args()
163*b7893ccfSSadaf Ebrahimi
164*b7893ccfSSadaf Ebrahimi    stats = OutputStats()
165*b7893ccfSSadaf Ebrahimi    for line in sys.stdin:
166*b7893ccfSSadaf Ebrahimi        stats.match(line)
167*b7893ccfSSadaf Ebrahimi    failed = stats.print_summary(args.fail_on_skip, args.fail_on_unexpected)
168*b7893ccfSSadaf Ebrahimi    if failed == True:
169*b7893ccfSSadaf Ebrahimi        print("\nFAILED CI")
170*b7893ccfSSadaf Ebrahimi        sys.exit(1)
171*b7893ccfSSadaf Ebrahimi
172*b7893ccfSSadaf Ebrahimiif __name__ == '__main__':
173*b7893ccfSSadaf Ebrahimi    main()
174