fix: data racing by parsing config

This commit is contained in:
debugtalk
2022-04-12 21:47:04 +08:00
parent c6286a222a
commit f07d96cb5e
5 changed files with 47 additions and 51 deletions

View File

@@ -5,7 +5,6 @@ import (
"sync" "sync"
"time" "time"
"github.com/jinzhu/copier"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/httprunner/funplugin" "github.com/httprunner/funplugin"
@@ -98,27 +97,21 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
Name: config.Name, Name: config.Name,
Weight: config.Weight, Weight: config.Weight,
Fn: func() { Fn: func() {
sessionTestCase := &TestCase{} sessionRunner := hrpRunner.NewSessionRunner(testcase)
// copy testcase to avoid data racing
if err := copier.Copy(sessionTestCase, testcase); err != nil {
log.Error().Err(err).Msg("copy testcase data failed")
return
}
sessionRunner := hrpRunner.NewSessionRunner(sessionTestCase)
sessionRunner.parser.plugin = plugin sessionRunner.parser.plugin = plugin
testcaseSuccess := true // flag whole testcase result testcaseSuccess := true // flag whole testcase result
var transactionSuccess = true // flag current transaction result var transactionSuccess = true // flag current transaction result
cfg := sessionTestCase.Config var parameterVariables map[string]interface{}
// iterate through all parameter iterators and update case variables // iterate through all parameter iterators and update case variables
for _, it := range cfg.ParametersSetting.Iterators { for _, it := range testcase.Config.ParametersSetting.Iterators {
if it.HasNext() { if it.HasNext() {
cfg.Variables = mergeVariables(it.Next(), cfg.Variables) parameterVariables = it.Next()
} }
} }
if err := sessionRunner.parseConfig(cfg); err != nil { if err := sessionRunner.parseConfig(parameterVariables); err != nil {
log.Error().Err(err).Msg("parse config failed") log.Error().Err(err).Msg("parse config failed")
return return
} }

View File

@@ -4,6 +4,7 @@ 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,6 +15,7 @@ type SessionRunner struct {
testCase *TestCase testCase *TestCase
hrpRunner *HRPRunner hrpRunner *HRPRunner
parser *Parser parser *Parser
parsedConfig *TConfig
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.
@@ -24,10 +26,10 @@ type SessionRunner struct {
func (r *SessionRunner) init() { func (r *SessionRunner) init() {
log.Info().Msg("init session runner") log.Info().Msg("init session runner")
r.parsedConfig = &TConfig{}
r.sessionVariables = make(map[string]interface{}) r.sessionVariables = make(map[string]interface{})
r.transactions = make(map[string]map[transactionType]time.Time) r.transactions = make(map[string]map[transactionType]time.Time)
r.startTime = time.Now() r.startTime = time.Now()
r.summary.Name = r.testCase.Config.Name
} }
func (r *SessionRunner) GetParser() *Parser { func (r *SessionRunner) GetParser() *Parser {
@@ -35,7 +37,7 @@ func (r *SessionRunner) GetParser() *Parser {
} }
func (r *SessionRunner) GetConfig() *TConfig { func (r *SessionRunner) GetConfig() *TConfig {
return r.testCase.Config return r.parsedConfig
} }
func (r *SessionRunner) LogOn() bool { func (r *SessionRunner) LogOn() bool {
@@ -62,7 +64,7 @@ func (r *SessionRunner) Start() error {
}() }()
// parse config // parse config
if err := r.parseConfig(config); err != nil { if err := r.parseConfig(nil); err != nil {
return err return err
} }
@@ -105,56 +107,67 @@ func (r *SessionRunner) MergeStepVariables(vars map[string]interface{}) (map[str
// step variables > session variables (extracted variables from previous steps) // step variables > session variables (extracted variables from previous steps)
overrideVars := mergeVariables(vars, r.sessionVariables) overrideVars := mergeVariables(vars, r.sessionVariables)
// step variables > testcase config variables // step variables > testcase config variables
overrideVars = mergeVariables(overrideVars, r.testCase.Config.Variables) overrideVars = mergeVariables(overrideVars, r.parsedConfig.Variables)
// parse step variables // parse step variables
parsedVariables, err := r.parser.ParseVariables(overrideVars) parsedVariables, err := r.parser.ParseVariables(overrideVars)
if err != nil { if err != nil {
log.Error().Interface("variables", r.testCase.Config.Variables). log.Error().Interface("variables", r.parsedConfig.Variables).
Err(err).Msg("parse step variables failed") Err(err).Msg("parse step variables failed")
return nil, err return nil, err
} }
return parsedVariables, nil return parsedVariables, nil
} }
func (r *SessionRunner) parseConfig(cfg *TConfig) error { // parseConfig parses testcase config with given variables, stores to parsedConfig.
func (r *SessionRunner) parseConfig(variables map[string]interface{}) error {
cfg := r.testCase.Config
// deep copy config to avoid data racing
if err := copier.Copy(r.parsedConfig, cfg); err != nil {
log.Error().Err(err).Msg("copy testcase config failed")
return err
}
// parse config variables // parse config variables
parsedVariables, err := r.parser.ParseVariables(cfg.Variables) mergedVars := mergeVariables(variables, cfg.Variables)
parsedVariables, err := r.parser.ParseVariables(mergedVars)
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
} }
cfg.Variables = parsedVariables r.parsedConfig.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) r.parsedConfig.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
} }
cfg.BaseURL = convertString(parsedBaseURL) r.parsedConfig.BaseURL = convertString(parsedBaseURL)
// ensure correction of think time config // ensure correction of think time config
cfg.ThinkTimeSetting.checkThinkTime() r.parsedConfig.ThinkTimeSetting.checkThinkTime()
return nil return nil
} }
func (r *SessionRunner) GetSummary() *TestCaseSummary { func (r *SessionRunner) GetSummary() *TestCaseSummary {
caseSummary := r.summary caseSummary := r.summary
caseSummary.Name = r.parsedConfig.Name
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()
exportVars := make(map[string]interface{}) exportVars := make(map[string]interface{})
for _, value := range r.testCase.Config.Export { for _, value := range r.parsedConfig.Export {
exportVars[value] = r.sessionVariables[value] exportVars[value] = r.sessionVariables[value]
} }
caseSummary.InOut.ExportVars = exportVars caseSummary.InOut.ExportVars = exportVars
caseSummary.InOut.ConfigVars = r.testCase.Config.Variables caseSummary.InOut.ConfigVars = r.parsedConfig.Variables
return caseSummary return caseSummary
} }

View File

@@ -81,6 +81,7 @@ func TestRunRequestRun(t *testing.T) {
} }
runner := NewRunner(t).SetRequestsLogOn() runner := NewRunner(t).SetRequestsLogOn()
sessionRunner := runner.NewSessionRunner(testcase) sessionRunner := runner.NewSessionRunner(testcase)
sessionRunner.parseConfig(nil)
if _, err := stepGET.Run(sessionRunner); err != nil { if _, err := stepGET.Run(sessionRunner); err != nil {
t.Fatalf("stepGET.Run() error: %v", err) t.Fatalf("stepGET.Run() error: %v", err)
} }

View File

@@ -55,18 +55,24 @@ func (s *StepTestCaseWithOptionalArgs) Run(r *SessionRunner) (*StepResult, error
return stepResult, err return stepResult, err
} }
// copy step to avoid data racing stepTestCase := s.step.TestCase.(*TestCase)
copiedStep := &TStep{}
if err := copier.Copy(copiedStep, s.step); err != nil { // copy testcase to avoid data racing
log.Error().Err(err).Msg("copy step failed") copiedTestCase := &TestCase{}
if err := copier.Copy(copiedTestCase, stepTestCase); err != nil {
log.Error().Err(err).Msg("copy step testcase failed")
return stepResult, err return stepResult, err
} }
copiedStep.Variables = stepVariables
copiedTestCase := copiedStep.TestCase.(*TestCase)
// override testcase config // override testcase config
extendWithTestCase(s.step, copiedTestCase) // override testcase name
if s.step.Name != "" {
copiedTestCase.Config.Name = s.step.Name
}
// merge & override variables
copiedTestCase.Config.Variables = mergeVariables(stepVariables, copiedTestCase.Config.Variables)
// merge & override extractors
copiedTestCase.Config.Export = mergeSlices(s.step.Export, copiedTestCase.Config.Export)
sessionRunner := r.hrpRunner.NewSessionRunner(copiedTestCase) sessionRunner := r.hrpRunner.NewSessionRunner(copiedTestCase)
@@ -84,11 +90,6 @@ func (s *StepTestCaseWithOptionalArgs) Run(r *SessionRunner) (*StepResult, error
stepResult.ExportVars = summary.InOut.ExportVars stepResult.ExportVars = summary.InOut.ExportVars
stepResult.Success = true stepResult.Success = true
// update extracted variables
for k, v := range stepResult.ExportVars {
r.sessionVariables[k] = v
}
// merge testcase summary // merge testcase summary
r.summary.Records = append(r.summary.Records, summary.Records...) r.summary.Records = append(r.summary.Records, summary.Records...)
r.summary.Stat.Total += summary.Stat.Total r.summary.Stat.Total += summary.Stat.Total
@@ -97,15 +98,3 @@ func (s *StepTestCaseWithOptionalArgs) Run(r *SessionRunner) (*StepResult, error
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)
}

View File

@@ -38,7 +38,7 @@ func (s *StepThinkTime) Run(r *SessionRunner) (*StepResult, error) {
Success: true, Success: true,
} }
cfg := r.testCase.Config.ThinkTimeSetting cfg := r.parsedConfig.ThinkTimeSetting
if cfg == nil { if cfg == nil {
cfg = &ThinkTimeConfig{thinkTimeDefault, nil, 0} cfg = &ThinkTimeConfig{thinkTimeDefault, nil, 0}
} }