Skip to content

Commit 6877eda

Browse files
authored
Add a test script to generate many Protobuf configuration files. (#16)
* run_test_batch script attempt for 1 * add options & fixes * add options for test script * fix lint * fix lint isort
1 parent 8d03208 commit 6877eda

File tree

4 files changed

+231
-8
lines changed

4 files changed

+231
-8
lines changed

options.cpp

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,40 @@ const char *RtSmithOptions::getIncludePath() {
1616
}
1717

1818
RtSmithOptions::RtSmithOptions()
19-
: AbstractP4cToolOptions("Remove control-plane dead code from a P4 program.") {}
19+
: AbstractP4cToolOptions("Remove control-plane dead code from a P4 program.") {
20+
registerOption(
21+
"--print-to-stdout", nullptr,
22+
[this](const char *) {
23+
printToStdout_ = true;
24+
return true;
25+
},
26+
"Whether to write the generated config to a file or to stdout.");
27+
28+
registerOption(
29+
"--output-dir", "outputDir",
30+
[this](const char *arg) {
31+
outputDir_ = std::filesystem::path(arg);
32+
return true;
33+
},
34+
"The path to the output file of the config file(s).");
35+
36+
registerOption(
37+
"--generate-config", "filePath",
38+
[this](const char *arg) {
39+
configFilePath = arg;
40+
if (configFilePath.value().extension() != ".txtpb") {
41+
::error("%1% must have a .txtpb extension.", configFilePath.value().c_str());
42+
return false;
43+
}
44+
return true;
45+
},
46+
"Write the generated config to the specified .txtpb file.");
47+
}
48+
49+
std::optional<std::filesystem::path> RtSmithOptions::getOutputDir() const { return outputDir_; }
50+
51+
bool RtSmithOptions::printToStdout() const { return printToStdout_; }
52+
53+
std::optional<std::string> RtSmithOptions::getConfigFilePath() const { return configFilePath; }
2054

2155
} // namespace P4Tools

options.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#ifndef BACKENDS_P4TOOLS_MODULES_P4RTSMITH_OPTIONS_H_
22
#define BACKENDS_P4TOOLS_MODULES_P4RTSMITH_OPTIONS_H_
33

4+
#include <filesystem>
5+
#include <optional>
6+
47
#include "backends/p4tools/common/options.h"
58

69
namespace P4Tools {
@@ -23,8 +26,26 @@ class RtSmithOptions : public AbstractP4cToolOptions {
2326

2427
const char *getIncludePath() override;
2528

29+
/// @returns true when the --print-to-stdout option has been set.
30+
[[nodiscard]] bool printToStdout() const;
31+
32+
/// @returns the path set with --output-dir.
33+
[[nodiscard]] std::optional<std::filesystem::path> getOutputDir() const;
34+
35+
/// @returns the path set with --generate-config.
36+
[[nodiscard]] std::optional<std::string> getConfigFilePath() const;
37+
2638
private:
2739
RtSmithOptions();
40+
41+
// Write the generated config to the specified file.
42+
std::optional<std::filesystem::path> configFilePath = std::nullopt;
43+
44+
/// The path to the output file of the config file.
45+
std::optional<std::filesystem::path> outputDir_ = std::nullopt;
46+
47+
/// Whether to write the generated config to a file or to stdout.
48+
bool printToStdout_ = false;
2849
};
2950

3051
} // namespace P4Tools

rtsmith.cpp

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
#include <google/protobuf/text_format.h>
44

55
#include <cstdlib>
6+
#include <filesystem>
67

78
#include "backends/p4tools/common/compiler/context.h"
89
#include "backends/p4tools/common/lib/logging.h"
910
#include "backends/p4tools/modules/p4rtsmith/core/target.h"
1011
#include "backends/p4tools/modules/p4rtsmith/core/util.h"
1112
#include "backends/p4tools/modules/p4rtsmith/register.h"
13+
#include "control-plane/p4RuntimeSerializer.h"
1214
#include "lib/error.h"
15+
#include "lib/nullstream.h"
1316

1417
namespace P4Tools::RTSmith {
1518

@@ -40,17 +43,59 @@ int RtSmith::mainImpl(const CompilerResult &compilerResult) {
4043
printInfo("Inferred API:\n%1%", p4RuntimeApi.p4Info->DebugString());
4144

4245
auto &fuzzer = RtSmithTarget::getFuzzer(*programInfo);
46+
const auto &rtsmithOptions = RtSmithOptions::get();
4347

4448
auto initialConfig = fuzzer.produceInitialConfig();
45-
printInfo("Generated initial configuration:");
46-
for (const auto &writeRequest : initialConfig) {
47-
printInfo("%1%", writeRequest.DebugString());
49+
auto timeSeriesUpdates = fuzzer.produceUpdateTimeSeries();
50+
51+
if (rtsmithOptions.getConfigFilePath().has_value() &&
52+
rtsmithOptions.getOutputDir().has_value()) {
53+
auto dirPath = rtsmithOptions.getOutputDir().value();
54+
auto fileName = rtsmithOptions.getConfigFilePath().value();
55+
56+
if (!(std::filesystem::exists(dirPath) && std::filesystem::is_directory(dirPath))) {
57+
if (!std::filesystem::create_directory(dirPath)) {
58+
::error("P4RuntimeSmith: Failed to create output directory. Exiting");
59+
return EXIT_FAILURE;
60+
}
61+
}
62+
63+
auto fullFilePath = (dirPath / fileName).generic_string();
64+
auto *outputFile = openFile(fullFilePath, true);
65+
if (outputFile == nullptr) {
66+
::error("P4RuntimeSmith: Config file path doesn't exist. Exiting");
67+
return EXIT_FAILURE;
68+
}
69+
70+
for (const auto &writeRequest : initialConfig) {
71+
std::string output;
72+
google::protobuf::TextFormat::Printer textPrinter;
73+
textPrinter.SetExpandAny(true);
74+
if (!textPrinter.PrintToString(writeRequest, &output)) {
75+
::error(ErrorType::ERR_IO, "Failed to serialize protobuf message to text");
76+
return false;
77+
}
78+
79+
*outputFile << output;
80+
if (!outputFile->good()) {
81+
::error(ErrorType::ERR_IO, "Failed to write text protobuf message to the output");
82+
return false;
83+
}
84+
85+
outputFile->flush();
86+
}
4887
}
4988

50-
auto timeSeriesUpdates = fuzzer.produceUpdateTimeSeries();
51-
printInfo("Time series updates:");
52-
for (const auto &[time, writeRequest] : timeSeriesUpdates) {
53-
printInfo("Time %1%:\n%2%", writeRequest.DebugString());
89+
if (rtsmithOptions.printToStdout()) {
90+
printInfo("Generated initial configuration:");
91+
for (const auto &writeRequest : initialConfig) {
92+
printInfo("%1%", writeRequest.DebugString());
93+
}
94+
95+
printInfo("Time series updates:");
96+
for (const auto &[time, writeRequest] : timeSeriesUpdates) {
97+
printInfo("Time %1%:\n%2%", writeRequest.DebugString());
98+
}
5499
}
55100

56101
return ::errorCount() == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import argparse
2+
import logging
3+
import os
4+
import subprocess
5+
import sys
6+
import tempfile
7+
from pathlib import Path
8+
from typing import Any, Optional
9+
10+
PARSER = argparse.ArgumentParser()
11+
PARSER.add_argument(
12+
"-td",
13+
"--testdir",
14+
dest="testdir",
15+
help="The location of the test directory.",
16+
)
17+
PARSER.add_argument("p4_file", help="The absolute path for the p4 file to process.")
18+
PARSER.add_argument(
19+
"p4rtsmith",
20+
help="The absolute path for the p4rtsmith bin file for this test.",
21+
)
22+
PARSER.add_argument(
23+
"-s",
24+
"--seed",
25+
default=1,
26+
dest="seed",
27+
help="Random seed for generating configs.",
28+
)
29+
30+
31+
# Parse options and process argv
32+
ARGS, ARGV = PARSER.parse_known_args()
33+
34+
# Append the root directory to the import path.
35+
FILE_DIR = Path(__file__).resolve().parent
36+
37+
38+
class Options:
39+
"""Options for this testing script. Usually correspond to command line inputs."""
40+
41+
# File that is being compiled.
42+
p4_file: Path = Path(".")
43+
# The path for the p4rtsmith bin file for this test.
44+
p4rtsmith: Path = Path(".")
45+
# Actual location of the test framework.
46+
testdir: Path = Path(".")
47+
# Random seed for generating configs.
48+
seed: int = 1
49+
50+
51+
def generate_config(p4rtsmith_path, seed, p4_program_path, testdir, config_file_path):
52+
command = f"{p4rtsmith_path} --target bmv2 --arch v1model --seed {seed} --output-dir {testdir} --generate-config {config_file_path} {p4_program_path}"
53+
subprocess.run(command, shell=True)
54+
55+
56+
def run_test(run_test_script, p4_program_path, config_file_path):
57+
command = f"sudo -E {run_test_script} .. {p4_program_path} -tf {config_file_path}"
58+
subprocess.run(command, shell=True)
59+
60+
61+
def find_p4c_dir():
62+
current_dir = Path(__file__).resolve()
63+
64+
while True:
65+
p4c_build_dir = current_dir / "p4c"
66+
if p4c_build_dir.exists():
67+
return p4c_build_dir
68+
69+
current_dir = current_dir.parent
70+
71+
if current_dir == current_dir.parent:
72+
raise RuntimeError("p4c/build directory not found.")
73+
74+
75+
def run_tests(options: Options) -> int:
76+
config_file_path = "initial_config.txtpb"
77+
78+
seed = options.seed
79+
testdir = options.testdir
80+
p4rtsmith_path = options.p4rtsmith
81+
run_test_script = FILE_DIR / "run-bmv2-proto-test.py"
82+
p4_program_path = options.p4_file
83+
84+
generate_config(p4rtsmith_path, seed, p4_program_path, testdir, config_file_path)
85+
run_test(run_test_script, p4_program_path, testdir / config_file_path)
86+
87+
88+
def create_options(test_args: Any) -> Optional[Options]:
89+
"""Parse the input arguments and create a processed options object."""
90+
options = Options()
91+
options.p4_file = Path(test_args.p4_file).absolute()
92+
options.p4rtsmith = Path(test_args.p4rtsmith).absolute()
93+
94+
p4c_dir = find_p4c_dir()
95+
p4c_build_dir = p4c_dir / "build"
96+
os.chdir(p4c_build_dir)
97+
98+
testdir = test_args.testdir
99+
if not testdir:
100+
testdir = tempfile.mkdtemp(dir=Path(".").absolute())
101+
os.chmod(testdir, 0o755)
102+
options.testdir = Path(testdir)
103+
options.seed = test_args.seed
104+
105+
# Configure logging.
106+
logging.basicConfig(
107+
filename=options.testdir.joinpath("test.log"),
108+
format="%(levelname)s: %(message)s",
109+
level=getattr(logging, "WARNING"),
110+
filemode="w",
111+
)
112+
stderr_log = logging.StreamHandler()
113+
stderr_log.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
114+
logging.getLogger().addHandler(stderr_log)
115+
return options
116+
117+
118+
if __name__ == "__main__":
119+
test_options = create_options(ARGS)
120+
if not test_options:
121+
sys.exit()
122+
123+
run_tests(test_options)

0 commit comments

Comments
 (0)