xref: /spdk/autorun_post.py (revision 7e64a4c6ead7884c4a68b89e4d9546f33eb65105)
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