Skip to content

Commit 02400e1

Browse files
Xenomegaanishnaik
andauthored
Added a unique exit code for failed tests, added generic way to bubble up errors with exit codes from cmd, organized exit code definitions into one package (crytic#301)
Co-authored-by: anishnaik <[email protected]>
1 parent c4eefc3 commit 02400e1

File tree

4 files changed

+67
-3
lines changed

4 files changed

+67
-3
lines changed

cmd/exitcodes/error_with_exit_code.go

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package exitcodes
2+
3+
// ErrorWithExitCode is an `error` type that wraps an existing error and exit code, providing exit codes
4+
// for a given error if they are bubbled up to the top-level.
5+
type ErrorWithExitCode struct {
6+
err error
7+
exitCode int
8+
}
9+
10+
// NewErrorWithExitCode creates a new error (ErrorWithExitCode) with the provided internal error and exit code.
11+
func NewErrorWithExitCode(err error, exitCode int) *ErrorWithExitCode {
12+
return &ErrorWithExitCode{
13+
err: err,
14+
exitCode: exitCode,
15+
}
16+
}
17+
18+
// Error returns the error message string, implementing the `error` interface.
19+
func (e *ErrorWithExitCode) Error() string {
20+
return e.err.Error()
21+
}
22+
23+
// GetErrorExitCode checks the given exit code that the application should exit with, if this error is bubbled to
24+
// the top-level. This will be 0 for a nil error, 1 for a generic error, or arbitrary if the error is of type
25+
// ErrorWithExitCode.
26+
// Returns the exit code associated with the error.
27+
func GetErrorExitCode(err error) int {
28+
// If we have no error, return 0, if we have a generic error, return 1, if we have a custom error code, unwrap
29+
// and return it.
30+
if err == nil {
31+
return ExitCodeSuccess
32+
} else if unwrappedErr, ok := err.(*ErrorWithExitCode); ok {
33+
return unwrappedErr.exitCode
34+
} else {
35+
return ExitCodeGeneralError
36+
}
37+
}

cmd/exitcodes/exit_codes.go

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package exitcodes
2+
3+
const (
4+
// ================================
5+
// Platform-universal exit codes
6+
// ================================
7+
8+
// ExitCodeSuccess indicates no errors or failures had occurred.
9+
ExitCodeSuccess = 0
10+
11+
// ExitCodeGeneralError indicates some type of general error occurred.
12+
ExitCodeGeneralError = 1
13+
14+
// ================================
15+
// Application-specific exit codes
16+
// ================================
17+
// Note: Despite not being standardized, exit codes 2-5 are often used for common use cases, so we avoid them.
18+
19+
// ExitCodeTestFailed indicates a test case had failed.
20+
ExitCodeTestFailed = 7
21+
)

cmd/fuzz.go

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cmd
22

33
import (
44
"fmt"
5+
"github.com/crytic/medusa/cmd/exitcodes"
56
"github.com/crytic/medusa/logging/colors"
67
"os"
78
"os/signal"
@@ -160,5 +161,10 @@ func cmdRunFuzz(cmd *cobra.Command, args []string) error {
160161
// Start the fuzzing process with our cancellable context.
161162
err = fuzzer.Start()
162163

164+
// If we have no error and failed test cases, we'll want to return a special exit code
165+
if err == nil && len(fuzzer.TestCasesWithStatus(fuzzing.TestCaseStatusFailed)) > 0 {
166+
return exitcodes.NewErrorWithExitCode(err, exitcodes.ExitCodeTestFailed)
167+
}
168+
163169
return err
164170
}

main.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ package main
22

33
import (
44
"github.com/crytic/medusa/cmd"
5+
"github.com/crytic/medusa/cmd/exitcodes"
56
"os"
67
)
78

89
func main() {
910
// Run our root CLI command, which contains all underlying command logic and will handle parsing/invocation.
1011
err := cmd.Execute()
1112

12-
if err != nil {
13-
os.Exit(1)
14-
}
13+
// Determine the exit code from any potential error and exit out.
14+
os.Exit(exitcodes.GetErrorExitCode(err))
1515
}

0 commit comments

Comments
 (0)