Skip to content

Commit 5a480f0

Browse files
committed
test(engine): add unit tests for multi-value transformer AST visitors
Signed-off-by: Roberto Scolaro <roberto.scolaro21@gmail.com>
1 parent a3d7dbe commit 5a480f0

3 files changed

Lines changed: 254 additions & 0 deletions

File tree

unit_tests/engine/test_filter_details_resolver.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,101 @@ TEST(DetailsResolver, resolve_ast) {
4848
ASSERT_EQ(details.lists.size(), 1);
4949
ASSERT_NE(details.lists.find("known_procs"), details.lists.end());
5050
}
51+
52+
// Tests for multi-value transformer support
53+
54+
TEST(DetailsResolver, resolve_single_value_transformer) {
55+
namespace ast = libsinsp::filter::ast;
56+
57+
// Build: tolower(proc.name) = nginx
58+
auto filter = ast::binary_check_expr::create(
59+
ast::field_transformer_expr::create("tolower",
60+
ast::field_expr::create("proc.name", "")),
61+
"=",
62+
ast::value_expr::create("nginx"));
63+
64+
filter_details details;
65+
filter_details_resolver resolver;
66+
resolver.run(filter.get(), details);
67+
68+
ASSERT_EQ(details.fields.size(), 1);
69+
ASSERT_NE(details.fields.find("proc.name"), details.fields.end());
70+
ASSERT_EQ(details.transformers.size(), 1);
71+
ASSERT_NE(details.transformers.find("tolower"), details.transformers.end());
72+
ASSERT_EQ(details.operators.size(), 1);
73+
ASSERT_NE(details.operators.find("="), details.operators.end());
74+
}
75+
76+
TEST(DetailsResolver, resolve_multi_value_transformer) {
77+
namespace ast = libsinsp::filter::ast;
78+
79+
// Build: concat(proc.name, proc.pname) = value
80+
std::vector<std::unique_ptr<ast::expr>> args;
81+
args.push_back(ast::field_expr::create("proc.name", ""));
82+
args.push_back(ast::field_expr::create("proc.pname", ""));
83+
auto filter =
84+
ast::binary_check_expr::create(ast::field_transformer_expr::create("concat", args),
85+
"=",
86+
ast::value_expr::create("value"));
87+
88+
filter_details details;
89+
filter_details_resolver resolver;
90+
resolver.run(filter.get(), details);
91+
92+
ASSERT_EQ(details.fields.size(), 2);
93+
ASSERT_NE(details.fields.find("proc.name"), details.fields.end());
94+
ASSERT_NE(details.fields.find("proc.pname"), details.fields.end());
95+
ASSERT_EQ(details.transformers.size(), 1);
96+
ASSERT_NE(details.transformers.find("concat"), details.transformers.end());
97+
}
98+
99+
TEST(DetailsResolver, resolve_transformer_with_list) {
100+
namespace ast = libsinsp::filter::ast;
101+
102+
// Build: join(",", (proc.name, proc.pid)) = value
103+
std::vector<std::unique_ptr<ast::expr>> list_children;
104+
list_children.push_back(ast::field_expr::create("proc.name", ""));
105+
list_children.push_back(ast::field_expr::create("proc.pid", ""));
106+
107+
std::vector<std::unique_ptr<ast::expr>> transformer_args;
108+
transformer_args.push_back(ast::value_expr::create(","));
109+
transformer_args.push_back(ast::transformer_list_expr::create(list_children));
110+
111+
auto filter = ast::binary_check_expr::create(
112+
ast::field_transformer_expr::create("join", transformer_args),
113+
"=",
114+
ast::value_expr::create("value"));
115+
116+
filter_details details;
117+
filter_details_resolver resolver;
118+
resolver.run(filter.get(), details);
119+
120+
ASSERT_EQ(details.fields.size(), 2);
121+
ASSERT_NE(details.fields.find("proc.name"), details.fields.end());
122+
ASSERT_NE(details.fields.find("proc.pid"), details.fields.end());
123+
ASSERT_EQ(details.transformers.size(), 1);
124+
ASSERT_NE(details.transformers.find("join"), details.transformers.end());
125+
}
126+
127+
TEST(DetailsResolver, resolve_nested_transformers) {
128+
namespace ast = libsinsp::filter::ast;
129+
130+
// Build: toupper(tolower(proc.name)) = value
131+
auto filter = ast::binary_check_expr::create(
132+
ast::field_transformer_expr::create(
133+
"toupper",
134+
ast::field_transformer_expr::create("tolower",
135+
ast::field_expr::create("proc.name", ""))),
136+
"=",
137+
ast::value_expr::create("value"));
138+
139+
filter_details details;
140+
filter_details_resolver resolver;
141+
resolver.run(filter.get(), details);
142+
143+
ASSERT_EQ(details.fields.size(), 1);
144+
ASSERT_NE(details.fields.find("proc.name"), details.fields.end());
145+
ASSERT_EQ(details.transformers.size(), 2);
146+
ASSERT_NE(details.transformers.find("toupper"), details.transformers.end());
147+
ASSERT_NE(details.transformers.find("tolower"), details.transformers.end());
148+
}

unit_tests/engine/test_filter_macro_resolver.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,3 +304,82 @@ TEST(MacroResolver, should_clone_macro_AST) {
304304
macro->left = filter_ast::field_expr::create("another.field", "");
305305
ASSERT_FALSE(filter->is_equal(macro.get()));
306306
}
307+
308+
// Tests for multi-value transformer support
309+
310+
TEST(MacroResolver, should_not_resolve_macro_inside_field_transformer) {
311+
namespace ast = libsinsp::filter::ast;
312+
313+
// Build: tolower(some.field) = value AND test_macro
314+
// The transformer wraps a field, not a macro -- no resolution inside it.
315+
ast::pos_info macro_pos(1, 0, 10);
316+
317+
std::shared_ptr<ast::expr> macro =
318+
ast::unary_check_expr::create(ast::field_expr::create("resolved.field", ""), "exists");
319+
320+
std::vector<std::unique_ptr<ast::expr>> filter_and;
321+
filter_and.push_back(ast::binary_check_expr::create(
322+
ast::field_transformer_expr::create("tolower",
323+
ast::field_expr::create("some.field", "")),
324+
"=",
325+
ast::value_expr::create("value")));
326+
filter_and.push_back(ast::identifier_expr::create(MACRO_NAME, macro_pos));
327+
std::shared_ptr<ast::expr> filter = ast::and_expr::create(filter_and);
328+
329+
filter_macro_resolver resolver;
330+
resolver.set_macro(MACRO_NAME, macro);
331+
332+
ASSERT_TRUE(resolver.run(filter));
333+
ASSERT_EQ(resolver.get_resolved_macros().size(), 1);
334+
ASSERT_STREQ(resolver.get_resolved_macros().begin()->first.c_str(), MACRO_NAME);
335+
ASSERT_TRUE(resolver.get_unknown_macros().empty());
336+
}
337+
338+
TEST(MacroResolver, should_not_resolve_macro_inside_multi_value_transformer) {
339+
namespace ast = libsinsp::filter::ast;
340+
341+
// Build: join(",", field1, field2) = value
342+
// Multi-value transformer with 3 args -- no macro resolution inside.
343+
std::vector<std::unique_ptr<ast::expr>> args;
344+
args.push_back(ast::value_expr::create(","));
345+
args.push_back(ast::field_expr::create("proc.name", ""));
346+
args.push_back(ast::field_expr::create("proc.pname", ""));
347+
348+
std::shared_ptr<ast::expr> filter =
349+
ast::binary_check_expr::create(ast::field_transformer_expr::create("join", args),
350+
"=",
351+
ast::value_expr::create("value"));
352+
353+
filter_macro_resolver resolver;
354+
resolver.set_macro(MACRO_NAME, ast::field_expr::create("x", ""));
355+
356+
ASSERT_FALSE(resolver.run(filter));
357+
ASSERT_TRUE(resolver.get_resolved_macros().empty());
358+
ASSERT_TRUE(resolver.get_unknown_macros().empty());
359+
}
360+
361+
TEST(MacroResolver, should_not_resolve_macro_inside_transformer_list) {
362+
namespace ast = libsinsp::filter::ast;
363+
364+
// Build: join(",", (field1, field2)) = value
365+
// transformer_list_expr wraps multiple fields -- no macro resolution inside.
366+
std::vector<std::unique_ptr<ast::expr>> list_children;
367+
list_children.push_back(ast::field_expr::create("proc.name", ""));
368+
list_children.push_back(ast::field_expr::create("proc.pname", ""));
369+
370+
std::vector<std::unique_ptr<ast::expr>> transformer_args;
371+
transformer_args.push_back(ast::value_expr::create(","));
372+
transformer_args.push_back(ast::transformer_list_expr::create(list_children));
373+
374+
std::shared_ptr<ast::expr> filter = ast::binary_check_expr::create(
375+
ast::field_transformer_expr::create("join", transformer_args),
376+
"=",
377+
ast::value_expr::create("value"));
378+
379+
filter_macro_resolver resolver;
380+
resolver.set_macro(MACRO_NAME, ast::field_expr::create("x", ""));
381+
382+
ASSERT_FALSE(resolver.run(filter));
383+
ASSERT_TRUE(resolver.get_resolved_macros().empty());
384+
ASSERT_TRUE(resolver.get_unknown_macros().empty());
385+
}

unit_tests/engine/test_filter_warning_resolver.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,80 @@ TEST(WarningResolver, warnings_in_filtering_conditions) {
4545
ASSERT_TRUE(warns("proc.name=test and evt.dir = <"));
4646
ASSERT_TRUE(warns("evt.dir = < and proc.name=test"));
4747
}
48+
49+
// Helper for programmatically-built ASTs (multi-value transformers can't be parsed yet)
50+
static bool warns_ast(libsinsp::filter::ast::expr& ast) {
51+
rule_loader::context ctx("test");
52+
rule_loader::result res("test");
53+
filter_warning_resolver().run(ctx, res, ast);
54+
return res.has_warnings();
55+
}
56+
57+
TEST(WarningResolver, warnings_with_transformer_wrapping_unsafe_field) {
58+
namespace ast = libsinsp::filter::ast;
59+
60+
// tolower(ka.field) = <NA> -- unsafe field inside transformer, should warn
61+
auto filter = ast::binary_check_expr::create(
62+
ast::field_transformer_expr::create("tolower", ast::field_expr::create("ka.field", "")),
63+
"=",
64+
ast::value_expr::create("<NA>"));
65+
ASSERT_TRUE(warns_ast(*filter));
66+
67+
// tolower(safe.field) = <NA> -- safe field inside transformer, should NOT warn
68+
auto filter2 = ast::binary_check_expr::create(
69+
ast::field_transformer_expr::create("tolower",
70+
ast::field_expr::create("safe.field", "")),
71+
"=",
72+
ast::value_expr::create("<NA>"));
73+
ASSERT_FALSE(warns_ast(*filter2));
74+
}
75+
76+
TEST(WarningResolver, warnings_with_multi_value_transformer) {
77+
namespace ast = libsinsp::filter::ast;
78+
79+
// concat(ka.field, other.field) = <NA> -- has unsafe field, but the warning
80+
// resolver traverses through the transformer's values via base_expr_visitor
81+
// defaults. The field_expr visit for ka.field sets m_last_node_is_unsafe_field,
82+
// but since binary_check_expr only checks the direct left child (the transformer),
83+
// the unsafe field detection depends on traversal order.
84+
// The base_expr_visitor default for field_transformer_expr iterates e->values,
85+
// which will visit field_expr nodes. The last field_expr visited determines
86+
// m_last_node_is_unsafe_field state.
87+
std::vector<std::unique_ptr<ast::expr>> args;
88+
args.push_back(ast::field_expr::create("safe.field", ""));
89+
args.push_back(ast::field_expr::create("ka.field", ""));
90+
auto filter =
91+
ast::binary_check_expr::create(ast::field_transformer_expr::create("concat", args),
92+
"=",
93+
ast::value_expr::create("<NA>"));
94+
// The base_expr_visitor will traverse into the transformer's values,
95+
// and the last field_expr visited (ka.field) will set the unsafe flag.
96+
// However, the warning resolver only overrides binary_check_expr to
97+
// check the left side, and the base default will visit the transformer's
98+
// children. Since field_expr for ka.field sets m_last_node_is_unsafe_field,
99+
// this should trigger the warning.
100+
ASSERT_TRUE(warns_ast(*filter));
101+
}
102+
103+
TEST(WarningResolver, no_crash_with_transformer_list) {
104+
namespace ast = libsinsp::filter::ast;
105+
106+
// join(",", (ka.field, safe.field)) = <NA>
107+
std::vector<std::unique_ptr<ast::expr>> list_children;
108+
list_children.push_back(ast::field_expr::create("ka.field", ""));
109+
list_children.push_back(ast::field_expr::create("safe.field", ""));
110+
111+
std::vector<std::unique_ptr<ast::expr>> transformer_args;
112+
transformer_args.push_back(ast::value_expr::create(","));
113+
transformer_args.push_back(ast::transformer_list_expr::create(list_children));
114+
115+
auto filter = ast::binary_check_expr::create(
116+
ast::field_transformer_expr::create("join", transformer_args),
117+
"=",
118+
ast::value_expr::create("<NA>"));
119+
120+
// Should not crash -- the base_expr_visitor default for transformer_list_expr
121+
// is a no-op, so ka.field inside it won't be visited for warning detection.
122+
// This is acceptable behavior for now.
123+
ASSERT_NO_FATAL_FAILURE(warns_ast(*filter));
124+
}

0 commit comments

Comments
 (0)