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
|
package funcs
import "testing"
func TestFunctionStackValid(t *testing.T) {
t.Parallel()
type want struct {
arg string
// result of calling the returned function stack on the original input
callResult string
}
cases := []struct {
input string
want want
}{
{
input: "md5sum($line)",
want: want{arg: "$line", callResult: "b38699013d79e50d9d122433753959c1"},
},
{
input: "maskdigits(md5sum(maskdigits($line)))",
want: want{arg: "$line", callResult: ".fac.bbe..bb.........d...a.c..b."},
},
{
// An argument containing nested parens that are balanced is valid.
input: "md5sum($foo)",
want: want{arg: "$foo"},
},
{
// Plain field with no function wrapper is a degenerate stack (empty).
input: "$line",
want: want{arg: "$line"},
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.input, func(t *testing.T) {
t.Parallel()
fs, arg, err := NewFunctionStack(tc.input)
if err != nil {
t.Fatalf("unexpected error for input %q: %v (stack %v)", tc.input, err, fs)
}
if arg != tc.want.arg {
t.Errorf("arg: got %q, want %q", arg, tc.want.arg)
}
if tc.want.callResult != "" {
got := fs.Call(tc.input)
if got != tc.want.callResult {
t.Errorf("Call(%q) = %q, want %q", tc.input, got, tc.want.callResult)
}
}
})
}
}
// TestFunctionStackMalformed verifies that NewFunctionStack rejects expressions
// that are structurally invalid. Before the fix, several of these were silently
// accepted and produced wrong results.
func TestFunctionStackMalformed(t *testing.T) {
t.Parallel()
cases := []string{
// Missing opening paren — no function call syntax at all.
"md5sum$line)",
// Known outer function but inner call is missing its closing paren.
"md5sum(makedigits$line))",
// Stray ')' inside the argument after stripping: "bar)baz" remains.
// Before the fix this was silently accepted and produced wrong output.
"md5sum(bar)baz)",
// Input ends with '(' — no closing ')' so the loop never strips,
// but the argument string itself contains an unclosed '('.
// Before the fix this was accepted as a plain field literal.
"foo(",
// Empty outer call — the name portion is empty (index == 0) which
// is caught by the existing index <= 0 guard.
"()",
}
for _, input := range cases {
input := input
t.Run(input, func(t *testing.T) {
t.Parallel()
fs, _, err := NewFunctionStack(input)
if err == nil {
t.Errorf("expected error for malformed input %q but got none (stack %v)", input, fs)
}
})
}
}
|