-
Notifications
You must be signed in to change notification settings - Fork 510
Expand file tree
/
Copy pathtest_framework.h
More file actions
202 lines (180 loc) · 9.5 KB
/
test_framework.h
File metadata and controls
202 lines (180 loc) · 9.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#ifndef BACKENDS_P4TOOLS_MODULES_TESTGEN_LIB_TEST_FRAMEWORK_H_
#define BACKENDS_P4TOOLS_MODULES_TESTGEN_LIB_TEST_FRAMEWORK_H_
#include <cstddef>
#include <filesystem>
#include <functional>
#include <iosfwd>
#include <map>
#include <optional>
#include <string>
#include <utility>
#include <inja/inja.hpp>
#include "backends/p4tools/common/lib/format_int.h"
#include "backends/p4tools/common/lib/trace_event.h"
#include "lib/castable.h"
#include "lib/cstring.h"
#include "backends/p4tools/modules/testgen/lib/test_backend_configuration.h"
#include "backends/p4tools/modules/testgen/lib/test_object.h"
#include "backends/p4tools/modules/testgen/lib/test_spec.h"
namespace P4::P4Tools::P4Testgen {
using namespace P4::literals;
/// Type definitions for abstract tests.
struct AbstractTest : ICastable {};
/// TODO: It would be nice if this were a reference to signal non-nullness.
/// Consider using an optional_ref implementation.
using AbstractTestReference = const AbstractTest *;
using AbstractTestReferenceOrError = std::optional<AbstractTestReference>;
using AbstractTestList = std::vector<AbstractTestReference>;
/// Template to convert a list of abstract tests to a list of concretely typed tests.
/// Only works for subclasses of AbstractTest.
template <class ConcreteTest,
typename = std::enable_if_t<std::is_base_of_v<AbstractTest, ConcreteTest>>>
std::vector<const ConcreteTest *> convertAbstractTestsToConcreteTests(
const P4Tools::P4Testgen::AbstractTestList &testList) {
std::vector<const ConcreteTest *> result;
std::transform(testList.begin(), testList.end(), std::back_inserter(result),
[](AbstractTestReference test) { return test->checkedTo<ConcreteTest>(); });
return result;
}
/// An file path which may or may not be set. Can influence the execution behavior the test
/// framework.
using OptionalFilePath = std::optional<std::filesystem::path>;
/// The default base class for the various test frameworks. Every test framework has a test
/// name and a seed associated with it. Also contains a variety of common utility functions.
class TestFramework {
private:
/// Configuration options for the test back end.
std::reference_wrapper<const TestBackendConfiguration> testBackendConfiguration;
protected:
/// Creates a generic test framework.
explicit TestFramework(const TestBackendConfiguration &testBackendConfiguration);
/// Converts the traces of this test into a string representation and Inja object.
/// @param stripNewline Currently most test frameworks don't handle newlines in the trace well,
/// therefore we strip them by default.
static inja::json getTrace(const TestSpec *testSpec, bool stripNewline = true) {
inja::json traceList = inja::json::array();
const auto *traces = testSpec->getTraces();
if (traces != nullptr) {
for (const auto &trace : *traces) {
std::stringstream ss;
ss << trace;
std::string traceStr = ss.str();
if (stripNewline) {
traceStr.erase(std::remove(traceStr.begin(), traceStr.end(), '\n'),
traceStr.cend());
}
traceList.push_back(traceStr);
}
}
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"_cs, false);
if (apObject != nullptr) {
const auto *actionProfile = apObject->checkedTo<ProfileType>();
tblJson["has_ap"] = true;
tblJson["action_profile"] = actionProfile->getProfileDecl()->controlPlaneName();
// 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"_cs, false);
if (asObject != nullptr) {
const auto *actionSelector = asObject->checkedTo<SelectorType>();
apAsMap[actionProfile->getProfileDecl()->controlPlaneName()] =
actionSelector->getSelectorDecl()->controlPlaneName();
tblJson["has_as"] = true;
tblJson["action_selector"] = actionSelector->getSelectorDecl()->controlPlaneName();
}
}
}
/// 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"_cs, 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"_cs);
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);
}
}
/// Returns the configuration options for the test back end.
[[nodiscard]] const TestBackendConfiguration &getTestBackendConfiguration() const;
public:
virtual ~TestFramework() = default;
/// The method used to output the test case to be implemented by
/// all the test frameworks (eg. STF, PTF, etc.).
/// @param spec the testcase specification to be outputted.
/// @param selectedBranches string describing branches selected for this testcase.
/// @param testIdx testcase unique number identifier. TODO: Make this a member?
/// @param currentCoverage current coverage ratio (between 0.0 and 1.0)
/// TODO (https://github.com/p4lang/p4c/issues/4403): This should not return void but instead a
/// status.
virtual void writeTestToFile(const TestSpec *spec, cstring selectedBranches, size_t testIdx,
float currentCoverage) = 0;
/// The method used to return the test case. This method is optional to each test framework.
/// @param spec the testcase specification to be outputted.
/// @param selectedBranches string describing branches selected for this testcase.
/// @param testIdx testcase unique number identifier. TODO: Make this a member?
/// @param currentCoverage current coverage ratio (between 0.0 and 1.0).
virtual AbstractTestReferenceOrError produceTest(const TestSpec *spec, cstring selectedBranches,
size_t testIdx, float currentCoverage);
/// @Returns true if the test framework is configured to write to a file.
[[nodiscard]] bool isInFileMode() const;
};
} // namespace P4::P4Tools::P4Testgen
#endif /* BACKENDS_P4TOOLS_MODULES_TESTGEN_LIB_TEST_FRAMEWORK_H_ */