Skip to content

Commit

Permalink
Add an errext package with error types, helpers and exitcodes
Browse files Browse the repository at this point in the history
  • Loading branch information
na-- committed Jun 8, 2021
1 parent b9a29eb commit d40069c
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 0 deletions.
77 changes: 77 additions & 0 deletions errext/errext_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
*
* k6 - a next-generation load testing tool
* Copyright (C) 2021 Load Impact
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package errext

import (
"errors"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func assertHasHint(t *testing.T, err error, hint string) {
var typederr HasHint
require.ErrorAs(t, err, &typederr)
assert.Equal(t, typederr.Hint(), hint)
assert.Contains(t, err.Error(), typederr.Error())
}

func assertHasExitCode(t *testing.T, err error, exitcode ExitCode) {
var typederr HasExitCode
require.ErrorAs(t, err, &typederr)
assert.Equal(t, typederr.ExitCode(), exitcode)
assert.Contains(t, err.Error(), typederr.Error())
}

func TestErrextHelpers(t *testing.T) {
t.Parallel()

const testExitCode ExitCode = 13
assert.Nil(t, WithHint(nil, "test hint"))
assert.Nil(t, WithExitCode(nil, testExitCode))

errBase := errors.New("base error")
errBaseWithHint := WithHint(errBase, "test hint")
assertHasHint(t, errBaseWithHint, "test hint")
errBaseWithTwoHints := WithHint(errBaseWithHint, "better hint")
assertHasHint(t, errBaseWithTwoHints, "better hint (test hint)")

errWrapperWithHints := fmt.Errorf("wrapper error: %w", errBaseWithTwoHints)
assertHasHint(t, errWrapperWithHints, "better hint (test hint)")

errWithExitCode := WithExitCode(errWrapperWithHints, testExitCode)
assertHasHint(t, errWithExitCode, "better hint (test hint)")
assertHasExitCode(t, errWithExitCode, testExitCode)

errWithExitCodeAgain := WithExitCode(errWithExitCode, ExitCode(27))
assertHasHint(t, errWithExitCodeAgain, "better hint (test hint)")
assertHasExitCode(t, errWithExitCodeAgain, testExitCode)

errBaseWithThreeHints := WithHint(errWithExitCodeAgain, "best hint")
assertHasHint(t, errBaseWithThreeHints, "best hint (better hint (test hint))")

finalErrorMess := fmt.Errorf("woot: %w", errBaseWithThreeHints)
assert.Equal(t, finalErrorMess.Error(), "woot: wrapper error: base error")
assertHasHint(t, finalErrorMess, "best hint (better hint (test hint))")
assertHasExitCode(t, finalErrorMess, testExitCode)
}
29 changes: 29 additions & 0 deletions errext/exception.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
*
* k6 - a next-generation load testing tool
* Copyright (C) 2021 Load Impact
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

// Package errext contains extensions for normal Go errors that are used in k6.
package errext

// Exception represents errors that resulted from a script exception and contain
// a stack trace that lead to them.
type Exception interface {
error
StackTrace() string
}
66 changes: 66 additions & 0 deletions errext/exit_code.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
*
* k6 - a next-generation load testing tool
* Copyright (C) 2021 Load Impact
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package errext

import "errors"

// ExitCode is the code with which the application should exit if this error
// bubbles up to the top of the scope. Values should be between 0 and 125:
// https://unix.stackexchange.com/questions/418784/what-is-the-min-and-max-values-of-exit-codes-in-linux
type ExitCode uint8

// HasExitCode is a wrapper around an error with an attached exit code.
type HasExitCode interface {
error
ExitCode() ExitCode
}

// WithExitCode can attach an exit code to the given error, if it doesn't have
// one already. It won't do anything if the error already had an exit code
// attached. Similarly, if there is no error (i.e. the given error is nil), it
// also won't do anything.
func WithExitCode(err error, exitCode ExitCode) error {
if err == nil {
// No error, do nothing
return nil
}
var ecerr HasExitCode
if errors.As(err, &ecerr) {
// The given error already has an exit code, do nothing
return err
}
return withExitCode{err, exitCode}
}

type withExitCode struct {
error
exitCode ExitCode
}

func (wh withExitCode) Unwrap() error {
return wh.error
}

func (wh withExitCode) ExitCode() ExitCode {
return wh.exitCode
}

var _ HasExitCode = withExitCode{}
39 changes: 39 additions & 0 deletions errext/exitcodes/codes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
*
* k6 - a next-generation load testing tool
* Copyright (C) 2021 Load Impact
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

// Package exitcodes contains the constants representing possible k6 exit error codes.
//nolint: golint
package exitcodes

import "go.k6.io/k6/errext"

const (
CloudTestRunFailed errext.ExitCode = 97 // This used to be 99 before k6 v0.33.0
CloudFailedToGetProgress errext.ExitCode = 98
ThresholdsHaveFailed errext.ExitCode = 99
SetupTimeout errext.ExitCode = 100
TeardownTimeout errext.ExitCode = 101
GenericTimeout errext.ExitCode = 102 // TODO: remove?
GenericEngine errext.ExitCode = 103
InvalidConfig errext.ExitCode = 104
ExternalAbort errext.ExitCode = 105
CannotStartRESTAPI errext.ExitCode = 106
ScriptException errext.ExitCode = 107
)
63 changes: 63 additions & 0 deletions errext/hint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
*
* k6 - a next-generation load testing tool
* Copyright (C) 2021 Load Impact
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package errext

import "errors"

// HasHint is a wrapper around an error with an attached user hint. These hints
// can be used to give extra human-readable information about the error,
// including suggestions on how the error can be fixed.
type HasHint interface {
error
Hint() string
}

// WithHint is a helper that can attach a hint to the given error. If there is
// no error (i.e. the given error is nil), it won't do anything. If the given
// error already had a hint, this helper will wrap it so that the new hint is
// "new hint (old hint)".
func WithHint(err error, hint string) error {
if err == nil {
// No error, do nothing
return nil
}
var oldhint HasHint
if errors.As(err, &oldhint) {
// The given error already had a hint, wrap it
hint = hint + " (" + oldhint.Hint() + ")"
}
return withHint{err, hint}
}

type withHint struct {
error
hint string
}

func (wh withHint) Unwrap() error {
return wh.error
}

func (wh withHint) Hint() string {
return wh.hint
}

var _ HasHint = withHint{}

0 comments on commit d40069c

Please sign in to comment.