Files
2025-08-07 19:18:40 +08:00

313 lines
7.2 KiB
Go

package expr
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestParseExpression 测试表达式解析功能
func TestParseExpression(t *testing.T) {
tests := []struct {
name string
tokens []string
expectError bool
description string
}{
{
name: "empty tokens",
tokens: []string{},
expectError: true,
description: "should return error for empty tokens",
},
{
name: "valid simple expression",
tokens: []string{"field1"},
expectError: false,
description: "should parse simple field",
},
{
name: "valid case expression",
tokens: []string{"CASE", "WHEN", "field1", "THEN", "1", "END"},
expectError: false,
description: "should parse CASE expression",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := ParseExpression(tt.tokens)
if tt.expectError && err == nil {
t.Errorf("expected error but got none: %s", tt.description)
}
if !tt.expectError && err != nil {
t.Errorf("unexpected error: %v", err)
}
})
}
}
func TestParseUnaryExpression(t *testing.T) {
tests := []struct {
name string
tokens []string
expectError bool
description string
}{
{
name: "empty tokens",
tokens: []string{},
expectError: true,
description: "should return error for empty tokens",
},
{
name: "unary minus with number",
tokens: []string{"-", "5"},
expectError: false,
description: "should parse unary minus with number",
},
{
name: "unary minus with field",
tokens: []string{"-", "field1"},
expectError: false,
description: "should parse unary minus with field",
},
{
name: "unary minus with expression",
tokens: []string{"-", "(", "field1", "+", "field2", ")"},
expectError: false,
description: "should parse unary minus with expression",
},
{
name: "unary minus with function",
tokens: []string{"-", "sum", "(", "field1", ")"},
expectError: false,
description: "should parse unary minus with function",
},
{
name: "unary minus with string",
tokens: []string{"-", "'value'"},
expectError: false,
description: "should parse unary minus with string",
},
{
name: "unary minus with missing operand",
tokens: []string{"-"},
expectError: true,
description: "should return error for missing operand",
},
{
name: "nested unary minus",
tokens: []string{"-", "-", "5"},
expectError: false,
description: "should parse nested unary minus",
},
{
name: "unary minus with complex expression",
tokens: []string{"-", "field1", "*", "field2"},
expectError: false,
description: "should parse unary minus with complex expression",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, _, err := parseUnaryExpression(tt.tokens)
if tt.expectError && err == nil {
t.Errorf("expected error but got none: %s", tt.description)
}
if !tt.expectError && err != nil {
t.Errorf("unexpected error: %v", err)
}
})
}
}
// TestParseExpressionWithPrecedence 测试运算符优先级解析
func TestParseExpressionWithPrecedence(t *testing.T) {
tests := []struct {
name string
tokens []string
expected string // 用字符串表示预期的树结构
}{
{"加法和乘法", []string{"a", "+", "b", "*", "c"}, "(a + (b * c))"},
{"乘法和除法", []string{"a", "*", "b", "/", "c"}, "((a * b) / c)"},
{"幂运算", []string{"a", "^", "b", "^", "c"}, "(a ^ (b ^ c))"},
{"混合运算", []string{"a", "+", "b", "*", "c", "^", "d"}, "(a + (b * (c ^ d)))"},
{"比较运算符", []string{"a", "+", "b", ">", "c"}, "((a + b) > c)"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := parseExpression(tt.tokens)
require.NoError(t, err, "解析不应该失败")
actual := nodeToString(result)
assert.Equal(t, tt.expected, actual, "运算符优先级应该正确")
})
}
}
// TestParseFunction 测试函数解析
func TestParseFunction(t *testing.T) {
tests := []struct {
name string
tokens []string
pos int
expected *ExprNode
newPos int
wantErr bool
}{
{
"无参数函数",
[]string{"now", "(", ")"},
0,
&ExprNode{Type: TypeFunction, Value: "now", Args: []*ExprNode{}},
3,
false,
},
{
"单参数函数",
[]string{"abs", "(", "x", ")"},
0,
&ExprNode{
Type: TypeFunction,
Value: "abs",
Args: []*ExprNode{{Type: TypeField, Value: "x"}},
},
4,
false,
},
{
"多参数函数",
[]string{"max", "(", "a", ",", "b", ",", "c", ")"},
0,
&ExprNode{
Type: TypeFunction,
Value: "max",
Args: []*ExprNode{
{Type: TypeField, Value: "a"},
{Type: TypeField, Value: "b"},
{Type: TypeField, Value: "c"},
},
},
8,
false,
},
{
"嵌套函数",
[]string{"sqrt", "(", "pow", "(", "x", ",", "2", ")", ")"},
0,
&ExprNode{
Type: TypeFunction,
Value: "sqrt",
Args: []*ExprNode{
{
Type: TypeFunction,
Value: "pow",
Args: []*ExprNode{
{Type: TypeField, Value: "x"},
{Type: TypeNumber, Value: "2"},
},
},
},
},
9,
false,
},
// 错误情况
{"缺少左括号", []string{"abs", "x", ")"}, 0, nil, 0, true},
{"缺少右括号", []string{"abs", "(", "x"}, 0, nil, 0, true},
{"参数分隔符错误", []string{"max", "(", "a", ";", "b", ")"}, 0, nil, 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, newPos, err := parseFunction(tt.tokens, tt.pos)
if tt.wantErr {
assert.Error(t, err, "应该返回错误")
} else {
require.NoError(t, err, "函数解析不应该失败")
assert.Equal(t, tt.newPos, newPos, "位置应该正确")
assertNodeEqual(t, tt.expected, result)
}
})
}
}
// TestGetOperatorPrecedence 测试运算符优先级获取
func TestGetOperatorPrecedence(t *testing.T) {
tests := []struct {
op string
expected int
}{
{"OR", 1},
{"AND", 2},
{"NOT", 3},
{"=", 4},
{"==", 4},
{"!=", 4},
{"<>", 4},
{"<", 4},
{">", 4},
{"<=", 4},
{">=", 4},
{"LIKE", 4},
{"IS", 4},
{"+", 5},
{"-", 5},
{"*", 6},
{"/", 6},
{"%", 6},
{"^", 7},
{"unknown", 0},
}
for _, tt := range tests {
t.Run(tt.op, func(t *testing.T) {
result := getOperatorPrecedence(tt.op)
assert.Equal(t, tt.expected, result, "运算符优先级应该正确")
})
}
}
// TestIsRightAssociative 测试右结合性判断
func TestIsRightAssociative(t *testing.T) {
tests := []struct {
op string
expected bool
}{
{"^", true},
{"+", false},
{"-", false},
{"*", false},
{"/", false},
{"=", false},
{"AND", false},
{"OR", false},
}
for _, tt := range tests {
t.Run(tt.op, func(t *testing.T) {
result := isRightAssociative(tt.op)
assert.Equal(t, tt.expected, result, "右结合性判断应该正确")
})
}
}
// 辅助函数:连接字符串数组
func joinStrings(strs []string, sep string) string {
if len(strs) == 0 {
return ""
}
if len(strs) == 1 {
return strs[0]
}
result := strs[0]
for i := 1; i < len(strs); i++ {
result += sep + strs[i]
}
return result
}