blob: 47c593fdd2c5b8eb55682d21d1b5cfa23c09afca [file] [log] [blame]
#!/usr/bin/env python3
# Copyright (c) 2015-2016 The Khronos Group Inc.
# Copyright (c) 2015-2016 Valve Corporation
# Copyright (c) 2015-2016 LunarG, Inc.
# Copyright (c) 2015-2016 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Author: Tobin Ehlis <tobine@google.com>
import argparse
import os
import sys
import platform
# vk_validation_stats.py overview
# This script is intended to generate statistics on the state of validation code
# based on information parsed from the source files and the database file
# Here's what it currently does:
# 1. Parse vk_validation_error_database.txt to store claimed state of validation checks
# 2. Parse vk_validation_error_messages.h to verify the actual checks in header vs. the
# claimed state of the checks
# 3. Parse source files to identify which checks are implemented and verify that this
# exactly matches the list of checks claimed to be implemented in the database
# 4. Parse test file(s) and verify that reported tests exist
# 5. Report out stats on number of checks, implemented checks, and duplicated checks
#
# TODO:
# 1. Would also like to report out number of existing checks that don't yet use new, unique enum
# 2. Could use notes to store custom fields (like TODO) and print those out here
# 3. Update test code to check if tests use new, unique enums to check for errors instead of strings
db_file = 'vk_validation_error_database.txt'
layer_source_files = [
'core_validation.cpp',
'descriptor_sets.cpp',
'parameter_validation.cpp',
'object_tracker.cpp',
]
header_file = 'vk_validation_error_messages.h'
# TODO : Don't hardcode linux path format if we want this to run on windows
test_file = '../tests/layer_validation_tests.cpp'
class ValidationDatabase:
def __init__(self, filename=db_file):
self.db_file = filename
self.delimiter = '~^~'
self.db_dict = {} # complete dict of all db values per error enum
# specialized data structs with slices of complete dict
self.db_implemented_enums = [] # list of all error enums claiming to be implemented in database file
self.db_enum_to_tests = {} # dict where enum is key to lookup list of tests implementing the enum
#self.src_implemented_enums
def read(self):
"""Read a database file into internal data structures, format of each line is <enum><implemented Y|N?><testname><api><errormsg><notes>"""
#db_dict = {} # This is a simple db of just enum->errormsg, the same as is created from spec
#max_id = 0
with open(self.db_file, "r") as infile:
for line in infile:
line = line.strip()
if line.startswith('#') or '' == line:
continue
db_line = line.split(self.delimiter)
if len(db_line) != 6:
print "ERROR: Bad database line doesn't have 6 elements: %s" % (line)
error_enum = db_line[0]
implemented = db_line[1]
testname = db_line[2]
api = db_line[3]
error_str = db_line[4]
note = db_line[5]
# Read complete database contents into our class var for later use
self.db_dict[error_enum] = {}
self.db_dict[error_enum]['check_implemented'] = implemented
self.db_dict[error_enum]['testname'] = testname
self.db_dict[error_enum]['api'] = api
self.db_dict[error_enum]['error_string'] = error_str
self.db_dict[error_enum]['note'] = note
# Now build custom data structs
if 'Y' == implemented:
self.db_implemented_enums.append(error_enum)
if testname.lower() not in ['unknown', 'none']:
self.db_enum_to_tests[error_enum] = testname.split(',')
#if len(self.db_enum_to_tests[error_enum]) > 1:
# print "Found check %s that has multiple tests: %s" % (error_enum, self.db_enum_to_tests[error_enum])
#else:
# print "Check %s has single test: %s" % (error_enum, self.db_enum_to_tests[error_enum])
#unique_id = int(db_line[0].split('_')[-1])
#if unique_id > max_id:
# max_id = unique_id
#print "Found %d total enums in database" % (len(self.db_dict.keys()))
#print "Found %d enums claiming to be implemented in source" % (len(self.db_implemented_enums))
#print "Found %d enums claiming to have tests implemented" % (len(self.db_enum_to_tests.keys()))
class ValidationHeader:
def __init__(self, filename=header_file):
self.filename = header_file
self.enums = []
def read(self):
"""Read unique error enum header file into internal data structures"""
grab_enums = False
with open(self.filename, "r") as infile:
for line in infile:
line = line.strip()
if 'enum UNIQUE_VALIDATION_ERROR_CODE {' in line:
grab_enums = True
continue
if grab_enums:
if 'VALIDATION_ERROR_MAX_ENUM' in line:
grab_enums = False
break # done
if 'VALIDATION_ERROR_' in line:
enum = line.split(' = ')[0]
self.enums.append(enum)
#print "Found %d error enums. First is %s and last is %s." % (len(self.enums), self.enums[0], self.enums[-1])
class ValidationSource:
def __init__(self, source_file_list):
self.source_files = source_file_list
self.enum_count_dict = {} # dict of enum values to the count of how much they're used
def parse(self):
duplicate_checks = 0
for sf in self.source_files:
with open(sf) as f:
for line in f:
if True in [line.strip().startswith(comment) for comment in ['//', '/*']]:
continue
# Find enums
#if 'VALIDATION_ERROR_' in line and True not in [ignore in line for ignore in ['[VALIDATION_ERROR_', 'UNIQUE_VALIDATION_ERROR_CODE']]:
if 'VALIDATION_ERROR_' in line and 'UNIQUE_VALIDATION_ERROR_CODE' not in line:
# Need to isolate the validation error enum
#print("Line has check:%s" % (line))
line_list = line.split()
enum = ''
for str in line_list:
if 'VALIDATION_ERROR_' in str and '[VALIDATION_ERROR_' not in str:
enum = str.strip(',);')
break
if enum != '':
if enum not in self.enum_count_dict:
self.enum_count_dict[enum] = 1
#print "Found enum %s implemented for first time in file %s" % (enum, sf)
else:
self.enum_count_dict[enum] = self.enum_count_dict[enum] + 1
#print "Found enum %s implemented for %d time in file %s" % (enum, self.enum_count_dict[enum], sf)
duplicate_checks = duplicate_checks + 1
#else:
#print("Didn't find actual check in line:%s" % (line))
#print "Found %d unique implemented checks and %d are duplicated at least once" % (len(self.enum_count_dict.keys()), duplicate_checks)
# Class to parse the validation layer test source and store testnames
# TODO: Enhance class to detect use of unique error enums in the test
class TestParser:
def __init__(self, test_file_list, test_group_name=['VkLayerTest', 'VkPositiveLayerTest', 'VkWsiEnabledLayerTest']):
self.test_files = test_file_list
self.tests_set = set()
self.test_trigger_txt_list = []
for tg in test_group_name:
self.test_trigger_txt_list.append('TEST_F(%s' % tg)
#print('Test trigger test list: %s' % (self.test_trigger_txt_list))
# Parse test files into internal data struct
def parse(self):
# For each test file, parse test names into set
grab_next_line = False # handle testname on separate line than wildcard
for test_file in self.test_files:
with open(test_file) as tf:
for line in tf:
if True in [line.strip().startswith(comment) for comment in ['//', '/*']]:
continue
if True in [ttt in line for ttt in self.test_trigger_txt_list]:
#print('Test wildcard in line: %s' % (line))
testname = line.split(',')[-1]
testname = testname.strip().strip(' {)')
#print('Inserting test: "%s"' % (testname))
if ('' == testname):
grab_next_line = True
continue
self.tests_set.add(testname)
if grab_next_line: # test name on its own line
grab_next_line = False
testname = testname.strip().strip(' {)')
self.tests_set.add(testname)
# Little helper class for coloring cmd line output
class bcolors:
def __init__(self):
self.GREEN = '\033[0;32m'
self.RED = '\033[0;31m'
self.YELLOW = '\033[1;33m'
self.ENDC = '\033[0m'
if 'Linux' != platform.system():
self.GREEN = ''
self.RED = ''
self.YELLOW = ''
self.ENDC = ''
def green(self):
return self.GREEN
def red(self):
return self.RED
def yellow(self):
return self.YELLOW
def endc(self):
return self.ENDC
# Class to parse the validation layer test source and store testnames
class TestParser:
def __init__(self, test_file_list, test_group_name=['VkLayerTest', 'VkPositiveLayerTest', 'VkWsiEnabledLayerTest']):
self.test_files = test_file_list
self.tests_set = set()
self.test_trigger_txt_list = []
for tg in test_group_name:
self.test_trigger_txt_list.append('TEST_F(%s' % tg)
#print('Test trigger test list: %s' % (self.test_trigger_txt_list))
# Parse test files into internal data struct
def parse(self):
# For each test file, parse test names into set
grab_next_line = False # handle testname on separate line than wildcard
for test_file in self.test_files:
with open(test_file) as tf:
for line in tf:
if True in [line.strip().startswith(comment) for comment in ['//', '/*']]:
continue
if True in [ttt in line for ttt in self.test_trigger_txt_list]:
#print('Test wildcard in line: %s' % (line))
testname = line.split(',')[-1]
testname = testname.strip().strip(' {)')
#print('Inserting test: "%s"' % (testname))
if ('' == testname):
grab_next_line = True
continue
self.tests_set.add(testname)
if grab_next_line: # test name on its own line
grab_next_line = False
testname = testname.strip().strip(' {)')
self.tests_set.add(testname)
def main(argv=None):
# parse db
val_db = ValidationDatabase()
val_db.read()
# parse header
val_header = ValidationHeader()
val_header.read()
# Create parser for layer files
val_source = ValidationSource(layer_source_files)
val_source.parse()
# Parse test files
test_parser = TestParser([test_file, ])
test_parser.parse()
# Process stats - Just doing this inline in main, could make a fancy class to handle
# all the processing of data and then get results from that
txt_color = bcolors()
print("Validation Statistics")
# First give number of checks in db & header and report any discrepancies
db_enums = len(val_db.db_dict.keys())
hdr_enums = len(val_header.enums)
print(" Database file includes %d unique checks" % (db_enums))
print(" Header file declares %d unique checks" % (hdr_enums))
tmp_db_dict = val_db.db_dict
db_missing = []
for enum in val_header.enums:
if not tmp_db_dict.pop(enum, False):
db_missing.append(enum)
if db_enums == hdr_enums and len(db_missing) == 0 and len(tmp_db_dict.keys()) == 0:
print(txt_color.green() + " Database and Header match, GREAT!" + txt_color.endc())
else:
print(txt_color.red() + " Uh oh, Database doesn't match Header :(" + txt_color.endc())
if len(db_missing) != 0:
print(txt_color.red() + " The following checks are in header but missing from database:" + txt_color.endc())
for missing_enum in db_missing:
print(txt_color.red() + " %s" % (missing_enum) + txt_color.endc())
if len(tmp_db_dict.keys()) != 0:
print(txt_color.red() + " The following checks are in database but haven't been declared in the header:" + txt_color.endc())
for extra_enum in tmp_db_dict:
print(txt_color.red() + " %s" % (extra_enum) + txt_color.endc())
# Report out claimed implemented checks vs. found actual implemented checks
imp_not_found = [] # Checks claimed to implemented in DB file but no source found
imp_not_claimed = [] # Checks found implemented but not claimed to be in DB
multiple_uses = False # Flag if any enums are used multiple times
for db_imp in val_db.db_implemented_enums:
if db_imp not in val_source.enum_count_dict:
imp_not_found.append(db_imp)
for src_enum in val_source.enum_count_dict:
if val_source.enum_count_dict[src_enum] > 1:
multiple_uses = True
if src_enum not in val_db.db_implemented_enums:
imp_not_claimed.append(src_enum)
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)))
if len(imp_not_found) == 0 and len(imp_not_claimed) == 0:
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())
else:
print(txt_color.red() + " Uh oh, Database claimed implemented don't match Source :(" + txt_color.endc())
if len(imp_not_found) != 0:
print(txt_color.red() + " The following checks are claimed to be implemented in Database, but weren't found in source:" + txt_color.endc())
for not_imp_enum in imp_not_found:
print(txt_color.red() + " %s" % (not_imp_enum) + txt_color.endc())
if len(imp_not_claimed) != 0:
print(txt_color.red() + " The following checks are implemented in source, but not claimed to be in Database:" + txt_color.endc())
for imp_enum in imp_not_claimed:
print(txt_color.red() + " %s" % (imp_enum) + txt_color.endc())
if multiple_uses:
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())
print(txt_color.yellow() + " Here is a list of each check used multiple times with its number of uses:" + txt_color.endc())
for enum in val_source.enum_count_dict:
if val_source.enum_count_dict[enum] > 1:
print(txt_color.yellow() + " %s: %d" % (enum, val_source.enum_count_dict[enum]) + txt_color.endc())
# Now check that tests claimed to be implemented are actual test names
bad_testnames = []
for enum in val_db.db_enum_to_tests:
for testname in val_db.db_enum_to_tests[enum]:
if testname not in test_parser.tests_set:
bad_testnames.append(testname)
print(" Database file claims that %d checks have tests written." % len(val_db.db_enum_to_tests))
if len(bad_testnames) == 0:
print(txt_color.green() + " All claimed tests have valid names. That's good!" + txt_color.endc())
else:
print(txt_color.red() + " The following testnames in Database appear to be invalid:")
for bt in bad_testnames:
print(txt_color.red() + " %s" % (bt))
return 0
if __name__ == "__main__":
sys.exit(main())