Merge pull request #52 from httprunner/scaffold

- feat: create scaffold project
- fix: report GA event with value to indicate failure
This commit is contained in:
debugtalk
2022-01-08 22:58:21 +08:00
committed by GitHub
27 changed files with 274 additions and 89 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }} goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }} goarch: ${{ matrix.goarch }}
project_path: "./hrp" # go build ./hrp/main.go project_path: "./cli/hrp" # go build ./cli/hrp/main.go
binary_name: "hrp" binary_name: "hrp"
ldflags: "-s -w" ldflags: "-s -w"
extra_files: LICENSE README.md docs/CHANGELOG.md extra_files: LICENSE README.md docs/CHANGELOG.md
+28
View File
@@ -0,0 +1,28 @@
name: Run scaffold
on:
pull_request:
types: [synchronize]
jobs:
scaffold:
strategy:
fail-fast: true
matrix:
go-version:
- 1.17.x
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Build hrp binary
run: make build
- name: Run start project
run: ./output/hrp startproject demo
- name: Run demo tests
run: ./output/hrp run demo/testcases/demo.json demo/testcases/demo.yaml
-1
View File
@@ -1,7 +1,6 @@
name: Run unittests name: Run unittests
on: on:
push:
pull_request: pull_request:
types: [synchronize] types: [synchronize]
schedule: schedule:
+1 -1
View File
@@ -11,7 +11,7 @@ test: ## run unit tests
.PHONY: build .PHONY: build
build: ## build hrp cli tool build: ## build hrp cli tool
@echo "[info] build hrp cli tool" @echo "[info] build hrp cli tool"
@. hrp/scripts/build.sh @. cli/scripts/build.sh
.PHONY: help .PHONY: help
help: ## print make commands help: ## print make commands
+8 -7
View File
@@ -29,13 +29,13 @@ See [CHANGELOG].
You can install `hrp` with one shell command, which will download the latest version's released binary and install to the current system. You can install `hrp` with one shell command, which will download the latest version's released binary and install to the current system.
```bash ```bash
$ curl -sL https://raw.githubusercontent.com/httprunner/hrp/main/hrp/scripts/install.sh | bash $ curl -sL https://raw.githubusercontent.com/httprunner/hrp/main/cli/scripts/install.sh | bash
``` ```
If you are a golang developer, you can also install `hrp` with `go get`. If you are a golang developer, you can also install `hrp` with `go get`.
```bash ```bash
$ go get -u github.com/httprunner/hrp/hrp $ go get -u github.com/httprunner/hrp/cli/hrp
``` ```
Since installed, you will get a `hrp` command with multiple sub-commands. Since installed, you will get a `hrp` command with multiple sub-commands.
@@ -62,11 +62,12 @@ Usage:
hrp [command] hrp [command]
Available Commands: Available Commands:
boom run load test with boomer boom run load test with boomer
completion generate the autocompletion script for the specified shell completion generate the autocompletion script for the specified shell
har2case Convert HAR to json/yaml testcase files har2case convert HAR to json/yaml testcase files
help Help about any command help Help about any command
run run API test run run API test
startproject create a scaffold project
Flags: Flags:
-h, --help help for hrp -h, --help help for hrp
View File
+1 -1
View File
@@ -55,7 +55,7 @@ var (
) )
func init() { func init() {
RootCmd.AddCommand(boomCmd) rootCmd.AddCommand(boomCmd)
boomCmd.Flags().Int64Var(&maxRPS, "max-rps", 0, "Max RPS that boomer can generate, disabled by default.") boomCmd.Flags().Int64Var(&maxRPS, "max-rps", 0, "Max RPS that boomer can generate, disabled by default.")
boomCmd.Flags().StringVar(&requestIncreaseRate, "request-increase-rate", "-1", "Request increase rate, disabled by default.") boomCmd.Flags().StringVar(&requestIncreaseRate, "request-increase-rate", "-1", "Request increase rate, disabled by default.")
@@ -4,13 +4,11 @@ import (
"testing" "testing"
"github.com/spf13/cobra/doc" "github.com/spf13/cobra/doc"
"github.com/httprunner/hrp/hrp/cmd"
) )
// run this test to generate markdown docs // run this test to generate markdown docs for hrp command
func TestGenMarkdownTree(t *testing.T) { func TestGenMarkdownTree(t *testing.T) {
err := doc.GenMarkdownTree(cmd.RootCmd, "./") err := doc.GenMarkdownTree(rootCmd, "../../../docs/cmd")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -9,9 +9,9 @@ import (
// har2caseCmd represents the har2case command // har2caseCmd represents the har2case command
var har2caseCmd = &cobra.Command{ var har2caseCmd = &cobra.Command{
Use: "har2case harPath...", Use: "har2case $har_path...",
Short: "Convert HAR to json/yaml testcase files", Short: "convert HAR to json/yaml testcase files",
Long: `Convert HAR to json/yaml testcase files`, Long: `convert HAR to json/yaml testcase files`,
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
PreRun: func(cmd *cobra.Command, args []string) { PreRun: func(cmd *cobra.Command, args []string) {
setLogLevel(logLevel) setLogLevel(logLevel)
@@ -52,7 +52,7 @@ var (
) )
func init() { func init() {
RootCmd.AddCommand(har2caseCmd) rootCmd.AddCommand(har2caseCmd)
har2caseCmd.Flags().BoolVarP(&genJSONFlag, "to-json", "j", false, "convert to JSON format (default)") har2caseCmd.Flags().BoolVarP(&genJSONFlag, "to-json", "j", false, "convert to JSON format (default)")
har2caseCmd.Flags().BoolVarP(&genYAMLFlag, "to-yaml", "y", false, "convert to JSON format") har2caseCmd.Flags().BoolVarP(&genYAMLFlag, "to-yaml", "y", false, "convert to JSON format")
har2caseCmd.Flags().StringVarP(&outputDir, "output-dir", "d", "", "specify output directory, default to the same dir with har file") har2caseCmd.Flags().StringVarP(&outputDir, "output-dir", "d", "", "specify output directory, default to the same dir with har file")
+5 -5
View File
@@ -11,8 +11,8 @@ import (
"github.com/httprunner/hrp/internal/version" "github.com/httprunner/hrp/internal/version"
) )
// RootCmd represents the base command when called without any subcommands // rootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "hrp", Use: "hrp",
Short: "One-stop solution for HTTP(S) testing.", Short: "One-stop solution for HTTP(S) testing.",
Long: ` Long: `
@@ -47,10 +47,10 @@ var (
// Execute adds all child commands to the root command and sets flags appropriately. // Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd. // This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() { func Execute() {
RootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", "INFO", "set log level") rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", "INFO", "set log level")
RootCmd.PersistentFlags().BoolVar(&logJSON, "log-json", false, "set log to json format") rootCmd.PersistentFlags().BoolVar(&logJSON, "log-json", false, "set log to json format")
if err := RootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
os.Exit(1) os.Exit(1)
} }
} }
+2 -2
View File
@@ -10,7 +10,7 @@ import (
// runCmd represents the run command // runCmd represents the run command
var runCmd = &cobra.Command{ var runCmd = &cobra.Command{
Use: "run path...", Use: "run $path...",
Short: "run API test", Short: "run API test",
Long: `run yaml/json testcase files for API test`, Long: `run yaml/json testcase files for API test`,
Example: ` $ hrp run demo.json # run specified json testcase file Example: ` $ hrp run demo.json # run specified json testcase file
@@ -45,7 +45,7 @@ var (
) )
func init() { func init() {
RootCmd.AddCommand(runCmd) rootCmd.AddCommand(runCmd)
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")
+28
View File
@@ -0,0 +1,28 @@
package cmd
import (
"os"
"github.com/spf13/cobra"
"github.com/httprunner/hrp/internal/scaffold"
)
var scaffoldCmd = &cobra.Command{
Use: "startproject $project_name",
Short: "create a scaffold project",
Args: cobra.ExactValidArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
setLogLevel(logLevel)
},
Run: func(cmd *cobra.Command, args []string) {
err := scaffold.CreateScaffold(args[0])
if err != nil {
os.Exit(1)
}
},
}
func init() {
rootCmd.AddCommand(scaffoldCmd)
}
+1 -1
View File
@@ -1,7 +1,7 @@
package main package main
import ( import (
"github.com/httprunner/hrp/hrp/cmd" "github.com/httprunner/hrp/cli/hrp/cmd"
"github.com/httprunner/hrp/internal/sentry" "github.com/httprunner/hrp/internal/sentry"
) )
@@ -5,7 +5,7 @@
# Usage: # Usage:
# $ make build # $ make build
# or # or
# $ bash hrp/scripts/build.sh # $ bash cli/scripts/build.sh
set -e set -e
set -x set -x
@@ -15,7 +15,7 @@ mkdir -p "output"
bin_path="output/hrp" bin_path="output/hrp"
# build # build
go build -ldflags '-s -w' -o "$bin_path" hrp/main.go go build -ldflags '-s -w' -o "$bin_path" cli/hrp/main.go
# check output and version # check output and version
ls -lh "$bin_path" ls -lh "$bin_path"
@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# install hrp with one shell command # install hrp with one shell command
# curl -sL https://raw.githubusercontent.com/httprunner/hrp/main/hrp/scripts/install.sh | bash # curl -sL https://raw.githubusercontent.com/httprunner/hrp/main/cli/scripts/install.sh | bash
set -e set -e
+2 -1
View File
@@ -1,9 +1,10 @@
# Release History # Release History
## v0.5.0 (2022-01-06) ## v0.5.0 (2022-01-08)
- feat: support creating and calling custom functions with [go plugin](https://pkg.go.dev/plugin) - feat: support creating and calling custom functions with [go plugin](https://pkg.go.dev/plugin)
- feat: install hrp with one shell command - feat: install hrp with one shell command
- feat: add `startproject` sub-command for creating scaffold project
- feat: report GA event for loading go plugin - feat: report GA event for loading go plugin
## v0.4.0 (2022-01-05) ## v0.4.0 (2022-01-05)
+2 -1
View File
@@ -31,5 +31,6 @@ Copyright 2021 debugtalk
* [hrp boom](hrp_boom.md) - run load test with boomer * [hrp boom](hrp_boom.md) - run load test with boomer
* [hrp har2case](hrp_har2case.md) - Convert HAR to json/yaml testcase files * [hrp har2case](hrp_har2case.md) - Convert HAR to json/yaml testcase files
* [hrp run](hrp_run.md) - run API test * [hrp run](hrp_run.md) - run API test
* [hrp startproject](hrp_startproject.md) - Create a scaffold project
###### Auto generated by spf13/cobra on 7-Jan-2022 ###### Auto generated by spf13/cobra on 8-Jan-2022
+1 -1
View File
@@ -38,4 +38,4 @@ hrp boom [flags]
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing. * [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
###### Auto generated by spf13/cobra on 7-Jan-2022 ###### Auto generated by spf13/cobra on 8-Jan-2022
+2 -2
View File
@@ -7,7 +7,7 @@ Convert HAR to json/yaml testcase files
Convert HAR to json/yaml testcase files Convert HAR to json/yaml testcase files
``` ```
hrp har2case harPath... [flags] hrp har2case $har_path... [flags]
``` ```
### Options ### Options
@@ -23,4 +23,4 @@ hrp har2case harPath... [flags]
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing. * [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
###### Auto generated by spf13/cobra on 7-Jan-2022 ###### Auto generated by spf13/cobra on 8-Jan-2022
+2 -2
View File
@@ -7,7 +7,7 @@ run API test
run yaml/json testcase files for API test run yaml/json testcase files for API test
``` ```
hrp run path... [flags] hrp run $path... [flags]
``` ```
### Examples ### Examples
@@ -31,4 +31,4 @@ hrp run path... [flags]
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing. * [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
###### Auto generated by spf13/cobra on 7-Jan-2022 ###### Auto generated by spf13/cobra on 8-Jan-2022
+19
View File
@@ -0,0 +1,19 @@
## hrp startproject
Create a scaffold project
```
hrp startproject $project_name [flags]
```
### Options
```
-h, --help help for startproject
```
### SEE ALSO
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
###### Auto generated by spf13/cobra on 8-Jan-2022
+2 -2
View File
@@ -8,7 +8,7 @@ func TestSendEvents(t *testing.T) {
event := EventTracking{ event := EventTracking{
Category: "unittest", Category: "unittest",
Action: "SendEvents", Action: "SendEvents",
Value: "123", Value: 123,
} }
err := gaClient.SendEvent(event) err := gaClient.SendEvent(event)
if err != nil { if err != nil {
@@ -21,7 +21,7 @@ func TestStructToUrlValues(t *testing.T) {
Category: "unittest", Category: "unittest",
Action: "convert", Action: "convert",
Label: "v0.3.0", Label: "v0.3.0",
Value: "123", Value: 123,
} }
val := structToUrlValues(event) val := structToUrlValues(event)
if val.Encode() != "ea=convert&ec=unittest&el=v0.3.0&ev=123" { if val.Encode() != "ea=convert&ec=unittest&el=v0.3.0&ev=123" {
+1 -1
View File
@@ -17,7 +17,7 @@ type EventTracking struct {
Category string `form:"ec"` // Required. Event Category. Category string `form:"ec"` // Required. Event Category.
Action string `form:"ea"` // Required. Event Action. Action string `form:"ea"` // Required. Event Action.
Label string `form:"el"` // Optional. Event label, used as version. Label string `form:"el"` // Optional. Event label, used as version.
Value string `form:"ev"` // Optional. Event value, must be digits, "123" Value int `form:"ev"` // Optional. Event value, must be non-negative integer
} }
func (e EventTracking) StartTiming(variable string) UserTimingTracking { func (e EventTracking) StartTiming(variable string) UserTimingTracking {
@@ -1,11 +1,6 @@
package examples package scaffold
import ( import "github.com/httprunner/hrp"
"fmt"
"testing"
"github.com/httprunner/hrp"
)
var demoTestCase = &hrp.TestCase{ var demoTestCase = &hrp.TestCase{
Config: hrp.NewConfig("demo with complex mechanisms"). Config: hrp.NewConfig("demo with complex mechanisms").
@@ -61,42 +56,17 @@ var demoTestCase = &hrp.TestCase{
}, },
} }
var ( // .gitignore
demoTestCaseJSONPath = "demo.json" var demoIgnoreContent = `.env
demoTestCaseYAMLPath = "demo.yaml" reports/*
) *.so
.vscode/
.idea/
.DS_Store
output/
`
func TestGenDemoTestCase(t *testing.T) { // .env
tCase, _ := demoTestCase.ToTCase() var demoEnvContent = `USERNAME=debugtalk
err := tCase.Dump2JSON(demoTestCaseJSONPath) "PASSWORD=123456
if err != nil { `
t.Fail()
}
err = tCase.Dump2YAML(demoTestCaseYAMLPath)
if err != nil {
t.Fail()
}
}
func Example_demo() {
err := hrp.NewRunner(nil).Run(demoTestCase) // hrp.Run(demoTestCase)
fmt.Println(err)
// Output:
// <nil>
}
func Example_jsonDemo() {
testCase := &hrp.TestCasePath{Path: demoTestCaseJSONPath}
err := hrp.NewRunner(nil).Run(testCase) // hrp.Run(testCase)
fmt.Println(err)
// Output:
// <nil>
}
func Example_yamlDemo() {
testCase := &hrp.TestCasePath{Path: demoTestCaseYAMLPath}
err := hrp.NewRunner(nil).Run(testCase) // hrp.Run(testCase)
fmt.Println(err)
// Output:
// <nil>
}
+48
View File
@@ -0,0 +1,48 @@
package scaffold
import (
"fmt"
"testing"
"github.com/httprunner/hrp"
)
var (
demoTestCaseJSONPath = "../../examples/demo.json"
demoTestCaseYAMLPath = "../../examples/demo.yaml"
)
func TestGenDemoTestCase(t *testing.T) {
tCase, _ := demoTestCase.ToTCase()
err := tCase.Dump2JSON(demoTestCaseJSONPath)
if err != nil {
t.Fail()
}
err = tCase.Dump2YAML(demoTestCaseYAMLPath)
if err != nil {
t.Fail()
}
}
func Example_demo() {
err := hrp.NewRunner(nil).Run(demoTestCase) // hrp.Run(demoTestCase)
fmt.Println(err)
// Output:
// <nil>
}
func Example_jsonDemo() {
testCase := &hrp.TestCasePath{Path: demoTestCaseJSONPath}
err := hrp.NewRunner(nil).Run(testCase) // hrp.Run(testCase)
fmt.Println(err)
// Output:
// <nil>
}
func Example_yamlDemo() {
testCase := &hrp.TestCasePath{Path: demoTestCaseYAMLPath}
err := hrp.NewRunner(nil).Run(testCase) // hrp.Run(testCase)
fmt.Println(err)
// Output:
// <nil>
}
+86
View File
@@ -0,0 +1,86 @@
package scaffold
import (
"fmt"
"io/ioutil"
"os"
"path"
"github.com/httprunner/hrp/internal/ga"
"github.com/rs/zerolog/log"
)
func CreateScaffold(projectName string) error {
// report event
ga.SendEvent(ga.EventTracking{
Category: "Scaffold",
Action: "hrp startproject",
})
// check if projectName exists
if _, err := os.Stat(projectName); err == nil {
log.Warn().Str("projectName", projectName).
Msg("project name already exists, please specify a new one.")
return fmt.Errorf("project name already exists")
}
log.Info().Str("projectName", projectName).Msg("create new scaffold project")
// create project folders
if err := createFolder(projectName); err != nil {
return err
}
if err := createFolder(path.Join(projectName, "har")); err != nil {
return err
}
if err := createFolder(path.Join(projectName, "testcases")); err != nil {
return err
}
if err := createFolder(path.Join(projectName, "reports")); err != nil {
return err
}
// create demo testcases
tCase, _ := demoTestCase.ToTCase()
err := tCase.Dump2JSON(path.Join(projectName, "testcases", "demo.json"))
if err != nil {
log.Error().Err(err).Msg("create demo.json testcase failed")
return err
}
err = tCase.Dump2YAML(path.Join(projectName, "testcases", "demo.yaml"))
if err != nil {
log.Error().Err(err).Msg("create demo.yml testcase failed")
return err
}
// create .gitignore
if err := createFile(path.Join(projectName, ".gitignore"), demoIgnoreContent); err != nil {
return err
}
// create .env
if err := createFile(path.Join(projectName, ".env"), demoEnvContent); err != nil {
return err
}
return nil
}
func createFolder(folderPath string) error {
log.Info().Str("path", folderPath).Msg("create folder")
err := os.MkdirAll(folderPath, os.ModePerm)
if err != nil {
log.Error().Err(err).Msg("create folder failed")
return err
}
return nil
}
func createFile(filePath string, data string) error {
log.Info().Str("path", filePath).Msg("create file")
err := ioutil.WriteFile(filePath, []byte(data), 0o644)
if err != nil {
log.Error().Err(err).Msg("create file failed")
return err
}
return nil
}
+10 -4
View File
@@ -35,10 +35,16 @@ func (p *parser) loadPlugin(path string) error {
} }
// report event for loading go plugin // report event for loading go plugin
go ga.SendEvent(ga.EventTracking{ defer func() {
Category: "LoadGoPlugin", event := ga.EventTracking{
Action: "plugin.Open", Category: "LoadGoPlugin",
}) Action: "plugin.Open",
}
if err != nil {
event.Value = 1 // failed
}
go ga.SendEvent(event)
}()
// load plugin // load plugin
plugins, err := plugin.Open(pluginPath) plugins, err := plugin.Open(pluginPath)