mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-11 18:11:21 +08:00
fix: typo
change: do not add console output by default feat: add GA for docs refactor: move builtin to internal refactor: relocate sentry sdk feat: report events with ga change: use http client session fix: report GA events change: sentry
This commit is contained in:
2
.github/workflows/unittest.yml
vendored
2
.github/workflows/unittest.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Run unittest with coverage
|
||||
name: Run unittests
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
12
boomer.go
12
boomer.go
@@ -4,6 +4,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/myzhan/boomer"
|
||||
|
||||
"github.com/httprunner/hrp/internal/ga"
|
||||
)
|
||||
|
||||
func NewStandaloneBoomer(spawnCount int, spawnRate float64) *Boomer {
|
||||
@@ -11,7 +13,6 @@ func NewStandaloneBoomer(spawnCount int, spawnRate float64) *Boomer {
|
||||
Boomer: boomer.NewStandaloneBoomer(spawnCount, spawnRate),
|
||||
debug: false,
|
||||
}
|
||||
b.AddOutput(boomer.NewConsoleOutput())
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -26,6 +27,15 @@ func (b *Boomer) SetDebug(debug bool) *Boomer {
|
||||
}
|
||||
|
||||
func (b *Boomer) Run(testcases ...ITestCase) {
|
||||
event := ga.EventTracking{
|
||||
Category: "RunLoadTests",
|
||||
Action: "hrp boom",
|
||||
}
|
||||
// report start event
|
||||
go ga.SendEvent(event)
|
||||
// report execution timing event
|
||||
defer ga.SendEvent(event.StartTiming("execution"))
|
||||
|
||||
var taskSlice []*boomer.Task
|
||||
for _, iTestCase := range testcases {
|
||||
testcase, err := iTestCase.ToTestCase()
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Release History
|
||||
|
||||
## v0.2.1 (2021-11-26)
|
||||
|
||||
- feat: report events with Google Analytics
|
||||
|
||||
## v0.2.0 (2021-11-19)
|
||||
|
||||
- feat: deploy mkdocs to github pages when PR merged
|
||||
|
||||
@@ -172,7 +172,7 @@ import (
|
||||
)
|
||||
|
||||
func TestCaseDemo(t *testing.T) {
|
||||
testcase := &hrp.TestCase{
|
||||
demoTestCase := &hrp.TestCase{
|
||||
Config: hrp.TConfig{
|
||||
Name: "demo with complex mechanisms",
|
||||
BaseURL: "https://postman-echo.com",
|
||||
|
||||
@@ -22,4 +22,4 @@ Copyright 2021 debugtalk
|
||||
* [hrp har2case](hrp_har2case.md) - Convert HAR to json/yaml testcase files
|
||||
* [hrp run](hrp_run.md) - run API test
|
||||
|
||||
###### Auto generated by spf13/cobra on 19-Nov-2021
|
||||
###### Auto generated by spf13/cobra on 26-Nov-2021
|
||||
|
||||
@@ -37,4 +37,4 @@ hrp boom [flags]
|
||||
|
||||
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
||||
|
||||
###### Auto generated by spf13/cobra on 19-Nov-2021
|
||||
###### Auto generated by spf13/cobra on 26-Nov-2021
|
||||
|
||||
@@ -23,4 +23,4 @@ hrp har2case harPath... [flags]
|
||||
|
||||
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
||||
|
||||
###### Auto generated by spf13/cobra on 19-Nov-2021
|
||||
###### Auto generated by spf13/cobra on 26-Nov-2021
|
||||
|
||||
@@ -30,4 +30,4 @@ hrp run path... [flags]
|
||||
|
||||
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
||||
|
||||
###### Auto generated by spf13/cobra on 19-Nov-2021
|
||||
###### Auto generated by spf13/cobra on 26-Nov-2021
|
||||
|
||||
1
go.mod
1
go.mod
@@ -5,6 +5,7 @@ go 1.16
|
||||
require (
|
||||
github.com/getsentry/sentry-go v0.11.0
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/jinzhu/copier v0.3.2
|
||||
github.com/jmespath/go-jmespath v0.4.0
|
||||
github.com/maja42/goval v1.2.1
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/httprunner/hrp"
|
||||
"github.com/httprunner/hrp/internal/ga"
|
||||
)
|
||||
|
||||
var log = hrp.GetLogger()
|
||||
@@ -41,6 +42,15 @@ func (h *HAR) SetOutputDir(dir string) {
|
||||
}
|
||||
|
||||
func (h *HAR) GenJSON() (jsonPath string, err error) {
|
||||
event := ga.EventTracking{
|
||||
Category: "har2case",
|
||||
Action: "hrp har2case --to-json",
|
||||
}
|
||||
// report start event
|
||||
go ga.SendEvent(event)
|
||||
// report running timing event
|
||||
defer ga.SendEvent(event.StartTiming("execution"))
|
||||
|
||||
tCase, err := h.makeTestCase()
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -51,6 +61,15 @@ func (h *HAR) GenJSON() (jsonPath string, err error) {
|
||||
}
|
||||
|
||||
func (h *HAR) GenYAML() (yamlPath string, err error) {
|
||||
event := ga.EventTracking{
|
||||
Category: "har2case",
|
||||
Action: "hrp har2case --to-yaml",
|
||||
}
|
||||
// report start event
|
||||
go ga.SendEvent(event)
|
||||
// report running timing event
|
||||
defer ga.SendEvent(event.StartTiming("execution"))
|
||||
|
||||
tCase, err := h.makeTestCase()
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -3,7 +3,7 @@ package cmd
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/myzhan/boomer"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/httprunner/hrp"
|
||||
@@ -19,15 +19,15 @@ var boomCmd = &cobra.Command{
|
||||
$ hrp boom examples/ # run testcases in specified folder`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
sentry.CaptureMessage("start to run load test")
|
||||
var paths []hrp.ITestCase
|
||||
for _, arg := range args {
|
||||
paths = append(paths, &hrp.TestCasePath{Path: arg})
|
||||
}
|
||||
boomer := hrp.NewStandaloneBoomer(spawnCount, spawnRate)
|
||||
boomer.EnableCPUProfile(cpuProfile, cpuProfileDuration)
|
||||
boomer.EnableMemoryProfile(memoryProfile, memoryProfileDuration)
|
||||
boomer.Run(paths...)
|
||||
hrpBoomer := hrp.NewStandaloneBoomer(spawnCount, spawnRate)
|
||||
hrpBoomer.AddOutput(boomer.NewConsoleOutput())
|
||||
hrpBoomer.EnableCPUProfile(cpuProfile, cpuProfileDuration)
|
||||
hrpBoomer.EnableMemoryProfile(memoryProfile, memoryProfileDuration)
|
||||
hrpBoomer.Run(paths...)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -15,7 +14,6 @@ var har2caseCmd = &cobra.Command{
|
||||
Long: `Convert HAR to json/yaml testcase files`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sentry.CaptureMessage("start to convert HAR to testcases")
|
||||
var outputFiles []string
|
||||
for _, arg := range args {
|
||||
var outputPath string
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/httprunner/hrp"
|
||||
"github.com/httprunner/hrp/internal/version"
|
||||
)
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
@@ -24,7 +25,7 @@ Copyright 2021 debugtalk`,
|
||||
}
|
||||
hrp.SetLogLevel(logLevel)
|
||||
},
|
||||
Version: hrp.VERSION,
|
||||
Version: version.VERSION,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/httprunner/hrp"
|
||||
@@ -17,7 +16,6 @@ var runCmd = &cobra.Command{
|
||||
$ hrp run examples/ # run testcases in specified folder`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
sentry.CaptureMessage("start to run API tests")
|
||||
var paths []hrp.ITestCase
|
||||
for _, arg := range args {
|
||||
paths = append(paths, &hrp.TestCasePath{Path: arg})
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/httprunner/hrp/hrp/cmd"
|
||||
"github.com/httprunner/hrp/internal/sentry"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Flush buffered events before the program terminates.
|
||||
defer sentry.Flush(2 * time.Second)
|
||||
defer sentry.Flush()
|
||||
|
||||
cmd.Execute()
|
||||
}
|
||||
|
||||
79
internal/ga/client.go
Normal file
79
internal/ga/client.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package ga
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
gaAPIDebugURL = "https://www.google-analytics.com/debug/collect" // used for debug
|
||||
gaAPIURL = "https://www.google-analytics.com/collect"
|
||||
)
|
||||
|
||||
type GAClient struct {
|
||||
TrackingID string `form:"tid"` // Tracking ID / Property ID, XX-XXXXXXX-X
|
||||
ClientID string `form:"cid"` // Anonymous Client ID
|
||||
Version string `form:"v"` // Version
|
||||
httpClient *http.Client // http client session
|
||||
}
|
||||
|
||||
// NewGAClient creates a new GAClient object with the trackingID and clientID.
|
||||
func NewGAClient(trackingID string) *GAClient {
|
||||
nodeUUID, _ := uuid.NewUUID()
|
||||
return &GAClient{
|
||||
TrackingID: trackingID,
|
||||
ClientID: nodeUUID.String(),
|
||||
Version: "1", // constant v1
|
||||
httpClient: &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SendEvent sends one event to Google Analytics
|
||||
func (g *GAClient) SendEvent(e IEvent) error {
|
||||
var data url.Values
|
||||
if event, ok := e.(UserTimingTracking); ok {
|
||||
event.duration = time.Since(event.startTime)
|
||||
data = event.ToUrlValues()
|
||||
} else {
|
||||
data = e.ToUrlValues()
|
||||
}
|
||||
|
||||
// append common params
|
||||
data.Add("v", g.Version)
|
||||
data.Add("tid", g.TrackingID)
|
||||
data.Add("cid", g.ClientID)
|
||||
|
||||
resp, err := g.httpClient.PostForm(gaAPIURL, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("response status: %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func structToUrlValues(i interface{}) (values url.Values) {
|
||||
values = url.Values{}
|
||||
iVal := reflect.ValueOf(i)
|
||||
for i := 0; i < iVal.NumField(); i++ {
|
||||
formTagName := iVal.Type().Field(i).Tag.Get("form")
|
||||
if formTagName == "" {
|
||||
continue
|
||||
}
|
||||
if iVal.Field(i).IsZero() {
|
||||
continue
|
||||
}
|
||||
values.Set(formTagName, fmt.Sprint(iVal.Field(i)))
|
||||
}
|
||||
return
|
||||
}
|
||||
28
internal/ga/client_test.go
Normal file
28
internal/ga/client_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package ga
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSendEvents(t *testing.T) {
|
||||
event := EventTracking{
|
||||
Category: "unittest",
|
||||
Action: "SendEvents",
|
||||
}
|
||||
err := gaClient.SendEvent(event)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStructToUrlValues(t *testing.T) {
|
||||
event := EventTracking{
|
||||
Category: "unittest",
|
||||
Action: "convert",
|
||||
Label: "StructToUrlValues",
|
||||
}
|
||||
val := structToUrlValues(event)
|
||||
if val.Encode() != "ea=convert&ec=unittest&el=StructToUrlValues" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
67
internal/ga/events.go
Normal file
67
internal/ga/events.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package ga
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
type IEvent interface {
|
||||
ToUrlValues() url.Values
|
||||
}
|
||||
|
||||
type EventTracking struct {
|
||||
HitType string `form:"t"` // Event hit type = event
|
||||
Category string `form:"ec"` // Required. Event Category.
|
||||
Action string `form:"ea"` // Required. Event Action.
|
||||
Label string `form:"el"` // Optional. Event label
|
||||
Value int `form:"ev"` // Optional. Event value
|
||||
}
|
||||
|
||||
func (e EventTracking) StartTiming(variable string) UserTimingTracking {
|
||||
return UserTimingTracking{
|
||||
HitType: "timing",
|
||||
Category: e.Category,
|
||||
Variable: variable,
|
||||
Label: e.Label,
|
||||
startTime: time.Now(), // starts the timer
|
||||
}
|
||||
}
|
||||
|
||||
func (e EventTracking) ToUrlValues() url.Values {
|
||||
e.HitType = "event"
|
||||
return structToUrlValues(e)
|
||||
}
|
||||
|
||||
type UserTimingTracking struct {
|
||||
HitType string `form:"t"` // Timing hit type
|
||||
Category string `form:"utc"` // Required. user timing category. e.g. jsonLoader
|
||||
Variable string `form:"utv"` // Required. timing variable. e.g. load
|
||||
Duration string `form:"utt"` // Required. time took duration. Required.
|
||||
Label string `form:"utl"` // Optional. user timing label. e.g jQuery
|
||||
startTime time.Time
|
||||
duration time.Duration // time took duration
|
||||
}
|
||||
|
||||
func (e UserTimingTracking) ToUrlValues() url.Values {
|
||||
e.HitType = "timing"
|
||||
e.Duration = fmt.Sprintf("%d", int64(e.duration.Seconds()*1000))
|
||||
return structToUrlValues(e)
|
||||
}
|
||||
|
||||
type Exception struct {
|
||||
HitType string `form:"t"` // Hit Type = exception
|
||||
Description string `form:"exd"` // exception description. i.e. IOException
|
||||
IsFatal string `form:"exf"` // if the exception was fatal
|
||||
isFatal bool
|
||||
}
|
||||
|
||||
func (e Exception) ToUrlValues() url.Values {
|
||||
e.HitType = "exception"
|
||||
if e.isFatal {
|
||||
e.IsFatal = "1"
|
||||
} else {
|
||||
e.IsFatal = "0"
|
||||
}
|
||||
return structToUrlValues(e)
|
||||
}
|
||||
15
internal/ga/init.go
Normal file
15
internal/ga/init.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package ga
|
||||
|
||||
const (
|
||||
trackingID = "UA-114587036-1" // Tracking ID for Google Analytics
|
||||
)
|
||||
|
||||
var gaClient *GAClient
|
||||
|
||||
func init() {
|
||||
gaClient = NewGAClient(trackingID)
|
||||
}
|
||||
|
||||
func SendEvent(e IEvent) error {
|
||||
return gaClient.SendEvent(e)
|
||||
}
|
||||
27
internal/sentry/init.go
Normal file
27
internal/sentry/init.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package sentry
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
|
||||
"github.com/httprunner/hrp/internal/version"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// init sentry sdk
|
||||
err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: "https://cff5efc69b1a4325a4cf873f1e70c13a@o334324.ingest.sentry.io/6070292",
|
||||
Release: version.VERSION,
|
||||
})
|
||||
if err != nil {
|
||||
panic("init sentry sdk failed!")
|
||||
}
|
||||
sentry.ConfigureScope(func(scope *sentry.Scope) {
|
||||
scope.SetLevel(sentry.LevelError)
|
||||
})
|
||||
}
|
||||
|
||||
func Flush() {
|
||||
sentry.Flush(3 * time.Second)
|
||||
}
|
||||
3
internal/version/init.go
Normal file
3
internal/version/init.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package version
|
||||
|
||||
const VERSION = "v0.2.1"
|
||||
15
log.go
15
log.go
@@ -4,25 +4,10 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/rs/zerolog"
|
||||
zlog "github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// init sentry sdk
|
||||
err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: "https://cff5efc69b1a4325a4cf873f1e70c13a@o334324.ingest.sentry.io/6070292",
|
||||
Release: VERSION,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("init sentry sdk failed!")
|
||||
}
|
||||
sentry.ConfigureScope(func(scope *sentry.Scope) {
|
||||
scope.SetLevel(sentry.LevelError)
|
||||
})
|
||||
}
|
||||
|
||||
var log = zlog.Logger
|
||||
|
||||
func SetLogLevel(level string) {
|
||||
|
||||
@@ -49,6 +49,9 @@ extra:
|
||||
link: https://debugtalk.com
|
||||
- icon: fontawesome/brands/github-alt
|
||||
link: 'https://github.com/httprunner'
|
||||
analytics:
|
||||
provider: google
|
||||
property: G-N2DPN3VP7K
|
||||
|
||||
# index pages
|
||||
nav:
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/maja42/goval"
|
||||
|
||||
"github.com/httprunner/hrp/builtin"
|
||||
"github.com/httprunner/hrp/internal/builtin"
|
||||
)
|
||||
|
||||
func buildURL(baseURL, stepURL string) string {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/jmespath/go-jmespath"
|
||||
|
||||
"github.com/httprunner/hrp/builtin"
|
||||
"github.com/httprunner/hrp/internal/builtin"
|
||||
)
|
||||
|
||||
func NewResponseObject(t *testing.T, resp *http.Response) (*ResponseObject, error) {
|
||||
|
||||
11
runner.go
11
runner.go
@@ -16,6 +16,8 @@ import (
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/httprunner/hrp/internal/ga"
|
||||
)
|
||||
|
||||
// run API test with default configs
|
||||
@@ -69,6 +71,15 @@ func (r *Runner) SetProxyUrl(proxyUrl string) *Runner {
|
||||
}
|
||||
|
||||
func (r *Runner) Run(testcases ...ITestCase) error {
|
||||
event := ga.EventTracking{
|
||||
Category: "RunAPITests",
|
||||
Action: "hrp run",
|
||||
}
|
||||
// report start event
|
||||
go ga.SendEvent(event)
|
||||
// report execution timing event
|
||||
defer ga.SendEvent(event.StartTiming("execution"))
|
||||
|
||||
for _, iTestCase := range testcases {
|
||||
testcase, err := iTestCase.ToTestCase()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
package hrp
|
||||
|
||||
const VERSION = "v0.2.0"
|
||||
Reference in New Issue
Block a user