blob: 6003fa1cae4d9854f33c7b508565fd06662e9503 [file] [log] [blame]
Tobin Ehlis35308dd2016-10-31 13:27:36 -06001#!/usr/bin/env python3
2# Copyright (c) 2015-2016 The Khronos Group Inc.
3# Copyright (c) 2015-2016 Valve Corporation
4# Copyright (c) 2015-2016 LunarG, Inc.
5# Copyright (c) 2015-2016 Google Inc.
6#
7# Licensed under the Apache License, Version 2.0 (the "License");
8# you may not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS,
15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18#
19# Author: Tobin Ehlis <tobine@google.com>
20
21import argparse
22import os
23import sys
24import platform
25
26# vk_validation_stats.py overview
27# This script is intended to generate statistics on the state of validation code
28# based on information parsed from the source files and the database file
29# Here's what it currently does:
30# 1. Parse vk_validation_error_database.txt to store claimed state of validation checks
31# 2. Parse vk_validation_error_messages.h to verify the actual checks in header vs. the
32# claimed state of the checks
33# 3. Parse source files to identify which checks are implemented and verify that this
34# exactly matches the list of checks claimed to be implemented in the database
35# 4. Parse test file(s) and verify that reported tests exist
36# 5. Report out stats on number of checks, implemented checks, and duplicated checks
37#
Tobin Ehlis20e32582016-12-05 14:50:03 -070038# If a mis-match is found during steps 2, 3, or 4, then the script exits w/ a non-zero error code
39# otherwise, the script will exit(0)
40#
Tobin Ehlis35308dd2016-10-31 13:27:36 -060041# TODO:
42# 1. Would also like to report out number of existing checks that don't yet use new, unique enum
43# 2. Could use notes to store custom fields (like TODO) and print those out here
44# 3. Update test code to check if tests use new, unique enums to check for errors instead of strings
45
46db_file = 'vk_validation_error_database.txt'
47layer_source_files = [
48'core_validation.cpp',
49'descriptor_sets.cpp',
50'parameter_validation.cpp',
51'object_tracker.cpp',
Mike Weiblen6a27de52016-12-09 17:36:28 -070052'image.cpp',
53'swapchain.cpp'
Tobin Ehlis35308dd2016-10-31 13:27:36 -060054]
55header_file = 'vk_validation_error_messages.h'
56# TODO : Don't hardcode linux path format if we want this to run on windows
57test_file = '../tests/layer_validation_tests.cpp'
Tobin Ehlis225b59c2016-12-22 13:59:42 -070058# List of enums that are allowed to be used more than once so don't warn on their duplicates
59duplicate_exceptions = [
Tobin Ehlis1fd9eaf2016-12-22 14:31:01 -070060'VALIDATION_ERROR_00018', # This covers the broad case that all child objects must be destroyed at DestroyInstance time
61'VALIDATION_ERROR_00049', # This covers the broad case that all child objects must be destroyed at DestroyDevice time
Tobin Ehlise664a2b2016-12-22 14:10:21 -070062'VALIDATION_ERROR_00648', # This is a case for VkMappedMemoryRange struct that is used by both Flush & Invalidate MappedMemoryRange
Tobin Ehlis1fd9eaf2016-12-22 14:31:01 -070063'VALIDATION_ERROR_00741', # This is a blanket case for all invalid image aspect bit errors. The spec link has appropriate details for all separate cases.
64'VALIDATION_ERROR_00768', # This case covers two separate checks which are done independently
65'VALIDATION_ERROR_00769', # This case covers two separate checks which are done independently
66'VALIDATION_ERROR_00942', # This is a descriptor set write update error that we use for a couple copy cases as well
67'VALIDATION_ERROR_02525', # Used twice for the same error codepath as both a param & to set a variable, so not really a duplicate
Tobin Ehlis225b59c2016-12-22 13:59:42 -070068]
Tobin Ehlis35308dd2016-10-31 13:27:36 -060069
70class ValidationDatabase:
71 def __init__(self, filename=db_file):
72 self.db_file = filename
73 self.delimiter = '~^~'
74 self.db_dict = {} # complete dict of all db values per error enum
75 # specialized data structs with slices of complete dict
76 self.db_implemented_enums = [] # list of all error enums claiming to be implemented in database file
77 self.db_enum_to_tests = {} # dict where enum is key to lookup list of tests implementing the enum
78 #self.src_implemented_enums
79 def read(self):
80 """Read a database file into internal data structures, format of each line is <enum><implemented Y|N?><testname><api><errormsg><notes>"""
81 #db_dict = {} # This is a simple db of just enum->errormsg, the same as is created from spec
82 #max_id = 0
83 with open(self.db_file, "r") as infile:
84 for line in infile:
85 line = line.strip()
86 if line.startswith('#') or '' == line:
87 continue
88 db_line = line.split(self.delimiter)
89 if len(db_line) != 6:
Tobin Ehlis027f3212016-12-09 12:15:26 -070090 print("ERROR: Bad database line doesn't have 6 elements: %s" % (line))
Tobin Ehlis35308dd2016-10-31 13:27:36 -060091 error_enum = db_line[0]
92 implemented = db_line[1]
93 testname = db_line[2]
94 api = db_line[3]
95 error_str = db_line[4]
96 note = db_line[5]
97 # Read complete database contents into our class var for later use
98 self.db_dict[error_enum] = {}
99 self.db_dict[error_enum]['check_implemented'] = implemented
100 self.db_dict[error_enum]['testname'] = testname
101 self.db_dict[error_enum]['api'] = api
102 self.db_dict[error_enum]['error_string'] = error_str
103 self.db_dict[error_enum]['note'] = note
104 # Now build custom data structs
105 if 'Y' == implemented:
106 self.db_implemented_enums.append(error_enum)
107 if testname.lower() not in ['unknown', 'none']:
108 self.db_enum_to_tests[error_enum] = testname.split(',')
109 #if len(self.db_enum_to_tests[error_enum]) > 1:
110 # print "Found check %s that has multiple tests: %s" % (error_enum, self.db_enum_to_tests[error_enum])
111 #else:
112 # print "Check %s has single test: %s" % (error_enum, self.db_enum_to_tests[error_enum])
113 #unique_id = int(db_line[0].split('_')[-1])
114 #if unique_id > max_id:
115 # max_id = unique_id
116 #print "Found %d total enums in database" % (len(self.db_dict.keys()))
117 #print "Found %d enums claiming to be implemented in source" % (len(self.db_implemented_enums))
118 #print "Found %d enums claiming to have tests implemented" % (len(self.db_enum_to_tests.keys()))
119
120class ValidationHeader:
121 def __init__(self, filename=header_file):
122 self.filename = header_file
123 self.enums = []
124 def read(self):
125 """Read unique error enum header file into internal data structures"""
126 grab_enums = False
127 with open(self.filename, "r") as infile:
128 for line in infile:
129 line = line.strip()
130 if 'enum UNIQUE_VALIDATION_ERROR_CODE {' in line:
131 grab_enums = True
132 continue
133 if grab_enums:
134 if 'VALIDATION_ERROR_MAX_ENUM' in line:
135 grab_enums = False
136 break # done
Tobin Ehlisf53eac32016-12-09 14:10:47 -0700137 elif 'VALIDATION_ERROR_UNDEFINED' in line:
138 continue
139 elif 'VALIDATION_ERROR_' in line:
Tobin Ehlis35308dd2016-10-31 13:27:36 -0600140 enum = line.split(' = ')[0]
141 self.enums.append(enum)
142 #print "Found %d error enums. First is %s and last is %s." % (len(self.enums), self.enums[0], self.enums[-1])
143
144class ValidationSource:
145 def __init__(self, source_file_list):
146 self.source_files = source_file_list
Tobin Ehlis3d1f2bd2016-12-22 11:19:15 -0700147 self.enum_count_dict = {} # dict of enum values to the count of how much they're used, and location of where they're used
Tobin Ehlis3d9dd942016-11-23 13:08:01 -0700148 # 1790 is a special case that provides an exception when an extension is enabled. No specific error is flagged, but the exception is handled so add it here
Tobin Ehlis3d1f2bd2016-12-22 11:19:15 -0700149 self.enum_count_dict['VALIDATION_ERROR_01790'] = {}
150 self.enum_count_dict['VALIDATION_ERROR_01790']['count'] = 1
Tobin Ehlis35308dd2016-10-31 13:27:36 -0600151 def parse(self):
152 duplicate_checks = 0
153 for sf in self.source_files:
Tobin Ehlis3d1f2bd2016-12-22 11:19:15 -0700154 line_num = 0
Tobin Ehlis35308dd2016-10-31 13:27:36 -0600155 with open(sf) as f:
156 for line in f:
Tobin Ehlis3d1f2bd2016-12-22 11:19:15 -0700157 line_num = line_num + 1
Tobin Ehlis35308dd2016-10-31 13:27:36 -0600158 if True in [line.strip().startswith(comment) for comment in ['//', '/*']]:
159 continue
160 # Find enums
161 #if 'VALIDATION_ERROR_' in line and True not in [ignore in line for ignore in ['[VALIDATION_ERROR_', 'UNIQUE_VALIDATION_ERROR_CODE']]:
Tobin Ehlisf53eac32016-12-09 14:10:47 -0700162 if ' VALIDATION_ERROR_' in line:
Tobin Ehlis35308dd2016-10-31 13:27:36 -0600163 # Need to isolate the validation error enum
164 #print("Line has check:%s" % (line))
165 line_list = line.split()
Tobin Ehlis928742e2016-12-09 17:11:13 -0700166 enum_list = []
Tobin Ehlis35308dd2016-10-31 13:27:36 -0600167 for str in line_list:
Tobin Ehlisf53eac32016-12-09 14:10:47 -0700168 if 'VALIDATION_ERROR_' in str and True not in [ignore_str in str for ignore_str in ['[VALIDATION_ERROR_', 'VALIDATION_ERROR_UNDEFINED', 'UNIQUE_VALIDATION_ERROR_CODE']]:
Tobin Ehlis928742e2016-12-09 17:11:13 -0700169 enum_list.append(str.strip(',);'))
170 #break
171 for enum in enum_list:
172 if enum != '':
173 if enum not in self.enum_count_dict:
Tobin Ehlis3d1f2bd2016-12-22 11:19:15 -0700174 self.enum_count_dict[enum] = {}
175 self.enum_count_dict[enum]['count'] = 1
176 self.enum_count_dict[enum]['file_line'] = []
177 self.enum_count_dict[enum]['file_line'].append('%s,%d' % (sf, line_num))
Tobin Ehlis928742e2016-12-09 17:11:13 -0700178 #print "Found enum %s implemented for first time in file %s" % (enum, sf)
179 else:
Tobin Ehlis3d1f2bd2016-12-22 11:19:15 -0700180 self.enum_count_dict[enum]['count'] = self.enum_count_dict[enum]['count'] + 1
181 self.enum_count_dict[enum]['file_line'].append('%s,%d' % (sf, line_num))
Tobin Ehlis928742e2016-12-09 17:11:13 -0700182 #print "Found enum %s implemented for %d time in file %s" % (enum, self.enum_count_dict[enum], sf)
183 duplicate_checks = duplicate_checks + 1
184 #else:
185 #print("Didn't find actual check in line:%s" % (line))
Tobin Ehlis35308dd2016-10-31 13:27:36 -0600186 #print "Found %d unique implemented checks and %d are duplicated at least once" % (len(self.enum_count_dict.keys()), duplicate_checks)
187
188# Class to parse the validation layer test source and store testnames
189# TODO: Enhance class to detect use of unique error enums in the test
190class TestParser:
191 def __init__(self, test_file_list, test_group_name=['VkLayerTest', 'VkPositiveLayerTest', 'VkWsiEnabledLayerTest']):
192 self.test_files = test_file_list
193 self.tests_set = set()
194 self.test_trigger_txt_list = []
195 for tg in test_group_name:
196 self.test_trigger_txt_list.append('TEST_F(%s' % tg)
197 #print('Test trigger test list: %s' % (self.test_trigger_txt_list))
198
199 # Parse test files into internal data struct
200 def parse(self):
201 # For each test file, parse test names into set
202 grab_next_line = False # handle testname on separate line than wildcard
203 for test_file in self.test_files:
204 with open(test_file) as tf:
205 for line in tf:
206 if True in [line.strip().startswith(comment) for comment in ['//', '/*']]:
207 continue
208
209 if True in [ttt in line for ttt in self.test_trigger_txt_list]:
210 #print('Test wildcard in line: %s' % (line))
211 testname = line.split(',')[-1]
212 testname = testname.strip().strip(' {)')
213 #print('Inserting test: "%s"' % (testname))
214 if ('' == testname):
215 grab_next_line = True
216 continue
217 self.tests_set.add(testname)
218 if grab_next_line: # test name on its own line
219 grab_next_line = False
220 testname = testname.strip().strip(' {)')
221 self.tests_set.add(testname)
222
223# Little helper class for coloring cmd line output
224class bcolors:
225
226 def __init__(self):
227 self.GREEN = '\033[0;32m'
228 self.RED = '\033[0;31m'
229 self.YELLOW = '\033[1;33m'
230 self.ENDC = '\033[0m'
231 if 'Linux' != platform.system():
232 self.GREEN = ''
233 self.RED = ''
234 self.YELLOW = ''
235 self.ENDC = ''
236
237 def green(self):
238 return self.GREEN
239
240 def red(self):
241 return self.RED
242
243 def yellow(self):
244 return self.YELLOW
245
246 def endc(self):
247 return self.ENDC
248
249# Class to parse the validation layer test source and store testnames
250class TestParser:
251 def __init__(self, test_file_list, test_group_name=['VkLayerTest', 'VkPositiveLayerTest', 'VkWsiEnabledLayerTest']):
252 self.test_files = test_file_list
253 self.tests_set = set()
254 self.test_trigger_txt_list = []
255 for tg in test_group_name:
256 self.test_trigger_txt_list.append('TEST_F(%s' % tg)
257 #print('Test trigger test list: %s' % (self.test_trigger_txt_list))
258
259 # Parse test files into internal data struct
260 def parse(self):
261 # For each test file, parse test names into set
262 grab_next_line = False # handle testname on separate line than wildcard
263 for test_file in self.test_files:
264 with open(test_file) as tf:
265 for line in tf:
266 if True in [line.strip().startswith(comment) for comment in ['//', '/*']]:
267 continue
268
269 if True in [ttt in line for ttt in self.test_trigger_txt_list]:
270 #print('Test wildcard in line: %s' % (line))
271 testname = line.split(',')[-1]
272 testname = testname.strip().strip(' {)')
273 #print('Inserting test: "%s"' % (testname))
274 if ('' == testname):
275 grab_next_line = True
276 continue
277 self.tests_set.add(testname)
278 if grab_next_line: # test name on its own line
279 grab_next_line = False
280 testname = testname.strip().strip(' {)')
281 self.tests_set.add(testname)
282
283def main(argv=None):
Tobin Ehlis20e32582016-12-05 14:50:03 -0700284 result = 0 # Non-zero result indicates an error case
Tobin Ehlis35308dd2016-10-31 13:27:36 -0600285 # parse db
286 val_db = ValidationDatabase()
287 val_db.read()
288 # parse header
289 val_header = ValidationHeader()
290 val_header.read()
291 # Create parser for layer files
292 val_source = ValidationSource(layer_source_files)
293 val_source.parse()
294 # Parse test files
295 test_parser = TestParser([test_file, ])
296 test_parser.parse()
297
298 # Process stats - Just doing this inline in main, could make a fancy class to handle
299 # all the processing of data and then get results from that
300 txt_color = bcolors()
301 print("Validation Statistics")
302 # First give number of checks in db & header and report any discrepancies
303 db_enums = len(val_db.db_dict.keys())
304 hdr_enums = len(val_header.enums)
305 print(" Database file includes %d unique checks" % (db_enums))
306 print(" Header file declares %d unique checks" % (hdr_enums))
307 tmp_db_dict = val_db.db_dict
308 db_missing = []
309 for enum in val_header.enums:
310 if not tmp_db_dict.pop(enum, False):
311 db_missing.append(enum)
312 if db_enums == hdr_enums and len(db_missing) == 0 and len(tmp_db_dict.keys()) == 0:
313 print(txt_color.green() + " Database and Header match, GREAT!" + txt_color.endc())
314 else:
315 print(txt_color.red() + " Uh oh, Database doesn't match Header :(" + txt_color.endc())
Tobin Ehlis20e32582016-12-05 14:50:03 -0700316 result = 1
Tobin Ehlis35308dd2016-10-31 13:27:36 -0600317 if len(db_missing) != 0:
318 print(txt_color.red() + " The following checks are in header but missing from database:" + txt_color.endc())
319 for missing_enum in db_missing:
320 print(txt_color.red() + " %s" % (missing_enum) + txt_color.endc())
321 if len(tmp_db_dict.keys()) != 0:
322 print(txt_color.red() + " The following checks are in database but haven't been declared in the header:" + txt_color.endc())
323 for extra_enum in tmp_db_dict:
324 print(txt_color.red() + " %s" % (extra_enum) + txt_color.endc())
325 # Report out claimed implemented checks vs. found actual implemented checks
326 imp_not_found = [] # Checks claimed to implemented in DB file but no source found
327 imp_not_claimed = [] # Checks found implemented but not claimed to be in DB
328 multiple_uses = False # Flag if any enums are used multiple times
329 for db_imp in val_db.db_implemented_enums:
330 if db_imp not in val_source.enum_count_dict:
331 imp_not_found.append(db_imp)
332 for src_enum in val_source.enum_count_dict:
Tobin Ehlis225b59c2016-12-22 13:59:42 -0700333 if val_source.enum_count_dict[src_enum]['count'] > 1 and src_enum not in duplicate_exceptions:
Tobin Ehlis35308dd2016-10-31 13:27:36 -0600334 multiple_uses = True
335 if src_enum not in val_db.db_implemented_enums:
336 imp_not_claimed.append(src_enum)
337 print(" Database file claims that %d checks (%s) are implemented in source." % (len(val_db.db_implemented_enums), "{0:.0f}%".format(float(len(val_db.db_implemented_enums))/db_enums * 100)))
338 if len(imp_not_found) == 0 and len(imp_not_claimed) == 0:
339 print(txt_color.green() + " All claimed Database implemented checks have been found in source, and no source checks aren't claimed in Database, GREAT!" + txt_color.endc())
340 else:
Tobin Ehlis20e32582016-12-05 14:50:03 -0700341 result = 1
Tobin Ehlis35308dd2016-10-31 13:27:36 -0600342 print(txt_color.red() + " Uh oh, Database claimed implemented don't match Source :(" + txt_color.endc())
343 if len(imp_not_found) != 0:
Tobin Ehlis3f0b2772016-11-18 16:56:15 -0700344 print(txt_color.red() + " The following %d checks are claimed to be implemented in Database, but weren't found in source:" % (len(imp_not_found)) + txt_color.endc())
Tobin Ehlis35308dd2016-10-31 13:27:36 -0600345 for not_imp_enum in imp_not_found:
346 print(txt_color.red() + " %s" % (not_imp_enum) + txt_color.endc())
347 if len(imp_not_claimed) != 0:
348 print(txt_color.red() + " The following checks are implemented in source, but not claimed to be in Database:" + txt_color.endc())
349 for imp_enum in imp_not_claimed:
350 print(txt_color.red() + " %s" % (imp_enum) + txt_color.endc())
351 if multiple_uses:
352 print(txt_color.yellow() + " Note that some checks are used multiple times. These may be good candidates for new valid usage spec language." + txt_color.endc())
353 print(txt_color.yellow() + " Here is a list of each check used multiple times with its number of uses:" + txt_color.endc())
354 for enum in val_source.enum_count_dict:
Tobin Ehlis225b59c2016-12-22 13:59:42 -0700355 if val_source.enum_count_dict[enum]['count'] > 1 and enum not in duplicate_exceptions:
Tobin Ehlis3d1f2bd2016-12-22 11:19:15 -0700356 print(txt_color.yellow() + " %s: %d uses in file,line:" % (enum, val_source.enum_count_dict[enum]['count']) + txt_color.endc())
357 for file_line in val_source.enum_count_dict[enum]['file_line']:
358 print(txt_color.yellow() + " \t%s" % (file_line) + txt_color.endc())
Tobin Ehlis35308dd2016-10-31 13:27:36 -0600359 # Now check that tests claimed to be implemented are actual test names
360 bad_testnames = []
361 for enum in val_db.db_enum_to_tests:
362 for testname in val_db.db_enum_to_tests[enum]:
363 if testname not in test_parser.tests_set:
364 bad_testnames.append(testname)
365 print(" Database file claims that %d checks have tests written." % len(val_db.db_enum_to_tests))
366 if len(bad_testnames) == 0:
367 print(txt_color.green() + " All claimed tests have valid names. That's good!" + txt_color.endc())
368 else:
369 print(txt_color.red() + " The following testnames in Database appear to be invalid:")
Tobin Ehlis20e32582016-12-05 14:50:03 -0700370 result = 1
Tobin Ehlis35308dd2016-10-31 13:27:36 -0600371 for bt in bad_testnames:
Tobin Ehlisb04c2c62016-11-21 15:51:45 -0700372 print(txt_color.red() + " %s" % (bt) + txt_color.endc())
Tobin Ehlis35308dd2016-10-31 13:27:36 -0600373
Tobin Ehlis20e32582016-12-05 14:50:03 -0700374 return result
Tobin Ehlis35308dd2016-10-31 13:27:36 -0600375
376if __name__ == "__main__":
377 sys.exit(main())
378