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
+1 -1
View File
@@ -88,7 +88,6 @@ Usage:
Available Commands: Available Commands:
adb simple utils for android device management adb simple utils for android device management
boom run load test with boomer
build build plugin for testing build build plugin for testing
completion Generate the autocompletion script for the specified shell completion Generate the autocompletion script for the specified shell
convert convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases 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 ios simple utils for ios device management
pytest run API test with pytest pytest run API test with pytest
run run API test with go engine run run API test with go engine
server start hrp server
startproject create a scaffold project startproject create a scaffold project
wiki visit https://httprunner.com wiki visit https://httprunner.com
+1 -10
View File
@@ -10,8 +10,6 @@
`HttpRunner` 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型。简单易用,功能强大,具有丰富的插件化机制和高度的可扩展能力。 `HttpRunner` 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型。简单易用,功能强大,具有丰富的插件化机制和高度的可扩展能力。
> HttpRunner [用户调研问卷][survey] 持续收集中,我们将基于用户反馈动态调整产品特性和需求优先级。
![flow chart](https://httprunner.com/image/hrp-flow.jpg) ![flow chart](https://httprunner.com/image/hrp-flow.jpg)
[版本发布日志] | [English] [版本发布日志] | [English]
@@ -81,7 +79,6 @@ Usage:
Available Commands: Available Commands:
adb simple utils for android device management adb simple utils for android device management
boom run load test with boomer
build build plugin for testing build build plugin for testing
completion Generate the autocompletion script for the specified shell completion Generate the autocompletion script for the specified shell
convert convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases 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 ios simple utils for ios device management
pytest run API test with pytest pytest run API test with pytest
run run API test with go engine run run API test with go engine
server start hrp server
startproject create a scaffold project startproject create a scaffold project
wiki visit https://httprunner.com 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/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> <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 ## Subscribe
关注 HttpRunner 的微信公众号,第一时间获得最新资讯。 关注 HttpRunner 的微信公众号,第一时间获得最新资讯。
+1 -1
View File
@@ -1 +1 @@
v5.0.0+2501071515 v5.0.0+2502052038
+1 -1
View File
@@ -180,7 +180,7 @@ func (iter *ParametersIterator) Next() map[string]interface{} {
return selectedParameters return selectedParameters
} }
func (iter *ParametersIterator) outParameters() map[string]interface{} { func (iter *ParametersIterator) Data() map[string]interface{} {
res := map[string]interface{}{} res := map[string]interface{}{}
for key, params := range iter.data { for key, params := range iter.data {
res[key] = params res[key] = params
+6 -6
View File
@@ -25,7 +25,7 @@ func newParser() *Parser {
} }
type Parser struct { 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) { 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 // only support return at most one result value
func (p *Parser) callFunc(funcName string, arguments ...interface{}) (interface{}, error) { func (p *Parser) callFunc(funcName string, arguments ...interface{}) (interface{}, error) {
// call with plugin function // call with plugin function
if p.plugin != nil { if p.Plugin != nil {
if p.plugin.Has(funcName) { if p.Plugin.Has(funcName) {
return p.plugin.Call(funcName, arguments...) return p.Plugin.Call(funcName, arguments...)
} }
commonName := fungo.ConvertCommonName(funcName) commonName := fungo.ConvertCommonName(funcName)
if p.plugin.Has(commonName) { if p.Plugin.Has(commonName) {
return p.plugin.Call(commonName, arguments...) return p.Plugin.Call(commonName, arguments...)
} }
} }
+43 -43
View File
@@ -1,4 +1,4 @@
package hrp package hrpboomer
import ( import (
"fmt" "fmt"
@@ -12,6 +12,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"golang.org/x/net/context" "golang.org/x/net/context"
"github.com/httprunner/httprunner/v4/hrp"
"github.com/httprunner/httprunner/v4/hrp/code" "github.com/httprunner/httprunner/v4/hrp/code"
"github.com/httprunner/httprunner/v4/hrp/internal/builtin" "github.com/httprunner/httprunner/v4/hrp/internal/builtin"
"github.com/httprunner/httprunner/v4/hrp/internal/json" "github.com/httprunner/httprunner/v4/hrp/internal/json"
@@ -19,13 +20,15 @@ import (
"github.com/httprunner/httprunner/v4/hrp/pkg/boomer" "github.com/httprunner/httprunner/v4/hrp/pkg/boomer"
) )
var pluginMap sync.Map // used for reusing plugin instance
func NewStandaloneBoomer(spawnCount int64, spawnRate float64) *HRPBoomer { func NewStandaloneBoomer(spawnCount int64, spawnRate float64) *HRPBoomer {
b := &HRPBoomer{ b := &HRPBoomer{
Boomer: boomer.NewStandaloneBoomer(spawnCount, spawnRate), Boomer: boomer.NewStandaloneBoomer(spawnCount, spawnRate),
pluginsMutex: new(sync.RWMutex), pluginsMutex: new(sync.RWMutex),
} }
b.hrpRunner = NewRunner(nil) b.hrpRunner = hrp.NewRunner(nil)
return b return b
} }
@@ -34,7 +37,7 @@ func NewMasterBoomer(masterBindHost string, masterBindPort int) *HRPBoomer {
Boomer: boomer.NewMasterBoomer(masterBindHost, masterBindPort), Boomer: boomer.NewMasterBoomer(masterBindHost, masterBindPort),
pluginsMutex: new(sync.RWMutex), pluginsMutex: new(sync.RWMutex),
} }
b.hrpRunner = NewRunner(nil) b.hrpRunner = hrp.NewRunner(nil)
return b return b
} }
@@ -44,7 +47,7 @@ func NewWorkerBoomer(masterHost string, masterPort int) *HRPBoomer {
pluginsMutex: new(sync.RWMutex), pluginsMutex: new(sync.RWMutex),
} }
b.hrpRunner = NewRunner(nil) b.hrpRunner = hrp.NewRunner(nil)
// set client transport for high concurrency load testing // set client transport for high concurrency load testing
b.hrpRunner.SetClientTransport(b.GetSpawnCount(), b.GetDisableKeepAlive(), b.GetDisableCompression()) b.hrpRunner.SetClientTransport(b.GetSpawnCount(), b.GetDisableKeepAlive(), b.GetDisableCompression())
return b return b
@@ -52,7 +55,7 @@ func NewWorkerBoomer(masterHost string, masterPort int) *HRPBoomer {
type HRPBoomer struct { type HRPBoomer struct {
*boomer.Boomer *boomer.Boomer
hrpRunner *HRPRunner hrpRunner *hrp.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
} }
@@ -91,7 +94,7 @@ func (b *HRPBoomer) SetPython3Venv(venv string) *HRPBoomer {
} }
// Run starts to run load test for one or multiple testcases. // 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() startTime := time.Now()
defer func() { defer func() {
// report boom event // report boom event
@@ -113,25 +116,25 @@ func (b *HRPBoomer) Run(testcases ...ITestCase) {
b.Boomer.Run(taskSlice...) 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 // load all testcases
testCases, err := LoadTestCases(testcases...) testCases, err := hrp.LoadTestCases(testcases...)
if err != nil { if err != nil {
log.Error().Err(err).Msg("failed to load testcases") log.Error().Err(err).Msg("failed to load testcases")
os.Exit(code.GetErrorCode(err)) os.Exit(code.GetErrorCode(err))
} }
for _, testcase := range testCases { for _, testcase := range testCases {
rendezvousList := initRendezvous(testcase, int64(b.GetSpawnCount())) rendezvousList := hrp.InitRendezvous(testcase, int64(b.GetSpawnCount()))
task := b.convertBoomerTask(testcase, rendezvousList) task := b.convertBoomerTask(testcase, rendezvousList)
taskSlice = append(taskSlice, task) taskSlice = append(taskSlice, task)
waitRendezvous(rendezvousList, b) hrp.WaitRendezvous(rendezvousList, b)
} }
return taskSlice return taskSlice
} }
func (b *HRPBoomer) ParseTestCases(testCases []*TestCase) []*TestCase { func (b *HRPBoomer) ParseTestCases(testCases []*hrp.TestCase) []*hrp.TestCase {
var parsedTestCases []*TestCase var parsedTestCases []*hrp.TestCase
for _, tc := range testCases { for _, tc := range testCases {
caseRunner, err := b.hrpRunner.NewCaseRunner(*tc) caseRunner, err := b.hrpRunner.NewCaseRunner(*tc)
if err != nil { if err != nil {
@@ -139,8 +142,8 @@ func (b *HRPBoomer) ParseTestCases(testCases []*TestCase) []*TestCase {
os.Exit(code.GetErrorCode(err)) os.Exit(code.GetErrorCode(err))
} }
caseConfig := caseRunner.TestCase.Config.Get() caseConfig := caseRunner.TestCase.Config.Get()
caseConfig.Parameters = caseRunner.parametersIterator.outParameters() caseConfig.Parameters = caseRunner.GetParametersIterator().Data()
parsedTestCases = append(parsedTestCases, &TestCase{ parsedTestCases = append(parsedTestCases, &hrp.TestCase{
Config: caseConfig, Config: caseConfig,
TestSteps: caseRunner.TestSteps, TestSteps: caseRunner.TestSteps,
}) })
@@ -148,9 +151,9 @@ func (b *HRPBoomer) ParseTestCases(testCases []*TestCase) []*TestCase {
return parsedTestCases return parsedTestCases
} }
func (b *HRPBoomer) TestCasesToBytes(testcases ...ITestCase) []byte { func (b *HRPBoomer) TestCasesToBytes(testcases ...hrp.ITestCase) []byte {
// load all testcases // load all testcases
testCases, err := LoadTestCases(testcases...) testCases, err := hrp.LoadTestCases(testcases...)
if err != nil { if err != nil {
log.Error().Err(err).Msg("failed to load testcases") log.Error().Err(err).Msg("failed to load testcases")
os.Exit(code.GetErrorCode(err)) os.Exit(code.GetErrorCode(err))
@@ -163,8 +166,8 @@ func (b *HRPBoomer) TestCasesToBytes(testcases ...ITestCase) []byte {
return testCasesBytes return testCasesBytes
} }
func (b *HRPBoomer) BytesToTCases(testCasesBytes []byte) []*TestCase { func (b *HRPBoomer) BytesToTCases(testCasesBytes []byte) []*hrp.TestCase {
var testcase []*TestCase var testcase []*hrp.TestCase
err := json.Unmarshal(testCasesBytes, &testcase) err := json.Unmarshal(testCasesBytes, &testcase)
if err != nil { if err != nil {
log.Error().Err(err).Msg("failed to unmarshal testcases") log.Error().Err(err).Msg("failed to unmarshal testcases")
@@ -176,7 +179,7 @@ func (b *HRPBoomer) Quit() {
b.Boomer.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 { for _, tc := range testCases {
// create temp dir to save testcase // create temp dir to save testcase
tempDir, err := os.MkdirTemp("", "hrp_testcases") tempDir, err := os.MkdirTemp("", "hrp_testcases")
@@ -289,9 +292,9 @@ func (b *HRPBoomer) PollTestCases(ctx context.Context) {
for { for {
select { select {
case <-b.Boomer.ParseTestCasesChan(): case <-b.Boomer.ParseTestCasesChan():
var tcs []ITestCase var tcs []hrp.ITestCase
for _, tc := range b.GetTestCasesPath() { for _, tc := range b.GetTestCasesPath() {
tcp := TestCasePath(tc) tcp := hrp.TestCasePath(tc)
tcs = append(tcs, &tcp) tcs = append(tcs, &tcp)
} }
b.TestCaseBytesChan() <- b.TestCasesToBytes(tcs...) 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 // init case runner for testcase
// this runner is shared by multiple session runners // this runner is shared by multiple session runners
caseRunner, err := b.hrpRunner.NewCaseRunner(*testcase) 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") log.Error().Err(err).Msg("failed to create runner")
os.Exit(code.GetErrorCode(err)) os.Exit(code.GetErrorCode(err))
} }
if caseRunner.parser.plugin != nil { plugin := caseRunner.GetParser().Plugin
if plugin != nil {
b.pluginsMutex.Lock() b.pluginsMutex.Lock()
b.plugins = append(b.plugins, caseRunner.parser.plugin) b.plugins = append(b.plugins, plugin)
b.pluginsMutex.Unlock() b.pluginsMutex.Unlock()
} }
@@ -322,12 +326,12 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
go func() { go func() {
<-b.GetSpawnDoneChan() <-b.GetSpawnDoneChan()
for _, rendezvous := range rendezvousList { for _, rendezvous := range rendezvousList {
rendezvous.setSpawnDone() rendezvous.SetSpawnDone()
} }
}() }()
// set paramters mode for load testing // set paramters mode for load testing
parametersIterator := caseRunner.parametersIterator parametersIterator := caseRunner.GetParametersIterator()
parametersIterator.SetUnlimitedMode() parametersIterator.SetUnlimitedMode()
// reset start time only once // reset start time only once
@@ -348,18 +352,18 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
mutex.Lock() mutex.Lock()
if parametersIterator.HasNext() { if parametersIterator.HasNext() {
sessionRunner.initWithParameters(parametersIterator.Next()) sessionRunner.InitWithParameters(parametersIterator.Next())
} }
mutex.Unlock() mutex.Unlock()
defer func() { defer func() {
sessionRunner.releaseResources() sessionRunner.ReleaseResources()
}() }()
startTime := time.Now() startTime := time.Now()
for _, step := range testcase.TestSteps { for _, step := range testcase.TestSteps {
// parse step struct // parse step struct
err = sessionRunner.parseStepStruct(step) err = sessionRunner.ParseStep(step)
if err != nil { if err != nil {
log.Error().Err(err).Msg("parse step struct failed") 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 // update step result name with parsed step name
stepResult.Name = step.Name() stepResult.Name = step.Name()
// record requests result of the step if step type is testcase // 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 // record requests of testcase step
for _, result := range stepResult.Data.([]*StepResult) { for _, result := range stepResult.Data.([]*hrp.StepResult) {
if result.Success { if result.Success {
b.RecordSuccess(string(result.StepType), result.Name, result.Elapsed, result.ContentSize) b.RecordSuccess(string(result.StepType), result.Name, result.Elapsed, result.ContentSize)
} else { } else {
@@ -396,26 +400,22 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
testcaseSuccess = false testcaseSuccess = false
transactionSuccess = 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") log.Warn().Err(err).Msg("run step failed, continue next step")
continue continue
} }
// record step success // record step success
if stepResult.StepType == stepTypeTransaction { if stepResult.StepType == hrp.StepTypeTransaction {
// transaction // transaction
// FIXME: support nested transactions // FIXME: support nested transactions
stepTransaction := step.(*StepTransaction) stepTransaction := step.(*hrp.StepTransaction)
if stepTransaction.Transaction.Type == transactionEnd { // only record when transaction ends if stepTransaction.Transaction.Type == hrp.TransactionEnd { // only record when transaction ends
b.RecordTransaction(stepTransaction.Name(), transactionSuccess, stepResult.Elapsed, 0) b.RecordTransaction(stepTransaction.Name(), transactionSuccess, stepResult.Elapsed, 0)
transactionSuccess = true // reset flag for next transaction transactionSuccess = true // reset flag for next transaction
} }
} else if stepResult.StepType == stepTypeRendezvous { } else if stepResult.StepType == hrp.StepTypeRendezvous {
// rendezvous // rendezvous
} else if stepResult.StepType == stepTypeThinkTime { } else if stepResult.StepType == hrp.StepTypeThinkTime {
// think time // think time
// no record required // no record required
} else { } else {
@@ -423,17 +423,17 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
b.RecordSuccess(string(step.Type()), stepResult.Name, stepResult.Elapsed, stepResult.ContentSize) b.RecordSuccess(string(step.Type()), stepResult.Name, stepResult.Elapsed, stepResult.ContentSize)
// update extracted variables // update extracted variables
for k, v := range stepResult.ExportVars { for k, v := range stepResult.ExportVars {
sessionRunner.sessionVariables[k] = v sessionRunner.GetSessionVariables()[k] = v
} }
} }
} }
endTime := time.Now() endTime := time.Now()
// report duration for transaction without end // report duration for transaction without end
for name, transaction := range sessionRunner.transactions { for name, transaction := range sessionRunner.GetTransactions() {
if len(transaction) == 1 { if len(transaction) == 1 {
// if transaction end time not exists, use testcase end time instead // 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) b.RecordTransaction(name, transactionSuccess, duration.Milliseconds(), 0)
} }
} }
@@ -1,4 +1,4 @@
package hrp package hrpboomer
import ( import (
"context" "context"
@@ -1,34 +1,32 @@
package hrp package hrpboomer
import ( import (
"testing" "testing"
"time" "time"
"github.com/httprunner/httprunner/v4/hrp"
) )
func TestBoomerStandaloneRun(t *testing.T) { func TestBoomerStandaloneRun(t *testing.T) {
buildHashicorpGoPlugin() testcase1 := &hrp.TestCase{
defer removeHashicorpGoPlugin() Config: hrp.NewConfig("TestCase1").SetBaseURL("https://postman-echo.com"),
TestSteps: []hrp.IStep{
testcase1 := &TestCase{ hrp.NewStep("headers").
Config: NewConfig("TestCase1").SetBaseURL("https://postman-echo.com"),
TestSteps: []IStep{
NewStep("headers").
GET("/headers"). GET("/headers").
Validate(). Validate().
AssertEqual("status_code", 200, "check status code"). AssertEqual("status_code", 200, "check status code").
AssertEqual("headers.\"Content-Type\"", "application/json", "check http response Content-Type"), AssertEqual("headers.\"Content-Type\"", "application/json", "check http response Content-Type"),
NewStep("user-agent"). hrp.NewStep("user-agent").
GET("/user-agent"). GET("/user-agent").
Validate(). Validate().
AssertEqual("status_code", 200, "check status code"). AssertEqual("status_code", 200, "check status code").
AssertEqual("headers.\"Content-Type\"", "application/json", "check http response Content-Type"), 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) b := NewStandaloneBoomer(2, 1)
go b.Run(testcase1, &testcase2) go b.Run(testcase1)
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
b.Quit() b.Quit()
} }
@@ -1,4 +1,4 @@
package cmd package main
import ( import (
"strings" "strings"
@@ -12,6 +12,7 @@ import (
"github.com/httprunner/httprunner/v4/hrp" "github.com/httprunner/httprunner/v4/hrp"
"github.com/httprunner/httprunner/v4/hrp/internal/sdk" "github.com/httprunner/httprunner/v4/hrp/internal/sdk"
"github.com/httprunner/httprunner/v4/hrp/pkg/boomer" "github.com/httprunner/httprunner/v4/hrp/pkg/boomer"
hrpboomer "github.com/httprunner/httprunner/v4/hrp/pkg/boomer/hrp"
) )
// boomCmd represents the boom command // boomCmd represents the boom command
@@ -25,11 +26,9 @@ var boomCmd = &cobra.Command{
Args: cobra.MinimumNArgs(0), Args: cobra.MinimumNArgs(0),
PreRun: func(cmd *cobra.Command, args []string) { PreRun: func(cmd *cobra.Command, args []string) {
boomer.SetUlimit(10240) // ulimit -n 10240 boomer.SetUlimit(10240) // ulimit -n 10240
if !strings.EqualFold(logLevel, "DEBUG") { // disable info logs for load testing
// disable info logs for load testing log.Info().Msg("Set global log level to WARN for load testing")
log.Info().Msg("Set global log level to WARN for load testing") zerolog.SetGlobalLevel(zerolog.WarnLevel)
zerolog.SetGlobalLevel(zerolog.WarnLevel)
}
}, },
RunE: func(cmd *cobra.Command, args []string) (err error) { RunE: func(cmd *cobra.Command, args []string) (err error) {
startTime := time.Now() startTime := time.Now()
@@ -57,13 +56,13 @@ var boomCmd = &cobra.Command{
} }
// init boomer // init boomer
var hrpBoomer *hrp.HRPBoomer var hrpBoomer *hrpboomer.HRPBoomer
if boomArgs.master { if boomArgs.master {
hrpBoomer = hrp.NewMasterBoomer(boomArgs.masterBindHost, boomArgs.masterBindPort) hrpBoomer = hrpboomer.NewMasterBoomer(boomArgs.masterBindHost, boomArgs.masterBindPort)
} else if boomArgs.worker { } else if boomArgs.worker {
hrpBoomer = hrp.NewWorkerBoomer(boomArgs.masterHost, boomArgs.masterPort) hrpBoomer = hrpboomer.NewWorkerBoomer(boomArgs.masterHost, boomArgs.masterPort)
} else { } else {
hrpBoomer = hrp.NewStandaloneBoomer(boomArgs.SpawnCount, boomArgs.SpawnRate) hrpBoomer = hrpboomer.NewStandaloneBoomer(boomArgs.SpawnCount, boomArgs.SpawnRate)
} }
hrpBoomer.SetProfile(&boomArgs.Profile) hrpBoomer.SetProfile(&boomArgs.Profile)
ctx := hrpBoomer.EnableGracefulQuit(context.Background()) ctx := hrpBoomer.EnableGracefulQuit(context.Background())
@@ -93,9 +92,6 @@ var boomCmd = &cobra.Command{
go hrpBoomer.PollTasks(ctx) go hrpBoomer.PollTasks(ctx)
hrpBoomer.RunWorker() hrpBoomer.RunWorker()
case "standalone": case "standalone":
if venv != "" {
hrpBoomer.SetPython3Venv(venv)
}
hrpBoomer.InitBoomer() hrpBoomer.InitBoomer()
hrpBoomer.Run(paths...) hrpBoomer.Run(paths...)
} }
@@ -122,8 +118,6 @@ type BoomArgs struct {
var boomArgs BoomArgs var boomArgs BoomArgs
func init() { func init() {
rootCmd.AddCommand(boomCmd)
boomCmd.Flags().Int64Var(&boomArgs.MaxRPS, "max-rps", 0, "Max RPS that boomer can generate, disabled by default.") 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().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") 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.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") 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
}
+22
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()
}
+25 -9
View File
@@ -291,7 +291,7 @@ func (r *HRPRunner) NewCaseRunner(testcase TestCase) (*CaseRunner, error) {
if err != nil { if err != nil {
return nil, errors.Wrap(err, "init plugin failed") return nil, errors.Wrap(err, "init plugin failed")
} }
caseRunner.parser.plugin = plugin caseRunner.parser.Plugin = plugin
// load plugin info to testcase config // load plugin info to testcase config
pluginPath := plugin.Path() pluginPath := plugin.Path()
@@ -339,6 +339,14 @@ type CaseRunner struct {
uixtDrivers map[string]*uixt.DriverExt 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. // parseConfig parses testcase config, stores to parsedConfig.
func (r *CaseRunner) parseConfig() (parsedConfig *TConfig, err error) { func (r *CaseRunner) parseConfig() (parsedConfig *TConfig, err error) {
cfg := r.TestCase.Config.Get() cfg := r.TestCase.Config.Get()
@@ -541,7 +549,7 @@ func (r *CaseRunner) NewSession() *SessionRunner {
sessionVariables: make(map[string]interface{}), sessionVariables: make(map[string]interface{}),
summary: NewCaseSummary(), summary: NewCaseSummary(),
transactions: make(map[string]map[transactionType]time.Time), transactions: make(map[string]map[TransactionType]time.Time),
ws: newWSSession(), ws: newWSSession(),
} }
return sessionRunner return sessionRunner
@@ -557,7 +565,7 @@ type SessionRunner struct {
// 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
// websocket session // websocket session
ws *wsSession 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") log.Info().Str("testcase", config.Name).Msg("run testcase start")
// update config variables with given variables // update config variables with given variables
r.initWithParameters(givenVars) r.InitWithParameters(givenVars)
defer func() { defer func() {
// release session resources // release session resources
r.releaseResources() r.ReleaseResources()
summary = r.summary summary = r.summary
summary.Name = config.Name 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) { func (r *SessionRunner) RunStep(step IStep) (stepResult *StepResult, err error) {
// parse step struct // 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") log.Error().Err(err).Msg("parse step struct failed")
if r.caseRunner.hrpRunner.failfast { if r.caseRunner.hrpRunner.failfast {
return nil, errors.Wrap(err, "parse step struct failed") return nil, errors.Wrap(err, "parse step struct failed")
@@ -714,7 +722,7 @@ func (r *SessionRunner) GetSummary() *TestCaseSummary {
return r.summary return r.summary
} }
func (r *SessionRunner) parseStepStruct(step IStep) error { func (r *SessionRunner) ParseStep(step IStep) error {
caseConfig := r.caseRunner.TestCase.Config.Get() caseConfig := r.caseRunner.TestCase.Config.Get()
stepConfig := step.Config() stepConfig := step.Config()
@@ -770,9 +778,9 @@ func (r *SessionRunner) parseStepStruct(step IStep) error {
return nil return nil
} }
// initWithParameters updates session variables with given parameters. // InitWithParameters updates session variables with given parameters.
// this is used for data driven // 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 { if len(parameters) == 0 {
return return
} }
@@ -782,3 +790,11 @@ func (r *SessionRunner) initWithParameters(parameters map[string]interface{}) {
r.sessionVariables[k] = v 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
}
+1 -1
View File
@@ -294,7 +294,7 @@ func TestSessionRunner(t *testing.T) {
t.Fatal() t.Fatal()
} }
err := sessionRunner.parseStepStruct(step) err := sessionRunner.ParseStep(step)
if err != nil { if err != nil {
t.Fatal() t.Fatal()
} }
+12 -12
View File
@@ -5,18 +5,18 @@ import "github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
type StepType string type StepType string
const ( const (
stepTypeRequest StepType = "request" StepTypeRequest StepType = "request"
stepTypeAPI StepType = "api" StepTypeAPI StepType = "api"
stepTypeTestCase StepType = "testcase" StepTypeTestCase StepType = "testcase"
stepTypeTransaction StepType = "transaction" StepTypeTransaction StepType = "transaction"
stepTypeRendezvous StepType = "rendezvous" StepTypeRendezvous StepType = "rendezvous"
stepTypeThinkTime StepType = "thinktime" StepTypeThinkTime StepType = "thinktime"
stepTypeWebSocket StepType = "websocket" StepTypeWebSocket StepType = "websocket"
stepTypeAndroid StepType = "android" StepTypeAndroid StepType = "android"
stepTypeHarmony StepType = "harmony" StepTypeHarmony StepType = "harmony"
stepTypeIOS StepType = "ios" StepTypeIOS StepType = "ios"
stepTypeShell StepType = "shell" StepTypeShell StepType = "shell"
stepTypeFunction StepType = "function" StepTypeFunction StepType = "function"
stepTypeSuffixExtraction StepType = "_extraction" stepTypeSuffixExtraction StepType = "_extraction"
stepTypeSuffixValidation StepType = "_validation" stepTypeSuffixValidation StepType = "_validation"
+2 -2
View File
@@ -90,7 +90,7 @@ func (s *StepAPIWithOptionalArgs) Name() string {
} }
func (s *StepAPIWithOptionalArgs) Type() StepType { func (s *StepAPIWithOptionalArgs) Type() StepType {
return stepTypeAPI return StepTypeAPI
} }
func (s *StepAPIWithOptionalArgs) Config() *StepConfig { func (s *StepAPIWithOptionalArgs) Config() *StepConfig {
@@ -99,7 +99,7 @@ func (s *StepAPIWithOptionalArgs) Config() *StepConfig {
func (s *StepAPIWithOptionalArgs) Run(r *SessionRunner) (stepResult *StepResult, err error) { func (s *StepAPIWithOptionalArgs) Run(r *SessionRunner) (stepResult *StepResult, err error) {
defer func() { defer func() {
stepResult.StepType = stepTypeAPI stepResult.StepType = StepTypeAPI
}() }()
// extend request with referenced API // extend request with referenced API
api, _ := s.API.(*API) api, _ := s.API.(*API)
+3 -3
View File
@@ -21,7 +21,7 @@ func (s *StepFunction) Name() string {
} }
func (s *StepFunction) Type() StepType { func (s *StepFunction) Type() StepType {
return stepTypeFunction return StepTypeFunction
} }
func (s *StepFunction) Config() *StepConfig { func (s *StepFunction) Config() *StepConfig {
@@ -46,13 +46,13 @@ func runStepFunction(r *SessionRunner, step IStep) (stepResult *StepResult, err
log.Info(). log.Info().
Str("name", step.Name()). Str("name", step.Name()).
Str("type", string(stepTypeFunction)). Str("type", string(StepTypeFunction)).
Msg("run function") Msg("run function")
start := time.Now() start := time.Now()
stepResult = &StepResult{ stepResult = &StepResult{
Name: step.Name(), Name: step.Name(),
StepType: stepTypeFunction, StepType: StepTypeFunction,
Success: false, Success: false,
ContentSize: 0, ContentSize: 0,
StartTime: start.Unix(), StartTime: start.Unix(),
+3 -3
View File
@@ -39,15 +39,15 @@ func (s *StepMobile) obj() *MobileUI {
if s.IOS != nil { if s.IOS != nil {
s.cache = s.IOS s.cache = s.IOS
s.cache.OSType = string(stepTypeIOS) s.cache.OSType = string(StepTypeIOS)
return s.cache return s.cache
} else if s.Harmony != nil { } else if s.Harmony != nil {
s.cache = s.Harmony s.cache = s.Harmony
s.cache.OSType = string(stepTypeHarmony) s.cache.OSType = string(StepTypeHarmony)
return s.cache return s.cache
} else if s.Android != nil { } else if s.Android != nil {
s.cache = s.Android s.cache = s.Android
s.cache.OSType = string(stepTypeAndroid) s.cache.OSType = string(StepTypeAndroid)
return s.cache return s.cache
} else if s.Mobile != nil { } else if s.Mobile != nil {
s.cache = s.Mobile s.cache = s.Mobile
+15 -11
View File
@@ -22,7 +22,7 @@ func (s *StepRendezvous) Name() string {
} }
func (s *StepRendezvous) Type() StepType { func (s *StepRendezvous) Type() StepType {
return stepTypeRendezvous return StepTypeRendezvous
} }
func (s *StepRendezvous) Config() *StepConfig { func (s *StepRendezvous) Config() *StepConfig {
@@ -43,7 +43,7 @@ func (s *StepRendezvous) Run(r *SessionRunner) (*StepResult, error) {
stepResult := &StepResult{ stepResult := &StepResult{
Name: rendezvous.Name, Name: rendezvous.Name,
StepType: stepTypeRendezvous, StepType: StepTypeRendezvous,
Success: true, Success: true,
} }
@@ -75,7 +75,7 @@ func (s *StepRendezvous) Run(r *SessionRunner) (*StepResult, error) {
func isPreRendezvousAllReleased(rendezvous *Rendezvous, testCase *TestCase) bool { func isPreRendezvousAllReleased(rendezvous *Rendezvous, testCase *TestCase) bool {
for _, step := range testCase.TestSteps { for _, step := range testCase.TestSteps {
if step.Type() != stepTypeRendezvous { if step.Type() != StepTypeRendezvous {
continue continue
} }
preRendezvous := step.(*StepRendezvous).Rendezvous preRendezvous := step.(*StepRendezvous).Rendezvous
@@ -149,7 +149,7 @@ func (r *Rendezvous) isSpawnDone() bool {
return atomic.LoadUint32(&r.spawnDoneFlag) == 1 return atomic.LoadUint32(&r.spawnDoneFlag) == 1
} }
func (r *Rendezvous) setSpawnDone() { func (r *Rendezvous) SetSpawnDone() {
atomic.StoreUint32(&r.spawnDoneFlag, 1) atomic.StoreUint32(&r.spawnDoneFlag, 1)
} }
@@ -161,7 +161,11 @@ func (r *Rendezvous) setReleased() {
atomic.StoreUint32(&r.releasedFlag, 1) 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 var rendezvousList []*Rendezvous
for _, s := range testcase.TestSteps { for _, s := range testcase.TestSteps {
step := s.(*StepRendezvous) step := s.(*StepRendezvous)
@@ -195,11 +199,7 @@ func initRendezvous(testcase *TestCase, total int64) []*Rendezvous {
return rendezvousList return rendezvousList
} }
func (r *Rendezvous) updateRendezvousNumber(number int64) { func WaitRendezvous(rendezvousList []*Rendezvous, b IBoomer) {
atomic.StoreInt64(&r.Number, int64(float32(number)*r.Percent))
}
func waitRendezvous(rendezvousList []*Rendezvous, b *HRPBoomer) {
if rendezvousList != nil { if rendezvousList != nil {
lastRendezvous := rendezvousList[len(rendezvousList)-1] lastRendezvous := rendezvousList[len(rendezvousList)-1]
for _, rendezvous := range rendezvousList { 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 { for {
// cycle start: block current checking until current rendezvous activated // cycle start: block current checking until current rendezvous activated
<-rendezvous.activateChan <-rendezvous.activateChan
@@ -260,3 +260,7 @@ func waitSingleRendezvous(rendezvous *Rendezvous, rendezvousList []*Rendezvous,
} }
} }
} }
type IBoomer interface {
GetSpawnCount() int
}
+1 -1
View File
@@ -55,7 +55,7 @@ func TestRunCaseWithRendezvous(t *testing.T) {
{number: 100, percent: 1, timeout: 5000}, {number: 100, percent: 1, timeout: 5000},
} }
rendezvousList := initRendezvous(rendezvousBoundaryTestcase, 100) rendezvousList := InitRendezvous(rendezvousBoundaryTestcase, 100)
for i, r := range rendezvousList { for i, r := range rendezvousList {
if r.Number != expectedRendezvousParams[i].number { if r.Number != expectedRendezvousParams[i].number {
+3 -3
View File
@@ -282,7 +282,7 @@ func runStepRequest(r *SessionRunner, step IStep) (stepResult *StepResult, err e
start := time.Now() start := time.Now()
stepResult = &StepResult{ stepResult = &StepResult{
Name: stepRequest.StepName, Name: stepRequest.StepName,
StepType: stepTypeRequest, StepType: StepTypeRequest,
Success: false, Success: false,
ContentSize: 0, ContentSize: 0,
StartTime: start.Unix(), StartTime: start.Unix(),
@@ -707,7 +707,7 @@ func (s *StepRequest) StartTransaction(name string) *StepTransaction {
StepConfig: s.StepConfig, StepConfig: s.StepConfig,
Transaction: &Transaction{ Transaction: &Transaction{
Name: name, Name: name,
Type: transactionStart, Type: TransactionStart,
}, },
} }
} }
@@ -718,7 +718,7 @@ func (s *StepRequest) EndTransaction(name string) *StepTransaction {
StepConfig: s.StepConfig, StepConfig: s.StepConfig,
Transaction: &Transaction{ Transaction: &Transaction{
Name: name, Name: name,
Type: transactionEnd, Type: TransactionEnd,
}, },
} }
} }
+4 -4
View File
@@ -26,7 +26,7 @@ func (s *StepShell) Name() string {
} }
func (s *StepShell) Type() StepType { func (s *StepShell) Type() StepType {
return stepTypeShell return StepTypeShell
} }
func (s *StepShell) Config() *StepConfig { func (s *StepShell) Config() *StepConfig {
@@ -59,7 +59,7 @@ func (s *StepShellValidation) Name() string {
} }
func (s *StepShellValidation) Type() StepType { func (s *StepShellValidation) Type() StepType {
return stepTypeShell + stepTypeSuffixValidation return StepTypeShell + stepTypeSuffixValidation
} }
func (s *StepShellValidation) Config() *StepConfig { func (s *StepShellValidation) Config() *StepConfig {
@@ -91,14 +91,14 @@ func runStepShell(r *SessionRunner, step IStep) (stepResult *StepResult, err err
log.Info(). log.Info().
Str("name", step.Name()). Str("name", step.Name()).
Str("type", string(stepTypeShell)). Str("type", string(StepTypeShell)).
Str("content", shell.String). Str("content", shell.String).
Msg("run shell string") Msg("run shell string")
start := time.Now() start := time.Now()
stepResult = &StepResult{ stepResult = &StepResult{
Name: step.Name(), Name: step.Name(),
StepType: stepTypeShell, StepType: StepTypeShell,
Success: false, Success: false,
ContentSize: 0, ContentSize: 0,
StartTime: start.Unix(), StartTime: start.Unix(),
+2 -2
View File
@@ -38,7 +38,7 @@ func (s *StepTestCaseWithOptionalArgs) Name() string {
} }
func (s *StepTestCaseWithOptionalArgs) Type() StepType { func (s *StepTestCaseWithOptionalArgs) Type() StepType {
return stepTypeTestCase return StepTypeTestCase
} }
func (s *StepTestCaseWithOptionalArgs) Config() *StepConfig { func (s *StepTestCaseWithOptionalArgs) Config() *StepConfig {
@@ -49,7 +49,7 @@ func (s *StepTestCaseWithOptionalArgs) Run(r *SessionRunner) (stepResult *StepRe
start := time.Now() start := time.Now()
stepResult = &StepResult{ stepResult = &StepResult{
Name: s.StepName, Name: s.StepName,
StepType: stepTypeTestCase, StepType: StepTypeTestCase,
Success: false, Success: false,
StartTime: start.Unix(), StartTime: start.Unix(),
} }
+2 -2
View File
@@ -23,7 +23,7 @@ func (s *StepThinkTime) Name() string {
} }
func (s *StepThinkTime) Type() StepType { func (s *StepThinkTime) Type() StepType {
return stepTypeThinkTime return StepTypeThinkTime
} }
func (s *StepThinkTime) Config() *StepConfig { func (s *StepThinkTime) Config() *StepConfig {
@@ -36,7 +36,7 @@ func (s *StepThinkTime) Run(r *SessionRunner) (*StepResult, error) {
stepResult := &StepResult{ stepResult := &StepResult{
Name: s.StepName, Name: s.StepName,
StepType: stepTypeThinkTime, StepType: StepTypeThinkTime,
Success: true, Success: true,
} }
+15 -15
View File
@@ -9,14 +9,14 @@ import (
type Transaction struct { type Transaction struct {
Name string `json:"name" yaml:"name"` 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 ( const (
transactionStart transactionType = "start" TransactionStart TransactionType = "start"
transactionEnd transactionType = "end" TransactionEnd TransactionType = "end"
) )
// StepTransaction implements IStep interface. // StepTransaction implements IStep interface.
@@ -33,7 +33,7 @@ func (s *StepTransaction) Name() string {
} }
func (s *StepTransaction) Type() StepType { func (s *StepTransaction) Type() StepType {
return stepTypeTransaction return StepTypeTransaction
} }
func (s *StepTransaction) Config() *StepConfig { func (s *StepTransaction) Config() *StepConfig {
@@ -49,7 +49,7 @@ func (s *StepTransaction) Run(r *SessionRunner) (*StepResult, error) {
stepResult := &StepResult{ stepResult := &StepResult{
Name: transaction.Name, Name: transaction.Name,
StepType: stepTypeTransaction, StepType: StepTypeTransaction,
Success: true, Success: true,
Elapsed: 0, Elapsed: 0,
ContentSize: 0, // TODO: record transaction total response length 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 // create transaction if not exists
if _, ok := r.transactions[transaction.Name]; !ok { 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 // record transaction start time, override if already exists
if transaction.Type == transactionStart { if transaction.Type == TransactionStart {
r.transactions[transaction.Name][transactionStart] = time.Now() r.transactions[transaction.Name][TransactionStart] = time.Now()
} }
// record transaction end time, override if already exists // record transaction end time, override if already exists
if transaction.Type == transactionEnd { if transaction.Type == TransactionEnd {
r.transactions[transaction.Name][transactionEnd] = time.Now() r.transactions[transaction.Name][TransactionEnd] = time.Now()
// if transaction start time not exists, use testcase start time instead // if transaction start time not exists, use testcase start time instead
if _, ok := r.transactions[transaction.Name][transactionStart]; !ok { if _, ok := r.transactions[transaction.Name][TransactionStart]; !ok {
r.transactions[transaction.Name][transactionStart] = r.summary.Time.StartAt r.transactions[transaction.Name][TransactionStart] = r.summary.Time.StartAt
} }
// calculate transaction duration // calculate transaction duration
duration := r.transactions[transaction.Name][transactionEnd].Sub( duration := r.transactions[transaction.Name][TransactionEnd].Sub(
r.transactions[transaction.Name][transactionStart]) r.transactions[transaction.Name][TransactionStart])
stepResult.Elapsed = duration.Milliseconds() stepResult.Elapsed = duration.Milliseconds()
log.Info().Str("name", transaction.Name).Dur("elapsed", duration).Msg("transaction") log.Info().Str("name", transaction.Name).Dur("elapsed", duration).Msg("transaction")
} }
+2 -2
View File
@@ -276,7 +276,7 @@ func runStepWebSocket(r *SessionRunner, step IStep) (stepResult *StepResult, err
start := time.Now() start := time.Now()
stepResult = &StepResult{ stepResult = &StepResult{
Name: step.Name(), Name: step.Name(),
StepType: stepTypeWebSocket, StepType: StepTypeWebSocket,
Success: false, Success: false,
ContentSize: 0, ContentSize: 0,
StartTime: start.Unix(), StartTime: start.Unix(),
@@ -703,7 +703,7 @@ func getContentSize(resp interface{}) int64 {
} }
} }
func (r *SessionRunner) releaseResources() { func (r *SessionRunner) ReleaseResources() {
// close websocket connections // close websocket connections
for _, wsConn := range r.ws.wsConnMap { for _, wsConn := range r.ws.wsConnMap {
if wsConn != nil { if wsConn != nil {
+1 -1
View File
@@ -193,7 +193,7 @@ type TestCaseSummary struct {
// AddStepResult updates summary of StepResult. // AddStepResult updates summary of StepResult.
func (s *TestCaseSummary) AddStepResult(stepResult *StepResult) { func (s *TestCaseSummary) AddStepResult(stepResult *StepResult) {
switch stepResult.StepType { switch stepResult.StepType {
case stepTypeTestCase: case StepTypeTestCase:
// record requests of testcase step // record requests of testcase step
records, ok := stepResult.Data.([]*StepResult) records, ok := stepResult.Data.([]*StepResult)
if !ok { if !ok {
+3 -3
View File
@@ -17,7 +17,7 @@ func TestGenHTMLReport(t *testing.T) {
caseSummary2 := NewCaseSummary() caseSummary2 := NewCaseSummary()
stepResult2 := &StepResult{ stepResult2 := &StepResult{
Name: "Test", Name: "Test",
StepType: stepTypeRequest, StepType: StepTypeRequest,
Success: false, Success: false,
ContentSize: 0, ContentSize: 0,
Attachments: "err", Attachments: "err",
@@ -40,7 +40,7 @@ func TestTestCaseSummary_AddStepResult(t *testing.T) {
caseSummary := NewCaseSummary() caseSummary := NewCaseSummary()
stepResult1 := &StepResult{ stepResult1 := &StepResult{
Name: "Test1", Name: "Test1",
StepType: stepTypeRequest, StepType: StepTypeRequest,
Success: true, Success: true,
ContentSize: 0, ContentSize: 0,
Attachments: "err", Attachments: "err",
@@ -48,7 +48,7 @@ func TestTestCaseSummary_AddStepResult(t *testing.T) {
caseSummary.AddStepResult(stepResult1) caseSummary.AddStepResult(stepResult1)
stepResult2 := &StepResult{ stepResult2 := &StepResult{
Name: "Test2", Name: "Test2",
StepType: stepTypeTestCase, StepType: StepTypeTestCase,
Success: false, Success: false,
ContentSize: 0, ContentSize: 0,
Attachments: "err", Attachments: "err",