mirror of
https://github.com/httprunner/httprunner.git
synced 2026-07-09 06:23:52 +08:00
fix: data race by summary
This commit is contained in:
@@ -72,15 +72,16 @@ func (b *HRPBoomer) Quit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rendezvous) *boomer.Task {
|
func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rendezvous) *boomer.Task {
|
||||||
// init session runner for testcase
|
// init runner for testcase
|
||||||
sessionRunner, err := b.hrpRunner.NewSessionRunner(testcase)
|
// this runner is shared by multiple session runners
|
||||||
|
caseRunner, err := b.hrpRunner.newCaseRunner(testcase)
|
||||||
if err != nil {
|
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)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if sessionRunner.parser.plugin != nil {
|
if caseRunner.parser.plugin != nil {
|
||||||
b.pluginsMutex.Lock()
|
b.pluginsMutex.Lock()
|
||||||
b.plugins = append(b.plugins, sessionRunner.parser.plugin)
|
b.plugins = append(b.plugins, caseRunner.parser.plugin)
|
||||||
b.pluginsMutex.Unlock()
|
b.pluginsMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +94,7 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// set paramters mode for load testing
|
// set paramters mode for load testing
|
||||||
parametersIterator := sessionRunner.parametersIterator
|
parametersIterator := caseRunner.parametersIterator
|
||||||
parametersIterator.SetUnlimitedMode()
|
parametersIterator.SetUnlimitedMode()
|
||||||
|
|
||||||
return &boomer.Task{
|
return &boomer.Task{
|
||||||
@@ -103,11 +104,13 @@ 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
|
||||||
|
|
||||||
|
// init session runner
|
||||||
|
sessionRunner := caseRunner.newSession()
|
||||||
|
|
||||||
if parametersIterator.HasNext() {
|
if parametersIterator.HasNext() {
|
||||||
sessionRunner.updateConfigVariables(parametersIterator.Next())
|
sessionRunner.updateConfigVariables(parametersIterator.Next())
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -353,7 +353,7 @@ func TestInitParametersIteratorContent(t *testing.T) {
|
|||||||
ParametersSetting: nil,
|
ParametersSetting: nil,
|
||||||
},
|
},
|
||||||
0,
|
0,
|
||||||
map[string]interface{}(nil),
|
map[string]interface{}{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, data := range testData {
|
for _, data := range testData {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
@@ -208,11 +209,23 @@ func (r *HRPRunner) Run(testcases ...ITestCase) error {
|
|||||||
// NewSessionRunner creates a new session runner for testcase.
|
// NewSessionRunner creates a new session runner for testcase.
|
||||||
// each testcase has its own session runner
|
// each testcase has its own session runner
|
||||||
func (r *HRPRunner) NewSessionRunner(testcase *TestCase) (*SessionRunner, error) {
|
func (r *HRPRunner) NewSessionRunner(testcase *TestCase) (*SessionRunner, error) {
|
||||||
|
runner, err := r.newCaseRunner(testcase)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
sessionRunner := &SessionRunner{
|
sessionRunner := &SessionRunner{
|
||||||
|
testCaseRunner: runner,
|
||||||
|
}
|
||||||
|
sessionRunner.resetSession()
|
||||||
|
return sessionRunner, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HRPRunner) newCaseRunner(testcase *TestCase) (*testCaseRunner, error) {
|
||||||
|
runner := &testCaseRunner{
|
||||||
testCase: testcase,
|
testCase: testcase,
|
||||||
hrpRunner: r,
|
hrpRunner: r,
|
||||||
parser: newParser(),
|
parser: newParser(),
|
||||||
summary: newSummary(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// init parser plugin
|
// init parser plugin
|
||||||
@@ -220,12 +233,80 @@ func (r *HRPRunner) NewSessionRunner(testcase *TestCase) (*SessionRunner, error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "init plugin failed")
|
return nil, errors.Wrap(err, "init plugin failed")
|
||||||
}
|
}
|
||||||
sessionRunner.parser.plugin = plugin
|
runner.parser.plugin = plugin
|
||||||
|
|
||||||
// parse testcase config
|
// 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 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
@@ -12,12 +11,8 @@ 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
|
*testCaseRunner
|
||||||
hrpRunner *HRPRunner
|
sessionVariables map[string]interface{}
|
||||||
parser *Parser
|
|
||||||
parsedConfig *TConfig
|
|
||||||
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
|
||||||
@@ -30,6 +25,7 @@ func (r *SessionRunner) resetSession() {
|
|||||||
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 = newSummary()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SessionRunner) GetParser() *Parser {
|
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 {
|
func (r *SessionRunner) GetSummary() *TestCaseSummary {
|
||||||
caseSummary := r.summary
|
caseSummary := r.summary
|
||||||
caseSummary.Name = r.parsedConfig.Name
|
caseSummary.Name = r.parsedConfig.Name
|
||||||
|
|||||||
Reference in New Issue
Block a user