#!/usr/bin/env python3 ############################################################################### # # MIT License # # Copyright (c) 2024 Advanced Micro Devices, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # ################################################################################# """gtest name linter""" import os import re import sys import argparse from collections import defaultdict import logging logging.basicConfig(level=logging.WARNING) logger = logging.getLogger("GTest name checker") """regexp based on https://github.com/ROCm/MIOpen/wiki/GTest-development#naming""" re_prefix = re.compile(r"^((Smoke.*)|(Standard.*)|(Full.*)|(Perf.*)|(Unit.*))$") re_hw = re.compile(r"^((CPU)|(GPU))$") re_datatype = re.compile( r"^((FP((8)|(16)|(32)|(64)))|(TF(32))|(BFP((8)|(16)))|(I((8)|(16)|(32)|(64)))|(NONE))\.?$" ) def parse_args(): """Function to parse cmd line arguments""" parser = argparse.ArgumentParser() parser.add_argument( "--list", dest="list", type=str, required=True, help="Specify gtest test list file", ) args = parser.parse_args() return args def check_naming_schema(args): mismatches = defaultdict(str) with open(args.list) as fp: for line in fp.readlines()[2:]: if not line.strip(): continue if line[0] == " ": continue line = line.split("#")[0].strip() full_name = line.split("/") if len(full_name) == 2: prefix = re.search(re_prefix, full_name[0]) name = full_name[1].split("_") # Try to recognize pattern like HW_NAME_TYPE/. This pattern is used in case of TYPED_TEST_SUITE if not prefix and full_name[1].replace(".", "").isnumeric(): name = full_name[0].split("_") prefix = ["empty"] else: prefix = ["empty"] name = full_name[0].split("_") hw = re.search(re_hw, name[0]) datatype = re.search(re_datatype, name[-1]) if not prefix: mismatches[line] += " Prefix" if not hw: mismatches[line] += " Hw" if not datatype: mismatches[line] += " Datatype" if hw and hw.group() == "GPU" and datatype and ("NONE" in datatype.group()): mismatches[line] += " Hw and Datatype combination (GPU+NONE)" for l, k in mismatches.items(): logger.warning("Name: " + l + " Mismatch types:" + k) if mismatches: logger.critical( "Tests do not match to the test naming scheme (see https://github.com/ROCm/MIOpen/wiki/GTest-development#naming )" ) return -1 # uncomment when all the tests will be renamed return 0 # This function makes sure that we don't have explicit name conflicts in gtest folder # For example if you have GPU_SomeTestName_FP32 in file1.cpp and same GPU_SomeTestName_FP32 in file2.cpp # in gtest folder, there will be a naming conflict when both files are combined into one single test binary miopen_gtest # If such a situation is detected we should force a developer to make proper unique naming for the tests in PR. # This script should be located in gtest folder def check_names_uniqueness(): dir_path = os.path.dirname(os.path.realpath(__file__)) files = os.listdir(dir_path) test_regexp = re.compile(r"^\s*(TEST)|(TEST_P)|(TEST_F)|(TYPED_TEST)\(.*,.*") occurences = {} error_count = 0 files_count = 0 cases_count = 0 for file_name_raw in files: file_name = os.path.join(dir_path, file_name_raw) if not os.path.isfile(file_name): continue if not file_name.endswith(".cpp"): continue files_count += 1 with open(file_name) as f: for line in f: if re.match(test_regexp, line): m = re.search("\((.*),", line) if m is None: continue test_class_name = m.group(1).strip(" \t") if not test_class_name in occurences: occurences[test_class_name] = set() occurences[test_class_name].add(file_name_raw) for key in occurences.keys(): if len(occurences[key]) > 1: print( "ERROR: test name " + key + " is used in multiple files: " + str(occurences[key]) ) error_count += 1 cases_count += len(occurences[key]) print( "Gtest folder test class names uniqueness check, total cpp-files checked: " + str(files_count) + ", total errors = " + str(error_count) + ". Total files with duplicates = " + str(cases_count) ) if error_count > 0: return -1 return 0 def main(): """Main function""" args = parse_args() naming_check_result = check_naming_schema(args) if naming_check_result < 0: return naming_check_result return check_names_uniqueness() if __name__ == "__main__": sys.exit(main())