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:
debugtalk
2021-11-26 15:39:17 +08:00
parent ae7710e9d0
commit 7fd26395ed
30 changed files with 286 additions and 42 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
name: Run unittest with coverage name: Run unittests
on: on:
push: push:
+11 -1
View File
@@ -4,6 +4,8 @@ import (
"time" "time"
"github.com/myzhan/boomer" "github.com/myzhan/boomer"
"github.com/httprunner/hrp/internal/ga"
) )
func NewStandaloneBoomer(spawnCount int, spawnRate float64) *Boomer { func NewStandaloneBoomer(spawnCount int, spawnRate float64) *Boomer {
@@ -11,7 +13,6 @@ func NewStandaloneBoomer(spawnCount int, spawnRate float64) *Boomer {
Boomer: boomer.NewStandaloneBoomer(spawnCount, spawnRate), Boomer: boomer.NewStandaloneBoomer(spawnCount, spawnRate),
debug: false, debug: false,
} }
b.AddOutput(boomer.NewConsoleOutput())
return b return b
} }
@@ -26,6 +27,15 @@ func (b *Boomer) SetDebug(debug bool) *Boomer {
} }
func (b *Boomer) Run(testcases ...ITestCase) { 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 var taskSlice []*boomer.Task
for _, iTestCase := range testcases { for _, iTestCase := range testcases {
testcase, err := iTestCase.ToTestCase() testcase, err := iTestCase.ToTestCase()
+4
View File
@@ -1,5 +1,9 @@
# Release History # Release History
## v0.2.1 (2021-11-26)
- feat: report events with Google Analytics
## v0.2.0 (2021-11-19) ## v0.2.0 (2021-11-19)
- feat: deploy mkdocs to github pages when PR merged - feat: deploy mkdocs to github pages when PR merged
+1 -1
View File
@@ -172,7 +172,7 @@ import (
) )
func TestCaseDemo(t *testing.T) { func TestCaseDemo(t *testing.T) {
testcase := &hrp.TestCase{ demoTestCase := &hrp.TestCase{
Config: hrp.TConfig{ Config: hrp.TConfig{
Name: "demo with complex mechanisms", Name: "demo with complex mechanisms",
BaseURL: "https://postman-echo.com", BaseURL: "https://postman-echo.com",
+1 -1
View File
@@ -22,4 +22,4 @@ Copyright 2021 debugtalk
* [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
###### Auto generated by spf13/cobra on 19-Nov-2021 ###### Auto generated by spf13/cobra on 26-Nov-2021
+1 -1
View File
@@ -37,4 +37,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 19-Nov-2021 ###### Auto generated by spf13/cobra on 26-Nov-2021
+1 -1
View File
@@ -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 19-Nov-2021 ###### Auto generated by spf13/cobra on 26-Nov-2021
+1 -1
View File
@@ -30,4 +30,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 19-Nov-2021 ###### Auto generated by spf13/cobra on 26-Nov-2021
+1
View File
@@ -5,6 +5,7 @@ go 1.16
require ( require (
github.com/getsentry/sentry-go v0.11.0 github.com/getsentry/sentry-go v0.11.0
github.com/go-ole/go-ole v1.2.6 // indirect 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/jinzhu/copier v0.3.2
github.com/jmespath/go-jmespath v0.4.0 github.com/jmespath/go-jmespath v0.4.0
github.com/maja42/goval v1.2.1 github.com/maja42/goval v1.2.1
+19
View File
@@ -14,6 +14,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/httprunner/hrp" "github.com/httprunner/hrp"
"github.com/httprunner/hrp/internal/ga"
) )
var log = hrp.GetLogger() var log = hrp.GetLogger()
@@ -41,6 +42,15 @@ func (h *HAR) SetOutputDir(dir string) {
} }
func (h *HAR) GenJSON() (jsonPath string, err error) { 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() tCase, err := h.makeTestCase()
if err != nil { if err != nil {
return "", err return "", err
@@ -51,6 +61,15 @@ func (h *HAR) GenJSON() (jsonPath string, err error) {
} }
func (h *HAR) GenYAML() (yamlPath 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() tCase, err := h.makeTestCase()
if err != nil { if err != nil {
return "", err return "", err
+6 -6
View File
@@ -3,7 +3,7 @@ package cmd
import ( import (
"time" "time"
"github.com/getsentry/sentry-go" "github.com/myzhan/boomer"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/httprunner/hrp" "github.com/httprunner/hrp"
@@ -19,15 +19,15 @@ var boomCmd = &cobra.Command{
$ hrp boom examples/ # run testcases in specified folder`, $ hrp boom examples/ # run testcases in specified folder`,
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
sentry.CaptureMessage("start to run load test")
var paths []hrp.ITestCase var paths []hrp.ITestCase
for _, arg := range args { for _, arg := range args {
paths = append(paths, &hrp.TestCasePath{Path: arg}) paths = append(paths, &hrp.TestCasePath{Path: arg})
} }
boomer := hrp.NewStandaloneBoomer(spawnCount, spawnRate) hrpBoomer := hrp.NewStandaloneBoomer(spawnCount, spawnRate)
boomer.EnableCPUProfile(cpuProfile, cpuProfileDuration) hrpBoomer.AddOutput(boomer.NewConsoleOutput())
boomer.EnableMemoryProfile(memoryProfile, memoryProfileDuration) hrpBoomer.EnableCPUProfile(cpuProfile, cpuProfileDuration)
boomer.Run(paths...) hrpBoomer.EnableMemoryProfile(memoryProfile, memoryProfileDuration)
hrpBoomer.Run(paths...)
}, },
} }
-2
View File
@@ -1,7 +1,6 @@
package cmd package cmd
import ( import (
"github.com/getsentry/sentry-go"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -15,7 +14,6 @@ var har2caseCmd = &cobra.Command{
Long: `Convert HAR to json/yaml testcase files`, Long: `Convert HAR to json/yaml testcase files`,
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
sentry.CaptureMessage("start to convert HAR to testcases")
var outputFiles []string var outputFiles []string
for _, arg := range args { for _, arg := range args {
var outputPath string var outputPath string
+2 -1
View File
@@ -7,6 +7,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/httprunner/hrp" "github.com/httprunner/hrp"
"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
@@ -24,7 +25,7 @@ Copyright 2021 debugtalk`,
} }
hrp.SetLogLevel(logLevel) hrp.SetLogLevel(logLevel)
}, },
Version: hrp.VERSION, Version: version.VERSION,
} }
var ( var (
-2
View File
@@ -1,7 +1,6 @@
package cmd package cmd
import ( import (
"github.com/getsentry/sentry-go"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/httprunner/hrp" "github.com/httprunner/hrp"
@@ -17,7 +16,6 @@ var runCmd = &cobra.Command{
$ hrp run examples/ # run testcases in specified folder`, $ hrp run examples/ # run testcases in specified folder`,
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
sentry.CaptureMessage("start to run API tests")
var paths []hrp.ITestCase var paths []hrp.ITestCase
for _, arg := range args { for _, arg := range args {
paths = append(paths, &hrp.TestCasePath{Path: arg}) paths = append(paths, &hrp.TestCasePath{Path: arg})
+2 -4
View File
@@ -1,15 +1,13 @@
package main package main
import ( import (
"time"
"github.com/getsentry/sentry-go"
"github.com/httprunner/hrp/hrp/cmd" "github.com/httprunner/hrp/hrp/cmd"
"github.com/httprunner/hrp/internal/sentry"
) )
func main() { func main() {
// Flush buffered events before the program terminates. // Flush buffered events before the program terminates.
defer sentry.Flush(2 * time.Second) defer sentry.Flush()
cmd.Execute() cmd.Execute()
} }
+79
View 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
View 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
View 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
View 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
View 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
View File
@@ -0,0 +1,3 @@
package version
const VERSION = "v0.2.1"
-15
View File
@@ -4,25 +4,10 @@ import (
"os" "os"
"strings" "strings"
"github.com/getsentry/sentry-go"
"github.com/rs/zerolog" "github.com/rs/zerolog"
zlog "github.com/rs/zerolog/log" 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 var log = zlog.Logger
func SetLogLevel(level string) { func SetLogLevel(level string) {
+3
View File
@@ -49,6 +49,9 @@ extra:
link: https://debugtalk.com link: https://debugtalk.com
- icon: fontawesome/brands/github-alt - icon: fontawesome/brands/github-alt
link: 'https://github.com/httprunner' link: 'https://github.com/httprunner'
analytics:
provider: google
property: G-N2DPN3VP7K
# index pages # index pages
nav: nav:
+1 -1
View File
@@ -10,7 +10,7 @@ import (
"github.com/maja42/goval" "github.com/maja42/goval"
"github.com/httprunner/hrp/builtin" "github.com/httprunner/hrp/internal/builtin"
) )
func buildURL(baseURL, stepURL string) string { func buildURL(baseURL, stepURL string) string {
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"github.com/jmespath/go-jmespath" "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) { func NewResponseObject(t *testing.T, resp *http.Response) (*ResponseObject, error) {
+11
View File
@@ -16,6 +16,8 @@ import (
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/httprunner/hrp/internal/ga"
) )
// run API test with default configs // run API test with default configs
@@ -69,6 +71,15 @@ func (r *Runner) SetProxyUrl(proxyUrl string) *Runner {
} }
func (r *Runner) Run(testcases ...ITestCase) error { 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 { for _, iTestCase := range testcases {
testcase, err := iTestCase.ToTestCase() testcase, err := iTestCase.ToTestCase()
if err != nil { if err != nil {
-3
View File
@@ -1,3 +0,0 @@
package hrp
const VERSION = "v0.2.0"