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-22 16:23:47 +08:00
parent ae7710e9d0
commit 7fd26395ed
30 changed files with 286 additions and 42 deletions

View File

@@ -1,4 +1,4 @@
name: Run unittest with coverage
name: Run unittests
on:
push:

View File

@@ -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()

View File

@@ -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

View File

@@ -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",

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
View File

@@ -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

View File

@@ -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

View File

@@ -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...)
},
}

View File

@@ -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

View File

@@ -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 (

View File

@@ -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})

View File

@@ -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
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
}

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
internal/ga/events.go Normal file
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
internal/ga/init.go Normal file
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
internal/sentry/init.go Normal file
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
internal/version/init.go Normal file
View File

@@ -0,0 +1,3 @@
package version
const VERSION = "v0.2.1"

15
log.go
View File

@@ -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) {

View File

@@ -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:

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -1,3 +0,0 @@
package hrp
const VERSION = "v0.2.0"