diff --git a/hrp/boomer.go b/hrp/boomer.go index 7bba9285..d639f981 100644 --- a/hrp/boomer.go +++ b/hrp/boomer.go @@ -72,15 +72,16 @@ func (b *HRPBoomer) Quit() { } func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rendezvous) *boomer.Task { - // init session runner for testcase - sessionRunner, err := b.hrpRunner.NewSessionRunner(testcase) + // init runner for testcase + // this runner is shared by multiple session runners + caseRunner, err := b.hrpRunner.newCaseRunner(testcase) if err != nil { - log.Error().Err(err).Msg("failed to create session runner") + log.Error().Err(err).Msg("failed to create runner") os.Exit(1) } - if sessionRunner.parser.plugin != nil { + if caseRunner.parser.plugin != nil { b.pluginsMutex.Lock() - b.plugins = append(b.plugins, sessionRunner.parser.plugin) + b.plugins = append(b.plugins, caseRunner.parser.plugin) b.pluginsMutex.Unlock() } @@ -93,7 +94,7 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend }() // set paramters mode for load testing - parametersIterator := sessionRunner.parametersIterator + parametersIterator := caseRunner.parametersIterator parametersIterator.SetUnlimitedMode() return &boomer.Task{ @@ -103,11 +104,13 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend testcaseSuccess := true // flag whole testcase result transactionSuccess := true // flag current transaction result + // init session runner + sessionRunner := caseRunner.newSession() + if parametersIterator.HasNext() { sessionRunner.updateConfigVariables(parametersIterator.Next()) } - sessionRunner.resetSession() startTime := time.Now() for _, step := range testcase.TestSteps { stepResult, err := step.Run(sessionRunner) diff --git a/hrp/parameters_test.go b/hrp/parameters_test.go index e4bac50a..0276be91 100644 --- a/hrp/parameters_test.go +++ b/hrp/parameters_test.go @@ -353,7 +353,7 @@ func TestInitParametersIteratorContent(t *testing.T) { ParametersSetting: nil, }, 0, - map[string]interface{}(nil), + map[string]interface{}{}, }, } for _, data := range testData { diff --git a/hrp/runner.go b/hrp/runner.go index 76da1e75..acdda315 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/jinzhu/copier" "github.com/pkg/errors" "github.com/rs/zerolog/log" "golang.org/x/net/http2" @@ -208,11 +209,23 @@ func (r *HRPRunner) Run(testcases ...ITestCase) error { // NewSessionRunner creates a new session runner for testcase. // each testcase has its own session runner func (r *HRPRunner) NewSessionRunner(testcase *TestCase) (*SessionRunner, error) { + runner, err := r.newCaseRunner(testcase) + if err != nil { + return nil, err + } + sessionRunner := &SessionRunner{ + testCaseRunner: runner, + } + sessionRunner.resetSession() + return sessionRunner, nil +} + +func (r *HRPRunner) newCaseRunner(testcase *TestCase) (*testCaseRunner, error) { + runner := &testCaseRunner{ testCase: testcase, hrpRunner: r, parser: newParser(), - summary: newSummary(), } // init parser plugin @@ -220,12 +233,80 @@ func (r *HRPRunner) NewSessionRunner(testcase *TestCase) (*SessionRunner, error) if err != nil { return nil, errors.Wrap(err, "init plugin failed") } - sessionRunner.parser.plugin = plugin + runner.parser.plugin = plugin // parse testcase config - if err := sessionRunner.parseConfig(); err != nil { + if err := runner.parseConfig(); err != nil { return nil, errors.Wrap(err, "parse testcase config failed") } - return sessionRunner, nil + return runner, nil +} + +type testCaseRunner struct { + testCase *TestCase + hrpRunner *HRPRunner + parser *Parser + parsedConfig *TConfig + parametersIterator *ParametersIterator +} + +// parseConfig parses testcase config, stores to parsedConfig. +func (r *testCaseRunner) parseConfig() error { + cfg := r.testCase.Config + + r.parsedConfig = &TConfig{} + // 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 + parsedVariables, err := r.parser.ParseVariables(cfg.Variables) + if err != nil { + log.Error().Interface("variables", cfg.Variables).Err(err).Msg("parse config variables failed") + return err + } + r.parsedConfig.Variables = parsedVariables + + // parse config name + parsedName, err := r.parser.ParseString(cfg.Name, parsedVariables) + if err != nil { + return errors.Wrap(err, "parse config name failed") + } + r.parsedConfig.Name = convertString(parsedName) + + // parse config base url + parsedBaseURL, err := r.parser.ParseString(cfg.BaseURL, parsedVariables) + if err != nil { + return errors.Wrap(err, "parse config base url failed") + } + r.parsedConfig.BaseURL = convertString(parsedBaseURL) + + // ensure correction of think time config + r.parsedConfig.ThinkTimeSetting.checkThinkTime() + + // parse testcase config parameters + parametersIterator, err := initParametersIterator(r.parsedConfig) + if err != nil { + 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") + } + r.parametersIterator = parametersIterator + + return nil +} + +// each boomer task initiates a new session +// in order to avoid data racing +func (r *testCaseRunner) newSession() *SessionRunner { + sessionRunner := &SessionRunner{ + testCaseRunner: r, + } + sessionRunner.resetSession() + return sessionRunner } diff --git a/hrp/session.go b/hrp/session.go index 05621d09..4d120cc5 100644 --- a/hrp/session.go +++ b/hrp/session.go @@ -4,7 +4,6 @@ import ( _ "embed" "time" - "github.com/jinzhu/copier" "github.com/pkg/errors" "github.com/rs/zerolog/log" ) @@ -12,12 +11,8 @@ import ( // SessionRunner is used to run testcase and its steps. // each testcase has its own SessionRunner instance and share session variables. type SessionRunner struct { - testCase *TestCase - hrpRunner *HRPRunner - parser *Parser - parsedConfig *TConfig - parametersIterator *ParametersIterator - sessionVariables map[string]interface{} + *testCaseRunner + sessionVariables map[string]interface{} // transactions stores transaction timing info. // 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 @@ -30,6 +25,7 @@ func (r *SessionRunner) resetSession() { r.sessionVariables = make(map[string]interface{}) r.transactions = make(map[string]map[transactionType]time.Time) r.startTime = time.Now() + r.summary = newSummary() } func (r *SessionRunner) GetParser() *Parser { @@ -122,56 +118,6 @@ func (r *SessionRunner) updateConfigVariables(parameters map[string]interface{}) } } -// parseConfig parses testcase config, stores to parsedConfig. -func (r *SessionRunner) parseConfig() error { - cfg := r.testCase.Config - - r.parsedConfig = &TConfig{} - // 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 - parsedVariables, err := r.parser.ParseVariables(cfg.Variables) - if err != nil { - log.Error().Interface("variables", cfg.Variables).Err(err).Msg("parse config variables failed") - return err - } - r.parsedConfig.Variables = parsedVariables - - // parse config name - parsedName, err := r.parser.ParseString(cfg.Name, parsedVariables) - if err != nil { - return errors.Wrap(err, "parse config name failed") - } - r.parsedConfig.Name = convertString(parsedName) - - // parse config base url - parsedBaseURL, err := r.parser.ParseString(cfg.BaseURL, parsedVariables) - if err != nil { - return errors.Wrap(err, "parse config base url failed") - } - r.parsedConfig.BaseURL = convertString(parsedBaseURL) - - // ensure correction of think time config - r.parsedConfig.ThinkTimeSetting.checkThinkTime() - - // parse testcase config parameters - parametersIterator, err := initParametersIterator(r.parsedConfig) - if err != nil { - 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") - } - r.parametersIterator = parametersIterator - - return nil -} - func (r *SessionRunner) GetSummary() *TestCaseSummary { caseSummary := r.summary caseSummary.Name = r.parsedConfig.Name