refactor: move boomer to hrp/pkg/boomer

This commit is contained in:
lilong.129
2025-02-05 20:38:46 +08:00
parent b731669da2
commit d36f0c9aa0
26 changed files with 189 additions and 183 deletions

View File

@@ -88,7 +88,6 @@ Usage:
Available Commands:
adb simple utils for android device management
boom run load test with boomer
build build plugin for testing
completion Generate the autocompletion script for the specified shell
convert convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases
@@ -96,6 +95,7 @@ Available Commands:
ios simple utils for ios device management
pytest run API test with pytest
run run API test with go engine
server start hrp server
startproject create a scaffold project
wiki visit https://httprunner.com

View File

@@ -10,8 +10,6 @@
`HttpRunner` 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型。简单易用,功能强大,具有丰富的插件化机制和高度的可扩展能力。
> HttpRunner [用户调研问卷][survey] 持续收集中,我们将基于用户反馈动态调整产品特性和需求优先级。
![flow chart](https://httprunner.com/image/hrp-flow.jpg)
[版本发布日志] | [English]
@@ -81,7 +79,6 @@ Usage:
Available Commands:
adb simple utils for android device management
boom run load test with boomer
build build plugin for testing
completion Generate the autocompletion script for the specified shell
convert convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases
@@ -89,6 +86,7 @@ Available Commands:
ios simple utils for ios device management
pytest run API test with pytest
run run API test with go engine
server start hrp server
startproject create a scaffold project
wiki visit https://httprunner.com
@@ -109,13 +107,6 @@ Use "hrp [command] --help" for more information about a command.
<a href="https://httprunner.com/docs/cases/umcare"><img src="https://httprunner.com/image/logo/umcare.png" title="通用环球医疗 - 使用 HttpRunner 实践接口自动化测试" width="100"></a>
<a href="https://httprunner.com/docs/cases/mihoyo"><img src="https://httprunner.com/image/logo/miHoYo.png" title="米哈游 - 基于 HttpRunner 搭建接口自动化测试体系" width="100"></a>
## 赞助商
[<img src="https://testing-studio.com/img/icon.png" alt="霍格沃兹测试开发学社" width="500">](https://qrcode.testing-studio.com/f?from=HttpRunner&url=https://testing-studio.com/)
> 霍格沃兹测试开发学社是中国软件测试开发高端教育品牌,产品由国内顶尖软件测试开发技术专家携手打造,为企业与个人提供专业的技能培训与咨询、测试工具与测试平台、测试外包与测试众包服务。领域涵盖 App/Web 自动化测试、接口自动化测试、性能测试、安全测试、持续交付/DevOps、测试左移、测试右移、精准测试、测试平台开发、测试管理等方向。-> [**联系我们**](http://qrcode.testing-studio.com/f?from=HttpRunner&url=https://ceshiren.com/t/topic/23745)
## Subscribe
关注 HttpRunner 的微信公众号,第一时间获得最新资讯。

View File

@@ -1 +1 @@
v5.0.0+2501071515
v5.0.0+2502052038

View File

@@ -180,7 +180,7 @@ func (iter *ParametersIterator) Next() map[string]interface{} {
return selectedParameters
}
func (iter *ParametersIterator) outParameters() map[string]interface{} {
func (iter *ParametersIterator) Data() map[string]interface{} {
res := map[string]interface{}{}
for key, params := range iter.data {
res[key] = params

View File

@@ -25,7 +25,7 @@ func newParser() *Parser {
}
type Parser struct {
plugin funplugin.IPlugin // plugin is used to call functions
Plugin funplugin.IPlugin // plugin is used to call functions
}
func buildURL(baseURL, stepURL string, queryParams url.Values) (fullUrl *url.URL) {
@@ -279,13 +279,13 @@ func (p *Parser) ParseString(raw string, variablesMapping map[string]interface{}
// only support return at most one result value
func (p *Parser) callFunc(funcName string, arguments ...interface{}) (interface{}, error) {
// call with plugin function
if p.plugin != nil {
if p.plugin.Has(funcName) {
return p.plugin.Call(funcName, arguments...)
if p.Plugin != nil {
if p.Plugin.Has(funcName) {
return p.Plugin.Call(funcName, arguments...)
}
commonName := fungo.ConvertCommonName(funcName)
if p.plugin.Has(commonName) {
return p.plugin.Call(commonName, arguments...)
if p.Plugin.Has(commonName) {
return p.Plugin.Call(commonName, arguments...)
}
}

View File

@@ -1,4 +1,4 @@
package hrp
package hrpboomer
import (
"fmt"
@@ -12,6 +12,7 @@ import (
"github.com/rs/zerolog/log"
"golang.org/x/net/context"
"github.com/httprunner/httprunner/v4/hrp"
"github.com/httprunner/httprunner/v4/hrp/code"
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
"github.com/httprunner/httprunner/v4/hrp/internal/json"
@@ -19,13 +20,15 @@ import (
"github.com/httprunner/httprunner/v4/hrp/pkg/boomer"
)
var pluginMap sync.Map // used for reusing plugin instance
func NewStandaloneBoomer(spawnCount int64, spawnRate float64) *HRPBoomer {
b := &HRPBoomer{
Boomer: boomer.NewStandaloneBoomer(spawnCount, spawnRate),
pluginsMutex: new(sync.RWMutex),
}
b.hrpRunner = NewRunner(nil)
b.hrpRunner = hrp.NewRunner(nil)
return b
}
@@ -34,7 +37,7 @@ func NewMasterBoomer(masterBindHost string, masterBindPort int) *HRPBoomer {
Boomer: boomer.NewMasterBoomer(masterBindHost, masterBindPort),
pluginsMutex: new(sync.RWMutex),
}
b.hrpRunner = NewRunner(nil)
b.hrpRunner = hrp.NewRunner(nil)
return b
}
@@ -44,7 +47,7 @@ func NewWorkerBoomer(masterHost string, masterPort int) *HRPBoomer {
pluginsMutex: new(sync.RWMutex),
}
b.hrpRunner = NewRunner(nil)
b.hrpRunner = hrp.NewRunner(nil)
// set client transport for high concurrency load testing
b.hrpRunner.SetClientTransport(b.GetSpawnCount(), b.GetDisableKeepAlive(), b.GetDisableCompression())
return b
@@ -52,7 +55,7 @@ func NewWorkerBoomer(masterHost string, masterPort int) *HRPBoomer {
type HRPBoomer struct {
*boomer.Boomer
hrpRunner *HRPRunner
hrpRunner *hrp.HRPRunner
plugins []funplugin.IPlugin // each task has its own plugin process
pluginsMutex *sync.RWMutex // avoid data race
}
@@ -91,7 +94,7 @@ func (b *HRPBoomer) SetPython3Venv(venv string) *HRPBoomer {
}
// Run starts to run load test for one or multiple testcases.
func (b *HRPBoomer) Run(testcases ...ITestCase) {
func (b *HRPBoomer) Run(testcases ...hrp.ITestCase) {
startTime := time.Now()
defer func() {
// report boom event
@@ -113,25 +116,25 @@ func (b *HRPBoomer) Run(testcases ...ITestCase) {
b.Boomer.Run(taskSlice...)
}
func (b *HRPBoomer) ConvertTestCasesToBoomerTasks(testcases ...ITestCase) (taskSlice []*boomer.Task) {
func (b *HRPBoomer) ConvertTestCasesToBoomerTasks(testcases ...hrp.ITestCase) (taskSlice []*boomer.Task) {
// load all testcases
testCases, err := LoadTestCases(testcases...)
testCases, err := hrp.LoadTestCases(testcases...)
if err != nil {
log.Error().Err(err).Msg("failed to load testcases")
os.Exit(code.GetErrorCode(err))
}
for _, testcase := range testCases {
rendezvousList := initRendezvous(testcase, int64(b.GetSpawnCount()))
rendezvousList := hrp.InitRendezvous(testcase, int64(b.GetSpawnCount()))
task := b.convertBoomerTask(testcase, rendezvousList)
taskSlice = append(taskSlice, task)
waitRendezvous(rendezvousList, b)
hrp.WaitRendezvous(rendezvousList, b)
}
return taskSlice
}
func (b *HRPBoomer) ParseTestCases(testCases []*TestCase) []*TestCase {
var parsedTestCases []*TestCase
func (b *HRPBoomer) ParseTestCases(testCases []*hrp.TestCase) []*hrp.TestCase {
var parsedTestCases []*hrp.TestCase
for _, tc := range testCases {
caseRunner, err := b.hrpRunner.NewCaseRunner(*tc)
if err != nil {
@@ -139,8 +142,8 @@ func (b *HRPBoomer) ParseTestCases(testCases []*TestCase) []*TestCase {
os.Exit(code.GetErrorCode(err))
}
caseConfig := caseRunner.TestCase.Config.Get()
caseConfig.Parameters = caseRunner.parametersIterator.outParameters()
parsedTestCases = append(parsedTestCases, &TestCase{
caseConfig.Parameters = caseRunner.GetParametersIterator().Data()
parsedTestCases = append(parsedTestCases, &hrp.TestCase{
Config: caseConfig,
TestSteps: caseRunner.TestSteps,
})
@@ -148,9 +151,9 @@ func (b *HRPBoomer) ParseTestCases(testCases []*TestCase) []*TestCase {
return parsedTestCases
}
func (b *HRPBoomer) TestCasesToBytes(testcases ...ITestCase) []byte {
func (b *HRPBoomer) TestCasesToBytes(testcases ...hrp.ITestCase) []byte {
// load all testcases
testCases, err := LoadTestCases(testcases...)
testCases, err := hrp.LoadTestCases(testcases...)
if err != nil {
log.Error().Err(err).Msg("failed to load testcases")
os.Exit(code.GetErrorCode(err))
@@ -163,8 +166,8 @@ func (b *HRPBoomer) TestCasesToBytes(testcases ...ITestCase) []byte {
return testCasesBytes
}
func (b *HRPBoomer) BytesToTCases(testCasesBytes []byte) []*TestCase {
var testcase []*TestCase
func (b *HRPBoomer) BytesToTCases(testCasesBytes []byte) []*hrp.TestCase {
var testcase []*hrp.TestCase
err := json.Unmarshal(testCasesBytes, &testcase)
if err != nil {
log.Error().Err(err).Msg("failed to unmarshal testcases")
@@ -176,7 +179,7 @@ func (b *HRPBoomer) Quit() {
b.Boomer.Quit()
}
func (b *HRPBoomer) parseTCases(testCases []*TestCase) (testcases []ITestCase) {
func (b *HRPBoomer) parseTCases(testCases []*hrp.TestCase) (testcases []hrp.ITestCase) {
for _, tc := range testCases {
// create temp dir to save testcase
tempDir, err := os.MkdirTemp("", "hrp_testcases")
@@ -289,9 +292,9 @@ func (b *HRPBoomer) PollTestCases(ctx context.Context) {
for {
select {
case <-b.Boomer.ParseTestCasesChan():
var tcs []ITestCase
var tcs []hrp.ITestCase
for _, tc := range b.GetTestCasesPath() {
tcp := TestCasePath(tc)
tcp := hrp.TestCasePath(tc)
tcs = append(tcs, &tcp)
}
b.TestCaseBytesChan() <- b.TestCasesToBytes(tcs...)
@@ -304,7 +307,7 @@ func (b *HRPBoomer) PollTestCases(ctx context.Context) {
}
}
func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rendezvous) *boomer.Task {
func (b *HRPBoomer) convertBoomerTask(testcase *hrp.TestCase, rendezvousList []*hrp.Rendezvous) *boomer.Task {
// init case runner for testcase
// this runner is shared by multiple session runners
caseRunner, err := b.hrpRunner.NewCaseRunner(*testcase)
@@ -312,9 +315,10 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
log.Error().Err(err).Msg("failed to create runner")
os.Exit(code.GetErrorCode(err))
}
if caseRunner.parser.plugin != nil {
plugin := caseRunner.GetParser().Plugin
if plugin != nil {
b.pluginsMutex.Lock()
b.plugins = append(b.plugins, caseRunner.parser.plugin)
b.plugins = append(b.plugins, plugin)
b.pluginsMutex.Unlock()
}
@@ -322,12 +326,12 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
go func() {
<-b.GetSpawnDoneChan()
for _, rendezvous := range rendezvousList {
rendezvous.setSpawnDone()
rendezvous.SetSpawnDone()
}
}()
// set paramters mode for load testing
parametersIterator := caseRunner.parametersIterator
parametersIterator := caseRunner.GetParametersIterator()
parametersIterator.SetUnlimitedMode()
// reset start time only once
@@ -348,18 +352,18 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
mutex.Lock()
if parametersIterator.HasNext() {
sessionRunner.initWithParameters(parametersIterator.Next())
sessionRunner.InitWithParameters(parametersIterator.Next())
}
mutex.Unlock()
defer func() {
sessionRunner.releaseResources()
sessionRunner.ReleaseResources()
}()
startTime := time.Now()
for _, step := range testcase.TestSteps {
// parse step struct
err = sessionRunner.parseStepStruct(step)
err = sessionRunner.ParseStep(step)
if err != nil {
log.Error().Err(err).Msg("parse step struct failed")
}
@@ -372,9 +376,9 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
// update step result name with parsed step name
stepResult.Name = step.Name()
// record requests result of the step if step type is testcase
if stepResult.StepType == stepTypeTestCase && stepResult.Data != nil {
if stepResult.StepType == hrp.StepTypeTestCase && stepResult.Data != nil {
// record requests of testcase step
for _, result := range stepResult.Data.([]*StepResult) {
for _, result := range stepResult.Data.([]*hrp.StepResult) {
if result.Success {
b.RecordSuccess(string(result.StepType), result.Name, result.Elapsed, result.ContentSize)
} else {
@@ -396,26 +400,22 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
testcaseSuccess = false
transactionSuccess = false
if b.hrpRunner.failfast {
log.Error().Err(err).Msg("abort running due to failfast setting")
break
}
log.Warn().Err(err).Msg("run step failed, continue next step")
continue
}
// record step success
if stepResult.StepType == stepTypeTransaction {
if stepResult.StepType == hrp.StepTypeTransaction {
// transaction
// FIXME: support nested transactions
stepTransaction := step.(*StepTransaction)
if stepTransaction.Transaction.Type == transactionEnd { // only record when transaction ends
stepTransaction := step.(*hrp.StepTransaction)
if stepTransaction.Transaction.Type == hrp.TransactionEnd { // only record when transaction ends
b.RecordTransaction(stepTransaction.Name(), transactionSuccess, stepResult.Elapsed, 0)
transactionSuccess = true // reset flag for next transaction
}
} else if stepResult.StepType == stepTypeRendezvous {
} else if stepResult.StepType == hrp.StepTypeRendezvous {
// rendezvous
} else if stepResult.StepType == stepTypeThinkTime {
} else if stepResult.StepType == hrp.StepTypeThinkTime {
// think time
// no record required
} else {
@@ -423,17 +423,17 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
b.RecordSuccess(string(step.Type()), stepResult.Name, stepResult.Elapsed, stepResult.ContentSize)
// update extracted variables
for k, v := range stepResult.ExportVars {
sessionRunner.sessionVariables[k] = v
sessionRunner.GetSessionVariables()[k] = v
}
}
}
endTime := time.Now()
// report duration for transaction without end
for name, transaction := range sessionRunner.transactions {
for name, transaction := range sessionRunner.GetTransactions() {
if len(transaction) == 1 {
// if transaction end time not exists, use testcase end time instead
duration := endTime.Sub(transaction[transactionStart])
duration := endTime.Sub(transaction[hrp.TransactionStart])
b.RecordTransaction(name, transactionSuccess, duration.Milliseconds(), 0)
}
}

View File

@@ -1,4 +1,4 @@
package hrp
package hrpboomer
import (
"context"

View File

@@ -1,34 +1,32 @@
package hrp
package hrpboomer
import (
"testing"
"time"
"github.com/httprunner/httprunner/v4/hrp"
)
func TestBoomerStandaloneRun(t *testing.T) {
buildHashicorpGoPlugin()
defer removeHashicorpGoPlugin()
testcase1 := &TestCase{
Config: NewConfig("TestCase1").SetBaseURL("https://postman-echo.com"),
TestSteps: []IStep{
NewStep("headers").
testcase1 := &hrp.TestCase{
Config: hrp.NewConfig("TestCase1").SetBaseURL("https://postman-echo.com"),
TestSteps: []hrp.IStep{
hrp.NewStep("headers").
GET("/headers").
Validate().
AssertEqual("status_code", 200, "check status code").
AssertEqual("headers.\"Content-Type\"", "application/json", "check http response Content-Type"),
NewStep("user-agent").
hrp.NewStep("user-agent").
GET("/user-agent").
Validate().
AssertEqual("status_code", 200, "check status code").
AssertEqual("headers.\"Content-Type\"", "application/json", "check http response Content-Type"),
NewStep("TestCase3").CallRefCase(&TestCase{Config: NewConfig("TestCase3")}),
hrp.NewStep("TestCase3").CallRefCase(&hrp.TestCase{Config: hrp.NewConfig("TestCase3")}),
},
}
testcase2 := TestCasePath(demoTestCaseWithPluginJSONPath)
b := NewStandaloneBoomer(2, 1)
go b.Run(testcase1, &testcase2)
go b.Run(testcase1)
time.Sleep(5 * time.Second)
b.Quit()
}

View File

@@ -1,4 +1,4 @@
package cmd
package main
import (
"strings"
@@ -12,6 +12,7 @@ import (
"github.com/httprunner/httprunner/v4/hrp"
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
"github.com/httprunner/httprunner/v4/hrp/pkg/boomer"
hrpboomer "github.com/httprunner/httprunner/v4/hrp/pkg/boomer/hrp"
)
// boomCmd represents the boom command
@@ -25,11 +26,9 @@ var boomCmd = &cobra.Command{
Args: cobra.MinimumNArgs(0),
PreRun: func(cmd *cobra.Command, args []string) {
boomer.SetUlimit(10240) // ulimit -n 10240
if !strings.EqualFold(logLevel, "DEBUG") {
// disable info logs for load testing
log.Info().Msg("Set global log level to WARN for load testing")
zerolog.SetGlobalLevel(zerolog.WarnLevel)
}
// disable info logs for load testing
log.Info().Msg("Set global log level to WARN for load testing")
zerolog.SetGlobalLevel(zerolog.WarnLevel)
},
RunE: func(cmd *cobra.Command, args []string) (err error) {
startTime := time.Now()
@@ -57,13 +56,13 @@ var boomCmd = &cobra.Command{
}
// init boomer
var hrpBoomer *hrp.HRPBoomer
var hrpBoomer *hrpboomer.HRPBoomer
if boomArgs.master {
hrpBoomer = hrp.NewMasterBoomer(boomArgs.masterBindHost, boomArgs.masterBindPort)
hrpBoomer = hrpboomer.NewMasterBoomer(boomArgs.masterBindHost, boomArgs.masterBindPort)
} else if boomArgs.worker {
hrpBoomer = hrp.NewWorkerBoomer(boomArgs.masterHost, boomArgs.masterPort)
hrpBoomer = hrpboomer.NewWorkerBoomer(boomArgs.masterHost, boomArgs.masterPort)
} else {
hrpBoomer = hrp.NewStandaloneBoomer(boomArgs.SpawnCount, boomArgs.SpawnRate)
hrpBoomer = hrpboomer.NewStandaloneBoomer(boomArgs.SpawnCount, boomArgs.SpawnRate)
}
hrpBoomer.SetProfile(&boomArgs.Profile)
ctx := hrpBoomer.EnableGracefulQuit(context.Background())
@@ -93,9 +92,6 @@ var boomCmd = &cobra.Command{
go hrpBoomer.PollTasks(ctx)
hrpBoomer.RunWorker()
case "standalone":
if venv != "" {
hrpBoomer.SetPython3Venv(venv)
}
hrpBoomer.InitBoomer()
hrpBoomer.Run(paths...)
}
@@ -122,8 +118,6 @@ type BoomArgs struct {
var boomArgs BoomArgs
func init() {
rootCmd.AddCommand(boomCmd)
boomCmd.Flags().Int64Var(&boomArgs.MaxRPS, "max-rps", 0, "Max RPS that boomer can generate, disabled by default.")
boomCmd.Flags().StringVar(&boomArgs.RequestIncreaseRate, "request-increase-rate", "-1", "Request increase rate, disabled by default.")
boomCmd.Flags().Int64Var(&boomArgs.SpawnCount, "spawn-count", 1, "The number of users to spawn for load testing")
@@ -151,22 +145,3 @@ func init() {
boomCmd.Flags().IntVar(&boomArgs.expectWorkers, "expect-workers", 1, "How many workers master should expect to connect before starting the test (only when --autostart is used)")
boomCmd.Flags().IntVar(&boomArgs.expectWorkersMaxWait, "expect-workers-max-wait", 120, "How many workers master should expect to connect before starting the test (only when --autostart is used")
}
func makeHRPBoomer() (*hrp.HRPBoomer, error) {
// if set profile, the priority is higher than the other commands
if boomArgs.profile != "" {
err := hrp.LoadFileObject(boomArgs.profile, &boomArgs)
if err != nil {
log.Error().Err(err).Msg("failed to load profile")
return nil, err
}
}
hrpBoomer := hrp.NewStandaloneBoomer(boomArgs.SpawnCount, boomArgs.SpawnRate)
if venv != "" {
hrpBoomer.SetPython3Venv(venv)
}
hrpBoomer.SetProfile(&boomArgs.Profile)
hrpBoomer.EnableGracefulQuit(context.Background())
hrpBoomer.InitBoomer()
return hrpBoomer, nil
}

View File

@@ -0,0 +1,22 @@
package main
import (
"time"
"github.com/getsentry/sentry-go"
)
func main() {
defer func() {
if err := recover(); err != nil {
// report panic to sentry
sentry.CurrentHub().Recover(err)
sentry.Flush(time.Second * 5)
// print panic trace
panic(err)
}
}()
boomCmd.Execute()
}

View File

@@ -291,7 +291,7 @@ func (r *HRPRunner) NewCaseRunner(testcase TestCase) (*CaseRunner, error) {
if err != nil {
return nil, errors.Wrap(err, "init plugin failed")
}
caseRunner.parser.plugin = plugin
caseRunner.parser.Plugin = plugin
// load plugin info to testcase config
pluginPath := plugin.Path()
@@ -339,6 +339,14 @@ type CaseRunner struct {
uixtDrivers map[string]*uixt.DriverExt
}
func (r *CaseRunner) GetParametersIterator() *ParametersIterator {
return r.parametersIterator
}
func (r *CaseRunner) GetParser() *Parser {
return r.parser
}
// parseConfig parses testcase config, stores to parsedConfig.
func (r *CaseRunner) parseConfig() (parsedConfig *TConfig, err error) {
cfg := r.TestCase.Config.Get()
@@ -541,7 +549,7 @@ func (r *CaseRunner) NewSession() *SessionRunner {
sessionVariables: make(map[string]interface{}),
summary: NewCaseSummary(),
transactions: make(map[string]map[transactionType]time.Time),
transactions: make(map[string]map[TransactionType]time.Time),
ws: newWSSession(),
}
return sessionRunner
@@ -557,7 +565,7 @@ type SessionRunner struct {
// 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
transactions map[string]map[TransactionType]time.Time
// websocket session
ws *wsSession
@@ -573,11 +581,11 @@ func (r *SessionRunner) Start(givenVars map[string]interface{}) (summary *TestCa
log.Info().Str("testcase", config.Name).Msg("run testcase start")
// update config variables with given variables
r.initWithParameters(givenVars)
r.InitWithParameters(givenVars)
defer func() {
// release session resources
r.releaseResources()
r.ReleaseResources()
summary = r.summary
summary.Name = config.Name
@@ -645,7 +653,7 @@ func (r *SessionRunner) Start(givenVars map[string]interface{}) (summary *TestCa
func (r *SessionRunner) RunStep(step IStep) (stepResult *StepResult, err error) {
// parse step struct
if err = r.parseStepStruct(step); err != nil {
if err = r.ParseStep(step); err != nil {
log.Error().Err(err).Msg("parse step struct failed")
if r.caseRunner.hrpRunner.failfast {
return nil, errors.Wrap(err, "parse step struct failed")
@@ -714,7 +722,7 @@ func (r *SessionRunner) GetSummary() *TestCaseSummary {
return r.summary
}
func (r *SessionRunner) parseStepStruct(step IStep) error {
func (r *SessionRunner) ParseStep(step IStep) error {
caseConfig := r.caseRunner.TestCase.Config.Get()
stepConfig := step.Config()
@@ -770,9 +778,9 @@ func (r *SessionRunner) parseStepStruct(step IStep) error {
return nil
}
// initWithParameters updates session variables with given parameters.
// InitWithParameters updates session variables with given parameters.
// this is used for data driven
func (r *SessionRunner) initWithParameters(parameters map[string]interface{}) {
func (r *SessionRunner) InitWithParameters(parameters map[string]interface{}) {
if len(parameters) == 0 {
return
}
@@ -782,3 +790,11 @@ func (r *SessionRunner) initWithParameters(parameters map[string]interface{}) {
r.sessionVariables[k] = v
}
}
func (r *SessionRunner) GetSessionVariables() map[string]interface{} {
return r.sessionVariables
}
func (r *SessionRunner) GetTransactions() map[string]map[TransactionType]time.Time {
return r.transactions
}

View File

@@ -294,7 +294,7 @@ func TestSessionRunner(t *testing.T) {
t.Fatal()
}
err := sessionRunner.parseStepStruct(step)
err := sessionRunner.ParseStep(step)
if err != nil {
t.Fatal()
}

View File

@@ -5,18 +5,18 @@ import "github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
type StepType string
const (
stepTypeRequest StepType = "request"
stepTypeAPI StepType = "api"
stepTypeTestCase StepType = "testcase"
stepTypeTransaction StepType = "transaction"
stepTypeRendezvous StepType = "rendezvous"
stepTypeThinkTime StepType = "thinktime"
stepTypeWebSocket StepType = "websocket"
stepTypeAndroid StepType = "android"
stepTypeHarmony StepType = "harmony"
stepTypeIOS StepType = "ios"
stepTypeShell StepType = "shell"
stepTypeFunction StepType = "function"
StepTypeRequest StepType = "request"
StepTypeAPI StepType = "api"
StepTypeTestCase StepType = "testcase"
StepTypeTransaction StepType = "transaction"
StepTypeRendezvous StepType = "rendezvous"
StepTypeThinkTime StepType = "thinktime"
StepTypeWebSocket StepType = "websocket"
StepTypeAndroid StepType = "android"
StepTypeHarmony StepType = "harmony"
StepTypeIOS StepType = "ios"
StepTypeShell StepType = "shell"
StepTypeFunction StepType = "function"
stepTypeSuffixExtraction StepType = "_extraction"
stepTypeSuffixValidation StepType = "_validation"

View File

@@ -90,7 +90,7 @@ func (s *StepAPIWithOptionalArgs) Name() string {
}
func (s *StepAPIWithOptionalArgs) Type() StepType {
return stepTypeAPI
return StepTypeAPI
}
func (s *StepAPIWithOptionalArgs) Config() *StepConfig {
@@ -99,7 +99,7 @@ func (s *StepAPIWithOptionalArgs) Config() *StepConfig {
func (s *StepAPIWithOptionalArgs) Run(r *SessionRunner) (stepResult *StepResult, err error) {
defer func() {
stepResult.StepType = stepTypeAPI
stepResult.StepType = StepTypeAPI
}()
// extend request with referenced API
api, _ := s.API.(*API)

View File

@@ -21,7 +21,7 @@ func (s *StepFunction) Name() string {
}
func (s *StepFunction) Type() StepType {
return stepTypeFunction
return StepTypeFunction
}
func (s *StepFunction) Config() *StepConfig {
@@ -46,13 +46,13 @@ func runStepFunction(r *SessionRunner, step IStep) (stepResult *StepResult, err
log.Info().
Str("name", step.Name()).
Str("type", string(stepTypeFunction)).
Str("type", string(StepTypeFunction)).
Msg("run function")
start := time.Now()
stepResult = &StepResult{
Name: step.Name(),
StepType: stepTypeFunction,
StepType: StepTypeFunction,
Success: false,
ContentSize: 0,
StartTime: start.Unix(),

View File

@@ -39,15 +39,15 @@ func (s *StepMobile) obj() *MobileUI {
if s.IOS != nil {
s.cache = s.IOS
s.cache.OSType = string(stepTypeIOS)
s.cache.OSType = string(StepTypeIOS)
return s.cache
} else if s.Harmony != nil {
s.cache = s.Harmony
s.cache.OSType = string(stepTypeHarmony)
s.cache.OSType = string(StepTypeHarmony)
return s.cache
} else if s.Android != nil {
s.cache = s.Android
s.cache.OSType = string(stepTypeAndroid)
s.cache.OSType = string(StepTypeAndroid)
return s.cache
} else if s.Mobile != nil {
s.cache = s.Mobile

View File

@@ -22,7 +22,7 @@ func (s *StepRendezvous) Name() string {
}
func (s *StepRendezvous) Type() StepType {
return stepTypeRendezvous
return StepTypeRendezvous
}
func (s *StepRendezvous) Config() *StepConfig {
@@ -43,7 +43,7 @@ func (s *StepRendezvous) Run(r *SessionRunner) (*StepResult, error) {
stepResult := &StepResult{
Name: rendezvous.Name,
StepType: stepTypeRendezvous,
StepType: StepTypeRendezvous,
Success: true,
}
@@ -75,7 +75,7 @@ func (s *StepRendezvous) Run(r *SessionRunner) (*StepResult, error) {
func isPreRendezvousAllReleased(rendezvous *Rendezvous, testCase *TestCase) bool {
for _, step := range testCase.TestSteps {
if step.Type() != stepTypeRendezvous {
if step.Type() != StepTypeRendezvous {
continue
}
preRendezvous := step.(*StepRendezvous).Rendezvous
@@ -149,7 +149,7 @@ func (r *Rendezvous) isSpawnDone() bool {
return atomic.LoadUint32(&r.spawnDoneFlag) == 1
}
func (r *Rendezvous) setSpawnDone() {
func (r *Rendezvous) SetSpawnDone() {
atomic.StoreUint32(&r.spawnDoneFlag, 1)
}
@@ -161,7 +161,11 @@ func (r *Rendezvous) setReleased() {
atomic.StoreUint32(&r.releasedFlag, 1)
}
func initRendezvous(testcase *TestCase, total int64) []*Rendezvous {
func (r *Rendezvous) updateRendezvousNumber(number int64) {
atomic.StoreInt64(&r.Number, int64(float32(number)*r.Percent))
}
func InitRendezvous(testcase *TestCase, total int64) []*Rendezvous {
var rendezvousList []*Rendezvous
for _, s := range testcase.TestSteps {
step := s.(*StepRendezvous)
@@ -195,11 +199,7 @@ func initRendezvous(testcase *TestCase, total int64) []*Rendezvous {
return rendezvousList
}
func (r *Rendezvous) updateRendezvousNumber(number int64) {
atomic.StoreInt64(&r.Number, int64(float32(number)*r.Percent))
}
func waitRendezvous(rendezvousList []*Rendezvous, b *HRPBoomer) {
func WaitRendezvous(rendezvousList []*Rendezvous, b IBoomer) {
if rendezvousList != nil {
lastRendezvous := rendezvousList[len(rendezvousList)-1]
for _, rendezvous := range rendezvousList {
@@ -208,7 +208,7 @@ func waitRendezvous(rendezvousList []*Rendezvous, b *HRPBoomer) {
}
}
func waitSingleRendezvous(rendezvous *Rendezvous, rendezvousList []*Rendezvous, lastRendezvous *Rendezvous, b *HRPBoomer) {
func waitSingleRendezvous(rendezvous *Rendezvous, rendezvousList []*Rendezvous, lastRendezvous *Rendezvous, b IBoomer) {
for {
// cycle start: block current checking until current rendezvous activated
<-rendezvous.activateChan
@@ -260,3 +260,7 @@ func waitSingleRendezvous(rendezvous *Rendezvous, rendezvousList []*Rendezvous,
}
}
}
type IBoomer interface {
GetSpawnCount() int
}

View File

@@ -55,7 +55,7 @@ func TestRunCaseWithRendezvous(t *testing.T) {
{number: 100, percent: 1, timeout: 5000},
}
rendezvousList := initRendezvous(rendezvousBoundaryTestcase, 100)
rendezvousList := InitRendezvous(rendezvousBoundaryTestcase, 100)
for i, r := range rendezvousList {
if r.Number != expectedRendezvousParams[i].number {

View File

@@ -282,7 +282,7 @@ func runStepRequest(r *SessionRunner, step IStep) (stepResult *StepResult, err e
start := time.Now()
stepResult = &StepResult{
Name: stepRequest.StepName,
StepType: stepTypeRequest,
StepType: StepTypeRequest,
Success: false,
ContentSize: 0,
StartTime: start.Unix(),
@@ -707,7 +707,7 @@ func (s *StepRequest) StartTransaction(name string) *StepTransaction {
StepConfig: s.StepConfig,
Transaction: &Transaction{
Name: name,
Type: transactionStart,
Type: TransactionStart,
},
}
}
@@ -718,7 +718,7 @@ func (s *StepRequest) EndTransaction(name string) *StepTransaction {
StepConfig: s.StepConfig,
Transaction: &Transaction{
Name: name,
Type: transactionEnd,
Type: TransactionEnd,
},
}
}

View File

@@ -26,7 +26,7 @@ func (s *StepShell) Name() string {
}
func (s *StepShell) Type() StepType {
return stepTypeShell
return StepTypeShell
}
func (s *StepShell) Config() *StepConfig {
@@ -59,7 +59,7 @@ func (s *StepShellValidation) Name() string {
}
func (s *StepShellValidation) Type() StepType {
return stepTypeShell + stepTypeSuffixValidation
return StepTypeShell + stepTypeSuffixValidation
}
func (s *StepShellValidation) Config() *StepConfig {
@@ -91,14 +91,14 @@ func runStepShell(r *SessionRunner, step IStep) (stepResult *StepResult, err err
log.Info().
Str("name", step.Name()).
Str("type", string(stepTypeShell)).
Str("type", string(StepTypeShell)).
Str("content", shell.String).
Msg("run shell string")
start := time.Now()
stepResult = &StepResult{
Name: step.Name(),
StepType: stepTypeShell,
StepType: StepTypeShell,
Success: false,
ContentSize: 0,
StartTime: start.Unix(),

View File

@@ -38,7 +38,7 @@ func (s *StepTestCaseWithOptionalArgs) Name() string {
}
func (s *StepTestCaseWithOptionalArgs) Type() StepType {
return stepTypeTestCase
return StepTypeTestCase
}
func (s *StepTestCaseWithOptionalArgs) Config() *StepConfig {
@@ -49,7 +49,7 @@ func (s *StepTestCaseWithOptionalArgs) Run(r *SessionRunner) (stepResult *StepRe
start := time.Now()
stepResult = &StepResult{
Name: s.StepName,
StepType: stepTypeTestCase,
StepType: StepTypeTestCase,
Success: false,
StartTime: start.Unix(),
}

View File

@@ -23,7 +23,7 @@ func (s *StepThinkTime) Name() string {
}
func (s *StepThinkTime) Type() StepType {
return stepTypeThinkTime
return StepTypeThinkTime
}
func (s *StepThinkTime) Config() *StepConfig {
@@ -36,7 +36,7 @@ func (s *StepThinkTime) Run(r *SessionRunner) (*StepResult, error) {
stepResult := &StepResult{
Name: s.StepName,
StepType: stepTypeThinkTime,
StepType: StepTypeThinkTime,
Success: true,
}

View File

@@ -9,14 +9,14 @@ import (
type Transaction struct {
Name string `json:"name" yaml:"name"`
Type transactionType `json:"type" yaml:"type"`
Type TransactionType `json:"type" yaml:"type"`
}
type transactionType string
type TransactionType string
const (
transactionStart transactionType = "start"
transactionEnd transactionType = "end"
TransactionStart TransactionType = "start"
TransactionEnd TransactionType = "end"
)
// StepTransaction implements IStep interface.
@@ -33,7 +33,7 @@ func (s *StepTransaction) Name() string {
}
func (s *StepTransaction) Type() StepType {
return stepTypeTransaction
return StepTypeTransaction
}
func (s *StepTransaction) Config() *StepConfig {
@@ -49,7 +49,7 @@ func (s *StepTransaction) Run(r *SessionRunner) (*StepResult, error) {
stepResult := &StepResult{
Name: transaction.Name,
StepType: stepTypeTransaction,
StepType: StepTypeTransaction,
Success: true,
Elapsed: 0,
ContentSize: 0, // TODO: record transaction total response length
@@ -57,25 +57,25 @@ func (s *StepTransaction) Run(r *SessionRunner) (*StepResult, error) {
// create transaction if not exists
if _, ok := r.transactions[transaction.Name]; !ok {
r.transactions[transaction.Name] = make(map[transactionType]time.Time)
r.transactions[transaction.Name] = make(map[TransactionType]time.Time)
}
// record transaction start time, override if already exists
if transaction.Type == transactionStart {
r.transactions[transaction.Name][transactionStart] = time.Now()
if transaction.Type == TransactionStart {
r.transactions[transaction.Name][TransactionStart] = time.Now()
}
// record transaction end time, override if already exists
if transaction.Type == transactionEnd {
r.transactions[transaction.Name][transactionEnd] = time.Now()
if transaction.Type == TransactionEnd {
r.transactions[transaction.Name][TransactionEnd] = time.Now()
// if transaction start time not exists, use testcase start time instead
if _, ok := r.transactions[transaction.Name][transactionStart]; !ok {
r.transactions[transaction.Name][transactionStart] = r.summary.Time.StartAt
if _, ok := r.transactions[transaction.Name][TransactionStart]; !ok {
r.transactions[transaction.Name][TransactionStart] = r.summary.Time.StartAt
}
// calculate transaction duration
duration := r.transactions[transaction.Name][transactionEnd].Sub(
r.transactions[transaction.Name][transactionStart])
duration := r.transactions[transaction.Name][TransactionEnd].Sub(
r.transactions[transaction.Name][TransactionStart])
stepResult.Elapsed = duration.Milliseconds()
log.Info().Str("name", transaction.Name).Dur("elapsed", duration).Msg("transaction")
}

View File

@@ -276,7 +276,7 @@ func runStepWebSocket(r *SessionRunner, step IStep) (stepResult *StepResult, err
start := time.Now()
stepResult = &StepResult{
Name: step.Name(),
StepType: stepTypeWebSocket,
StepType: StepTypeWebSocket,
Success: false,
ContentSize: 0,
StartTime: start.Unix(),
@@ -703,7 +703,7 @@ func getContentSize(resp interface{}) int64 {
}
}
func (r *SessionRunner) releaseResources() {
func (r *SessionRunner) ReleaseResources() {
// close websocket connections
for _, wsConn := range r.ws.wsConnMap {
if wsConn != nil {

View File

@@ -193,7 +193,7 @@ type TestCaseSummary struct {
// AddStepResult updates summary of StepResult.
func (s *TestCaseSummary) AddStepResult(stepResult *StepResult) {
switch stepResult.StepType {
case stepTypeTestCase:
case StepTypeTestCase:
// record requests of testcase step
records, ok := stepResult.Data.([]*StepResult)
if !ok {

View File

@@ -17,7 +17,7 @@ func TestGenHTMLReport(t *testing.T) {
caseSummary2 := NewCaseSummary()
stepResult2 := &StepResult{
Name: "Test",
StepType: stepTypeRequest,
StepType: StepTypeRequest,
Success: false,
ContentSize: 0,
Attachments: "err",
@@ -40,7 +40,7 @@ func TestTestCaseSummary_AddStepResult(t *testing.T) {
caseSummary := NewCaseSummary()
stepResult1 := &StepResult{
Name: "Test1",
StepType: stepTypeRequest,
StepType: StepTypeRequest,
Success: true,
ContentSize: 0,
Attachments: "err",
@@ -48,7 +48,7 @@ func TestTestCaseSummary_AddStepResult(t *testing.T) {
caseSummary.AddStepResult(stepResult1)
stepResult2 := &StepResult{
Name: "Test2",
StepType: stepTypeTestCase,
StepType: StepTypeTestCase,
Success: false,
ContentSize: 0,
Attachments: "err",