Files
httprunner/boomer.go
buyuxiang 5619556698 del unused once field
Change-Id: I8086a4bc20baffaf94c4845d3d5e62ce7120fe33
2022-02-17 15:09:41 +08:00

181 lines
4.9 KiB
Go

package hrp
import (
"sync"
"time"
"github.com/jinzhu/copier"
"github.com/rs/zerolog/log"
"github.com/httprunner/hrp/internal/boomer"
"github.com/httprunner/hrp/internal/ga"
"github.com/httprunner/hrp/plugin/common"
)
func NewBoomer(spawnCount int, spawnRate float64) *HRPBoomer {
b := &HRPBoomer{
Boomer: boomer.NewStandaloneBoomer(spawnCount, spawnRate),
pluginsMutex: new(sync.RWMutex),
debug: false,
}
return b
}
type HRPBoomer struct {
*boomer.Boomer
plugins []common.Plugin // each task has its own plugin process
pluginsMutex *sync.RWMutex // avoid data race
debug bool
}
// SetDebug configures whether to log HTTP request and response content.
func (b *HRPBoomer) SetDebug(debug bool) *HRPBoomer {
b.debug = debug
return b
}
// Run starts to run load test for one or multiple testcases.
func (b *HRPBoomer) Run(testcases ...ITestCase) {
event := ga.EventTracking{
Category: "RunLoadTests",
Action: "hrp boom",
}
// report start event
go ga.SendEvent(event)
// report execution timing event
defer ga.SendEvent(event.StartTiming("execution"))
var taskSlice []*boomer.Task
for _, iTestCase := range testcases {
testcase, err := iTestCase.ToTestCase()
if err != nil {
panic(err)
}
cfg := testcase.Config
err = initParameterIterator(cfg, "boomer")
if err != nil {
panic(err)
}
rendezvousList := initRendezvous(testcase, int64(b.GetSpawnCount()))
task := b.convertBoomerTask(testcase, rendezvousList)
taskSlice = append(taskSlice, task)
waitRendezvous(rendezvousList)
}
b.Boomer.Run(taskSlice...)
}
func (b *HRPBoomer) Quit() {
b.pluginsMutex.Lock()
plugins := b.plugins
b.pluginsMutex.Unlock()
for _, plugin := range plugins {
plugin.Quit()
}
b.Boomer.Quit()
}
func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rendezvous) *boomer.Task {
hrpRunner := NewRunner(nil).SetDebug(b.debug)
config := testcase.Config
// each testcase has its own plugin process
plugin, _ := initPlugin(config.Path)
if plugin != nil {
b.pluginsMutex.Lock()
b.plugins = append(b.plugins, plugin)
b.pluginsMutex.Unlock()
}
// broadcast to all rendezvous at once when spawn done
go func() {
<-b.GetSpawnDoneChan()
for _, rendezvous := range rendezvousList {
rendezvous.setSpawnDone()
}
}()
return &boomer.Task{
Name: config.Name,
Weight: config.Weight,
Fn: func() {
runner := hrpRunner.newCaseRunner(testcase)
runner.parser.plugin = plugin
testcaseSuccess := true // flag whole testcase result
var transactionSuccess = true // flag current transaction result
cfg := testcase.Config
caseConfig := &TConfig{}
// copy config to avoid data racing
if err := copier.Copy(caseConfig, cfg); err != nil {
log.Error().Err(err).Msg("copy config data failed")
return
}
// iterate through all parameter iterators and update case variables
for _, it := range caseConfig.ParametersSetting.Iterators {
if it.HasNext() {
caseConfig.Variables = mergeVariables(it.Next(), caseConfig.Variables)
}
}
if err := runner.parseConfig(caseConfig); err != nil {
log.Error().Err(err).Msg("parse config failed")
return
}
startTime := time.Now()
for index, step := range testcase.TestSteps {
stepData, err := runner.runStep(index, caseConfig)
if err != nil {
// step failed
var elapsed int64
if stepData != nil {
elapsed = stepData.Elapsed
}
b.RecordFailure(step.Type(), step.Name(), elapsed, err.Error())
// update flag
testcaseSuccess = false
transactionSuccess = false
if runner.hrpRunner.failfast {
log.Error().Msg("abort running due to failfast setting")
break
}
log.Warn().Err(err).Msg("run step failed, continue next step")
continue
}
// step success
if stepData.StepType == stepTypeTransaction {
// transaction
// FIXME: support nested transactions
if step.ToStruct().Transaction.Type == transactionEnd { // only record when transaction ends
b.RecordTransaction(stepData.Name, transactionSuccess, stepData.Elapsed, 0)
transactionSuccess = true // reset flag for next transaction
}
} else if stepData.StepType == stepTypeRendezvous {
// rendezvous
// TODO: implement rendezvous in boomer
} else {
// request or testcase step
b.RecordSuccess(step.Type(), step.Name(), stepData.Elapsed, stepData.ContentSize)
}
}
endTime := time.Now()
// report duration for transaction without end
for name, transaction := range runner.transactions {
if len(transaction) == 1 {
// if transaction end time not exists, use testcase end time instead
duration := endTime.Sub(transaction[transactionStart])
b.RecordTransaction(name, transactionSuccess, duration.Milliseconds(), 0)
}
}
// report testcase as a whole Action transaction, inspired by LoadRunner
b.RecordTransaction("Action", testcaseSuccess, endTime.Sub(startTime).Milliseconds(), 0)
},
}
}