From 1bf04985476f52240992f893221c210528bdae78 Mon Sep 17 00:00:00 2001 From: Daria Bialobrzeska Date: Tue, 30 Jul 2024 12:03:41 +0200 Subject: [PATCH 1/5] Add rate limiting for async delivery by using a goroutine with consumer channel --- features/fixtures/app/Dockerfile | 4 +- features/fixtures/app/main.go | 15 ++-- features/fixtures/autoconfigure/Dockerfile | 4 +- features/fixtures/net_http/Dockerfile | 4 +- features/fixtures/net_http/main.go | 13 ++-- features/handled.feature | 10 +-- v2/bugsnag_test.go | 14 +++- v2/configuration.go | 11 +++ v2/notifier.go | 11 +-- v2/report_publisher.go | 82 +++++++++++++++++++--- 10 files changed, 132 insertions(+), 36 deletions(-) diff --git a/features/fixtures/app/Dockerfile b/features/fixtures/app/Dockerfile index 2533d28d..e046de76 100644 --- a/features/fixtures/app/Dockerfile +++ b/features/fixtures/app/Dockerfile @@ -1,7 +1,7 @@ ARG GO_VERSION FROM golang:${GO_VERSION}-alpine -RUN apk update && apk upgrade && apk add git bash +RUN apk update && apk upgrade && apk add git bash build-base ENV GOPATH /app @@ -28,6 +28,8 @@ WORKDIR /app/src/test # Skip on old versions of Go which pre-date modules RUN if [[ $GO_VERSION != '1.11' && $GO_VERSION != '1.12' ]]; then \ go mod init && go mod tidy; \ + echo "replace github.com/bugsnag/bugsnag-go/v2 => /app/src/github.com/bugsnag/bugsnag-go/v2" >> go.mod; \ + go mod tidy; \ fi RUN chmod +x run.sh diff --git a/features/fixtures/app/main.go b/features/fixtures/app/main.go index 2751a643..96243536 100644 --- a/features/fixtures/app/main.go +++ b/features/fixtures/app/main.go @@ -14,12 +14,13 @@ import ( bugsnag "github.com/bugsnag/bugsnag-go/v2" ) -func configureBasicBugsnag(testcase string) { +func configureBasicBugsnag(testcase string, ctx context.Context) { config := bugsnag.Configuration{ - APIKey: os.Getenv("API_KEY"), - AppVersion: os.Getenv("APP_VERSION"), - AppType: os.Getenv("APP_TYPE"), - Hostname: os.Getenv("HOSTNAME"), + APIKey: os.Getenv("API_KEY"), + AppVersion: os.Getenv("APP_VERSION"), + AppType: os.Getenv("APP_TYPE"), + Hostname: os.Getenv("HOSTNAME"), + MainContext: ctx, } if notifyReleaseStages := os.Getenv("NOTIFY_RELEASE_STAGES"); notifyReleaseStages != "" { @@ -63,11 +64,13 @@ func configureBasicBugsnag(testcase string) { } func main() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() test := flag.String("test", "handled", "what the app should send, either handled, unhandled, session, autonotify") flag.Parse() - configureBasicBugsnag(*test) + configureBasicBugsnag(*test, ctx) time.Sleep(100 * time.Millisecond) // Ensure tests are less flaky by ensuring the start-up session gets sent switch *test { diff --git a/features/fixtures/autoconfigure/Dockerfile b/features/fixtures/autoconfigure/Dockerfile index b1ecac15..2d6db32c 100644 --- a/features/fixtures/autoconfigure/Dockerfile +++ b/features/fixtures/autoconfigure/Dockerfile @@ -1,7 +1,7 @@ ARG GO_VERSION FROM golang:${GO_VERSION}-alpine -RUN apk update && apk upgrade && apk add git bash +RUN apk update && apk upgrade && apk add git bash build-base ENV GOPATH /app @@ -28,6 +28,8 @@ WORKDIR /app/src/test # Skip on old versions of Go which pre-date modules RUN if [[ $GO_VERSION != '1.11' && $GO_VERSION != '1.12' ]]; then \ go mod init && go mod tidy; \ + echo "replace github.com/bugsnag/bugsnag-go/v2 => /app/src/github.com/bugsnag/bugsnag-go/v2" >> go.mod; \ + go mod tidy; \ fi RUN chmod +x run.sh diff --git a/features/fixtures/net_http/Dockerfile b/features/fixtures/net_http/Dockerfile index d8307933..fb5e526e 100644 --- a/features/fixtures/net_http/Dockerfile +++ b/features/fixtures/net_http/Dockerfile @@ -3,7 +3,7 @@ FROM golang:${GO_VERSION}-alpine RUN apk update && \ apk upgrade && \ - apk add git + apk add git build-base ENV GOPATH /app @@ -30,4 +30,6 @@ WORKDIR /app/src/test # Skip on old versions of Go which pre-date modules RUN if [[ $GO_VERSION != '1.11' && $GO_VERSION != '1.12' ]]; then \ go mod init && go mod tidy; \ + echo "replace github.com/bugsnag/bugsnag-go/v2 => /app/src/github.com/bugsnag/bugsnag-go/v2" >> go.mod; \ + go mod tidy; \ fi diff --git a/features/fixtures/net_http/main.go b/features/fixtures/net_http/main.go index ac359263..516efd2a 100644 --- a/features/fixtures/net_http/main.go +++ b/features/fixtures/net_http/main.go @@ -14,7 +14,9 @@ import ( ) func main() { - configureBasicBugsnag() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + configureBasicBugsnag(ctx) http.HandleFunc("/handled", handledError) http.HandleFunc("/autonotify-then-recover", unhandledCrash) @@ -40,16 +42,17 @@ func recoverWrap(h http.Handler) http.Handler { }) } -func configureBasicBugsnag() { +func configureBasicBugsnag(ctx context.Context) { config := bugsnag.Configuration{ APIKey: os.Getenv("API_KEY"), Endpoints: bugsnag.Endpoints{ Notify: os.Getenv("BUGSNAG_ENDPOINT"), Sessions: os.Getenv("BUGSNAG_ENDPOINT"), }, - AppVersion: os.Getenv("APP_VERSION"), - AppType: os.Getenv("APP_TYPE"), - Hostname: os.Getenv("HOSTNAME"), + AppVersion: os.Getenv("APP_VERSION"), + AppType: os.Getenv("APP_TYPE"), + Hostname: os.Getenv("HOSTNAME"), + MainContext: ctx, } if notifyReleaseStages := os.Getenv("NOTIFY_RELEASE_STAGES"); notifyReleaseStages != "" { diff --git a/features/handled.feature b/features/handled.feature index 2555e34e..a7797a8a 100644 --- a/features/handled.feature +++ b/features/handled.feature @@ -37,7 +37,7 @@ Scenario: Sending an event using a callback to modify report contents And the event "severityReason.type" equals "userCallbackSetSeverity" And the event "context" equals "nonfatal.go:14" And the "file" of stack frame 0 equals "main.go" - And stack frame 0 contains a local function spanning 242 to 248 + And stack frame 0 contains a local function spanning 245 to 253 And the "file" of stack frame 1 equals ">insertion<" And the "lineNumber" of stack frame 1 equals 0 @@ -50,7 +50,7 @@ Scenario: Marking an error as unhandled in a callback And the event "severityReason.type" equals "userCallbackSetSeverity" And the event "severityReason.unhandledOverridden" is true And the "file" of stack frame 0 equals "main.go" - And stack frame 0 contains a local function spanning 254 to 257 + And stack frame 0 contains a local function spanning 257 to 262 Scenario: Unwrapping the causes of a handled error When I run the go service "app" with the test case "nested-error" @@ -59,12 +59,12 @@ Scenario: Unwrapping the causes of a handled error And the event "unhandled" is false And the event "severity" equals "warning" And the event "exceptions.0.message" equals "terminate process" - And the "lineNumber" of stack frame 0 equals 292 + And the "lineNumber" of stack frame 0 equals 295 And the "file" of stack frame 0 equals "main.go" And the "method" of stack frame 0 equals "nestedHandledError" And the event "exceptions.1.message" equals "login failed" And the event "exceptions.1.stacktrace.0.file" equals "main.go" - And the event "exceptions.1.stacktrace.0.lineNumber" equals 312 + And the event "exceptions.1.stacktrace.0.lineNumber" equals 315 And the event "exceptions.2.message" equals "invalid token" And the event "exceptions.2.stacktrace.0.file" equals "main.go" - And the event "exceptions.2.stacktrace.0.lineNumber" equals 320 + And the event "exceptions.2.stacktrace.0.lineNumber" equals 323 diff --git a/v2/bugsnag_test.go b/v2/bugsnag_test.go index cd2511eb..da72f3b1 100644 --- a/v2/bugsnag_test.go +++ b/v2/bugsnag_test.go @@ -16,6 +16,10 @@ import ( "github.com/bugsnag/bugsnag-go/v2/sessions" ) +// ATTENTION - tests in this file are changing global state variables +// like default config or default report publisher +// TAKE CARE to reset them to default after testcase! + // The line numbers of this method are used in tests. // If you move this function you'll have to change tests func crashyHandler(w http.ResponseWriter, r *http.Request) { @@ -132,7 +136,7 @@ func TestNotify(t *testing.T) { } exception := getIndex(event, "exceptions", 0) - verifyExistsInStackTrace(t, exception, &StackFrame{File: "bugsnag_test.go", Method: "TestNotify", LineNumber: 98, InProject: true}) + verifyExistsInStackTrace(t, exception, &StackFrame{File: "bugsnag_test.go", Method: "TestNotify", LineNumber: 102, InProject: true}) } type testPublisher struct { @@ -144,6 +148,9 @@ func (tp *testPublisher) publishReport(p *payload) error { return nil } +func (tp *testPublisher) setMainProgramContext(context.Context) { +} + func TestNotifySyncThenAsync(t *testing.T) { ts, _ := setup() defer ts.Close() @@ -152,7 +159,7 @@ func TestNotifySyncThenAsync(t *testing.T) { pub := new(testPublisher) publisher = pub - defer func() { publisher = new(defaultReportPublisher) }() + defer func() { publisher = newPublisher() }() Notify(fmt.Errorf("oopsie")) if pub.sync { @@ -175,6 +182,7 @@ func TestHandlerFunc(t *testing.T) { defer eventserver.Close() Configure(generateSampleConfig(eventserver.URL)) + // NOTE - this testcase will print a panic in verbose mode t.Run("unhandled", func(st *testing.T) { sessionTracker = nil startSessionTracking() @@ -315,7 +323,7 @@ func TestHandler(t *testing.T) { } exception := getIndex(event, "exceptions", 0) - verifyExistsInStackTrace(t, exception, &StackFrame{File: "bugsnag_test.go", Method: "crashyHandler", InProject: true, LineNumber: 24}) + verifyExistsInStackTrace(t, exception, &StackFrame{File: "bugsnag_test.go", Method: "crashyHandler", InProject: true, LineNumber: 28}) } func TestAutoNotify(t *testing.T) { diff --git a/v2/configuration.go b/v2/configuration.go index 48680bb3..08ecb7ee 100644 --- a/v2/configuration.go +++ b/v2/configuration.go @@ -1,6 +1,7 @@ package bugsnag import ( + "context" "log" "net/http" "os" @@ -101,6 +102,12 @@ type Configuration struct { // Whether bugsnag should notify synchronously. This defaults to false which // causes bugsnag-go to spawn a new goroutine for each notification. Synchronous bool + + // Context created in the main program + // Used in event delivery - after this context is marked Done + // the event sending goroutine will switch to a graceful shutdown + // and will try to send any remaining events. + MainContext context.Context // Whether the notifier should send all sessions recorded so far to Bugsnag // when repanicking to ensure that no session information is lost in a // fatal crash. @@ -160,6 +167,10 @@ func (config *Configuration) update(other *Configuration) *Configuration { if other.Synchronous { config.Synchronous = true } + if other.MainContext != nil { + config.MainContext = other.MainContext + publisher.setMainProgramContext(other.MainContext) + } if other.AutoCaptureSessions != nil { config.AutoCaptureSessions = other.AutoCaptureSessions diff --git a/v2/notifier.go b/v2/notifier.go index 54399de9..876dcc8a 100644 --- a/v2/notifier.go +++ b/v2/notifier.go @@ -4,7 +4,7 @@ import ( "github.com/bugsnag/bugsnag-go/v2/errors" ) -var publisher reportPublisher = new(defaultReportPublisher) +var publisher reportPublisher = newPublisher() // Notifier sends errors to Bugsnag. type Notifier struct { @@ -84,10 +84,11 @@ func (notifier *Notifier) NotifySync(err error, sync bool, rawData ...interface{ // AutoNotify notifies Bugsnag of any panics, then repanics. // It sends along any rawData that gets passed in. // Usage: -// go func() { -// defer AutoNotify() -// // (possibly crashy code) -// }() +// +// go func() { +// defer AutoNotify() +// // (possibly crashy code) +// }() func (notifier *Notifier) AutoNotify(rawData ...interface{}) { if err := recover(); err != nil { severity := notifier.getDefaultSeverity(rawData, SeverityError) diff --git a/v2/report_publisher.go b/v2/report_publisher.go index 87d228fc..6ccb17e7 100644 --- a/v2/report_publisher.go +++ b/v2/report_publisher.go @@ -1,14 +1,74 @@ package bugsnag -import "fmt" +import ( + "context" + "fmt" + "os" + "os/signal" + "syscall" +) type reportPublisher interface { publishReport(*payload) error + setMainProgramContext(context.Context) } -type defaultReportPublisher struct{} +func (defPub *defaultReportPublisher) delivery() { + signalsCh := make(chan os.Signal, 1) + signal.Notify(signalsCh, syscall.SIGINT, syscall.SIGTERM) -func (*defaultReportPublisher) publishReport(p *payload) error { +waitForEnd: + for { + select { + case <-signalsCh: + defPub.isClosing = true + break waitForEnd + case <-defPub.mainProgramCtx.Done(): + defPub.isClosing = true + break waitForEnd + case p, ok := <-defPub.eventsChan: + if ok { + if err := p.deliver(); err != nil { + // Ensure that any errors are logged if they occur in a goroutine. + p.logf("bugsnag/defaultReportPublisher.publishReport: %v", err) + } + } else { + p.logf("Event channel closed") + return + } + } + } + + // Send remaining elements from the queue + close(defPub.eventsChan) + for p := range defPub.eventsChan { + if err := p.deliver(); err != nil { + // Ensure that any errors are logged if they occur in a goroutine. + p.logf("bugsnag/defaultReportPublisher.publishReport: %v", err) + } + } +} + +type defaultReportPublisher struct { + eventsChan chan *payload + mainProgramCtx context.Context + isClosing bool +} + +func newPublisher() reportPublisher { + defPub := defaultReportPublisher{isClosing: false, mainProgramCtx: context.TODO()} + defPub.eventsChan = make(chan *payload, 100) + + go defPub.delivery() + + return &defPub +} + +func (defPub *defaultReportPublisher) setMainProgramContext(ctx context.Context) { + defPub.mainProgramCtx = ctx +} + +func (defPub *defaultReportPublisher) publishReport(p *payload) error { p.logf("notifying bugsnag: %s", p.Message) if !p.notifyInReleaseStage() { return fmt.Errorf("not notifying in %s", p.ReleaseStage) @@ -17,11 +77,15 @@ func (*defaultReportPublisher) publishReport(p *payload) error { return p.deliver() } - go func(p *payload) { - if err := p.deliver(); err != nil { - // Ensure that any errors are logged if they occur in a goroutine. - p.logf("bugsnag/defaultReportPublisher.publishReport: %v", err) - } - }(p) + if defPub.isClosing { + return fmt.Errorf("main program is stopping, new events won't be sent") + } + + select { + case defPub.eventsChan <- p: + default: + p.logf("Events channel full. Discarding value") + } + return nil } From cf0947ff10c6412db42e8bf5e1ebf56223dcca2d Mon Sep 17 00:00:00 2001 From: Daria Bialobrzeska Date: Mon, 5 Aug 2024 12:47:29 +0200 Subject: [PATCH 2/5] Updated changelog, added hints for local testing --- CHANGELOG.md | 8 ++++++++ LOCAL_TESTING.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 LOCAL_TESTING.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 400e7fe8..409bb4d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## TBD + +### Enhancements + +* Limit resource usage while sending events asynchronously \ + Added MainContext configuration option for providing context from main app + [#231](https://github.com/bugsnag/bugsnag-go/pull/231) + ## 2.4.0 (2024-04-15) ### Enhancements diff --git a/LOCAL_TESTING.md b/LOCAL_TESTING.md new file mode 100644 index 00000000..05ea8960 --- /dev/null +++ b/LOCAL_TESTING.md @@ -0,0 +1,29 @@ + +## Unit tests +* Install old golang version (do not install just 1.11 - it's not compatible with running newer modules): + +``` +ASDF_GOLANG_OVERWRITE_ARCH=amd64 asdf install golang 1.11.13 +``` + +* If you see error below use `CGO_ENABLED=0`. + +``` +# crypto/x509 +malformed DWARF TagVariable entry +``` + +## Local testing with maze runner + +* Maze runner tests require + * Specyfing `GO_VERSION` env variable to set a golang version for docker container. + * Ruby 2.7. + * Running docker. + +* Commands to run tests + +``` + bundle install + bundle exec bugsnag-maze-runner + bundle exec bugsnag-maze-runner -c features/ +``` \ No newline at end of file From 6038944f84cb33510c040559e3767ff3b9aadc9b Mon Sep 17 00:00:00 2001 From: Steve K-W Date: Thu, 8 Aug 2024 15:06:43 +0100 Subject: [PATCH 3/5] Pin tests to Ubuntu 20.04 --- .github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index 0feee70e..adddc5ac 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml @@ -5,14 +5,14 @@ on: [ push, pull_request ] jobs: test: - runs-on: ${{ matrix.os }}-latest + runs-on: ${{ matrix.os }} defaults: run: working-directory: 'go/src/github.com/bugsnag/bugsnag-go/v2' # relative to $GITHUB_WORKSPACE strategy: fail-fast: false matrix: - os: [ubuntu, windows] + os: [ubuntu-20.04, windows-latest] go-version: ['1.11', '1.12', '1.13', '1.14', '1.15', '1.16', '1.17', '1.18', '1.19', '1.20', '1.21', '1.22'] steps: From 47533b24755dda1c23cdd8f4a604b98745a41818 Mon Sep 17 00:00:00 2001 From: Daria Bialobrzeska <88393714+DariaKunoichi@users.noreply.github.com> Date: Tue, 27 Aug 2024 11:43:34 +0200 Subject: [PATCH 4/5] Update maze-runner tests from v1 to v9 (#244) * Prepare CI for new mazerunner tests, use GO111MODULE for older golang versions * Add command pattern in main test app * Prepare ruby steps for new mazerunner version, set test server address * Separate test functions into scenario files * Add scenarios to map in main file * Remove nethttp and autoconfigure test apps * Remove autoconfig and nethttp from docker-compose, use only real bugsnag env vars * Rewrite basic features to new steps * Rewrite nethttp features to new steps * Fix auto session value in tests - number instead of string * Consider startup session in session count in tests * Unify scenario names with scenario func names --------- Co-authored-by: Steve Kirkland --- .github/workflows/test-package.yml | 24 +- .gitignore | 3 +- Gemfile | 2 +- Gemfile.lock | 55 --- features/apptype.feature | 24 +- features/appversion.feature | 25 +- features/autonotify.feature | 18 +- features/configuration.feature | 104 ++--- features/endpoint.feature | 16 - features/fixtures/app/Dockerfile | 18 +- features/fixtures/app/autoconfig_scenario.go | 33 ++ features/fixtures/app/command.go | 42 ++ features/fixtures/app/handled_scenario.go | 100 +++++ features/fixtures/app/main.go | 359 +++--------------- features/fixtures/app/metadata_scenario.go | 32 ++ .../main.go => app/nethttp_scenario.go} | 67 +--- features/fixtures/app/panic_scenario.go | 22 ++ features/fixtures/app/run.sh | 2 + features/fixtures/app/session_scenario.go | 23 ++ features/fixtures/app/unhandled_scenario.go | 35 ++ features/fixtures/app/user_scenario.go | 19 + features/fixtures/app/utils.go | 55 +++ features/fixtures/autoconfigure/Dockerfile | 36 -- features/fixtures/autoconfigure/cases.go | 24 -- features/fixtures/autoconfigure/main.go | 34 -- features/fixtures/autoconfigure/run.sh | 25 -- features/fixtures/docker-compose.yml | 57 +-- features/fixtures/net_http/Dockerfile | 35 -- features/handled.feature | 65 ++-- features/hostname.feature | 29 +- features/metadata.feature | 15 +- features/multieventsession.feature | 29 +- features/net-http/appversion.feature | 33 +- features/net-http/autocapturesessions.feature | 24 -- features/net-http/autonotify.feature | 29 +- features/net-http/handled.feature | 44 +-- features/net-http/onbeforenotify.feature | 21 +- features/net-http/recover.feature | 19 +- features/net-http/releasestage.feature | 31 +- features/net-http/request.feature | 17 +- features/net-http/user.feature | 20 +- features/onbeforenotify.feature | 17 +- features/paramfilters.feature | 31 +- features/plain_features/panics.feature | 25 +- features/recover.feature | 14 +- features/releasestage.feature | 106 +++--- features/sessioncontext.feature | 22 +- features/steps/go_steps.rb | 135 ++----- features/support/env.rb | 24 +- features/synchronous.feature | 20 - features/user.feature | 17 +- v2/headers/prefixed.go | 8 +- v2/payload.go | 5 +- v2/sessions/publisher.go | 10 + 54 files changed, 835 insertions(+), 1264 deletions(-) delete mode 100644 Gemfile.lock delete mode 100644 features/endpoint.feature create mode 100644 features/fixtures/app/autoconfig_scenario.go create mode 100644 features/fixtures/app/command.go create mode 100644 features/fixtures/app/handled_scenario.go create mode 100644 features/fixtures/app/metadata_scenario.go rename features/fixtures/{net_http/main.go => app/nethttp_scenario.go} (58%) create mode 100644 features/fixtures/app/panic_scenario.go create mode 100644 features/fixtures/app/session_scenario.go create mode 100644 features/fixtures/app/unhandled_scenario.go create mode 100644 features/fixtures/app/user_scenario.go create mode 100644 features/fixtures/app/utils.go delete mode 100644 features/fixtures/autoconfigure/Dockerfile delete mode 100644 features/fixtures/autoconfigure/cases.go delete mode 100644 features/fixtures/autoconfigure/main.go delete mode 100755 features/fixtures/autoconfigure/run.sh delete mode 100644 features/fixtures/net_http/Dockerfile delete mode 100644 features/net-http/autocapturesessions.feature delete mode 100644 features/synchronous.feature diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index adddc5ac..392fda7c 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml @@ -4,15 +4,14 @@ on: [ push, pull_request ] jobs: test: - - runs-on: ${{ matrix.os }} + runs-on: ${{ matrix.os }}-latest defaults: run: working-directory: 'go/src/github.com/bugsnag/bugsnag-go/v2' # relative to $GITHUB_WORKSPACE strategy: fail-fast: false matrix: - os: [ubuntu-20.04, windows-latest] + os: [ubuntu, windows] go-version: ['1.11', '1.12', '1.13', '1.14', '1.15', '1.16', '1.17', '1.18', '1.19', '1.20', '1.21', '1.22'] steps: @@ -27,31 +26,36 @@ jobs: if: matrix.os == 'windows' run: | bash -c 'echo "GOPATH=$GITHUB_WORKSPACE\\\\go" >> $GITHUB_ENV' + - name: set GO111MODULE + run: | + bash -c 'echo "GO111MODULE=on" >> $GITHUB_ENV' - uses: actions/setup-go@v2 with: go-version: ${{ matrix.go-version }} - name: install dependencies run: go get -v -d ./... - name: run tests - run: go test ./... + run: go test $(go list ./... | grep -v /features/) - name: vet package # go1.12 vet shows spurious 'unknown identifier' issues if: matrix.go-version != '1.12' - run: go vet ./... + run: go vet $(go list ./... | grep -v /features/) + - name: install integration dependencies + if: matrix.os == 'ubuntu' + run: | + sudo apt-get update + sudo apt-get install libcurl4-openssl-dev - name: install Ruby if: matrix.os == 'ubuntu' uses: ruby/setup-ruby@v1 with: - ruby-version: 2.7 + ruby-version: '3.2' bundler-cache: true working-directory: go/src/github.com/bugsnag/bugsnag-go # relative to $GITHUB_WORKSPACE - - name: install integration dependencies - if: matrix.os == 'ubuntu' - run: sudo apt-get install docker-compose - name: maze tests working-directory: go/src/github.com/bugsnag/bugsnag-go if: matrix.os == 'ubuntu' env: GO_VERSION: ${{ matrix.go-version }} - run: bundle exec bugsnag-maze-runner --color --format progress + run: bundle exec maze-runner --color --format progress \ No newline at end of file diff --git a/.gitignore b/.gitignore index 98616f19..109a5be3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ maze_output vendor -features/fixtures/testbuild \ No newline at end of file +# ignore the gemfile to prevent testing against stale versions +Gemfile.lock \ No newline at end of file diff --git a/Gemfile b/Gemfile index 2971d8a3..e20e035b 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,3 @@ source 'https://rubygems.org' -gem 'bugsnag-maze-runner', git: 'https://github.com/bugsnag/maze-runner', branch: 'v1' +gem "bugsnag-maze-runner", "~> 9.0" \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 5ff8a517..00000000 --- a/Gemfile.lock +++ /dev/null @@ -1,55 +0,0 @@ -GIT - remote: https://github.com/bugsnag/maze-runner - revision: 7377529a77eb7585afc66cd2080fcdc4eea3306a - branch: v1 - specs: - bugsnag-maze-runner (1.1.0) - cucumber (~> 3.1.0) - cucumber-expressions (= 5.0.15) - minitest (~> 5.0) - os (~> 1.0.0) - rack (~> 2.0.0) - rake (~> 12.3.3) - test-unit (~> 3.2.0) - -GEM - remote: https://rubygems.org/ - specs: - backports (3.21.0) - builder (3.2.4) - cucumber (3.1.0) - builder (>= 2.1.2) - cucumber-core (~> 3.1.0) - cucumber-expressions (~> 5.0.4) - cucumber-wire (~> 0.0.1) - diff-lcs (~> 1.3) - gherkin (~> 5.0) - multi_json (>= 1.7.5, < 2.0) - multi_test (>= 0.1.2) - cucumber-core (3.1.0) - backports (>= 3.8.0) - cucumber-tag_expressions (~> 1.1.0) - gherkin (>= 5.0.0) - cucumber-expressions (5.0.15) - cucumber-tag_expressions (1.1.1) - cucumber-wire (0.0.1) - diff-lcs (1.4.4) - gherkin (5.1.0) - minitest (5.14.4) - multi_json (1.15.0) - multi_test (0.1.2) - os (1.0.1) - power_assert (2.0.0) - rack (2.0.9.3) - rake (12.3.3) - test-unit (3.2.9) - power_assert - -PLATFORMS - ruby - -DEPENDENCIES - bugsnag-maze-runner! - -BUNDLED WITH - 2.1.4 diff --git a/features/apptype.feature b/features/apptype.feature index efe897fc..26ec346f 100644 --- a/features/apptype.feature +++ b/features/apptype.feature @@ -1,22 +1,18 @@ Feature: Configuring app type Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I configure the bugsnag endpoint - And I set environment variable "APP_TYPE" to "background-queue" - And I have built the service "app" + Given I set environment variable "BUGSNAG_APP_TYPE" to "background-queue" Scenario: An error report contains the configured app type when running a go app - Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - When I run the go service "app" with the test case "handled" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + Given I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" + When I start the service "app" + And I run "HandledErrorScenario" + And I wait to receive an error And the event "app.type" equals "background-queue" Scenario: An session report contains the configured app type when running a go app - Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true" - When I run the go service "app" with the test case "session" - Then I wait to receive a request after the start up session - And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the payload field "app.type" equals "background-queue" - + Given I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "1" + When I start the service "app" + And I run "SendSessionScenario" + And I wait to receive 2 sessions + And the session payload field "app.type" equals "background-queue" diff --git a/features/appversion.feature b/features/appversion.feature index 805bf940..c08916ef 100644 --- a/features/appversion.feature +++ b/features/appversion.feature @@ -1,21 +1,18 @@ Feature: Configuring app version Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I configure the bugsnag endpoint - And I set environment variable "APP_VERSION" to "3.1.2" - And I have built the service "app" + And I set environment variable "BUGSNAG_APP_VERSION" to "3.1.2" Scenario: An error report contains the configured app type when running a go app - Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - When I run the go service "app" with the test case "handled" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + Given I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" + When I start the service "app" + And I run "HandledErrorScenario" + And I wait to receive an error And the event "app.version" equals "3.1.2" -Scenario: An session report contains the configured app type when running a go app - Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true" - When I run the go service "app" with the test case "session" - Then I wait to receive a request after the start up session - And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the payload field "app.version" equals "3.1.2" +Scenario: A session report contains the configured app type when running a go app + Given I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "1" + When I start the service "app" + And I run "SendSessionScenario" + And I wait to receive 2 sessions + And the session payload field "app.version" equals "3.1.2" \ No newline at end of file diff --git a/features/autonotify.feature b/features/autonotify.feature index 9a79dcfa..5dad68f1 100644 --- a/features/autonotify.feature +++ b/features/autonotify.feature @@ -1,13 +1,11 @@ Feature: Using auto notify -Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I configure the bugsnag endpoint - And I have built the service "app" - Scenario: An error report is sent when an AutoNotified crash occurs which later gets recovered - When I run the go service "app" with the test case "autonotify" - Then I wait for 3 seconds - And the request 1 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the exception "errorClass" equals "*errors.errorString" for request 1 - And the exception "message" equals "Go routine killed with auto notify" for request 1 + When I start the service "app" + And I run "AutonotifyPanicScenario" + And I wait to receive 2 errors + And the exception "errorClass" equals "*errors.errorString" + And the exception "message" equals "Go routine killed with auto notify" + And I discard the oldest error + And the exception "errorClass" equals "panic" + And the exception "message" equals "Go routine killed with auto notify [recovered]" \ No newline at end of file diff --git a/features/configuration.feature b/features/configuration.feature index a5e374eb..f5f58591 100644 --- a/features/configuration.feature +++ b/features/configuration.feature @@ -5,92 +5,96 @@ Feature: Configure integration with environment variables Background: Given I set environment variable "BUGSNAG_API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I set environment variable "BUGSNAG_NOTIFY_ENDPOINT" to the notify endpoint - And I set environment variable "BUGSNAG_SESSIONS_ENDPOINT" to the sessions endpoint - And I have built the service "autoconfigure" Scenario Outline: Adding content to handled events through env variables Given I set environment variable "" to "" And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" - When I run the go service "autoconfigure" with the test case "" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + When I start the service "app" + And I run "" + And I wait to receive an error And the event "" equals "" Examples: | testcase | variable | value | field | - | panic | BUGSNAG_APP_VERSION | 1.4.34 | app.version | - | panic | BUGSNAG_APP_TYPE | mailer-daemon | app.type | - | panic | BUGSNAG_RELEASE_STAGE | beta1 | app.releaseStage | - | panic | BUGSNAG_HOSTNAME | dream-machine-2 | device.hostname | - | panic | BUGSNAG_METADATA_device_instance | kube2-33-A | metaData.device.instance | - | panic | BUGSNAG_METADATA_framework_version | v3.1.0 | metaData.framework.version | - | panic | BUGSNAG_METADATA_device_runtime_level | 1C | metaData.device.runtime_level | - | panic | BUGSNAG_METADATA_Carrot | orange | metaData.custom.Carrot | + | AutoconfigPanicScenario | BUGSNAG_APP_VERSION | 1.4.34 | app.version | + | AutoconfigPanicScenario | BUGSNAG_APP_TYPE | mailer-daemon | app.type | + | AutoconfigPanicScenario | BUGSNAG_RELEASE_STAGE | beta1 | app.releaseStage | + | AutoconfigPanicScenario | BUGSNAG_HOSTNAME | dream-machine-2 | device.hostname | + | AutoconfigPanicScenario | BUGSNAG_METADATA_device_instance | kube2-33-A | metaData.device.instance | + | AutoconfigPanicScenario | BUGSNAG_METADATA_framework_version | v3.1.0 | metaData.framework.version | + | AutoconfigPanicScenario | BUGSNAG_METADATA_device_runtime_level | 1C | metaData.device.runtime_level | + | AutoconfigPanicScenario | BUGSNAG_METADATA_Carrot | orange | metaData.custom.Carrot | - | handled | BUGSNAG_APP_VERSION | 1.4.34 | app.version | - | handled | BUGSNAG_APP_TYPE | mailer-daemon | app.type | - | handled | BUGSNAG_RELEASE_STAGE | beta1 | app.releaseStage | - | handled | BUGSNAG_HOSTNAME | dream-machine-2 | device.hostname | - | handled | BUGSNAG_METADATA_device_instance | kube2-33-A | metaData.device.instance | - | handled | BUGSNAG_METADATA_framework_version | v3.1.0 | metaData.framework.version | - | handled | BUGSNAG_METADATA_device_runtime_level | 1C | metaData.device.runtime_level | - | handled | BUGSNAG_METADATA_Carrot | orange | metaData.custom.Carrot | + | AutoconfigHandledScenario | BUGSNAG_APP_VERSION | 1.4.34 | app.version | + | AutoconfigHandledScenario | BUGSNAG_APP_TYPE | mailer-daemon | app.type | + | AutoconfigHandledScenario | BUGSNAG_RELEASE_STAGE | beta1 | app.releaseStage | + | AutoconfigHandledScenario | BUGSNAG_HOSTNAME | dream-machine-2 | device.hostname | + | AutoconfigHandledScenario | BUGSNAG_METADATA_device_instance | kube2-33-A | metaData.device.instance | + | AutoconfigHandledScenario | BUGSNAG_METADATA_framework_version | v3.1.0 | metaData.framework.version | + | AutoconfigHandledScenario | BUGSNAG_METADATA_device_runtime_level | 1C | metaData.device.runtime_level | + | AutoconfigHandledScenario | BUGSNAG_METADATA_Carrot | orange | metaData.custom.Carrot | Scenario: Configuring project packages Given I set environment variable "BUGSNAG_PROJECT_PACKAGES" to "main,test" And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" - When I run the go service "autoconfigure" with the test case "panic" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the in-project frames of the stacktrace are: - | file | method | lineNumber | - | cases.go | explicitPanic | 22 | - | main.go | main | 11 | + When I start the service "app" + And I run "AutoconfigPanicScenario" + And I wait to receive an error + And the "file" of stack frame 0 equals "features/fixtures/app/autoconfig_scenario.go" + And the "method" of stack frame 0 equals "AutoconfigPanicScenario.func1" + And the "lineNumber" of stack frame 0 equals 11 + And the "file" of stack frame 1 equals "features/fixtures/app/main.go" + And the "method" of stack frame 1 equals "main" + And the "lineNumber" of stack frame 1 equals 65 Scenario: Configuring source root - Given I set environment variable "BUGSNAG_SOURCE_ROOT" to the app directory + Given I set environment variable "BUGSNAG_SOURCE_ROOT" to "/app/src/features/fixtures/app/" And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" - And I run the go service "autoconfigure" with the test case "panic" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the in-project frames of the stacktrace are: - | file | method | lineNumber | - | cases.go | explicitPanic | 22 | - | main.go | main | 11 | + When I start the service "app" + And I run "AutoconfigPanicScenario" + And I wait to receive an error + And the "file" of stack frame 0 equals "autoconfig_scenario.go" + And the "method" of stack frame 0 equals "AutoconfigPanicScenario.func1" + And the "lineNumber" of stack frame 0 equals 11 + And the "file" of stack frame 1 equals "main.go" + And the "method" of stack frame 1 equals "main" + And the "lineNumber" of stack frame 1 equals 65 Scenario: Delivering events filtering through notify release stages Given I set environment variable "BUGSNAG_NOTIFY_RELEASE_STAGES" to "prod,beta" And I set environment variable "BUGSNAG_RELEASE_STAGE" to "beta" And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" - And I run the go service "autoconfigure" with the test case "panic" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + When I start the service "app" + And I run "AutoconfigPanicScenario" + And I wait to receive an error Scenario: Suppressing events through notify release stages Given I set environment variable "BUGSNAG_NOTIFY_RELEASE_STAGES" to "prod,beta" And I set environment variable "BUGSNAG_RELEASE_STAGE" to "dev" And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" - And I run the go service "autoconfigure" with the test case "panic" - Then 0 requests were received + When I start the service "app" + And I run "AutoconfigPanicScenario" + Then I should receive no errors Scenario: Suppressing events using panic handler Given I set environment variable "BUGSNAG_DISABLE_PANIC_HANDLER" to "1" And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" - And I run the go service "autoconfigure" with the test case "panic" - And I wait for 2 seconds - Then 0 requests were received + When I start the service "app" + And I run "AutoconfigPanicScenario" + Then I should receive no errors Scenario: Enabling synchronous event delivery Given I set environment variable "BUGSNAG_SYNCHRONOUS" to "1" And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" - When I run the go service "autoconfigure" with the test case "handled" - Then 1 request was received + When I start the service "app" + And I run "AutoconfigHandledScenario" + And I wait to receive an error Scenario: Filtering metadata Given I set environment variable "BUGSNAG_PARAMS_FILTERS" to "tomato,pears" And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" - When I run the go service "autoconfigure" with the test case "handled-metadata" - Then I wait to receive a request + When I start the service "app" + And I run "AutoconfigMetadataScenario" + And I wait to receive an error And the event "metaData.fruit.Tomato" equals "[FILTERED]" - And the event "metaData.snacks.Carrot" equals "4" + And the event "metaData.snacks.Carrot" equals "4" \ No newline at end of file diff --git a/features/endpoint.feature b/features/endpoint.feature deleted file mode 100644 index 803f6f4b..00000000 --- a/features/endpoint.feature +++ /dev/null @@ -1,16 +0,0 @@ -Feature: Configuring endpoint - -Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I configure the bugsnag endpoint - And I have built the service "app" - -Scenario: An error report is sent successfully using the notify endpoint only - When I run the go service "app" with the test case "endpoint-notify" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - -Scenario: Configuring Bugsnag will panic if the sessions endpoint is configured without the notify endpoint - When I run the go service "app" with the test case "endpoint-session" - And I wait for 3 second - Then I should receive no requests diff --git a/features/fixtures/app/Dockerfile b/features/fixtures/app/Dockerfile index e046de76..61400ce0 100644 --- a/features/fixtures/app/Dockerfile +++ b/features/fixtures/app/Dockerfile @@ -4,8 +4,10 @@ FROM golang:${GO_VERSION}-alpine RUN apk update && apk upgrade && apk add git bash build-base ENV GOPATH /app +ENV GO111MODULE="on" -COPY testbuild /app/src/github.com/bugsnag/bugsnag-go +COPY features /app/src/features +COPY v2 /app/src/github.com/bugsnag/bugsnag-go/v2 WORKDIR /app/src/github.com/bugsnag/bugsnag-go/v2 # Ensure subsequent steps are re-run if the GO_VERSION variable changes @@ -20,17 +22,13 @@ RUN if [[ $(echo -e "1.11\n$GO_VERSION\n1.16" | sort -V | head -2 | tail -1) == go install ./...; \ fi -# Copy test scenarios -COPY ./app /app/src/test -WORKDIR /app/src/test +WORKDIR /app/src/features/fixtures/app # Create app module - avoid locking bugsnag dep by not checking it in # Skip on old versions of Go which pre-date modules -RUN if [[ $GO_VERSION != '1.11' && $GO_VERSION != '1.12' ]]; then \ - go mod init && go mod tidy; \ - echo "replace github.com/bugsnag/bugsnag-go/v2 => /app/src/github.com/bugsnag/bugsnag-go/v2" >> go.mod; \ - go mod tidy; \ - fi +RUN go mod init && go mod tidy && \ + echo "replace github.com/bugsnag/bugsnag-go/v2 => /app/src/github.com/bugsnag/bugsnag-go/v2" >> go.mod && \ + go mod tidy RUN chmod +x run.sh -CMD ["/app/src/test/run.sh"] +CMD ["/app/src/features/fixtures/app/run.sh"] \ No newline at end of file diff --git a/features/fixtures/app/autoconfig_scenario.go b/features/fixtures/app/autoconfig_scenario.go new file mode 100644 index 00000000..8b52b44f --- /dev/null +++ b/features/fixtures/app/autoconfig_scenario.go @@ -0,0 +1,33 @@ +package main + +import ( + "fmt" + + bugsnag "github.com/bugsnag/bugsnag-go/v2" +) + +func AutoconfigPanicScenario(command Command) func() { + scenarioFunc := func() { + panic("PANIQ!") + } + return scenarioFunc +} + +func AutoconfigHandledScenario(command Command) func() { + scenarioFunc := func() { + bugsnag.Notify(fmt.Errorf("gone awry!")) + } + return scenarioFunc +} + +func AutoconfigMetadataScenario(command Command) func() { + scenarioFunc := func() { + bugsnag.OnBeforeNotify(func(event *bugsnag.Event, config *bugsnag.Configuration) error { + event.MetaData.Add("fruit", "Tomato", "beefsteak") + event.MetaData.Add("snacks", "Carrot", "4") + return nil + }) + bugsnag.Notify(fmt.Errorf("gone awry!")) + } + return scenarioFunc +} \ No newline at end of file diff --git a/features/fixtures/app/command.go b/features/fixtures/app/command.go new file mode 100644 index 00000000..8c45c601 --- /dev/null +++ b/features/fixtures/app/command.go @@ -0,0 +1,42 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "time" +) + +const DEFAULT_MAZE_ADDRESS = "http://localhost:9339" + +type Command struct { + Action string `json:"action,omitempty"` + ScenarioName string `json:"scenario_name,omitempty"` + APIKey string `json:"api_key,omitempty"` + NotifyEndpoint string `json:"notify_endpoint,omitempty"` + SessionsEndpoint string `json:"sessions_endpoint,omitempty"` + UUID string `json:"uuid,omitempty"` + RunUUID string `json:"run_uuid,omitempty"` +} + +func GetCommand(mazeAddress string) Command { + var command Command + mazeURL := fmt.Sprintf("%+v/command", mazeAddress) + client := http.Client{Timeout: 2 * time.Second} + res, err := client.Get(mazeURL) + if err != nil { + fmt.Printf("[Bugsnag] Error while receiving command: %+v\n", err) + return command + } + + if res != nil { + err = json.NewDecoder(res.Body).Decode(&command) + res.Body.Close() + if err != nil { + fmt.Printf("[Bugsnag] Error while decoding command: %+v\n", err) + return command + } + } + + return command +} diff --git a/features/fixtures/app/handled_scenario.go b/features/fixtures/app/handled_scenario.go new file mode 100644 index 00000000..ba0974ec --- /dev/null +++ b/features/fixtures/app/handled_scenario.go @@ -0,0 +1,100 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + + "github.com/bugsnag/bugsnag-go/v2" +) + +func HandledErrorScenario(command Command) func() { + scenarioFunc := func() { + if _, err := os.Open("nonexistent_file.txt"); err != nil { + if errClass := os.Getenv("ERROR_CLASS"); errClass != "" { + bugsnag.Notify(err, bugsnag.ErrorClass{Name: errClass}) + } else { + bugsnag.Notify(err) + } + } + } + return scenarioFunc +} + +func MultipleHandledErrorsScenario(command Command) func() { + //Make the order of the below predictable + bugsnag.Configure(bugsnag.Configuration{Synchronous: true}) + + scenarioFunc := func() { + ctx := bugsnag.StartSession(context.Background()) + bugsnag.Notify(fmt.Errorf("oops"), ctx) + bugsnag.Notify(fmt.Errorf("oops"), ctx) + } + return scenarioFunc +} + +func NestedHandledErrorScenario(command Command) func() { + scenarioFunc := func() { + if err := Login("token " + os.Getenv("API_KEY")); err != nil { + bugsnag.Notify(NewCustomErr("terminate process", err)) + } else { + i := len(os.Getenv("API_KEY")) + // Some nonsense to avoid inlining checkValue + if val, err := CheckValue(i); err != nil { + fmt.Printf("err: %v, val: %d\n", err, val) + } + if val, err := CheckValue(i - 46); err != nil { + fmt.Printf("err: %v, val: %d\n", err, val) + } + + log.Fatalf("This test is broken - no error was generated.") + } + } + return scenarioFunc +} + +func HandledCallbackErrorScenario(command Command) func() { + scenarioFunc := func() { + bugsnag.Notify(fmt.Errorf("inadequent Prep Error"), func(event *bugsnag.Event) { + event.Context = "nonfatal.go:14" + event.Severity = bugsnag.SeverityInfo + + event.Stacktrace[1].File = ">insertion<" + event.Stacktrace[1].LineNumber = 0 + }) + } + return scenarioFunc +} + +func HandledToUnhandledScenario(command Command) func() { + scenarioFunc := func() { + bugsnag.Notify(fmt.Errorf("unknown event"), func(event *bugsnag.Event) { + event.Unhandled = true + event.Severity = bugsnag.SeverityError + }) + } + return scenarioFunc +} + +func OnBeforeNotifyScenario(command Command) func() { + bugsnag.Configure(bugsnag.Configuration{Synchronous: true}) + + scenarioFunc := func() { + bugsnag.OnBeforeNotify( + func(event *bugsnag.Event, config *bugsnag.Configuration) error { + if event.Message == "ignore this error" { + return fmt.Errorf("not sending errors to ignore") + } + // continue notifying as normal + if event.Message == "change error message" { + event.Message = "error message was changed" + } + return nil + }) + bugsnag.Notify(fmt.Errorf("ignore this error")) + bugsnag.Notify(fmt.Errorf("don't ignore this error")) + bugsnag.Notify(fmt.Errorf("change error message")) + } + return scenarioFunc +} diff --git a/features/fixtures/app/main.go b/features/fixtures/app/main.go index 96243536..a86e4ce4 100644 --- a/features/fixtures/app/main.go +++ b/features/fixtures/app/main.go @@ -1,331 +1,70 @@ package main import ( - "context" - "flag" "fmt" - "log" "os" - "runtime" - "strconv" - "strings" "time" - bugsnag "github.com/bugsnag/bugsnag-go/v2" + "github.com/bugsnag/bugsnag-go/v2" ) -func configureBasicBugsnag(testcase string, ctx context.Context) { - config := bugsnag.Configuration{ - APIKey: os.Getenv("API_KEY"), - AppVersion: os.Getenv("APP_VERSION"), - AppType: os.Getenv("APP_TYPE"), - Hostname: os.Getenv("HOSTNAME"), - MainContext: ctx, - } - - if notifyReleaseStages := os.Getenv("NOTIFY_RELEASE_STAGES"); notifyReleaseStages != "" { - config.NotifyReleaseStages = strings.Split(notifyReleaseStages, ",") - } - - if releaseStage := os.Getenv("RELEASE_STAGE"); releaseStage != "" { - config.ReleaseStage = releaseStage - } - - if filters := os.Getenv("PARAMS_FILTERS"); filters != "" { - config.ParamsFilters = []string{filters} - } - - sync, err := strconv.ParseBool(os.Getenv("SYNCHRONOUS")) - if err == nil { - config.Synchronous = sync - } - - acs, err := strconv.ParseBool(os.Getenv("AUTO_CAPTURE_SESSIONS")) - if err == nil { - config.AutoCaptureSessions = acs - } - - switch testcase { - case "endpoint-notify": - config.Endpoints = bugsnag.Endpoints{Notify: os.Getenv("BUGSNAG_ENDPOINT")} - case "endpoint-session": - config.Endpoints = bugsnag.Endpoints{Sessions: os.Getenv("BUGSNAG_ENDPOINT")} - default: - config.Endpoints = bugsnag.Endpoints{ - Notify: os.Getenv("BUGSNAG_ENDPOINT"), - Sessions: os.Getenv("BUGSNAG_ENDPOINT"), - } - } - bugsnag.Configure(config) - - time.Sleep(200 * time.Millisecond) - // Increase publish rate for testing - bugsnag.DefaultSessionPublishInterval = time.Millisecond * 100 +var scenariosMap = map[string]func(Command) func(){ + "UnhandledCrashScenario": UnhandledCrashScenario, + "HandledErrorScenario": HandledErrorScenario, + "MultipleUnhandledErrorsScenario": MultipleUnhandledErrorsScenario, + "MultipleHandledErrorsScenario": MultipleHandledErrorsScenario, + "NestedHandledErrorScenario": NestedHandledErrorScenario, + "MetadataScenario": MetadataScenario, + "FilteredMetadataScenario": FilteredMetadataScenario, + "HandledCallbackErrorScenario": HandledCallbackErrorScenario, + "SendSessionScenario": SendSessionScenario, + "HandledToUnhandledScenario": HandledToUnhandledScenario, + "SetUserScenario": SetUserScenario, + "RecoverAfterPanicScenario": RecoverAfterPanicScenario, + "AutonotifyPanicScenario": AutonotifyPanicScenario, + "SessionAndErrorScenario": SessionAndErrorScenario, + "OnBeforeNotifyScenario": OnBeforeNotifyScenario, + "AutoconfigPanicScenario": AutoconfigPanicScenario, + "AutoconfigHandledScenario": AutoconfigHandledScenario, + "AutoconfigMetadataScenario": AutoconfigMetadataScenario, + "HttpServerScenario": HttpServerScenario, } func main() { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - test := flag.String("test", "handled", "what the app should send, either handled, unhandled, session, autonotify") - flag.Parse() - - configureBasicBugsnag(*test, ctx) - time.Sleep(100 * time.Millisecond) // Ensure tests are less flaky by ensuring the start-up session gets sent - - switch *test { - case "unhandled": - unhandledCrash() - case "handled", "endpoint-legacy", "endpoint-notify", "endpoint-session": - handledError() - case "handled-with-callback": - handledCallbackError() - case "session": - session() - case "autonotify": - autonotify() - case "metadata": - metadata() - case "onbeforenotify": - onBeforeNotify() - case "filtered": - filtered() - case "recover": - dontDie() - case "session-and-error": - sessionAndError() - case "send-and-exit": - sendAndExit() - case "user": - user() - case "multiple-handled": - multipleHandled() - case "multiple-unhandled": - multipleUnhandled() - case "make-unhandled-with-callback": - handledToUnhandled() - case "nested-error": - nestedHandledError() - default: - log.Println("Not a valid test flag: " + *test) - os.Exit(1) + addr := os.Getenv("DEFAULT_MAZE_ADDRESS") + if addr == "" { + addr = DEFAULT_MAZE_ADDRESS } -} - -func multipleHandled() { - //Make the order of the below predictable - bugsnag.Configure(bugsnag.Configuration{Synchronous: true}) - - ctx := bugsnag.StartSession(context.Background()) - bugsnag.Notify(fmt.Errorf("oops"), ctx) - bugsnag.Notify(fmt.Errorf("oops"), ctx) -} - -func multipleUnhandled() { - //Make the order of the below predictable - notifier := bugsnag.New(bugsnag.Configuration{Synchronous: true}) - notifier.FlushSessionsOnRepanic(false) - - ctx := bugsnag.StartSession(context.Background()) - defer func() { recover() }() - defer notifier.AutoNotify(ctx) - defer notifier.AutoNotify(ctx) - panic("oops") -} - -//go:noinline -func unhandledCrash() { - // Invalid type assertion, will panic - func(a interface{}) string { - return a.(string) - }(struct{}{}) -} - -func handledError() { - if _, err := os.Open("nonexistent_file.txt"); err != nil { - if errClass := os.Getenv("ERROR_CLASS"); errClass != "" { - bugsnag.Notify(err, bugsnag.ErrorClass{Name: errClass}) - } else { - bugsnag.Notify(err) - } + endpoints := bugsnag.Endpoints{ + Notify: fmt.Sprintf("%+v/notify", addr), + Sessions: fmt.Sprintf("%+v/sessions", addr), } - // Give some time for the error to be sent before exiting - time.Sleep(200 * time.Millisecond) -} - -func session() { - bugsnag.StartSession(context.Background()) - - // Give some time for the session to be sent before exiting - time.Sleep(200 * time.Millisecond) -} - -func autonotify() { - go func() { - defer bugsnag.AutoNotify() - panic("Go routine killed with auto notify") - }() - - // Give enough time for the panic to happen - time.Sleep(100 * time.Millisecond) -} - -func metadata() { - customerData := map[string]string{"Name": "Joe Bloggs", "Age": "21"} - bugsnag.Notify(fmt.Errorf("oops"), bugsnag.MetaData{ - "Scheme": { - "Customer": customerData, - "Level": "Blue", - }, + // HAS TO RUN FIRST BECAUSE OF PANIC WRAP + // https://github.com/bugsnag/panicwrap/blob/master/panicwrap.go#L177-L203 + bugsnag.Configure(bugsnag.Configuration{ + APIKey: "166f5ad3590596f9aa8d601ea89af845", + Endpoints: endpoints, }) - time.Sleep(200 * time.Millisecond) -} - -func filtered() { - bugsnag.Notify(fmt.Errorf("oops"), bugsnag.MetaData{ - "Account": { - "Name": "Company XYZ", - "Price(dollars)": "1 Million", - }, - }) - time.Sleep(200 * time.Millisecond) -} - -func onBeforeNotify() { - bugsnag.OnBeforeNotify( - func(event *bugsnag.Event, config *bugsnag.Configuration) error { - if event.Message == "Ignore this error" { - return fmt.Errorf("not sending errors to ignore") + // Increase publish rate for testing + bugsnag.DefaultSessionPublishInterval = time.Millisecond * 50 + + // Listening to the OS Signals + ticker := time.NewTicker(1 * time.Second) + for { + select { + case <-ticker.C: + command := GetCommand(addr) + fmt.Printf("[Bugsnag] Received command: %+v\n", command) + if command.Action != "run-scenario" { + continue } - // continue notifying as normal - if event.Message == "Change error message" { - event.Message = "Error message was changed" + prepareScenarioFunc, ok := scenariosMap[command.ScenarioName] + if ok { + scenarioFunc := prepareScenarioFunc(command) + scenarioFunc() + time.Sleep(200 * time.Millisecond) } - return nil - }) - bugsnag.Notify(fmt.Errorf("Ignore this error")) - time.Sleep(100 * time.Millisecond) - bugsnag.Notify(fmt.Errorf("Don't ignore this error")) - time.Sleep(100 * time.Millisecond) - bugsnag.Notify(fmt.Errorf("Change error message")) - time.Sleep(100 * time.Millisecond) -} - -func dontDie() { - go func() { - defer bugsnag.Recover() - panic("Go routine killed but recovered") - }() - time.Sleep(100 * time.Millisecond) -} - -func sessionAndError() { - ctx := bugsnag.StartSession(context.Background()) - bugsnag.Notify(fmt.Errorf("oops"), ctx) - - time.Sleep(200 * time.Millisecond) -} - -func sendAndExit() { - bugsnag.Notify(fmt.Errorf("oops")) -} - -func user() { - bugsnag.Notify(fmt.Errorf("oops"), bugsnag.User{ - Id: "test-user-id", - Name: "test-user-name", - Email: "test-user-email", - }) - - time.Sleep(200 * time.Millisecond) -} - -func handledCallbackError() { - bugsnag.Notify(fmt.Errorf("Inadequent Prep Error"), func(event *bugsnag.Event) { - event.Context = "nonfatal.go:14" - event.Severity = bugsnag.SeverityInfo - - event.Stacktrace[1].File = ">insertion<" - event.Stacktrace[1].LineNumber = 0 - }) - // Give some time for the error to be sent before exiting - time.Sleep(200 * time.Millisecond) -} - -func handledToUnhandled() { - bugsnag.Notify(fmt.Errorf("unknown event"), func(event *bugsnag.Event) { - event.Unhandled = true - event.Severity = bugsnag.SeverityError - }) - // Give some time for the error to be sent before exiting - time.Sleep(200 * time.Millisecond) -} - -type customErr struct { - msg string - cause error - callers []uintptr -} - -func newCustomErr(msg string, cause error) error { - callers := make([]uintptr, 8) - runtime.Callers(2, callers) - return customErr{ - msg: msg, - cause: cause, - callers: callers, - } -} - -func (err customErr) Error() string { - return err.msg -} - -func (err customErr) Unwrap() error { - return err.cause -} - -func (err customErr) Callers() []uintptr { - return err.callers -} - -func nestedHandledError() { - if err := login("token " + os.Getenv("API_KEY")); err != nil { - bugsnag.Notify(newCustomErr("terminate process", err)) - // Give some time for the error to be sent before exiting - time.Sleep(200 * time.Millisecond) - } else { - i := len(os.Getenv("API_KEY")) - // Some nonsense to avoid inlining checkValue - if val, err := checkValue(i); err != nil { - fmt.Printf("err: %v, val: %d", err, val) - } - if val, err := checkValue(i - 46); err != nil { - fmt.Printf("err: %v, val: %d", err, val) } - - log.Fatalf("This test is broken - no error was generated.") - } -} - -func login(token string) error { - val, err := checkValue(len(token) * -1) - if err != nil { - return newCustomErr("login failed", err) } - fmt.Printf("val: %d", val) - return nil -} - -func checkValue(i int) (int, error) { - if i < 0 { - return 0, newCustomErr("invalid token", nil) - } else if i%2 == 0 { - return i / 2, nil - } else if i < 9 { - return i * 3, nil - } - - return i * 4, nil } diff --git a/features/fixtures/app/metadata_scenario.go b/features/fixtures/app/metadata_scenario.go new file mode 100644 index 00000000..e4cb7f70 --- /dev/null +++ b/features/fixtures/app/metadata_scenario.go @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + + "github.com/bugsnag/bugsnag-go/v2" +) + +func MetadataScenario(command Command) func() { + scenarioFunc := func() { + customerData := map[string]string{"Name": "Joe Bloggs", "Age": "21"} + bugsnag.Notify(fmt.Errorf("oops"), bugsnag.MetaData{ + "Scheme": { + "Customer": customerData, + "Level": "Blue", + }, + }) + } + return scenarioFunc +} + +func FilteredMetadataScenario(command Command) func() { + scenarioFunc := func() { + bugsnag.Notify(fmt.Errorf("oops"), bugsnag.MetaData{ + "Account": { + "Name": "Company XYZ", + "Price(dollars)": "1 Million", + }, + }) + } + return scenarioFunc +} diff --git a/features/fixtures/net_http/main.go b/features/fixtures/app/nethttp_scenario.go similarity index 58% rename from features/fixtures/net_http/main.go rename to features/fixtures/app/nethttp_scenario.go index 516efd2a..0018f10b 100644 --- a/features/fixtures/net_http/main.go +++ b/features/fixtures/app/nethttp_scenario.go @@ -6,27 +6,25 @@ import ( "log" "net/http" "os" - "strconv" - "strings" "time" - bugsnag "github.com/bugsnag/bugsnag-go/v2" + "github.com/bugsnag/bugsnag-go/v2" ) -func main() { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - configureBasicBugsnag(ctx) - - http.HandleFunc("/handled", handledError) - http.HandleFunc("/autonotify-then-recover", unhandledCrash) - http.HandleFunc("/session", session) - http.HandleFunc("/autonotify", autonotify) - http.HandleFunc("/onbeforenotify", onBeforeNotify) - http.HandleFunc("/recover", dontdie) - http.HandleFunc("/user", user) +func HttpServerScenario(Command) func() { + scenarioFunc := func() { + http.HandleFunc("/handled", handledError) + http.HandleFunc("/autonotify-then-recover", unhandledCrash) + http.HandleFunc("/session", session) + http.HandleFunc("/autonotify", autonotify) + http.HandleFunc("/onbeforenotify", onBeforeNotify) + http.HandleFunc("/recover", dontdie) + http.HandleFunc("/user", user) + + http.ListenAndServe(":4512", recoverWrap(bugsnag.Handler(nil))) + } - http.ListenAndServe(":"+os.Getenv("SERVER_PORT"), recoverWrap(bugsnag.Handler(nil))) + return scenarioFunc } // Simple wrapper to send internal server error on panics @@ -42,41 +40,6 @@ func recoverWrap(h http.Handler) http.Handler { }) } -func configureBasicBugsnag(ctx context.Context) { - config := bugsnag.Configuration{ - APIKey: os.Getenv("API_KEY"), - Endpoints: bugsnag.Endpoints{ - Notify: os.Getenv("BUGSNAG_ENDPOINT"), - Sessions: os.Getenv("BUGSNAG_ENDPOINT"), - }, - AppVersion: os.Getenv("APP_VERSION"), - AppType: os.Getenv("APP_TYPE"), - Hostname: os.Getenv("HOSTNAME"), - MainContext: ctx, - } - - if notifyReleaseStages := os.Getenv("NOTIFY_RELEASE_STAGES"); notifyReleaseStages != "" { - config.NotifyReleaseStages = strings.Split(notifyReleaseStages, ",") - } - - if releaseStage := os.Getenv("RELEASE_STAGE"); releaseStage != "" { - config.ReleaseStage = releaseStage - } - - if filters := os.Getenv("PARAMS_FILTERS"); filters != "" { - config.ParamsFilters = []string{filters} - } - - acs, err := strconv.ParseBool(os.Getenv("AUTO_CAPTURE_SESSIONS")) - if err == nil { - config.AutoCaptureSessions = acs - } - bugsnag.Configure(config) - - // Increase publish rate for testing - bugsnag.DefaultSessionPublishInterval = time.Millisecond * 300 -} - func handledError(w http.ResponseWriter, r *http.Request) { if _, err := os.Open("nonexistent_file.txt"); err != nil { if errClass := os.Getenv("ERROR_CLASS"); errClass != "" { @@ -137,4 +100,4 @@ func user(w http.ResponseWriter, r *http.Request) { Name: "test-user-name", Email: "test-user-email", }) -} +} \ No newline at end of file diff --git a/features/fixtures/app/panic_scenario.go b/features/fixtures/app/panic_scenario.go new file mode 100644 index 00000000..aed6c1ec --- /dev/null +++ b/features/fixtures/app/panic_scenario.go @@ -0,0 +1,22 @@ +package main + +import ( + "github.com/bugsnag/bugsnag-go/v2" +) + +func AutonotifyPanicScenario(command Command) func() { + scenarioFunc := func() { + defer bugsnag.AutoNotify() + panic("Go routine killed with auto notify") + } + + return scenarioFunc +} + +func RecoverAfterPanicScenario(command Command) func() { + scenarioFunc := func() { + defer bugsnag.Recover() + panic("Go routine killed but recovered") + } + return scenarioFunc +} \ No newline at end of file diff --git a/features/fixtures/app/run.sh b/features/fixtures/app/run.sh index 3a98cd7e..67eb31d5 100755 --- a/features/fixtures/app/run.sh +++ b/features/fixtures/app/run.sh @@ -23,3 +23,5 @@ $PROC & # signal APP_PID=$! wait $APP_PID + +go run . \ No newline at end of file diff --git a/features/fixtures/app/session_scenario.go b/features/fixtures/app/session_scenario.go new file mode 100644 index 00000000..cdd66a80 --- /dev/null +++ b/features/fixtures/app/session_scenario.go @@ -0,0 +1,23 @@ +package main + +import ( + "context" + "fmt" + + "github.com/bugsnag/bugsnag-go/v2" +) + +func SendSessionScenario(command Command) func() { + scenarioFunc := func() { + bugsnag.StartSession(context.Background()) + } + return scenarioFunc +} + +func SessionAndErrorScenario(command Command) func() { + scenarioFunc := func() { + ctx := bugsnag.StartSession(context.Background()) + bugsnag.Notify(fmt.Errorf("oops"), ctx) + } + return scenarioFunc +} \ No newline at end of file diff --git a/features/fixtures/app/unhandled_scenario.go b/features/fixtures/app/unhandled_scenario.go new file mode 100644 index 00000000..1fbed7de --- /dev/null +++ b/features/fixtures/app/unhandled_scenario.go @@ -0,0 +1,35 @@ +package main + +import ( + "context" + "fmt" + + "github.com/bugsnag/bugsnag-go/v2" +) + +//go:noinline +func UnhandledCrashScenario(command Command) func() { + scenarioFunc := func() { + fmt.Printf("Calling panic\n") + // Invalid type assertion, will panic + func(a interface{}) string { + return a.(string) + }(struct{}{}) + } + return scenarioFunc +} + +func MultipleUnhandledErrorsScenario(command Command) func() { + scenarioFunc := func() { + //Make the order of the below predictable + notifier := bugsnag.New(bugsnag.Configuration{Synchronous: true}) + notifier.FlushSessionsOnRepanic(false) + + ctx := bugsnag.StartSession(context.Background()) + defer func() { recover() }() + defer notifier.AutoNotify(ctx) + defer notifier.AutoNotify(ctx) + panic("oops") + } + return scenarioFunc +} \ No newline at end of file diff --git a/features/fixtures/app/user_scenario.go b/features/fixtures/app/user_scenario.go new file mode 100644 index 00000000..b8b57239 --- /dev/null +++ b/features/fixtures/app/user_scenario.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + + "github.com/bugsnag/bugsnag-go/v2" +) + +func SetUserScenario(command Command) func() { + scenarioFunc := func() { + bugsnag.Notify(fmt.Errorf("oops"), bugsnag.User{ + Id: "test-user-id", + Name: "test-user-name", + Email: "test-user-email", + }) + } + + return scenarioFunc +} \ No newline at end of file diff --git a/features/fixtures/app/utils.go b/features/fixtures/app/utils.go new file mode 100644 index 00000000..87937b27 --- /dev/null +++ b/features/fixtures/app/utils.go @@ -0,0 +1,55 @@ +package main + +import ( + "fmt" + "runtime" +) + +type CustomErr struct { + msg string + cause error + callers []uintptr +} + +func NewCustomErr(msg string, cause error) error { + callers := make([]uintptr, 8) + runtime.Callers(2, callers) + return CustomErr{ + msg: msg, + cause: cause, + callers: callers, + } +} + +func (err CustomErr) Error() string { + return err.msg +} + +func (err CustomErr) Unwrap() error { + return err.cause +} + +func (err CustomErr) Callers() []uintptr { + return err.callers +} + +func Login(token string) error { + val, err := CheckValue(len(token) * -1) + if err != nil { + return NewCustomErr("login failed", err) + } + fmt.Printf("val: %d\n", val) + return nil +} + +func CheckValue(i int) (int, error) { + if i < 0 { + return 0, NewCustomErr("invalid token", nil) + } else if i%2 == 0 { + return i / 2, nil + } else if i < 9 { + return i * 3, nil + } + + return i * 4, nil +} \ No newline at end of file diff --git a/features/fixtures/autoconfigure/Dockerfile b/features/fixtures/autoconfigure/Dockerfile deleted file mode 100644 index 2d6db32c..00000000 --- a/features/fixtures/autoconfigure/Dockerfile +++ /dev/null @@ -1,36 +0,0 @@ -ARG GO_VERSION -FROM golang:${GO_VERSION}-alpine - -RUN apk update && apk upgrade && apk add git bash build-base - -ENV GOPATH /app - -COPY testbuild /app/src/github.com/bugsnag/bugsnag-go -WORKDIR /app/src/github.com/bugsnag/bugsnag-go/v2 - -# Ensure subsequent steps are re-run if the GO_VERSION variable changes -ARG GO_VERSION - -# Get bugsnag dependencies using a conditional call to run go get or go install based on the go version -RUN if [[ $(echo -e "1.11\n$GO_VERSION\n1.16" | sort -V | head -2 | tail -1) == "$GO_VERSION" ]]; then \ - echo "Version is between 1.11 and 1.16, running go get"; \ - go get ./...; \ - else \ - echo "Version is greater than 1.16, running go install"; \ - go install ./...; \ - fi - -# Copy test scenarios -COPY ./autoconfigure /app/src/test -WORKDIR /app/src/test - -# Create app module - avoid locking bugsnag dep by not checking it in -# Skip on old versions of Go which pre-date modules -RUN if [[ $GO_VERSION != '1.11' && $GO_VERSION != '1.12' ]]; then \ - go mod init && go mod tidy; \ - echo "replace github.com/bugsnag/bugsnag-go/v2 => /app/src/github.com/bugsnag/bugsnag-go/v2" >> go.mod; \ - go mod tidy; \ - fi - -RUN chmod +x run.sh -CMD ["/app/src/test/run.sh"] diff --git a/features/fixtures/autoconfigure/cases.go b/features/fixtures/autoconfigure/cases.go deleted file mode 100644 index 2700a4c8..00000000 --- a/features/fixtures/autoconfigure/cases.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/bugsnag/bugsnag-go/v2" -) - -func explicitPanic() { - panic("PANIQ!") -} - -func handledEvent() { - bugsnag.Notify(fmt.Errorf("gone awry!")) -} - -func handledMetadata() { - bugsnag.OnBeforeNotify(func(event *bugsnag.Event, config *bugsnag.Configuration) error { - event.MetaData.Add("fruit", "Tomato", "beefsteak") - event.MetaData.Add("snacks", "Carrot", "4") - return nil - }) - handledEvent() -} diff --git a/features/fixtures/autoconfigure/main.go b/features/fixtures/autoconfigure/main.go deleted file mode 100644 index 8dddcebe..00000000 --- a/features/fixtures/autoconfigure/main.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "time" - - "github.com/bugsnag/bugsnag-go/v2" -) - -var testcase = flag.String("test", "", "the error scenario to run") - -func main() { - bugsnag.Configure(bugsnag.Configuration{}) - - // Increase publish rate for testing - bugsnag.DefaultSessionPublishInterval = time.Millisecond * 50 - - flag.Parse() - - switch *testcase { - case "panic": - explicitPanic() - case "handled": - handledEvent() - case "handled-metadata": - handledMetadata() - case "no-op": - // nothing to see here - default: - fmt.Printf("No test case found for '%s'\n", *testcase) - } - time.Sleep(time.Millisecond * 100) // time to send before termination -} diff --git a/features/fixtures/autoconfigure/run.sh b/features/fixtures/autoconfigure/run.sh deleted file mode 100755 index 3a98cd7e..00000000 --- a/features/fixtures/autoconfigure/run.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash - -# SIGTERM or SIGINT trapped (likely SIGTERM from docker), pass it onto app -# process -function _term_or_init { - kill -TERM "$APP_PID" 2>/dev/null - wait $APP_PID -} - -# The bugsnag notifier monitor process needs at least 300ms, in order to ensure -# that it can send its notify -function _exit { - sleep 1 -} - -trap _term_or_init SIGTERM SIGINT -trap _exit EXIT - -PROC="${@:1}" -$PROC & - -# Wait on the app process to ensure that this script is able to trap the SIGTERM -# signal -APP_PID=$! -wait $APP_PID diff --git a/features/fixtures/docker-compose.yml b/features/fixtures/docker-compose.yml index 6bc2f8df..a48ece35 100644 --- a/features/fixtures/docker-compose.yml +++ b/features/fixtures/docker-compose.yml @@ -1,35 +1,15 @@ -version: '3.4' services: app: build: - context: . - dockerfile: app/Dockerfile + context: ../../ + dockerfile: ./features/fixtures/app/Dockerfile args: - GO_VERSION + ports: + - "4512:4512" environment: - - API_KEY + - DEFAULT_MAZE_ADDRESS - ERROR_CLASS - - BUGSNAG_ENDPOINT - - APP_VERSION - - APP_TYPE - - AUTO_CAPTURE_SESSIONS - - HOSTNAME - - NOTIFY_RELEASE_STAGES - - RELEASE_STAGE - - PARAMS_FILTERS - - SYNCHRONOUS - - SERVER_PORT - - BUGSNAG_SOURCE_ROOT - - BUGSNAG_PROJECT_PACKAGES - restart: "no" - - autoconfigure: - build: - context: . - dockerfile: autoconfigure/Dockerfile - args: - - GO_VERSION - environment: - BUGSNAG_API_KEY - BUGSNAG_APP_TYPE - BUGSNAG_APP_VERSION @@ -51,33 +31,6 @@ services: - BUGSNAG_METADATA_fruit_Tomato - BUGSNAG_METADATA_snacks_Carrot restart: "no" - command: go run . - - nethttp: - build: - context: . - dockerfile: net_http/Dockerfile - args: - - GO_VERSION - ports: - - "4512:4512" - environment: - - API_KEY - - ERROR_CLASS - - BUGSNAG_ENDPOINT - - APP_VERSION - - APP_TYPE - - HOSTNAME - - NOTIFY_RELEASE_STAGES - - RELEASE_STAGE - - PARAMS_FILTERS - - AUTO_CAPTURE_SESSIONS - - SYNCHRONOUS - - SERVER_PORT - - BUGSNAG_SOURCE_ROOT - - BUGSNAG_PROJECT_PACKAGES - restart: "no" - command: go run main.go gin: build: diff --git a/features/fixtures/net_http/Dockerfile b/features/fixtures/net_http/Dockerfile deleted file mode 100644 index fb5e526e..00000000 --- a/features/fixtures/net_http/Dockerfile +++ /dev/null @@ -1,35 +0,0 @@ -ARG GO_VERSION -FROM golang:${GO_VERSION}-alpine - -RUN apk update && \ - apk upgrade && \ - apk add git build-base - -ENV GOPATH /app - -COPY testbuild /app/src/github.com/bugsnag/bugsnag-go -WORKDIR /app/src/github.com/bugsnag/bugsnag-go/v2 - -# Ensure subsequent steps are re-run if the GO_VERSION variable changes -ARG GO_VERSION - -# Get bugsnag dependencies using a conditional call to run go get or go install based on the go version -RUN if [[ $(echo -e "1.11\n$GO_VERSION\n1.16" | sort -V | head -2 | tail -1) == "$GO_VERSION" ]]; then \ - echo "Version is between 1.11 and 1.16, running go get"; \ - go get ./...; \ - else \ - echo "Version is greater than 1.16, running go install"; \ - go install ./...; \ - fi - -# Copy test scenarios -COPY ./net_http /app/src/test -WORKDIR /app/src/test - -# Create app module - avoid locking bugsnag dep by not checking it in -# Skip on old versions of Go which pre-date modules -RUN if [[ $GO_VERSION != '1.11' && $GO_VERSION != '1.12' ]]; then \ - go mod init && go mod tidy; \ - echo "replace github.com/bugsnag/bugsnag-go/v2 => /app/src/github.com/bugsnag/bugsnag-go/v2" >> go.mod; \ - go mod tidy; \ - fi diff --git a/features/handled.feature b/features/handled.feature index a7797a8a..c5b98cdf 100644 --- a/features/handled.feature +++ b/features/handled.feature @@ -1,70 +1,67 @@ Feature: Plain handled errors Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - Given I set environment variable "BUGSNAG_SOURCE_ROOT" to the app directory - And I configure the bugsnag endpoint - And I have built the service "app" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" + Given I set environment variable "BUGSNAG_SOURCE_ROOT" to "/app/src/features/fixtures/app/" + And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" Scenario: A handled error sends a report - When I run the go service "app" with the test case "handled" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + When I start the service "app" + And I run "HandledErrorScenario" + And I wait to receive an error And the event "unhandled" is false And the event "severity" equals "warning" And the event "severityReason.type" equals "handledError" - And the exception is a PathError for request 0 - And the "file" of stack frame 0 equals "main.go" + And the exception "errorClass" matches "\*os.PathError|\*fs.PathError" + And the "file" of stack frame 0 equals "handled_scenario.go" Scenario: A handled error sends a report with a custom name Given I set environment variable "ERROR_CLASS" to "MyCustomErrorClass" - When I run the go service "app" with the test case "handled" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + When I start the service "app" + And I run "HandledErrorScenario" + And I wait to receive an error And the event "unhandled" is false And the event "severity" equals "warning" And the event "severityReason.type" equals "handledError" And the exception "errorClass" equals "MyCustomErrorClass" - And the "file" of stack frame 0 equals "main.go" + And the "file" of stack frame 0 equals "handled_scenario.go" Scenario: Sending an event using a callback to modify report contents - When I run the go service "app" with the test case "handled-with-callback" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + When I start the service "app" + And I run "HandledCallbackErrorScenario" + And I wait to receive an error And the event "unhandled" is false And the event "severity" equals "info" And the event "severityReason.type" equals "userCallbackSetSeverity" And the event "context" equals "nonfatal.go:14" - And the "file" of stack frame 0 equals "main.go" - And stack frame 0 contains a local function spanning 245 to 253 + And the "file" of stack frame 0 equals "handled_scenario.go" + And the "lineNumber" of stack frame 0 equals 59 And the "file" of stack frame 1 equals ">insertion<" And the "lineNumber" of stack frame 1 equals 0 Scenario: Marking an error as unhandled in a callback - When I run the go service "app" with the test case "make-unhandled-with-callback" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + When I start the service "app" + And I run "HandledToUnhandledScenario" + And I wait to receive an error And the event "unhandled" is true And the event "severity" equals "error" And the event "severityReason.type" equals "userCallbackSetSeverity" And the event "severityReason.unhandledOverridden" is true - And the "file" of stack frame 0 equals "main.go" - And stack frame 0 contains a local function spanning 257 to 262 + And the "file" of stack frame 0 equals "handled_scenario.go" + And the "lineNumber" of stack frame 0 equals 72 Scenario: Unwrapping the causes of a handled error - When I run the go service "app" with the test case "nested-error" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + When I start the service "app" + And I run "NestedHandledErrorScenario" + And I wait to receive an error And the event "unhandled" is false And the event "severity" equals "warning" And the event "exceptions.0.message" equals "terminate process" - And the "lineNumber" of stack frame 0 equals 295 - And the "file" of stack frame 0 equals "main.go" - And the "method" of stack frame 0 equals "nestedHandledError" + And the "lineNumber" of stack frame 0 equals 40 + And the "file" of stack frame 0 equals "handled_scenario.go" + And the "method" of stack frame 0 equals "NestedHandledErrorScenario.func1" And the event "exceptions.1.message" equals "login failed" - And the event "exceptions.1.stacktrace.0.file" equals "main.go" - And the event "exceptions.1.stacktrace.0.lineNumber" equals 315 + And the event "exceptions.1.stacktrace.0.file" equals "utils.go" + And the event "exceptions.1.stacktrace.0.lineNumber" equals 39 And the event "exceptions.2.message" equals "invalid token" - And the event "exceptions.2.stacktrace.0.file" equals "main.go" - And the event "exceptions.2.stacktrace.0.lineNumber" equals 323 + And the event "exceptions.2.stacktrace.0.file" equals "utils.go" + And the event "exceptions.2.stacktrace.0.lineNumber" equals 47 \ No newline at end of file diff --git a/features/hostname.feature b/features/hostname.feature index 14c72c90..ddd44368 100644 --- a/features/hostname.feature +++ b/features/hostname.feature @@ -1,22 +1,17 @@ Feature: Configuring hostname -Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I configure the bugsnag endpoint - And I have built the service "app" - Scenario: An error report contains the configured hostname - Given I set environment variable "HOSTNAME" to "server-1a" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - When I run the go service "app" with the test case "handled" - And I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + Given I set environment variable "BUGSNAG_HOSTNAME" to "server-1a" + And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" + When I start the service "app" + And I run "HandledErrorScenario" + And I wait to receive an error And the event "device.hostname" equals "server-1a" -Scenario: An session report contains the configured hostname - Given I set environment variable "HOSTNAME" to "server-1a" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "true" - When I run the go service "app" with the test case "session" - And I wait to receive a request after the start up session - And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the payload field "device.hostname" equals "server-1a" +Scenario: A session report contains the configured hostname + Given I set environment variable "BUGSNAG_HOSTNAME" to "server-1a" + And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "1" + When I start the service "app" + And I run "SendSessionScenario" + And I wait to receive 2 sessions + And the session payload field "device.hostname" equals "server-1a" \ No newline at end of file diff --git a/features/metadata.feature b/features/metadata.feature index 94a8bbf6..acdc1411 100644 --- a/features/metadata.feature +++ b/features/metadata.feature @@ -1,15 +1,10 @@ Feature: Sending meta data -Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - And I configure the bugsnag endpoint - And I have built the service "app" - Scenario: An error report contains custom meta data - When I run the go service "app" with the test case "metadata" - And I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + When I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" + And I start the service "app" + And I run "MetadataScenario" + And I wait to receive an error And the event "metaData.Scheme.Customer.Name" equals "Joe Bloggs" And the event "metaData.Scheme.Customer.Age" equals "21" - And the event "metaData.Scheme.Level" equals "Blue" + And the event "metaData.Scheme.Level" equals "Blue" \ No newline at end of file diff --git a/features/multieventsession.feature b/features/multieventsession.feature index e9e4dbc1..bb9039dc 100644 --- a/features/multieventsession.feature +++ b/features/multieventsession.feature @@ -1,23 +1,20 @@ Feature: Reporting multiple handled and unhandled errors in the same session Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I configure the bugsnag endpoint - And I have built the service "app" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" + Given I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" Scenario: Handled errors know about previous reported handled errors - When I run the go service "app" with the test case "multiple-handled" - And I wait to receive 2 requests - And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the request 1 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the event handled sessions count equals 1 for request 0 - And the event handled sessions count equals 2 for request 1 + When I start the service "app" + And I run "MultipleHandledErrorsScenario" + And I wait to receive 2 errors + And the event handled sessions count equals 1 + And I discard the oldest error + And the event handled sessions count equals 2 Scenario: Unhandled errors know about previous reported handled errors - When I run the go service "app" with the test case "multiple-unhandled" - And I wait to receive 2 requests - And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the request 1 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the event unhandled sessions count equals 1 for request 0 - And the event unhandled sessions count equals 2 for request 1 + When I start the service "app" + And I run "MultipleUnhandledErrorsScenario" + And I wait to receive 2 errors + And the event unhandled sessions count equals 1 + And I discard the oldest error + And the event unhandled sessions count equals 2 \ No newline at end of file diff --git a/features/net-http/appversion.feature b/features/net-http/appversion.feature index 06e9f61e..c2d10ade 100644 --- a/features/net-http/appversion.feature +++ b/features/net-http/appversion.feature @@ -1,28 +1,25 @@ Feature: Configuring app version Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I configure the bugsnag endpoint - And I set environment variable "APP_VERSION" to "3.1.2" - And I set environment variable "SERVER_PORT" to "4512" + And I set environment variable "BUGSNAG_APP_VERSION" to "3.1.2" Scenario: A error report contains the configured app type when using a net http app - Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - When I start the service "nethttp" - And I wait for the app to open port "4512" - And I wait for 2 seconds + Given I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" + When I start the service "app" + And I run "HttpServerScenario" + And I wait for the host "localhost" to open port "4512" And I open the URL "http://localhost:4512/handled" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + And I wait to receive an error + And I should receive no sessions + And the error is valid for the error reporting API version "4" for the "Bugsnag Go" notifier And the event "app.version" equals "3.1.2" Scenario: A session report contains the configured app type when using a net http app - Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true" - When I start the service "nethttp" - And I wait for the app to open port "4512" - And I wait for 2 seconds + Given I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "1" + When I start the service "app" + And I run "HttpServerScenario" + And I wait for the host "localhost" to open port "4512" And I open the URL "http://localhost:4512/session" - Then I wait to receive a request after the start up session - And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the payload field "app.version" equals "3.1.2" - + And I wait to receive a session + And the session is valid for the session reporting API version "1.0" for the "Bugsnag Go" notifier + And the session payload field "app.version" equals "3.1.2" diff --git a/features/net-http/autocapturesessions.feature b/features/net-http/autocapturesessions.feature deleted file mode 100644 index b7f00bb4..00000000 --- a/features/net-http/autocapturesessions.feature +++ /dev/null @@ -1,24 +0,0 @@ -Feature: Configure auto capture sessions - -Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I configure the bugsnag endpoint - And I set environment variable "SERVER_PORT" to "4512" - -Scenario: A session is not sent if auto capture sessions is off - Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - When I start the service "nethttp" - And I wait for the app to open port "4512" - And I wait for 2 seconds - And I open the URL "http://localhost:4512/session" - And I wait for 2 seconds - Then I should receive no requests - -Scenario: A session is sent if auto capture sessions is on - Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true" - When I start the service "nethttp" - And I wait for the app to open port "4512" - And I wait for 2 seconds - And I open the URL "http://localhost:4512/session" - Then I wait to receive a request after the start up session - And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" diff --git a/features/net-http/autonotify.feature b/features/net-http/autonotify.feature index baf4198c..cbea77d3 100644 --- a/features/net-http/autonotify.feature +++ b/features/net-http/autonotify.feature @@ -1,30 +1,23 @@ Feature: Using auto notify -Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I configure the bugsnag endpoint - And I set environment variable "SERVER_PORT" to "4512" - Scenario: An error report is sent when an AutoNotified crash occurs which later gets recovered - Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - When I start the service "nethttp" - And I wait for the app to open port "4512" - And I wait for 2 seconds + Given I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" + When I start the service "app" + And I run "HttpServerScenario" + And I wait for the host "localhost" to open port "4512" And I open the URL "http://localhost:4512/autonotify-then-recover" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + Then I wait to receive an error And the event "unhandled" is true And the exception "errorClass" equals "*runtime.TypeAssertionError" And the exception "message" matches "interface conversion: interface ({} )?is struct {}, not string" Scenario: An error report is sent when a go routine crashes which is reported through auto notify - Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - When I start the service "nethttp" - And I wait for the app to open port "4512" - And I wait for 2 seconds + Given I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" + When I start the service "app" + And I run "HttpServerScenario" + And I wait for the host "localhost" to open port "4512" And I open the URL "http://localhost:4512/autonotify" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + Then I wait to receive an error And the event "unhandled" is true And the exception "errorClass" equals "*errors.errorString" - And the exception "message" equals "Go routine killed with auto notify" + And the exception "message" equals "Go routine killed with auto notify" \ No newline at end of file diff --git a/features/net-http/handled.feature b/features/net-http/handled.feature index 4c9eb4cc..c450db0d 100644 --- a/features/net-http/handled.feature +++ b/features/net-http/handled.feature @@ -1,35 +1,29 @@ Feature: Handled errors Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - Given I set environment variable "BUGSNAG_SOURCE_ROOT" to the app directory - And I configure the bugsnag endpoint - And I set environment variable "SERVER_PORT" to "4512" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" + Given I set environment variable "BUGSNAG_SOURCE_ROOT" to "/app/src/features/fixtures/app/" Scenario: A handled error sends a report - When I start the service "nethttp" - And I wait for the app to open port "4512" - And I wait for 2 seconds + When I start the service "app" + And I run "HttpServerScenario" + And I wait for the host "localhost" to open port "4512" And I open the URL "http://localhost:4512/handled" - Then I wait to receive a request - And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the event "unhandled" is false for request 0 - And the event "severity" equals "warning" for request 0 - And the event "severityReason.type" equals "handledError" for request 0 - And the exception is a PathError for request 0 - And the "file" of stack frame 0 equals "main.go" for request 0 + Then I wait to receive an error + And the event "unhandled" is false + And the event "severity" equals "warning" + And the event "severityReason.type" equals "handledError" + And the exception "errorClass" matches "\*os.PathError|\*fs.PathError" + And the "file" of stack frame 0 equals "nethttp_scenario.go" Scenario: A handled error sends a report with a custom name Given I set environment variable "ERROR_CLASS" to "MyCustomErrorClass" - When I start the service "nethttp" - And I wait for the app to open port "4512" - And I wait for 2 seconds + When I start the service "app" + And I run "HttpServerScenario" + And I wait for the host "localhost" to open port "4512" And I open the URL "http://localhost:4512/handled" - Then I wait to receive a request - And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the event "unhandled" is false for request 0 - And the event "severity" equals "warning" for request 0 - And the event "severityReason.type" equals "handledError" for request 0 - And the exception "errorClass" equals "MyCustomErrorClass" for request 0 - And the "file" of stack frame 0 equals "main.go" for request 0 + Then I wait to receive an error + And the event "unhandled" is false + And the event "severity" equals "warning" + And the event "severityReason.type" equals "handledError" + And the exception "errorClass" equals "MyCustomErrorClass" + And the "file" of stack frame 0 equals "nethttp_scenario.go" \ No newline at end of file diff --git a/features/net-http/onbeforenotify.feature b/features/net-http/onbeforenotify.feature index d0b519c4..15d58b22 100644 --- a/features/net-http/onbeforenotify.feature +++ b/features/net-http/onbeforenotify.feature @@ -1,18 +1,11 @@ Feature: Configuring on before notify -Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I configure the bugsnag endpoint - And I set environment variable "SERVER_PORT" to "4512" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - Scenario: Send three bugsnags and use on before notify to drop one and modify the message of another - When I start the service "nethttp" - And I wait for the app to open port "4512" - And I wait for 2 seconds + When I start the service "app" + And I run "HttpServerScenario" + And I wait for the host "localhost" to open port "4512" And I open the URL "http://localhost:4512/onbeforenotify" - Then I wait to receive 2 requests - And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the exception "message" equals "Don't ignore this error" for request 0 - And the request 1 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the exception "message" equals "Error message was changed" for request 1 + Then I wait to receive 2 errors + And the exception "message" equals "Don't ignore this error" + And I discard the oldest error + And the exception "message" equals "Error message was changed" \ No newline at end of file diff --git a/features/net-http/recover.feature b/features/net-http/recover.feature index 5f64d78b..8517bfda 100644 --- a/features/net-http/recover.feature +++ b/features/net-http/recover.feature @@ -1,17 +1,10 @@ Feature: Using recover -Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I configure the bugsnag endpoint - And I set environment variable "SERVER_PORT" to "4512" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - Scenario: An error report is sent when request crashes but is recovered - When I start the service "nethttp" - And I wait for the app to open port "4512" - And I wait for 2 seconds + When I start the service "app" + And I run "HttpServerScenario" + And I wait for the host "localhost" to open port "4512" And I open the URL "http://localhost:4512/recover" - Then I wait to receive a request - And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the exception "errorClass" equals "*errors.errorString" for request 0 - And the exception "message" equals "Request killed but recovered" for request 0 + Then I wait to receive an error + And the exception "errorClass" equals "*errors.errorString" + And the exception "message" equals "Request killed but recovered" \ No newline at end of file diff --git a/features/net-http/releasestage.feature b/features/net-http/releasestage.feature index e1b50169..f303c765 100644 --- a/features/net-http/releasestage.feature +++ b/features/net-http/releasestage.feature @@ -1,27 +1,22 @@ Feature: Configuring release stage Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I configure the bugsnag endpoint - And I set environment variable "SERVER_PORT" to "4512" - And I set environment variable "RELEASE_STAGE" to "my-stage" + Given I set environment variable "BUGSNAG_RELEASE_STAGE" to "my-stage" Scenario: An error report is sent with configured release stage - Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - When I start the service "nethttp" - And I wait for the app to open port "4512" - And I wait for 2 seconds + Given I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" + When I start the service "app" + And I run "HttpServerScenario" + And I wait for the host "localhost" to open port "4512" And I open the URL "http://localhost:4512/handled" - Then I wait to receive a request - And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the event "app.releaseStage" equals "my-stage" for request 0 + Then I wait to receive an error + And the event "app.releaseStage" equals "my-stage" Scenario: A session report contains the configured app type - Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true" - When I start the service "nethttp" - And I wait for the app to open port "4512" - And I wait for 2 seconds + Given I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "1" + When I start the service "app" + And I run "HttpServerScenario" + And I wait for the host "localhost" to open port "4512" And I open the URL "http://localhost:4512/session" - Then I wait to receive a request after the start up session - And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the payload field "app.releaseStage" equals "my-stage" + Then I wait to receive a session + And the session payload field "app.releaseStage" equals "my-stage" \ No newline at end of file diff --git a/features/net-http/request.feature b/features/net-http/request.feature index ecf7f347..b6d4067d 100644 --- a/features/net-http/request.feature +++ b/features/net-http/request.feature @@ -1,20 +1,13 @@ Feature: Capturing request information automatically -Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I configure the bugsnag endpoint - And I set environment variable "SERVER_PORT" to "4512" - Scenario: An error report will automatically contain request information - Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - When I start the service "nethttp" - And I wait for the app to open port "4512" - And I wait for 2 seconds + When I start the service "app" + And I run "HttpServerScenario" + And I wait for the host "localhost" to open port "4512" And I open the URL "http://localhost:4512/handled" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + Then I wait to receive an error And the event "request.clientIp" is not null And the event "request.headers.User-Agent" equals "Ruby" And the event "request.httpMethod" equals "GET" And the event "request.url" ends with "/handled" - And the event "request.url" starts with "http://" + And the event "request.url" starts with "http://" \ No newline at end of file diff --git a/features/net-http/user.feature b/features/net-http/user.feature index dd03d9cd..78468002 100644 --- a/features/net-http/user.feature +++ b/features/net-http/user.feature @@ -1,21 +1,11 @@ Feature: Sending user data -Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I configure the bugsnag endpoint - And I set environment variable "SERVER_PORT" to "4512" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - Scenario: An error report contains custom user data - Given I set environment variable "USER_ID" to "test-user-id" - And I set environment variable "USER_NAME" to "test-user-name" - And I set environment variable "USER_EMAIL" to "test-user-email" - When I start the service "nethttp" - And I wait for the app to open port "4512" - And I wait for 2 seconds + When I start the service "app" + And I run "HttpServerScenario" + And I wait for the host "localhost" to open port "4512" And I open the URL "http://localhost:4512/user" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + Then I wait to receive an error And the event "user.id" equals "test-user-id" And the event "user.name" equals "test-user-name" - And the event "user.email" equals "test-user-email" + And the event "user.email" equals "test-user-email" \ No newline at end of file diff --git a/features/onbeforenotify.feature b/features/onbeforenotify.feature index 7b4b332f..a53fe1f0 100644 --- a/features/onbeforenotify.feature +++ b/features/onbeforenotify.feature @@ -1,14 +1,9 @@ Feature: Configuring on before notify -Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I configure the bugsnag endpoint - And I have built the service "app" - Scenario: Send three bugsnags and use on before notify to drop one and modify the message of another - When I run the go service "app" with the test case "onbeforenotify" - Then I wait to receive 2 requests after the start up session - And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the exception "message" equals "Don't ignore this error" for request 0 - And the request 1 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the exception "message" equals "Error message was changed" for request 1 + When I start the service "app" + And I run "OnBeforeNotifyScenario" + And I wait to receive 2 errors + And the exception "message" equals "don't ignore this error" + And I discard the oldest error + And the exception "message" equals "error message was changed" \ No newline at end of file diff --git a/features/paramfilters.feature b/features/paramfilters.feature index d1b714b8..58f2c7d8 100644 --- a/features/paramfilters.feature +++ b/features/paramfilters.feature @@ -1,29 +1,22 @@ Feature: Configuring param filters -Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - And I configure the bugsnag endpoint - And I have built the service "app" - Scenario: An error report containing meta data is not filtered when the param filters are set but do not match - Given I set environment variable "PARAMS_FILTERS" to "Name" - When I run the go service "app" with the test case "filtered" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + Given I set environment variable "BUGSNAG_PARAMS_FILTERS" to "Name" + When I start the service "app" + And I run "FilteredMetadataScenario" + And I wait to receive an error And the event "metaData.Account.Price(dollars)" equals "1 Million" Scenario: An error report containing meta data is filtered when the param filters are set and completely match - Given I set environment variable "PARAMS_FILTERS" to "Price(dollars)" - When I run the go service "app" with the test case "filtered" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + Given I set environment variable "BUGSNAG_PARAMS_FILTERS" to "Price(dollars)" + When I start the service "app" + And I run "FilteredMetadataScenario" + And I wait to receive an error And the event "metaData.Account.Price(dollars)" equals "[FILTERED]" Scenario: An error report containing meta data is filtered when the param filters are set and partially match - Given I set environment variable "PARAMS_FILTERS" to "Price" - When I run the go service "app" with the test case "filtered" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + Given I set environment variable "BUGSNAG_PARAMS_FILTERS" to "Price" + When I start the service "app" + And I run "FilteredMetadataScenario" + And I wait to receive an error And the event "metaData.Account.Price(dollars)" equals "[FILTERED]" - diff --git a/features/plain_features/panics.feature b/features/plain_features/panics.feature index 2f239a3b..691a007e 100644 --- a/features/plain_features/panics.feature +++ b/features/plain_features/panics.feature @@ -1,24 +1,19 @@ Feature: Panic handling Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - Given I set environment variable "BUGSNAG_SOURCE_ROOT" to the app directory - And I configure the bugsnag endpoint - And I have built the service "app" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" + Given I set environment variable "BUGSNAG_SOURCE_ROOT" to "/app/src/features/fixtures/app/" + And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" Scenario: Capturing a panic - When I run the go service "app" with the test case "unhandled" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + When I start the service "app" + And I run "UnhandledCrashScenario" + And I wait to receive an error And the event "unhandled" is true And the event "severity" equals "error" And the event "severityReason.type" equals "unhandledPanic" And the exception "errorClass" equals "panic" - And the exception "message" is one of: - | interface conversion: interface is struct {}, not string | - | interface conversion: interface {} is struct {}, not string | - And the in-project frames of the stacktrace are: - | file | method | - | main.go | unhandledCrash.func1 | - | main.go | unhandledCrash | + And the exception "message" matches "^interface conversion: interface.*?is struct {}, not string" + And the "file" of stack frame 0 equals "unhandled_scenario.go" + And the "method" of stack frame 0 equals "UnhandledCrashScenario.func1.1" + And the "file" of stack frame 1 equals "unhandled_scenario.go" + And the "method" of stack frame 1 equals "UnhandledCrashScenario.func1" \ No newline at end of file diff --git a/features/recover.feature b/features/recover.feature index cbdb1f55..8df1bf2a 100644 --- a/features/recover.feature +++ b/features/recover.feature @@ -1,14 +1,8 @@ Feature: Using recover -Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - And I configure the bugsnag endpoint - And I have built the service "app" - Scenario: An error report is sent when a go routine crashes but recovers - When I run the go service "app" with the test case "recover" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + When I start the service "app" + And I run "RecoverAfterPanicScenario" + And I wait to receive an error And the exception "errorClass" equals "*errors.errorString" - And the exception "message" equals "Go routine killed but recovered" + And the exception "message" equals "Go routine killed but recovered" \ No newline at end of file diff --git a/features/releasestage.feature b/features/releasestage.feature index 6b16ee48..2652b0c5 100644 --- a/features/releasestage.feature +++ b/features/releasestage.feature @@ -1,71 +1,65 @@ Feature: Configuring release stages and notify release stages -Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I configure the bugsnag endpoint - And I have built the service "app" - Scenario: An error report is sent when release stage matches notify release stages - Given I set environment variable "NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - And I set environment variable "RELEASE_STAGE" to "stage2" - When I run the go service "app" with the test case "handled" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + Given I set environment variable "BUGSNAG_NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3" + And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" + And I set environment variable "BUGSNAG_RELEASE_STAGE" to "stage2" + When I start the service "app" + And I run "HandledErrorScenario" + And I wait to receive an error And the event "app.releaseStage" equals "stage2" Scenario: An error report is sent when no notify release stages are specified - Given I set environment variable "RELEASE_STAGE" to "stage2" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - When I run the go service "app" with the test case "handled" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + Given I set environment variable "BUGSNAG_RELEASE_STAGE" to "stage2" + And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" + When I start the service "app" + And I run "HandledErrorScenario" + And I wait to receive an error And the event "app.releaseStage" equals "stage2" Scenario: An error report is sent regardless of notify release stages if release stage is not set - Given I set environment variable "NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - When I run the go service "app" with the test case "handled" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + Given I set environment variable "BUGSNAG_NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3" + And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" + When I start the service "app" + And I run "HandledErrorScenario" + And I wait to receive an error Scenario: An error report is not sent if the release stage does not match the notify release stages - Given I set environment variable "NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - And I set environment variable "RELEASE_STAGE" to "stage4" - When I run the go service "app" with the test case "handled" - And I wait for 3 second - Then I should receive no requests - -Scenario: An session report is sent when release stage matches notify release stages - Given I set environment variable "NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "true" - And I set environment variable "RELEASE_STAGE" to "stage2" - When I run the go service "app" with the test case "session" - Then I wait to receive a request after the start up session - And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the payload field "app.releaseStage" equals "stage2" + Given I set environment variable "BUGSNAG_NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3" + And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0" + And I set environment variable "BUGSNAG_RELEASE_STAGE" to "stage4" + When I start the service "app" + And I run "HandledErrorScenario" + And I should receive no errors -Scenario: An session report is sent when no notify release stages are specified - Given I set environment variable "RELEASE_STAGE" to "stage2" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "true" - When I run the go service "app" with the test case "session" - Then I wait to receive a request after the start up session - And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the payload field "app.releaseStage" equals "stage2" +Scenario: A session report is sent when release stage matches notify release stages + Given I set environment variable "BUGSNAG_NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3" + And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "1" + And I set environment variable "BUGSNAG_RELEASE_STAGE" to "stage2" + When I start the service "app" + And I run "SendSessionScenario" + And I wait to receive 2 sessions + And the session payload field "app.releaseStage" equals "stage2" -Scenario: An session report is sent regardless of notify release stages if release stage is not set - Given I set environment variable "NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "true" - When I run the go service "app" with the test case "session" - Then I wait to receive a request after the start up session - And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" +Scenario: A session report is sent when no notify release stages are specified + Given I set environment variable "BUGSNAG_RELEASE_STAGE" to "stage2" + And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "1" + When I start the service "app" + And I run "SendSessionScenario" + And I wait to receive 2 sessions + And the session payload field "app.releaseStage" equals "stage2" -Scenario: An session report is not sent if the release stage does not match the notify release stages - Given I set environment variable "NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "true" - And I set environment variable "RELEASE_STAGE" to "stage4" - When I run the go service "app" with the test case "session" - And I wait for 3 second - Then I should receive no requests +Scenario: A session report is sent regardless of notify release stages if release stage is not set + Given I set environment variable "BUGSNAG_NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3" + And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "1" + When I start the service "app" + And I run "SendSessionScenario" + And I wait to receive 2 sessions +Scenario: A session report is not sent if the release stage does not match the notify release stages + Given I set environment variable "BUGSNAG_NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3" + And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "1" + And I set environment variable "BUGSNAG_RELEASE_STAGE" to "stage4" + When I start the service "app" + And I run "SendSessionScenario" + And I should receive no sessions diff --git a/features/sessioncontext.feature b/features/sessioncontext.feature index 0b986395..8cc09a14 100644 --- a/features/sessioncontext.feature +++ b/features/sessioncontext.feature @@ -1,15 +1,13 @@ Feature: Session data inside an error report using a session context -Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I configure the bugsnag endpoint - And I have built the service "app" - Scenario: An error report contains a session count when part of a session - When I run the go service "app" with the test case "session-and-error" - Then I wait to receive 2 requests after the start up session - And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the request 1 is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - And the event handled sessions count equals 1 for request 0 - And the event unhandled sessions count equals 0 for request 0 - And the number of sessions started equals 1 for request 1 + When I start the service "app" + And I run "SessionAndErrorScenario" + Then I wait to receive 1 error + # one session is created on start + And I wait to receive 2 session + And the error is valid for the error reporting API version "4" for the "Bugsnag Go" notifier + And the session is valid for the session reporting API version "1.0" for the "Bugsnag Go" notifier + And I discard the oldest session + And the session is valid for the session reporting API version "1.0" for the "Bugsnag Go" notifier + And the session payload has a valid sessions array \ No newline at end of file diff --git a/features/steps/go_steps.rb b/features/steps/go_steps.rb index 16a0e307..b8d5e398 100644 --- a/features/steps/go_steps.rb +++ b/features/steps/go_steps.rb @@ -1,122 +1,51 @@ require 'net/http' +require 'os' -Given("I set environment variable {string} to the app directory") do |key| - step("I set environment variable \"#{key}\" to \"/app/src/test/\"") +When('I run {string}') do |scenario_name| + execute_command 'run-scenario', scenario_name end -Given("I set environment variable {string} to the notify endpoint") do |key| - step("I set environment variable \"#{key}\" to \"http://#{current_ip}:#{MOCK_API_PORT}\"") +When('I configure the base endpoint') do + steps %( + When I set environment variable "DEFAULT_MAZE_ADDRESS" to "http://#{local_ip}:9339" + ) end -Given("I set environment variable {string} to the sessions endpoint") do |key| - # they're the same picture dot gif - # split them out for the future endpoint splitting work - step("I set environment variable \"#{key}\" to \"http://#{current_ip}:#{MOCK_API_PORT}\"") +Then('the event unhandled sessions count equals {int}') do |count| + step "the error payload field \"events.0.session.events.unhandled\" equals #{count}" end -Then(/^the request(?: (\d+))? is a valid error report with api key "(.*)"$/) do |request_index, api_key| - request_index ||= 0 - steps %Q{ - And the request #{request_index} is valid for the error reporting API - And the "bugsnag-api-key" header equals "#{api_key}" for request #{request_index} - And the payload field "apiKey" equals "#{api_key}" for request #{request_index} - } -end - -Then(/^the exception is a PathError for request (\d+)$/) do |request_index| - body = find_request(request_index)[:body] - error_class = body["events"][0]["exceptions"][0]["errorClass"] - if ['1.11', '1.12', '1.13', '1.14', '1.15'].include? ENV['GO_VERSION'] - assert_equal(error_class, '*os.PathError') - else - assert_equal(error_class, '*fs.PathError') - end -end - -Then(/^the request(?: (\d+))? is a valid session report with api key "(.*)"$/) do |request_index, api_key| - request_index ||= 0 - steps %Q{ - And the request #{request_index} is valid for the session tracking API - And the "bugsnag-api-key" header equals "#{api_key}" for request #{request_index} - } -end - -Then(/^the event unhandled sessions count equals (\d+) for request (\d+)$/) do |count, request_index| - step "the payload field \"events.0.session.events.unhandled\" equals #{count} for request #{request_index}" +Then('the event handled sessions count equals {int}') do |count| + step "the error payload field \"events.0.session.events.handled\" equals #{count}" end -Then(/^the event handled sessions count equals (\d+) for request (\d+)$/) do |count, request_index| - step "the payload field \"events.0.session.events.handled\" equals #{count} for request #{request_index}" -end -Then(/^the number of sessions started equals (\d+) for request (\d+)$/) do |count, request_index| - step "the payload field \"sessionCounts.0.sessionsStarted\" equals #{count} for request #{request_index}" -end +def execute_command(action, scenario_name = '') + address = $address ? $address : "#{local_ip}:9339" -When("I run the go service {string} with the test case {string}") do |service, testcase| - run_service_with_command(service, "./run.sh go run . -test=\"#{testcase}\"") -end - -Then(/^I wait to receive a request after the start up session$/) do - step "I wait to receive 1 requests after the start up session" -end - -Then(/^I wait to receive (\d+) requests after the start up session?$/) do |request_count| - max_attempts = 50 - attempts = 0 - start_up_message_received = false - start_up_message_removed = false - received = false - until (attempts >= max_attempts) || received - attempts += 1 - start_up_message_received ||= (stored_requests.size == 1) - if start_up_message_received && !start_up_message_removed - step 'the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"' - stored_requests.shift - start_up_message_removed = true - next - end - received = (stored_requests.size == request_count) - sleep 0.2 - end - raise "Requests not received in 10s (received #{stored_requests.size})" unless received - # Wait an extra second to ensure there are no further requests - sleep 1 - assert_equal(request_count, stored_requests.size, "#{stored_requests.size} requests received") -end + command = { + action: action, + scenario_name: scenario_name, + notify_endpoint: "http://#{address}/notify", + sessions_endpoint: "http://#{address}/sessions", + api_key: $api_key, + } -Then(/^(\d+) requests? (?:was|were) received$/) do |request_count| - sleep 1 - assert_equal(request_count, stored_requests.size, "#{stored_requests.size} requests received") -end + $logger.debug("Queuing command: #{command}") + Maze::Server.commands.add command -Then("the in-project frames of the stacktrace are:") do |table| - body = find_request(0)[:body] - stacktrace = body["events"][0]["exceptions"][0]["stacktrace"] - found = 0 # counts matching frames and ensures ordering is correct - expected = table.hashes.length - stacktrace.each do |frame| - if found < expected and frame["inProject"] and - frame["file"] == table.hashes[found]["file"] and - frame["method"] == table.hashes[found]["method"] - found = found + 1 - end - end - assert_equal(found, expected, "expected #{expected} matching frames but found #{found}. stacktrace:\n#{stacktrace}") + # Ensure fixture has read the command + count = 900 + sleep 0.1 until Maze::Server.commands.remaining.empty? || (count -= 1) < 1 + raise 'Test fixture did not GET /command' unless Maze::Server.commands.remaining.empty? end -Then("stack frame {int} contains a local function spanning {int} to {int}") do |frame, val, old_val| - # Old versions of Go put the line number on the end of the function - if ['1.7', '1.8'].include? ENV['GO_VERSION'] - step "the \"lineNumber\" of stack frame #{frame} equals #{old_val}" +def local_ip + if OS.mac? + 'host.docker.internal' else - step "the \"lineNumber\" of stack frame #{frame} equals #{val}" + ip_addr = `ifconfig | grep -Eo 'inet (addr:)?([0-9]*\\\.){3}[0-9]*' | grep -v '127.0.0.1'` + ip_list = /((?:[0-9]*\.){3}[0-9]*)/.match(ip_addr) + ip_list.captures.first end end - -Then("the exception {string} is one of:") do |key, table| - body = find_request(0)[:body] - exception = body["events"][0]["exceptions"][0] - options = table.raw.flatten - assert(options.include?(exception[key]), "expected '#{key}' to be one of #{options}") -end diff --git a/features/support/env.rb b/features/support/env.rb index 3e70280e..d806b189 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -1,16 +1,8 @@ -require 'fileutils' -require 'socket' -require 'timeout' - -testBuildFolder = 'features/fixtures/testbuild' - -FileUtils.rm_rf(testBuildFolder) -Dir.mkdir testBuildFolder - -# Copy the existing dir -`find . -name '*.go' -o -name 'go.sum' -o -name 'go.mod' \ - -not -path "./examples/*" \ - -not -path "./testutil/*" \ - -not -path "./v2/testutil/*" \ - -not -path "./features/*" \ - -not -name '*_test.go' | cpio -pdm #{testBuildFolder}` +Before do + Maze.config.enforce_bugsnag_integrity = false + $address = nil + $api_key = "166f5ad3590596f9aa8d601ea89af845" + steps %( + When I configure the base endpoint + ) +end \ No newline at end of file diff --git a/features/synchronous.feature b/features/synchronous.feature deleted file mode 100644 index 0a132c48..00000000 --- a/features/synchronous.feature +++ /dev/null @@ -1,20 +0,0 @@ -Feature: Configuring synchronous flag - -Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - And I configure the bugsnag endpoint - And I have built the service "app" - -Scenario: An error report is sent asynchrously but exits immediately so is not sent - Given I set environment variable "SYNCHRONOUS" to "false" - When I run the go service "app" with the test case "send-and-exit" - And I wait for 3 second - Then I should receive no requests - -Scenario: An error report is report synchronously so it will send before exiting - Given I set environment variable "SYNCHRONOUS" to "true" - When I run the go service "app" with the test case "send-and-exit" - Then I wait to receive 1 requests - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" - diff --git a/features/user.feature b/features/user.feature index 49040ac1..4cae7234 100644 --- a/features/user.feature +++ b/features/user.feature @@ -1,18 +1,9 @@ Feature: Sending user data -Background: - Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa" - Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false" - And I configure the bugsnag endpoint - And I have built the service "app" - Scenario: An error report contains custom user data - Given I set environment variable "USER_ID" to "test-user-id" - And I set environment variable "USER_NAME" to "test-user-name" - And I set environment variable "USER_EMAIL" to "test-user-email" - When I run the go service "app" with the test case "user" - Then I wait to receive a request - And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa" + When I start the service "app" + And I run "SetUserScenario" + And I wait to receive an error And the event "user.id" equals "test-user-id" And the event "user.name" equals "test-user-name" - And the event "user.email" equals "test-user-email" + And the event "user.email" equals "test-user-email" \ No newline at end of file diff --git a/v2/headers/prefixed.go b/v2/headers/prefixed.go index 099d7947..ea93f6b6 100644 --- a/v2/headers/prefixed.go +++ b/v2/headers/prefixed.go @@ -1,9 +1,11 @@ package headers -import "time" +import ( + "time" +) -//PrefixedHeaders returns a map of Content-Type and the 'Bugsnag-' headers for -//API key, payload version, and the time at which the request is being sent. +// PrefixedHeaders returns a map of Content-Type and the 'Bugsnag-' headers for +// API key, payload version, and the time at which the request is being sent. func PrefixedHeaders(apiKey, payloadVersion string) map[string]string { return map[string]string{ "Content-Type": "application/json", diff --git a/v2/payload.go b/v2/payload.go index be2234ce..1379fea7 100644 --- a/v2/payload.go +++ b/v2/payload.go @@ -32,7 +32,6 @@ func (p *payload) deliver() error { } buf, err := p.MarshalJSON() - if err != nil { return fmt.Errorf("bugsnag/payload.deliver: %v", err) } @@ -76,7 +75,7 @@ func (p *payload) MarshalJSON() ([]byte, error) { OsName: runtime.GOOS, RuntimeVersions: device.GetRuntimeVersions(), }, - Request: p.Request, + Request: p.Request, Exceptions: p.exceptions(), GroupingHash: p.GroupingHash, Metadata: p.MetaData.sanitize(p.ParamsFilters), @@ -123,7 +122,7 @@ func (p *payload) makeSession() *sessionJSON { func (p *payload) severityReasonPayload() *severityReasonJSON { if reason := p.handledState.SeverityReason; reason != "" { json := &severityReasonJSON{ - Type: reason, + Type: reason, UnhandledOverridden: p.handledState.Unhandled != p.Unhandled, } if p.handledState.Framework != "" { diff --git a/v2/sessions/publisher.go b/v2/sessions/publisher.go index b06985fa..5d636f47 100644 --- a/v2/sessions/publisher.go +++ b/v2/sessions/publisher.go @@ -36,41 +36,51 @@ func (p *publisher) publish(sessions []*Session) error { // log every minute return nil } + if apiKey := p.config.APIKey; len(apiKey) != 32 { return fmt.Errorf("bugsnag/sessions/publisher.publish invalid API key: '%s'", apiKey) } + nrs, rs := p.config.NotifyReleaseStages, p.config.ReleaseStage if rs != "" && (nrs != nil && !contains(nrs, rs)) { // Always send sessions if the release stage is not set, but don't send any // sessions when notify release stages don't match the current release stage return nil } + if len(sessions) == 0 { return fmt.Errorf("bugsnag/sessions/publisher.publish requested publication of 0") } + p.config.mutex.Lock() defer p.config.mutex.Unlock() + payload := makeSessionPayload(sessions, p.config) buf, err := json.Marshal(payload) if err != nil { return fmt.Errorf("bugsnag/sessions/publisher.publish unable to marshal json: %v", err) } + req, err := http.NewRequest("POST", p.config.Endpoint, bytes.NewBuffer(buf)) if err != nil { return fmt.Errorf("bugsnag/sessions/publisher.publish unable to create request: %v", err) } + for k, v := range headers.PrefixedHeaders(p.config.APIKey, sessionPayloadVersion) { req.Header.Add(k, v) } + res, err := p.client.Do(req) if err != nil { return fmt.Errorf("bugsnag/sessions/publisher.publish unable to deliver session: %v", err) } + defer func(res *http.Response) { if err := res.Body.Close(); err != nil { p.config.logf("%v", err) } }(res) + if res.StatusCode != 202 { return fmt.Errorf("bugsnag/session.publish expected 202 response status, got HTTP %s", res.Status) } From 1b144de921466833b4672bfe96ea898a6bea15d2 Mon Sep 17 00:00:00 2001 From: Daria Bialobrzeska Date: Mon, 5 Aug 2024 12:56:02 +0200 Subject: [PATCH 5/5] Update version to 2.5.0 --- CHANGELOG.md | 2 +- v2/bugsnag.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 409bb4d3..d1963617 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## TBD +## 2.5.0 (2024-08-27) ### Enhancements diff --git a/v2/bugsnag.go b/v2/bugsnag.go index 631d2e93..66a58954 100644 --- a/v2/bugsnag.go +++ b/v2/bugsnag.go @@ -21,7 +21,7 @@ import ( ) // Version defines the version of this Bugsnag notifier -const Version = "2.4.0" +const Version = "2.5.0" var panicHandlerOnce sync.Once var sessionTrackerOnce sync.Once