mirror of
https://github.com/httprunner/httprunner.git
synced 2026-09-05 23:51:25 +08:00
refactor: ParametersIterator
This commit is contained in:
+7
-8
@@ -83,7 +83,6 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
|
|||||||
b.plugins = append(b.plugins, sessionRunner.parser.plugin)
|
b.plugins = append(b.plugins, sessionRunner.parser.plugin)
|
||||||
b.pluginsMutex.Unlock()
|
b.pluginsMutex.Unlock()
|
||||||
}
|
}
|
||||||
sessionRunner.resetSession()
|
|
||||||
|
|
||||||
// broadcast to all rendezvous at once when spawn done
|
// broadcast to all rendezvous at once when spawn done
|
||||||
go func() {
|
go func() {
|
||||||
@@ -93,6 +92,10 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// set paramters mode for load testing
|
||||||
|
parametersIterator := sessionRunner.parametersIterator
|
||||||
|
parametersIterator.SetUnlimitedMode()
|
||||||
|
|
||||||
return &boomer.Task{
|
return &boomer.Task{
|
||||||
Name: testcase.Config.Name,
|
Name: testcase.Config.Name,
|
||||||
Weight: testcase.Config.Weight,
|
Weight: testcase.Config.Weight,
|
||||||
@@ -100,15 +103,11 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
|
|||||||
testcaseSuccess := true // flag whole testcase result
|
testcaseSuccess := true // flag whole testcase result
|
||||||
transactionSuccess := true // flag current transaction result
|
transactionSuccess := true // flag current transaction result
|
||||||
|
|
||||||
var parameterVariables map[string]interface{}
|
if parametersIterator.HasNext() {
|
||||||
// iterate through all parameter iterators and update case variables
|
sessionRunner.updateConfigVariables(parametersIterator.Next())
|
||||||
for _, it := range sessionRunner.parsedConfig.ParametersSetting.Iterators {
|
|
||||||
if it.HasNext() {
|
|
||||||
parameterVariables = it.Next()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sessionRunner.updateConfigVariables(parameterVariables)
|
|
||||||
|
|
||||||
|
sessionRunner.resetSession()
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
for _, step := range testcase.TestSteps {
|
for _, step := range testcase.TestSteps {
|
||||||
stepResult, err := step.Run(sessionRunner)
|
stepResult, err := step.Run(sessionRunner)
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package hrp
|
package hrp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/hrp/internal/builtin"
|
"github.com/httprunner/httprunner/hrp/internal/builtin"
|
||||||
)
|
)
|
||||||
@@ -158,59 +155,3 @@ const (
|
|||||||
var (
|
var (
|
||||||
thinkTimeDefaultRandom = map[string]float64{"min_percentage": 0.5, "max_percentage": 1.5}
|
thinkTimeDefaultRandom = map[string]float64{"min_percentage": 0.5, "max_percentage": 1.5}
|
||||||
)
|
)
|
||||||
|
|
||||||
type TParamsConfig struct {
|
|
||||||
Strategy interface{} `json:"strategy,omitempty" yaml:"strategy,omitempty"` // map[string]string、string
|
|
||||||
Iteration int `json:"iteration,omitempty" yaml:"iteration,omitempty"`
|
|
||||||
Iterators []*Iterator `json:"parameterIterator,omitempty" yaml:"parameterIterator,omitempty"` // 保存参数的迭代器
|
|
||||||
}
|
|
||||||
|
|
||||||
type Iterator struct {
|
|
||||||
sync.Mutex
|
|
||||||
data iteratorParamsType
|
|
||||||
strategy iteratorStrategyType // random, sequential
|
|
||||||
iteration int
|
|
||||||
index int
|
|
||||||
}
|
|
||||||
|
|
||||||
type iteratorStrategyType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
strategyRandom iteratorStrategyType = "random"
|
|
||||||
strategySequential iteratorStrategyType = "sequential"
|
|
||||||
)
|
|
||||||
|
|
||||||
type iteratorParamsType []map[string]interface{}
|
|
||||||
|
|
||||||
func (params iteratorParamsType) Iterator() *Iterator {
|
|
||||||
return &Iterator{
|
|
||||||
data: params,
|
|
||||||
iteration: len(params),
|
|
||||||
index: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (iter *Iterator) HasNext() bool {
|
|
||||||
if iter.iteration == -1 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return iter.index < iter.iteration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (iter *Iterator) Next() (value map[string]interface{}) {
|
|
||||||
iter.Lock()
|
|
||||||
defer iter.Unlock()
|
|
||||||
if len(iter.data) == 0 {
|
|
||||||
iter.index++
|
|
||||||
return map[string]interface{}{}
|
|
||||||
}
|
|
||||||
if iter.strategy == strategyRandom {
|
|
||||||
randSource := rand.New(rand.NewSource(time.Now().Unix()))
|
|
||||||
randIndex := randSource.Intn(len(iter.data))
|
|
||||||
value = iter.data[randIndex]
|
|
||||||
} else {
|
|
||||||
value = iter.data[iter.index%len(iter.data)]
|
|
||||||
}
|
|
||||||
iter.index++
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,345 @@
|
|||||||
|
package hrp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TParamsConfig struct {
|
||||||
|
Strategy iteratorStrategy `json:"strategy,omitempty" yaml:"strategy,omitempty"` // overall strategy
|
||||||
|
Strategies map[string]iteratorStrategy `json:"strategies,omitempty" yaml:"strategies,omitempty"` // map[string]string、string
|
||||||
|
Limit int `json:"limit,omitempty" yaml:"limit,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type iteratorStrategy string
|
||||||
|
|
||||||
|
const (
|
||||||
|
strategySequential iteratorStrategy = "sequential"
|
||||||
|
strategyRandom iteratorStrategy = "random"
|
||||||
|
strategyUnique iteratorStrategy = "unique"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
[
|
||||||
|
{"username": "test1", "password": "111111"},
|
||||||
|
{"username": "test2", "password": "222222"},
|
||||||
|
]
|
||||||
|
*/
|
||||||
|
type Parameters []map[string]interface{}
|
||||||
|
|
||||||
|
func initParametersIterator(cfg *TConfig) (*ParametersIterator, error) {
|
||||||
|
parameters, err := loadParameters(cfg.Parameters, cfg.Variables)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newParametersIterator(parameters, cfg.ParametersSetting), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newParametersIterator(parameters map[string]Parameters, config *TParamsConfig) *ParametersIterator {
|
||||||
|
iterator := &ParametersIterator{
|
||||||
|
data: parameters,
|
||||||
|
hasNext: true,
|
||||||
|
sequentialParameters: nil,
|
||||||
|
randomParameterNames: nil,
|
||||||
|
limit: config.Limit,
|
||||||
|
index: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parameters) == 0 {
|
||||||
|
iterator.hasNext = false
|
||||||
|
return iterator
|
||||||
|
}
|
||||||
|
|
||||||
|
parametersList := make([]Parameters, 0)
|
||||||
|
for paramName := range parameters {
|
||||||
|
// check parameter individual strategy
|
||||||
|
strategy, ok := config.Strategies[paramName]
|
||||||
|
if !ok {
|
||||||
|
// default to overall strategy
|
||||||
|
strategy = config.Strategy
|
||||||
|
}
|
||||||
|
|
||||||
|
// group parameters by strategy
|
||||||
|
if strategy == strategyRandom {
|
||||||
|
iterator.randomParameterNames = append(iterator.randomParameterNames, paramName)
|
||||||
|
} else {
|
||||||
|
parametersList = append(parametersList, parameters[paramName])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate cartesian product for sequential parameters
|
||||||
|
iterator.sequentialParameters = genCartesianProduct(parametersList)
|
||||||
|
if iterator.limit == 0 {
|
||||||
|
if len(iterator.sequentialParameters) > 0 {
|
||||||
|
iterator.limit = len(iterator.sequentialParameters)
|
||||||
|
} else {
|
||||||
|
iterator.limit = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return iterator
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParametersIterator struct {
|
||||||
|
sync.Mutex
|
||||||
|
data map[string]Parameters
|
||||||
|
hasNext bool // cache query result
|
||||||
|
sequentialParameters Parameters // cartesian product for sequential parameters
|
||||||
|
randomParameterNames []string // value is parameter names
|
||||||
|
limit int
|
||||||
|
index int
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUnlimitedMode is used for load testing
|
||||||
|
func (iter *ParametersIterator) SetUnlimitedMode() {
|
||||||
|
iter.limit = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *ParametersIterator) HasNext() bool {
|
||||||
|
if !iter.hasNext {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlimited mode
|
||||||
|
if iter.limit == -1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// reached limit
|
||||||
|
if iter.index >= iter.limit {
|
||||||
|
// cache query result
|
||||||
|
iter.hasNext = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *ParametersIterator) Next() map[string]interface{} {
|
||||||
|
iter.Lock()
|
||||||
|
defer iter.Unlock()
|
||||||
|
|
||||||
|
if !iter.hasNext {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(iter.data) == 0 {
|
||||||
|
iter.hasNext = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedParameters := make(map[string]interface{})
|
||||||
|
if iter.index < len(iter.sequentialParameters) {
|
||||||
|
selectedParameters = iter.sequentialParameters[iter.index]
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, paramName := range iter.randomParameterNames {
|
||||||
|
randSource := rand.New(rand.NewSource(time.Now().Unix()))
|
||||||
|
randIndex := randSource.Intn(len(iter.data[paramName]))
|
||||||
|
selectedParameters[paramName] = iter.data[paramName][randIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
iter.index++
|
||||||
|
if iter.limit > 0 && iter.index >= iter.limit {
|
||||||
|
iter.hasNext = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedParameters
|
||||||
|
}
|
||||||
|
|
||||||
|
func genCartesianProduct(multiParameters []Parameters) Parameters {
|
||||||
|
if len(multiParameters) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cartesianProduct := multiParameters[0]
|
||||||
|
for i := 0; i < len(multiParameters)-1; i++ {
|
||||||
|
var tempProduct Parameters
|
||||||
|
for _, param1 := range cartesianProduct {
|
||||||
|
for _, param2 := range multiParameters[i+1] {
|
||||||
|
tempProduct = append(tempProduct, mergeVariables(param1, param2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cartesianProduct = tempProduct
|
||||||
|
}
|
||||||
|
|
||||||
|
return cartesianProduct
|
||||||
|
}
|
||||||
|
|
||||||
|
/* loadParameters loads parameters from multiple sources.
|
||||||
|
|
||||||
|
parameter value may be in three types:
|
||||||
|
(1) data list, e.g. ["iOS/10.1", "iOS/10.2", "iOS/10.3"]
|
||||||
|
(2) call built-in parameterize function, "${parameterize(account.csv)}"
|
||||||
|
(3) call custom function in debugtalk.py, "${gen_app_version()}"
|
||||||
|
|
||||||
|
configParameters = {
|
||||||
|
"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"], // case 1
|
||||||
|
"username-password": "${parameterize(account.csv)}", // case 2
|
||||||
|
"app_version": "${gen_app_version()}", // case 3
|
||||||
|
}
|
||||||
|
|
||||||
|
=>
|
||||||
|
|
||||||
|
{
|
||||||
|
"user_agent": [
|
||||||
|
{"user_agent": "iOS/10.1"},
|
||||||
|
{"user_agent": "iOS/10.2"},
|
||||||
|
{"user_agent": "iOS/10.3"},
|
||||||
|
],
|
||||||
|
"username-password": [
|
||||||
|
{"username": "test1", "password": "111111"},
|
||||||
|
{"username": "test2", "password": "222222"},
|
||||||
|
],
|
||||||
|
"app_version": [
|
||||||
|
{"app_version": "1.0.0"},
|
||||||
|
{"app_version": "1.0.1"},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
func loadParameters(configParameters map[string]interface{}, variablesMapping map[string]interface{}) (
|
||||||
|
map[string]Parameters, error) {
|
||||||
|
|
||||||
|
if len(configParameters) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedParameters := make(map[string]Parameters)
|
||||||
|
|
||||||
|
for k, v := range configParameters {
|
||||||
|
var parametersRawList interface{}
|
||||||
|
rawValue := reflect.ValueOf(v)
|
||||||
|
|
||||||
|
switch rawValue.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
// case 1
|
||||||
|
// e.g. user_agent: ["iOS/10.1", "iOS/10.2"]
|
||||||
|
// => ["iOS/10.1", "iOS/10.2"]
|
||||||
|
parametersRawList = rawValue.Interface()
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
// case 2 or case 3
|
||||||
|
// e.g. username-password: ${parameterize(examples/hrp/account.csv)}
|
||||||
|
// => [{"username": "test1", "password": "111111"}, {"username": "test2", "password": "222222"}]
|
||||||
|
// => [["test1", "111111"], ["test2", "222222"]]
|
||||||
|
// e.g. "app_version": "${gen_app_version()}"
|
||||||
|
// => ["1.0.0", "1.0.1"]
|
||||||
|
parsedParameterContent, err := newParser().ParseString(rawValue.String(), variablesMapping)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).
|
||||||
|
Str("parametersRawContent", rawValue.String()).
|
||||||
|
Msg("parse parameters content failed")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedParameterRawValue := reflect.ValueOf(parsedParameterContent)
|
||||||
|
if parsedParameterRawValue.Kind() != reflect.Slice {
|
||||||
|
log.Error().
|
||||||
|
Interface("parsedParameterContent", parsedParameterRawValue).
|
||||||
|
Msg("parsed parameters content is not slice")
|
||||||
|
return nil, errors.New("parsed parameters content should be slice")
|
||||||
|
}
|
||||||
|
parametersRawList = parsedParameterRawValue.Interface()
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Error().
|
||||||
|
Interface("parameters", configParameters).
|
||||||
|
Msg("config parameters raw value should be slice or string (functions call)")
|
||||||
|
return nil, errors.New("config parameters raw value format error")
|
||||||
|
}
|
||||||
|
|
||||||
|
parameterSlice, err := convertParameters(k, parametersRawList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
parsedParameters[k] = parameterSlice
|
||||||
|
}
|
||||||
|
return parsedParameters, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/* convert parameters to standard format
|
||||||
|
|
||||||
|
key and parametersRawList may be in three types:
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
key = "user_agent"
|
||||||
|
parametersRawList = ["iOS/10.1", "iOS/10.2"]
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
key = "username-password"
|
||||||
|
parametersRawList = [{"username": "test1", "password": "111111"}, {"username": "test2", "password": "222222"}]
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
key = "username-password"
|
||||||
|
parametersRawList = [["test1", "111111"], ["test2", "222222"]]
|
||||||
|
*/
|
||||||
|
func convertParameters(key string, parametersRawList interface{}) (parameterSlice []map[string]interface{}, err error) {
|
||||||
|
parametersRawSlice := reflect.ValueOf(parametersRawList)
|
||||||
|
if parametersRawSlice.Kind() != reflect.Slice {
|
||||||
|
return nil, errors.New("parameters raw value is not list")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ["user_agent"], ["username", "password"], ["app_version"]
|
||||||
|
parameterNames := strings.Split(key, "-")
|
||||||
|
|
||||||
|
for i := 0; i < parametersRawSlice.Len(); i++ {
|
||||||
|
parametersLine := make(map[string]interface{})
|
||||||
|
elem := parametersRawSlice.Index(i)
|
||||||
|
switch elem.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
// case 3
|
||||||
|
// e.g. "username-password": ["test1", "111111"]
|
||||||
|
// => {"username": "test1", "password": "111111"}
|
||||||
|
if len(parameterNames) != elem.Len() {
|
||||||
|
log.Error().
|
||||||
|
Strs("parameterNames", parameterNames).
|
||||||
|
Int("lineIndex", i).
|
||||||
|
Interface("content", elem.Interface()).
|
||||||
|
Msg("parameters line length does not match to names length")
|
||||||
|
return nil, errors.New("parameters line length does not match to names length")
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := 0; j < elem.Len(); j++ {
|
||||||
|
parametersLine[parameterNames[j]] = elem.Index(j).Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
// case 2
|
||||||
|
// e.g. "username-password": {"username": "test1", "password": "111111", "other": "111"}
|
||||||
|
// => {"username": "test1", "password": "passwd1"}
|
||||||
|
for _, name := range parameterNames {
|
||||||
|
lineMap := elem.Interface().(map[string]interface{})
|
||||||
|
if _, ok := lineMap[name]; ok {
|
||||||
|
parametersLine[name] = elem.MapIndex(reflect.ValueOf(name)).Interface()
|
||||||
|
} else {
|
||||||
|
log.Error().
|
||||||
|
Strs("parameterNames", parameterNames).
|
||||||
|
Str("name", name).
|
||||||
|
Msg("parameter name not found")
|
||||||
|
return nil, errors.New("parameter name not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// case 1
|
||||||
|
// e.g. "user_agent": "iOS/10.1"
|
||||||
|
// -> {"user_agent": "iOS/10.1"}
|
||||||
|
if len(parameterNames) != 1 {
|
||||||
|
log.Error().
|
||||||
|
Strs("parameterNames", parameterNames).
|
||||||
|
Int("lineIndex", i).
|
||||||
|
Msg("parameters format error")
|
||||||
|
return nil, errors.New("parameters format error")
|
||||||
|
}
|
||||||
|
parametersLine[parameterNames[0]] = elem.Interface()
|
||||||
|
}
|
||||||
|
parameterSlice = append(parameterSlice, parametersLine)
|
||||||
|
}
|
||||||
|
return parameterSlice, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,299 @@
|
|||||||
|
package hrp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadParameters(t *testing.T) {
|
||||||
|
testData := []struct {
|
||||||
|
configParameters map[string]interface{}
|
||||||
|
loadedParameters map[string]Parameters
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"username-password": fmt.Sprintf("${parameterize(%s/$file)}", hrpExamplesDir),
|
||||||
|
},
|
||||||
|
map[string]Parameters{
|
||||||
|
"username-password": {
|
||||||
|
{"username": "test1", "password": "111111"},
|
||||||
|
{"username": "test2", "password": "222222"},
|
||||||
|
{"username": "test3", "password": "333333"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"username-password": [][]interface{}{
|
||||||
|
{"test1", "111111"},
|
||||||
|
{"test2", "222222"},
|
||||||
|
},
|
||||||
|
"user_agent": []interface{}{"IOS/10.1", "IOS/10.2"},
|
||||||
|
"app_version": []interface{}{4.0},
|
||||||
|
},
|
||||||
|
map[string]Parameters{
|
||||||
|
"username-password": {
|
||||||
|
{"username": "test1", "password": "111111"},
|
||||||
|
{"username": "test2", "password": "222222"},
|
||||||
|
},
|
||||||
|
"user_agent": {
|
||||||
|
{"user_agent": "IOS/10.1"},
|
||||||
|
{"user_agent": "IOS/10.2"},
|
||||||
|
},
|
||||||
|
"app_version": {
|
||||||
|
{"app_version": 4.0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
map[string]interface{}{},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
variablesMapping := map[string]interface{}{
|
||||||
|
"file": "account.csv",
|
||||||
|
}
|
||||||
|
for _, data := range testData {
|
||||||
|
value, err := loadParameters(data.configParameters, variablesMapping)
|
||||||
|
if !assert.Nil(t, err) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, data.loadedParameters, value) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadParametersError(t *testing.T) {
|
||||||
|
testData := []struct {
|
||||||
|
configParameters map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"username_password": fmt.Sprintf("${parameterize(%s/account.csv)}", hrpExamplesDir),
|
||||||
|
"user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"username-password": fmt.Sprintf("${parameterize(%s/account.csv)}", hrpExamplesDir),
|
||||||
|
"user-agent": []interface{}{"IOS/10.1", "IOS/10.2"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
map[string]interface{}{
|
||||||
|
"username-password": fmt.Sprintf("${param(%s/account.csv)}", hrpExamplesDir),
|
||||||
|
"user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, data := range testData {
|
||||||
|
_, err := loadParameters(data.configParameters, map[string]interface{}{})
|
||||||
|
if !assert.Error(t, err) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitParametersIterator(t *testing.T) {
|
||||||
|
configParameters := map[string]interface{}{
|
||||||
|
"username-password": fmt.Sprintf("${parameterize(%s/account.csv)}", hrpExamplesDir), // 3
|
||||||
|
"user_agent": []interface{}{"IOS/10.1", "IOS/10.2"},
|
||||||
|
"app_version": []interface{}{4.0},
|
||||||
|
}
|
||||||
|
testData := []struct {
|
||||||
|
cfg *TConfig
|
||||||
|
expectLimit int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
&TConfig{
|
||||||
|
Parameters: configParameters,
|
||||||
|
ParametersSetting: &TParamsConfig{},
|
||||||
|
},
|
||||||
|
6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&TConfig{
|
||||||
|
Parameters: configParameters,
|
||||||
|
ParametersSetting: &TParamsConfig{
|
||||||
|
Strategy: "random",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&TConfig{
|
||||||
|
Parameters: configParameters,
|
||||||
|
ParametersSetting: &TParamsConfig{
|
||||||
|
Strategies: map[string]iteratorStrategy{
|
||||||
|
"username-password": "random",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, data := range testData {
|
||||||
|
iterator, err := initParametersIterator(data.cfg)
|
||||||
|
if !assert.Nil(t, err) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, data.expectLimit, iterator.limit) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < data.expectLimit; i++ {
|
||||||
|
if !assert.True(t, iterator.HasNext()) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
log.Info().Interface("next", iterator.Next()).Msg("get next parameters")
|
||||||
|
}
|
||||||
|
// should not have next
|
||||||
|
if !assert.False(t, iterator.HasNext()) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenCartesianProduct(t *testing.T) {
|
||||||
|
testData := []struct {
|
||||||
|
multiParameters []Parameters
|
||||||
|
expect Parameters
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
[]Parameters{
|
||||||
|
{
|
||||||
|
{"app_version": 4.0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"username": "test1", "password": "111111"},
|
||||||
|
{"username": "test2", "password": "222222"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"user_agent": "iOS/10.1"},
|
||||||
|
{"user_agent": "iOS/10.2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Parameters{
|
||||||
|
{"app_version": 4.0, "password": "111111", "user_agent": "iOS/10.1", "username": "test1"},
|
||||||
|
{"app_version": 4.0, "password": "111111", "user_agent": "iOS/10.2", "username": "test1"},
|
||||||
|
{"app_version": 4.0, "password": "222222", "user_agent": "iOS/10.1", "username": "test2"},
|
||||||
|
{"app_version": 4.0, "password": "222222", "user_agent": "iOS/10.2", "username": "test2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]Parameters{},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, data := range testData {
|
||||||
|
parameters := genCartesianProduct(data.multiParameters)
|
||||||
|
if !assert.Equal(t, data.expect, parameters) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertParameters(t *testing.T) {
|
||||||
|
testData := []struct {
|
||||||
|
key string
|
||||||
|
parametersRawList 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},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_agent",
|
||||||
|
[]string{"iOS/10.1", "iOS/10.2"},
|
||||||
|
[]map[string]interface{}{
|
||||||
|
{"user_agent": "iOS/10.1"},
|
||||||
|
{"user_agent": "iOS/10.2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, data := range testData {
|
||||||
|
value, err := convertParameters(data.key, data.parametersRawList)
|
||||||
|
if !assert.Nil(t, err) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, data.expect, value) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertParametersError(t *testing.T) {
|
||||||
|
testData := []struct {
|
||||||
|
key string
|
||||||
|
parametersRawList interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"app_version",
|
||||||
|
123, // not slice
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"app_version",
|
||||||
|
"123", // not slice
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username-password",
|
||||||
|
[]map[string]interface{}{ // parameter names not match
|
||||||
|
{"username": "test1", "other": "111"},
|
||||||
|
{"username": "test2", "other": "222"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username-password",
|
||||||
|
[][]string{ // parameter names length not match
|
||||||
|
{"test1"},
|
||||||
|
{"test2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, data := range testData {
|
||||||
|
_, err := convertParameters(data.key, data.parametersRawList)
|
||||||
|
if !assert.Error(t, err) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-177
@@ -10,7 +10,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/maja42/goval"
|
"github.com/maja42/goval"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/httprunner/funplugin"
|
"github.com/httprunner/funplugin"
|
||||||
@@ -532,179 +531,3 @@ func findallVariables(raw string) variableSet {
|
|||||||
|
|
||||||
return varSet
|
return varSet
|
||||||
}
|
}
|
||||||
|
|
||||||
func genCartesianProduct(paramsMap map[string]iteratorParamsType) iteratorParamsType {
|
|
||||||
if len(paramsMap) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var params []iteratorParamsType
|
|
||||||
for _, v := range paramsMap {
|
|
||||||
params = append(params, v)
|
|
||||||
}
|
|
||||||
var cartesianProduct iteratorParamsType
|
|
||||||
cartesianProduct = params[0]
|
|
||||||
for i := 0; i < len(params)-1; i++ {
|
|
||||||
var tempProduct iteratorParamsType
|
|
||||||
for _, param1 := range cartesianProduct {
|
|
||||||
for _, param2 := range params[i+1] {
|
|
||||||
tempProduct = append(tempProduct, mergeVariables(param1, param2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cartesianProduct = tempProduct
|
|
||||||
}
|
|
||||||
return cartesianProduct
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseParameters(parameters map[string]interface{}, variablesMapping map[string]interface{}) (map[string]iteratorParamsType, error) {
|
|
||||||
if len(parameters) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
parsedParametersSlice := make(map[string]iteratorParamsType)
|
|
||||||
var err error
|
|
||||||
for k, v := range parameters {
|
|
||||||
var parameterSlice iteratorParamsType
|
|
||||||
rawValue := reflect.ValueOf(v)
|
|
||||||
switch rawValue.Kind() {
|
|
||||||
case reflect.String:
|
|
||||||
// e.g. username-password: ${parameterize(examples/hrp/account.csv)} -> [{"username": "test1", "password": "111111"}, {"username": "test2", "password": "222222"}]
|
|
||||||
var parsedParameterContent interface{}
|
|
||||||
parsedParameterContent, err = newParser().ParseString(rawValue.String(), variablesMapping)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Interface("parameterContent", rawValue).Msg("[parseParameters] parse parameter content error")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
parsedParameterRawValue := reflect.ValueOf(parsedParameterContent)
|
|
||||||
if parsedParameterRawValue.Kind() != reflect.Slice {
|
|
||||||
log.Error().Interface("parameterContent", parsedParameterRawValue).Msg("[parseParameters] parsed parameter content should be slice")
|
|
||||||
return nil, errors.New("parsed parameter content should be slice")
|
|
||||||
}
|
|
||||||
parameterSlice, err = parseSlice(k, parsedParameterRawValue.Interface())
|
|
||||||
case reflect.Slice:
|
|
||||||
// e.g. user_agent: ["iOS/10.1", "iOS/10.2"] -> [{"user_agent": "iOS/10.1"}, {"user_agent": "iOS/10.2"}]
|
|
||||||
parameterSlice, err = parseSlice(k, rawValue.Interface())
|
|
||||||
default:
|
|
||||||
log.Error().Interface("parameter", parameters).Msg("[parseParameters] parameter content should be slice or text(functions call)")
|
|
||||||
return nil, errors.New("parameter content should be slice or text(functions call)")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
parsedParametersSlice[k] = parameterSlice
|
|
||||||
}
|
|
||||||
return parsedParametersSlice, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSlice(parameterName string, parameterContent interface{}) ([]map[string]interface{}, error) {
|
|
||||||
parameterNameSlice := strings.Split(parameterName, "-")
|
|
||||||
var parameterSlice []map[string]interface{}
|
|
||||||
parameterContentSlice := reflect.ValueOf(parameterContent)
|
|
||||||
if parameterContentSlice.Kind() != reflect.Slice {
|
|
||||||
return nil, errors.New("parameterContent should be slice")
|
|
||||||
}
|
|
||||||
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 content 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func initParameterIterator(cfg *TConfig, mode string) (err error) {
|
|
||||||
var parameters map[string]iteratorParamsType
|
|
||||||
parameters, err = parseParameters(cfg.Parameters, cfg.Variables)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// parse config parameters setting
|
|
||||||
if cfg.ParametersSetting == nil {
|
|
||||||
cfg.ParametersSetting = &TParamsConfig{Iterators: []*Iterator{}}
|
|
||||||
}
|
|
||||||
// boomer模式下不限制迭代次数
|
|
||||||
if mode == "boomer" {
|
|
||||||
cfg.ParametersSetting.Iteration = -1
|
|
||||||
}
|
|
||||||
rawValue := reflect.ValueOf(cfg.ParametersSetting.Strategy)
|
|
||||||
switch rawValue.Kind() {
|
|
||||||
case reflect.Map:
|
|
||||||
// strategy: {"user_agent": "sequential", "username-password": "random"}, 每个参数对应一个迭代器,每个迭代器随机、顺序选取元素互不影响
|
|
||||||
for k, v := range parameters {
|
|
||||||
if _, ok := rawValue.Interface().(map[string]interface{})[k]; ok {
|
|
||||||
// use strategy if configured
|
|
||||||
cfg.ParametersSetting.Iterators = append(
|
|
||||||
cfg.ParametersSetting.Iterators,
|
|
||||||
newIterator(v, iteratorStrategyType(rawValue.MapIndex(reflect.ValueOf(k)).String()), cfg.ParametersSetting.Iteration),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// use sequential strategy by default
|
|
||||||
cfg.ParametersSetting.Iterators = append(
|
|
||||||
cfg.ParametersSetting.Iterators,
|
|
||||||
newIterator(v, strategySequential, cfg.ParametersSetting.Iteration),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case reflect.String:
|
|
||||||
// strategy: random, 仅生成一个的迭代器,该迭代器在参数笛卡尔积slice中随机选取元素
|
|
||||||
if len(rawValue.String()) == 0 {
|
|
||||||
cfg.ParametersSetting.Strategy = strategySequential
|
|
||||||
} else {
|
|
||||||
cfg.ParametersSetting.Strategy = iteratorStrategyType(strings.ToLower(rawValue.String()))
|
|
||||||
}
|
|
||||||
cfg.ParametersSetting.Iterators = append(
|
|
||||||
cfg.ParametersSetting.Iterators,
|
|
||||||
newIterator(genCartesianProduct(parameters), cfg.ParametersSetting.Strategy.(iteratorStrategyType), cfg.ParametersSetting.Iteration),
|
|
||||||
)
|
|
||||||
default:
|
|
||||||
// default strategy: sequential, 仅生成一个的迭代器,该迭代器在参数笛卡尔积slice中顺序选取元素
|
|
||||||
cfg.ParametersSetting.Strategy = strategySequential
|
|
||||||
cfg.ParametersSetting.Iterators = append(
|
|
||||||
cfg.ParametersSetting.Iterators,
|
|
||||||
newIterator(genCartesianProduct(parameters), cfg.ParametersSetting.Strategy.(iteratorStrategyType), cfg.ParametersSetting.Iteration),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newIterator(parameters iteratorParamsType, strategy iteratorStrategyType, iteration int) *Iterator {
|
|
||||||
iter := parameters.Iterator()
|
|
||||||
iter.strategy = strategy
|
|
||||||
if iteration > 0 {
|
|
||||||
iter.iteration = iteration
|
|
||||||
} else if iteration < 0 {
|
|
||||||
iter.iteration = -1
|
|
||||||
} else if iter.iteration == 0 {
|
|
||||||
iter.iteration = 1
|
|
||||||
}
|
|
||||||
return iter
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package hrp
|
package hrp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -754,152 +753,3 @@ func TestFindallVariables(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseParameters(t *testing.T) {
|
|
||||||
testData := []struct {
|
|
||||||
rawVars map[string]interface{}
|
|
||||||
expectLength int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"username-password": fmt.Sprintf("${parameterize(%s/account.csv)}", hrpExamplesDir),
|
|
||||||
"user_agent": []interface{}{"IOS/10.1", "IOS/10.2"},
|
|
||||||
},
|
|
||||||
6,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"username-password": [][]interface{}{
|
|
||||||
{"test1", "111111"},
|
|
||||||
{"test2", "222222"},
|
|
||||||
{"test3", "333333"},
|
|
||||||
},
|
|
||||||
"user_agent": []interface{}{"IOS/10.1", "IOS/10.2"},
|
|
||||||
"app_version": []interface{}{0.3},
|
|
||||||
},
|
|
||||||
6,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"username-password": [][]interface{}{
|
|
||||||
{"test1", "111111"},
|
|
||||||
{"test2", "222222"},
|
|
||||||
{"test3", "333333"},
|
|
||||||
},
|
|
||||||
"user_agent": []interface{}{"IOS/10.1", "IOS/10.2"},
|
|
||||||
"app_version": []interface{}{0.3, 0.4, 0.5},
|
|
||||||
},
|
|
||||||
18,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
nil,
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, data := range testData {
|
|
||||||
params, _ := parseParameters(data.rawVars, map[string]interface{}{})
|
|
||||||
value := genCartesianProduct(params)
|
|
||||||
if !assert.Len(t, value, data.expectLength) {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseParametersError(t *testing.T) {
|
|
||||||
testData := []struct {
|
|
||||||
rawVars map[string]interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"username_password": fmt.Sprintf("${parameterize(%s/account.csv)}", hrpExamplesDir),
|
|
||||||
"user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"username-password": fmt.Sprintf("${parameterize(%s/account.csv)}", hrpExamplesDir),
|
|
||||||
"user-agent": []interface{}{"IOS/10.1", "IOS/10.2"}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"username-password": fmt.Sprintf("${param(%s/account.csv)}", hrpExamplesDir),
|
|
||||||
"user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, data := range testData {
|
|
||||||
_, err := parseParameters(data.rawVars, map[string]interface{}{})
|
|
||||||
if !assert.Error(t, err) {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseSlice(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, _ := parseSlice(data.rawVar1, data.rawVar2)
|
|
||||||
if !assert.Equal(t, data.expect, value) {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseSliceError(t *testing.T) {
|
|
||||||
testData := []struct {
|
|
||||||
rawVar1 string
|
|
||||||
rawVar2 interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"app_version",
|
|
||||||
123,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"app_version",
|
|
||||||
"123",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, data := range testData {
|
|
||||||
_, err := parseSlice(data.rawVar1, data.rawVar2)
|
|
||||||
if !assert.Error(t, err) {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+2
-10
@@ -163,16 +163,8 @@ func (r *HRPRunner) Run(testcases ...ITestCase) error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 在runner模式下,指定整体策略,cfg.ParametersSetting.Iterators仅包含一个CartesianProduct的迭代器
|
for it := sessionRunner.parametersIterator; it.HasNext(); {
|
||||||
for it := sessionRunner.parsedConfig.ParametersSetting.Iterators[0]; it.HasNext(); {
|
if err = sessionRunner.Start(it.Next()); err != nil {
|
||||||
var parameterVariables map[string]interface{}
|
|
||||||
// iterate through all parameter iterators and update case variables
|
|
||||||
for _, it := range sessionRunner.parsedConfig.ParametersSetting.Iterators {
|
|
||||||
if it.HasNext() {
|
|
||||||
parameterVariables = it.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = sessionRunner.Start(parameterVariables); err != nil {
|
|
||||||
log.Error().Err(err).Msg("[Run] run testcase failed")
|
log.Error().Err(err).Msg("[Run] run testcase failed")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-9
@@ -12,11 +12,12 @@ import (
|
|||||||
// SessionRunner is used to run testcase and its steps.
|
// SessionRunner is used to run testcase and its steps.
|
||||||
// each testcase has its own SessionRunner instance and share session variables.
|
// each testcase has its own SessionRunner instance and share session variables.
|
||||||
type SessionRunner struct {
|
type SessionRunner struct {
|
||||||
testCase *TestCase
|
testCase *TestCase
|
||||||
hrpRunner *HRPRunner
|
hrpRunner *HRPRunner
|
||||||
parser *Parser
|
parser *Parser
|
||||||
parsedConfig *TConfig
|
parsedConfig *TConfig
|
||||||
sessionVariables map[string]interface{}
|
parametersIterator *ParametersIterator
|
||||||
|
sessionVariables map[string]interface{}
|
||||||
// transactions stores transaction timing info.
|
// transactions stores transaction timing info.
|
||||||
// key is transaction name, value is map of transaction type and time, e.g. start time and end time.
|
// key is transaction name, value is map of transaction type and time, e.g. start time and end time.
|
||||||
transactions map[string]map[transactionType]time.Time
|
transactions map[string]map[transactionType]time.Time
|
||||||
@@ -61,13 +62,16 @@ func (r *SessionRunner) Start(givenVars map[string]interface{}) error {
|
|||||||
Str("type", string(step.Type())).Msg("run step start")
|
Str("type", string(step.Type())).Msg("run step start")
|
||||||
|
|
||||||
stepResult, err := step.Run(r)
|
stepResult, err := step.Run(r)
|
||||||
if err != nil && r.hrpRunner.failfast {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Str("step", stepResult.Name).
|
Str("step", stepResult.Name).
|
||||||
Str("type", string(stepResult.StepType)).
|
Str("type", string(stepResult.StepType)).
|
||||||
Bool("success", false).
|
Bool("success", false).
|
||||||
Msg("run step end")
|
Msg("run step end")
|
||||||
return errors.Wrap(err, "abort running due to failfast setting")
|
|
||||||
|
if r.hrpRunner.failfast {
|
||||||
|
return errors.Wrap(err, "abort running due to failfast setting")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update extracted variables
|
// update extracted variables
|
||||||
@@ -150,11 +154,15 @@ func (r *SessionRunner) parseConfig() error {
|
|||||||
r.parsedConfig.ThinkTimeSetting.checkThinkTime()
|
r.parsedConfig.ThinkTimeSetting.checkThinkTime()
|
||||||
|
|
||||||
// parse testcase config parameters
|
// parse testcase config parameters
|
||||||
err = initParameterIterator(r.parsedConfig, "runner")
|
parametersIterator, err := initParametersIterator(r.parsedConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Interface("parameters", r.parsedConfig.Parameters).Err(err).Msg("parse config parameters failed")
|
log.Error().Err(err).
|
||||||
|
Interface("parameters", r.parsedConfig.Parameters).
|
||||||
|
Interface("parametersSetting", r.parsedConfig.ParametersSetting).
|
||||||
|
Msg("parse config parameters failed")
|
||||||
return errors.Wrap(err, "parse testcase config parameters failed")
|
return errors.Wrap(err, "parse testcase config parameters failed")
|
||||||
}
|
}
|
||||||
|
r.parametersIterator = parametersIterator
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user