mirror of
https://github.com/httprunner/httprunner.git
synced 2026-08-28 11:36:56 +08:00
feat: parseVariables
This commit is contained in:
@@ -316,5 +316,134 @@ func parseFunctionArguments(argsStr string) ([]interface{}, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseVariables(variables map[string]interface{}) (map[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
@@ -1,6 +1,7 @@
|
|||||||
package httpboomer
|
package httpboomer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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