forked from GiteaTest2015/streamsql
353 lines
8.8 KiB
Go
353 lines
8.8 KiB
Go
package expr
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestTokenize 测试分词功能
|
|
func TestTokenize(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
expr string
|
|
expected []string
|
|
wantErr bool
|
|
}{
|
|
// 基本分词测试
|
|
{"简单表达式", "a + b", []string{"a", "+", "b"}, false},
|
|
{"数字和运算符", "123 + 456", []string{"123", "+", "456"}, false},
|
|
{"小数", "3.14 * 2", []string{"3.14", "*", "2"}, false},
|
|
{"负数", "-5 + 3", []string{"-5", "+", "3"}, false},
|
|
{"负小数", "-3.14 * 2", []string{"-3.14", "*", "2"}, false},
|
|
|
|
// 括号和函数
|
|
{"括号表达式", "(a + b) * c", []string{"(", "a", "+", "b", ")", "*", "c"}, false},
|
|
{"函数调用", "abs(x)", []string{"abs", "(", "x", ")"}, false},
|
|
{"函数参数", "max(a, b)", []string{"max", "(", "a", ",", "b", ")"}, false},
|
|
|
|
// 比较运算符
|
|
{"等于运算符", "a == b", []string{"a", "==", "b"}, false},
|
|
{"不等于运算符", "a != b", []string{"a", "!=", "b"}, false},
|
|
{"大于等于", "a >= b", []string{"a", ">=", "b"}, false},
|
|
{"小于等于", "a <= b", []string{"a", "<=", "b"}, false},
|
|
{"不等于SQL风格", "a <> b", []string{"a", "<>", "b"}, false},
|
|
|
|
// 字符串字面量
|
|
{"单引号字符串", "'hello'", []string{"'hello'"}, false},
|
|
{"双引号字符串", "\"world\"", []string{"\"world\""}, false},
|
|
{"字符串比较", "name == 'test'", []string{"name", "==", "'test'"}, false},
|
|
{"包含转义的字符串", "'hello\\world'", []string{"'hello\\world'"}, false},
|
|
|
|
// 反引号标识符
|
|
{"反引号字段", "`field name`", []string{"`field name`"}, false},
|
|
{"反引号表达式", "`user.name` + `user.age`", []string{"`user.name`", "+", "`user.age`"}, false},
|
|
|
|
// CASE表达式
|
|
{"简单CASE", "CASE WHEN a > 0 THEN 1 ELSE 0 END", []string{"CASE", "WHEN", "a", ">", "0", "THEN", "1", "ELSE", "0", "END"}, false},
|
|
|
|
// 复杂表达式
|
|
{"复杂算术", "a + b * c - d / e", []string{"a", "+", "b", "*", "c", "-", "d", "/", "e"}, false},
|
|
{"幂运算", "a ^ b", []string{"a", "^", "b"}, false},
|
|
{"取模运算", "a % b", []string{"a", "%", "b"}, false},
|
|
|
|
// 空白字符处理
|
|
{"多个空格", "a + b", []string{"a", "+", "b"}, false},
|
|
{"制表符", "a\t+\tb", []string{"a", "+", "b"}, false},
|
|
{"换行符", "a\n+\nb", []string{"a", "+", "b"}, false},
|
|
|
|
// 错误情况
|
|
{"空表达式", "", nil, true},
|
|
{"只有空格", " ", nil, true},
|
|
{"未闭合字符串", "'hello", nil, true},
|
|
{"未闭合反引号", "`field", nil, true},
|
|
{"无效字符", "a @ b", nil, true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result, err := tokenize(tt.expr)
|
|
if tt.wantErr {
|
|
assert.Error(t, err, "应该返回错误")
|
|
} else {
|
|
require.NoError(t, err, "分词不应该失败")
|
|
assert.Equal(t, tt.expected, result, "分词结果应该匹配")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIsDigit 测试数字字符判断
|
|
func TestIsDigit(t *testing.T) {
|
|
tests := []struct {
|
|
ch byte
|
|
expected bool
|
|
}{
|
|
{'0', true},
|
|
{'5', true},
|
|
{'9', true},
|
|
{'a', false},
|
|
{'A', false},
|
|
{' ', false},
|
|
{'.', false},
|
|
{'+', false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(string(tt.ch), func(t *testing.T) {
|
|
result := isDigit(tt.ch)
|
|
assert.Equal(t, tt.expected, result, "数字字符判断应该正确")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIsLetter 测试字母字符判断
|
|
func TestIsLetter(t *testing.T) {
|
|
tests := []struct {
|
|
ch byte
|
|
expected bool
|
|
}{
|
|
{'a', true},
|
|
{'z', true},
|
|
{'A', true},
|
|
{'Z', true},
|
|
{'0', false},
|
|
{'9', false},
|
|
{' ', false},
|
|
{'_', false},
|
|
{'+', false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(string(tt.ch), func(t *testing.T) {
|
|
result := isLetter(tt.ch)
|
|
assert.Equal(t, tt.expected, result, "字母字符判断应该正确")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIsNumber 测试数字字符串判断
|
|
func TestIsNumber(t *testing.T) {
|
|
tests := []struct {
|
|
s string
|
|
expected bool
|
|
}{
|
|
{"123", true},
|
|
{"0", true},
|
|
{"3.14", true},
|
|
{"-5", true},
|
|
{"-3.14", true},
|
|
{"1e10", true},
|
|
{"1.5e-3", true},
|
|
{"abc", false},
|
|
{"12a", false},
|
|
{"", false},
|
|
{".", false},
|
|
{"--5", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.s, func(t *testing.T) {
|
|
result := isNumber(tt.s)
|
|
assert.Equal(t, tt.expected, result, "数字字符串判断应该正确")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIsIdentifier 测试标识符判断
|
|
func TestIsIdentifier(t *testing.T) {
|
|
tests := []struct {
|
|
s string
|
|
expected bool
|
|
}{
|
|
{"abc", true},
|
|
{"_var", true},
|
|
{"var123", true},
|
|
{"CamelCase", true},
|
|
{"snake_case", true},
|
|
{"123abc", false},
|
|
{"", false},
|
|
{"var-name", false},
|
|
{"var.name", false},
|
|
{"var name", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.s, func(t *testing.T) {
|
|
result := isIdentifier(tt.s)
|
|
assert.Equal(t, tt.expected, result, "标识符判断应该正确")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIsOperator 测试运算符判断
|
|
func TestIsOperator(t *testing.T) {
|
|
tests := []struct {
|
|
s string
|
|
expected bool
|
|
}{
|
|
{"+", true},
|
|
{"-", true},
|
|
{"*", true},
|
|
{"/", true},
|
|
{"%", true},
|
|
{"^", true},
|
|
{">", true},
|
|
{"<", true},
|
|
{">=", true},
|
|
{"<=", true},
|
|
{"==", true},
|
|
{"=", true},
|
|
{"!=", true},
|
|
{"<>", true},
|
|
{"AND", true},
|
|
{"OR", true},
|
|
{"NOT", true},
|
|
{"LIKE", true},
|
|
{"IS", true},
|
|
{"abc", false},
|
|
{"123", false},
|
|
{"(", false},
|
|
{")", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.s, func(t *testing.T) {
|
|
result := isOperator(tt.s)
|
|
assert.Equal(t, tt.expected, result, "运算符判断应该正确")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIsComparisonOperator 测试比较运算符判断
|
|
func TestIsComparisonOperator(t *testing.T) {
|
|
tests := []struct {
|
|
s string
|
|
expected bool
|
|
}{
|
|
{">", true},
|
|
{"<", true},
|
|
{">=", true},
|
|
{"<=", true},
|
|
{"==", true},
|
|
{"=", true},
|
|
{"!=", true},
|
|
{"<>", true},
|
|
{"+", false},
|
|
{"-", false},
|
|
{"*", false},
|
|
{"/", false},
|
|
{"AND", false},
|
|
{"OR", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.s, func(t *testing.T) {
|
|
result := isComparisonOperator(tt.s)
|
|
assert.Equal(t, tt.expected, result, "比较运算符判断应该正确")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIsStringLiteral 测试字符串字面量判断
|
|
func TestIsStringLiteral(t *testing.T) {
|
|
tests := []struct {
|
|
s string
|
|
expected bool
|
|
}{
|
|
{"'hello'", true},
|
|
{"\"world\"", true},
|
|
{"''", true},
|
|
{"\"\"", true},
|
|
{"'hello", false},
|
|
{"hello'", false},
|
|
{"\"hello", false},
|
|
{"hello\"", false},
|
|
{"hello", false},
|
|
{"", false},
|
|
{"'", false},
|
|
{"\"", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.s, func(t *testing.T) {
|
|
result := isStringLiteral(tt.s)
|
|
assert.Equal(t, tt.expected, result, "字符串字面量判断应该正确")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestTokenizeComplexExpressions 测试复杂表达式分词
|
|
func TestTokenizeComplexExpressions(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
expr string
|
|
expected []string
|
|
}{
|
|
{
|
|
"温度转换表达式",
|
|
"temperature * 1.8 + 32",
|
|
[]string{"temperature", "*", "1.8", "+", "32"},
|
|
},
|
|
{
|
|
"复杂CASE表达式",
|
|
"CASE WHEN temperature > 30 AND humidity < 60 THEN 'HOT' ELSE 'NORMAL' END",
|
|
[]string{"CASE", "WHEN", "temperature", ">", "30", "AND", "humidity", "<", "60", "THEN", "'HOT'", "ELSE", "'NORMAL'", "END"},
|
|
},
|
|
{
|
|
"嵌套函数调用",
|
|
"sqrt(pow(a, 2) + pow(b, 2))",
|
|
[]string{"sqrt", "(", "pow", "(", "a", ",", "2", ")", "+", "pow", "(", "b", ",", "2", ")", ")"},
|
|
},
|
|
{
|
|
"负数在比较运算符后",
|
|
"a > -5 AND b <= -3.14",
|
|
[]string{"a", ">", "-5", "AND", "b", "<=", "-3.14"},
|
|
},
|
|
{
|
|
"幂运算后的负数",
|
|
"a ^ -2",
|
|
[]string{"a", "^", "-2"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result, err := tokenize(tt.expr)
|
|
require.NoError(t, err, "复杂表达式分词不应该失败")
|
|
assert.Equal(t, tt.expected, result, "复杂表达式分词结果应该匹配")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestTokenizeEdgeCases 测试边界情况
|
|
func TestTokenizeEdgeCases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
expr string
|
|
expected []string
|
|
wantErr bool
|
|
}{
|
|
{"只有数字", "123", []string{"123"}, false},
|
|
{"只有小数点开头的数字", ".5", []string{".5"}, false},
|
|
{"连续运算符(应该在解析阶段检测)", "a + + b", []string{"a", "+", "+", "b"}, false},
|
|
{"多个小数点", "3.14.15", []string{"3.14", ".", "15"}, false}, // 分词器不检查语法错误
|
|
{"空字符串转义", "''", []string{"''"}, false},
|
|
{"包含空格的反引号标识符", "`user name`", []string{"`user name`"}, false},
|
|
{"特殊字符在字符串中", "'hello@world#test'", []string{"'hello@world#test'"}, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result, err := tokenize(tt.expr)
|
|
if tt.wantErr {
|
|
assert.Error(t, err, "应该返回错误")
|
|
} else {
|
|
require.NoError(t, err, "分词不应该失败")
|
|
assert.Equal(t, tt.expected, result, "分词结果应该匹配")
|
|
}
|
|
})
|
|
}
|
|
}
|