Skip to content

Commit

Permalink
Implement an option to ignore generation of test entries for specific…
Browse files Browse the repository at this point in the history
… control plane elements.
  • Loading branch information
fruffy committed Feb 13, 2024
1 parent 8a6bcda commit e8e922b
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -546,8 +546,6 @@ void TableStepper::evalTargetTable(
}

bool TableStepper::eval() {
// Set the appropriate properties when the table is immutable, meaning it has constant entries.
TableUtils::checkTableImmutability(*table, properties);
// Resolve any non-symbolic table keys. The function returns true when a key needs replacement.
if (resolveTableKeys()) {
return false;
Expand Down Expand Up @@ -578,6 +576,15 @@ TableStepper::TableStepper(ExprStepper *stepper, const IR::P4Table *table)
const auto *action = table->getActionList()->actionList.at(index);
properties.actionIdMap.emplace(action->controlPlaneName(), index);
}

// Set the appropriate properties when the table is immutable, meaning it has constant entries.
TableUtils::checkTableImmutability(*table, properties);

// If the table is in the set of entities to skip, we set it immutable.
// P4Testgen will not add a control plane entry for this table.
if (TestgenOptions::get().skippedControlPlaneEntities.count(properties.tableName) != 0U) {
properties.tableIsImmutable = true;
}
}

} // namespace P4Tools::P4Testgen
21 changes: 18 additions & 3 deletions backends/p4tools/modules/testgen/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,8 @@ TestgenOptions::TestgenOptions()
// Each element is then again split by colon (':').
std::stringstream argStream(arg);
while (argStream.good()) {
std::string substr;
std::getline(argStream, substr, ',');
auto rangeStr = std::string(arg);
std::string rangeStr;
std::getline(argStream, rangeStr, ',');
size_t portStr = rangeStr.find_first_of(':');
try {
auto loPortStr = rangeStr.substr(0, portStr);
Expand Down Expand Up @@ -154,6 +153,22 @@ TestgenOptions::TestgenOptions()
"target-specific port variable, it will overflow. Default behavior is delegated to the "
"test back end. Some test back ends may restrict the available port ranges.");

registerOption(
"--skip-control-plane-entities", "skippedControlPlaneEntities",
[this](const char *arg) {
// Convert the input into a StringStream and split by comma (',').
std::stringstream argStream(arg);
while (argStream.good()) {
std::string substr;
std::getline(argStream, substr, ',');
skippedControlPlaneEntities.emplace(substr);
}
return true;
},
"Specify the set of control plane entities where P4Testgen should not generate a "
"configuration for. For example, if a particular table is in the set, P4Testgen will "
"assume that only the default action or constant entries can be executed. ");

registerOption(
"--out-dir", "outputDir",
[this](const char *arg) {
Expand Down
3 changes: 3 additions & 0 deletions backends/p4tools/modules/testgen/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ class TestgenOptions : public AbstractP4cToolOptions {
/// TestGen will consider these when choosing input and output ports.
std::vector<std::pair<int, int>> permittedPortRanges;

/// Skip generating a control plane entry for the entities in this list.
std::set<cstring> skippedControlPlaneEntities;

/// Enforces the test generation of tests with mandatory output packet.
bool outputPacketOnly = false;

Expand Down
2 changes: 2 additions & 0 deletions backends/p4tools/modules/testgen/targets/bmv2/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ set(TESTGEN_SOURCES
set(TESTGEN_GTEST_SOURCES
${TESTGEN_GTEST_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/test/testgen_api/api_test.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/testgen_api/control_plane_filter_test.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/testgen_api/output_option_test.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/test_backend/ptf.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/test_backend/stf.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/small-step/binary.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression
const auto &externInstance = state.findDecl(externRef);
// Retrieve the register state from the object store. If it is already present, just
// cast the object to the correct class and retrieve the current value according to the
// index. If the register has not been added had, create a new register object.
// index. If the register has not been added yet, create a new register object.
const auto *registerState =
state.getTestObject("registervalues", externInstance->controlPlaneName(), false);
const Bmv2V1ModelRegisterValue *registerValue = nullptr;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include "test/gtest/helpers.h"

#include "backends/p4tools/modules/testgen/options.h"
#include "backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf_ir.h"
#include "backends/p4tools/modules/testgen/testgen.h"

namespace Test {

namespace {

TEST(P4TestgenControlPlaneFilterTest, FiltersControlPlaneEntities) {
std::stringstream streamTest;
streamTest << R"p4(
header ethernet_t {
bit<48> dst_addr;
bit<48> src_addr;
bit<16> ether_type;
}
struct Headers {
ethernet_t eth_hdr;
}
struct Metadata { }
parser parse(packet_in pkt, out Headers hdr, inout Metadata m, inout standard_metadata_t sm) {
state start {
pkt.extract(hdr.eth_hdr);
transition accept;
}
}
control ingress(inout Headers hdr, inout Metadata meta, inout standard_metadata_t sm) {
// Drop the packet.
action acl_drop() {
mark_to_drop(sm);
}
table drop_table {
key = {
hdr.eth_hdr.dst_addr : ternary @name("dst_eth");
}
actions = {
acl_drop();
@defaultonly NoAction();
}
}
apply {
if (hdr.eth_hdr.isValid()) {
drop_table.apply();
}
}
}
control egress(inout Headers hdr, inout Metadata meta, inout standard_metadata_t sm) {
apply {}
}
control deparse(packet_out pkt, in Headers hdr) {
apply {
pkt.emit(hdr.eth_hdr);
}
}
control verifyChecksum(inout Headers hdr, inout Metadata meta) {
apply {}
}
control computeChecksum(inout Headers hdr, inout Metadata meta) {
apply {}
}
V1Switch(parse(), verifyChecksum(), ingress(), egress(), computeChecksum(), deparse()) main;
)p4";
auto source = P4_SOURCE(P4Headers::V1MODEL, streamTest.str().c_str());
auto compilerOptions = CompilerOptions();
compilerOptions.target = "bmv2";
compilerOptions.arch = "v1model";
auto &testgenOptions = P4Tools::P4Testgen::TestgenOptions::get();
testgenOptions.testBackend = "PROTOBUF_IR";
testgenOptions.testBaseName = "dummy";
testgenOptions.seed = 1;
testgenOptions.maxTests = 0;
// Create a bespoke packet for the Ethernet extract call.
testgenOptions.minPktSize = 112;
testgenOptions.maxPktSize = 112;

// First, we ensure that tests are generated correctly. We expect two tests.
// One which exercises action acl_drop and one which exercises the default action, NoAction.
{
auto testListOpt =
P4Tools::P4Testgen::Testgen::generateTests(source, compilerOptions, testgenOptions);

ASSERT_TRUE(testListOpt.has_value());
auto testList = testListOpt.value();
ASSERT_EQ(testList.size(), 2);
const auto *testWithDrop =
testList[0]->checkedTo<P4Tools::P4Testgen::Bmv2::ProtobufIrTest>();
EXPECT_THAT(testWithDrop->getFormattedTest(),
testing::Not(testing::HasSubstr(R"(expected_packet)")));
const auto *testWithForwarding =
testList[1]->checkedTo<P4Tools::P4Testgen::Bmv2::ProtobufIrTest>();
EXPECT_THAT(testWithForwarding->getFormattedTest(),
testing::HasSubstr(R"(expected_output_packet)"));
}

// Now we install a filter.
// Since we can not generate a config for the table we should only generate one test.
{
testgenOptions.skippedControlPlaneEntities = {"ingress.drop_table"};

auto testListOpt =
P4Tools::P4Testgen::Testgen::generateTests(source, compilerOptions, testgenOptions);

ASSERT_TRUE(testListOpt.has_value());
auto testList = testListOpt.value();
ASSERT_EQ(testList.size(), 1);
const auto *test = testList[0];
const auto *protobufIrTest = test->checkedTo<P4Tools::P4Testgen::Bmv2::ProtobufIrTest>();
EXPECT_THAT(protobufIrTest->getFormattedTest(),
testing::Not(testing::HasSubstr(R"(expected_packet)")));
}
}

} // namespace

} // namespace Test

0 comments on commit e8e922b

Please sign in to comment.