Skip to content

Commit

Permalink
Refactor test frameworks and use a utility library.
Browse files Browse the repository at this point in the history
  • Loading branch information
fruffy committed Nov 11, 2022
1 parent 173d2a5 commit 8740303
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 310 deletions.
102 changes: 101 additions & 1 deletion backends/p4tools/testgen/lib/tf.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
#include <cstddef>

#include <boost/optional/optional.hpp>
#include <inja/inja.hpp>

#include "backends/p4tools/common/lib/format_int.h"
#include "lib/cstring.h"

#include "backends/p4tools/testgen/lib/test_spec.h"
Expand All @@ -14,7 +16,7 @@ namespace P4Tools {
namespace P4Testgen {

/// THe default base class for the various test frameworks (TF). Every test framework has a test
/// name and a seed associated with it.
/// name and a seed associated with it. Also contains a variety of common utility functions.
class TF {
protected:
/// The @testName to be used in test case generation.
Expand All @@ -26,6 +28,104 @@ class TF {
/// Creates a generic test framework.
explicit TF(cstring, boost::optional<unsigned int>);

/// Converts the traces of this test into a string representation and Inja object.
static inja::json getTrace(const TestSpec* testSpec) {
inja::json traceList = inja::json::array();
const auto* traces = testSpec->getTraces();
if ((traces != nullptr) && !traces->empty()) {
for (const auto& trace : *traces) {
std::stringstream ss;
ss << *trace;
traceList.push_back(ss.str());
}
}
return traceList;
}

/// Checks whether a table object has an action profile or selector associated with it.
/// If that is the case, we set a boolean flag for this particular inja object.
template <class ProfileType, class SelectorType>
static void checkForTableActionProfile(inja::json& tblJson, std::map<cstring, cstring>& apAsMap,
const TableConfig* tblConfig) {
const auto* apObject = tblConfig->getProperty("action_profile", false);
if (apObject != nullptr) {
const auto* actionProfile = apObject->checkedTo<ProfileType>();
tblJson["has_ap"] = true;
// Check if we have an Action Selector too.
// TODO: Change this to check in ActionSelector with table
// property "action_selectors".
const auto* asObject = tblConfig->getProperty("action_selector", false);
if (asObject != nullptr) {
const auto* actionSelector = asObject->checkedTo<SelectorType>();
apAsMap[actionProfile->getProfileDecl()->controlPlaneName()] =
actionSelector->getSelectorDecl()->controlPlaneName();
tblJson["has_as"] = true;
}
}
}

/// Check whether the table object has an overridden default action.
/// In this case, we assume there are no keys and we just set the default action of the table.
static void checkForDefaultActionOverride(inja::json& tblJson, const TableConfig* tblConfig) {
const auto* defaultOverrideObj = tblConfig->getProperty("overriden_default_action", false);
if (defaultOverrideObj != nullptr) {
const auto* defaultAction = defaultOverrideObj->checkedTo<ActionCall>();
inja::json a;
a["action_name"] = defaultAction->getActionName();
auto const* actionArgs = defaultAction->getArgs();
inja::json b = inja::json::array();
for (const auto& actArg : *actionArgs) {
inja::json j;
j["param"] = actArg.getActionParamName().c_str();
j["value"] = formatHexExpr(actArg.getEvaluatedValue());
b.push_back(j);
}
a["act_args"] = b;
tblJson["default_override"] = a;
}
}

/// Collect all the action profile objects. These will have to be declared in the test.
template <class ProfileType>
static void collectActionProfileDeclarations(const TestSpec* testSpec,
inja::json& controlPlaneJson,
const std::map<cstring, cstring>& apAsMap) {
auto actionProfiles = testSpec->getTestObjectCategory("action_profiles");
if (!actionProfiles.empty()) {
controlPlaneJson["action_profiles"] = inja::json::array();
}
for (auto const& testObject : actionProfiles) {
const auto* const actionProfile = testObject.second->checkedTo<ProfileType>();
const auto* actions = actionProfile->getActions();
inja::json j;
j["profile"] = actionProfile->getProfileDecl()->controlPlaneName();
j["actions"] = inja::json::array();
for (size_t idx = 0; idx < actions->size(); ++idx) {
const auto& action = actions->at(idx);
auto actionName = action.first;
auto actionArgs = action.second;
inja::json a;
a["action_name"] = actionName;
a["action_idx"] = std::to_string(idx);
inja::json b = inja::json::array();
for (const auto& actArg : actionArgs) {
inja::json c;
c["param"] = actArg.getActionParamName().c_str();
c["value"] = formatHexExpr(actArg.getEvaluatedValue()).c_str();
b.push_back(c);
}
a["act_args"] = b;
j["actions"].push_back(a);
}
// Look up the selectors associated with the profile.
if (apAsMap.find(actionProfile->getProfileDecl()->controlPlaneName()) !=
apAsMap.end()) {
j["selector"] = apAsMap.at(actionProfile->getProfileDecl()->controlPlaneName());
}
controlPlaneJson["action_profiles"].push_back(j);
}
}

public:
/// The method used to output the test case to be implemented by
/// all the test frameworks (eg. STF, PTF, etc.).
Expand Down
98 changes: 15 additions & 83 deletions backends/p4tools/testgen/targets/bmv2/backend/protobuf/protobuf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
#include "backends/p4tools/common/lib/format_int.h"
#include "backends/p4tools/common/lib/trace_events.h"
#include "backends/p4tools/common/lib/util.h"
#include "control-plane/p4RuntimeArchStandard.h"
#include "gsl/gsl-lite.hpp"
#include "ir/ir.h"
#include "lib/big_int_util.h"
Expand All @@ -34,10 +33,6 @@ namespace P4Testgen {

namespace Bmv2 {

using P4::ControlPlaneAPI::p4rt_id_t;
using P4::ControlPlaneAPI::P4RuntimeSymbolType;
using P4::ControlPlaneAPI::Standard::SymbolType;

/// Wrapper helper function that automatically inserts separators for hex strings.
std::string formatHexExprWithSep(const IR::Expression* expr) {
return insertHexSeparators(formatHexExpr(expr, false, true, false));
Expand All @@ -46,22 +41,7 @@ std::string formatHexExprWithSep(const IR::Expression* expr) {
Protobuf::Protobuf(cstring testName, boost::optional<unsigned int> seed = boost::none)
: TF(testName, seed) {}

inja::json Protobuf::getTrace(const TestSpec* testSpec) {
inja::json traceList = inja::json::array();
const auto* traces = testSpec->getTraces();
if ((traces != nullptr) && !traces->empty()) {
for (const auto& trace : *traces) {
std::stringstream ss;
ss << *trace;
traceList.push_back(ss.str());
}
}
return traceList;
}

/// @return the id allocated to the object through the @id annotation if any, or
/// boost::none.
static boost::optional<p4rt_id_t> getIdAnnotation(const IR::IAnnotated* node) {
boost::optional<p4rt_id_t> Protobuf::getIdAnnotation(const IR::IAnnotated* node) {
const auto* idAnnotation = node->getAnnotation("id");
if (idAnnotation == nullptr) {
return boost::none;
Expand All @@ -75,11 +55,8 @@ static boost::optional<p4rt_id_t> getIdAnnotation(const IR::IAnnotated* node) {
return static_cast<p4rt_id_t>(idConstant->value);
}

/// @return the value of any P4 '@id' annotation @declaration may have, and
/// ensure that the value is correct with respect to the P4Runtime
/// specification. The name 'externalId' is in analogy with externalName().
static boost::optional<p4rt_id_t> externalId(const P4RuntimeSymbolType& type,
const IR::IDeclaration* declaration) {
boost::optional<p4rt_id_t> Protobuf::externalId(const P4RuntimeSymbolType& type,
const IR::IDeclaration* declaration) {
CHECK_NULL(declaration);
if (!declaration->is<IR::IAnnotated>()) {
return boost::none; // Assign an id later; see below.
Expand Down Expand Up @@ -133,7 +110,7 @@ inja::json Protobuf::getControlPlane(const TestSpec* testSpec) {
inja::json controlPlaneJson = inja::json::object();

// Map of actionProfiles and actionSelectors for easy reference.
std::map<cstring, cstring> apASMap;
std::map<cstring, cstring> apAsMap;

auto tables = testSpec->getTestObjectCategory("tables");
if (!tables.empty()) {
Expand Down Expand Up @@ -163,70 +140,25 @@ inja::json Protobuf::getControlPlane(const TestSpec* testSpec) {
actionDecl);
rule["action_id"] = *p4RuntimeId;
auto j = getControlPlaneForTable(*matches, *actionArgs);
rule["priority"] = tblRule.getPriority();
rule["rules"] = std::move(j);
rule["priority"] = tblRule.getPriority();
tblJson["rules"].push_back(rule);
}

// Action Profile
const auto* apObject = tblConfig->getProperty("action_profile", false);
const Bmv2_V1ModelActionProfile* actionProfile = nullptr;
const Bmv2_V1ModelActionSelector* actionSelector = nullptr;
if (apObject != nullptr) {
actionProfile = apObject->checkedTo<Bmv2_V1ModelActionProfile>();
// Check if we have an Action Selector too.
// TODO: Change this to check in ActionSelector with table
// property "action_selectors".
const auto* asObject = tblConfig->getProperty("action_selector", false);
if (asObject != nullptr) {
actionSelector = asObject->checkedTo<Bmv2_V1ModelActionSelector>();
apASMap[actionProfile->getProfileDecl()->controlPlaneName()] =
actionSelector->getSelectorDecl()->controlPlaneName();
}
}
if (actionProfile != nullptr) {
tblJson["has_ap"] = true;
}
// Collect action profiles and selectors associated with the table.
checkForTableActionProfile<Bmv2_V1ModelActionProfile, Bmv2_V1ModelActionSelector>(
tblJson, apAsMap, tblConfig);

if (actionSelector != nullptr) {
tblJson["has_as"] = true;
}
// Check whether the default action is overridden for this table.
checkForDefaultActionOverride(tblJson, tblConfig);

controlPlaneJson["tables"].push_back(tblJson);
}
auto actionProfiles = testSpec->getTestObjectCategory("action_profiles");
if (!actionProfiles.empty()) {
controlPlaneJson["action_profiles"] = inja::json::array();
}
for (auto const& testObject : actionProfiles) {
const auto* const actionProfile = testObject.second->checkedTo<Bmv2_V1ModelActionProfile>();
const auto* actions = actionProfile->getActions();
inja::json j;
const auto* profileDecl = actionProfile->getProfileDecl();
j["profile"] = profileDecl->controlPlaneName();
j["actions"] = inja::json::array();
for (size_t idx = 0; idx < actions->size(); ++idx) {
const auto& action = actions->at(idx);
auto actionName = action.first;
auto actionArgs = action.second;
inja::json a;
a["action_name"] = actionName;
a["action_idx"] = std::to_string(idx);
inja::json b = inja::json::array();
for (const auto& actArg : actionArgs) {
inja::json c;
c["param"] = actArg.getActionParamName().c_str();
c["value"] = formatHexExprWithSep(actArg.getEvaluatedValue()).c_str();
b.push_back(c);
}
a["action_args"] = b;
j["actions"].push_back(a);
}
if (apASMap.find(actionProfile->getProfileDecl()->controlPlaneName()) != apASMap.end()) {
j["selector"] = apASMap[actionProfile->getProfileDecl()->controlPlaneName()];
}
controlPlaneJson["action_profiles"].push_back(j);
}

// Collect declarations of action profiles.
collectActionProfileDeclarations<Bmv2_V1ModelActionProfile>(testSpec, controlPlaneJson,
apAsMap);

return controlPlaneJson;
}

Expand Down
20 changes: 16 additions & 4 deletions backends/p4tools/testgen/targets/bmv2/backend/protobuf/protobuf.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#include <boost/optional/optional.hpp>
#include <inja/inja.hpp>

/// Inja
#include "control-plane/p4RuntimeArchStandard.h"
#include "ir/ir.h"
#include "lib/cstring.h"

Expand All @@ -24,6 +24,10 @@ namespace P4Testgen {

namespace Bmv2 {

using P4::ControlPlaneAPI::p4rt_id_t;
using P4::ControlPlaneAPI::P4RuntimeSymbolType;
using P4::ControlPlaneAPI::Standard::SymbolType;

/// Extracts information from the @testSpec to emit a Protobuf test case.
class Protobuf : public TF {
/// The output file.
Expand All @@ -42,6 +46,7 @@ class Protobuf : public TF {

Protobuf(cstring testName, boost::optional<unsigned int> seed);

/// Produce a Protobuf test.
void outputTest(const TestSpec* spec, cstring selectedBranches, size_t testIdx,
float currentCoverage) override;

Expand All @@ -58,9 +63,6 @@ class Protobuf : public TF {
void emitTestcase(const TestSpec* testSpec, cstring selectedBranches, size_t testId,
const std::string& testCase, float currentCoverage);

/// Converts the traces of this test into a string representation and Inja object.
static inja::json getTrace(const TestSpec* testSpec);

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

Expand All @@ -77,6 +79,16 @@ class Protobuf : public TF {
/// Helper function for the control plane table inja objects.
static inja::json getControlPlaneForTable(const std::map<cstring, const FieldMatch>& matches,
const std::vector<ActionArg>& args);

/// @return the id allocated to the object through the @id annotation if any, or
/// boost::none.
static boost::optional<p4rt_id_t> getIdAnnotation(const IR::IAnnotated* node);

/// @return the value of any P4 '@id' annotation @declaration may have, and
/// ensure that the value is correct with respect to the P4Runtime
/// specification. The name 'externalId' is in analogy with externalName().
static boost::optional<p4rt_id_t> externalId(const P4RuntimeSymbolType& type,
const IR::IDeclaration* declaration);
};

} // namespace Bmv2
Expand Down
Loading

0 comments on commit 8740303

Please sign in to comment.