mirror of
https://github.com/httprunner/httprunner.git
synced 2026-07-12 16:01:27 +08:00
feat: record execution data for report #25
This commit is contained in:
10
boomer.go
10
boomer.go
@@ -122,7 +122,7 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase) *boomer.Task {
|
|||||||
// step failed
|
// step failed
|
||||||
var elapsed int64
|
var elapsed int64
|
||||||
if stepData != nil {
|
if stepData != nil {
|
||||||
elapsed = stepData.elapsed
|
elapsed = stepData.Elapsed
|
||||||
}
|
}
|
||||||
b.RecordFailure(step.Type(), step.Name(), elapsed, err.Error())
|
b.RecordFailure(step.Type(), step.Name(), elapsed, err.Error())
|
||||||
|
|
||||||
@@ -139,14 +139,14 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase) *boomer.Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// step success
|
// step success
|
||||||
if stepData.stepType == stepTypeTransaction {
|
if stepData.StepType == stepTypeTransaction {
|
||||||
// transaction
|
// transaction
|
||||||
// FIXME: support nested transactions
|
// FIXME: support nested transactions
|
||||||
if step.ToStruct().Transaction.Type == transactionEnd { // only record when transaction ends
|
if step.ToStruct().Transaction.Type == transactionEnd { // only record when transaction ends
|
||||||
b.RecordTransaction(stepData.name, transactionSuccess, stepData.elapsed, 0)
|
b.RecordTransaction(stepData.Name, transactionSuccess, stepData.Elapsed, 0)
|
||||||
transactionSuccess = true // reset flag for next transaction
|
transactionSuccess = true // reset flag for next transaction
|
||||||
}
|
}
|
||||||
} else if stepData.stepType == stepTypeRendezvous {
|
} else if stepData.StepType == stepTypeRendezvous {
|
||||||
// rendezvous
|
// rendezvous
|
||||||
// TODO: implement rendezvous in boomer
|
// TODO: implement rendezvous in boomer
|
||||||
rendezvous := step.ToStruct().Rendezvous
|
rendezvous := step.ToStruct().Rendezvous
|
||||||
@@ -155,7 +155,7 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase) *boomer.Task {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// request or testcase step
|
// request or testcase step
|
||||||
b.RecordSuccess(step.Type(), step.Name(), stepData.elapsed, stepData.contentSize)
|
b.RecordSuccess(step.Type(), step.Name(), stepData.Elapsed, stepData.ContentSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
endTime := time.Now()
|
endTime := time.Now()
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ var runCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
runner := hrp.NewRunner(nil).
|
runner := hrp.NewRunner(nil).
|
||||||
SetDebug(!silentFlag).
|
SetDebug(!silentFlag).
|
||||||
SetFailfast(!continueOnFailure)
|
SetFailfast(!continueOnFailure).
|
||||||
|
SetSaveTests(saveTests)
|
||||||
if proxyUrl != "" {
|
if proxyUrl != "" {
|
||||||
runner.SetProxyUrl(proxyUrl)
|
runner.SetProxyUrl(proxyUrl)
|
||||||
}
|
}
|
||||||
@@ -42,6 +43,7 @@ var (
|
|||||||
continueOnFailure bool
|
continueOnFailure bool
|
||||||
silentFlag bool
|
silentFlag bool
|
||||||
proxyUrl string
|
proxyUrl string
|
||||||
|
saveTests bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -49,5 +51,6 @@ func init() {
|
|||||||
runCmd.Flags().BoolVar(&continueOnFailure, "continue-on-failure", false, "continue running next step when failure occurs")
|
runCmd.Flags().BoolVar(&continueOnFailure, "continue-on-failure", false, "continue running next step when failure occurs")
|
||||||
runCmd.Flags().BoolVarP(&silentFlag, "silent", "s", false, "disable logging request & response details")
|
runCmd.Flags().BoolVarP(&silentFlag, "silent", "s", false, "disable logging request & response details")
|
||||||
runCmd.Flags().StringVarP(&proxyUrl, "proxy-url", "p", "", "set proxy url")
|
runCmd.Flags().StringVarP(&proxyUrl, "proxy-url", "p", "", "set proxy url")
|
||||||
|
runCmd.Flags().BoolVar(&saveTests, "save-tests", false, "save tests summary")
|
||||||
// runCmd.Flags().BoolP("gen-html-report", "r", false, "Generate HTML report")
|
// runCmd.Flags().BoolP("gen-html-report", "r", false, "Generate HTML report")
|
||||||
}
|
}
|
||||||
|
|||||||
43
convert.go
43
convert.go
@@ -11,49 +11,6 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (tc *TCase) Dump2JSON(path string) error {
|
|
||||||
path, err := filepath.Abs(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("convert absolute path failed")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Info().Str("path", path).Msg("dump testcase to json")
|
|
||||||
file, _ := json.MarshalIndent(tc, "", " ")
|
|
||||||
err = ioutil.WriteFile(path, file, 0644)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("dump json path failed")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tc *TCase) Dump2YAML(path string) error {
|
|
||||||
path, err := filepath.Abs(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("convert absolute path failed")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Info().Str("path", path).Msg("dump testcase to yaml")
|
|
||||||
|
|
||||||
// init yaml encoder
|
|
||||||
buffer := new(bytes.Buffer)
|
|
||||||
encoder := yaml.NewEncoder(buffer)
|
|
||||||
encoder.SetIndent(4)
|
|
||||||
|
|
||||||
// encode
|
|
||||||
err = encoder.Encode(tc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ioutil.WriteFile(path, buffer.Bytes(), 0644)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("dump yaml path failed")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadFromJSON(path string) (*TCase, error) {
|
func loadFromJSON(path string) (*TCase, error) {
|
||||||
path, err := filepath.Abs(path)
|
path, err := filepath.Abs(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/httprunner/hrp"
|
"github.com/httprunner/hrp"
|
||||||
|
"github.com/httprunner/hrp/internal/builtin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var rendezvousTestcase = &hrp.TestCase{
|
var rendezvousTestcase = &hrp.TestCase{
|
||||||
@@ -58,7 +59,7 @@ func TestRendezvousDump2JSON(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ToTCase error: %v", err)
|
t.Fatalf("ToTCase error: %v", err)
|
||||||
}
|
}
|
||||||
err = tCase.Dump2JSON("rendezvous_test.json")
|
err = builtin.Dump2JSON(tCase, "rendezvous_test.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("dump to json error: %v", err)
|
t.Fatalf("dump to json error: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ var Assertions = map[string]func(t assert.TestingT, expected interface{}, actual
|
|||||||
"greater_or_equals": assert.GreaterOrEqual,
|
"greater_or_equals": assert.GreaterOrEqual,
|
||||||
"less_or_equals": assert.LessOrEqual,
|
"less_or_equals": assert.LessOrEqual,
|
||||||
"not_equal": assert.NotEqual,
|
"not_equal": assert.NotEqual,
|
||||||
"contained_by": assert.Contains,
|
"contained_by": assert.Contains,
|
||||||
"regex_match": assert.Regexp,
|
"regex_match": assert.Regexp,
|
||||||
"type_match": assert.IsType,
|
"type_match": assert.IsType,
|
||||||
// custom assertions
|
// custom assertions
|
||||||
@@ -28,7 +28,7 @@ var Assertions = map[string]func(t assert.TestingT, expected interface{}, actual
|
|||||||
"length_less_or_equals": LessOrEqualsLength,
|
"length_less_or_equals": LessOrEqualsLength,
|
||||||
"length_greater_than": GreaterThanLength,
|
"length_greater_than": GreaterThanLength,
|
||||||
"length_greater_or_equals": GreaterOrEqualsLength,
|
"length_greater_or_equals": GreaterOrEqualsLength,
|
||||||
"contains": Contains,
|
"contains": Contains,
|
||||||
"string_equals": EqualString,
|
"string_equals": EqualString,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package builtin
|
package builtin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
@@ -11,6 +13,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,7 +23,7 @@ var Functions = map[string]interface{}{
|
|||||||
"sleep": sleep, // call with one argument
|
"sleep": sleep, // call with one argument
|
||||||
"gen_random_string": genRandomString, // call with one argument
|
"gen_random_string": genRandomString, // call with one argument
|
||||||
"max": math.Max, // call with two arguments
|
"max": math.Max, // call with two arguments
|
||||||
"md5": MD5, // call with one argument
|
"md5": MD5, // call with one argument
|
||||||
"parameterize": loadFromCSV,
|
"parameterize": loadFromCSV,
|
||||||
"P": loadFromCSV,
|
"P": loadFromCSV,
|
||||||
}
|
}
|
||||||
@@ -82,3 +86,46 @@ func loadFromCSV(path string) []map[string]interface{} {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Dump2JSON(data interface{}, path string) error {
|
||||||
|
path, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("convert absolute path failed")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info().Str("path", path).Msg("dump data to json")
|
||||||
|
file, _ := json.MarshalIndent(data, "", " ")
|
||||||
|
err = ioutil.WriteFile(path, file, 0644)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("dump json path failed")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Dump2YAML(data interface{}, path string) error {
|
||||||
|
path, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("convert absolute path failed")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info().Str("path", path).Msg("dump data to yaml")
|
||||||
|
|
||||||
|
// init yaml encoder
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
encoder := yaml.NewEncoder(buffer)
|
||||||
|
encoder.SetIndent(4)
|
||||||
|
|
||||||
|
// encode
|
||||||
|
err = encoder.Encode(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(path, buffer.Bytes(), 0644)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("dump yaml path failed")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/httprunner/hrp"
|
"github.com/httprunner/hrp"
|
||||||
|
"github.com/httprunner/hrp/internal/builtin"
|
||||||
"github.com/httprunner/hrp/internal/ga"
|
"github.com/httprunner/hrp/internal/ga"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@ func (h *har) GenJSON() (jsonPath string, err error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
jsonPath = h.genOutputPath(suffixJSON)
|
jsonPath = h.genOutputPath(suffixJSON)
|
||||||
err = tCase.Dump2JSON(jsonPath)
|
err = builtin.Dump2JSON(tCase, jsonPath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ func (h *har) GenYAML() (yamlPath string, err error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
yamlPath = h.genOutputPath(suffixYAML)
|
yamlPath = h.genOutputPath(suffixYAML)
|
||||||
err = tCase.Dump2YAML(yamlPath)
|
err = builtin.Dump2YAML(tCase, yamlPath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/httprunner/hrp"
|
"github.com/httprunner/hrp"
|
||||||
|
"github.com/httprunner/hrp/internal/builtin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -32,11 +33,11 @@ func removeHashicorpPlugin() {
|
|||||||
|
|
||||||
func TestGenDemoTestCase(t *testing.T) {
|
func TestGenDemoTestCase(t *testing.T) {
|
||||||
tCase, _ := demoTestCase.ToTCase()
|
tCase, _ := demoTestCase.ToTCase()
|
||||||
err := tCase.Dump2JSON(demoTestCaseJSONPath)
|
err := builtin.Dump2JSON(tCase, demoTestCaseJSONPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
err = tCase.Dump2YAML(demoTestCaseYAMLPath)
|
err = builtin.Dump2YAML(tCase, demoTestCaseYAMLPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/httprunner/hrp/internal/builtin"
|
||||||
"github.com/httprunner/hrp/internal/ga"
|
"github.com/httprunner/hrp/internal/ga"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
@@ -48,12 +49,12 @@ func CreateScaffold(projectName string) error {
|
|||||||
|
|
||||||
// create demo testcases
|
// create demo testcases
|
||||||
tCase, _ := demoTestCase.ToTCase()
|
tCase, _ := demoTestCase.ToTCase()
|
||||||
err := tCase.Dump2JSON(path.Join(projectName, "testcases", "demo.json"))
|
err := builtin.Dump2JSON(tCase, path.Join(projectName, "testcases", "demo.json"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("create demo.json testcase failed")
|
log.Error().Err(err).Msg("create demo.json testcase failed")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = tCase.Dump2YAML(path.Join(projectName, "testcases", "demo.yaml"))
|
err = builtin.Dump2YAML(tCase, path.Join(projectName, "testcases", "demo.yaml"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("create demo.yml testcase failed")
|
log.Error().Err(err).Msg("create demo.yml testcase failed")
|
||||||
return err
|
return err
|
||||||
|
|||||||
137
models.go
137
models.go
@@ -1,9 +1,13 @@
|
|||||||
package hrp
|
package hrp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/httprunner/hrp/internal/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -209,13 +213,132 @@ func (tc *TestCase) ToTCase() (*TCase, error) {
|
|||||||
return &tCase, nil
|
return &tCase, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type testCaseSummary struct{}
|
type testCaseStat struct {
|
||||||
|
Total int `json:"total" yaml:"total"`
|
||||||
|
Success int `json:"success" yaml:"success"`
|
||||||
|
Fail int `json:"fail" yaml:"fail"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type testStepStat struct {
|
||||||
|
Total int `json:"total" yaml:"total"`
|
||||||
|
Successes int `json:"successes" yaml:"successes"`
|
||||||
|
Failures int `json:"failures" yaml:"failures"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type stat struct {
|
||||||
|
TestCases testCaseStat `json:"testcases" yaml:"test_cases"`
|
||||||
|
TestSteps testStepStat `json:"teststeps" yaml:"test_steps"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type testCaseTime struct {
|
||||||
|
StartAt time.Time `json:"start_at,omitempty" yaml:"start_at,omitempty"`
|
||||||
|
Duration float64 `json:"duration,omitempty" yaml:"duration,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type platform struct {
|
||||||
|
HttprunnerVersion string `json:"httprunner_version" yaml:"httprunner_version"`
|
||||||
|
GoVersion string `json:"go_version" yaml:"go_version"`
|
||||||
|
Platform string `json:"platform" yaml:"platform"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// summary stores tests summary for current task execution, maybe include one or multiple testcases
|
||||||
|
type summary struct {
|
||||||
|
Success bool `json:"success" yaml:"success"`
|
||||||
|
Stat *stat `json:"stat" yaml:"stat"`
|
||||||
|
Time *testCaseTime `json:"time" yaml:"time"`
|
||||||
|
Platform *platform `json:"platform" yaml:"platform"`
|
||||||
|
Details []*testCaseSummary `json:"details" yaml:"details"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOutSummary() *summary {
|
||||||
|
platForm := &platform{
|
||||||
|
HttprunnerVersion: version.VERSION,
|
||||||
|
GoVersion: runtime.Version(),
|
||||||
|
Platform: fmt.Sprintf("%v-%v", runtime.GOOS, runtime.GOARCH),
|
||||||
|
}
|
||||||
|
return &summary{
|
||||||
|
Success: true,
|
||||||
|
Stat: &stat{},
|
||||||
|
Time: &testCaseTime{
|
||||||
|
StartAt: time.Now(),
|
||||||
|
},
|
||||||
|
Platform: platForm,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *summary) appendCaseSummary(caseSummary *testCaseSummary) {
|
||||||
|
s.Success = s.Success && caseSummary.Success
|
||||||
|
s.Stat.TestCases.Total += 1
|
||||||
|
s.Stat.TestSteps.Total += len(caseSummary.Records)
|
||||||
|
if caseSummary.Success {
|
||||||
|
s.Stat.TestCases.Success += 1
|
||||||
|
s.Stat.TestSteps.Successes += len(caseSummary.Records)
|
||||||
|
} else {
|
||||||
|
s.Stat.TestCases.Fail += 1
|
||||||
|
s.Stat.TestSteps.Successes += len(caseSummary.Records) - 1
|
||||||
|
s.Stat.TestSteps.Failures += 1
|
||||||
|
}
|
||||||
|
s.Details = append(s.Details, caseSummary)
|
||||||
|
s.Success = s.Success && caseSummary.Success
|
||||||
|
}
|
||||||
|
|
||||||
type stepData struct {
|
type stepData struct {
|
||||||
name string // step name
|
Name string `json:"name" yaml:"name"` // step name
|
||||||
stepType stepType // step type, testcase/request/transaction/rendezvous
|
StepType stepType `json:"step_type" yaml:"step_type"` // step type, testcase/request/transaction/rendezvous
|
||||||
success bool // step execution result
|
Success bool `json:"success" yaml:"success"` // step execution result
|
||||||
elapsed int64 // step execution time in millisecond(ms)
|
Elapsed int64 `json:"elapsed_ms" yaml:"elapsed_ms"` // step execution time in millisecond(ms)
|
||||||
contentSize int64 // response body length
|
Data interface{} `json:"data,omitempty" yaml:"data,omitempty"` // session data or slice of step data
|
||||||
exportVars map[string]interface{} // extract variables
|
ContentSize int64 `json:"content_size" yaml:"content_size"` // response body length
|
||||||
|
ExportVars map[string]interface{} `json:"export_vars,omitempty" yaml:"export_vars,omitempty"` // extract variables
|
||||||
|
}
|
||||||
|
|
||||||
|
type testCaseInOut struct {
|
||||||
|
ConfigVars map[string]interface{} `json:"config_vars" yaml:"config_vars"`
|
||||||
|
ExportVars map[string]interface{} `json:"export_vars" yaml:"export_vars"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// testCaseSummary stores tests summary for one testcase
|
||||||
|
type testCaseSummary struct {
|
||||||
|
Name string `json:"name" yaml:"name"`
|
||||||
|
Success bool `json:"success" yaml:"success"`
|
||||||
|
CaseId string `json:"case_id,omitempty" yaml:"case_id,omitempty"` //TODO
|
||||||
|
Time *testCaseTime `json:"time" yaml:"time"`
|
||||||
|
InOut *testCaseInOut `json:"in_out" yaml:"in_out"`
|
||||||
|
Log string `json:"log,omitempty" yaml:"log,omitempty"` //TODO
|
||||||
|
Records []*stepData `json:"records" yaml:"records"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type validationResult struct {
|
||||||
|
Validator
|
||||||
|
CheckValue interface{} `json:"check_value" yaml:"check_value"`
|
||||||
|
CheckResult string `json:"check_result" yaml:"check_result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type reqResps struct {
|
||||||
|
Request *Request `json:"request" yaml:"request"`
|
||||||
|
Response interface{} `json:"response" yaml:"response"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type address struct {
|
||||||
|
ClientIP string `json:"client_ip,omitempty" yaml:"client_ip,omitempty"`
|
||||||
|
ClientPort string `json:"client_port,omitempty" yaml:"client_port,omitempty"`
|
||||||
|
ServerIP string `json:"server_ip,omitempty" yaml:"server_ip,omitempty"`
|
||||||
|
ServerPort string `json:"server_port,omitempty" yaml:"server_port,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SessionData struct {
|
||||||
|
Success bool `json:"success" yaml:"success"`
|
||||||
|
ReqResps *reqResps `json:"req_resps" yaml:"req_resps"`
|
||||||
|
Address *address `json:"address,omitempty" yaml:"address,omitempty"` //TODO
|
||||||
|
Validators []*validationResult `json:"validators,omitempty" yaml:"validators,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSessionData() *SessionData {
|
||||||
|
reqResps := &reqResps{
|
||||||
|
Request: &Request{},
|
||||||
|
}
|
||||||
|
return &SessionData{
|
||||||
|
Success: false,
|
||||||
|
ReqResps: reqResps,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
response.go
16
response.go
@@ -79,7 +79,7 @@ type responseObject struct {
|
|||||||
t *testing.T
|
t *testing.T
|
||||||
parser *parser
|
parser *parser
|
||||||
respObjMeta interface{}
|
respObjMeta interface{}
|
||||||
validationResults map[string]interface{}
|
validationResults []*validationResult
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *responseObject) Extract(extractors map[string]string) map[string]interface{} {
|
func (v *responseObject) Extract(extractors map[string]string) map[string]interface{} {
|
||||||
@@ -122,9 +122,23 @@ func (v *responseObject) Validate(validators []Validator, variablesMapping map[s
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
validResult := &validationResult{
|
||||||
|
Validator: Validator{
|
||||||
|
Check: validator.Check,
|
||||||
|
Expect: expectValue,
|
||||||
|
Assert: assertMethod,
|
||||||
|
Message: validator.Message,
|
||||||
|
},
|
||||||
|
CheckValue: checkValue,
|
||||||
|
CheckResult: "fail",
|
||||||
|
}
|
||||||
|
|
||||||
// do assertion
|
// do assertion
|
||||||
result := assertFunc(v.t, expectValue, checkValue)
|
result := assertFunc(v.t, expectValue, checkValue)
|
||||||
|
if result {
|
||||||
|
validResult.CheckResult = "pass"
|
||||||
|
}
|
||||||
|
v.validationResults = append(v.validationResults, validResult)
|
||||||
log.Info().
|
log.Info().
|
||||||
Str("assertMethod", assertMethod).
|
Str("assertMethod", assertMethod).
|
||||||
Interface("expectValue", expectValue).
|
Interface("expectValue", expectValue).
|
||||||
|
|||||||
172
runner.go
172
runner.go
@@ -23,10 +23,15 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/httprunner/hrp/internal/builtin"
|
||||||
"github.com/httprunner/hrp/internal/ga"
|
"github.com/httprunner/hrp/internal/ga"
|
||||||
"github.com/httprunner/hrp/plugin/common"
|
"github.com/httprunner/hrp/plugin/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
summaryPath string = "summary.json"
|
||||||
|
)
|
||||||
|
|
||||||
// Run starts to run API test with default configs.
|
// Run starts to run API test with default configs.
|
||||||
func Run(testcases ...ITestCase) error {
|
func Run(testcases ...ITestCase) error {
|
||||||
t := &testing.T{}
|
t := &testing.T{}
|
||||||
@@ -52,10 +57,11 @@ func NewRunner(t *testing.T) *HRPRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HRPRunner struct {
|
type HRPRunner struct {
|
||||||
t *testing.T
|
t *testing.T
|
||||||
failfast bool
|
failfast bool
|
||||||
debug bool
|
debug bool
|
||||||
client *http.Client
|
saveTests bool
|
||||||
|
client *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFailfast configures whether to stop running when one step fails.
|
// SetFailfast configures whether to stop running when one step fails.
|
||||||
@@ -87,6 +93,13 @@ func (r *HRPRunner) SetProxyUrl(proxyUrl string) *HRPRunner {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSaveTests configures whether to save summary of tests.
|
||||||
|
func (r *HRPRunner) SetSaveTests(saveTests bool) *HRPRunner {
|
||||||
|
log.Info().Bool("saveTests", saveTests).Msg("[init] SetSaveTests")
|
||||||
|
r.saveTests = saveTests
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
// Run starts to execute one or multiple testcases.
|
// Run starts to execute one or multiple testcases.
|
||||||
func (r *HRPRunner) Run(testcases ...ITestCase) error {
|
func (r *HRPRunner) Run(testcases ...ITestCase) error {
|
||||||
event := ga.EventTracking{
|
event := ga.EventTracking{
|
||||||
@@ -111,6 +124,7 @@ func (r *HRPRunner) Run(testcases ...ITestCase) error {
|
|||||||
log.Error().Interface("parameters", cfg.Parameters).Err(err).Msg("parse config parameters failed")
|
log.Error().Interface("parameters", cfg.Parameters).Err(err).Msg("parse config parameters failed")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
s := newOutSummary()
|
||||||
// 在runner模式下,指定整体策略,cfg.ParametersSetting.Iterators仅包含一个CartesianProduct的迭代器
|
// 在runner模式下,指定整体策略,cfg.ParametersSetting.Iterators仅包含一个CartesianProduct的迭代器
|
||||||
for it := cfg.ParametersSetting.Iterators[0]; it.HasNext(); {
|
for it := cfg.ParametersSetting.Iterators[0]; it.HasNext(); {
|
||||||
// iterate through all parameter iterators and update case variables
|
// iterate through all parameter iterators and update case variables
|
||||||
@@ -119,10 +133,18 @@ func (r *HRPRunner) Run(testcases ...ITestCase) error {
|
|||||||
cfg.Variables = mergeVariables(it.Next(), cfg.Variables)
|
cfg.Variables = mergeVariables(it.Next(), cfg.Variables)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := r.newCaseRunner(testcase).run(); err != nil {
|
caseRunnerObj := r.newCaseRunner(testcase)
|
||||||
|
if err := caseRunnerObj.run(); err != nil {
|
||||||
log.Error().Err(err).Msg("[Run] run testcase failed")
|
log.Error().Err(err).Msg("[Run] run testcase failed")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
caseSummary := caseRunnerObj.getSummary()
|
||||||
|
s.appendCaseSummary(caseSummary)
|
||||||
|
}
|
||||||
|
s.Time.Duration = time.Since(s.Time.StartAt).Seconds()
|
||||||
|
if r.saveTests {
|
||||||
|
err = builtin.Dump2JSON(s, summaryPath)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -133,6 +155,7 @@ func (r *HRPRunner) newCaseRunner(testcase *TestCase) *caseRunner {
|
|||||||
TestCase: testcase,
|
TestCase: testcase,
|
||||||
hrpRunner: r,
|
hrpRunner: r,
|
||||||
parser: newParser(),
|
parser: newParser(),
|
||||||
|
summary: newSummary(),
|
||||||
}
|
}
|
||||||
caseRunner.reset()
|
caseRunner.reset()
|
||||||
return caseRunner
|
return caseRunner
|
||||||
@@ -148,7 +171,8 @@ type caseRunner 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
|
||||||
startTime time.Time // record start time of the testcase
|
startTime time.Time // record start time of the testcase
|
||||||
|
summary *testCaseSummary // record test case summary
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset clears runner session variables.
|
// reset clears runner session variables.
|
||||||
@@ -157,6 +181,7 @@ func (r *caseRunner) reset() *caseRunner {
|
|||||||
r.sessionVariables = make(map[string]interface{})
|
r.sessionVariables = make(map[string]interface{})
|
||||||
r.transactions = make(map[string]map[transactionType]time.Time)
|
r.transactions = make(map[string]map[transactionType]time.Time)
|
||||||
r.startTime = time.Now()
|
r.startTime = time.Now()
|
||||||
|
r.summary.Name = r.Config.Name
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,12 +204,16 @@ func (r *caseRunner) run() error {
|
|||||||
|
|
||||||
r.startTime = time.Now()
|
r.startTime = time.Now()
|
||||||
for index := range r.TestCase.TestSteps {
|
for index := range r.TestCase.TestSteps {
|
||||||
_, err := r.runStep(index, config)
|
stepData, err := r.runStep(index, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if r.hrpRunner.failfast {
|
if r.hrpRunner.failfast {
|
||||||
return errors.Wrap(err, "abort running due to failfast setting")
|
return errors.Wrap(err, "abort running due to failfast setting")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if stepData != nil {
|
||||||
|
r.summary.Records = append(r.summary.Records, stepData)
|
||||||
|
r.summary.Success = r.summary.Success && stepData.Success
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info().Str("testcase", config.Name).Msg("run testcase end")
|
log.Info().Str("testcase", config.Name).Msg("run testcase end")
|
||||||
@@ -269,29 +298,34 @@ func (r *caseRunner) runStep(index int, caseConfig *TConfig) (stepResult *stepDa
|
|||||||
stepResult, err = r.runStepTestCase(copiedStep)
|
stepResult, err = r.runStepTestCase(copiedStep)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("run referenced testcase step failed")
|
log.Error().Err(err).Msg("run referenced testcase step failed")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// parse step request url
|
||||||
|
var requestUrl interface{}
|
||||||
|
requestUrl, err = r.parser.parseString(copiedStep.Request.URL, copiedStep.Variables)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("parse request url failed")
|
||||||
|
requestUrl = copiedStep.Variables
|
||||||
|
}
|
||||||
|
copiedStep.Request.URL = buildURL(caseConfig.BaseURL, convertString(requestUrl)) // avoid data racing
|
||||||
// run request
|
// run request
|
||||||
copiedStep.Request.URL = buildURL(caseConfig.BaseURL, copiedStep.Request.URL) // avoid data racing
|
|
||||||
stepResult, err = r.runStepRequest(copiedStep)
|
stepResult, err = r.runStepRequest(copiedStep)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("run request step failed")
|
log.Error().Err(err).Msg("run request step failed")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update extracted variables
|
// update extracted variables
|
||||||
for k, v := range stepResult.exportVars {
|
for k, v := range stepResult.ExportVars {
|
||||||
r.sessionVariables[k] = v
|
r.sessionVariables[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info().
|
log.Info().
|
||||||
Str("step", step.Name()).
|
Str("step", step.Name()).
|
||||||
Bool("success", stepResult.success).
|
Bool("success", stepResult.Success).
|
||||||
Interface("exportVars", stepResult.exportVars).
|
Interface("exportVars", stepResult.ExportVars).
|
||||||
Msg("run step end")
|
Msg("run step end")
|
||||||
return stepResult, nil
|
return stepResult, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *caseRunner) runStepTransaction(transaction *Transaction) (stepResult *stepData, err error) {
|
func (r *caseRunner) runStepTransaction(transaction *Transaction) (stepResult *stepData, err error) {
|
||||||
@@ -301,11 +335,11 @@ func (r *caseRunner) runStepTransaction(transaction *Transaction) (stepResult *s
|
|||||||
Msg("transaction")
|
Msg("transaction")
|
||||||
|
|
||||||
stepResult = &stepData{
|
stepResult = &stepData{
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// create transaction if not exists
|
// create transaction if not exists
|
||||||
@@ -329,7 +363,7 @@ func (r *caseRunner) runStepTransaction(transaction *Transaction) (stepResult *s
|
|||||||
// 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,9 +378,9 @@ func (r *caseRunner) runStepRendezvous(rendezvous *Rendezvous) (stepResult *step
|
|||||||
Int64("timeout", rendezvous.Timeout).
|
Int64("timeout", rendezvous.Timeout).
|
||||||
Msg("rendezvous")
|
Msg("rendezvous")
|
||||||
stepResult = &stepData{
|
stepResult = &stepData{
|
||||||
name: rendezvous.Name,
|
Name: rendezvous.Name,
|
||||||
stepType: stepTypeRendezvous,
|
StepType: stepTypeRendezvous,
|
||||||
success: true,
|
Success: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// pass current rendezvous if already released, activate rendezvous sequentially after spawn done
|
// pass current rendezvous if already released, activate rendezvous sequentially after spawn done
|
||||||
@@ -518,12 +552,16 @@ func waitSingleRendezvous(rendezvous *Rendezvous, rendezvousList []*Rendezvous,
|
|||||||
|
|
||||||
func (r *caseRunner) runStepRequest(step *TStep) (stepResult *stepData, err error) {
|
func (r *caseRunner) runStepRequest(step *TStep) (stepResult *stepData, err error) {
|
||||||
stepResult = &stepData{
|
stepResult = &stepData{
|
||||||
name: step.Name,
|
Name: step.Name,
|
||||||
stepType: stepTypeRequest,
|
StepType: stepTypeRequest,
|
||||||
success: false,
|
Success: false,
|
||||||
contentSize: 0,
|
ContentSize: 0,
|
||||||
|
}
|
||||||
|
sessionData := newSessionData()
|
||||||
|
if err = copier.Copy(&sessionData.ReqResps.Request, step.Request); err != nil {
|
||||||
|
log.Error().Err(err).Msg("copy step request data failed")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rawUrl := step.Request.URL
|
rawUrl := step.Request.URL
|
||||||
method := step.Request.Method
|
method := step.Request.Method
|
||||||
req := &http.Request{
|
req := &http.Request{
|
||||||
@@ -538,7 +576,7 @@ func (r *caseRunner) runStepRequest(step *TStep) (stepResult *stepData, err erro
|
|||||||
if len(step.Request.Headers) > 0 {
|
if len(step.Request.Headers) > 0 {
|
||||||
headers, err := r.parser.parseHeaders(step.Request.Headers, step.Variables)
|
headers, err := r.parser.parseHeaders(step.Request.Headers, step.Variables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "parse headers failed")
|
return stepResult, errors.Wrap(err, "parse headers failed")
|
||||||
}
|
}
|
||||||
for key, value := range headers {
|
for key, value := range headers {
|
||||||
req.Header.Add(key, value)
|
req.Header.Add(key, value)
|
||||||
@@ -555,9 +593,10 @@ func (r *caseRunner) runStepRequest(step *TStep) (stepResult *stepData, err erro
|
|||||||
if len(step.Request.Params) > 0 {
|
if len(step.Request.Params) > 0 {
|
||||||
params, err := r.parser.parseData(step.Request.Params, step.Variables)
|
params, err := r.parser.parseData(step.Request.Params, step.Variables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "parse data failed")
|
return stepResult, errors.Wrap(err, "parse data failed")
|
||||||
}
|
}
|
||||||
parsedParams := params.(map[string]interface{})
|
parsedParams := params.(map[string]interface{})
|
||||||
|
sessionData.ReqResps.Request.Params = parsedParams
|
||||||
if len(parsedParams) > 0 {
|
if len(parsedParams) > 0 {
|
||||||
queryParams = make(url.Values)
|
queryParams = make(url.Values)
|
||||||
for k, v := range parsedParams {
|
for k, v := range parsedParams {
|
||||||
@@ -587,8 +626,9 @@ func (r *caseRunner) runStepRequest(step *TStep) (stepResult *stepData, err erro
|
|||||||
if step.Request.Body != nil {
|
if step.Request.Body != nil {
|
||||||
data, err := r.parser.parseData(step.Request.Body, step.Variables)
|
data, err := r.parser.parseData(step.Request.Body, step.Variables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return stepResult, err
|
||||||
}
|
}
|
||||||
|
sessionData.ReqResps.Request.Body = data
|
||||||
var dataBytes []byte
|
var dataBytes []byte
|
||||||
switch vv := data.(type) {
|
switch vv := data.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
@@ -604,7 +644,7 @@ func (r *caseRunner) runStepRequest(step *TStep) (stepResult *stepData, err erro
|
|||||||
// post json
|
// post json
|
||||||
dataBytes, err = json.Marshal(vv)
|
dataBytes, err = json.Marshal(vv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return stepResult, err
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json; charset=UTF-8")
|
req.Header.Set("Content-Type", "application/json; charset=UTF-8")
|
||||||
}
|
}
|
||||||
@@ -615,15 +655,20 @@ func (r *caseRunner) runStepRequest(step *TStep) (stepResult *stepData, err erro
|
|||||||
case bytes.Buffer:
|
case bytes.Buffer:
|
||||||
dataBytes = vv.Bytes()
|
dataBytes = vv.Bytes()
|
||||||
default: // unexpected body type
|
default: // unexpected body type
|
||||||
return nil, errors.New("unexpected request body type")
|
return stepResult, errors.New("unexpected request body type")
|
||||||
}
|
}
|
||||||
setBodyBytes(req, dataBytes)
|
setBodyBytes(req, dataBytes)
|
||||||
}
|
}
|
||||||
|
// update header
|
||||||
|
sessionData.ReqResps.Request.Headers = make(map[string]string)
|
||||||
|
for key, value := range req.Header {
|
||||||
|
sessionData.ReqResps.Request.Headers[key] = value[0]
|
||||||
|
}
|
||||||
|
|
||||||
// prepare url
|
// prepare url
|
||||||
u, err := url.Parse(rawUrl)
|
u, err := url.Parse(rawUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "parse url failed")
|
return stepResult, errors.Wrap(err, "parse url failed")
|
||||||
}
|
}
|
||||||
req.URL = u
|
req.URL = u
|
||||||
req.Host = u.Host
|
req.Host = u.Host
|
||||||
@@ -632,7 +677,7 @@ func (r *caseRunner) runStepRequest(step *TStep) (stepResult *stepData, err erro
|
|||||||
if r.hrpRunner.debug {
|
if r.hrpRunner.debug {
|
||||||
reqDump, err := httputil.DumpRequest(req, true)
|
reqDump, err := httputil.DumpRequest(req, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "dump request failed")
|
return stepResult, errors.Wrap(err, "dump request failed")
|
||||||
}
|
}
|
||||||
fmt.Println("-------------------- request --------------------")
|
fmt.Println("-------------------- request --------------------")
|
||||||
fmt.Println(string(reqDump))
|
fmt.Println(string(reqDump))
|
||||||
@@ -641,9 +686,9 @@ func (r *caseRunner) runStepRequest(step *TStep) (stepResult *stepData, err erro
|
|||||||
// do request action
|
// do request action
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
resp, err := r.hrpRunner.client.Do(req)
|
resp, err := r.hrpRunner.client.Do(req)
|
||||||
stepResult.elapsed = time.Since(start).Milliseconds()
|
stepResult.Elapsed = time.Since(start).Milliseconds()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "do request failed")
|
return stepResult, errors.Wrap(err, "do request failed")
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@@ -652,7 +697,7 @@ func (r *caseRunner) runStepRequest(step *TStep) (stepResult *stepData, err erro
|
|||||||
fmt.Println("==================== response ===================")
|
fmt.Println("==================== response ===================")
|
||||||
respDump, err := httputil.DumpResponse(resp, true)
|
respDump, err := httputil.DumpResponse(resp, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "dump response failed")
|
return stepResult, errors.Wrap(err, "dump response failed")
|
||||||
}
|
}
|
||||||
fmt.Println(string(respDump))
|
fmt.Println(string(respDump))
|
||||||
fmt.Println("--------------------------------------------------")
|
fmt.Println("--------------------------------------------------")
|
||||||
@@ -664,31 +709,33 @@ func (r *caseRunner) runStepRequest(step *TStep) (stepResult *stepData, err erro
|
|||||||
err = errors.Wrap(err, "init ResponseObject error")
|
err = errors.Wrap(err, "init ResponseObject error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
sessionData.ReqResps.Response = respObj.respObjMeta
|
||||||
|
|
||||||
// extract variables from response
|
// extract variables from response
|
||||||
extractors := step.Extract
|
extractors := step.Extract
|
||||||
extractMapping := respObj.Extract(extractors)
|
extractMapping := respObj.Extract(extractors)
|
||||||
stepResult.exportVars = extractMapping
|
stepResult.ExportVars = extractMapping
|
||||||
|
|
||||||
// override step variables with extracted variables
|
// override step variables with extracted variables
|
||||||
stepVariables := mergeVariables(step.Variables, extractMapping)
|
stepVariables := mergeVariables(step.Variables, extractMapping)
|
||||||
|
|
||||||
// validate response
|
// validate response
|
||||||
err = respObj.Validate(step.Validators, stepVariables)
|
err = respObj.Validate(step.Validators, stepVariables)
|
||||||
if err != nil {
|
sessionData.Validators = respObj.validationResults
|
||||||
return
|
if err == nil {
|
||||||
|
sessionData.Success = true
|
||||||
|
stepResult.Success = true
|
||||||
}
|
}
|
||||||
|
stepResult.ContentSize = resp.ContentLength
|
||||||
stepResult.success = true
|
stepResult.Data = sessionData
|
||||||
stepResult.contentSize = resp.ContentLength
|
return stepResult, err
|
||||||
return stepResult, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *caseRunner) runStepTestCase(step *TStep) (stepResult *stepData, err error) {
|
func (r *caseRunner) runStepTestCase(step *TStep) (stepResult *stepData, err error) {
|
||||||
stepResult = &stepData{
|
stepResult = &stepData{
|
||||||
name: step.Name,
|
Name: step.Name,
|
||||||
stepType: stepTypeTestCase,
|
StepType: stepTypeTestCase,
|
||||||
success: false,
|
Success: false,
|
||||||
}
|
}
|
||||||
testcase := step.TestCase
|
testcase := step.TestCase
|
||||||
|
|
||||||
@@ -696,16 +743,18 @@ func (r *caseRunner) runStepTestCase(step *TStep) (stepResult *stepData, err err
|
|||||||
copiedTestCase := &TestCase{}
|
copiedTestCase := &TestCase{}
|
||||||
if err = copier.Copy(copiedTestCase, testcase); err != nil {
|
if err = copier.Copy(copiedTestCase, testcase); err != nil {
|
||||||
log.Error().Err(err).Msg("copy testcase failed")
|
log.Error().Err(err).Msg("copy testcase failed")
|
||||||
return nil, err
|
return stepResult, err
|
||||||
}
|
}
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
err = r.hrpRunner.newCaseRunner(copiedTestCase).run()
|
caseRunnerObj := r.hrpRunner.newCaseRunner(copiedTestCase)
|
||||||
stepResult.elapsed = time.Since(start).Milliseconds()
|
err = caseRunnerObj.run()
|
||||||
|
stepResult.Elapsed = time.Since(start).Milliseconds()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return stepResult, err
|
return stepResult, err
|
||||||
}
|
}
|
||||||
stepResult.success = true
|
stepResult.Data = caseRunnerObj.getSummary()
|
||||||
|
stepResult.Success = true
|
||||||
return stepResult, nil
|
return stepResult, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -735,8 +784,25 @@ func (r *caseRunner) parseConfig(cfg *TConfig) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newSummary() *testCaseSummary {
|
||||||
|
return &testCaseSummary{
|
||||||
|
Success: true,
|
||||||
|
Time: &testCaseTime{},
|
||||||
|
InOut: &testCaseInOut{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (r *caseRunner) getSummary() *testCaseSummary {
|
func (r *caseRunner) getSummary() *testCaseSummary {
|
||||||
return &testCaseSummary{}
|
caseSummary := r.summary
|
||||||
|
caseSummary.Time.StartAt = r.startTime
|
||||||
|
caseSummary.Time.Duration = time.Since(r.startTime).Seconds()
|
||||||
|
exportVars := make(map[string]interface{})
|
||||||
|
for _, value := range r.Config.Export {
|
||||||
|
exportVars[value] = r.Config.Variables[value]
|
||||||
|
}
|
||||||
|
caseSummary.InOut.ExportVars = exportVars
|
||||||
|
caseSummary.InOut.ConfigVars = r.Config.Variables
|
||||||
|
return caseSummary
|
||||||
}
|
}
|
||||||
|
|
||||||
func setBodyBytes(req *http.Request, data []byte) {
|
func setBodyBytes(req *http.Request, data []byte) {
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ func (s *StepRequestValidation) AssertTypeMatch(jmesPath string, expected interf
|
|||||||
v := Validator{
|
v := Validator{
|
||||||
Check: jmesPath,
|
Check: jmesPath,
|
||||||
Assert: "type_match",
|
Assert: "type_match",
|
||||||
Expect: expected,
|
Expect: expected,
|
||||||
Message: msg,
|
Message: msg,
|
||||||
}
|
}
|
||||||
s.step.Validators = append(s.step.Validators, v)
|
s.step.Validators = append(s.step.Validators, v)
|
||||||
@@ -156,12 +156,11 @@ func (s *StepRequestValidation) AssertLengthEqual(jmesPath string, expected inte
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (s *StepRequestValidation) AssertContainedBy(jmesPath string, expected interface{}, msg string) *StepRequestValidation {
|
func (s *StepRequestValidation) AssertContainedBy(jmesPath string, expected interface{}, msg string) *StepRequestValidation {
|
||||||
v := Validator{
|
v := Validator{
|
||||||
Check: jmesPath,
|
Check: jmesPath,
|
||||||
Assert: "contained_by",
|
Assert: "contained_by",
|
||||||
Expect: expected,
|
Expect: expected,
|
||||||
Message: msg,
|
Message: msg,
|
||||||
}
|
}
|
||||||
s.step.Validators = append(s.step.Validators, v)
|
s.step.Validators = append(s.step.Validators, v)
|
||||||
@@ -183,7 +182,7 @@ func (s *StepRequestValidation) AssertStringEqual(jmesPath string, expected inte
|
|||||||
v := Validator{
|
v := Validator{
|
||||||
Check: jmesPath,
|
Check: jmesPath,
|
||||||
Assert: "string_equals",
|
Assert: "string_equals",
|
||||||
Expect: expected,
|
Expect: expected,
|
||||||
Message: msg,
|
Message: msg,
|
||||||
}
|
}
|
||||||
s.step.Validators = append(s.step.Validators, v)
|
s.step.Validators = append(s.step.Validators, v)
|
||||||
|
|||||||
Reference in New Issue
Block a user