diff --git a/.github/workflows/hrp-scaffold.yml b/.github/workflows/hrp-scaffold.yml index 0bf56c20..b3a5931e 100644 --- a/.github/workflows/hrp-scaffold.yml +++ b/.github/workflows/hrp-scaffold.yml @@ -26,7 +26,7 @@ jobs: - name: Run start project run: ./output/hrp startproject demo - name: Run generated demo tests - run: ./output/hrp run demo/testcases/demo_with_funplugin.json demo/testcases/demo_requests.yml demo/testcases/demo_ref_testcase.yml + run: ./output/hrp run demo/testcases/ - name: Run demo in examples run: | ./output/hrp run examples/demo-with-py-plugin/testcases/demo_with_funplugin.json @@ -51,7 +51,7 @@ jobs: - name: Run start project run: ./output/hrp startproject demo --go - name: Run generated demo tests - run: ./output/hrp run demo/testcases/demo_with_funplugin.json demo/testcases/demo_requests.yml demo/testcases/demo_ref_testcase.yml + run: ./output/hrp run demo/testcases/ - name: Run demo in examples run: | go build -o examples/demo-with-go-plugin/debugtalk.bin examples/demo-with-go-plugin/plugin/debugtalk.go diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index c726e78b..3c7931d9 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -7,6 +7,7 @@ **go version** - feat: add `--profile` flag for har2case to support overwrite headers/cookies with specified yaml/json profile file +- feat: support run testcases in specified folder path - change: integrate [sentry sdk][sentry sdk] for panic reporting and analysis - change: lock funplugin version when creating scaffold project - fix: call referenced api/testcase with relative path @@ -30,8 +31,8 @@ ## hrp-v0.7.0 (2022-03-15) -- feat: support API layer for testcase #94 -- feat: support global headers for testcase #95 +- feat: support API layer for testcase +- feat: support global headers for testcase - feat: support call referenced testcase by path in YAML/JSON testcases - fix: decode failure when content-encoding is deflate - fix: unstable RPS when load testing in high concurrency diff --git a/hrp/boomer.go b/hrp/boomer.go index 9d0cf667..8d49296f 100644 --- a/hrp/boomer.go +++ b/hrp/boomer.go @@ -38,11 +38,14 @@ func (b *HRPBoomer) Run(testcases ...ITestCase) { defer sdk.SendEvent(event.StartTiming("execution")) var taskSlice []*boomer.Task - for _, iTestCase := range testcases { - testcase, err := iTestCase.ToTestCase() - if err != nil { - panic(err) - } + + // load all testcases + testCases, err := loadTestCases(testcases...) + if err != nil { + panic(err) + } + + for _, testcase := range testCases { cfg := testcase.Config err = initParameterIterator(cfg, "boomer") if err != nil { diff --git a/hrp/convert.go b/hrp/convert.go index 3d09b025..a7be579c 100644 --- a/hrp/convert.go +++ b/hrp/convert.go @@ -116,7 +116,7 @@ func (tc *TCase) ToTestCase() (*TestCase, error) { var apiFullPath string if apiPath, ok := step.API.(string); ok { apiFullPath = filepath.Join(projectRootDir, apiPath) - } else if apiPath, ok := step.API.(APIPath); ok { + } else if apiPath, ok := step.API.(*APIPath); ok { apiFullPath = filepath.Join(projectRootDir, apiPath.GetPath()) } else { return nil, errors.New("invalid api format") diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index d1701bb2..6f8b4442 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -132,6 +132,18 @@ func IsFilePathExists(path string) bool { return true } +// IsFolderPathExists returns true if path exists and path is folder +func IsFolderPathExists(path string) bool { + info, err := os.Stat(path) + if err != nil { + // path not exists + return false + } + + // path exists and is dir + return info.IsDir() +} + func EnsureFolderExists(folderPath string) error { if !IsPathExists(folderPath) { err := CreateFolder(folderPath) diff --git a/hrp/runner.go b/hrp/runner.go index 34d030ce..cafe83c5 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -10,6 +10,7 @@ import ( "fmt" "html/template" "io" + "io/ioutil" "net" "net/http" "net/http/httputil" @@ -148,15 +149,17 @@ func (r *HRPRunner) Run(testcases ...ITestCase) error { defer sdk.SendEvent(event.StartTiming("execution")) // record execution data to summary s := newOutSummary() - for _, iTestCase := range testcases { - testcase, err := iTestCase.ToTestCase() - if err != nil { - log.Error().Err(err).Msg("[Run] convert ITestCase interface to TestCase struct failed") - return err - } + + // load all testcases + testCases, err := loadTestCases(testcases...) + if err != nil { + return err + } + + for _, testcase := range testCases { cfg := testcase.Config // parse config parameters - err = initParameterIterator(cfg, "runner") + err := initParameterIterator(cfg, "runner") if err != nil { log.Error().Interface("parameters", cfg.Parameters).Err(err).Msg("parse config parameters failed") return err @@ -201,6 +204,56 @@ func (r *HRPRunner) Run(testcases ...ITestCase) error { return nil } +func loadTestCases(iTestCases ...ITestCase) ([]*TestCase, error) { + testCases := make([]*TestCase, 0) + + for _, iTestCase := range iTestCases { + if _, ok := iTestCase.(*TestCase); ok { + testcase, err := iTestCase.ToTestCase() + if err != nil { + log.Error().Err(err).Msg("failed to convert ITestCase interface to TestCase struct") + return nil, err + } + testCases = append(testCases, testcase) + continue + } + + // iTestCase should be a TestCasePath, file path or folder path + testCasePath, ok := iTestCase.(*TestCasePath) + if !ok { + return nil, errors.New("invalid iTestCase type") + } + + casePaths := make([]*TestCasePath, 0) + casePath := iTestCase.GetPath() + if builtin.IsFolderPathExists(casePath) { + // folder path + files, err := ioutil.ReadDir(casePath) + if err != nil { + return nil, errors.Wrap(err, "failed to read dir") + } + for _, f := range files { + path := TestCasePath(filepath.Join(casePath, f.Name())) + casePaths = append(casePaths, &path) + } + } else { + // file path + casePaths = append(casePaths, testCasePath) + } + + for _, path := range casePaths { + tc, err := path.ToTestCase() + if err != nil { + continue + } + testCases = append(testCases, tc) + } + } + + log.Info().Int("count", len(testCases)).Msg("load testcases successfully") + return testCases, nil +} + func (r *HRPRunner) newCaseRunner(testcase *TestCase) *caseRunner { caseRunner := &caseRunner{ TestCase: testcase, diff --git a/hrp/runner_test.go b/hrp/runner_test.go index dcab96c5..2b7c41b4 100644 --- a/hrp/runner_test.go +++ b/hrp/runner_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/rs/zerolog/log" + "github.com/stretchr/testify/assert" "github.com/httprunner/httprunner/hrp/internal/scaffold" ) @@ -283,3 +284,37 @@ func TestRunCaseWithRefAPI(t *testing.T) { t.Fail() } } + +func TestLoadTestCases(t *testing.T) { + // load test cases from folder path + tc := TestCasePath(templatesDir + "testcases") + testCases, err := loadTestCases(&tc) + if !assert.Nil(t, err) { + t.Fail() + } + if !assert.GreaterOrEqual(t, len(testCases), 5) { + t.Fail() + } + + // load test cases from single file path + tc = demoTestCaseWithPluginJSONPath + testCases, err = loadTestCases(&tc) + if !assert.Nil(t, err) { + t.Fail() + } + if !assert.Equal(t, len(testCases), 1) { + t.Fail() + } + + // load test cases from TestCase instance + testcase := &TestCase{ + Config: NewConfig("TestCase").SetWeight(3), + } + testCases, err = loadTestCases(testcase) + if !assert.Nil(t, err) { + t.Fail() + } + if !assert.Equal(t, len(testCases), 1) { + t.Fail() + } +}