Skip to content

Commit a1a178e

Browse files
committed
Add p4tc backend to p4testgen
Signed-off-by: Victor Nogueira <victor@mojatatu.com>
1 parent 929c928 commit a1a178e

17 files changed

+859
-2
lines changed

backends/p4tools/modules/testgen/targets/pna/CMakeLists.txt

+5
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@ set(
1010
${TESTGEN_SOURCES}
1111
${CMAKE_CURRENT_SOURCE_DIR}/backend/metadata/metadata.cpp
1212
${CMAKE_CURRENT_SOURCE_DIR}/backend/ptf/ptf.cpp
13+
${CMAKE_CURRENT_SOURCE_DIR}/backend/stf/stf.cpp
1314
${CMAKE_CURRENT_SOURCE_DIR}/dpdk/cmd_stepper.cpp
1415
${CMAKE_CURRENT_SOURCE_DIR}/dpdk/expr_stepper.cpp
1516
${CMAKE_CURRENT_SOURCE_DIR}/dpdk/program_info.cpp
1617
${CMAKE_CURRENT_SOURCE_DIR}/dpdk/table_stepper.cpp
18+
${CMAKE_CURRENT_SOURCE_DIR}/p4tc/cmd_stepper.cpp
19+
${CMAKE_CURRENT_SOURCE_DIR}/p4tc/expr_stepper.cpp
20+
${CMAKE_CURRENT_SOURCE_DIR}/p4tc/program_info.cpp
21+
${CMAKE_CURRENT_SOURCE_DIR}/p4tc/table_stepper.cpp
1722
${CMAKE_CURRENT_SOURCE_DIR}/concolic.cpp
1823
${CMAKE_CURRENT_SOURCE_DIR}/constants.cpp
1924
${CMAKE_CURRENT_SOURCE_DIR}/shared_cmd_stepper.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
#include "backends/p4tools/modules/testgen/targets/pna/backend/stf/stf.h"
2+
3+
#include <filesystem>
4+
#include <fstream>
5+
#include <iomanip>
6+
#include <list>
7+
#include <map>
8+
#include <optional>
9+
#include <regex> // NOLINT
10+
#include <string>
11+
#include <utility>
12+
#include <vector>
13+
14+
#include <boost/multiprecision/cpp_int.hpp>
15+
#include <boost/multiprecision/detail/et_ops.hpp>
16+
#include <boost/multiprecision/number.hpp>
17+
#include <boost/multiprecision/traits/explicit_conversion.hpp>
18+
#include <inja/inja.hpp>
19+
20+
#include "backends/p4tools/common/lib/format_int.h"
21+
#include "backends/p4tools/common/lib/util.h"
22+
#include "ir/ir.h"
23+
#include "ir/irutils.h"
24+
#include "lib/exceptions.h"
25+
#include "lib/log.h"
26+
#include "nlohmann/json.hpp"
27+
28+
#include "backends/p4tools/modules/testgen/lib/exceptions.h"
29+
#include "backends/p4tools/modules/testgen/lib/test_framework.h"
30+
#include "backends/p4tools/modules/testgen/lib/test_object.h"
31+
32+
namespace P4::P4Tools::P4Testgen::Pna {
33+
34+
STF::STF(const TestBackendConfiguration &testBackendConfiguration)
35+
: TestFramework(testBackendConfiguration) {}
36+
37+
inja::json STF::getControlPlane(const TestSpec *testSpec) {
38+
inja::json controlPlaneJson = inja::json::object();
39+
40+
// Map of actionProfiles and actionSelectors for easy reference.
41+
std::map<cstring, cstring> apAsMap;
42+
43+
auto tables = testSpec->getTestObjectCategory("tables"_cs);
44+
if (!tables.empty()) {
45+
controlPlaneJson["tables"] = inja::json::array();
46+
}
47+
for (const auto &testObject : tables) {
48+
inja::json tblJson;
49+
tblJson["table_name"] = testObject.first.c_str();
50+
const auto *const tblConfig = testObject.second->checkedTo<TableConfig>();
51+
const auto *tblRules = tblConfig->getRules();
52+
tblJson["rules"] = inja::json::array();
53+
for (const auto &tblRule : *tblRules) {
54+
inja::json rule;
55+
const auto *matches = tblRule.getMatches();
56+
const auto *actionCall = tblRule.getActionCall();
57+
const auto *actionArgs = actionCall->getArgs();
58+
rule["action_name"] = actionCall->getActionName().c_str();
59+
auto j = getControlPlaneForTable(*matches, *actionArgs);
60+
rule["rules"] = std::move(j);
61+
rule["priority"] = tblRule.getPriority();
62+
tblJson["rules"].push_back(rule);
63+
}
64+
65+
controlPlaneJson["tables"].push_back(tblJson);
66+
}
67+
68+
return controlPlaneJson;
69+
}
70+
71+
inja::json STF::getControlPlaneForTable(const TableMatchMap &matches,
72+
const std::vector<ActionArg> &args) {
73+
inja::json rulesJson;
74+
75+
rulesJson["matches"] = inja::json::array();
76+
rulesJson["act_args"] = inja::json::array();
77+
rulesJson["needs_priority"] = false;
78+
79+
for (const auto &match : matches) {
80+
auto fieldName = match.first;
81+
const auto &fieldMatch = match.second;
82+
83+
// Replace header stack indices hdr[<index>] with hdr$<index>.
84+
// TODO: This is a limitation of the stf parser. We should fix this.
85+
std::regex hdrStackRegex(R"(\[([0-9]+)\])");
86+
auto indexName = std::regex_replace(fieldName.c_str(), hdrStackRegex, "$$$1");
87+
if (indexName != fieldName.c_str()) {
88+
fieldName = cstring::to_cstring(indexName);
89+
}
90+
91+
inja::json j;
92+
j["field_name"] = fieldName;
93+
if (const auto *elem = fieldMatch->to<Exact>()) {
94+
j["value"] = formatHexExpr(elem->getEvaluatedValue());
95+
} else if (const auto *elem = fieldMatch->to<Ternary>()) {
96+
const auto *dataValue = elem->getEvaluatedValue();
97+
const auto *maskField = elem->getEvaluatedMask();
98+
BUG_CHECK(dataValue->type->width_bits() == maskField->type->width_bits(),
99+
"Data value and its mask should have the same bit width.");
100+
// Using the width from mask - should be same as data
101+
auto dataStr = formatBinExpr(dataValue, {false, true, false});
102+
auto maskStr = formatBinExpr(maskField, {false, true, false});
103+
std::string data = "0b";
104+
for (size_t dataPos = 0; dataPos < dataStr.size(); ++dataPos) {
105+
if (maskStr.at(dataPos) == '0') {
106+
data += "*";
107+
} else {
108+
data += dataStr.at(dataPos);
109+
}
110+
}
111+
j["value"] = data;
112+
// If the rule has a ternary match we need to add the priority.
113+
rulesJson["needs_priority"] = true;
114+
} else if (const auto *elem = fieldMatch->to<LPM>()) {
115+
const auto *dataValue = elem->getEvaluatedValue();
116+
auto prefixLen = elem->getEvaluatedPrefixLength()->asInt();
117+
auto fieldWidth = dataValue->type->width_bits();
118+
auto maxVal = IR::getMaxBvVal(prefixLen);
119+
const auto *maskField =
120+
IR::Constant::get(dataValue->type, maxVal << (fieldWidth - prefixLen));
121+
BUG_CHECK(dataValue->type->width_bits() == maskField->type->width_bits(),
122+
"Data value and its mask should have the same bit width.");
123+
// Using the width from mask - should be same as data
124+
auto dataStr = formatBinExpr(dataValue, {false, true, false});
125+
auto maskStr = formatBinExpr(maskField, {false, true, false});
126+
std::string data = "0b";
127+
for (size_t dataPos = 0; dataPos < dataStr.size(); ++dataPos) {
128+
if (maskStr.at(dataPos) == '0') {
129+
data += "*";
130+
} else {
131+
data += dataStr.at(dataPos);
132+
}
133+
}
134+
j["value"] = data;
135+
// If the rule has a ternary match we need to add the priority.
136+
rulesJson["needs_priority"] = true;
137+
} else {
138+
TESTGEN_UNIMPLEMENTED("Unsupported table key match type \"%1%\"",
139+
fieldMatch->getObjectName());
140+
}
141+
rulesJson["matches"].push_back(j);
142+
}
143+
144+
for (const auto &actArg : args) {
145+
inja::json j;
146+
j["param"] = actArg.getActionParamName().c_str();
147+
j["value"] = formatHexExpr(actArg.getEvaluatedValue());
148+
rulesJson["act_args"].push_back(j);
149+
}
150+
151+
return rulesJson;
152+
}
153+
154+
inja::json STF::getSend(const TestSpec *testSpec) {
155+
const auto *iPacket = testSpec->getIngressPacket();
156+
const auto *payload = iPacket->getEvaluatedPayload();
157+
inja::json sendJson;
158+
sendJson["ig_port"] = iPacket->getPort();
159+
auto dataStr = formatHexExpr(payload, {false, true, false});
160+
sendJson["pkt"] = dataStr;
161+
sendJson["pkt_size"] = payload->type->width_bits();
162+
return sendJson;
163+
}
164+
165+
inja::json STF::getVerify(const TestSpec *testSpec) {
166+
inja::json verifyData = inja::json::object();
167+
if (testSpec->getEgressPacket() != std::nullopt) {
168+
const auto &packet = **testSpec->getEgressPacket();
169+
verifyData["eg_port"] = packet.getPort();
170+
const auto *payload = packet.getEvaluatedPayload();
171+
const auto *payloadMask = packet.getEvaluatedPayloadMask();
172+
auto dataStr = formatHexExpr(payload, {false, true, false});
173+
if (payloadMask != nullptr) {
174+
// If a mask is present, construct the packet data with wildcard `*` where there are
175+
// non zero nibbles
176+
auto maskStr = formatHexExpr(payloadMask, {false, true, false});
177+
std::string packetData;
178+
for (size_t dataPos = 0; dataPos < dataStr.size(); ++dataPos) {
179+
if (maskStr.at(dataPos) != 'F') {
180+
// TODO: We are being conservative here and adding a wildcard for any 0
181+
// in the 4b nibble
182+
packetData += "*";
183+
} else {
184+
packetData += dataStr[dataPos];
185+
}
186+
}
187+
verifyData["exp_pkt"] = packetData;
188+
} else {
189+
verifyData["exp_pkt"] = dataStr;
190+
}
191+
}
192+
return verifyData;
193+
}
194+
195+
std::string STF::getTestCaseTemplate() {
196+
static std::string TEST_CASE(
197+
R"""(# p4testgen seed: {{ default(seed, "none") }}
198+
# Date generated: {{timestamp}}
199+
## if length(selected_branches) > 0
200+
# {{selected_branches}}
201+
## endif
202+
# Current node coverage: {{coverage}}
203+
# Traces
204+
## for trace_item in trace
205+
# {{trace_item}}
206+
##endfor
207+
208+
## if control_plane
209+
## for table in control_plane.tables
210+
# Table {{table.table_name}}
211+
## for rule in table.rules
212+
add {{table.table_name}} {% if rule.rules.needs_priority %}{{rule.priority}} {% endif %}{% for r in rule.rules.matches %}{{r.field_name}}:{{r.value}} {% endfor %}{{rule.action_name}}({% for a in rule.rules.act_args %}{{a.param}}:{{a.value}}{% if not loop.is_last %},{% endif %}{% endfor %})
213+
## endfor
214+
## endfor
215+
## endif
216+
217+
packet {{send.ig_port}} {{send.pkt}}
218+
## if verify
219+
expect {{verify.eg_port}} {{verify.exp_pkt}}
220+
## endif
221+
)""");
222+
return TEST_CASE;
223+
}
224+
225+
void STF::emitTestcase(const TestSpec *testSpec, cstring selectedBranches, size_t testId,
226+
const std::string &testCase, float currentCoverage) {
227+
inja::json dataJson;
228+
if (selectedBranches != nullptr) {
229+
dataJson["selected_branches"] = selectedBranches.c_str();
230+
}
231+
232+
auto optSeed = getTestBackendConfiguration().seed;
233+
if (optSeed.has_value()) {
234+
dataJson["seed"] = optSeed.value();
235+
}
236+
dataJson["test_id"] = testId;
237+
dataJson["trace"] = getTrace(testSpec);
238+
dataJson["control_plane"] = getControlPlane(testSpec);
239+
dataJson["send"] = getSend(testSpec);
240+
dataJson["verify"] = getVerify(testSpec);
241+
dataJson["timestamp"] = Utils::getTimeStamp();
242+
std::stringstream coverageStr;
243+
coverageStr << std::setprecision(2) << currentCoverage;
244+
dataJson["coverage"] = coverageStr.str();
245+
246+
LOG5("STF test back end: emitting testcase:" << std::setw(4) << dataJson);
247+
auto optBasePath = getTestBackendConfiguration().fileBasePath;
248+
BUG_CHECK(optBasePath.has_value(), "Base path is not set.");
249+
auto incrementedbasePath = optBasePath.value();
250+
incrementedbasePath.concat("_" + std::to_string(testId));
251+
incrementedbasePath.replace_extension(".stf");
252+
auto stfFileStream = std::ofstream(incrementedbasePath);
253+
inja::render_to(stfFileStream, testCase, dataJson);
254+
stfFileStream.flush();
255+
}
256+
257+
void STF::writeTestToFile(const TestSpec *testSpec, cstring selectedBranches, size_t testId,
258+
float currentCoverage) {
259+
std::string testCase = getTestCaseTemplate();
260+
emitTestcase(testSpec, selectedBranches, testId, testCase, currentCoverage);
261+
}
262+
263+
} // namespace P4::P4Tools::P4Testgen::Pna
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#ifndef BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_PNA_BACKEND_STF_STF_H_
2+
#define BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_PNA_BACKEND_STF_STF_H_
3+
4+
#include <cstddef>
5+
#include <string>
6+
#include <vector>
7+
8+
#include <inja/inja.hpp>
9+
10+
#include "lib/cstring.h"
11+
12+
#include "backends/p4tools/modules/testgen/lib/test_framework.h"
13+
#include "backends/p4tools/modules/testgen/lib/test_spec.h"
14+
15+
namespace P4::P4Tools::P4Testgen::Pna {
16+
17+
/// Extracts information from the @testSpec to emit a STF test case.
18+
class STF : public TestFramework {
19+
public:
20+
~STF() override = default;
21+
STF(const STF &) = delete;
22+
STF(STF &&) = delete;
23+
STF &operator=(const STF &) = delete;
24+
STF &operator=(STF &&) = delete;
25+
26+
explicit STF(const TestBackendConfiguration &testBackendConfiguration);
27+
28+
/// Produce an STF test.
29+
void writeTestToFile(const TestSpec *spec, cstring selectedBranches, size_t testId,
30+
float currentCoverage) override;
31+
32+
private:
33+
/// Emits a test case.
34+
/// @param testId specifies the test name.
35+
/// @param selectedBranches enumerates the choices the interpreter made for this path.
36+
/// @param currentCoverage contains statistics about the current coverage of this test and its
37+
/// preceding tests.
38+
void emitTestcase(const TestSpec *testSpec, cstring selectedBranches, size_t testId,
39+
const std::string &testCase, float currentCoverage);
40+
41+
/// @returns the inja test case template as a string.
42+
static std::string getTestCaseTemplate();
43+
44+
/// Converts all the control plane objects into Inja format.
45+
static inja::json getControlPlane(const TestSpec *testSpec);
46+
47+
/// Converts the input packet and port into Inja format.
48+
static inja::json getSend(const TestSpec *testSpec);
49+
50+
/// Converts the output packet, port, and mask into Inja format.
51+
static inja::json getVerify(const TestSpec *testSpec);
52+
53+
/// Helper function for the control plane table inja objects.
54+
static inja::json getControlPlaneForTable(const TableMatchMap &matches,
55+
const std::vector<ActionArg> &args);
56+
};
57+
58+
} // namespace P4::P4Tools::P4Testgen::Pna
59+
60+
#endif /* BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_PNA_BACKEND_STF_STF_H_ */

backends/p4tools/modules/testgen/targets/pna/concolic.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,10 @@ const ConcolicMethodImpls::ImplList *PnaDpdkConcolic::getPnaDpdkConcolicMethodIm
1010
return &PNA_DPDK_CONCOLIC_METHOD_IMPLS;
1111
}
1212

13+
const ConcolicMethodImpls::ImplList PnaP4TCConcolic::PNA_P4TC_CONCOLIC_METHOD_IMPLS{};
14+
15+
const ConcolicMethodImpls::ImplList *PnaP4TCConcolic::getPnaP4TCConcolicMethodImpls() {
16+
return &PNA_P4TC_CONCOLIC_METHOD_IMPLS;
17+
}
18+
1319
} // namespace P4::P4Tools::P4Testgen::Pna

backends/p4tools/modules/testgen/targets/pna/concolic.h

+10
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ class PnaDpdkConcolic : public Concolic {
1515
static const ConcolicMethodImpls::ImplList *getPnaDpdkConcolicMethodImpls();
1616
};
1717

18+
class PnaP4TCConcolic : public Concolic {
19+
private:
20+
/// This is the list of concolic functions that are implemented in this class.
21+
static const ConcolicMethodImpls::ImplList PNA_P4TC_CONCOLIC_METHOD_IMPLS;
22+
23+
public:
24+
/// @returns the concolic functions that are implemented for this particular target.
25+
static const ConcolicMethodImpls::ImplList *getPnaP4TCConcolicMethodImpls();
26+
};
27+
1828
} // namespace P4::P4Tools::P4Testgen::Pna
1929

2030
#endif /* BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_PNA_CONCOLIC_H_ */

0 commit comments

Comments
 (0)