refactor: TestCaseSessionRunner

This commit is contained in:
debugtalk
2022-03-30 12:06:16 +08:00
parent b3610e550b
commit 20d0749841
9 changed files with 173 additions and 158 deletions

View File

@@ -96,7 +96,6 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
Weight: config.Weight, Weight: config.Weight,
Fn: func() { Fn: func() {
sessionRunner := hrpRunner.NewSessionRunner(testcase) sessionRunner := hrpRunner.NewSessionRunner(testcase)
sessionRunner.init()
sessionRunner.parser.plugin = plugin sessionRunner.parser.plugin = plugin
testcaseSuccess := true // flag whole testcase result testcaseSuccess := true // flag whole testcase result
@@ -136,7 +135,7 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
testcaseSuccess = false testcaseSuccess = false
transactionSuccess = false transactionSuccess = false
if sessionRunner.hrpRunner.failfast { if hrpRunner.failfast {
log.Error().Msg("abort running due to failfast setting") log.Error().Msg("abort running due to failfast setting")
break break
} }

View File

@@ -17,11 +17,11 @@ import (
"github.com/httprunner/httprunner/hrp/internal/builtin" "github.com/httprunner/httprunner/hrp/internal/builtin"
) )
func newParser() *parser { func newParser() *Parser {
return &parser{} return &Parser{}
} }
type parser struct { type Parser struct {
plugin funplugin.IPlugin // plugin is used to call functions plugin funplugin.IPlugin // plugin is used to call functions
} }
@@ -42,9 +42,9 @@ func buildURL(baseURL, stepURL string) string {
return uStep.String() return uStep.String()
} }
func (p *parser) parseHeaders(rawHeaders map[string]string, variablesMapping map[string]interface{}) (map[string]string, error) { func (p *Parser) ParseHeaders(rawHeaders map[string]string, variablesMapping map[string]interface{}) (map[string]string, error) {
parsedHeaders := make(map[string]string) parsedHeaders := make(map[string]string)
headers, err := p.parseData(rawHeaders, variablesMapping) headers, err := p.Parse(rawHeaders, variablesMapping)
if err != nil { if err != nil {
return rawHeaders, err return rawHeaders, err
} }
@@ -64,7 +64,7 @@ func convertString(raw interface{}) string {
} }
} }
func (p *parser) parseData(raw interface{}, variablesMapping map[string]interface{}) (interface{}, error) { func (p *Parser) Parse(raw interface{}, variablesMapping map[string]interface{}) (interface{}, error) {
rawValue := reflect.ValueOf(raw) rawValue := reflect.ValueOf(raw)
switch rawValue.Kind() { switch rawValue.Kind() {
case reflect.String: case reflect.String:
@@ -75,11 +75,11 @@ func (p *parser) parseData(raw interface{}, variablesMapping map[string]interfac
// other string // other string
value := rawValue.String() value := rawValue.String()
value = strings.TrimSpace(value) value = strings.TrimSpace(value)
return p.parseString(value, variablesMapping) return p.ParseString(value, variablesMapping)
case reflect.Slice: case reflect.Slice:
parsedSlice := make([]interface{}, rawValue.Len()) parsedSlice := make([]interface{}, rawValue.Len())
for i := 0; i < rawValue.Len(); i++ { for i := 0; i < rawValue.Len(); i++ {
parsedValue, err := p.parseData(rawValue.Index(i).Interface(), variablesMapping) parsedValue, err := p.Parse(rawValue.Index(i).Interface(), variablesMapping)
if err != nil { if err != nil {
return raw, err return raw, err
} }
@@ -89,12 +89,12 @@ func (p *parser) parseData(raw interface{}, variablesMapping map[string]interfac
case reflect.Map: // convert any map to map[string]interface{} case reflect.Map: // convert any map to map[string]interface{}
parsedMap := make(map[string]interface{}) parsedMap := make(map[string]interface{})
for _, k := range rawValue.MapKeys() { for _, k := range rawValue.MapKeys() {
parsedKey, err := p.parseString(k.String(), variablesMapping) parsedKey, err := p.ParseString(k.String(), variablesMapping)
if err != nil { if err != nil {
return raw, err return raw, err
} }
v := rawValue.MapIndex(k) v := rawValue.MapIndex(k)
parsedValue, err := p.parseData(v.Interface(), variablesMapping) parsedValue, err := p.Parse(v.Interface(), variablesMapping)
if err != nil { if err != nil {
return raw, err return raw, err
} }
@@ -131,8 +131,8 @@ var (
regexCompileNumber = regexp.MustCompile(regexNumber) // parse number regexCompileNumber = regexp.MustCompile(regexNumber) // parse number
) )
// parseString parse string with variables // ParseString parse string with variables
func (p *parser) parseString(raw string, variablesMapping map[string]interface{}) (interface{}, error) { func (p *Parser) ParseString(raw string, variablesMapping map[string]interface{}) (interface{}, error) {
matchStartPosition := 0 matchStartPosition := 0
parsedString := "" parsedString := ""
remainedString := raw remainedString := raw
@@ -171,12 +171,12 @@ func (p *parser) parseString(raw string, variablesMapping map[string]interface{}
if err != nil { if err != nil {
return raw, err return raw, err
} }
parsedArgs, err := p.parseData(arguments, variablesMapping) parsedArgs, err := p.Parse(arguments, variablesMapping)
if err != nil { if err != nil {
return raw, err return raw, err
} }
result, err := p.callFunc(funcName, parsedArgs.([]interface{})...) result, err := p.CallFunc(funcName, parsedArgs.([]interface{})...)
if err != nil { if err != nil {
log.Error().Str("funcName", funcName).Interface("arguments", arguments). log.Error().Str("funcName", funcName).Interface("arguments", arguments).
Err(err).Msg("call function failed") Err(err).Msg("call function failed")
@@ -237,9 +237,9 @@ func (p *parser) parseString(raw string, variablesMapping map[string]interface{}
return parsedString, nil return parsedString, nil
} }
// callFunc calls function with arguments // CallFunc calls function with arguments
// only support return at most one result value // only support return at most one result value
func (p *parser) callFunc(funcName string, arguments ...interface{}) (interface{}, error) { func (p *Parser) CallFunc(funcName string, arguments ...interface{}) (interface{}, error) {
// call with plugin function // call with plugin function
if p.plugin != nil && p.plugin.Has(funcName) { if p.plugin != nil && p.plugin.Has(funcName) {
return p.plugin.Call(funcName, arguments...) return p.plugin.Call(funcName, arguments...)
@@ -342,38 +342,6 @@ func mergeSlices(slice, overriddenSlice []string) []string {
return slice return slice
} }
// extend teststep with api, teststep will merge and override referenced api
func extendWithAPI(testStep *TStep, overriddenStep *API) {
// override api name
if testStep.Name == "" {
testStep.Name = overriddenStep.Name
}
// merge & override request
testStep.Request = overriddenStep.Request
// merge & override variables
testStep.Variables = mergeVariables(testStep.Variables, overriddenStep.Variables)
// merge & override extractors
testStep.Extract = mergeMap(testStep.Extract, overriddenStep.Extract)
// merge & override validators
testStep.Validators = mergeValidators(testStep.Validators, overriddenStep.Validators)
// merge & override setupHooks
testStep.SetupHooks = mergeSlices(testStep.SetupHooks, overriddenStep.SetupHooks)
// merge & override teardownHooks
testStep.TeardownHooks = mergeSlices(testStep.TeardownHooks, overriddenStep.TeardownHooks)
}
// extend referenced testcase with teststep, teststep config merge and override referenced testcase config
func extendWithTestCase(testStep *TStep, overriddenTestCase *TestCase) {
// override testcase name
if testStep.Name != "" {
overriddenTestCase.Config.Name = testStep.Name
}
// merge & override variables
overriddenTestCase.Config.Variables = mergeVariables(testStep.Variables, overriddenTestCase.Config.Variables)
// merge & override extractors
overriddenTestCase.Config.Export = mergeSlices(testStep.Export, overriddenTestCase.Config.Export)
}
var eval = goval.NewEvaluator() var eval = goval.NewEvaluator()
// literalEval parse string to number if possible // literalEval parse string to number if possible
@@ -420,7 +388,7 @@ func parseFunctionArguments(argsStr string) ([]interface{}, error) {
return arguments, nil return arguments, nil
} }
func (p *parser) parseVariables(variables map[string]interface{}) (map[string]interface{}, error) { func (p *Parser) ParseVariables(variables map[string]interface{}) (map[string]interface{}, error) {
parsedVariables := make(map[string]interface{}) parsedVariables := make(map[string]interface{})
var traverseRounds int var traverseRounds int
@@ -458,7 +426,7 @@ func (p *parser) parseVariables(variables map[string]interface{}) (map[string]in
return variables, fmt.Errorf("variable not defined: %v", undefinedVars) return variables, fmt.Errorf("variable not defined: %v", undefinedVars)
} }
parsedValue, err := p.parseData(varValue, parsedVariables) parsedValue, err := p.Parse(varValue, parsedVariables)
if err != nil { if err != nil {
continue continue
} }
@@ -588,7 +556,7 @@ func parseParameters(parameters map[string]interface{}, variablesMapping map[str
case reflect.String: case reflect.String:
// e.g. username-password: ${parameterize(examples/hrp/account.csv)} -> [{"username": "test1", "password": "111111"}, {"username": "test2", "password": "222222"}] // e.g. username-password: ${parameterize(examples/hrp/account.csv)} -> [{"username": "test1", "password": "111111"}, {"username": "test2", "password": "222222"}]
var parsedParameterContent interface{} var parsedParameterContent interface{}
parsedParameterContent, err = newParser().parseString(rawValue.String(), variablesMapping) parsedParameterContent, err = newParser().ParseString(rawValue.String(), variablesMapping)
if err != nil { if err != nil {
log.Error().Interface("parameterContent", rawValue).Msg("[parseParameters] parse parameter content error") log.Error().Interface("parameterContent", rawValue).Msg("[parseParameters] parse parameter content error")
return nil, err return nil, err

View File

@@ -163,7 +163,7 @@ func TestParseDataStringWithVariables(t *testing.T) {
parser := newParser() parser := newParser()
for _, data := range testData { for _, data := range testData {
parsedData, err := parser.parseData(data.expr, variablesMapping) parsedData, err := parser.Parse(data.expr, variablesMapping)
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.Fail() t.Fail()
} }
@@ -188,7 +188,7 @@ func TestParseDataStringWithUndefinedVariables(t *testing.T) {
parser := newParser() parser := newParser()
for _, data := range testData { for _, data := range testData {
parsedData, err := parser.parseData(data.expr, variablesMapping) parsedData, err := parser.Parse(data.expr, variablesMapping)
if !assert.Error(t, err) { if !assert.Error(t, err) {
t.Fail() t.Fail()
} }
@@ -233,7 +233,7 @@ func TestParseDataStringWithVariablesAbnormal(t *testing.T) {
parser := newParser() parser := newParser()
for _, data := range testData { for _, data := range testData {
parsedData, err := parser.parseData(data.expr, variablesMapping) parsedData, err := parser.Parse(data.expr, variablesMapping)
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.Fail() t.Fail()
} }
@@ -264,7 +264,7 @@ func TestParseDataMapWithVariables(t *testing.T) {
parser := newParser() parser := newParser()
for _, data := range testData { for _, data := range testData {
parsedData, err := parser.parseData(data.expr, variablesMapping) parsedData, err := parser.Parse(data.expr, variablesMapping)
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.Fail() t.Fail()
} }
@@ -298,7 +298,7 @@ func TestParseHeaders(t *testing.T) {
parser := newParser() parser := newParser()
for _, data := range testData { for _, data := range testData {
parsedHeaders, err := parser.parseHeaders(data.rawHeaders, variablesMapping) parsedHeaders, err := parser.ParseHeaders(data.rawHeaders, variablesMapping)
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.Fail() t.Fail()
} }
@@ -444,14 +444,14 @@ func TestCallBuiltinFunction(t *testing.T) {
parser := newParser() parser := newParser()
// call function without arguments // call function without arguments
_, err := parser.callFunc("get_timestamp") _, err := parser.CallFunc("get_timestamp")
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.Fail() t.Fail()
} }
// call function with one argument // call function with one argument
timeStart := time.Now() timeStart := time.Now()
_, err = parser.callFunc("sleep", 1) _, err = parser.CallFunc("sleep", 1)
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.Fail() t.Fail()
} }
@@ -460,7 +460,7 @@ func TestCallBuiltinFunction(t *testing.T) {
} }
// call function with one argument // call function with one argument
result, err := parser.callFunc("gen_random_string", 10) result, err := parser.CallFunc("gen_random_string", 10)
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.Fail() t.Fail()
} }
@@ -469,7 +469,7 @@ func TestCallBuiltinFunction(t *testing.T) {
} }
// call function with two argument // call function with two argument
result, err = parser.callFunc("max", float64(10), 9.99) result, err = parser.CallFunc("max", float64(10), 9.99)
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.Fail() t.Fail()
} }
@@ -556,7 +556,7 @@ func TestParseDataStringWithFunctions(t *testing.T) {
parser := newParser() parser := newParser()
for _, data := range testData1 { for _, data := range testData1 {
value, err := parser.parseData(data.expr, variablesMapping) value, err := parser.Parse(data.expr, variablesMapping)
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.Fail() t.Fail()
} }
@@ -575,7 +575,7 @@ func TestParseDataStringWithFunctions(t *testing.T) {
} }
for _, data := range testData2 { for _, data := range testData2 {
value, err := parser.parseData(data.expr, variablesMapping) value, err := parser.Parse(data.expr, variablesMapping)
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.Fail() t.Fail()
} }
@@ -623,7 +623,7 @@ func TestParseVariables(t *testing.T) {
parser := newParser() parser := newParser()
for _, data := range testData { for _, data := range testData {
value, err := parser.parseVariables(data.rawVars) value, err := parser.ParseVariables(data.rawVars)
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.Fail() t.Fail()
} }
@@ -654,7 +654,7 @@ func TestParseVariablesAbnormal(t *testing.T) {
parser := newParser() parser := newParser()
for _, data := range testData { for _, data := range testData {
value, err := parser.parseVariables(data.rawVars) value, err := parser.ParseVariables(data.rawVars)
if !assert.Error(t, err) { if !assert.Error(t, err) {
t.Fail() t.Fail()
} }

View File

@@ -18,7 +18,7 @@ import (
"github.com/httprunner/httprunner/hrp/internal/json" "github.com/httprunner/httprunner/hrp/internal/json"
) )
func newResponseObject(t *testing.T, parser *parser, resp *http.Response) (*responseObject, error) { func newResponseObject(t *testing.T, parser *Parser, resp *http.Response) (*responseObject, error) {
// prepare response headers // prepare response headers
headers := make(map[string]string) headers := make(map[string]string)
for k, v := range resp.Header { for k, v := range resp.Header {
@@ -82,7 +82,7 @@ type respObjMeta struct {
type responseObject struct { type responseObject struct {
t *testing.T t *testing.T
parser *parser parser *Parser
respObjMeta interface{} respObjMeta interface{}
validationResults []*ValidationResult validationResults []*ValidationResult
} }
@@ -126,7 +126,7 @@ func (v *responseObject) Validate(iValidators []interface{}, variablesMapping ma
var checkValue interface{} var checkValue interface{}
if strings.Contains(checkItem, "$") { if strings.Contains(checkItem, "$") {
// reference variable // reference variable
checkValue, err = v.parser.parseData(checkItem, variablesMapping) checkValue, err = v.parser.Parse(checkItem, variablesMapping)
if err != nil { if err != nil {
return err return err
} }
@@ -143,7 +143,7 @@ func (v *responseObject) Validate(iValidators []interface{}, variablesMapping ma
} }
// parse expected value // parse expected value
expectValue, err := v.parser.parseData(validator.Expect, variablesMapping) expectValue, err := v.parser.Parse(validator.Expect, variablesMapping)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -151,11 +151,11 @@ func (r *HRPRunner) Run(testcases ...ITestCase) error {
} }
} }
sessionRunner := r.NewSessionRunner(testcase) sessionRunner := r.NewSessionRunner(testcase)
if err = sessionRunner.Run(); err != nil { if err = sessionRunner.Start(); err != nil {
log.Error().Err(err).Msg("[Run] run testcase failed") log.Error().Err(err).Msg("[Run] run testcase failed")
return err return err
} }
caseSummary := sessionRunner.getSummary() caseSummary := sessionRunner.GetSummary()
s.appendCaseSummary(caseSummary) s.appendCaseSummary(caseSummary)
} }
} }
@@ -191,5 +191,6 @@ func (r *HRPRunner) NewSessionRunner(testcase *TestCase) *SessionRunner {
parser: newParser(), parser: newParser(),
summary: newSummary(), summary: newSummary(),
} }
sessionRunner.init()
return sessionRunner return sessionRunner
} }

View File

@@ -4,7 +4,6 @@ import (
_ "embed" _ "embed"
"time" "time"
"github.com/jinzhu/copier"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -14,7 +13,7 @@ import (
type SessionRunner struct { type SessionRunner struct {
testCase *TestCase testCase *TestCase
hrpRunner *HRPRunner hrpRunner *HRPRunner
parser *parser parser *Parser
sessionVariables map[string]interface{} 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.
@@ -31,8 +30,20 @@ func (r *SessionRunner) init() {
r.summary.Name = r.testCase.Config.Name r.summary.Name = r.testCase.Config.Name
} }
// Run runs the test steps in sequential order. func (r *SessionRunner) GetParser() *Parser {
func (r *SessionRunner) Run() error { return r.parser
}
func (r *SessionRunner) GetConfig() *TConfig {
return r.testCase.Config
}
func (r *SessionRunner) LogOn() bool {
return r.hrpRunner.requestsLogOn
}
// Start runs the test steps in sequential order.
func (r *SessionRunner) Start() error {
config := r.testCase.Config config := r.testCase.Config
log.Info().Str("testcase", config.Name).Msg("run testcase start") log.Info().Str("testcase", config.Name).Msg("run testcase start")
@@ -68,48 +79,46 @@ func (r *SessionRunner) Run() error {
return nil return nil
} }
func (r *SessionRunner) overrideVariables(step *TStep) (*TStep, error) { func (r *SessionRunner) UpdateSession(vars map[string]interface{}) {
// copy step and config to avoid data racing for k, v := range vars {
copiedStep := &TStep{} r.sessionVariables[k] = v
if err := copier.Copy(copiedStep, step); err != nil {
log.Error().Err(err).Msg("copy step data failed")
return nil, err
} }
stepVariables := copiedStep.Variables
// override variables
// step variables > session variables (extracted variables from previous steps)
stepVariables = mergeVariables(stepVariables, r.sessionVariables)
// step variables > testcase config variables
stepVariables = mergeVariables(stepVariables, r.testCase.Config.Variables)
// parse step variables
parsedVariables, err := r.parser.parseVariables(stepVariables)
if err != nil {
log.Error().Interface("variables", r.testCase.Config.Variables).Err(err).Msg("parse step variables failed")
return nil, err
}
copiedStep.Variables = parsedVariables // avoid data racing
return copiedStep, nil
} }
func (r *SessionRunner) overrideConfig(step *TStep) { // UpdateSummary appends step result to summary
// override headers func (r *SessionRunner) UpdateSummary(stepResult *StepResult) {
if r.testCase.Config.Headers != nil { r.summary.Records = append(r.summary.Records, stepResult)
step.Request.Headers = mergeMap(step.Request.Headers, r.testCase.Config.Headers) r.summary.Stat.Total += 1
if stepResult.Success {
r.summary.Stat.Successes += 1
} else {
r.summary.Stat.Failures += 1
// update summary result to failed
r.summary.Success = false
} }
// parse step request url }
requestUrl, err := r.parser.parseString(step.Request.URL, step.Variables)
// MergeStepVariables merges step variables with config variables and session variables
func (r *SessionRunner) MergeStepVariables(vars map[string]interface{}) (map[string]interface{}, error) {
// override variables
// step variables > session variables (extracted variables from previous steps)
overrideVars := mergeVariables(vars, r.sessionVariables)
// step variables > testcase config variables
overrideVars = mergeVariables(overrideVars, r.testCase.Config.Variables)
// parse step variables
parsedVariables, err := r.parser.ParseVariables(overrideVars)
if err != nil { if err != nil {
log.Error().Err(err).Msg("parse request url failed") log.Error().Interface("variables", r.testCase.Config.Variables).
requestUrl = step.Variables Err(err).Msg("parse step variables failed")
return nil, err
} }
step.Request.URL = buildURL(r.testCase.Config.BaseURL, convertString(requestUrl)) // avoid data racing return parsedVariables, nil
} }
func (r *SessionRunner) parseConfig(cfg *TConfig) error { func (r *SessionRunner) parseConfig(cfg *TConfig) error {
// parse config variables // parse config variables
parsedVariables, err := r.parser.parseVariables(cfg.Variables) parsedVariables, err := r.parser.ParseVariables(cfg.Variables)
if err != nil { if err != nil {
log.Error().Interface("variables", cfg.Variables).Err(err).Msg("parse config variables failed") log.Error().Interface("variables", cfg.Variables).Err(err).Msg("parse config variables failed")
return err return err
@@ -117,14 +126,14 @@ func (r *SessionRunner) parseConfig(cfg *TConfig) error {
cfg.Variables = parsedVariables cfg.Variables = parsedVariables
// parse config name // parse config name
parsedName, err := r.parser.parseString(cfg.Name, cfg.Variables) parsedName, err := r.parser.ParseString(cfg.Name, cfg.Variables)
if err != nil { if err != nil {
return err return err
} }
cfg.Name = convertString(parsedName) cfg.Name = convertString(parsedName)
// parse config base url // parse config base url
parsedBaseURL, err := r.parser.parseString(cfg.BaseURL, cfg.Variables) parsedBaseURL, err := r.parser.ParseString(cfg.BaseURL, cfg.Variables)
if err != nil { if err != nil {
return err return err
} }
@@ -136,7 +145,7 @@ func (r *SessionRunner) parseConfig(cfg *TConfig) error {
return nil return nil
} }
func (r *SessionRunner) getSummary() *TestCaseSummary { func (r *SessionRunner) GetSummary() *TestCaseSummary {
caseSummary := r.summary caseSummary := r.summary
caseSummary.Time.StartAt = r.startTime caseSummary.Time.StartAt = r.startTime
caseSummary.Time.Duration = time.Since(r.startTime).Seconds() caseSummary.Time.Duration = time.Since(r.startTime).Seconds()

View File

@@ -107,3 +107,23 @@ func (s *StepAPIWithOptionalArgs) Run(r *SessionRunner) (*StepResult, error) {
stepResult.StepType = stepTypeAPI stepResult.StepType = stepTypeAPI
return stepResult, nil return stepResult, nil
} }
// extend teststep with api, teststep will merge and override referenced api
func extendWithAPI(testStep *TStep, overriddenStep *API) {
// override api name
if testStep.Name == "" {
testStep.Name = overriddenStep.Name
}
// merge & override request
testStep.Request = overriddenStep.Request
// merge & override variables
testStep.Variables = mergeVariables(testStep.Variables, overriddenStep.Variables)
// merge & override extractors
testStep.Extract = mergeMap(testStep.Extract, overriddenStep.Extract)
// merge & override validators
testStep.Validators = mergeValidators(testStep.Validators, overriddenStep.Validators)
// merge & override setupHooks
testStep.SetupHooks = mergeSlices(testStep.SetupHooks, overriddenStep.SetupHooks)
// merge & override teardownHooks
testStep.TeardownHooks = mergeSlices(testStep.TeardownHooks, overriddenStep.TeardownHooks)
}

View File

@@ -51,42 +51,55 @@ type Request struct {
} }
func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err error) { func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err error) {
step, err = r.overrideVariables(step)
if err != nil {
return nil, err
}
r.overrideConfig(step)
log.Info().Str("step", step.Name).Msg("run step start") log.Info().Str("step", step.Name).Msg("run step start")
stepResult = &StepResult{ stepResult = &StepResult{
Name: step.Name, Name: step.Name,
StepType: stepTypeRequest, StepType: stepTypeRequest,
Success: false, Success: false,
ContentSize: 0, ContentSize: 0,
} }
sessionData := newSessionData()
defer func() { defer func() {
// update testcase summary
if err != nil { if err != nil {
log.Error().Err(err).Msg("run request step failed") log.Error().Err(err).Msg("run request step failed")
stepResult.Attachment = err.Error() stepResult.Attachment = err.Error()
r.summary.Success = false
} else { } else {
// update extracted variables
r.UpdateSession(stepResult.ExportVars)
log.Info(). log.Info().
Str("step", step.Name). Str("step", step.Name).
Bool("success", stepResult.Success). Bool("success", stepResult.Success).
Interface("exportVars", stepResult.ExportVars). Interface("exportVars", stepResult.ExportVars).
Msg("run step end") Msg("run step end")
} }
r.UpdateSummary(stepResult)
}() }()
sessionData := newSessionData()
parser := r.GetParser()
config := r.GetConfig()
// override step variables
stepVariables, err := r.MergeStepVariables(step.Variables)
if err != nil {
return
}
// convert request struct to map // convert request struct to map
jsonRequest, _ := json.Marshal(&step.Request) jsonRequest, _ := json.Marshal(&step.Request)
var requestMap map[string]interface{} var requestMap map[string]interface{}
_ = json.Unmarshal(jsonRequest, &requestMap) _ = json.Unmarshal(jsonRequest, &requestMap)
rawUrl := step.Request.URL // parse step request url
requestUrl, err := r.parser.ParseString(step.Request.URL, stepVariables)
if err != nil {
log.Error().Err(err).Msg("parse request url failed")
return
}
rawUrl := buildURL(r.testCase.Config.BaseURL, convertString(requestUrl))
method := step.Request.Method method := step.Request.Method
req := &http.Request{ req := &http.Request{
Method: string(method), Method: string(method),
@@ -97,8 +110,14 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
} }
// prepare request headers // prepare request headers
if len(step.Request.Headers) > 0 { stepHeaders := step.Request.Headers
headers, err := r.parser.parseHeaders(step.Request.Headers, step.Variables) if config.Headers != nil {
// override headers
stepHeaders = mergeMap(stepHeaders, config.Headers)
}
if len(stepHeaders) > 0 {
headers, err := parser.ParseHeaders(stepHeaders, stepVariables)
if err != nil { if err != nil {
return stepResult, errors.Wrap(err, "parse headers failed") return stepResult, errors.Wrap(err, "parse headers failed")
} }
@@ -121,7 +140,7 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
// prepare request params // prepare request params
var queryParams url.Values var queryParams url.Values
if len(step.Request.Params) > 0 { if len(step.Request.Params) > 0 {
params, err := r.parser.parseData(step.Request.Params, step.Variables) params, err := parser.Parse(step.Request.Params, stepVariables)
if err != nil { if err != nil {
return stepResult, errors.Wrap(err, "parse request params failed") return stepResult, errors.Wrap(err, "parse request params failed")
} }
@@ -146,7 +165,7 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
// prepare request cookies // prepare request cookies
for cookieName, cookieValue := range step.Request.Cookies { for cookieName, cookieValue := range step.Request.Cookies {
value, err := r.parser.parseData(cookieValue, step.Variables) value, err := parser.Parse(cookieValue, stepVariables)
if err != nil { if err != nil {
return stepResult, errors.Wrap(err, "parse cookie value failed") return stepResult, errors.Wrap(err, "parse cookie value failed")
} }
@@ -158,7 +177,7 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
// prepare request body // prepare request body
if step.Request.Body != nil { if step.Request.Body != nil {
data, err := r.parser.parseData(step.Request.Body, step.Variables) data, err := parser.Parse(step.Request.Body, stepVariables)
if err != nil { if err != nil {
return stepResult, err return stepResult, err
} }
@@ -232,19 +251,19 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
req.Host = u.Host req.Host = u.Host
// add request object to step variables, could be used in setup hooks // add request object to step variables, could be used in setup hooks
step.Variables["hrp_step_name"] = step.Name stepVariables["hrp_step_name"] = step.Name
step.Variables["hrp_step_request"] = requestMap stepVariables["hrp_step_request"] = requestMap
// deal with setup hooks // deal with setup hooks
for _, setupHook := range step.SetupHooks { for _, setupHook := range step.SetupHooks {
_, err = r.parser.parseData(setupHook, step.Variables) _, err = parser.Parse(setupHook, stepVariables)
if err != nil { if err != nil {
return stepResult, errors.Wrap(err, "run setup hooks failed") return stepResult, errors.Wrap(err, "run setup hooks failed")
} }
} }
// log & print request // log & print request
if r.hrpRunner.requestsLogOn { if r.LogOn() {
if err := printRequest(req); err != nil { if err := printRequest(req); err != nil {
return stepResult, err return stepResult, err
} }
@@ -266,25 +285,25 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
} }
// log & print response // log & print response
if r.hrpRunner.requestsLogOn { if r.LogOn() {
if err := printResponse(resp); err != nil { if err := printResponse(resp); err != nil {
return stepResult, err return stepResult, err
} }
} }
// new response object // new response object
respObj, err := newResponseObject(r.hrpRunner.t, r.parser, resp) respObj, err := newResponseObject(r.hrpRunner.t, parser, resp)
if err != nil { if err != nil {
err = errors.Wrap(err, "init ResponseObject error") err = errors.Wrap(err, "init ResponseObject error")
return return
} }
// add response object to step variables, could be used in teardown hooks // add response object to step variables, could be used in teardown hooks
step.Variables["hrp_step_response"] = respObj.respObjMeta stepVariables["hrp_step_response"] = respObj.respObjMeta
// deal with teardown hooks // deal with teardown hooks
for _, teardownHook := range step.TeardownHooks { for _, teardownHook := range step.TeardownHooks {
_, err = r.parser.parseData(teardownHook, step.Variables) _, err = parser.Parse(teardownHook, stepVariables)
if err != nil { if err != nil {
return stepResult, errors.Wrap(err, "run teardown hooks failed") return stepResult, errors.Wrap(err, "run teardown hooks failed")
} }
@@ -298,13 +317,8 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
extractMapping := respObj.Extract(extractors) extractMapping := respObj.Extract(extractors)
stepResult.ExportVars = extractMapping stepResult.ExportVars = extractMapping
// update extracted variables
for k, v := range stepResult.ExportVars {
r.sessionVariables[k] = v
}
// override step variables with extracted variables // override step variables with extracted variables
stepVariables := mergeVariables(step.Variables, extractMapping) stepVariables = mergeVariables(stepVariables, extractMapping)
// validate response // validate response
err = respObj.Validate(step.Validators, stepVariables) err = respObj.Validate(step.Validators, stepVariables)
@@ -316,15 +330,6 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
stepResult.ContentSize = resp.ContentLength stepResult.ContentSize = resp.ContentLength
stepResult.Data = sessionData stepResult.Data = sessionData
// append step result to summary
r.summary.Records = append(r.summary.Records, stepResult)
r.summary.Stat.Total += 1
if stepResult.Success {
r.summary.Stat.Successes += 1
} else {
r.summary.Stat.Failures += 1
}
return stepResult, err return stepResult, err
} }

View File

@@ -44,18 +44,19 @@ func (s *StepTestCaseWithOptionalArgs) ToStruct() *TStep {
} }
func (s *StepTestCaseWithOptionalArgs) Run(r *SessionRunner) (*StepResult, error) { func (s *StepTestCaseWithOptionalArgs) Run(r *SessionRunner) (*StepResult, error) {
copiedStep, err := r.overrideVariables(s.step) stepVariables, err := r.MergeStepVariables(s.step.Variables)
if err != nil { if err != nil {
return nil, err return nil, err
} }
s.step.Variables = stepVariables
log.Info().Str("testcase", copiedStep.Name).Msg("run referenced testcase") log.Info().Str("testcase", s.step.Name).Msg("run referenced testcase")
stepResult := &StepResult{ stepResult := &StepResult{
Name: copiedStep.Name, Name: s.step.Name,
StepType: stepTypeTestCase, StepType: stepTypeTestCase,
Success: false, Success: false,
} }
testcase := copiedStep.TestCase.(*TestCase) testcase := s.step.TestCase.(*TestCase)
// copy testcase to avoid data racing // copy testcase to avoid data racing
copiedTestCase := &TestCase{} copiedTestCase := &TestCase{}
@@ -64,21 +65,21 @@ func (s *StepTestCaseWithOptionalArgs) Run(r *SessionRunner) (*StepResult, error
return stepResult, err return stepResult, err
} }
// override testcase config // override testcase config
extendWithTestCase(copiedStep, copiedTestCase) extendWithTestCase(s.step, copiedTestCase)
sessionRunner := r.hrpRunner.NewSessionRunner(copiedTestCase) sessionRunner := r.hrpRunner.NewSessionRunner(copiedTestCase)
start := time.Now() start := time.Now()
err = sessionRunner.Run() err = sessionRunner.Start()
stepResult.Elapsed = time.Since(start).Milliseconds() stepResult.Elapsed = time.Since(start).Milliseconds()
if err != nil { if err != nil {
log.Error().Err(err).Msg("run referenced testcase step failed") log.Error().Err(err).Msg("run referenced testcase step failed")
log.Info().Str("step", copiedStep.Name).Bool("success", false).Msg("run step end") log.Info().Str("step", s.step.Name).Bool("success", false).Msg("run step end")
stepResult.Attachment = err.Error() stepResult.Attachment = err.Error()
r.summary.Success = false r.summary.Success = false
return stepResult, err return stepResult, err
} }
summary := sessionRunner.getSummary() summary := sessionRunner.GetSummary()
stepResult.Data = summary stepResult.Data = summary
// export testcase export variables // export testcase export variables
stepResult.ExportVars = sessionRunner.summary.InOut.ExportVars stepResult.ExportVars = sessionRunner.summary.InOut.ExportVars
@@ -96,10 +97,22 @@ func (s *StepTestCaseWithOptionalArgs) Run(r *SessionRunner) (*StepResult, error
r.summary.Stat.Failures += summary.Stat.Failures r.summary.Stat.Failures += summary.Stat.Failures
log.Info(). log.Info().
Str("step", copiedStep.Name). Str("step", s.step.Name).
Bool("success", true). Bool("success", true).
Interface("exportVars", stepResult.ExportVars). Interface("exportVars", stepResult.ExportVars).
Msg("run step end") Msg("run step end")
return stepResult, nil return stepResult, nil
} }
// extend referenced testcase with teststep, teststep config merge and override referenced testcase config
func extendWithTestCase(testStep *TStep, overriddenTestCase *TestCase) {
// override testcase name
if testStep.Name != "" {
overriddenTestCase.Config.Name = testStep.Name
}
// merge & override variables
overriddenTestCase.Config.Variables = mergeVariables(testStep.Variables, overriddenTestCase.Config.Variables)
// merge & override extractors
overriddenTestCase.Config.Export = mergeSlices(testStep.Export, overriddenTestCase.Config.Export)
}