Skip to content

Commit b1c59b7

Browse files
committed
Add format subcommand to regression_test
1 parent e03f8a1 commit b1c59b7

5 files changed

Lines changed: 298 additions & 10 deletions

File tree

test/common/modsecurity_test.cc

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,18 +68,25 @@ bool ModSecurityTest<T>::load_test_json(const std::string &file) {
6868
return false;
6969
}
7070

71-
size_t num_tests = node->u.array.len;
72-
for ( int i = 0; i < num_tests; i++ ) {
73-
yajl_val obj = node->u.array.values[i];
74-
75-
auto u = std::unique_ptr<T>(T::from_yajl_node(obj));
71+
if (m_format) {
72+
auto u = std::unique_ptr<T>(T::from_yajl_node(node));
7673
u->filename = file;
7774

78-
const auto key = u->filename + ":" + u->name;
79-
(*this)[key].push_back(std::move(u));
80-
}
75+
(*this)[file].push_back(std::move(u));
76+
} else {
77+
size_t num_tests = node->u.array.len;
78+
for ( int i = 0; i < num_tests; i++ ) {
79+
yajl_val obj = node->u.array.values[i];
8180

82-
yajl_tree_free(node);
81+
auto u = std::unique_ptr<T>(T::from_yajl_node(obj));
82+
u->filename = file;
83+
84+
const auto key = u->filename + ":" + u->name;
85+
(*this)[key].push_back(std::move(u));
86+
}
87+
88+
yajl_tree_free(node);
89+
}
8390

8491
return true;
8592
}
@@ -140,6 +147,10 @@ void ModSecurityTest<T>::cmd_options(int argc, char **argv) {
140147
i++;
141148
m_test_multithreaded = true;
142149
}
150+
if (argc > i && strcmp(argv[i], "format") == 0) {
151+
i++;
152+
m_format = true;
153+
}
143154
if (std::getenv("AUTOMAKE_TESTS")) {
144155
m_automake_output = true;
145156
}

test/common/modsecurity_test.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ template <class T> class ModSecurityTest :
3535
: m_test_number(0),
3636
m_automake_output(false),
3737
m_count_all(false),
38-
m_test_multithreaded(false) { }
38+
m_test_multithreaded(false),
39+
m_format{false} { }
3940

4041
std::string header();
4142
void cmd_options(int, char **);
@@ -50,6 +51,7 @@ template <class T> class ModSecurityTest :
5051
bool m_automake_output;
5152
bool m_count_all;
5253
bool m_test_multithreaded;
54+
bool m_format;
5355
};
5456

5557
} // namespace modsecurity_test

test/regression/regression.cc

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ using modsecurity_test::CustomDebugLog;
4141
using modsecurity_test::ModSecurityTest;
4242
using modsecurity_test::ModSecurityTestResults;
4343
using modsecurity_test::RegressionTest;
44+
using modsecurity_test::RegressionTests;
4445
using modsecurity_test::RegressionTestResult;
4546

4647
using modsecurity::Utils::regex_search;
@@ -436,6 +437,32 @@ int main(int argc, char **argv)
436437
return 0;
437438
#else
438439
test.cmd_options(argc, argv);
440+
441+
if (test.m_format) {
442+
#ifdef WITH_YAJL
443+
std::cout << "start formatting test case JSON files" << std::endl;
444+
ModSecurityTest<RegressionTests> test;
445+
test.cmd_options(argc, argv);
446+
test.load_tests();
447+
for (const auto &[name, tests] : test) {
448+
std::ofstream ofs{name};
449+
if (!ofs.is_open()) {
450+
std::cerr << "cannot open " << name << " for writing." << std::endl;
451+
return 1;
452+
}
453+
ofs << tests[0]->toJSON();
454+
ofs.close();
455+
std::cout << "written formatted JSON to " << name << std::endl;
456+
}
457+
std::cout << "finished formatting files." << std::endl;
458+
return 0;
459+
#else
460+
std::cout << "Test utility cannot format test case JSON files without being built with YAJL." \
461+
<< std::endl;
462+
return 1;
463+
#endif
464+
}
465+
439466
if (!test.m_automake_output && !test.m_count_all) {
440467
std::cout << test.header();
441468
}

test/regression/regression_test.cc

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
#include <unordered_map>
2222
#include <string>
2323

24+
#ifdef WITH_YAJL
25+
#include <yajl/yajl_gen.h>
26+
#endif
27+
2428
namespace modsecurity_test {
2529

2630
std::string RegressionTest::print() {
@@ -218,4 +222,235 @@ RegressionTest *RegressionTest::from_yajl_node(const yajl_val &node) {
218222
return u;
219223
}
220224

225+
RegressionTests *RegressionTests::from_yajl_node(const yajl_val &node) {
226+
RegressionTests *u = new RegressionTests(node);
227+
size_t num_tests = node->u.array.len;
228+
for ( int i = 0; i < num_tests; i++ ) {
229+
yajl_val obj = node->u.array.values[i];
230+
u->tests.emplace_back(*RegressionTest::from_yajl_node(obj));
231+
}
232+
return u;
233+
}
234+
235+
RegressionTests::~RegressionTests() {
236+
#ifdef WITH_YAJL
237+
yajl_tree_free(node);
238+
#endif
239+
}
240+
241+
#ifdef WITH_YAJL
242+
243+
static yajl_gen_status jayl_gen_string_view(yajl_gen g, std::string_view s) {
244+
return yajl_gen_string(g, reinterpret_cast<const unsigned char *>(s.data()), s.length());
245+
}
246+
247+
static yajl_gen_status jayl_gen_key_val(yajl_gen g, std::string_view key, std::string_view val) {
248+
auto s = jayl_gen_string_view(g, key);
249+
if (s != yajl_gen_status_ok) {
250+
return s;
251+
}
252+
return jayl_gen_string_view(g, val);
253+
}
254+
255+
static yajl_gen_status copy_number(yajl_gen g, std::string_view key, yajl_val val) {
256+
if (!YAJL_IS_NUMBER(val)) {
257+
std::cerr << "error: " << key << " must be number.\n";
258+
exit(1);
259+
}
260+
auto s = jayl_gen_string_view(g, key);
261+
if (s != yajl_gen_status_ok) {
262+
return s;
263+
}
264+
return yajl_gen_number(g,
265+
reinterpret_cast<const char *>(val->u.number.r),
266+
strlen(val->u.number.r));
267+
}
268+
269+
static yajl_gen_status copy_string(yajl_gen g, std::string_view key, yajl_val val) {
270+
if (!YAJL_IS_STRING(val)) {
271+
std::cerr << "error: " << key << " must be string.\n";
272+
exit(1);
273+
}
274+
return jayl_gen_key_val(g, key, val->u.string);
275+
}
276+
277+
static void ensure_obj(std::string_view key, yajl_val obj) {
278+
if (!YAJL_IS_OBJECT(obj)) {
279+
std::cerr << "error: " << key << " must be object.\n";
280+
exit(1);
281+
}
282+
}
283+
284+
static void copy_str_map(yajl_gen g, std::string_view key, yajl_val val) {
285+
if (!YAJL_IS_OBJECT(val)) {
286+
std::cerr << "error: " << key << " must be object.\n";
287+
exit(1);
288+
}
289+
jayl_gen_string_view(g, key);
290+
yajl_gen_map_open(g);
291+
for (size_t i = 0; i < val->u.object.len; ++i) {
292+
const char *key2 = val->u.object.keys[i];
293+
yajl_val val2 = val->u.object.values[i];
294+
copy_string(g, key2, val2);
295+
}
296+
yajl_gen_map_close(g);
297+
}
298+
299+
static void copy_str_array(yajl_gen g, std::string_view key, yajl_val val) {
300+
if (!YAJL_IS_ARRAY(val)) {
301+
std::cerr << "error: " << key << " must be array.\n";
302+
exit(1);
303+
}
304+
jayl_gen_string_view(g, key);
305+
yajl_gen_array_open(g);
306+
for (size_t i = 0; i < val->u.array.len; ++i) {
307+
yajl_val val2 = val->u.array.values[i];
308+
if (!YAJL_IS_STRING(val2)) {
309+
std::cerr << "error: array element of " << key << " must be string.\n";
310+
exit(1);
311+
}
312+
jayl_gen_string_view(g, val2->u.string);
313+
}
314+
yajl_gen_array_close(g);
315+
}
316+
317+
static void copy_body(yajl_gen g, std::string_view key, yajl_val val) {
318+
if (YAJL_IS_STRING(val)) {
319+
jayl_gen_key_val(g, key, val->u.string);
320+
} else {
321+
copy_str_array(g, key, val);
322+
}
323+
}
324+
325+
std::string RegressionTests::toJSON() {
326+
const unsigned char *buf;
327+
size_t len;
328+
yajl_gen g;
329+
330+
g = yajl_gen_alloc(NULL);
331+
if (g == NULL) {
332+
return "";
333+
}
334+
yajl_gen_config(g, yajl_gen_beautify, 1);
335+
336+
if (!YAJL_IS_ARRAY(node)) {
337+
std::cerr << "error: toplevel must be array.\n";
338+
exit(1);
339+
}
340+
341+
yajl_gen_array_open(g);
342+
for (size_t i = 0; i < node->u.array.len; ++i) {
343+
yajl_val test_obj = node->u.array.values[i];
344+
ensure_obj("test", test_obj);
345+
yajl_gen_map_open(g);
346+
for (size_t j = 0; j < test_obj->u.object.len; ++j) {
347+
const char *key = test_obj->u.object.keys[j];
348+
yajl_val val = test_obj->u.object.values[j];
349+
if (strcmp(key, "enabled") == 0
350+
|| strcmp(key, "version_min") == 0
351+
|| strcmp(key, "version_max") == 0
352+
|| strcmp(key, "github_issue") == 0) {
353+
copy_number(g, key, val);
354+
} else if (strcmp(key, "title") == 0
355+
|| strcmp(key, "url") == 0
356+
|| strcmp(key, "resource") == 0) {
357+
copy_string(g, key, val);
358+
} else if (strcmp(key, "client") == 0) {
359+
ensure_obj("client", val);
360+
jayl_gen_string_view(g, "client");
361+
yajl_gen_map_open(g);
362+
for (size_t k = 0; k < val->u.object.len; ++k) {
363+
const char *key2 = val->u.object.keys[k];
364+
yajl_val val2 = val->u.object.values[k];
365+
if (strcmp(key2, "ip") == 0) {
366+
copy_string(g, key2, val2);
367+
} else if (strcmp(key2, "port") == 0) {
368+
copy_number(g, key2, val2);
369+
}
370+
}
371+
yajl_gen_map_close(g);
372+
} else if (strcmp(key, "server") == 0) {
373+
ensure_obj("server", val);
374+
jayl_gen_string_view(g, "server");
375+
yajl_gen_map_open(g);
376+
for (size_t k = 0; k < val->u.object.len; ++k) {
377+
const char *key2 = val->u.object.keys[k];
378+
yajl_val val2 = val->u.object.values[k];
379+
if (strcmp(key2, "ip") == 0
380+
|| strcmp(key2, "hostname") == 0) {
381+
copy_string(g, key2, val2);
382+
} else if (strcmp(key2, "port") == 0) {
383+
copy_number(g, key2, val2);
384+
}
385+
}
386+
yajl_gen_map_close(g);
387+
} else if (strcmp(key, "request") == 0) {
388+
ensure_obj("request", val);
389+
jayl_gen_string_view(g, "request");
390+
yajl_gen_map_open(g);
391+
for (size_t k = 0; k < val->u.object.len; ++k) {
392+
const char *key2 = val->u.object.keys[k];
393+
yajl_val val2 = val->u.object.values[k];
394+
if (strcmp(key2, "url") == 0
395+
|| strcmp(key2, "method") == 0) {
396+
copy_string(g, key2, val2);
397+
} else if (strcmp(key2, "http_version") == 0) {
398+
copy_number(g, key2, val2);
399+
} else if (strcmp(key2, "headers") == 0) {
400+
copy_str_map(g, key2, val2);
401+
} else if (strcmp(key2, "body") == 0) {
402+
copy_body(g, key2, val2);
403+
}
404+
}
405+
yajl_gen_map_close(g);
406+
} else if (strcmp(key, "response") == 0) {
407+
ensure_obj("response", val);
408+
jayl_gen_string_view(g, "response");
409+
yajl_gen_map_open(g);
410+
for (size_t k = 0; k < val->u.object.len; ++k) {
411+
const char *key2 = val->u.object.keys[k];
412+
yajl_val val2 = val->u.object.values[k];
413+
if (strcmp(key2, "protocol") == 0) {
414+
copy_string(g, key2, val2);
415+
} else if (strcmp(key2, "headers") == 0) {
416+
copy_str_map(g, key2, val2);
417+
} else if (strcmp(key2, "body") == 0) {
418+
copy_body(g, key2, val2);
419+
}
420+
}
421+
yajl_gen_map_close(g);
422+
} else if (strcmp(key, "expected") == 0) {
423+
ensure_obj("expected", val);
424+
jayl_gen_string_view(g, "expected");
425+
yajl_gen_map_open(g);
426+
for (size_t k = 0; k < val->u.object.len; ++k) {
427+
const char *key2 = val->u.object.keys[k];
428+
yajl_val val2 = val->u.object.values[k];
429+
if (strcmp(key2, "audit_log") == 0
430+
|| strcmp(key2, "debug_log") == 0
431+
|| strcmp(key2, "error_log") == 0
432+
|| strcmp(key2, "redirect_url") == 0
433+
|| strcmp(key2, "parser_error") == 0) {
434+
copy_string(g, key2, val2);
435+
} else if (strcmp(key2, "http_code") == 0) {
436+
copy_number(g, key2, val2);
437+
}
438+
}
439+
yajl_gen_map_close(g);
440+
} else if (strcmp(key, "rules") == 0) {
441+
copy_str_array(g, key, val);
442+
}
443+
}
444+
yajl_gen_map_close(g);
445+
}
446+
yajl_gen_array_close(g);
447+
448+
yajl_gen_get_buf(g, &buf, &len);
449+
std::string s{reinterpret_cast<const char*>(buf), len};
450+
yajl_gen_free(g);
451+
return s;
452+
}
453+
454+
#endif // WITH_YAJL
455+
221456
} // namespace modsecurity_test

test/regression/regression_test.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,19 @@ class RegressionTest {
7878
std::string redirect_url;
7979
};
8080

81+
class RegressionTests {
82+
public:
83+
RegressionTests(const yajl_val &node) : node{node} {}
84+
~RegressionTests();
85+
static RegressionTests *from_yajl_node(const yajl_val &);
86+
std::string toJSON();
87+
88+
std::string filename;
89+
std::string name;
90+
91+
std::vector<RegressionTest> tests;
92+
const yajl_val node;
93+
};
8194

8295
class RegressionTestResult {
8396
public:

0 commit comments

Comments
 (0)