1515
1616#include < string.h>
1717#include < cstring>
18-
18+ #include < cassert>
19+ #include < thread>
20+ #include < array>
1921#include < iostream>
2022#include < ctime>
2123#include < string>
3840
3941
4042using modsecurity_test::UnitTest;
43+ using modsecurity_test::UnitTestResult;
4144using modsecurity_test::ModSecurityTest;
4245using modsecurity_test::ModSecurityTestResults;
4346using modsecurity::actions::transformations::Transformation;
@@ -53,64 +56,149 @@ void print_help() {
5356}
5457
5558
56- void perform_unit_test (ModSecurityTest<UnitTest> *test, UnitTest *t,
57- ModSecurityTestResults<UnitTest>* res) {
58- std::string error;
59+ struct OperatorTest {
60+ using ItemType = Operator;
61+
62+ static ItemType* init (const UnitTest &t) {
63+ auto op = Operator::instantiate (t.name , t.param );
64+ assert (op != nullptr );
65+
66+ std::string error;
67+ op->init (t.filename , &error);
68+
69+ return op;
70+ }
71+
72+ static UnitTestResult eval (ItemType &op, const UnitTest &t) {
73+ return {op.evaluate (nullptr , nullptr , t.input , nullptr ), {}};
74+ }
75+
76+ static bool check (const UnitTestResult &result, const UnitTest &t) {
77+ return result.ret != t.ret ;
78+ }
79+ };
80+
81+
82+ struct TransformationTest {
83+ using ItemType = Transformation;
84+
85+ static ItemType* init (const UnitTest &t) {
86+ auto tfn = Transformation::instantiate (" t:" + t.name );
87+ assert (tfn != nullptr );
88+
89+ return tfn;
90+ }
91+
92+ static UnitTestResult eval (ItemType &tfn, const UnitTest &t) {
93+ return {1 , tfn.evaluate (t.input , nullptr )};
94+ }
95+
96+ static bool check (const UnitTestResult &result, const UnitTest &t) {
97+ return result.output != t.output ;
98+ }
99+ };
100+
101+
102+ template <typename TestType>
103+ UnitTestResult perform_unit_test_once (const UnitTest &t) {
104+ std::unique_ptr<typename TestType::ItemType> item (TestType::init (t));
105+ assert (item.get () != nullptr );
106+
107+ return TestType::eval (*item.get (), t);
108+ }
109+
110+
111+ template <typename TestType>
112+ UnitTestResult perform_unit_test_multithreaded (const UnitTest &t) {
113+
114+ constexpr auto NUM_THREADS = 50 ;
115+ constexpr auto ITERATIONS = 5'000 ;
116+
117+ std::array<std::thread, NUM_THREADS> threads;
118+ std::array<UnitTestResult, NUM_THREADS> results;
119+
120+ std::unique_ptr<typename TestType::ItemType> item (TestType::init (t));
121+ assert (item.get () != nullptr );
122+
123+ for (auto i = 0 ; i != threads.size (); ++i)
124+ {
125+ auto &result = results[i];
126+ threads[i] = std::thread (
127+ [&item, &t, &result]()
128+ {
129+ for (auto j = 0 ; j != ITERATIONS; ++j)
130+ result = TestType::eval (*item.get (), t);
131+ });
132+ }
133+
134+ UnitTestResult ret;
135+
136+ for (auto i = 0 ; i != threads.size (); ++i)
137+ {
138+ threads[i].join ();
139+ if (TestType::check (results[i], t))
140+ ret = results[i]; // error value, keep iterating to join all threads
141+ else if (i == 0 )
142+ ret = results[i]; // initial value
143+ }
144+
145+ return ret; // cppcheck-suppress uninitvar ; false positive, ret assigned at least once in previous loop
146+ }
147+
148+
149+ template <typename TestType>
150+ void perform_unit_test_helper (const ModSecurityTest<UnitTest> &test, UnitTest &t,
151+ ModSecurityTestResults<UnitTest> &res) {
152+
153+ if (!test.m_test_multithreaded )
154+ t.result = perform_unit_test_once<TestType>(t);
155+ else
156+ t.result = perform_unit_test_multithreaded<TestType>(t);
157+
158+ if (TestType::check (t.result , t)) {
159+ res.push_back (&t);
160+ if (test.m_automake_output ) {
161+ std::cout << " FAIL " ;
162+ }
163+ } else if (test.m_automake_output ) {
164+ std::cout << " PASS " ;
165+ }
166+ }
167+
168+
169+ void perform_unit_test (const ModSecurityTest<UnitTest> &test, UnitTest &t,
170+ ModSecurityTestResults<UnitTest> &res) {
59171 bool found = true ;
60172
61- if (test-> m_automake_output ) {
173+ if (test. m_automake_output ) {
62174 std::cout << " :test-result: " ;
63175 }
64176
65- if (t-> resource .empty () == false ) {
66- found = ( std::find (resources.begin (), resources.end (), t-> resource )
67- != resources.end ()) ;
177+ if (t. resource .empty () == false ) {
178+ found = std::find (resources.begin (), resources.end (), t. resource )
179+ != resources.end ();
68180 }
69181
70182 if (!found) {
71- t-> skipped = true ;
72- res-> push_back (t);
73- if (test-> m_automake_output ) {
183+ t. skipped = true ;
184+ res. push_back (& t);
185+ if (test. m_automake_output ) {
74186 std::cout << " SKIP " ;
75187 }
76188 }
77189
78- if (t->type == " op" ) {
79- Operator *op = Operator::instantiate (t->name , t->param );
80- op->init (t->filename , &error);
81- int ret = op->evaluate (NULL , NULL , t->input , NULL );
82- t->obtained = ret;
83- if (ret != t->ret ) {
84- res->push_back (t);
85- if (test->m_automake_output ) {
86- std::cout << " FAIL " ;
87- }
88- } else if (test->m_automake_output ) {
89- std::cout << " PASS " ;
90- }
91- delete op;
92- } else if (t->type == " tfn" ) {
93- Transformation *tfn = Transformation::instantiate (" t:" + t->name );
94- std::string ret = tfn->evaluate (t->input , NULL );
95- t->obtained = 1 ;
96- t->obtainedOutput = ret;
97- if (ret != t->output ) {
98- res->push_back (t);
99- if (test->m_automake_output ) {
100- std::cout << " FAIL " ;
101- }
102- } else if (test->m_automake_output ) {
103- std::cout << " PASS " ;
104- }
105- delete tfn;
190+ if (t.type == " op" ) {
191+ perform_unit_test_helper<OperatorTest>(test, t, res);
192+ } else if (t.type == " tfn" ) {
193+ perform_unit_test_helper<TransformationTest>(test, t, res);
106194 } else {
107- std::cerr << " Failed. Test type is unknown: << " << t-> type ;
195+ std::cerr << " Failed. Test type is unknown: << " << t. type ;
108196 std::cerr << std::endl;
109197 }
110198
111- if (test-> m_automake_output ) {
112- std::cout << t-> name << " "
113- << modsecurity::utils::string::toHexIfNeeded (t-> input )
199+ if (test. m_automake_output ) {
200+ std::cout << t. name << " "
201+ << modsecurity::utils::string::toHexIfNeeded (t. input )
114202 << std::endl;
115203 }
116204}
@@ -151,17 +239,15 @@ int main(int argc, char **argv) {
151239 test.load_tests (" test-cases/secrules-language-tests/transformations" );
152240 }
153241
154- for (std::pair<std::string, std::vector<UnitTest *> *> a : test) {
155- std::vector<UnitTest *> *tests = a.second ;
156-
242+ for (auto & [filename, tests] : test) {
157243 total += tests->size ();
158- for (UnitTest * t : *tests) {
244+ for (auto t : *tests) {
159245 ModSecurityTestResults<UnitTest> r;
160246
161247 if (!test.m_automake_output ) {
162- std::cout << " " << a. first << " ...\t " ;
248+ std::cout << " " << filename << " ...\t " ;
163249 }
164- perform_unit_test (& test, t, & r);
250+ perform_unit_test (test, * t, r);
165251
166252 if (!test.m_automake_output ) {
167253 int skp = 0 ;
@@ -191,7 +277,7 @@ int main(int argc, char **argv) {
191277 std::cout << " Total >> " << total << std::endl;
192278 }
193279
194- for (UnitTest * t : results) {
280+ for (const auto t : results) {
195281 std::cout << t->print () << std::endl;
196282 }
197283
@@ -216,8 +302,8 @@ int main(int argc, char **argv) {
216302 }
217303
218304 for (auto a : test) {
219- auto * vec = a.second ;
220- for (auto * t : *vec)
305+ auto vec = a.second ;
306+ for (auto t : *vec)
221307 delete t;
222308 delete vec;
223309 }
0 commit comments