• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Alan-Jowett / bpf_conformance / 21533638983

30 Jan 2026 11:00PM UTC coverage: 95.198% (+0.2%) from 94.976%
21533638983

Pull #451

github

web-flow
Merge 166724862 into 484e6b32c
Pull Request #451: test: Add negative tests for unused instruction fields

1487 of 1562 relevant lines covered (95.2%)

12341.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.07
/src/runner.cc
1
// Copyright (c) Microsoft Corporation
2
// SPDX-License-Identifier: MIT
3

4
#include <filesystem>
5
#include <iostream>
6
#include <sstream>
7
#include <boost/program_options.hpp>
8

9
#include "../include/bpf_conformance.h"
10

11
#if defined(__linux__)
12
#include <signal.h>
13

14
static void
15
sig_handler(int signo)
×
16
{
17
    if (signo != SIGPIPE) {
×
18
        std::cerr << "Received signal " << signo << std::endl;
×
19
        exit(1);
×
20
    }
21
}
×
22

23
static void
24
install_sigpipe_handler()
76✔
25
{
26
    struct sigaction sa;
27
    sa.sa_handler = sig_handler;
76✔
28
    sigemptyset(&sa.sa_mask);
76✔
29
    sa.sa_flags = 0;
76✔
30
    sigaction(SIGPIPE, &sa, NULL);
76✔
31
}
76✔
32
#else
33
static void
34
install_sigpipe_handler()
35
{
36
}
37
#endif
38

39
// This program reads a collection of BPF test programs from the test folder,
40
// assembles the BPF programs to byte code, calls the plugin to execute the
41
// BPF programs, and compares the results with the expected results.
42
// If the test program has memory contents, the program will also pass the
43
// memory contents to the plugin.
44

45
/**
46
 * @brief Read the list of test files from the provided directory.
47
 *
48
 * @param[in] test_file_directory Path to the collection of test files.
49
 * @return Vector of test files names.
50
 */
51
static std::vector<std::filesystem::path>
52
_get_test_files(const std::filesystem::path& test_file_directory)
16✔
53
{
54
    std::vector<std::filesystem::path> result;
8✔
55
    for (auto& p : std::filesystem::directory_iterator(test_file_directory)) {
6,609✔
56
        if (p.path().extension() == ".data") {
6,573✔
57
            result.push_back(p.path());
4,382✔
58
        }
59
    }
7✔
60
    return result;
14✔
61
}
2✔
62

63
static const std::map<std::string, bpf_conformance_groups_t> _conformance_groups = {
64
    {"atomic32", bpf_conformance_groups_t::atomic32},
65
    {"atomic64", bpf_conformance_groups_t::atomic32 | bpf_conformance_groups_t::atomic64},
66
    {"base32", bpf_conformance_groups_t::base32},
67
    {"base64", bpf_conformance_groups_t::base32 | bpf_conformance_groups_t::base64},
68
    {"callx", bpf_conformance_groups_t::callx},
69
    {"divmul32", bpf_conformance_groups_t::divmul32},
70
    {"divmul64", bpf_conformance_groups_t::divmul32 | bpf_conformance_groups_t::divmul64},
71
    {"packet", bpf_conformance_groups_t::packet}};
72

73
static std::optional<bpf_conformance_groups_t>
74
_get_conformance_group_by_name(std::string group)
12✔
75
{
76
    if (!_conformance_groups.contains(group)) {
12✔
77
        return {};
×
78
    }
79
    return _conformance_groups.find(group)->second;
12✔
80
}
81

82
static std::string
83
_get_conformance_group_names()
76✔
84
{
85
    std::string result;
38✔
86
    for (const auto& entry : _conformance_groups) {
684✔
87
        if (!result.empty()) {
608✔
88
            result += ", ";
266✔
89
        }
90
        result += entry.first;
304✔
91
    }
92
    return result;
76✔
93
}
94

95
int
96
main(int argc, char** argv)
76✔
97
{
98
    // Handle sigpipe handler to avoid crashing when writing to a closed pipe.
99
    install_sigpipe_handler();
76✔
100

101
    try {
102
        boost::program_options::options_description desc("Options");
114✔
103
        desc.add_options()("help", "Print help messages")(
114✔
104
            "test_file_path", boost::program_options::value<std::string>(), "Path to test file")(
76✔
105
            "test_file_directory", boost::program_options::value<std::string>(), "Path to test file directory")(
76✔
106
            "plugin_path", boost::program_options::value<std::string>(), "Path to plugin")(
76✔
107
            "plugin_options", boost::program_options::value<std::string>(), "Options to pass to plugin")(
76✔
108
            "list_instructions", boost::program_options::value<bool>(), "List instructions used and not used in tests")(
76✔
109
            "list_used_instructions", boost::program_options::value<bool>(), "List instructions used in tests")(
76✔
110
            "list_unused_instructions", boost::program_options::value<bool>(), "List instructions not used in tests")(
76✔
111
            "debug", boost::program_options::value<bool>(), "Print debug information")(
76✔
112
            "xdp_prolog", boost::program_options::value<bool>(), "XDP prolog")(
76✔
113
            "elf", boost::program_options::value<bool>(), "ELF format")(
76✔
114
            "cpu_version", boost::program_options::value<std::string>(), "CPU version (valid values: v1, v2, v3, v4), default is v3")(
152✔
115
            "include_groups",
116
            boost::program_options::value<std::vector<std::string>>()->multitoken(),
38✔
117
            ("Include conformance groups (valid group names: " + _get_conformance_group_names() + ")").c_str())(
228✔
118
            "exclude_groups",
119
            boost::program_options::value<std::vector<std::string>>()->multitoken(),
38✔
120
            "Exclude conformance groups, where callx and packet are excluded by default")(
114✔
121
            "include_regex", boost::program_options::value<std::string>(), "Include regex")(
76✔
122
            "exclude_regex", boost::program_options::value<std::string>(), "Exclude regex");
38✔
123

124
        boost::program_options::variables_map vm;
76✔
125
        boost::program_options::store(boost::program_options::parse_command_line(argc, argv, desc), vm);
114✔
126
        boost::program_options::notify(vm);
76✔
127

128
        if (vm.count("help")) {
114✔
129
            std::cout << "Usage: bpf_conformance_runner [options]" << std::endl;
1✔
130
            std::cout << std::endl << desc;
2✔
131
            std::cout << R"(
2✔
132
Examples:
133
  bpf_conformance_runner --test_file_directory ./tests --plugin_path ./my_plugin --exclude_groups atomic64
134
    Run all tests in the ./tests directory, allowing instructions in cpu version 3
135
    but without the atomic64 (or atomic32 which is a subset of atomic64), callx, or packet
136
    conformance groups.
137

138
  bpf_conformance_runner --test_file_directory ./tests --plugin_path ./my_plugin --cpu_version v2 --include_groups callx atomic64 --exclude_groups atomic32
139
    Run all tests in the ./tests directory, allowing instructions in cpu version 2
140
    plus the callx and atomic64 conformance groups, except for those in atomic32 or packet.
141

142
  bpf_conformance_runner --test_file_directory ./tests --plugin_path ./my_plugin --exclude_regex "lock+"
143
    Run all tests in the ./tests directory, allowing instructions in cpu version 3
144
    but without the callx or packet conformance group, skipping any tests whose
145
    filenames contain "loc" followed by one or more "k"s.
146
)";
147
            return 1;
1✔
148
        }
149

150
        if ((vm.count("test_file_path") == 0) && (vm.count("test_file_directory") == 0)) {
129✔
151
            std::cout << "test_file_path or test_file_directory is required" << std::endl;
1✔
152
            return 1;
1✔
153
        }
154

155
        if (!vm.count("plugin_path")) {
108✔
156
            std::cout << "plugin_path is required" << std::endl;
1✔
157
            return 1;
1✔
158
        }
159

160
        std::string plugin_path = vm["plugin_path"].as<std::string>();
105✔
161
        std::stringstream plugin_options_stream(
162
            vm.count("plugin_options") ? vm["plugin_options"].as<std::string>() : "");
299✔
163

164
        std::vector<std::string> plugin_options;
35✔
165
        std::string option;
35✔
166
        while (std::getline(plugin_options_stream, option, ' ')) {
124✔
167
            plugin_options.push_back(option);
54✔
168
        }
169

170
        // Assume version 3 if not specified.
171
        bpf_conformance_test_cpu_version_t cpu_version = bpf_conformance_test_cpu_version_t::v3;
35✔
172
        if (vm.count("cpu_version")) {
105✔
173
            std::string cpu_version_string = vm["cpu_version"].as<std::string>();
40✔
174
            if (cpu_version_string == "v1") {
20✔
175
                cpu_version = bpf_conformance_test_cpu_version_t::v1;
4✔
176
            } else if (cpu_version_string == "v2") {
12✔
177
                cpu_version = bpf_conformance_test_cpu_version_t::v2;
1✔
178
            } else if (cpu_version_string == "v3") {
10✔
179
                cpu_version = bpf_conformance_test_cpu_version_t::v3;
3✔
180
            } else if (cpu_version_string == "v4") {
4✔
181
                cpu_version = bpf_conformance_test_cpu_version_t::v4;
1✔
182
            } else {
183
                std::cout << "Invalid CPU version" << std::endl;
1✔
184
                return 1;
1✔
185
            }
186
        }
10✔
187

188
        // Enable default conformance groups, which don't include callx or packet.
189
        bpf_conformance_groups_t groups = bpf_conformance_groups_t::default_groups;
34✔
190
        if (vm.count("include_groups")) {
102✔
191
            auto include_groups = vm["include_groups"].as<std::vector<std::string>>();
×
192
            for (std::string group_name : include_groups) {
×
193
                if (auto group = _get_conformance_group_by_name(group_name)) {
×
194
                    groups |= *group;
×
195
                } else {
196
                    std::cout << "Invalid group: " << group_name << std::endl;
197
                    return 1;
×
198
                }
199
            }
200
        }
×
201
        if (vm.count("exclude_groups")) {
102✔
202
            auto exclude_groups = vm["exclude_groups"].as<std::vector<std::string>>();
30✔
203
            for (std::string group_name : exclude_groups) {
24✔
204
                if (auto group = _get_conformance_group_by_name(group_name)) {
12✔
205
                    groups &= ~(*group);
12✔
206
                } else {
207
                    std::cout << "Invalid group: " << group_name << std::endl;
208
                    return 1;
×
209
                }
210
            }
6✔
211
        }
12✔
212

213
        std::optional<std::string> include_regex = vm.count("include_regex") ? std::make_optional(vm["include_regex"].as<std::string>()) : std::nullopt;
119✔
214
        std::optional<std::string> exclude_regex = vm.count("exclude_regex") ? std::make_optional(vm["exclude_regex"].as<std::string>()) : std::nullopt;
140✔
215

216
        std::vector<std::filesystem::path> tests;
34✔
217
        if (vm.count("test_file_path")) {
102✔
218
            tests.push_back(vm["test_file_path"].as<std::string>());
104✔
219
        } else if (vm.count("test_file_directory")) {
24✔
220
            tests = _get_test_files(vm["test_file_directory"].as<std::string>());
35✔
221
        }
222
        std::sort(tests.begin(), tests.end());
66✔
223

224
        size_t tests_passed = 0;
33✔
225
        size_t tests_run = 0;
33✔
226
        bool show_instructions = vm.count("list_instructions") ? vm["list_instructions"].as<bool>() : false;
105✔
227
        bool debug = vm.count("debug") ? vm["debug"].as<bool>() : false;
151✔
228
        bool list_used_instructions = vm.count("list_used_instructions") ? vm["list_used_instructions"].as<bool>() : false;
101✔
229
        bool list_unused_instructions = vm.count("list_unused_instructions") ? vm["list_unused_instructions"].as<bool>() : false;
101✔
230
        bool xdp_prolog = vm.count("xdp_prolog") ? vm["xdp_prolog"].as<bool>() : false;
151✔
231
        bool elf_format = vm.count("elf") ? vm["elf"].as<bool>() : false;
101✔
232
        bpf_conformance_list_instructions_t list_instructions = bpf_conformance_list_instructions_t::LIST_INSTRUCTIONS_NONE;
33✔
233
        if (show_instructions) {
66✔
234
            list_instructions = bpf_conformance_list_instructions_t::LIST_INSTRUCTIONS_ALL;
3✔
235
        } else if (list_used_instructions) {
60✔
236
            list_instructions = bpf_conformance_list_instructions_t::LIST_INSTRUCTIONS_USED;
1✔
237
        } else if (list_unused_instructions) {
58✔
238
            list_instructions = bpf_conformance_list_instructions_t::LIST_INSTRUCTIONS_UNUSED;
1✔
239
        }
240

241
        bpf_conformance_options_t options;
33✔
242
        options.include_test_regex = include_regex;
33✔
243
        options.exclude_test_regex = exclude_regex;
33✔
244
        options.cpu_version = cpu_version;
66✔
245
        options.groups = groups;
66✔
246
        options.list_instructions_option = list_instructions;
66✔
247
        options.debug = debug;
66✔
248
        options.xdp_prolog = xdp_prolog;
66✔
249
        options.elf_format = elf_format;
66✔
250

251
        std::map<std::filesystem::path, std::tuple<bpf_conformance_test_result_t, std::string>> test_results;
33✔
252
        test_results = bpf_conformance_options(tests, plugin_path, plugin_options, options);
109✔
253

254
        // At the end of all the tests, print a summary of the results.
255
        std::cout << "Test results:" << std::endl;
23✔
256
        for (auto& test : test_results) {
4,460✔
257
            auto [result, message] = test.second;
2,207✔
258
            switch (result) {
4,414✔
259
            case bpf_conformance_test_result_t::TEST_RESULT_PASS:
1,328✔
260
                std::cout << "PASS: " << test.first << std::endl;
2,656✔
261
                tests_passed++;
2,656✔
262
                tests_run++;
2,656✔
263
                break;
2,656✔
264
            case bpf_conformance_test_result_t::TEST_RESULT_FAIL:
4✔
265
                std::cout << "FAIL: " << test.first << " " << message << std::endl;
8✔
266
                tests_run++;
8✔
267
                break;
8✔
268
            case bpf_conformance_test_result_t::TEST_RESULT_ERROR:
29✔
269
                std::cout << "ERROR: " << test.first << " " << message << std::endl;
58✔
270
                tests_run++;
58✔
271
                break;
58✔
272
            case bpf_conformance_test_result_t::TEST_RESULT_SKIP:
846✔
273
                std::cout << "SKIP: " << test.first << " " << message << std::endl;
1,692✔
274
                break;
846✔
275
            case bpf_conformance_test_result_t::TEST_RESULT_UNKNOWN:
276
                std::cout << "UNKNOWN: " << test.first << " " << message << std::endl;
×
277
                break;
278
            }
279
        }
2,207✔
280

281
        std::cout << "Passed " << tests_passed << " out of " << tests_run << " tests." << std::endl;
23✔
282

283
        return tests_passed == tests_run ? 0 : 1;
46✔
284
    } catch (std::filesystem::filesystem_error& e) {
297✔
285
        std::cerr << "Error reading test files: " << e.what() << std::endl;
2✔
286
        return 2;
1✔
287
    } catch (std::exception& e) {
22✔
288
        std::cerr << "Unhandled Exception reached the top of main: " << e.what() << ", application will now exit"
20✔
289
                  << std::endl;
10✔
290
        return 2;
10✔
291
    } catch (...) {
20✔
292
        std::cerr << "Unhandled Exception reached the top of main: "
293
                  << ", application will now exit" << std::endl;
294
        return 2;
295
    }
×
296
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc