Skip to content
This repository has been archived by the owner on Mar 28, 2022. It is now read-only.

Commit

Permalink
Merge pull request #133 from xucong053/support-api-layer-for-testcase
Browse files Browse the repository at this point in the history
feat: support api layer and global headers for testcase #94 #95
  • Loading branch information
xucong053 authored Mar 15, 2022
2 parents 3b9e6cd + af6c433 commit 4cee7aa
Show file tree
Hide file tree
Showing 37 changed files with 904 additions and 132 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ func TestCaseDemo(t *testing.T) {
}).
GET("/get").
WithParams(map[string]interface{}{"foo1": "$varFoo1", "foo2": "$varFoo2"}). // request with params
WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}). // request with headers
WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}). // request with headers
Extract().
WithJmesPath("body.args.foo1", "varFoo1"). // extract variable with jmespath
Validate().
Expand Down
2 changes: 2 additions & 0 deletions boomer.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ func (b *HRPBoomer) Quit() {

func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rendezvous) *boomer.Task {
hrpRunner := NewRunner(nil)
// set client transport for high concurrency load testing
hrpRunner.SetClientTransport(b.GetSpawnCount(), b.GetDisableKeepAlive(), b.GetDisableCompression())
config := testcase.Config

// each testcase has its own plugin process
Expand Down
2 changes: 1 addition & 1 deletion boomer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestBoomerStandaloneRun(t *testing.T) {
NewStep("TestCase3").CallRefCase(&TestCase{Config: NewConfig("TestCase3")}),
},
}
testcase2 := &TestCasePath{demoTestCaseJSONPath}
testcase2 := &demoTestCaseJSONPath

b := NewBoomer(2, 1)
go b.Run(testcase1, testcase2)
Expand Down
9 changes: 8 additions & 1 deletion cli/hrp/cmd/boom.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ var boomCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
var paths []hrp.ITestCase
for _, arg := range args {
paths = append(paths, &hrp.TestCasePath{Path: arg})
path := hrp.TestCasePath(arg)
paths = append(paths, &path)
}
hrpBoomer := hrp.NewBoomer(spawnCount, spawnRate)
hrpBoomer.SetRateLimiter(maxRPS, requestIncreaseRate)
Expand All @@ -38,6 +39,8 @@ var boomCmd = &cobra.Command{
if prometheusPushgatewayURL != "" {
hrpBoomer.AddOutput(boomer.NewPrometheusPusherOutput(prometheusPushgatewayURL, "hrp"))
}
hrpBoomer.SetDisableKeepAlive(disableKeepalive)
hrpBoomer.SetDisableCompression(disableCompression)
hrpBoomer.EnableCPUProfile(cpuProfile, cpuProfileDuration)
hrpBoomer.EnableMemoryProfile(memoryProfile, memoryProfileDuration)
hrpBoomer.Run(paths...)
Expand All @@ -56,6 +59,8 @@ var (
cpuProfileDuration time.Duration
prometheusPushgatewayURL string
disableConsoleOutput bool
disableCompression bool
disableKeepalive bool
)

func init() {
Expand All @@ -72,4 +77,6 @@ func init() {
boomCmd.Flags().DurationVar(&cpuProfileDuration, "cpu-profile-duration", 30*time.Second, "CPU profile duration.")
boomCmd.Flags().StringVar(&prometheusPushgatewayURL, "prometheus-gateway", "", "Prometheus Pushgateway url.")
boomCmd.Flags().BoolVar(&disableConsoleOutput, "disable-console-output", false, "Disable console output.")
boomCmd.Flags().BoolVar(&disableCompression, "disable-compression", false, "Disable compression")
boomCmd.Flags().BoolVar(&disableKeepalive, "disable-keepalive", false, "Disable keepalive")
}
3 changes: 2 additions & 1 deletion cli/hrp/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ var runCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
var paths []hrp.ITestCase
for _, arg := range args {
paths = append(paths, &hrp.TestCasePath{Path: arg})
path := hrp.TestCasePath(arg)
paths = append(paths, &path)
}
runner := hrp.NewRunner(nil).
SetFailfast(!continueOnFailure).
Expand Down
180 changes: 115 additions & 65 deletions convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,52 +13,80 @@ import (
"github.com/httprunner/hrp/internal/json"
)

func loadFromJSON(path string) (*TCase, error) {
func loadFromJSON(path string, structObj interface{}) error {
path, err := filepath.Abs(path)
if err != nil {
log.Error().Str("path", path).Err(err).Msg("convert absolute path failed")
return nil, err
return err
}
log.Info().Str("path", path).Msg("load json testcase")
log.Info().Str("path", path).Msg("load json")

file, err := os.ReadFile(path)
if err != nil {
log.Error().Err(err).Msg("load json path failed")
return nil, err
return err
}

tc := &TCase{}
decoder := json.NewDecoder(bytes.NewReader(file))
decoder.UseNumber()
err = decoder.Decode(tc)
if err != nil {
return tc, err
}
err = convertCompatTestCase(tc)
return tc, err
err = decoder.Decode(structObj)
return err
}

func loadFromYAML(path string) (*TCase, error) {
func loadFromYAML(path string, structObj interface{}) error {
path, err := filepath.Abs(path)
if err != nil {
log.Error().Str("path", path).Err(err).Msg("convert absolute path failed")
return nil, err
return err
}
log.Info().Str("path", path).Msg("load yaml testcase")
log.Info().Str("path", path).Msg("load yaml")

file, err := os.ReadFile(path)
if err != nil {
log.Error().Err(err).Msg("load yaml path failed")
return nil, err
return err
}

tc := &TCase{}
err = yaml.Unmarshal(file, tc)
if err != nil {
return tc, nil
err = yaml.Unmarshal(file, structObj)
return err
}

func convertCompatValidator(Validators []interface{}) (err error) {
for i, iValidator := range Validators {
validatorMap := iValidator.(map[string]interface{})
validator := Validator{}
_, checkExisted := validatorMap["check"]
_, assertExisted := validatorMap["assert"]
_, expectExisted := validatorMap["expect"]
// check priority: HRP > HttpRunner
if checkExisted && assertExisted && expectExisted {
// HRP validator format
validator.Check = validatorMap["check"].(string)
validator.Assert = validatorMap["assert"].(string)
validator.Expect = validatorMap["expect"]
if msg, existed := validatorMap["msg"]; existed {
validator.Message = msg.(string)
}
validator.Check = convertCheckExpr(validator.Check)
Validators[i] = validator
} else if len(validatorMap) == 1 {
// HttpRunner validator format
for assertMethod, iValidatorContent := range validatorMap {
checkAndExpect := iValidatorContent.([]interface{})
if len(checkAndExpect) != 2 {
return fmt.Errorf("unexpected validator format: %v", validatorMap)
}
validator.Check = checkAndExpect[0].(string)
validator.Assert = assertMethod
validator.Expect = checkAndExpect[1]
}
validator.Check = convertCheckExpr(validator.Check)
Validators[i] = validator
} else {
return fmt.Errorf("unexpected validator format: %v", validatorMap)
}
}
err = convertCompatTestCase(tc)
return tc, err
return nil
}

func convertCompatTestCase(tc *TCase) (err error) {
Expand All @@ -79,42 +107,12 @@ func convertCompatTestCase(tc *TCase) (err error) {
}

// 2. deal with validators compatible with HttpRunner
for i, iValidator := range step.Validators {
validatorMap := iValidator.(map[string]interface{})
validator := Validator{}
_, checkExisted := validatorMap["check"]
_, assertExisted := validatorMap["assert"]
_, expectExisted := validatorMap["expect"]
// check priority: HRP > HttpRunner
if checkExisted && assertExisted && expectExisted {
// HRP validator format
validator.Check = validatorMap["check"].(string)
validator.Assert = validatorMap["assert"].(string)
validator.Expect = validatorMap["expect"]
if msg, existed := validatorMap["msg"]; existed {
validator.Message = msg.(string)
}
validator.Check = convertCheckExpr(validator.Check)
step.Validators[i] = validator
} else if len(validatorMap) == 1 {
// HttpRunner validator format
for assertMethod, iValidatorContent := range validatorMap {
checkAndExpect := iValidatorContent.([]interface{})
if len(checkAndExpect) != 2 {
return fmt.Errorf("unexpected validator format: %v", validatorMap)
}
validator.Check = checkAndExpect[0].(string)
validator.Assert = assertMethod
validator.Expect = checkAndExpect[1]
}
validator.Check = convertCheckExpr(validator.Check)
step.Validators[i] = validator
} else {
return fmt.Errorf("unexpected validator format: %v", validatorMap)
}
err = convertCompatValidator(step.Validators)
if err != nil {
return err
}
}
return err
return nil
}

// convertCheckExpr deals with check expression including hyphen
Expand All @@ -136,14 +134,32 @@ func (tc *TCase) ToTestCase() (*TestCase, error) {
Config: tc.Config,
}
for _, step := range tc.TestSteps {
if step.Request != nil {
testCase.TestSteps = append(testCase.TestSteps, &StepRequestWithOptionalArgs{
if step.APIPath != "" {
refAPI := APIPath(step.APIPath)
step.APIContent = &refAPI
apiContent, err := step.APIContent.ToAPI()
if err != nil {
return nil, err
}
step.APIContent = apiContent
testCase.TestSteps = append(testCase.TestSteps, &StepAPIWithOptionalArgs{
step: step,
})
} else if step.TestCase != nil {
} else if step.TestCasePath != "" {
refTestCase := TestCasePath(step.TestCasePath)
step.TestCaseContent = &refTestCase
tc, err := step.TestCaseContent.ToTestCase()
if err != nil {
return nil, err
}
step.TestCaseContent = tc
testCase.TestSteps = append(testCase.TestSteps, &StepTestCaseWithOptionalArgs{
step: step,
})
} else if step.Request != nil {
testCase.TestSteps = append(testCase.TestSteps, &StepRequestWithOptionalArgs{
step: step,
})
} else if step.Transaction != nil {
testCase.TestSteps = append(testCase.TestSteps, &StepTransaction{
step: step,
Expand All @@ -161,29 +177,63 @@ func (tc *TCase) ToTestCase() (*TestCase, error) {

var ErrUnsupportedFileExt = fmt.Errorf("unsupported testcase file extension")

// APIPath implements IAPI interface.
type APIPath string

func (path *APIPath) ToString() string {
return fmt.Sprintf("%v", *path)
}

func (path *APIPath) ToAPI() (*API, error) {
api := &API{}
var err error

apiPath := path.ToString()
ext := filepath.Ext(apiPath)
switch ext {
case ".json":
err = loadFromJSON(apiPath, api)
case ".yaml", ".yml":
err = loadFromYAML(apiPath, api)
default:
err = ErrUnsupportedFileExt
}
if err != nil {
return nil, err
}
err = convertCompatValidator(api.Validators)
return api, err
}

// TestCasePath implements ITestCase interface.
type TestCasePath struct {
Path string
type TestCasePath string

func (path *TestCasePath) ToString() string {
return fmt.Sprintf("%v", *path)
}

func (path *TestCasePath) ToTestCase() (*TestCase, error) {
var tc *TCase
tc := &TCase{}
var err error

casePath := path.Path
casePath := path.ToString()
ext := filepath.Ext(casePath)
switch ext {
case ".json":
tc, err = loadFromJSON(casePath)
err = loadFromJSON(casePath, tc)
case ".yaml", ".yml":
tc, err = loadFromYAML(casePath)
err = loadFromYAML(casePath, tc)
default:
err = ErrUnsupportedFileExt
}
if err != nil {
return nil, err
}
tc.Config.Path = path.Path
err = convertCompatTestCase(tc)
if err != nil {
return nil, err
}
tc.Config.Path = path.ToString()
testcase, err := tc.ToTestCase()
if err != nil {
return nil, err
Expand Down
13 changes: 9 additions & 4 deletions convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,21 @@ import (
)

var (
demoTestCaseJSONPath = "examples/demo.json"
demoTestCaseYAMLPath = "examples/demo.yaml"
demoTestCaseJSONPath TestCasePath = "examples/demo.json"
demoTestCaseYAMLPath TestCasePath = "examples/demo.yaml"
demoRefAPIYAMLPath TestCasePath = "examples/ref_api_test.yaml"
demoRefTestCaseJSONPath TestCasePath = "examples/ref_testcase_test.json"
demoAPIYAMLPath APIPath = "examples/api/put.yml"
)

func TestLoadCase(t *testing.T) {
tcJSON, err := loadFromJSON(demoTestCaseJSONPath)
tcJSON := &TCase{}
tcYAML := &TCase{}
err := loadFromJSON(demoTestCaseJSONPath.ToString(), tcJSON)
if !assert.NoError(t, err) {
t.Fail()
}
tcYAML, err := loadFromYAML(demoTestCaseYAMLPath)
err = loadFromYAML(demoTestCaseYAMLPath.ToString(), tcYAML)
if !assert.NoError(t, err) {
t.Fail()
}
Expand Down
2 changes: 1 addition & 1 deletion docs/cmd/hrp.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ Copyright 2021 debugtalk
* [hrp run](hrp_run.md) - run API test
* [hrp startproject](hrp_startproject.md) - create a scaffold project

###### Auto generated by spf13/cobra on 10-Mar-2022
###### Auto generated by spf13/cobra on 15-Mar-2022
4 changes: 3 additions & 1 deletion docs/cmd/hrp_boom.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ hrp boom [flags]
```
--cpu-profile string Enable CPU profiling.
--cpu-profile-duration duration CPU profile duration. (default 30s)
--disable-compression Disable compression
--disable-console-output Disable console output.
--disable-keepalive Disable keepalive
-h, --help help for boom
--loop-count int The specify running cycles for load testing (default -1)
--max-rps int Max RPS that boomer can generate, disabled by default.
Expand All @@ -39,4 +41,4 @@ hrp boom [flags]

* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.

###### Auto generated by spf13/cobra on 10-Mar-2022
###### Auto generated by spf13/cobra on 15-Mar-2022
2 changes: 1 addition & 1 deletion docs/cmd/hrp_har2case.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ hrp har2case $har_path... [flags]

* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.

###### Auto generated by spf13/cobra on 10-Mar-2022
###### Auto generated by spf13/cobra on 15-Mar-2022
2 changes: 1 addition & 1 deletion docs/cmd/hrp_run.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ hrp run $path... [flags]

* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.

###### Auto generated by spf13/cobra on 10-Mar-2022
###### Auto generated by spf13/cobra on 15-Mar-2022
2 changes: 1 addition & 1 deletion docs/cmd/hrp_startproject.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ hrp startproject $project_name [flags]

* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.

###### Auto generated by spf13/cobra on 10-Mar-2022
###### Auto generated by spf13/cobra on 15-Mar-2022
Loading

0 comments on commit 4cee7aa

Please sign in to comment.