Merge pull request #1244 from httprunner/refactor-session-runner

refactor: session runner
This commit is contained in:
debugtalk
2022-04-13 16:42:10 +08:00
committed by GitHub
5 changed files with 92 additions and 76 deletions

View File

@@ -17,11 +17,17 @@ func NewBoomer(spawnCount int, spawnRate float64) *HRPBoomer {
Boomer: boomer.NewStandaloneBoomer(spawnCount, spawnRate), Boomer: boomer.NewStandaloneBoomer(spawnCount, spawnRate),
pluginsMutex: new(sync.RWMutex), pluginsMutex: new(sync.RWMutex),
} }
b.hrpRunner = NewRunner(nil)
// set client transport for high concurrency load testing
b.hrpRunner.SetClientTransport(b.GetSpawnCount(), b.GetDisableKeepAlive(), b.GetDisableCompression())
return b return b
} }
type HRPBoomer struct { type HRPBoomer struct {
*boomer.Boomer *boomer.Boomer
hrpRunner *HRPRunner
plugins []funplugin.IPlugin // each task has its own plugin process plugins []funplugin.IPlugin // each task has its own plugin process
pluginsMutex *sync.RWMutex // avoid data race pluginsMutex *sync.RWMutex // avoid data race
} }
@@ -47,12 +53,6 @@ func (b *HRPBoomer) Run(testcases ...ITestCase) {
} }
for _, testcase := range testCases { for _, testcase := range testCases {
cfg := testcase.Config
err = initParameterIterator(cfg, "boomer")
if err != nil {
log.Error().Err(err).Msg("failed to init parameter iterator")
os.Exit(1)
}
rendezvousList := initRendezvous(testcase, int64(b.GetSpawnCount())) rendezvousList := initRendezvous(testcase, int64(b.GetSpawnCount()))
task := b.convertBoomerTask(testcase, rendezvousList) task := b.convertBoomerTask(testcase, rendezvousList)
taskSlice = append(taskSlice, task) taskSlice = append(taskSlice, task)
@@ -72,18 +72,18 @@ func (b *HRPBoomer) Quit() {
} }
func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rendezvous) *boomer.Task { func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rendezvous) *boomer.Task {
hrpRunner := NewRunner(nil) // init session runner for testcase
// set client transport for high concurrency load testing sessionRunner, err := b.hrpRunner.NewSessionRunner(testcase)
hrpRunner.SetClientTransport(b.GetSpawnCount(), b.GetDisableKeepAlive(), b.GetDisableCompression()) if err != nil {
config := testcase.Config log.Error().Err(err).Msg("failed to create session runner")
os.Exit(1)
// each testcase has its own plugin process }
plugin, _ := initPlugin(config.Path, false) if sessionRunner.parser.plugin != nil {
if plugin != nil {
b.pluginsMutex.Lock() b.pluginsMutex.Lock()
b.plugins = append(b.plugins, 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() {
@@ -94,14 +94,11 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
}() }()
return &boomer.Task{ return &boomer.Task{
Name: config.Name, Name: testcase.Config.Name,
Weight: config.Weight, Weight: testcase.Config.Weight,
Fn: func() { Fn: func() {
sessionRunner := hrpRunner.NewSessionRunner(testcase) testcaseSuccess := true // flag whole testcase result
sessionRunner.parser.plugin = plugin transactionSuccess := true // flag current transaction result
testcaseSuccess := true // flag whole testcase result
var transactionSuccess = true // flag current transaction result
var parameterVariables map[string]interface{} var parameterVariables map[string]interface{}
// iterate through all parameter iterators and update case variables // iterate through all parameter iterators and update case variables
@@ -110,11 +107,7 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
parameterVariables = it.Next() parameterVariables = it.Next()
} }
} }
sessionRunner.updateConfigVariables(parameterVariables)
if err := sessionRunner.parseConfig(parameterVariables); err != nil {
log.Error().Err(err).Msg("parse config failed")
return
}
startTime := time.Now() startTime := time.Now()
for _, step := range testcase.TestSteps { for _, step := range testcase.TestSteps {
@@ -131,7 +124,7 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
testcaseSuccess = false testcaseSuccess = false
transactionSuccess = false transactionSuccess = false
if hrpRunner.failfast { if b.hrpRunner.failfast {
log.Error().Msg("abort running due to failfast setting") log.Error().Msg("abort running due to failfast setting")
break break
} }

View File

@@ -10,6 +10,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/pkg/errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"golang.org/x/net/http2" "golang.org/x/net/http2"
@@ -151,23 +152,27 @@ func (r *HRPRunner) Run(testcases ...ITestCase) error {
// run testcase one by one // run testcase one by one
for _, testcase := range testCases { for _, testcase := range testCases {
cfg := testcase.Config sessionRunner, err := r.NewSessionRunner(testcase)
// parse config parameters
err := initParameterIterator(cfg, "runner")
if err != nil { if err != nil {
log.Error().Interface("parameters", cfg.Parameters).Err(err).Msg("parse config parameters failed") log.Error().Err(err).Msg("[Run] init session runner failed")
return err return err
} }
defer func() {
if sessionRunner.parser.plugin != nil {
sessionRunner.parser.plugin.Quit()
}
}()
// 在runner模式下指定整体策略cfg.ParametersSetting.Iterators仅包含一个CartesianProduct的迭代器 // 在runner模式下指定整体策略cfg.ParametersSetting.Iterators仅包含一个CartesianProduct的迭代器
for it := cfg.ParametersSetting.Iterators[0]; it.HasNext(); { for it := sessionRunner.parsedConfig.ParametersSetting.Iterators[0]; it.HasNext(); {
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 sessionRunner.parsedConfig.ParametersSetting.Iterators {
if it.HasNext() { if it.HasNext() {
cfg.Variables = mergeVariables(it.Next(), cfg.Variables) parameterVariables = it.Next()
} }
} }
sessionRunner := r.NewSessionRunner(testcase) if err = sessionRunner.Start(parameterVariables); 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
} }
@@ -200,13 +205,27 @@ func (r *HRPRunner) Run(testcases ...ITestCase) error {
return nil return nil
} }
func (r *HRPRunner) NewSessionRunner(testcase *TestCase) *SessionRunner { // NewSessionRunner creates a new session runner for testcase.
// each testcase has its own session runner
func (r *HRPRunner) NewSessionRunner(testcase *TestCase) (*SessionRunner, error) {
sessionRunner := &SessionRunner{ sessionRunner := &SessionRunner{
testCase: testcase, testCase: testcase,
hrpRunner: r, hrpRunner: r,
parser: newParser(), parser: newParser(),
summary: newSummary(), summary: newSummary(),
} }
sessionRunner.init()
return sessionRunner // init parser plugin
plugin, err := initPlugin(testcase.Config.Path, r.pluginLogOn)
if err != nil {
return nil, errors.Wrap(err, "init plugin failed")
}
sessionRunner.parser.plugin = plugin
// parse testcase config
if err := sessionRunner.parseConfig(); err != nil {
return nil, errors.Wrap(err, "parse testcase config failed")
}
return sessionRunner, nil
} }

View File

@@ -24,9 +24,8 @@ type SessionRunner struct {
summary *TestCaseSummary // record test case summary summary *TestCaseSummary // record test case summary
} }
func (r *SessionRunner) init() { func (r *SessionRunner) resetSession() {
log.Info().Msg("init session runner") log.Info().Msg("clear 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()
@@ -45,30 +44,17 @@ func (r *SessionRunner) LogOn() bool {
} }
// Start runs the test steps in sequential order. // Start runs the test steps in sequential order.
func (r *SessionRunner) Start() error { // givenVars is used for data driven
func (r *SessionRunner) Start(givenVars map[string]interface{}) 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")
// init session runner // update config variables with given variables
r.init() r.updateConfigVariables(givenVars)
// init plugin // reset session runner
var err error r.resetSession()
if r.parser.plugin, err = initPlugin(config.Path, r.hrpRunner.pluginLogOn); err != nil {
return err
}
defer func() {
if r.parser.plugin != nil {
r.parser.plugin.Quit()
}
}()
// parse config
if err := r.parseConfig(nil); err != nil {
return err
}
r.startTime = time.Now()
// run step in sequential order // run step in sequential order
for _, step := range r.testCase.TestSteps { for _, step := range r.testCase.TestSteps {
log.Info().Str("step", step.Name()). log.Info().Str("step", step.Name()).
@@ -119,10 +105,19 @@ func (r *SessionRunner) MergeStepVariables(vars map[string]interface{}) (map[str
return parsedVariables, nil return parsedVariables, nil
} }
// parseConfig parses testcase config with given variables, stores to parsedConfig. // updateConfigVariables updates config variables with given variables.
func (r *SessionRunner) parseConfig(variables map[string]interface{}) error { // this is used for data driven
func (r *SessionRunner) updateConfigVariables(givenVars map[string]interface{}) {
for k, v := range givenVars {
r.parsedConfig.Variables[k] = v
}
}
// parseConfig parses testcase config, stores to parsedConfig.
func (r *SessionRunner) parseConfig() error {
cfg := r.testCase.Config cfg := r.testCase.Config
r.parsedConfig = &TConfig{}
// deep copy config to avoid data racing // deep copy config to avoid data racing
if err := copier.Copy(r.parsedConfig, cfg); err != nil { if err := copier.Copy(r.parsedConfig, cfg); err != nil {
log.Error().Err(err).Msg("copy testcase config failed") log.Error().Err(err).Msg("copy testcase config failed")
@@ -130,8 +125,7 @@ func (r *SessionRunner) parseConfig(variables map[string]interface{}) error {
} }
// parse config variables // parse config variables
mergedVars := mergeVariables(variables, cfg.Variables) parsedVariables, err := r.parser.ParseVariables(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
@@ -139,22 +133,29 @@ func (r *SessionRunner) parseConfig(variables map[string]interface{}) error {
r.parsedConfig.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, parsedVariables)
if err != nil { if err != nil {
return err return errors.Wrap(err, "parse config name failed")
} }
r.parsedConfig.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, parsedVariables)
if err != nil { if err != nil {
return err return errors.Wrap(err, "parse config base url failed")
} }
r.parsedConfig.BaseURL = convertString(parsedBaseURL) r.parsedConfig.BaseURL = convertString(parsedBaseURL)
// ensure correction of think time config // ensure correction of think time config
r.parsedConfig.ThinkTimeSetting.checkThinkTime() r.parsedConfig.ThinkTimeSetting.checkThinkTime()
// parse testcase config parameters
err = initParameterIterator(r.parsedConfig, "runner")
if err != nil {
log.Error().Interface("parameters", r.parsedConfig.Parameters).Err(err).Msg("parse config parameters failed")
return errors.Wrap(err, "parse testcase config parameters failed")
}
return nil return nil
} }

View File

@@ -80,8 +80,8 @@ func TestRunRequestRun(t *testing.T) {
TestSteps: []IStep{stepGET, stepPOSTData}, TestSteps: []IStep{stepGET, stepPOSTData},
} }
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

@@ -69,15 +69,18 @@ func (s *StepTestCaseWithOptionalArgs) Run(r *SessionRunner) (*StepResult, error
if s.step.Name != "" { if s.step.Name != "" {
copiedTestCase.Config.Name = s.step.Name copiedTestCase.Config.Name = s.step.Name
} }
// merge & override variables
copiedTestCase.Config.Variables = mergeVariables(stepVariables, copiedTestCase.Config.Variables)
// merge & override extractors // merge & override extractors
copiedTestCase.Config.Export = mergeSlices(s.step.Export, copiedTestCase.Config.Export) copiedTestCase.Config.Export = mergeSlices(s.step.Export, copiedTestCase.Config.Export)
sessionRunner := r.hrpRunner.NewSessionRunner(copiedTestCase) sessionRunner, err := r.hrpRunner.NewSessionRunner(copiedTestCase)
if err != nil {
log.Error().Err(err).Msg("create session runner failed")
return stepResult, err
}
start := time.Now() start := time.Now()
err = sessionRunner.Start() // run referenced testcase with step variables
err = sessionRunner.Start(stepVariables)
stepResult.Elapsed = time.Since(start).Milliseconds() stepResult.Elapsed = time.Since(start).Milliseconds()
if err != nil { if err != nil {
stepResult.Attachment = err.Error() stepResult.Attachment = err.Error()