From a70b11401ec8cb62ff32424bf808d6f86e5ea958 Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Wed, 6 Jul 2022 13:46:12 +0800 Subject: [PATCH] support using curl as subcommand --- hrp/cmd/boom.go | 63 +++++------ hrp/cmd/convert.go | 69 ++++++------ hrp/cmd/curl.go | 135 ++++++++++++++++++++++++ hrp/cmd/root.go | 3 +- hrp/cmd/run.go | 47 +++++---- hrp/internal/convert/converter.go | 6 +- hrp/internal/convert/from_curl.go | 168 ++++++++++++++---------------- hrp/testcase.go | 66 ++++++------ 8 files changed, 347 insertions(+), 210 deletions(-) create mode 100644 hrp/cmd/curl.go diff --git a/hrp/cmd/boom.go b/hrp/cmd/boom.go index 922f593b..120109e0 100644 --- a/hrp/cmd/boom.go +++ b/hrp/cmd/boom.go @@ -35,35 +35,7 @@ var boomCmd = &cobra.Command{ path := hrp.TestCasePath(arg) paths = append(paths, &path) } - // if set profile, the priority is higher than the other commands - if boomArgs.profile != "" { - err := builtin.LoadFile(boomArgs.profile, &boomArgs) - if err != nil { - log.Error().Err(err).Msg("failed to load profile") - os.Exit(1) - } - } - - hrpBoomer := hrp.NewBoomer(boomArgs.SpawnCount, boomArgs.SpawnRate) - hrpBoomer.SetRateLimiter(boomArgs.MaxRPS, boomArgs.RequestIncreaseRate) - if boomArgs.LoopCount > 0 { - hrpBoomer.SetLoopCount(boomArgs.LoopCount) - } - if !boomArgs.DisableConsoleOutput { - hrpBoomer.AddOutput(boomer.NewConsoleOutput()) - } - if boomArgs.PrometheusPushgatewayURL != "" { - hrpBoomer.AddOutput(boomer.NewPrometheusPusherOutput(boomArgs.PrometheusPushgatewayURL, "hrp", hrpBoomer.GetMode())) - } - hrpBoomer.SetDisableKeepAlive(boomArgs.DisableKeepalive) - hrpBoomer.SetDisableCompression(boomArgs.DisableCompression) - hrpBoomer.SetClientTransport() - if venv != "" { - hrpBoomer.SetPython3Venv(venv) - } - hrpBoomer.EnableCPUProfile(boomArgs.CPUProfile, boomArgs.CPUProfileDuration) - hrpBoomer.EnableMemoryProfile(boomArgs.MemoryProfile, boomArgs.MemoryProfileDuration) - hrpBoomer.EnableGracefulQuit() + hrpBoomer := makeHRPBoomer() hrpBoomer.Run(paths...) }, } @@ -105,3 +77,36 @@ func init() { boomCmd.Flags().BoolVar(&boomArgs.DisableKeepalive, "disable-keepalive", false, "Disable keepalive") boomCmd.Flags().StringVar(&boomArgs.profile, "profile", "", "profile for load testing") } + +func makeHRPBoomer() *hrp.HRPBoomer { + // if set profile, the priority is higher than the other commands + if boomArgs.profile != "" { + err := builtin.LoadFile(boomArgs.profile, &boomArgs) + if err != nil { + log.Error().Err(err).Msg("failed to load profile") + os.Exit(1) + } + } + + hrpBoomer := hrp.NewBoomer(boomArgs.SpawnCount, boomArgs.SpawnRate) + hrpBoomer.SetRateLimiter(boomArgs.MaxRPS, boomArgs.RequestIncreaseRate) + if boomArgs.LoopCount > 0 { + hrpBoomer.SetLoopCount(boomArgs.LoopCount) + } + if !boomArgs.DisableConsoleOutput { + hrpBoomer.AddOutput(boomer.NewConsoleOutput()) + } + if boomArgs.PrometheusPushgatewayURL != "" { + hrpBoomer.AddOutput(boomer.NewPrometheusPusherOutput(boomArgs.PrometheusPushgatewayURL, "hrp", hrpBoomer.GetMode())) + } + hrpBoomer.SetDisableKeepAlive(boomArgs.DisableKeepalive) + hrpBoomer.SetDisableCompression(boomArgs.DisableCompression) + hrpBoomer.SetClientTransport() + if venv != "" { + hrpBoomer.SetPython3Venv(venv) + } + hrpBoomer.EnableCPUProfile(boomArgs.CPUProfile, boomArgs.CPUProfileDuration) + hrpBoomer.EnableMemoryProfile(boomArgs.MemoryProfile, boomArgs.MemoryProfileDuration) + hrpBoomer.EnableGracefulQuit() + return hrpBoomer +} diff --git a/hrp/cmd/convert.go b/hrp/cmd/convert.go index 3611274c..27163f60 100644 --- a/hrp/cmd/convert.go +++ b/hrp/cmd/convert.go @@ -19,39 +19,7 @@ var convertCmd = &cobra.Command{ PreRun: func(cmd *cobra.Command, args []string) { setLogLevel(logLevel) }, - RunE: func(cmd *cobra.Command, args []string) error { - var flagCount int - var outputType convert.OutputType - if toJSONFlag { - flagCount++ - } - if toYAMLFlag { - flagCount++ - outputType = convert.OutputTypeYAML - } - if toGoTestFlag { - flagCount++ - outputType = convert.OutputTypeGoTest - } - if toPyTestFlag { - flagCount++ - outputType = convert.OutputTypePyTest - - packages := []string{ - fmt.Sprintf("httprunner==%s", version.VERSION), - } - _, err := builtin.EnsurePython3Venv(venv, packages...) - if err != nil { - log.Error().Err(err).Msg("python3 venv is not ready") - return err - } - } - if flagCount > 1 { - return errors.New("please specify at most one conversion flag") - } - convert.Run(outputType, outputDir, profilePath, args) - return nil - }, + RunE: convertRun, } var ( @@ -61,6 +29,8 @@ var ( toPyTestFlag bool outputDir string profilePath string + + outputType convert.OutputType ) func init() { @@ -72,3 +42,36 @@ func init() { convertCmd.Flags().StringVarP(&outputDir, "output-dir", "d", "", "specify output directory, default to the same dir with har file") convertCmd.Flags().StringVarP(&profilePath, "profile", "p", "", "specify profile path to override headers and cookies") } + +func convertRun(cmd *cobra.Command, args []string) error { + var flagCount int + if toJSONFlag { + flagCount++ + } + if toYAMLFlag { + flagCount++ + outputType = convert.OutputTypeYAML + } + if toGoTestFlag { + flagCount++ + outputType = convert.OutputTypeGoTest + } + if toPyTestFlag { + flagCount++ + outputType = convert.OutputTypePyTest + + packages := []string{ + fmt.Sprintf("httprunner==%s", version.VERSION), + } + _, err := builtin.EnsurePython3Venv(venv, packages...) + if err != nil { + log.Error().Err(err).Msg("python3 venv is not ready") + return err + } + } + if flagCount > 1 { + return errors.New("please specify at most one conversion flag") + } + convert.Run(outputType, outputDir, profilePath, args) + return nil +} diff --git a/hrp/cmd/curl.go b/hrp/cmd/curl.go new file mode 100644 index 00000000..1dafa5bb --- /dev/null +++ b/hrp/cmd/curl.go @@ -0,0 +1,135 @@ +package cmd + +import ( + "os" + "strings" + + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + + "github.com/httprunner/httprunner/v4/hrp" + "github.com/httprunner/httprunner/v4/hrp/internal/boomer" + "github.com/httprunner/httprunner/v4/hrp/internal/convert" +) + +var runCurlCmd = &cobra.Command{ + Use: "curl URLs", + Short: "run API test with go engine using converted curl testcase", + Args: cobra.MinimumNArgs(1), + PreRun: func(cmd *cobra.Command, args []string) { + setLogLevel(logLevel) + }, + Run: func(cmd *cobra.Command, args []string) { + runner := makeHRPRunner() + if runner.Run(makeCurlTestCase(args)) != nil { + os.Exit(1) + } + }, +} + +var boomCurlCmd = &cobra.Command{ + Use: "curl URLs", + Short: "run load test with boomer using converted curl testcase", + Args: cobra.MinimumNArgs(1), + PreRun: func(cmd *cobra.Command, args []string) { + boomer.SetUlimit(10240) // ulimit -n 10240 + if !strings.EqualFold(logLevel, "DEBUG") { + logLevel = "WARN" // disable info logs for load testing + } + setLogLevel(logLevel) + }, + Run: func(cmd *cobra.Command, args []string) { + boomer := makeHRPBoomer() + boomer.Run(makeCurlTestCase(args)) + }, +} + +var convertCurlCmd = &cobra.Command{ + Use: "curl URLs", + Short: "convert curl command(s) to httprunner testcase", + Args: cobra.MinimumNArgs(1), + PreRun: func(cmd *cobra.Command, args []string) { + setLogLevel(logLevel) + }, + RunE: func(cmd *cobra.Command, args []string) error { + curlCommand := makeCurlCommand(args) + return convertRun(cmd, []string{curlCommand}) + }, +} + +var ( + cookieSlice []string + dataSlice []string + formSlice []string + get bool + head bool + headerSlice []string + request string +) + +func init() { + runCmd.AddCommand(runCurlCmd) + addCurlFlags(runCurlCmd) + + boomCmd.AddCommand(boomCurlCmd) + addCurlFlags(boomCurlCmd) + + convertCmd.AddCommand(convertCurlCmd) + addCurlFlags(convertCurlCmd) +} + +func addCurlFlags(cmd *cobra.Command) { + cmd.Flags().StringSliceVarP(&cookieSlice, "cookie", "b", nil, "-b, --cookie in curl") + cmd.Flags().StringSliceVarP(&dataSlice, "data", "d", nil, "-d, --data in curl") + cmd.Flags().StringSliceVarP(&formSlice, "form", "F", nil, "-F, --form in curl") + cmd.Flags().BoolVarP(&get, "get", "G", false, "-G, --get in curl") + cmd.Flags().BoolVarP(&head, "head", "I", false, "-I, --head in curl") + cmd.Flags().StringSliceVarP(&headerSlice, "header", "H", nil, "-H, --header in curl") + cmd.Flags().StringVarP(&request, "request", "X", "", "-X, --request in curl") +} + +func makeCurlTestCase(args []string) *hrp.TestCase { + curlCommand := makeCurlCommand(args) + tCase, err := convert.LoadSingleCurlCase(curlCommand) + if err != nil { + log.Error().Err(err).Msg("convert curl command failed") + os.Exit(1) + } + casePath, err := os.Getwd() + if err != nil { + } + testCase, err := tCase.ToTestCase(casePath) + if err != nil { + log.Error().Err(err).Msg("convert testcase failed") + os.Exit(1) + } + return testCase +} + +func makeCurlCommand(args []string) string { + var cmdList []string + cmdList = append(cmdList, "curl") + for _, c := range cookieSlice { + cmdList = append(cmdList, "--cookie", c) + } + for _, d := range dataSlice { + cmdList = append(cmdList, "--data", d) + } + for _, f := range formSlice { + cmdList = append(cmdList, "--form", f) + } + if get { + cmdList = append(cmdList, "--get") + } + if head { + cmdList = append(cmdList, "--head") + } + for _, h := range headerSlice { + cmdList = append(cmdList, "--header", h) + } + if request != "" { + cmdList = append(cmdList, "--request", request) + } + cmdList = append(cmdList, args...) + return strings.Join(cmdList, " ") +} diff --git a/hrp/cmd/root.go b/hrp/cmd/root.go index 698d0598..4ecb63f8 100644 --- a/hrp/cmd/root.go +++ b/hrp/cmd/root.go @@ -42,7 +42,8 @@ Copyright 2017 debugtalk`, log.Info().Msg("Set log to color console other than JSON format.") } }, - Version: version.VERSION, + Version: version.VERSION, + TraverseChildren: true, } var ( diff --git a/hrp/cmd/run.go b/hrp/cmd/run.go index 5c7bcd90..c628f93a 100644 --- a/hrp/cmd/run.go +++ b/hrp/cmd/run.go @@ -26,27 +26,7 @@ var runCmd = &cobra.Command{ path := hrp.TestCasePath(arg) paths = append(paths, &path) } - runner := hrp.NewRunner(nil). - SetFailfast(!continueOnFailure). - SetSaveTests(saveTests) - if genHTMLReport { - runner.GenHTMLReport() - } - if !requestsLogOff { - runner.SetRequestsLogOn() - } - if httpStatOn { - runner.SetHTTPStatOn() - } - if pluginLogOn { - runner.SetPluginLogOn() - } - if venv != "" { - runner.SetPython3Venv(venv) - } - if proxyUrl != "" { - runner.SetProxyUrl(proxyUrl) - } + runner := makeHRPRunner() err := runner.Run(paths...) if err != nil { os.Exit(1) @@ -74,3 +54,28 @@ func init() { runCmd.Flags().BoolVarP(&saveTests, "save-tests", "s", false, "save tests summary") runCmd.Flags().BoolVarP(&genHTMLReport, "gen-html-report", "g", false, "generate html report") } + +func makeHRPRunner() *hrp.HRPRunner { + runner := hrp.NewRunner(nil). + SetFailfast(!continueOnFailure). + SetSaveTests(saveTests) + if genHTMLReport { + runner.GenHTMLReport() + } + if !requestsLogOff { + runner.SetRequestsLogOn() + } + if httpStatOn { + runner.SetHTTPStatOn() + } + if pluginLogOn { + runner.SetPluginLogOn() + } + if venv != "" { + runner.SetPython3Venv(venv) + } + if proxyUrl != "" { + runner.SetProxyUrl(proxyUrl) + } + return runner +} diff --git a/hrp/internal/convert/converter.go b/hrp/internal/convert/converter.go index 5e5a446a..e1613679 100644 --- a/hrp/internal/convert/converter.go +++ b/hrp/internal/convert/converter.go @@ -105,9 +105,9 @@ func Run(outputType OutputType, outputDir, profilePath string, args []string) { // LoadTCase loads source file and convert to TCase type func LoadTCase(path string) (*hrp.TCase, error) { - if strings.HasPrefix(path, "curl") { + if strings.HasPrefix(path, "curl ") { // 'path' contains curl command - curlCase, err := LoadCurlCase(path) + curlCase, err := LoadSingleCurlCase(path) if err != nil { return nil, err } @@ -187,7 +187,7 @@ type TCaseConverter struct { func (c *TCaseConverter) genOutputPath(suffix string) string { var outFileFullName string if curlCmd := strings.TrimSpace(c.InputSample); strings.HasPrefix(curlCmd, "curl") { - outFileFullName = fmt.Sprintf("curl_%v_test_%v", time.Now().Format("20060102150405"), suffix) + outFileFullName = fmt.Sprintf("curl_%v_test%v", time.Now().Format("20060102150405"), suffix) if c.OutputDir != "" { return filepath.Join(c.OutputDir, outFileFullName) } else { diff --git a/hrp/internal/convert/from_curl.go b/hrp/internal/convert/from_curl.go index 57b7fc18..ca73b564 100644 --- a/hrp/internal/convert/from_curl.go +++ b/hrp/internal/convert/from_curl.go @@ -92,9 +92,9 @@ func init() { } } -func LoadCurlCase(inputSample string) (*hrp.TCase, error) { - var err error - cmds, err := builtin.ReadCmdLines(inputSample) +// LoadCurlCase loads testcase from one or more curl commands in .txt file +func LoadCurlCase(path string) (*hrp.TCase, error) { + cmds, err := builtin.ReadCmdLines(path) if err != nil { return nil, err } @@ -102,15 +102,11 @@ func LoadCurlCase(inputSample string) (*hrp.TCase, error) { Config: &hrp.TConfig{Name: "testcase converted from curl command"}, } for _, cmd := range cmds { - caseCurl, err := loadCaseCurl(cmd) + tSteps, err := LoadCurlSteps(cmd) if err != nil { return nil, err } - tStep, err := caseCurl.toTStep() - if err != nil { - return nil, err - } - tCase.TestSteps = append(tCase.TestSteps, tStep) + tCase.TestSteps = append(tCase.TestSteps, tSteps...) } err = tCase.MakeCompat() if err != nil { @@ -119,6 +115,32 @@ func LoadCurlCase(inputSample string) (*hrp.TCase, error) { return tCase, nil } +// LoadSingleCurlCase one testcase from one curl command +func LoadSingleCurlCase(cmd string) (*hrp.TCase, error) { + tSteps, err := LoadCurlSteps(cmd) + if err != nil { + return nil, err + } + tCase := &hrp.TCase{ + Config: &hrp.TConfig{Name: "testcase converted from curl command"}, + TestSteps: tSteps, + } + err = tCase.MakeCompat() + if err != nil { + return nil, err + } + return tCase, nil +} + +// LoadCurlSteps loads one teststep from one curl command +func LoadCurlSteps(cmd string) ([]*hrp.TStep, error) { + caseCurl, err := loadCaseCurl(cmd) + if err != nil { + return nil, err + } + return caseCurl.toTSteps() +} + func loadCaseCurl(cmd string) (CaseCurl, error) { caseCurl := make(CaseCurl) var err error @@ -146,15 +168,6 @@ func parseCaseCurl(cmd string) (CaseCurl, error) { return nil, err } - // deal with \n in the command string - //var cmdWords []string - //for _, w := range rawCmd { - // if w == "\n" { - // continue - // } - // cmdWords = append(cmdWords, strings.Trim(w, "\n)) - //} - // parse the command string to map res := make(CaseCurl) var i int @@ -165,7 +178,7 @@ func parseCaseCurl(cmd string) (CaseCurl, error) { for i < len(cmdWords) { if !strings.HasPrefix(cmdWords[i], "-") { // save target url - res.Set(targetUrlKey, cmdWords[i]) + res.Add(targetUrlKey, cmdWords[i]) i++ continue } @@ -185,17 +198,17 @@ func parseCaseCurl(cmd string) (CaseCurl, error) { type CaseCurl map[string][]string -// GetFirst gets the first value associated with the given key. -// If there are no values associated with the key, GetFirst returns the empty string. -func (c CaseCurl) GetFirst(key string) string { +// GetByIndex gets the value by index associated with the given key. +// If there are no value by index associated with the key, GetByIndex returns the empty string. +func (c CaseCurl) GetByIndex(key string, index int) string { if c == nil { return "" } vs := c[key] - if len(vs) == 0 { - return "" + if index >= 0 && index < len(vs) { + return vs[index] } - return vs[0] + return "" } func (c CaseCurl) Set(key, value string) { @@ -215,19 +228,6 @@ func (c CaseCurl) HaveKey(key string) bool { return ok } -// HaveKeyWithPrefix checks key with prefix existed or not -func (c CaseCurl) HaveKeyWithPrefix(prefix string) bool { - if c == nil { - return false - } - for k := range c { - if strings.HasPrefix(k, prefix) { - return true - } - } - return false -} - func (c CaseCurl) toAlias() error { for option, args := range c { if !strings.HasPrefix(option, "-") || strings.HasPrefix(option, "--") { @@ -258,53 +258,42 @@ func (c CaseCurl) checkOptions() error { return nil } -func (c CaseCurl) ToTCase() (*hrp.TCase, error) { - testSteps, err := c.toTStep() - if err != nil { - return nil, err - } - tCase := &hrp.TCase{ - Config: &hrp.TConfig{Name: "testcase converted from curl command"}, - TestSteps: []*hrp.TStep{testSteps}, - } - err = tCase.MakeCompat() - if err != nil { - return nil, err - } - return tCase, nil -} +func (c CaseCurl) toTSteps() ([]*hrp.TStep, error) { + var tSteps []*hrp.TStep + for _, rawUrl := range c[targetUrlKey] { + log.Info(). + Str("url", rawUrl). + Msg("convert test steps") -func (c CaseCurl) toTStep() (*hrp.TStep, error) { - log.Info(). - Str("cmd", c.GetFirst(originCmdKey)). - Msg("convert teststep") - step := &stepFromCurl{ - TStep: &hrp.TStep{ - Request: &hrp.Request{}, - }, + step := &stepFromCurl{ + TStep: &hrp.TStep{ + Request: &hrp.Request{}, + }, + } + if err := step.makeRequestName(c); err != nil { + return nil, err + } + if err := step.makeRequestMethod(c); err != nil { + return nil, err + } + if err := step.makeRequestURL(rawUrl); err != nil { + return nil, err + } + if err := step.makeRequestParams(rawUrl); err != nil { + return nil, err + } + if err := step.makeRequestHeaders(c); err != nil { + return nil, err + } + if err := step.makeRequestCookies(c); err != nil { + return nil, err + } + if err := step.makeRequestBody(c); err != nil { + return nil, err + } + tSteps = append(tSteps, step.TStep) } - if err := step.makeRequestName(c); err != nil { - return nil, err - } - if err := step.makeRequestMethod(c); err != nil { - return nil, err - } - if err := step.makeRequestURL(c); err != nil { - return nil, err - } - if err := step.makeRequestParams(c); err != nil { - return nil, err - } - if err := step.makeRequestHeaders(c); err != nil { - return nil, err - } - if err := step.makeRequestCookies(c); err != nil { - return nil, err - } - if err := step.makeRequestBody(c); err != nil { - return nil, err - } - return step.TStep, nil + return tSteps, nil } type stepFromCurl struct { @@ -312,7 +301,7 @@ type stepFromCurl struct { } func (s *stepFromCurl) makeRequestName(c CaseCurl) error { - s.Name = c.GetFirst(originCmdKey) + s.Name = c.GetByIndex(originCmdKey, 0) return nil } @@ -326,16 +315,12 @@ func (s *stepFromCurl) makeRequestMethod(c CaseCurl) error { s.Request.Method = http.MethodHead } if c.HaveKey("--request") { - s.Request.Method = hrp.HTTPMethod(strings.ToUpper(c.GetFirst("--request"))) + s.Request.Method = hrp.HTTPMethod(strings.ToUpper(c.GetByIndex("--request", 0))) } return nil } -func (s *stepFromCurl) makeRequestURL(c CaseCurl) error { - rawUrl := c.GetFirst(targetUrlKey) - if rawUrl == "" { - return errors.New("URL not found") - } +func (s *stepFromCurl) makeRequestURL(rawUrl string) error { u, err := url.Parse(rawUrl) if err != nil { return errors.Wrap(err, "parse URL error") @@ -348,9 +333,8 @@ func (s *stepFromCurl) makeRequestURL(c CaseCurl) error { return nil } -func (s *stepFromCurl) makeRequestParams(c CaseCurl) error { +func (s *stepFromCurl) makeRequestParams(rawUrl string) error { s.Request.Params = make(map[string]interface{}) - rawUrl := c.GetFirst(targetUrlKey) u, err := url.Parse(rawUrl) if err != nil { return errors.Wrap(err, "parse URL error") diff --git a/hrp/testcase.go b/hrp/testcase.go index 6bc6de4e..afe03713 100644 --- a/hrp/testcase.go +++ b/hrp/testcase.go @@ -60,11 +60,45 @@ func (path *TestCasePath) ToTestCase() (*TestCase, error) { if err != nil { return nil, err } + return tc.ToTestCase(casePath) +} + +// TCase represents testcase data structure. +// Each testcase includes one public config and several sequential teststeps. +type TCase struct { + Config *TConfig `json:"config" yaml:"config"` + TestSteps []*TStep `json:"teststeps" yaml:"teststeps"` +} + +// MakeCompat converts TCase compatible with Golang engine style +func (tc *TCase) MakeCompat() (err error) { + defer func() { + if p := recover(); p != nil { + err = fmt.Errorf("[MakeCompat] convert compat testcase error: %v", p) + } + }() + for _, step := range tc.TestSteps { + // 1. deal with request body compatibility + convertCompatRequestBody(step.Request) + + // 2. deal with validators compatibility + err = convertCompatValidator(step.Validators) + if err != nil { + return err + } + + // 3. deal with extract expr including hyphen + convertExtract(step.Extract) + } + return nil +} + +func (tc *TCase) ToTestCase(casePath string) (*TestCase, error) { if tc.TestSteps == nil { return nil, errors.New("invalid testcase format, missing teststeps!") } - err = tc.MakeCompat() + err := tc.MakeCompat() if err != nil { return nil, err } @@ -173,36 +207,6 @@ func (path *TestCasePath) ToTestCase() (*TestCase, error) { return testCase, nil } -// TCase represents testcase data structure. -// Each testcase includes one public config and several sequential teststeps. -type TCase struct { - Config *TConfig `json:"config" yaml:"config"` - TestSteps []*TStep `json:"teststeps" yaml:"teststeps"` -} - -// MakeCompat converts TCase compatible with Golang engine style -func (tc *TCase) MakeCompat() (err error) { - defer func() { - if p := recover(); p != nil { - err = fmt.Errorf("[MakeCompat] convert compat testcase error: %v", p) - } - }() - for _, step := range tc.TestSteps { - // 1. deal with request body compatibility - convertCompatRequestBody(step.Request) - - // 2. deal with validators compatibility - err = convertCompatValidator(step.Validators) - if err != nil { - return err - } - - // 3. deal with extract expr including hyphen - convertExtract(step.Extract) - } - return nil -} - func convertCompatRequestBody(request *Request) { if request != nil && request.Body == nil { if request.Json != nil {