1a64e3767SSeth Howell#!/usr/bin/python3 2588dfe31SMichal Berger# SPDX-License-Identifier: BSD-3-Clause 3588dfe31SMichal Berger# Copyright (C) 2017 Intel Corporation. 4588dfe31SMichal Berger# All rights reserved. 5588dfe31SMichal Berger 6a64e3767SSeth Howell 79aa4bc70SDaniel Verkampimport shutil 8a64e3767SSeth Howellimport subprocess 9a64e3767SSeth Howellimport argparse 1093b572f2SMichal Bergerimport itertools 11a64e3767SSeth Howellimport os 1204b16168SPawel Piatekimport sys 13a64e3767SSeth Howellimport glob 148a9cfbb6SSeth Howellimport re 15df531e7fSSeth Howellimport pandas as pd 16df531e7fSSeth Howell 17df531e7fSSeth Howell 18087b6cb6SPawel Piatekdef generateTestCompletionTableByTest(output_dir, data_table): 19087b6cb6SPawel Piatek columns_to_group = ['Domain', 'Test', 'Agent'] 20087b6cb6SPawel Piatek 21087b6cb6SPawel Piatek total_tests_number = len(data_table.groupby('Test')) 22087b6cb6SPawel Piatek 23087b6cb6SPawel Piatek has_agent = data_table['Agent'] != 'None' 24087b6cb6SPawel Piatek data_table_with_agent = data_table[has_agent] 25087b6cb6SPawel Piatek executed_tests = len(data_table_with_agent.groupby('Test')) 26087b6cb6SPawel Piatek tests_executions = len(data_table_with_agent.groupby(columns_to_group)) 27087b6cb6SPawel Piatek 28*7e64a4c6SKarol Latecki pivot_by_test = pd.pivot_table(data_table, index=columns_to_group, aggfunc=any) 29087b6cb6SPawel Piatek 30087b6cb6SPawel Piatek output_file = os.path.join(output_dir, 'post_process', 'completions_table_by_test.html') 31087b6cb6SPawel Piatek with open(output_file, 'w') as f: 32087b6cb6SPawel Piatek table_row = '<tr><td>{}</td><td>{}</td>\n' 33087b6cb6SPawel Piatek f.write('<table>\n') 34087b6cb6SPawel Piatek f.write(table_row.format('Total number of tests', total_tests_number)) 35087b6cb6SPawel Piatek f.write(table_row.format('Tests executed', executed_tests)) 36087b6cb6SPawel Piatek f.write(table_row.format('Number of test executions', tests_executions)) 37087b6cb6SPawel Piatek f.write('</table>\n') 38087b6cb6SPawel Piatek f.write(pivot_by_test.to_html(None)) 39087b6cb6SPawel Piatek 40087b6cb6SPawel Piatek 41df531e7fSSeth Howelldef generateTestCompletionTables(output_dir, completion_table): 42ea781d6dSSeth Howell data_table = pd.DataFrame(completion_table, columns=["Agent", "Domain", "Test", "With Asan", "With UBsan"]) 43df531e7fSSeth Howell data_table.to_html(os.path.join(output_dir, 'completions_table.html')) 441791ccfdSSeth Howell os.makedirs(os.path.join(output_dir, "post_process"), exist_ok=True) 45df531e7fSSeth Howell 46*7e64a4c6SKarol Latecki pivot_by_agent = pd.pivot_table(data_table, index=["Agent", "Domain", "Test"], aggfunc=any) 471791ccfdSSeth Howell pivot_by_agent.to_html(os.path.join(output_dir, "post_process", 'completions_table_by_agent.html')) 48087b6cb6SPawel Piatek 49087b6cb6SPawel Piatek generateTestCompletionTableByTest(output_dir, data_table) 50087b6cb6SPawel Piatek 5129eb8b7eSPawel Piatek pivot_by_asan = pd.pivot_table(data_table, index=["Domain", "Test"], values=["With Asan"], aggfunc=any) 521791ccfdSSeth Howell pivot_by_asan.to_html(os.path.join(output_dir, "post_process", 'completions_table_by_asan.html')) 5329eb8b7eSPawel Piatek pivot_by_ubsan = pd.pivot_table(data_table, index=["Domain", "Test"], values=["With UBsan"], aggfunc=any) 541791ccfdSSeth Howell pivot_by_ubsan.to_html(os.path.join(output_dir, "post_process", 'completions_table_by_ubsan.html')) 55a64e3767SSeth Howell 56dc6f9571SDaniel Verkamp 579aa4bc70SDaniel Verkampdef generateCoverageReport(output_dir, repo_dir): 58a64e3767SSeth Howell coveragePath = os.path.join(output_dir, '**', 'cov_total.info') 597f3e956cSKarol Latecki covfiles = [os.path.abspath(p) for p in glob.glob(coveragePath, recursive=True)] 60a64e3767SSeth Howell for f in covfiles: 61237ef4c6SSeth Howell print(f) 62a64e3767SSeth Howell if len(covfiles) == 0: 63a64e3767SSeth Howell return 64a64e3767SSeth Howell lcov_opts = [ 6593b572f2SMichal Berger '--rc', 'lcov_branch_coverage=1', 6693b572f2SMichal Berger '--rc', 'lcov_function_coverage=1', 6793b572f2SMichal Berger '--rc', 'genhtml_branch_coverage=1', 6893b572f2SMichal Berger '--rc', 'genhtml_function_coverage=1', 6993b572f2SMichal Berger '--rc', 'genhtml_legend=1', 7093b572f2SMichal Berger '--rc', 'geninfo_all_blocks=1', 71a64e3767SSeth Howell ] 7293b572f2SMichal Berger 7393b572f2SMichal Berger # HACK: This is a workaround for some odd CI assumptions 7493b572f2SMichal Berger details = '--show-details' 7593b572f2SMichal Berger 767f3e956cSKarol Latecki cov_total = os.path.abspath(os.path.join(output_dir, 'cov_total.info')) 77a64e3767SSeth Howell coverage = os.path.join(output_dir, 'coverage') 7893b572f2SMichal Berger lcov = ['lcov', *lcov_opts, '-q', *itertools.chain(*[('-a', f) for f in covfiles]), '-o', cov_total] 7993b572f2SMichal Berger genhtml = ['genhtml', *lcov_opts, '-q', cov_total, '--legend', '-t', 'Combined', *details.split(), '-o', coverage] 80a64e3767SSeth Howell try: 8193b572f2SMichal Berger subprocess.check_call(lcov) 82a64e3767SSeth Howell except subprocess.CalledProcessError as e: 83237ef4c6SSeth Howell print("lcov failed") 84237ef4c6SSeth Howell print(e) 85a64e3767SSeth Howell return 8604b16168SPawel Piatek 8704b16168SPawel Piatek with open(cov_total, 'r') as cov_total_file: 88a64e3767SSeth Howell file_contents = cov_total_file.readlines() 8904b16168SPawel Piatek 9004b16168SPawel Piatek replacement = "SF:" + repo_dir 91a64e3767SSeth Howell os.remove(cov_total) 92a64e3767SSeth Howell with open(cov_total, 'w+') as file: 93a64e3767SSeth Howell for Line in file_contents: 94a64e3767SSeth Howell Line = re.sub("^SF:.*/repo", replacement, Line) 95a64e3767SSeth Howell file.write(Line + '\n') 96a64e3767SSeth Howell try: 9793b572f2SMichal Berger subprocess.check_call(genhtml) 98a64e3767SSeth Howell except subprocess.CalledProcessError as e: 99237ef4c6SSeth Howell print("genhtml failed") 100237ef4c6SSeth Howell print(e) 101be69f0ffSDaniel Verkamp for f in covfiles: 102be69f0ffSDaniel Verkamp os.remove(f) 103a64e3767SSeth Howell 104a64e3767SSeth Howell 105bba169c1SDaniel Verkampdef collectOne(output_dir, dir_name): 106bba169c1SDaniel Verkamp dirs = glob.glob(os.path.join(output_dir, '*', dir_name)) 107bba169c1SDaniel Verkamp dirs.sort() 108bba169c1SDaniel Verkamp if len(dirs) == 0: 1099aa4bc70SDaniel Verkamp return 1109aa4bc70SDaniel Verkamp 111bba169c1SDaniel Verkamp # Collect first instance of dir_name and move it to the top level 112bba169c1SDaniel Verkamp collect_dir = dirs.pop(0) 113bba169c1SDaniel Verkamp shutil.move(collect_dir, os.path.join(output_dir, dir_name)) 114bba169c1SDaniel Verkamp 115bba169c1SDaniel Verkamp # Delete all other instances 116bba169c1SDaniel Verkamp for d in dirs: 117bba169c1SDaniel Verkamp shutil.rmtree(d) 1189aa4bc70SDaniel Verkamp 1199aa4bc70SDaniel Verkamp 1204e3104d0SSeth Howelldef getCompletions(completionFile, test_list, test_completion_table): 1214e3104d0SSeth Howell agent_name = os.path.basename(os.path.dirname(completionFile)) 1224e3104d0SSeth Howell with open(completionFile, 'r') as completionList: 1234e3104d0SSeth Howell completions = completionList.read() 124a562812dSSeth Howell 1254e3104d0SSeth Howell asan_enabled = "asan" in completions 1264e3104d0SSeth Howell ubsan_enabled = "ubsan" in completions 127a562812dSSeth Howell 1284e3104d0SSeth Howell for line in completions.splitlines(): 129a562812dSSeth Howell try: 130ea781d6dSSeth Howell domain, test_name = line.strip().split() 131ea781d6dSSeth Howell test_list[test_name] = (True, asan_enabled | test_list[test_name][1], ubsan_enabled | test_list[test_name][2]) 132ea781d6dSSeth Howell test_completion_table.append([agent_name, domain, test_name, asan_enabled, ubsan_enabled]) 133df531e7fSSeth Howell try: 134ea781d6dSSeth Howell test_completion_table.remove(["None", "None", test_name, False, False]) 135df531e7fSSeth Howell except ValueError: 136df531e7fSSeth Howell continue 137a562812dSSeth Howell except KeyError: 138a562812dSSeth Howell continue 139a562812dSSeth Howell 14083b019a5SDaniel Verkamp 1414e3104d0SSeth Howelldef printList(header, test_list, index, condition): 1424e3104d0SSeth Howell print("\n\n-----%s------" % header) 1434e3104d0SSeth Howell executed_tests = [x for x in sorted(test_list) if test_list[x][index] is condition] 1444e3104d0SSeth Howell print(*executed_tests, sep="\n") 145a562812dSSeth Howell 146a562812dSSeth Howell 1474e3104d0SSeth Howelldef printListInformation(table_type, test_list): 1484e3104d0SSeth Howell printList("%s Executed in Build" % table_type, test_list, 0, True) 1494e3104d0SSeth Howell printList("%s Missing From Build" % table_type, test_list, 0, False) 1504e3104d0SSeth Howell printList("%s Missing ASAN" % table_type, test_list, 1, False) 1514e3104d0SSeth Howell printList("%s Missing UBSAN" % table_type, test_list, 2, False) 152d798ac57SKarol Latecki 153a562812dSSeth Howell 154df254d5aSSeth Howelldef getSkippedTests(repo_dir): 155df254d5aSSeth Howell skipped_test_file = os.path.join(repo_dir, "test", "common", "skipped_tests.txt") 156df254d5aSSeth Howell if not os.path.exists(skipped_test_file): 157df254d5aSSeth Howell return [] 15804b16168SPawel Piatek 159df254d5aSSeth Howell with open(skipped_test_file, "r") as skipped_test_data: 160df254d5aSSeth Howell return [x.strip() for x in skipped_test_data.readlines() if "#" not in x and x.strip() != ''] 161df254d5aSSeth Howell 162df254d5aSSeth Howell 163bde783d4SSeth Howelldef confirmPerPatchTests(test_list, skiplist): 164bde783d4SSeth Howell missing_tests = [x for x in sorted(test_list) if test_list[x][0] is False 165bde783d4SSeth Howell and x not in skiplist] 166bde783d4SSeth Howell if len(missing_tests) > 0: 167bde783d4SSeth Howell print("Not all tests were run. Failing the build.") 168bde783d4SSeth Howell print(missing_tests) 16904b16168SPawel Piatek sys.exit(1) 170bde783d4SSeth Howell 171bde783d4SSeth Howell 172ac26fec9SKarol Lateckidef aggregateCompletedTests(output_dir, repo_dir, skip_confirm=False): 1734e3104d0SSeth Howell test_list = {} 1744e3104d0SSeth Howell test_completion_table = [] 1754e3104d0SSeth Howell 1764e3104d0SSeth Howell testFiles = glob.glob(os.path.join(output_dir, '**', 'all_tests.txt'), recursive=True) 1774e3104d0SSeth Howell completionFiles = glob.glob(os.path.join(output_dir, '**', 'test_completions.txt'), recursive=True) 1784e3104d0SSeth Howell 1794e3104d0SSeth Howell if len(testFiles) == 0: 1804e3104d0SSeth Howell print("Unable to perform test completion aggregator. No input files.") 1814e3104d0SSeth Howell return 0 1824e3104d0SSeth Howell 1834e3104d0SSeth Howell with open(testFiles[0], 'r') as raw_test_list: 1844e3104d0SSeth Howell for line in raw_test_list: 185ea781d6dSSeth Howell try: 186ea781d6dSSeth Howell test_name = line.strip() 187ea781d6dSSeth Howell except Exception: 188ea781d6dSSeth Howell print("Failed to parse a test type.") 189ea781d6dSSeth Howell return 1 190ea781d6dSSeth Howell 191ea781d6dSSeth Howell test_list[test_name] = (False, False, False) 192ea781d6dSSeth Howell test_completion_table.append(["None", "None", test_name, False, False]) 1934e3104d0SSeth Howell 1944e3104d0SSeth Howell for completionFile in completionFiles: 1954e3104d0SSeth Howell getCompletions(completionFile, test_list, test_completion_table) 1964e3104d0SSeth Howell 1974e3104d0SSeth Howell printListInformation("Tests", test_list) 198df531e7fSSeth Howell generateTestCompletionTables(output_dir, test_completion_table) 199df254d5aSSeth Howell skipped_tests = getSkippedTests(repo_dir) 200ac26fec9SKarol Latecki if not skip_confirm: 201bde783d4SSeth Howell confirmPerPatchTests(test_list, skipped_tests) 202df531e7fSSeth Howell 20304b16168SPawel Piatek return 0 20404b16168SPawel Piatek 205a562812dSSeth Howell 206ac26fec9SKarol Lateckidef main(output_dir, repo_dir, skip_confirm=False): 207d5eb5835SSeth Howell print("-----Begin Post Process Script------") 2089aa4bc70SDaniel Verkamp generateCoverageReport(output_dir, repo_dir) 209bba169c1SDaniel Verkamp collectOne(output_dir, 'doc') 2106cf871d4SDaniel Verkamp collectOne(output_dir, 'ut_coverage') 211ac26fec9SKarol Latecki aggregateCompletedTests(output_dir, repo_dir, skip_confirm) 2129aa4bc70SDaniel Verkamp 2139aa4bc70SDaniel Verkamp 214a64e3767SSeth Howellif __name__ == "__main__": 215a64e3767SSeth Howell parser = argparse.ArgumentParser(description="SPDK Coverage Processor") 216a64e3767SSeth Howell parser.add_argument("-d", "--directory_location", type=str, required=True, 217a64e3767SSeth Howell help="The location of your build's output directory") 218a64e3767SSeth Howell parser.add_argument("-r", "--repo_directory", type=str, required=True, 219a64e3767SSeth Howell help="The location of your spdk repository") 220ac26fec9SKarol Latecki parser.add_argument("-s", "--skip_confirm", required=False, action="store_true", 221ac26fec9SKarol Latecki help="Do not check if all autotest.sh tests were executed.") 222a64e3767SSeth Howell args = parser.parse_args() 223ac26fec9SKarol Latecki main(args.directory_location, args.repo_directory, args.skip_confirm) 224