mirror of
https://github.com/httprunner/httprunner.git
synced 2026-07-20 03:52:09 +08:00
refactor: CaseRunner and SessionRunner
This commit is contained in:
@@ -51,28 +51,50 @@ type HRPRunner struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *HRPRunner) Run(testcases ...ITestCase) error
|
func (r *HRPRunner) Run(testcases ...ITestCase) error
|
||||||
func (r *HRPRunner) NewSessionRunner(testcase *TestCase) *SessionRunner
|
func (r *HRPRunner) NewCaseRunner(testcase *TestCase) (*CaseRunner, error)
|
||||||
```
|
```
|
||||||
|
|
||||||
重点关注两个方法:
|
重点关注两个方法:
|
||||||
|
|
||||||
- Run:测试执行的主入口,支持运行一个或多个测试用例
|
- Run:测试执行的主入口,支持运行一个或多个测试用例
|
||||||
- NewSessionRunner:针对给定的测试用例初始化一个 SessionRunner
|
- NewCaseRunner:针对给定的测试用例初始化一个 CaseRunner
|
||||||
|
|
||||||
### 用例执行器 SessionRunner
|
### 用例执行器 CaseRunner
|
||||||
|
|
||||||
测试用例的具体执行都由 `SessionRunner` 完成,每个 TestCase 对应一个实例,在该实例中除了包含测试用例自身内容外,还会包含测试过程的 session 数据和最终测试结果 summary。
|
针对每个测试用例,采用 CaseRunner 存储其公共信息,包括 plugin/parser
|
||||||
|
|
||||||
|
```go
|
||||||
|
type CaseRunner struct {
|
||||||
|
testCase *TestCase
|
||||||
|
hrpRunner *HRPRunner
|
||||||
|
parser *Parser
|
||||||
|
|
||||||
|
parsedConfig *TConfig
|
||||||
|
parametersIterator *ParametersIterator
|
||||||
|
rootDir string // project root dir
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CaseRunner) NewSession() *SessionRunner {
|
||||||
|
```
|
||||||
|
|
||||||
|
重点关注一个方法:
|
||||||
|
|
||||||
|
- NewSession:测试用例的每一次执行对应一个 SessionRunner
|
||||||
|
|
||||||
|
### SessionRunner
|
||||||
|
|
||||||
|
测试用例的具体执行都由 `SessionRunner` 完成,每个 session 实例中除了包含测试用例自身内容外,还会包含测试过程的 session 数据和最终测试结果 summary。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type SessionRunner struct {
|
type SessionRunner struct {
|
||||||
testCase *TestCase
|
caseRunner *CaseRunner
|
||||||
hrpRunner *HRPRunner
|
|
||||||
parser *Parser
|
|
||||||
sessionVariables map[string]interface{}
|
sessionVariables map[string]interface{}
|
||||||
transactions map[string]map[transactionType]time.Time
|
transactions map[string]map[transactionType]time.Time
|
||||||
startTime time.Time // record start time of the testcase
|
startTime time.Time // record start time of the testcase
|
||||||
summary *TestCaseSummary // record test case summary
|
summary *TestCaseSummary // record test case summary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *SessionRunner) Start(givenVars map[string]interface{}) error
|
||||||
```
|
```
|
||||||
|
|
||||||
重点关注一个方法:
|
重点关注一个方法:
|
||||||
@@ -80,12 +102,29 @@ type SessionRunner struct {
|
|||||||
- Start:启动执行用例,依次执行所有测试步骤
|
- Start:启动执行用例,依次执行所有测试步骤
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (r *SessionRunner) Start() error {
|
func (r *SessionRunner) Start(givenVars map[string]interface{}) error {
|
||||||
...
|
...
|
||||||
|
r.resetSession()
|
||||||
|
|
||||||
|
r.InitWithParameters(givenVars)
|
||||||
|
|
||||||
// run step in sequential order
|
// run step in sequential order
|
||||||
for _, step := range r.testCase.TestSteps {
|
for _, step := range r.testCase.TestSteps {
|
||||||
_, err := step.Run(r)
|
// parse step
|
||||||
if err != nil && r.hrpRunner.failfast {
|
|
||||||
|
// run step
|
||||||
|
stepResult, err := step.Run(r)
|
||||||
|
|
||||||
|
// update summary
|
||||||
|
r.summary.Records = append(r.summary.Records, stepResult)
|
||||||
|
|
||||||
|
// update extracted variables
|
||||||
|
for k, v := range stepResult.ExportVars {
|
||||||
|
r.sessionVariables[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if failfast
|
||||||
|
if err != nil && r.caseRunner.hrpRunner.failfast {
|
||||||
return errors.Wrap(err, "abort running due to failfast setting")
|
return errors.Wrap(err, "abort running due to failfast setting")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ func (b *HRPBoomer) ConvertTestCasesToBoomerTasks(testcases ...ITestCase) (taskS
|
|||||||
func (b *HRPBoomer) ParseTestCases(testCases []*TestCase) []*TCase {
|
func (b *HRPBoomer) ParseTestCases(testCases []*TestCase) []*TCase {
|
||||||
var parsedTestCases []*TCase
|
var parsedTestCases []*TCase
|
||||||
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 {
|
||||||
log.Error().Err(err).Msg("failed to create runner")
|
log.Error().Err(err).Msg("failed to create runner")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -313,9 +313,9 @@ func (b *HRPBoomer) PollTestCases(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rendezvous) *boomer.Task {
|
func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rendezvous) *boomer.Task {
|
||||||
// init 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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("failed to create runner")
|
log.Error().Err(err).Msg("failed to create runner")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -352,18 +352,19 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend
|
|||||||
transactionSuccess := true // flag current transaction result
|
transactionSuccess := true // flag current transaction result
|
||||||
|
|
||||||
// init session runner
|
// init session runner
|
||||||
sessionRunner := caseRunner.newSession()
|
sessionRunner := caseRunner.NewSession()
|
||||||
|
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
if parametersIterator.HasNext() {
|
if parametersIterator.HasNext() {
|
||||||
sessionRunner.updateSessionVariables(parametersIterator.Next())
|
sessionRunner.InitWithParameters(parametersIterator.Next())
|
||||||
}
|
}
|
||||||
mutex.Unlock()
|
mutex.Unlock()
|
||||||
|
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
for _, step := range testcase.TestSteps {
|
for _, step := range testcase.TestSteps {
|
||||||
|
// TODO: parse step struct
|
||||||
// parse step name
|
// parse step name
|
||||||
parsedName, err := sessionRunner.parser.ParseString(step.Name(), sessionRunner.sessionVariables)
|
parsedName, err := caseRunner.parser.ParseString(step.Name(), sessionRunner.sessionVariables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
parsedName = step.Name()
|
parsedName = step.Name()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// NOTE: Generated By hrp v4.2.0, DO NOT EDIT!
|
// NOTE: Generated By hrp v4.3.0-beta-10172144, DO NOT EDIT!
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
235
hrp/runner.go
235
hrp/runner.go
@@ -2,6 +2,7 @@ package hrp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
_ "embed"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
@@ -206,19 +207,24 @@ func (r *HRPRunner) Run(testcases ...ITestCase) error {
|
|||||||
var runErr error
|
var runErr error
|
||||||
// run testcase one by one
|
// run testcase one by one
|
||||||
for _, testcase := range testCases {
|
for _, testcase := range testCases {
|
||||||
sessionRunner, err := r.NewSessionRunner(testcase)
|
// each testcase has its own case runner
|
||||||
|
caseRunner, err := r.NewCaseRunner(testcase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("[Run] init session runner failed")
|
log.Error().Err(err).Msg("[Run] init case runner failed")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// release UI driver session
|
// release UI driver session
|
||||||
defer func() {
|
defer func() {
|
||||||
for _, client := range sessionRunner.hrpRunner.uiClients {
|
for _, client := range r.uiClients {
|
||||||
client.Driver.DeleteSession()
|
client.Driver.DeleteSession()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for it := sessionRunner.parametersIterator; it.HasNext(); {
|
for it := caseRunner.parametersIterator; it.HasNext(); {
|
||||||
|
// case runner can run multiple times with different parameters
|
||||||
|
// each run has its own session runner
|
||||||
|
sessionRunner := caseRunner.NewSession()
|
||||||
err1 := sessionRunner.Start(it.Next())
|
err1 := sessionRunner.Start(it.Next())
|
||||||
caseSummary, err2 := sessionRunner.GetSummary()
|
caseSummary, err2 := sessionRunner.GetSummary()
|
||||||
s.appendCaseSummary(caseSummary)
|
s.appendCaseSummary(caseSummary)
|
||||||
@@ -250,23 +256,10 @@ func (r *HRPRunner) Run(testcases ...ITestCase) error {
|
|||||||
return runErr
|
return runErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSessionRunner creates a new session runner for testcase.
|
// NewCaseRunner creates a new case runner for testcase.
|
||||||
// each testcase has its own session runner
|
// each testcase has its own case runner
|
||||||
func (r *HRPRunner) NewSessionRunner(testcase *TestCase) (*SessionRunner, error) {
|
func (r *HRPRunner) NewCaseRunner(testcase *TestCase) (*CaseRunner, error) {
|
||||||
runner, err := r.newCaseRunner(testcase)
|
caseRunner := &CaseRunner{
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionRunner := &SessionRunner{
|
|
||||||
testCaseRunner: runner,
|
|
||||||
}
|
|
||||||
sessionRunner.resetSession()
|
|
||||||
return sessionRunner, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *HRPRunner) newCaseRunner(testcase *TestCase) (*testCaseRunner, error) {
|
|
||||||
runner := &testCaseRunner{
|
|
||||||
testCase: testcase,
|
testCase: testcase,
|
||||||
hrpRunner: r,
|
hrpRunner: r,
|
||||||
parser: newParser(),
|
parser: newParser(),
|
||||||
@@ -278,34 +271,31 @@ func (r *HRPRunner) newCaseRunner(testcase *TestCase) (*testCaseRunner, error) {
|
|||||||
return nil, errors.Wrap(err, "init plugin failed")
|
return nil, errors.Wrap(err, "init plugin failed")
|
||||||
}
|
}
|
||||||
if plugin != nil {
|
if plugin != nil {
|
||||||
runner.parser.plugin = plugin
|
caseRunner.parser.plugin = plugin
|
||||||
runner.rootDir = filepath.Dir(plugin.Path())
|
caseRunner.rootDir = filepath.Dir(plugin.Path())
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse testcase config
|
// parse testcase config
|
||||||
if err := runner.parseConfig(); err != nil {
|
if err := caseRunner.parseConfig(); err != nil {
|
||||||
return nil, errors.Wrap(err, "parse testcase config failed")
|
return nil, errors.Wrap(err, "parse testcase config failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// init websocket params
|
|
||||||
initWebSocket(testcase)
|
|
||||||
|
|
||||||
// set testcase timeout in seconds
|
// set testcase timeout in seconds
|
||||||
if runner.testCase.Config.Timeout != 0 {
|
if testcase.Config.Timeout != 0 {
|
||||||
timeout := time.Duration(runner.testCase.Config.Timeout*1000) * time.Millisecond
|
timeout := time.Duration(testcase.Config.Timeout*1000) * time.Millisecond
|
||||||
runner.hrpRunner.SetTimeout(timeout)
|
r.SetTimeout(timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// load plugin info to testcase config
|
// load plugin info to testcase config
|
||||||
if plugin != nil {
|
if plugin != nil {
|
||||||
pluginPath, _ := locatePlugin(testcase.Config.Path)
|
pluginPath, _ := locatePlugin(testcase.Config.Path)
|
||||||
if runner.parsedConfig.PluginSetting == nil {
|
if caseRunner.parsedConfig.PluginSetting == nil {
|
||||||
pluginContent, err := builtin.ReadFile(pluginPath)
|
pluginContent, err := builtin.ReadFile(pluginPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tp := strings.Split(plugin.Path(), ".")
|
tp := strings.Split(plugin.Path(), ".")
|
||||||
runner.parsedConfig.PluginSetting = &PluginConfig{
|
caseRunner.parsedConfig.PluginSetting = &PluginConfig{
|
||||||
Path: pluginPath,
|
Path: pluginPath,
|
||||||
Content: pluginContent,
|
Content: pluginContent,
|
||||||
Type: tp[len(tp)-1],
|
Type: tp[len(tp)-1],
|
||||||
@@ -313,10 +303,10 @@ func (r *HRPRunner) newCaseRunner(testcase *TestCase) (*testCaseRunner, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return runner, nil
|
return caseRunner, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type testCaseRunner struct {
|
type CaseRunner struct {
|
||||||
testCase *TestCase
|
testCase *TestCase
|
||||||
hrpRunner *HRPRunner
|
hrpRunner *HRPRunner
|
||||||
parser *Parser
|
parser *Parser
|
||||||
@@ -327,7 +317,7 @@ type testCaseRunner struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseConfig parses testcase config, stores to parsedConfig.
|
// parseConfig parses testcase config, stores to parsedConfig.
|
||||||
func (r *testCaseRunner) parseConfig() error {
|
func (r *CaseRunner) parseConfig() error {
|
||||||
cfg := r.testCase.Config
|
cfg := r.testCase.Config
|
||||||
|
|
||||||
r.parsedConfig = &TConfig{}
|
r.parsedConfig = &TConfig{}
|
||||||
@@ -441,10 +431,181 @@ func (r *testCaseRunner) parseConfig() error {
|
|||||||
|
|
||||||
// each boomer task initiates a new session
|
// each boomer task initiates a new session
|
||||||
// in order to avoid data racing
|
// in order to avoid data racing
|
||||||
func (r *testCaseRunner) newSession() *SessionRunner {
|
func (r *CaseRunner) NewSession() *SessionRunner {
|
||||||
sessionRunner := &SessionRunner{
|
sessionRunner := &SessionRunner{
|
||||||
testCaseRunner: r,
|
caseRunner: r,
|
||||||
}
|
}
|
||||||
sessionRunner.resetSession()
|
sessionRunner.resetSession()
|
||||||
return sessionRunner
|
return sessionRunner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SessionRunner is used to run testcase and its steps.
|
||||||
|
// each testcase has its own SessionRunner instance and share session variables.
|
||||||
|
type SessionRunner struct {
|
||||||
|
caseRunner *CaseRunner
|
||||||
|
sessionVariables map[string]interface{}
|
||||||
|
// transactions stores transaction timing info.
|
||||||
|
// key is transaction name, value is map of transaction type and time, e.g. start time and end time.
|
||||||
|
transactions map[string]map[transactionType]time.Time
|
||||||
|
startTime time.Time // record start time of the testcase
|
||||||
|
summary *TestCaseSummary // record test case summary
|
||||||
|
wsConnMap map[string]*websocket.Conn // save all websocket connections
|
||||||
|
pongResponseChan chan string // channel used to receive pong response message
|
||||||
|
closeResponseChan chan *wsCloseRespObject // channel used to receive close response message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SessionRunner) resetSession() {
|
||||||
|
log.Info().Msg("reset session runner")
|
||||||
|
r.sessionVariables = make(map[string]interface{})
|
||||||
|
r.transactions = make(map[string]map[transactionType]time.Time)
|
||||||
|
r.startTime = time.Now()
|
||||||
|
r.summary = newSummary()
|
||||||
|
r.wsConnMap = make(map[string]*websocket.Conn)
|
||||||
|
r.pongResponseChan = make(chan string, 1)
|
||||||
|
r.closeResponseChan = make(chan *wsCloseRespObject, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start runs the test steps in sequential order.
|
||||||
|
// givenVars is used for data driven
|
||||||
|
func (r *SessionRunner) Start(givenVars map[string]interface{}) error {
|
||||||
|
config := r.caseRunner.testCase.Config
|
||||||
|
log.Info().Str("testcase", config.Name).Msg("run testcase start")
|
||||||
|
|
||||||
|
// reset session runner
|
||||||
|
r.resetSession()
|
||||||
|
|
||||||
|
// update config variables with given variables
|
||||||
|
r.InitWithParameters(givenVars)
|
||||||
|
|
||||||
|
// run step in sequential order
|
||||||
|
for _, step := range r.caseRunner.testCase.TestSteps {
|
||||||
|
// TODO: parse step struct
|
||||||
|
// parse step name
|
||||||
|
parsedName, err := r.caseRunner.parser.ParseString(step.Name(), r.sessionVariables)
|
||||||
|
if err != nil {
|
||||||
|
parsedName = step.Name()
|
||||||
|
}
|
||||||
|
stepName := convertString(parsedName)
|
||||||
|
log.Info().Str("step", stepName).
|
||||||
|
Str("type", string(step.Type())).Msg("run step start")
|
||||||
|
|
||||||
|
// run step
|
||||||
|
stepResult, err := step.Run(r)
|
||||||
|
stepResult.Name = stepName
|
||||||
|
|
||||||
|
// update summary
|
||||||
|
r.summary.Records = append(r.summary.Records, stepResult)
|
||||||
|
r.summary.Stat.Total += 1
|
||||||
|
if stepResult.Success {
|
||||||
|
r.summary.Stat.Successes += 1
|
||||||
|
} else {
|
||||||
|
r.summary.Stat.Failures += 1
|
||||||
|
// update summary result to failed
|
||||||
|
r.summary.Success = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// update extracted variables
|
||||||
|
for k, v := range stepResult.ExportVars {
|
||||||
|
r.sessionVariables[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
log.Info().Str("step", stepResult.Name).
|
||||||
|
Str("type", string(stepResult.StepType)).
|
||||||
|
Bool("success", true).
|
||||||
|
Interface("exportVars", stepResult.ExportVars).
|
||||||
|
Msg("run step end")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// failed
|
||||||
|
log.Error().Err(err).Str("step", stepResult.Name).
|
||||||
|
Str("type", string(stepResult.StepType)).
|
||||||
|
Bool("success", false).
|
||||||
|
Msg("run step end")
|
||||||
|
|
||||||
|
// check if failfast
|
||||||
|
if r.caseRunner.hrpRunner.failfast {
|
||||||
|
return errors.Wrap(err, "abort running due to failfast setting")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// close websocket connection after all steps done
|
||||||
|
defer func() {
|
||||||
|
for _, wsConn := range r.wsConnMap {
|
||||||
|
if wsConn != nil {
|
||||||
|
log.Info().Str("testcase", config.Name).Msg("websocket disconnected")
|
||||||
|
err := wsConn.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("websocket disconnection failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Info().Str("testcase", config.Name).Msg("run testcase end")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseStepVariables merges step variables with config variables and session variables
|
||||||
|
func (r *SessionRunner) ParseStepVariables(stepVariables map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
// override variables
|
||||||
|
// step variables > session variables (extracted variables from previous steps)
|
||||||
|
overrideVars := mergeVariables(stepVariables, r.sessionVariables)
|
||||||
|
// step variables > testcase config variables
|
||||||
|
overrideVars = mergeVariables(overrideVars, r.caseRunner.parsedConfig.Variables)
|
||||||
|
|
||||||
|
// parse step variables
|
||||||
|
parsedVariables, err := r.caseRunner.parser.ParseVariables(overrideVars)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Interface("variables", r.caseRunner.parsedConfig.Variables).
|
||||||
|
Err(err).Msg("parse step variables failed")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return parsedVariables, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitWithParameters updates session variables with given parameters.
|
||||||
|
// this is used for data driven
|
||||||
|
func (r *SessionRunner) InitWithParameters(parameters map[string]interface{}) {
|
||||||
|
if len(parameters) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Interface("parameters", parameters).Msg("update session variables")
|
||||||
|
for k, v := range parameters {
|
||||||
|
r.sessionVariables[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SessionRunner) GetSummary() (*TestCaseSummary, error) {
|
||||||
|
caseSummary := r.summary
|
||||||
|
caseSummary.Name = r.caseRunner.parsedConfig.Name
|
||||||
|
caseSummary.Time.StartAt = r.startTime
|
||||||
|
caseSummary.Time.Duration = time.Since(r.startTime).Seconds()
|
||||||
|
exportVars := make(map[string]interface{})
|
||||||
|
for _, value := range r.caseRunner.parsedConfig.Export {
|
||||||
|
exportVars[value] = r.sessionVariables[value]
|
||||||
|
}
|
||||||
|
caseSummary.InOut.ExportVars = exportVars
|
||||||
|
caseSummary.InOut.ConfigVars = r.caseRunner.parsedConfig.Variables
|
||||||
|
|
||||||
|
for uuid, client := range r.caseRunner.hrpRunner.uiClients {
|
||||||
|
// add WDA/UIA logs to summary
|
||||||
|
log, err := client.Driver.StopCaptureLog()
|
||||||
|
if err != nil {
|
||||||
|
return caseSummary, err
|
||||||
|
}
|
||||||
|
logs := map[string]interface{}{
|
||||||
|
"uuid": uuid,
|
||||||
|
"content": log,
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop performance monitor
|
||||||
|
logs["performance"] = client.GetPerfData()
|
||||||
|
|
||||||
|
caseSummary.Logs = append(caseSummary.Logs, logs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return caseSummary, nil
|
||||||
|
}
|
||||||
|
|||||||
191
hrp/session.go
191
hrp/session.go
@@ -1,191 +0,0 @@
|
|||||||
package hrp
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SessionRunner is used to run testcase and its steps.
|
|
||||||
// each testcase has its own SessionRunner instance and share session variables.
|
|
||||||
type SessionRunner struct {
|
|
||||||
*testCaseRunner
|
|
||||||
sessionVariables map[string]interface{}
|
|
||||||
// transactions stores transaction timing info.
|
|
||||||
// key is transaction name, value is map of transaction type and time, e.g. start time and end time.
|
|
||||||
transactions map[string]map[transactionType]time.Time
|
|
||||||
startTime time.Time // record start time of the testcase
|
|
||||||
summary *TestCaseSummary // record test case summary
|
|
||||||
wsConnMap map[string]*websocket.Conn // save all websocket connections
|
|
||||||
pongResponseChan chan string // channel used to receive pong response message
|
|
||||||
closeResponseChan chan *wsCloseRespObject // channel used to receive close response message
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *SessionRunner) resetSession() {
|
|
||||||
log.Info().Msg("reset session runner")
|
|
||||||
r.sessionVariables = make(map[string]interface{})
|
|
||||||
r.transactions = make(map[string]map[transactionType]time.Time)
|
|
||||||
r.startTime = time.Now()
|
|
||||||
r.summary = newSummary()
|
|
||||||
r.wsConnMap = make(map[string]*websocket.Conn)
|
|
||||||
r.pongResponseChan = make(chan string, 1)
|
|
||||||
r.closeResponseChan = make(chan *wsCloseRespObject, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *SessionRunner) HTTPStatOn() bool {
|
|
||||||
return r.hrpRunner.httpStatOn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *SessionRunner) LogOn() bool {
|
|
||||||
return r.hrpRunner.requestsLogOn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start runs the test steps in sequential order.
|
|
||||||
// givenVars is used for data driven
|
|
||||||
func (r *SessionRunner) Start(givenVars map[string]interface{}) error {
|
|
||||||
config := r.testCase.Config
|
|
||||||
log.Info().Str("testcase", config.Name).Msg("run testcase start")
|
|
||||||
|
|
||||||
// reset session runner
|
|
||||||
r.resetSession()
|
|
||||||
|
|
||||||
// update config variables with given variables
|
|
||||||
r.updateSessionVariables(givenVars)
|
|
||||||
|
|
||||||
// run step in sequential order
|
|
||||||
for _, step := range r.testCase.TestSteps {
|
|
||||||
// TODO: parse step
|
|
||||||
// parse step name
|
|
||||||
parsedName, err := r.parser.ParseString(step.Name(), r.sessionVariables)
|
|
||||||
if err != nil {
|
|
||||||
parsedName = step.Name()
|
|
||||||
}
|
|
||||||
stepName := convertString(parsedName)
|
|
||||||
log.Info().Str("step", stepName).
|
|
||||||
Str("type", string(step.Type())).Msg("run step start")
|
|
||||||
|
|
||||||
// merge step variables with session variables
|
|
||||||
step.Struct().Variables, err = r.mergeStepVariables(step.Struct().Variables)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "merge step variables with session variables failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// run step
|
|
||||||
stepResult, err := step.Run(r)
|
|
||||||
stepResult.Name = stepName
|
|
||||||
|
|
||||||
// update summary
|
|
||||||
r.summary.Records = append(r.summary.Records, stepResult)
|
|
||||||
r.summary.Stat.Total += 1
|
|
||||||
if stepResult.Success {
|
|
||||||
r.summary.Stat.Successes += 1
|
|
||||||
log.Info().
|
|
||||||
Str("step", stepResult.Name).
|
|
||||||
Str("type", string(stepResult.StepType)).
|
|
||||||
Bool("success", true).
|
|
||||||
Interface("exportVars", stepResult.ExportVars).
|
|
||||||
Msg("run step end")
|
|
||||||
} else {
|
|
||||||
r.summary.Stat.Failures += 1
|
|
||||||
// update summary result to failed
|
|
||||||
r.summary.Success = false
|
|
||||||
log.Error().
|
|
||||||
Str("step", stepResult.Name).
|
|
||||||
Str("type", string(stepResult.StepType)).
|
|
||||||
Bool("success", false).
|
|
||||||
Msg("run step end")
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if failfast
|
|
||||||
if err != nil && r.hrpRunner.failfast {
|
|
||||||
return errors.Wrap(err, "abort running due to failfast setting")
|
|
||||||
}
|
|
||||||
|
|
||||||
// update extracted variables
|
|
||||||
for k, v := range stepResult.ExportVars {
|
|
||||||
r.sessionVariables[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// close websocket connection after all steps done
|
|
||||||
defer func() {
|
|
||||||
for _, wsConn := range r.wsConnMap {
|
|
||||||
if wsConn != nil {
|
|
||||||
log.Info().Str("testcase", config.Name).Msg("websocket disconnected")
|
|
||||||
err := wsConn.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("websocket disconnection failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Info().Str("testcase", config.Name).Msg("run testcase end")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// mergeStepVariables merges step variables with config variables and session variables
|
|
||||||
func (r *SessionRunner) mergeStepVariables(vars map[string]interface{}) (map[string]interface{}, error) {
|
|
||||||
// override variables
|
|
||||||
// step variables > session variables (extracted variables from previous steps)
|
|
||||||
overrideVars := mergeVariables(vars, r.sessionVariables)
|
|
||||||
// step variables > testcase config variables
|
|
||||||
overrideVars = mergeVariables(overrideVars, r.parsedConfig.Variables)
|
|
||||||
|
|
||||||
// parse step variables
|
|
||||||
parsedVariables, err := r.parser.ParseVariables(overrideVars)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Interface("variables", r.parsedConfig.Variables).
|
|
||||||
Err(err).Msg("parse step variables failed")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return parsedVariables, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateSessionVariables updates session variables with given variables.
|
|
||||||
// this is used for data driven
|
|
||||||
func (r *SessionRunner) updateSessionVariables(parameters map[string]interface{}) {
|
|
||||||
if len(parameters) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info().Interface("parameters", parameters).Msg("update session variables")
|
|
||||||
for k, v := range parameters {
|
|
||||||
r.sessionVariables[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *SessionRunner) GetSummary() (*TestCaseSummary, error) {
|
|
||||||
caseSummary := r.summary
|
|
||||||
caseSummary.Name = r.parsedConfig.Name
|
|
||||||
caseSummary.Time.StartAt = r.startTime
|
|
||||||
caseSummary.Time.Duration = time.Since(r.startTime).Seconds()
|
|
||||||
exportVars := make(map[string]interface{})
|
|
||||||
for _, value := range r.parsedConfig.Export {
|
|
||||||
exportVars[value] = r.sessionVariables[value]
|
|
||||||
}
|
|
||||||
caseSummary.InOut.ExportVars = exportVars
|
|
||||||
caseSummary.InOut.ConfigVars = r.parsedConfig.Variables
|
|
||||||
|
|
||||||
for uuid, client := range r.hrpRunner.uiClients {
|
|
||||||
// add WDA/UIA logs to summary
|
|
||||||
log, err := client.Driver.StopCaptureLog()
|
|
||||||
if err != nil {
|
|
||||||
return caseSummary, err
|
|
||||||
}
|
|
||||||
logs := map[string]interface{}{
|
|
||||||
"uuid": uuid,
|
|
||||||
"content": log,
|
|
||||||
}
|
|
||||||
|
|
||||||
// stop performance monitor
|
|
||||||
logs["performance"] = client.GetPerfData()
|
|
||||||
|
|
||||||
caseSummary.Logs = append(caseSummary.Logs, logs)
|
|
||||||
}
|
|
||||||
|
|
||||||
return caseSummary, nil
|
|
||||||
}
|
|
||||||
@@ -554,7 +554,13 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
|
|||||||
ContentSize: 0,
|
ContentSize: 0,
|
||||||
}
|
}
|
||||||
screenshots := make([]string, 0)
|
screenshots := make([]string, 0)
|
||||||
stepVariables := step.Variables
|
|
||||||
|
// merge step variables with session variables
|
||||||
|
stepVariables, err := s.ParseStepVariables(step.Variables)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "parse step variables failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var osType string
|
var osType string
|
||||||
var mobileStep *MobileStep
|
var mobileStep *MobileStep
|
||||||
@@ -569,7 +575,7 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// init wda/uia driver
|
// init wda/uia driver
|
||||||
uiDriver, err := s.hrpRunner.initUIClient(mobileStep.Serial, osType)
|
uiDriver, err := s.caseRunner.hrpRunner.initUIClient(mobileStep.Serial, osType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -602,7 +608,7 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
|
|||||||
|
|
||||||
// run actions
|
// run actions
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
if action.Params, err = s.parser.Parse(action.Params, stepVariables); err != nil {
|
if action.Params, err = s.caseRunner.parser.Parse(action.Params, stepVariables); err != nil {
|
||||||
return stepResult, errors.Wrap(err, "parse action params failed")
|
return stepResult, errors.Wrap(err, "parse action params failed")
|
||||||
}
|
}
|
||||||
if err := uiDriver.DoAction(action); err != nil {
|
if err := uiDriver.DoAction(action); err != nil {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func (s *StepRendezvous) Run(r *SessionRunner) (*StepResult, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// pass current rendezvous if already released, activate rendezvous sequentially after spawn done
|
// pass current rendezvous if already released, activate rendezvous sequentially after spawn done
|
||||||
if rendezvous.isReleased() || !isPreRendezvousAllReleased(rendezvous, r.testCase.ToTCase()) || !rendezvous.isSpawnDone() {
|
if rendezvous.isReleased() || !isPreRendezvousAllReleased(rendezvous, r.caseRunner.testCase.ToTCase()) || !rendezvous.isSpawnDone() {
|
||||||
return stepResult, nil
|
return stepResult, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -291,7 +291,13 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
|
|||||||
Success: false,
|
Success: false,
|
||||||
ContentSize: 0,
|
ContentSize: 0,
|
||||||
}
|
}
|
||||||
stepVariables := step.Variables
|
|
||||||
|
// merge step variables with session variables
|
||||||
|
stepVariables, err := r.ParseStepVariables(step.Variables)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "parse step variables failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// update testcase summary
|
// update testcase summary
|
||||||
@@ -300,14 +306,14 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = prepareUpload(r.parser, step, stepVariables)
|
err = prepareUpload(r.caseRunner.parser, step, stepVariables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionData := newSessionData()
|
sessionData := newSessionData()
|
||||||
parser := r.parser
|
parser := r.caseRunner.parser
|
||||||
config := r.parsedConfig
|
config := r.caseRunner.parsedConfig
|
||||||
|
|
||||||
rb := newRequestBuilder(parser, config, step.Request)
|
rb := newRequestBuilder(parser, config, step.Request)
|
||||||
rb.req.Method = string(step.Request.Method)
|
rb.req.Method = string(step.Request.Method)
|
||||||
@@ -340,7 +346,7 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// log & print request
|
// log & print request
|
||||||
if r.LogOn() {
|
if r.caseRunner.hrpRunner.requestsLogOn {
|
||||||
if err := printRequest(rb.req); err != nil {
|
if err := printRequest(rb.req); err != nil {
|
||||||
return stepResult, err
|
return stepResult, err
|
||||||
}
|
}
|
||||||
@@ -348,7 +354,7 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
|
|||||||
|
|
||||||
// stat HTTP request
|
// stat HTTP request
|
||||||
var httpStat httpstat.Stat
|
var httpStat httpstat.Stat
|
||||||
if r.HTTPStatOn() {
|
if r.caseRunner.hrpRunner.httpStatOn {
|
||||||
ctx := httpstat.WithHTTPStat(rb.req, &httpStat)
|
ctx := httpstat.WithHTTPStat(rb.req, &httpStat)
|
||||||
rb.req = rb.req.WithContext(ctx)
|
rb.req = rb.req.WithContext(ctx)
|
||||||
}
|
}
|
||||||
@@ -356,9 +362,9 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
|
|||||||
// select HTTP client
|
// select HTTP client
|
||||||
var client *http.Client
|
var client *http.Client
|
||||||
if step.Request.HTTP2 {
|
if step.Request.HTTP2 {
|
||||||
client = r.hrpRunner.http2Client
|
client = r.caseRunner.hrpRunner.http2Client
|
||||||
} else {
|
} else {
|
||||||
client = r.hrpRunner.httpClient
|
client = r.caseRunner.hrpRunner.httpClient
|
||||||
}
|
}
|
||||||
|
|
||||||
// set step timeout
|
// set step timeout
|
||||||
@@ -384,21 +390,21 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// log & print response
|
// log & print response
|
||||||
if r.LogOn() {
|
if r.caseRunner.hrpRunner.requestsLogOn {
|
||||||
if err := printResponse(resp); err != nil {
|
if err := printResponse(resp); err != nil {
|
||||||
return stepResult, err
|
return stepResult, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// new response object
|
// new response object
|
||||||
respObj, err := newHttpResponseObject(r.hrpRunner.t, parser, resp)
|
respObj, err := newHttpResponseObject(r.caseRunner.hrpRunner.t, parser, resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, "init ResponseObject error")
|
err = errors.Wrap(err, "init ResponseObject error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stepResult.Elapsed = time.Since(start).Milliseconds()
|
stepResult.Elapsed = time.Since(start).Milliseconds()
|
||||||
if r.HTTPStatOn() {
|
if r.caseRunner.hrpRunner.httpStatOn {
|
||||||
// resp.Body has been ReadAll
|
// resp.Body has been ReadAll
|
||||||
httpStat.Finish()
|
httpStat.Finish()
|
||||||
stepResult.HttpStat = httpStat.Durations()
|
stepResult.HttpStat = httpStat.Durations()
|
||||||
|
|||||||
@@ -77,29 +77,13 @@ func TestRunRequestPostDataToStruct(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunRequestRun(t *testing.T) {
|
|
||||||
testcase := &TestCase{
|
|
||||||
Config: NewConfig("test").SetBaseURL("https://postman-echo.com"),
|
|
||||||
TestSteps: []IStep{stepGET, stepPOSTData},
|
|
||||||
}
|
|
||||||
runner := NewRunner(t).SetRequestsLogOn()
|
|
||||||
sessionRunner, _ := runner.NewSessionRunner(testcase)
|
|
||||||
|
|
||||||
if _, err := stepGET.Run(sessionRunner); err != nil {
|
|
||||||
t.Fatalf("stepGET.Run() error: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := stepPOSTData.Run(sessionRunner); err != nil {
|
|
||||||
t.Fatalf("stepPOSTData.Run() error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunRequestStatOn(t *testing.T) {
|
func TestRunRequestStatOn(t *testing.T) {
|
||||||
testcase := &TestCase{
|
testcase := &TestCase{
|
||||||
Config: NewConfig("test").SetBaseURL("https://postman-echo.com"),
|
Config: NewConfig("test").SetBaseURL("https://postman-echo.com"),
|
||||||
TestSteps: []IStep{stepGET, stepPOSTData},
|
TestSteps: []IStep{stepGET, stepPOSTData},
|
||||||
}
|
}
|
||||||
runner := NewRunner(t).SetHTTPStatOn()
|
caseRunner, _ := NewRunner(t).SetHTTPStatOn().NewCaseRunner(testcase)
|
||||||
sessionRunner, _ := runner.NewSessionRunner(testcase)
|
sessionRunner := caseRunner.NewSession()
|
||||||
if err := sessionRunner.Start(nil); err != nil {
|
if err := sessionRunner.Start(nil); err != nil {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,13 @@ func (s *StepTestCaseWithOptionalArgs) Run(r *SessionRunner) (stepResult *StepRe
|
|||||||
StepType: stepTypeTestCase,
|
StepType: stepTypeTestCase,
|
||||||
Success: false,
|
Success: false,
|
||||||
}
|
}
|
||||||
stepVariables := s.step.Variables
|
|
||||||
|
// merge step variables with session variables
|
||||||
|
stepVariables, err := r.ParseStepVariables(s.step.Variables)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "parse step variables failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// update testcase summary
|
// update testcase summary
|
||||||
@@ -77,11 +83,12 @@ func (s *StepTestCaseWithOptionalArgs) Run(r *SessionRunner) (stepResult *StepRe
|
|||||||
// merge & override extractors
|
// merge & override extractors
|
||||||
copiedTestCase.Config.Export = mergeSlices(s.step.Export, copiedTestCase.Config.Export)
|
copiedTestCase.Config.Export = mergeSlices(s.step.Export, copiedTestCase.Config.Export)
|
||||||
|
|
||||||
sessionRunner, err := r.hrpRunner.NewSessionRunner(copiedTestCase)
|
caseRunner, err := r.caseRunner.hrpRunner.NewCaseRunner(copiedTestCase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("create session runner failed")
|
log.Error().Err(err).Msg("create case runner failed")
|
||||||
return stepResult, err
|
return stepResult, err
|
||||||
}
|
}
|
||||||
|
sessionRunner := caseRunner.NewSession()
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
// run referenced testcase with step variables
|
// run referenced testcase with step variables
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func (s *StepThinkTime) Run(r *SessionRunner) (*StepResult, error) {
|
|||||||
Success: true,
|
Success: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := r.parsedConfig.ThinkTimeSetting
|
cfg := r.caseRunner.parsedConfig.ThinkTimeSetting
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
cfg = &ThinkTimeConfig{thinkTimeDefault, nil, 0}
|
cfg = &ThinkTimeConfig{thinkTimeDefault, nil, 0}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -231,20 +231,19 @@ type WebSocketAction struct {
|
|||||||
Timeout int64 `json:"timeout,omitempty" yaml:"timeout,omitempty"`
|
Timeout int64 `json:"timeout,omitempty" yaml:"timeout,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func initWebSocket(testcase *TestCase) {
|
func (w *WebSocketAction) GetTimeout() int64 {
|
||||||
for _, step := range testcase.TestSteps {
|
if w.Timeout <= 0 {
|
||||||
if step.Struct().WebSocket == nil {
|
return defaultTimeout
|
||||||
continue
|
|
||||||
}
|
|
||||||
// init websocket action parameters
|
|
||||||
if step.Struct().WebSocket.Timeout <= 0 {
|
|
||||||
step.Struct().WebSocket.Timeout = defaultTimeout
|
|
||||||
}
|
|
||||||
// close status code range: [1000, 4999]. ref: https://datatracker.ietf.org/doc/html/rfc6455#section-11.7
|
|
||||||
if step.Struct().WebSocket.CloseStatusCode < 1000 || step.Struct().WebSocket.CloseStatusCode > 4999 {
|
|
||||||
step.Struct().WebSocket.CloseStatusCode = defaultCloseStatus
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return w.Timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WebSocketAction) GetCloseStatusCode() int64 {
|
||||||
|
// close status code range: [1000, 4999]. ref: https://datatracker.ietf.org/doc/html/rfc6455#section-11.7
|
||||||
|
if w.CloseStatusCode < 1000 || w.CloseStatusCode > 4999 {
|
||||||
|
return defaultCloseStatus
|
||||||
|
}
|
||||||
|
return w.CloseStatusCode
|
||||||
}
|
}
|
||||||
|
|
||||||
func runStepWebSocket(r *SessionRunner, step *TStep) (stepResult *StepResult, err error) {
|
func runStepWebSocket(r *SessionRunner, step *TStep) (stepResult *StepResult, err error) {
|
||||||
@@ -254,7 +253,13 @@ func runStepWebSocket(r *SessionRunner, step *TStep) (stepResult *StepResult, er
|
|||||||
Success: false,
|
Success: false,
|
||||||
ContentSize: 0,
|
ContentSize: 0,
|
||||||
}
|
}
|
||||||
stepVariables := step.Variables
|
|
||||||
|
// merge step variables with session variables
|
||||||
|
stepVariables, err := r.ParseStepVariables(step.Variables)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "parse step variables failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// update testcase summary
|
// update testcase summary
|
||||||
@@ -264,8 +269,8 @@ func runStepWebSocket(r *SessionRunner, step *TStep) (stepResult *StepResult, er
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
sessionData := newSessionData()
|
sessionData := newSessionData()
|
||||||
parser := r.parser
|
parser := r.caseRunner.parser
|
||||||
config := r.parsedConfig
|
config := r.caseRunner.parsedConfig
|
||||||
|
|
||||||
dummyReq := &Request{
|
dummyReq := &Request{
|
||||||
URL: step.WebSocket.URL,
|
URL: step.WebSocket.URL,
|
||||||
@@ -302,12 +307,12 @@ func runStepWebSocket(r *SessionRunner, step *TStep) (stepResult *StepResult, er
|
|||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
// do websocket action
|
// do websocket action
|
||||||
if r.LogOn() {
|
if r.caseRunner.hrpRunner.requestsLogOn {
|
||||||
fmt.Printf("-------------------- websocket action: %v --------------------\n", step.WebSocket.Type.toString())
|
fmt.Printf("-------------------- websocket action: %v --------------------\n", step.WebSocket.Type.toString())
|
||||||
}
|
}
|
||||||
switch step.WebSocket.Type {
|
switch step.WebSocket.Type {
|
||||||
case wsOpen:
|
case wsOpen:
|
||||||
log.Info().Int64("timeout(ms)", step.WebSocket.Timeout).Str("url", parsedURL).Msg("open websocket connection")
|
log.Info().Int64("timeout(ms)", step.WebSocket.GetTimeout()).Str("url", parsedURL).Msg("open websocket connection")
|
||||||
// use the current websocket connection if existed
|
// use the current websocket connection if existed
|
||||||
if r.wsConnMap[parsedURL] != nil {
|
if r.wsConnMap[parsedURL] != nil {
|
||||||
break
|
break
|
||||||
@@ -317,12 +322,12 @@ func runStepWebSocket(r *SessionRunner, step *TStep) (stepResult *StepResult, er
|
|||||||
return stepResult, errors.Wrap(err, "open connection failed")
|
return stepResult, errors.Wrap(err, "open connection failed")
|
||||||
}
|
}
|
||||||
case wsPing:
|
case wsPing:
|
||||||
log.Info().Int64("timeout(ms)", step.WebSocket.Timeout).Str("url", parsedURL).Msg("send ping and expect pong")
|
log.Info().Int64("timeout(ms)", step.WebSocket.GetTimeout()).Str("url", parsedURL).Msg("send ping and expect pong")
|
||||||
err = writeWebSocket(parsedURL, r, step, stepVariables)
|
err = writeWebSocket(parsedURL, r, step, stepVariables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return stepResult, errors.Wrap(err, "send ping message failed")
|
return stepResult, errors.Wrap(err, "send ping message failed")
|
||||||
}
|
}
|
||||||
timer := time.NewTimer(time.Duration(step.WebSocket.Timeout) * time.Millisecond)
|
timer := time.NewTimer(time.Duration(step.WebSocket.GetTimeout()) * time.Millisecond)
|
||||||
// asynchronous receiving pong message with timeout
|
// asynchronous receiving pong message with timeout
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
@@ -335,7 +340,7 @@ func runStepWebSocket(r *SessionRunner, step *TStep) (stepResult *StepResult, er
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
case wsWriteAndRead:
|
case wsWriteAndRead:
|
||||||
log.Info().Int64("timeout(ms)", step.WebSocket.Timeout).Str("url", parsedURL).Msg("write a message and read response")
|
log.Info().Int64("timeout(ms)", step.WebSocket.GetTimeout()).Str("url", parsedURL).Msg("write a message and read response")
|
||||||
err = writeWebSocket(parsedURL, r, step, stepVariables)
|
err = writeWebSocket(parsedURL, r, step, stepVariables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return stepResult, errors.Wrap(err, "write message failed")
|
return stepResult, errors.Wrap(err, "write message failed")
|
||||||
@@ -345,7 +350,7 @@ func runStepWebSocket(r *SessionRunner, step *TStep) (stepResult *StepResult, er
|
|||||||
return stepResult, errors.Wrap(err, "read message failed")
|
return stepResult, errors.Wrap(err, "read message failed")
|
||||||
}
|
}
|
||||||
case wsRead:
|
case wsRead:
|
||||||
log.Info().Int64("timeout(ms)", step.WebSocket.Timeout).Str("url", parsedURL).Msg("read only")
|
log.Info().Int64("timeout(ms)", step.WebSocket.GetTimeout()).Str("url", parsedURL).Msg("read only")
|
||||||
resp, err = readMessageWithTimeout(parsedURL, r, step)
|
resp, err = readMessageWithTimeout(parsedURL, r, step)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return stepResult, errors.Wrap(err, "read message failed")
|
return stepResult, errors.Wrap(err, "read message failed")
|
||||||
@@ -357,7 +362,7 @@ func runStepWebSocket(r *SessionRunner, step *TStep) (stepResult *StepResult, er
|
|||||||
return stepResult, errors.Wrap(err, "write message failed")
|
return stepResult, errors.Wrap(err, "write message failed")
|
||||||
}
|
}
|
||||||
case wsClose:
|
case wsClose:
|
||||||
log.Info().Int64("timeout(ms)", step.WebSocket.Timeout).Str("url", parsedURL).Msg("close webSocket connection")
|
log.Info().Int64("timeout(ms)", step.WebSocket.GetTimeout()).Str("url", parsedURL).Msg("close webSocket connection")
|
||||||
resp, err = closeWithTimeout(parsedURL, r, step, stepVariables)
|
resp, err = closeWithTimeout(parsedURL, r, step, stepVariables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return stepResult, errors.Wrap(err, "close connection failed")
|
return stepResult, errors.Wrap(err, "close connection failed")
|
||||||
@@ -365,7 +370,7 @@ func runStepWebSocket(r *SessionRunner, step *TStep) (stepResult *StepResult, er
|
|||||||
default:
|
default:
|
||||||
return stepResult, errors.Errorf("unexpected websocket frame type: %v", step.WebSocket.Type)
|
return stepResult, errors.Errorf("unexpected websocket frame type: %v", step.WebSocket.Type)
|
||||||
}
|
}
|
||||||
if r.LogOn() {
|
if r.caseRunner.hrpRunner.requestsLogOn {
|
||||||
err = printWebSocketResponse(resp)
|
err = printWebSocketResponse(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return stepResult, errors.Wrap(err, "print response failed")
|
return stepResult, errors.Wrap(err, "print response failed")
|
||||||
@@ -373,7 +378,7 @@ func runStepWebSocket(r *SessionRunner, step *TStep) (stepResult *StepResult, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
stepResult.Elapsed = time.Since(start).Milliseconds()
|
stepResult.Elapsed = time.Since(start).Milliseconds()
|
||||||
respObj, err := getResponseObject(r.hrpRunner.t, r.parser, resp)
|
respObj, err := getResponseObject(r.caseRunner.hrpRunner.t, r.caseRunner.parser, resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, "get response object error")
|
err = errors.Wrap(err, "get response object error")
|
||||||
return
|
return
|
||||||
@@ -455,7 +460,7 @@ func openWithTimeout(urlStr string, requestHeader http.Header, r *SessionRunner,
|
|||||||
openResponseChan := make(chan *http.Response)
|
openResponseChan := make(chan *http.Response)
|
||||||
errorChan := make(chan error)
|
errorChan := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
conn, resp, err := r.hrpRunner.wsDialer.Dial(urlStr, requestHeader)
|
conn, resp, err := r.caseRunner.hrpRunner.wsDialer.Dial(urlStr, requestHeader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorChan <- errors.Wrap(err, "dial tcp failed")
|
errorChan <- errors.Wrap(err, "dial tcp failed")
|
||||||
return
|
return
|
||||||
@@ -481,7 +486,7 @@ func openWithTimeout(urlStr string, requestHeader http.Header, r *SessionRunner,
|
|||||||
openResponseChan <- resp
|
openResponseChan <- resp
|
||||||
}()
|
}()
|
||||||
|
|
||||||
timer := time.NewTimer(time.Duration(step.WebSocket.Timeout) * time.Millisecond)
|
timer := time.NewTimer(time.Duration(step.WebSocket.GetTimeout()) * time.Millisecond)
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
timer.Stop()
|
timer.Stop()
|
||||||
@@ -511,7 +516,7 @@ func readMessageWithTimeout(urlString string, r *SessionRunner, step *TStep) (*w
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
timer := time.NewTimer(time.Duration(step.WebSocket.Timeout) * time.Millisecond)
|
timer := time.NewTimer(time.Duration(step.WebSocket.GetTimeout()) * time.Millisecond)
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
timer.Stop()
|
timer.Stop()
|
||||||
@@ -530,7 +535,7 @@ func writeWebSocket(urlString string, r *SessionRunner, step *TStep, stepVariabl
|
|||||||
}
|
}
|
||||||
// check priority: text message > binary message
|
// check priority: text message > binary message
|
||||||
if step.WebSocket.TextMessage != nil {
|
if step.WebSocket.TextMessage != nil {
|
||||||
parsedMessage, parseErr := r.parser.Parse(step.WebSocket.TextMessage, stepVariables)
|
parsedMessage, parseErr := r.caseRunner.parser.Parse(step.WebSocket.TextMessage, stepVariables)
|
||||||
if parseErr != nil {
|
if parseErr != nil {
|
||||||
return parseErr
|
return parseErr
|
||||||
}
|
}
|
||||||
@@ -539,7 +544,7 @@ func writeWebSocket(urlString string, r *SessionRunner, step *TStep, stepVariabl
|
|||||||
return writeErr
|
return writeErr
|
||||||
}
|
}
|
||||||
} else if step.WebSocket.BinaryMessage != nil {
|
} else if step.WebSocket.BinaryMessage != nil {
|
||||||
parsedMessage, parseErr := r.parser.Parse(step.WebSocket.BinaryMessage, stepVariables)
|
parsedMessage, parseErr := r.caseRunner.parser.Parse(step.WebSocket.BinaryMessage, stepVariables)
|
||||||
if parseErr != nil {
|
if parseErr != nil {
|
||||||
return parseErr
|
return parseErr
|
||||||
}
|
}
|
||||||
@@ -582,7 +587,7 @@ func writeWithAction(c *websocket.Conn, step *TStep, messageType int, message []
|
|||||||
case wsPing:
|
case wsPing:
|
||||||
return c.WriteControl(websocket.PingMessage, message, time.Now().Add(defaultWriteWait))
|
return c.WriteControl(websocket.PingMessage, message, time.Now().Add(defaultWriteWait))
|
||||||
case wsClose:
|
case wsClose:
|
||||||
closeMessage := websocket.FormatCloseMessage(int(step.WebSocket.CloseStatusCode), string(message))
|
closeMessage := websocket.FormatCloseMessage(int(step.WebSocket.GetCloseStatusCode()), string(message))
|
||||||
return c.WriteControl(websocket.CloseMessage, closeMessage, time.Now().Add(defaultWriteWait))
|
return c.WriteControl(websocket.CloseMessage, closeMessage, time.Now().Add(defaultWriteWait))
|
||||||
default:
|
default:
|
||||||
return c.WriteMessage(messageType, message)
|
return c.WriteMessage(messageType, message)
|
||||||
@@ -622,7 +627,7 @@ func closeWithTimeout(urlString string, r *SessionRunner, step *TStep, stepVaria
|
|||||||
// r.wsConn.Close() will be called at the end of current session, so no need to Close here
|
// r.wsConn.Close() will be called at the end of current session, so no need to Close here
|
||||||
log.Info().Str("msg", readErr.Error()).Msg("connection closed")
|
log.Info().Str("msg", readErr.Error()).Msg("connection closed")
|
||||||
}()
|
}()
|
||||||
timer := time.NewTimer(time.Duration(step.WebSocket.Timeout) * time.Millisecond)
|
timer := time.NewTimer(time.Duration(step.WebSocket.GetTimeout()) * time.Millisecond)
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
timer.Stop()
|
timer.Stop()
|
||||||
|
|||||||
Reference in New Issue
Block a user