Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backends/p4tools/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ cc_library(
"modules/testgen/lib/*.h",
"modules/testgen/*.h",
]) + glob(
["modules/testgen/targets/%s/backend/*.h" % target for target in TESTGEN_TARGETS] +
["modules/testgen/targets/%s/backend/**/*.h" % target for target in TESTGEN_TARGETS] +
["modules/testgen/targets/%s/test_backend/*.h" % target for target in TESTGEN_TARGETS],
allow_empty = True,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ set(
${TESTGEN_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/backend/metadata/metadata.cpp
${CMAKE_CURRENT_SOURCE_DIR}/backend/ptf/ptf.cpp
${CMAKE_CURRENT_SOURCE_DIR}/backend/stf/stf.cpp
${CMAKE_CURRENT_SOURCE_DIR}/dpdk/cmd_stepper.cpp
${CMAKE_CURRENT_SOURCE_DIR}/dpdk/expr_stepper.cpp
${CMAKE_CURRENT_SOURCE_DIR}/dpdk/program_info.cpp
Expand Down
250 changes: 250 additions & 0 deletions backends/p4tools/modules/testgen/targets/pna/backend/stf/stf.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
#include "backends/p4tools/modules/testgen/targets/pna/backend/stf/stf.h"

#include <fstream>
#include <iomanip>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include <boost/multiprecision/cpp_int.hpp>
#include <boost/multiprecision/detail/et_ops.hpp>
#include <boost/multiprecision/number.hpp>
#include <boost/multiprecision/traits/explicit_conversion.hpp>
#include <inja/inja.hpp>

#include "backends/p4tools/common/lib/format_int.h"
#include "backends/p4tools/common/lib/util.h"
#include "ir/ir.h"
#include "ir/irutils.h"
#include "lib/exceptions.h"
#include "lib/log.h"
#include "nlohmann/json.hpp"

#include "backends/p4tools/modules/testgen/lib/exceptions.h"
#include "backends/p4tools/modules/testgen/targets/pna/test_spec.h"

namespace P4::P4Tools::P4Testgen::Pna {

STF::STF(const TestBackendConfiguration &testBackendConfiguration)
: TestFramework(testBackendConfiguration) {}

inja::json STF::getSend(const TestSpec *testSpec) {
const auto *iPacket = testSpec->getIngressPacket();
const auto *payload = iPacket->getEvaluatedPayload();
inja::json sendJson;
sendJson["ig_port"] = iPacket->getPort();
sendJson["pkt"] = formatHexExpr(payload, {false, true, false});
sendJson["pkt_size"] = payload->type->width_bits();
return sendJson;
}

inja::json STF::getExpectedPacket(const TestSpec *testSpec) {
inja::json verifyData = inja::json::object();
if (testSpec->getEgressPacket() != std::nullopt) {
const auto &packet = **testSpec->getEgressPacket();
verifyData["eg_port"] = packet.getPort();
const auto *payload = packet.getEvaluatedPayload();
const auto *payloadMask = packet.getEvaluatedPayloadMask();
auto dataStr = formatHexExpr(payload, {false, true, false});
if (payloadMask != nullptr) {
auto maskStr = formatHexExpr(payloadMask, {false, true, false});
std::string packetData;
for (size_t dataPos = 0; dataPos < dataStr.size(); ++dataPos) {
if (maskStr.at(dataPos) != 'F') {
packetData += "*";
} else {
packetData += dataStr[dataPos];
}
}
verifyData["exp_pkt"] = packetData;
} else {
verifyData["exp_pkt"] = dataStr;
}
}
return verifyData;
}

inja::json STF::getControlPlaneForTable(const TableMatchMap &matches,
const std::vector<ActionArg> &args) {
inja::json rulesJson;

rulesJson["matches"] = inja::json::array();
rulesJson["act_args"] = inja::json::array();
rulesJson["needs_priority"] = false;

for (const auto &match : matches) {
const auto fieldName = match.first;
const auto &fieldMatch = match.second;

inja::json j;
j["field_name"] = fieldName;
if (const auto *elem = fieldMatch->to<Exact>()) {
j["value"] = formatHexExpr(elem->getEvaluatedValue());
} else if (const auto *elem = fieldMatch->to<Ternary>()) {
const auto *dataValue = elem->getEvaluatedValue();
const auto *maskField = elem->getEvaluatedMask();
BUG_CHECK(dataValue->type->width_bits() == maskField->type->width_bits(),
"Data value and its mask should have the same bit width.");
auto dataStr = formatBinExpr(dataValue, {false, true, false});
auto maskStr = formatBinExpr(maskField, {false, true, false});
std::string data = "0b";
for (size_t dataPos = 0; dataPos < dataStr.size(); ++dataPos) {
if (maskStr.at(dataPos) == '0') {
data += "*";
} else {
data += dataStr.at(dataPos);
}
}
j["value"] = data;
rulesJson["needs_priority"] = true;
} else if (const auto *elem = fieldMatch->to<LPM>()) {
const auto *dataValue = elem->getEvaluatedValue();
auto prefixLen = elem->getEvaluatedPrefixLength()->asInt();
auto fieldWidth = dataValue->type->width_bits();
auto maxVal = IR::getMaxBvVal(prefixLen);
const auto *maskField =
IR::Constant::get(dataValue->type, maxVal << (fieldWidth - prefixLen));
BUG_CHECK(dataValue->type->width_bits() == maskField->type->width_bits(),
"Data value and its mask should have the same bit width.");
auto dataStr = formatBinExpr(dataValue, {false, true, false});
auto maskStr = formatBinExpr(maskField, {false, true, false});
std::string data = "0b";
for (size_t dataPos = 0; dataPos < dataStr.size(); ++dataPos) {
if (maskStr.at(dataPos) == '0') {
data += "*";
} else {
data += dataStr.at(dataPos);
}
}
j["value"] = data;
rulesJson["needs_priority"] = true;
} else if (const auto *elem = fieldMatch->to<Optional>()) {
j["value"] = formatHexExpr(elem->getEvaluatedValue()).c_str();
rulesJson["needs_priority"] = true;
} else {
TESTGEN_UNIMPLEMENTED("Unsupported table key match type \"%1%\"",
fieldMatch->getObjectName());
}
rulesJson["matches"].push_back(j);
}

for (const auto &actArg : args) {
inja::json j;
j["param"] = actArg.getActionParamName().c_str();
j["value"] = formatHexExpr(actArg.getEvaluatedValue());
rulesJson["act_args"].push_back(j);
}

return rulesJson;
}

inja::json STF::getControlPlane(const TestSpec *testSpec) {
inja::json controlPlaneJson = inja::json::object();

auto tables = testSpec->getTestObjectCategory("tables"_cs);
if (!tables.empty()) {
controlPlaneJson["tables"] = inja::json::array();
}
for (const auto &testObject : tables) {
inja::json tblJson;
tblJson["table_name"] = testObject.first.c_str();
const auto *const tblConfig = testObject.second->checkedTo<TableConfig>();
const auto *tblRules = tblConfig->getRules();
tblJson["rules"] = inja::json::array();
for (const auto &tblRule : *tblRules) {
inja::json rule;
const auto *matches = tblRule.getMatches();
const auto *actionCall = tblRule.getActionCall();
const auto *actionArgs = actionCall->getArgs();
rule["action_name"] = actionCall->getActionName().c_str();
auto j = getControlPlaneForTable(*matches, *actionArgs);
rule["rules"] = std::move(j);
rule["priority"] = tblRule.getPriority();
tblJson["rules"].push_back(rule);
}

checkForDefaultActionOverride(tblJson, tblConfig);

controlPlaneJson["tables"].push_back(tblJson);
}

return controlPlaneJson;
}

std::string STF::getTestCaseTemplate() {
static std::string TEST_CASE(
R"""(# p4testgen seed: {{ default(seed, "none") }}
# Date generated: {{timestamp}}
## if length(selected_branches) > 0
# {{selected_branches}}
## endif
# Current node coverage: {{coverage}}
# Traces
## for trace_item in trace
# {{trace_item}}
##endfor

## if control_plane
## for table in control_plane.tables
# Table {{table.table_name}}
## if existsIn(table, "default_override")
setdefault "{{table.table_name}}" "{{table.default_override.action_name}}"({% for a in table.default_override.act_args %}"{{a.param}}":{{a.value}}{% if not loop.is_last %},{% endif %}{% endfor %})
## else
## for rule in table.rules
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 %})
## endfor
## endif

## endfor
## endif

packet {{send.ig_port}} {{send.pkt}}
## if verify
expect {{verify.eg_port}} {{verify.exp_pkt}}$
## endif

)""");
return TEST_CASE;
}

void STF::emitTestcase(const TestSpec *testSpec, cstring selectedBranches, size_t testId,
const std::string &testCase, float currentCoverage) {
inja::json dataJson;
if (selectedBranches != nullptr) {
dataJson["selected_branches"] = selectedBranches.c_str();
}
auto optSeed = getTestBackendConfiguration().seed;
if (optSeed.has_value()) {
dataJson["seed"] = optSeed.value();
}

dataJson["test_id"] = testId;
dataJson["trace"] = getTrace(testSpec);
dataJson["control_plane"] = getControlPlane(testSpec);
dataJson["send"] = getSend(testSpec);
dataJson["verify"] = getExpectedPacket(testSpec);
dataJson["timestamp"] = Utils::getTimeStamp();
std::stringstream coverageStr;
coverageStr << std::setprecision(2) << currentCoverage;
dataJson["coverage"] = coverageStr.str();

LOG5("STF test back end: emitting testcase:" << std::setw(4) << dataJson);

auto optBasePath = getTestBackendConfiguration().fileBasePath;
BUG_CHECK(optBasePath.has_value(), "Base path is not set.");
auto incrementedbasePath = optBasePath.value();
incrementedbasePath.concat("_" + std::to_string(testId));
incrementedbasePath.replace_extension(".stf");
auto stfFileStream = std::ofstream(incrementedbasePath);
inja::render_to(stfFileStream, testCase, dataJson);
stfFileStream.flush();
}

void STF::writeTestToFile(const TestSpec *testSpec, cstring selectedBranches, size_t testId,
float currentCoverage) {
std::string testCase = getTestCaseTemplate();
emitTestcase(testSpec, selectedBranches, testId, testCase, currentCoverage);
}

} // namespace P4::P4Tools::P4Testgen::Pna
55 changes: 55 additions & 0 deletions backends/p4tools/modules/testgen/targets/pna/backend/stf/stf.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#ifndef BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_PNA_BACKEND_STF_STF_H_
#define BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_PNA_BACKEND_STF_STF_H_

#include <cstddef>
#include <string>
#include <vector>

#include <inja/inja.hpp>

#include "lib/cstring.h"

#include "backends/p4tools/modules/testgen/lib/test_framework.h"
#include "backends/p4tools/modules/testgen/lib/test_object.h"
#include "backends/p4tools/modules/testgen/lib/test_spec.h"

namespace P4::P4Tools::P4Testgen::Pna {

/// Extracts information from the @testSpec to emit an STF test case.
class STF : public TestFramework {
public:
explicit STF(const TestBackendConfiguration &testBackendConfiguration);

/// Produce an STF test.
void writeTestToFile(const TestSpec *spec, cstring selectedBranches, size_t testId,
float currentCoverage) override;

private:
/// Emits a test case.
/// @param testId specifies the test name.
/// @param selectedBranches enumerates the choices the interpreter made for this path.
/// @param currentCoverage contains statistics about the current coverage of this test and its
/// preceding tests.
void emitTestcase(const TestSpec *testSpec, cstring selectedBranches, size_t testId,
const std::string &testCase, float currentCoverage);

/// @returns the inja test case template as a string.
static std::string getTestCaseTemplate();

/// Converts all the control plane objects into Inja format.
static inja::json getControlPlane(const TestSpec *testSpec);

/// Converts the input packet and port into Inja format.
static inja::json getSend(const TestSpec *testSpec);

/// Converts the output packet, port, and mask into Inja format.
static inja::json getExpectedPacket(const TestSpec *testSpec);

/// Helper function for the control plane table inja objects.
static inja::json getControlPlaneForTable(const TableMatchMap &matches,
const std::vector<ActionArg> &args);
};

} // namespace P4::P4Tools::P4Testgen::Pna

#endif /* BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_PNA_BACKEND_STF_STF_H_ */
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@
#include "backends/p4tools/modules/testgen/options.h"
#include "backends/p4tools/modules/testgen/targets/pna/backend/metadata/metadata.h"
#include "backends/p4tools/modules/testgen/targets/pna/backend/ptf/ptf.h"
#include "backends/p4tools/modules/testgen/targets/pna/backend/stf/stf.h"
#include "backends/p4tools/modules/testgen/targets/pna/dpdk/program_info.h"
#include "backends/p4tools/modules/testgen/targets/pna/test_spec.h"

namespace P4::P4Tools::P4Testgen::Pna {

const std::set<std::string> PnaTestBackend::SUPPORTED_BACKENDS = {"METADATA", "PTF"};
const std::set<std::string> PnaTestBackend::SUPPORTED_BACKENDS = {"METADATA", "PTF", "STF"};

PnaTestBackend::PnaTestBackend(const ProgramInfo &programInfo,
const TestBackendConfiguration &testBackendConfiguration,
Expand All @@ -48,6 +49,8 @@ PnaTestBackend::PnaTestBackend(const ProgramInfo &programInfo,
testWriter = new Metadata(testBackendConfiguration);
} else if (testBackendString == "PTF") {
testWriter = new PTF(testBackendConfiguration);
} else if (testBackendString == "STF") {
testWriter = new STF(testBackendConfiguration);
} else {
P4C_UNIMPLEMENTED(
"Test back end %1% not implemented for this target. Supported back ends are %2%.",
Expand Down
Loading