From fc3c9c19a29c2c226d5b1243882008c6a1344b60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Thu, 30 Dec 2021 17:31:14 +0800 Subject: [PATCH] feat: data-driven. --- examples/parameters_test.json | 7 +- examples/parameters_test.yaml | 1 - internal/builtin/function.go | 6 +- parser.go | 117 ++++++++++++++++------------------ parser_test.go | 39 ++++++++++++ 5 files changed, 102 insertions(+), 68 deletions(-) diff --git a/examples/parameters_test.json b/examples/parameters_test.json index 6ad52032..c519e928 100644 --- a/examples/parameters_test.json +++ b/examples/parameters_test.json @@ -6,8 +6,11 @@ "iOS/10.1", "iOS/10.2" ], - "username-password": "${parameterize(examples/account.csv)}", - "app_version": "${getAppVersion()}" + "username1-password1": [ + ["a", "123"], + ["b", "456"] + ], + "username-password": "${parameterize(examples/account.csv)}" }, "parameters_setting": { "strategy": "random" diff --git a/examples/parameters_test.yaml b/examples/parameters_test.yaml index 7afb4252..65c307a8 100644 --- a/examples/parameters_test.yaml +++ b/examples/parameters_test.yaml @@ -3,7 +3,6 @@ config: parameters: user_agent: ["iOS/10.1", "iOS/10.2"] username-password: ${parameterize(examples/account.csv)} - app_version: ${getAppVersion()} parameters_setting: strategy: random variables: diff --git a/internal/builtin/function.go b/internal/builtin/function.go index 3d5c96de..8d6fd87e 100644 --- a/internal/builtin/function.go +++ b/internal/builtin/function.go @@ -52,7 +52,7 @@ func MD5(str string) string { return hex.EncodeToString(hasher.Sum(nil)) } -func LoadFromCSV(path string) []map[string]string { +func LoadFromCSV(path string) []map[string]interface{} { path, err := filepath.Abs(path) if err != nil { log.Error().Str("path", path).Err(err).Msg("convert absolute path failed") @@ -71,9 +71,9 @@ func LoadFromCSV(path string) []map[string]string { log.Error().Err(err).Msg("parse csv file failed") panic(err) } - var result []map[string]string + var result []map[string]interface{} for i := 1; i < len(content); i++ { - row := make(map[string]string) + row := make(map[string]interface{}) for j := 0; j < len(content[i]); j++ { row[content[0][j]] = content[i][j] } diff --git a/parser.go b/parser.go index 300293b5..cc92fde8 100644 --- a/parser.go +++ b/parser.go @@ -497,7 +497,7 @@ func findallVariables(raw string) variableSet { } func shuffleCartesianProduct(slice []map[string]interface{}) { - if slice == nil || len(slice) == 0 { + if len(slice) == 0 { return } r := rand.New(rand.NewSource(time.Now().Unix())) @@ -510,7 +510,7 @@ func shuffleCartesianProduct(slice []map[string]interface{}) { } func genCartesianProduct(params [][]map[string]interface{}) []map[string]interface{} { - if params == nil || len(params) == 0 { + if len(params) == 0 { return nil } var cartesianProduct []map[string]interface{} @@ -541,17 +541,18 @@ func getParameters(config IConfig) []map[string]interface{} { } func parseParameters(parameters map[string]interface{}, variablesMapping map[string]interface{}) ([]map[string]interface{}, error) { - if parameters == nil || len(parameters) == 0 { + if len(parameters) == 0 { return nil, nil } var parsedParametersSlice [][]map[string]interface{} + var err error for k, v := range parameters { - parameterNameSlice := strings.Split(k, "-") var parameterSlice []map[string]interface{} rawValue := reflect.ValueOf(v) switch rawValue.Kind() { case reflect.String: - parsedParameterContent, err := parseData(rawValue.Interface(), variablesMapping) + var parsedParameterContent interface{} + parsedParameterContent, err = parseData(rawValue.Interface(), variablesMapping) if err != nil { log.Error().Interface("parameterContent", rawValue).Msg("[parseParameters] parse parameter content error") return nil, err @@ -561,69 +562,61 @@ func parseParameters(parameters map[string]interface{}, variablesMapping map[str log.Error().Interface("parameterContent", parsedParameterRawValue).Msg("[parseParameters] parsed parameter content should be Slice, got %v") return nil, errors.New("parsed parameter content should be Slice") } - for i := 0; i < parsedParameterRawValue.Len(); i++ { - parameterMap := make(map[string]interface{}) - // e.g. - elem := reflect.ValueOf(parsedParameterRawValue.Index(i).Interface()) - if elem.Kind() == reflect.Map { - // e.g. [{"username": "test1", "password": "passwd1", "other": "111"}, {"username": "test2", "password": "passwd2", "other": ""222}] - // -> [{"username": "test1", "password": "passwd1"}, {"username": "test2", "password": "passwd2"}] (username, password in parameterNameSlice) - for _, key := range parameterNameSlice { - if _, ok := elem.Interface().(map[string]string)[key]; ok { - parameterMap[key] = elem.MapIndex(reflect.ValueOf(key)).Interface() - } else { - log.Error().Interface("parameterNameSlice", parameterNameSlice).Msg("[parseParameters] parameter name not found") - return nil, errors.New("parameter name not found") - } - } - } else if elem.Kind() == reflect.Slice { - // e.g. [["test1", "passwd1"], ["test2", "passwd2"]] -> [{"username": "test1", "password": "passwd1"}, {"username": "test2", "password": "passwd2"}] - if len(parameterNameSlice) != elem.Len() { - log.Error().Interface("parameterNameSlice", parameterNameSlice).Interface("parameterContent", elem.Interface()).Msg("[parseParameters] parameter name Slice and parameter content Slice should have the same length") - return nil, errors.New("parameter name Slice and parameter cjntent Slice should have the same length") - } else { - for j := 0; j < elem.Len(); j++ { - parameterMap[parameterNameSlice[j]] = elem.Index(j).Interface() - } - } - } else { - // e.g. ${getAppVersion()} -> [3.1, 3.0] -> [{"app_version": 3.1}, {"app_version": 3.0}] - if len(parameterNameSlice) != 1 { - log.Error().Interface("parameterNameSlice", parameterNameSlice).Msg("[parseParameters] parameter name slice should have only one element when parameter content is string") - return nil, errors.New("parameter name slice should have only one element when parameter content is string") - } - parameterMap[parameterNameSlice[0]] = elem.Interface() - } - parameterSlice = append(parameterSlice, parameterMap) - } + parameterSlice, err = handleSlice(k, parsedParameterRawValue.Interface()) case reflect.Slice: - for i := 0; i < rawValue.Len(); i++ { - parameterMap := make(map[string]interface{}) - elem := reflect.ValueOf(rawValue.Index(i).Interface()) - if elem.Kind() == reflect.Slice { - // e.g. username-password: [["test1", "passwd1"], ["test2", "passwd2"]] - if len(parameterNameSlice) != elem.Len() { - log.Error().Interface("parameterNameSlice", parameterNameSlice).Interface("parameterContent", elem.Interface()).Msg("[parseParameters] parameter name Slice and parameter content Slice should have the same length") - return nil, errors.New("parameter name Slice and parameter content Slice should have the same length") - } - for j := 0; j < elem.Len(); j++ { - parameterMap[parameterNameSlice[j]] = elem.Index(j).Interface() - } - } else { - // e.g. user_agent: ["iOS/10.1", "iOS/10.2"] - if len(parameterNameSlice) != 1 { - log.Error().Interface("parameterNameSlice", parameterNameSlice).Msg("[parseParameters] parameter name slice should have only one element when parameter content is string") - return nil, errors.New("parameter name slice should have only one element when parameter content is string") - } - parameterMap[parameterNameSlice[0]] = elem.Interface() - } - parameterSlice = append(parameterSlice, parameterMap) - } + parameterSlice, err = handleSlice(k, rawValue.Interface()) default: log.Error().Interface("parameter", parameters).Msg("[parseParameters] parameter content should be Slice or Text(variables or functions call)") return nil, errors.New("parameter content should be Slice or Text(variables or functions call)") } + if err != nil { + return nil, err + } parsedParametersSlice = append(parsedParametersSlice, parameterSlice) } return genCartesianProduct(parsedParametersSlice), nil } + +func handleSlice(parameterName string, parameterContent interface{}) ([]map[string]interface{}, error) { + parameterNameSlice := strings.Split(parameterName, "-") + var parameterSlice []map[string]interface{} + parameterContentSlice := reflect.ValueOf(parameterContent) + for i := 0; i < parameterContentSlice.Len(); i++ { + parameterMap := make(map[string]interface{}) + elem := reflect.ValueOf(parameterContentSlice.Index(i).Interface()) + switch elem.Kind() { + case reflect.Map: + // e.g. "username-password": [{"username": "test1", "password": "passwd1", "other": "111"}, {"username": "test2", "password": "passwd2", "other": ""222}] + // -> [{"username": "test1", "password": "passwd1"}, {"username": "test2", "password": "passwd2"}] + for _, key := range parameterNameSlice { + if _, ok := elem.Interface().(map[string]interface{})[key]; ok { + parameterMap[key] = elem.MapIndex(reflect.ValueOf(key)).Interface() + } else { + log.Error().Interface("parameterNameSlice", parameterNameSlice).Msg("[parseParameters] parameter name not found") + return nil, errors.New("parameter name not found") + } + } + case reflect.Slice: + // e.g. "username-password": [["test1", "passwd1"], ["test2", "passwd2"]] + // -> [{"username": "test1", "password": "passwd1"}, {"username": "test2", "password": "passwd2"}] + if len(parameterNameSlice) != elem.Len() { + log.Error().Interface("parameterNameSlice", parameterNameSlice).Interface("parameterContent", elem.Interface()).Msg("[parseParameters] parameter name Slice and parameter content Slice should have the same length") + return nil, errors.New("parameter name Slice and parameter cjntent Slice should have the same length") + } else { + for j := 0; j < elem.Len(); j++ { + parameterMap[parameterNameSlice[j]] = elem.Index(j).Interface() + } + } + default: + // e.g. "app_version": [3.1, 3.0] + // -> [{"app_version": 3.1}, {"app_version": 3.0}] + if len(parameterNameSlice) != 1 { + log.Error().Interface("parameterNameSlice", parameterNameSlice).Msg("[parseParameters] parameter name slice should have only one element when parameter content is string") + return nil, errors.New("parameter name slice should have only one element when parameter content is string") + } + parameterMap[parameterNameSlice[0]] = elem.Interface() + } + parameterSlice = append(parameterSlice, parameterMap) + } + return parameterSlice, nil +} diff --git a/parser_test.go b/parser_test.go index cb1bcd8f..c69a584d 100644 --- a/parser_test.go +++ b/parser_test.go @@ -691,3 +691,42 @@ func TestParseParametersError(t *testing.T) { } } } + +func TestHandleSlice(t *testing.T) { + testData := []struct { + rawVar1 string + rawVar2 interface{} + expect []map[string]interface{} + }{ + { + "username-password", + []map[string]interface{}{{"username": "test1", "password": 111111, "other": "111"}, {"username": "test2", "password": 222222, "other": "222"}}, + []map[string]interface{}{ + {"username": "test1", "password": 111111}, + {"username": "test2", "password": 222222}, + }, + }, + { + "username-password", + [][]string{{"test1", "111111"}, {"test2", "222222"}}, + []map[string]interface{}{ + {"username": "test1", "password": "111111"}, + {"username": "test2", "password": "222222"}, + }, + }, + { + "app_version", + []float64{3.1, 3.0}, + []map[string]interface{}{ + {"app_version": 3.1}, + {"app_version": 3.0}, + }, + }, + } + for _, data := range testData { + value, _ := handleSlice(data.rawVar1, data.rawVar2) + if !assert.Equal(t, data.expect, value) { + t.Fail() + } + } +}