feat: set step loops with expression variable

This commit is contained in:
lilong.129
2025-05-14 14:49:05 +08:00
parent d145784910
commit c71ac5c3cd
5 changed files with 157 additions and 12 deletions

View File

@@ -1 +1 @@
v5.0.0-beta-2505141436
v5.0.0-beta-2505141501

View File

@@ -11,6 +11,7 @@ import (
"os"
"os/signal"
"reflect"
"strconv"
"strings"
"syscall"
"testing"
@@ -707,14 +708,9 @@ func (r *SessionRunner) RunStep(step IStep) (stepResult *StepResult, err error)
log.Info().Str("step", stepName).Str("type", stepType).Msg("run step start")
// run times of step
loopTimes := step.Config().Loops
if loopTimes < 0 {
log.Warn().Int("loops", loopTimes).Msg("loop times should be positive, set to 1")
loopTimes = 1
} else if loopTimes == 0 {
loopTimes = 1
} else if loopTimes > 1 {
log.Info().Int("loops", loopTimes).Msg("run step with specified loop times")
loopTimes, err := r.getLoopTimes(step)
if err != nil {
return nil, errors.Wrap(err, "failed to get loop times")
}
// run step with specified loop times
@@ -840,3 +836,39 @@ func (r *SessionRunner) GetSessionVariables() map[string]interface{} {
func (r *SessionRunner) GetTransactions() map[string]map[TransactionType]time.Time {
return r.transactions
}
func (r *SessionRunner) getLoopTimes(step IStep) (int, error) {
loops := step.Config().Loops
if loops == nil {
// default run once
return 1, nil
}
loopTimes, err := loops.Value()
if err != nil {
parsed, err := r.caseRunner.parser.ParseString(
*loops.StringValue, step.Config().Variables)
if err != nil {
return 0, errors.Wrap(err, "failed to parse loop times")
}
switch v := parsed.(type) {
case int:
loopTimes = v
case string:
n, err := strconv.Atoi(v)
if err != nil {
return 0, errors.Wrap(err, "failed to parse loop times")
}
loopTimes = n
}
}
if loopTimes < 0 {
return 0, fmt.Errorf("loop times should be positive, got %d", loopTimes)
} else if loopTimes == 0 {
loopTimes = 1
} else if loopTimes > 1 {
log.Info().Int("loops", loopTimes).Msg("set multiple loop times")
}
return loopTimes, nil
}

View File

@@ -1,6 +1,9 @@
package hrp
import "github.com/httprunner/httprunner/v5/uixt"
import (
"github.com/httprunner/httprunner/v5/uixt"
"github.com/httprunner/httprunner/v5/uixt/types"
)
type StepType string
@@ -31,7 +34,7 @@ type StepConfig struct {
Extract map[string]string `json:"extract,omitempty" yaml:"extract,omitempty"`
Validators []interface{} `json:"validate,omitempty" yaml:"validate,omitempty"`
StepExport []string `json:"export,omitempty" yaml:"export,omitempty"`
Loops int `json:"loops,omitempty" yaml:"loops,omitempty"`
Loops *types.IntOrString `json:"loops,omitempty" yaml:"loops,omitempty"`
IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"`
}

View File

@@ -25,6 +25,7 @@ import (
"github.com/httprunner/httprunner/v5/internal/httpstat"
"github.com/httprunner/httprunner/v5/internal/json"
"github.com/httprunner/httprunner/v5/uixt/option"
"github.com/httprunner/httprunner/v5/uixt/types"
)
type HTTPMethod string
@@ -559,7 +560,9 @@ func (s *StepRequest) HTTP2() *StepRequest {
// Loop specify running times for the current step
func (s *StepRequest) Loop(times int) *StepRequest {
s.Loops = times
s.Loops = &types.IntOrString{
IntValue: &times,
}
return s
}

107
uixt/types/field.go Normal file
View File

@@ -0,0 +1,107 @@
package types
import (
"encoding/json"
"fmt"
"strconv"
)
// IntOrString supports int or string
type IntOrString struct {
IntValue *int // e.g 513
StringValue *string // e.g "513", "$var"
}
// Value returns the int value, converting from string if necessary
func (ios *IntOrString) Value() (int, error) {
if ios == nil {
return 0, nil
}
if ios.IntValue != nil {
return *ios.IntValue, nil
}
if ios.StringValue != nil {
if *ios.StringValue == "" {
return 0, nil
}
n, err := strconv.Atoi(*ios.StringValue)
if err != nil {
// variable expression, e.g. "$var"
return 0, err
}
return n, nil
}
// IntValue and StringValue are both nil
return 0, nil
}
// UnmarshalJSON implements custom JSON unmarshalling for IntOrString
func (ios *IntOrString) UnmarshalJSON(data []byte) error {
// Try to unmarshal as int
var i int
if err := json.Unmarshal(data, &i); err == nil {
ios.IntValue = &i
ios.StringValue = nil
return nil
}
// Try to unmarshal as string
var s string
if err := json.Unmarshal(data, &s); err == nil {
ios.StringValue = &s
ios.IntValue = nil
return nil
}
return fmt.Errorf("invalid IntOrString data: %s", string(data))
}
// FloatOrString supports float64 or string
type FloatOrString struct {
FloatValue *float64 // e.g 5.13
StringValue *string // e.g "5.13", "$var"
}
// Value returns the float value, converting from string if necessary
func (ios *FloatOrString) Value() (float64, error) {
if ios == nil {
return 0, nil
}
if ios.FloatValue != nil {
return *ios.FloatValue, nil
}
if ios.StringValue != nil {
if *ios.StringValue == "" {
return 0, nil
}
n, err := strconv.ParseFloat(*ios.StringValue, 64)
if err != nil {
// variable expression, e.g. "$var"
return 0, err
}
return n, nil
}
// IntValue and StringValue are both nil
return 0, nil
}
// UnmarshalJSON implements custom JSON unmarshalling for IntOrString
func (ios *FloatOrString) UnmarshalJSON(data []byte) error {
// Try to unmarshal as float
var f float64
if err := json.Unmarshal(data, &f); err == nil {
ios.FloatValue = &f
ios.StringValue = nil
return nil
}
// Try to unmarshal as string
var s string
if err := json.Unmarshal(data, &s); err == nil {
ios.StringValue = &s
ios.FloatValue = nil
return nil
}
return fmt.Errorf("invalid FloatOrString data: %s", string(data))
}