mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-12 02:21:29 +08:00
feat: parseVariables
This commit is contained in:
131
parser.go
131
parser.go
@@ -316,5 +316,134 @@ func parseFunctionArguments(argsStr string) ([]interface{}, error) {
|
||||
}
|
||||
|
||||
func parseVariables(variables map[string]interface{}) (map[string]interface{}, error) {
|
||||
return variables, nil
|
||||
parsedVariables := make(map[string]interface{})
|
||||
var traverseRounds int
|
||||
|
||||
for len(parsedVariables) != len(variables) {
|
||||
for varName, varValue := range variables {
|
||||
// skip parsed variables
|
||||
if _, ok := parsedVariables[varName]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// extract variables from current value
|
||||
extractVarsSet := extractVariables(varValue)
|
||||
|
||||
// check if reference variable itself
|
||||
// e.g.
|
||||
// variables = {"token": "abc$token"}
|
||||
// variables = {"key": ["$key", 2]}
|
||||
if _, ok := extractVarsSet[varName]; ok {
|
||||
log.Printf("[parseVariables] variable self reference error: %v", variables)
|
||||
return variables, fmt.Errorf("variable self reference: %v", varName)
|
||||
}
|
||||
|
||||
// check if reference variable not in variables mapping
|
||||
// e.g.
|
||||
// {"varA": "123$varB", "varB": "456$varC"} => $varC not defined
|
||||
// {"varC": "${sum_two($a, $b)}"} => $a, $b not defined
|
||||
var undefinedVars []string
|
||||
for extractVar := range extractVarsSet {
|
||||
if _, ok := variables[extractVar]; !ok { // not in variables mapping
|
||||
undefinedVars = append(undefinedVars, extractVar)
|
||||
}
|
||||
}
|
||||
if len(undefinedVars) > 0 {
|
||||
log.Printf("[parseVariables] variable not defined error: %v", undefinedVars)
|
||||
return variables, fmt.Errorf("variable not defined: %v", undefinedVars)
|
||||
}
|
||||
|
||||
parsedValue := parseData(varValue, parsedVariables)
|
||||
if parsedValue == nil {
|
||||
continue
|
||||
}
|
||||
parsedVariables[varName] = parsedValue
|
||||
}
|
||||
traverseRounds += 1
|
||||
// check if circular reference exists
|
||||
if traverseRounds > len(variables) {
|
||||
log.Printf("[parseVariables] circular reference error, break infinite loop!")
|
||||
return variables, fmt.Errorf("circular reference")
|
||||
}
|
||||
}
|
||||
|
||||
return parsedVariables, nil
|
||||
}
|
||||
|
||||
type variableSet map[string]struct{}
|
||||
|
||||
func extractVariables(raw interface{}) variableSet {
|
||||
rawValue := reflect.ValueOf(raw)
|
||||
switch rawValue.Kind() {
|
||||
case reflect.String:
|
||||
return findallVariables(rawValue.String())
|
||||
case reflect.Slice:
|
||||
varSet := make(variableSet)
|
||||
for i := 0; i < rawValue.Len(); i++ {
|
||||
for extractVar := range extractVariables(rawValue.Index(i).Interface()) {
|
||||
varSet[extractVar] = struct{}{}
|
||||
}
|
||||
}
|
||||
return varSet
|
||||
case reflect.Map:
|
||||
varSet := make(variableSet)
|
||||
for _, key := range rawValue.MapKeys() {
|
||||
value := rawValue.MapIndex(key)
|
||||
for extractVar := range extractVariables(value.Interface()) {
|
||||
varSet[extractVar] = struct{}{}
|
||||
}
|
||||
}
|
||||
return varSet
|
||||
default:
|
||||
// other types, e.g. nil, int, float, bool
|
||||
return make(variableSet)
|
||||
}
|
||||
}
|
||||
|
||||
func findallVariables(raw string) variableSet {
|
||||
matchStartPosition := 0
|
||||
remainedString := raw
|
||||
varSet := make(variableSet)
|
||||
|
||||
for matchStartPosition < len(raw) {
|
||||
// locate $ char position
|
||||
startPosition := strings.Index(remainedString, "$")
|
||||
if startPosition == -1 { // no $ found
|
||||
return varSet
|
||||
}
|
||||
|
||||
// found $, check if variable or function
|
||||
matchStartPosition += startPosition
|
||||
remainedString = remainedString[startPosition:]
|
||||
|
||||
// Notice: notation priority
|
||||
// $$ > $var
|
||||
|
||||
// search $$, use $$ to escape $ notation
|
||||
if strings.HasPrefix(remainedString, "$$") { // found $$
|
||||
matchStartPosition += 2
|
||||
remainedString = remainedString[2:]
|
||||
continue
|
||||
}
|
||||
|
||||
// search variable like ${var} or $var
|
||||
varMatched := regexCompileVariable.FindStringSubmatch(remainedString)
|
||||
if len(varMatched) == 3 {
|
||||
var varName string
|
||||
if varMatched[1] != "" {
|
||||
varName = varMatched[1] // match ${var}
|
||||
} else {
|
||||
varName = varMatched[2] // match $var
|
||||
}
|
||||
varSet[varName] = struct{}{}
|
||||
|
||||
matchStartPosition += len(varMatched[0])
|
||||
remainedString = raw[matchStartPosition:]
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return varSet
|
||||
}
|
||||
|
||||
125
parser_test.go
125
parser_test.go
@@ -1,6 +1,7 @@
|
||||
package httpboomer
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -428,3 +429,127 @@ func TestConvertString(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseVariables(t *testing.T) {
|
||||
testData := []struct {
|
||||
rawVars map[string]interface{}
|
||||
expectVars map[string]interface{}
|
||||
}{
|
||||
{
|
||||
map[string]interface{}{"varA": "$varB", "varB": "$varC", "varC": "123", "a": 1, "b": 2},
|
||||
map[string]interface{}{"varA": "123", "varB": "123", "varC": "123", "a": 1, "b": 2},
|
||||
},
|
||||
}
|
||||
|
||||
for _, data := range testData {
|
||||
value, err := parseVariables(data.rawVars)
|
||||
if !assert.Nil(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
if !assert.Equal(t, data.expectVars, value) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseVariablesAbnormal(t *testing.T) {
|
||||
testData := []struct {
|
||||
rawVars map[string]interface{}
|
||||
expectVars map[string]interface{}
|
||||
}{
|
||||
{ // self referenced variable $varA
|
||||
map[string]interface{}{"varA": "$varA"},
|
||||
map[string]interface{}{"varA": "$varA"},
|
||||
},
|
||||
{ // undefined variable $varC
|
||||
map[string]interface{}{"varA": "$varB", "varB": "$varC", "a": 1, "b": 2},
|
||||
map[string]interface{}{"varA": "$varB", "varB": "$varC", "a": 1, "b": 2},
|
||||
},
|
||||
{ // circular reference
|
||||
map[string]interface{}{"varA": "$varB", "varB": "$varA"},
|
||||
map[string]interface{}{"varA": "$varB", "varB": "$varA"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, data := range testData {
|
||||
value, err := parseVariables(data.rawVars)
|
||||
if !assert.NotNil(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
if !assert.Equal(t, data.expectVars, value) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractVariables(t *testing.T) {
|
||||
testData := []struct {
|
||||
raw interface{}
|
||||
expectVars []string
|
||||
}{
|
||||
{nil, nil},
|
||||
{"/$var1/$var1", []string{"var1"}},
|
||||
{
|
||||
map[string]interface{}{"varA": "$varB", "varB": "$varC", "varC": "123"},
|
||||
[]string{"varB", "varC"},
|
||||
},
|
||||
{
|
||||
[]interface{}{"varA", "$varB", 123, "$varC", "123"},
|
||||
[]string{"varB", "varC"},
|
||||
},
|
||||
{ // nested map and slice
|
||||
map[string]interface{}{"varA": "$varB", "varB": map[string]interface{}{"C": "$varC", "D": []string{"$varE"}}},
|
||||
[]string{"varB", "varC", "varE"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, data := range testData {
|
||||
var varList []string
|
||||
for varName := range extractVariables(data.raw) {
|
||||
varList = append(varList, varName)
|
||||
}
|
||||
sort.Strings(varList)
|
||||
if !assert.Equal(t, data.expectVars, varList) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindallVariables(t *testing.T) {
|
||||
testData := []struct {
|
||||
raw string
|
||||
expectVars []string
|
||||
}{
|
||||
{"", nil},
|
||||
{"$variable", []string{"variable"}},
|
||||
{"${variable}123", []string{"variable"}},
|
||||
{"/blog/$postid", []string{"postid"}},
|
||||
{"/$var1/$var2", []string{"var1", "var2"}},
|
||||
{"/$var1/$var1", []string{"var1"}},
|
||||
{"abc", nil},
|
||||
{"Z:2>1*0*1+1$a", []string{"a"}},
|
||||
{"Z:2>1*0*1+1$$a", nil},
|
||||
{"Z:2>1*0*1+1$$$a", []string{"a"}},
|
||||
{"Z:2>1*0*1+1$$$$a", nil},
|
||||
{"Z:2>1*0*1+1$$a$b", []string{"b"}},
|
||||
{"Z:2>1*0*1+1$$a$$b", nil},
|
||||
{"Z:2>1*0*1+1$a$b", []string{"a", "b"}},
|
||||
{"Z:2>1*0*1+1$$1", nil},
|
||||
{"a$var", []string{"var"}},
|
||||
{"a$v b", []string{"v"}},
|
||||
{"${func()}", nil},
|
||||
{"a${func(1,2)}b", nil},
|
||||
{"${gen_md5($TOKEN, $data, $random)}", []string{"TOKEN", "data", "random"}},
|
||||
}
|
||||
|
||||
for _, data := range testData {
|
||||
var varList []string
|
||||
for varName := range findallVariables(data.raw) {
|
||||
varList = append(varList, varName)
|
||||
}
|
||||
sort.Strings(varList)
|
||||
if !assert.Equal(t, data.expectVars, varList) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user