mirror of
https://github.com/httprunner/httprunner.git
synced 2026-09-05 07:26:55 +08:00
refactor: move boomer to hrp/pkg/boomer
This commit is contained in:
+1
-1
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,6 @@
|
|||||||
|
|
||||||
`HttpRunner` 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型。简单易用,功能强大,具有丰富的插件化机制和高度的可扩展能力。
|
`HttpRunner` 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型。简单易用,功能强大,具有丰富的插件化机制和高度的可扩展能力。
|
||||||
|
|
||||||
> HttpRunner [用户调研问卷][survey] 持续收集中,我们将基于用户反馈动态调整产品特性和需求优先级。
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
[版本发布日志] | [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 @@
|
|||||||
v5.0.0+2501071515
|
v5.0.0+2502052038
|
||||||
|
|||||||
+1
-1
@@ -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
@@ -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...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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(),
|
||||||
|
|||||||
@@ -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(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user