diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 0000000..73418e9 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1,2 @@ +repo_token: 4FZysTXA8OlRMafXynFGoFOjM5F9qQHDx + diff --git a/.gitattributes b/.gitattributes old mode 100644 new mode 100755 index 1f72540..827ff73 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,5 @@ -stylesheets/* linguist-vendored linguist-documentation=false -*.json linguist-vendored linguist-documentation=false -*.html linguist-vendored linguist-documentation=false -*.css linguist-vendored linguist-documentation=false -*.go linguist-language=Go +stylesheets/* linguist-vendored linguist-documentation=false +*.json linguist-vendored linguist-documentation=false +*.html linguist-vendored linguist-documentation=false +*.css linguist-vendored linguist-documentation=false +*.go linguist-language=Go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6de13d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,152 @@ +################################################################################################### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio + +*.iml + +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties + + +################################################################################################### +# MacOS + +**/.DS_Store +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +################################################################################################### +# Linux + +*~ + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + + +################################################################################################### +# Vi + +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist +*~ + + +################################################################################################### +# Go + +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so +*.dll +*.dylib + +# Folders +_obj +_test +bin/ +pkg/ + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.out +*.exe +*.test +*.prof + + +################################################################################################### +# Javascript + +static/bower_components/ +static/*.log + + +################################################################################################### +# ~~ Others ~~ + +\.tmp/ +data/*.db +config/drivers.yaml +HOWTO.txt +dist/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..f682250 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ +language: go +sudo: false +os: + - linux + - osx +go: + - "go1.7" + - "go1.8" + - "go1.9" + - "go1.10" +before_install: + - go get github.com/mattn/goveralls +script: + - "$GOPATH/bin/goveralls -service=travis-ci" diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 index e6e0817..3c96b6a --- a/README.md +++ b/README.md @@ -1,2 +1,211 @@ -# goerrors -_This project is still under development_ +# GoErrors - Easy error informations and stack traces for Go with rich informations and a Try/Catch/Finally mechanism. +[![Build Status](https://img.shields.io/travis/corebreaker/goerrors/master.svg?style=plastic)](https://travis-ci.org/corebreaker/goerrors) +[![Coverage Status](https://img.shields.io/coveralls/github/corebreaker/goerrors/master.svg?style=plastic)](https://coveralls.io/github/corebreaker/goerrors) +[![GoDoc](https://img.shields.io/badge/godoc-reference-5272B4.svg?style=plastic)](https://godoc.org/github.com/corebreaker/goerrors) +[![Release](https://img.shields.io/github/release/corebreaker/goerrors.svg?style=plastic)](https://github.com/corebreaker/goerrors/releases) + +It's a package that's allow you to make Go errors more comprehensive, more featured and easier to use. + + +## Features +- Verbose with stack trace +- Ready for Try/Catch/Finally paradigm +- Extensible with your own error +- Error hierarchy +- Entirely customizable + + +## Installation +go get github.com/corebreaker/goerrors + + +## How is it work ? +A normal error is just an interface. But here we added an extended interface IError which can transport other infomations. +Therefore to add informations, the standard error (`error` interface) is «decorated» with informations and so +we obtain a new error value. These informations are: +- stack trace informations (files + lines) +- additionnal customized string +- optionnal error code + +When the `Error` method is called, all these informations will be formated in a string. + +This type of error gives you rich informations on an error without using `panic` function (even if with a panic, you +could show your additionnal infomations). + + +## How to use it ? +### Decorate error to add a stack trace +```go +// for example, let's open a file +func OpenMyFile() (*os.File, error) { + file, err := os.Open("MyFile.txt") + if err != nil { + // Here, we decorate the error + return nil, goerrors.DecorateError(err) + } + + return file, nil +} +``` + +Then, we can `panic` this decorated error or simply print it, like this: +```go +func main() { + // First we must enable the debug mode to activate the stacktrace + goerrors.SetDebug(true) + + // Use a function that use the `goerrors` package + file, err := OpenMyFile() + + // Check the error + if err != nil { + // Show the error + fmt.Println(err) + + // Terminate + return + } + + // … +} +``` + +You will see some thing like this: +``` +github.com/corebreaker/goerrors.tStandardError: open MyFile.txt: no such file or directory + github.com/corebreaker/goerrors.(*GoError).Init (/home/frederic/go/src/github.com/corebreaker/goerrors/errors.go:219) + github.com/corebreaker/goerrors.DecorateError (/home/frederic/go/src/github.com/corebreaker/goerrors/standard.go:51) + main.OpenMyFile (/home/frederic/.local/share/data/liteide/liteide/goplay.go:13) + main.main (/home/frederic/.local/share/data/liteide/liteide/goplay.go:24) +------------------------------------------------------------------------------ +``` + +### A Try/Catch/Finally mechanism +Plus, this library uses the `panic()` function, the `recover()` function and the `defer` instruction, +as a Throw and a Try/Catch/Finally mechanisms and can be used like this: +```go +goerrors.Try(func(err goerrors.IError) error { + // Try block +}, func(err goerrors.IError) error { + // Catch block +}, func(err goerrors.IError) error { + // Finally block +}) +``` + + +## A simple example +```go +package main + +import ( + "fmt" + gerr "github.com/corebreaker/goerrors" +) + +// A function which return checked quotient +func my_func(i, j int) (int, error) { + if j == 0 { + return 0, gerr.MakeError("Division by zero") + } + + return i / j, nil +} + +// Main function +func main() { + // Activate stack trace + gerr.SetDebug(true) + + i, j := 1, 0 + + // Call checked function + q, err := my_func(i, j) + if err != nil { + fmt.Println(err) + + return + } + + // Here, in this example, this code won't never be executed + fmt.Print(i, "/", j, "=", q) +} +``` + +This will show: +``` +Division by zero + main.my_func (/projects/go/prototype/main.go:11) + main.main (/projects/go/prototype/main.go:23) +------------------------------------------------------------------------------ +``` + + +## Another example with existing error +```go +package main + +import ( + "fmt" + "os" + gerr "github.com/corebreaker/goerrors" +) + +// A function which open a file +func open_file(name string) (*os.File, error) { + f, err := os.Open(name) + + // Decorate the opening error + if err != nil { + return nil, gerr.DecorateError(err) + } + + return f, nil +} + +// A function which read one byte in the opened file +func read_file(f *os.File) (byte, error) { + var b [1]byte + + n, err := f.Read(b[:]) + + // Decorate the read error + if err != nil { + return 0, gerr.DecorateError(err) + } + + // Return custom error + if n == 0 { + return 0, gerr.MakeError("No data to read") + } + + return b[0], nil +} + +// Main function +func main() { + // Activate stack trace + gerr.SetDebug(true) + + // Call the checked open function + f, err := open_file("a_file.txt") + if err != nil { + fmt.Println(err) + + return + } + + // Here, in this example, this code won't never be executed if the file can't be opened + defer f.Close() + + _, err = read_file(f) +} +``` + +This will show: +``` +open a_file.txt: no such file or directory + main.open_file (/projects/go/src/github.com/corebreaker/goerrors.go:15) + main.main (/projects/go/src/github.com/corebreaker/goerrors.go:46) +------------------------------------------------------------------------------ +``` diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 5372399..0000000 --- a/_config.yml +++ /dev/null @@ -1,8 +0,0 @@ -title: Goerrors -description: Easy error informations and stack traces for Go. -google_analytics: -show_downloads: true -theme: jekyll-theme-cayman - -gems: - - jekyll-mentions diff --git a/doc.go b/doc.go old mode 100644 new mode 100755 index c91e893..d79dc1b --- a/doc.go +++ b/doc.go @@ -1,3 +1,2 @@ -// Easy error informations and stack traces. +// Easy error informations and stack traces for Go with rich informations and a Try/Catch/Finally mechanism. package goerrors - diff --git a/debug.go b/env.go old mode 100644 new mode 100755 similarity index 83% rename from debug.go rename to env.go index 2b3091b..40f2d5f --- a/debug.go +++ b/env.go @@ -1,16 +1,16 @@ package goerrors var ( - err_debug bool = false + errDebug bool = false ) // Return the Debug boolean flag which indicates that the stack trace will be provided in errors func GetDebug() bool { - return err_debug + return errDebug } // Modify the Debug boolean flag for enable or disable the stack trace in errors. // If the `debug` parameter is true, so the stack trace will be provided in errors. func SetDebug(debug bool) { - err_debug = debug + errDebug = debug } diff --git a/debug_test.go b/env_test.go old mode 100644 new mode 100755 similarity index 100% rename from debug_test.go rename to env_test.go diff --git a/errors.go b/errors.go old mode 100644 new mode 100755 index d898c4a..b93c03d --- a/errors.go +++ b/errors.go @@ -1,129 +1,288 @@ package goerrors import ( - "bytes" - "fmt" - "runtime" + "bytes" + "fmt" + "reflect" + "unsafe" +) + +var ( + // Inheritance cache + error_hierarchies = make(map[string][]string) ) // Interface for extended Go errors -type GoError interface { - // This is an error - error +type IError interface { + // This is an error + error + + // Get error name + GetName() string + + // Get original error + GetSource() error + + // Get error message + GetMessage() string + + // Get custon data + GetData() interface{} + + // Complete try/catch/finally block + Try(try, catch, finally ErrorHandler) error + + // Catch error (used as a defered call) + Catch(err *error, catch, finally ErrorHandler) + + // Raise error + Raise() + + // Test if this error is one of parents of error `err` passed in parameter + IsParentOf(err error) bool - // Add more informations on that error - AddInfo(info string, args ...interface{}) GoError + // Get the real reference on this error + get_reference() IError + + // This method construct the stack trace only in 'Debug' Mode + populateStackTrace(prune_levels uint) + + // Get type of this error + get_parents() []string + + // Raise error with pruned levels + raise(prune_levels uint) } -// Internal error structure -type tError struct { - source error // Cause or original error - infos bytes.Buffer // Additionnal informations - trace []string // Stack trace +// Handler for executing Try, Catch, or Finally block +type ErrorHandler func(err IError) error + +// Basic error structure +type GoError struct { + source error // Cause or original error + message string // Error message + trace []string // Stack trace + data interface{} // Custom data + err_type reflect.Type // Type of this error } // Standard method of `error` interface -// @see error.Error -func (self *tError) Error() string { - var out bytes.Buffer - - // Prints error informations - fmt.Fprintln(&out, self.source) - fmt.Fprint(&out, &self.infos) - - // Prints stack trace - for _, line := range self.trace { - fmt.Fprintln(&out, " ", line) - } - - // Prints a separator if stack trace is not empty - if len(self.trace) > 0 { - fmt.Fprintln(&out, "------------------------------------------------------------------------------") - } - - // Return content of the buffer resulting from printing theses informations - return out.String() -} - -// Add informations in extended Go error -func (self *tError) AddInfo(info string, args ...interface{}) GoError { - // Just prints into internal buffer the informations passed as parameters - fmt.Fprintln(&self.infos, fmt.Sprintf(info, args...)) - - return self -} - -// Error decorator -// Constructs an extended Go error from an existing error -func decorate_error(err error, prune_levels int) GoError { - // If error is nil, therefore returns nil - if err == nil { - return nil - } - - // Checks that pruned levels passed in parameted is really a positive value - // A negative value means, no pruning - if prune_levels < 0 { - prune_levels = 0 - } - - // Program Counter initialisation - var pc uintptr = 1 - - // Resulting stack trace - stack := make([]string, 0) - - // If we are in debugging mode, - if err_debug { - // Populate the stack trace - for i := prune_levels + 2; pc != 0; i++ { - // Retreive runtime informations - ptr, file, line, ok := runtime.Caller(i) - pc = ptr - - // If there isn't significant information, go to next level - if (pc == 0) || (!ok) { - continue - } - - // Retreive called function - f := runtime.FuncForPC(pc) - - // Add stack trace entry - stack = append(stack, fmt.Sprintf("%s (%s:%d)", f.Name(), file, line)) - } - } - - // Finalize by constructing the resulting extended Go error - return &tError{ - source: err, - trace: stack, - } -} - -// «Decorate» the error passed as "err" parameter. -// The error returned will be an extended Go error with additionnal informations and stack trace. -func DecorateError(err error) GoError { - return decorate_error(err, -1) -} - -// Make an extended Go error from a message passed as "message" parameter -func MakeError(message string, args ...interface{}) GoError { - // make a standard error with fmt.Errorf, then decorate it with pruning one level in stack trace - // (to eliminate this function calling) - return decorate_error(fmt.Errorf(message, args...), -1) -} - -// Global function to add information in an error whatever. -// This function just call the "AddInfo" method of an extended Go error. -func AddInfo(err error, info string, args ...interface{}) GoError { - // Check if "err" is already an extended Go error - go_err, ok := err.(GoError) - if !ok { - // Otherwise decorate that unknown error - go_err = DecorateError(err) - } - - // Delegate call to "AddInfo" method - return go_err.AddInfo(info, args...) +func (self *GoError) Error() string { + var out bytes.Buffer + + err := self.get_reference() + + // Prints error name + fmt.Fprintf(&out, "%s: ", err.GetName()) + + // Get informations + message := err.GetMessage() + source := err.GetSource() + data := err.GetData() + + // Prints error informations + if message != "" { + fmt.Fprintln(&out, message) + + if data != nil { + fmt.Fprintln(&out, data) + } + + if source != nil { + fmt.Fprintln(&out) + fmt.Fprintln(&out, "Source:", source) + } + } else { + if source != nil { + fmt.Fprintln(&out, source) + } + + if data != nil { + fmt.Fprintln(&out, data) + } + } + + // Prints stack trace only in debug mode + if errDebug { + for _, entry := range self.trace { + fmt.Fprintln(&out, " ", entry) + } + + // Prints a separator if stack trace is not empty + if len(self.trace) > 0 { + fmt.Fprintln(&out, "------------------------------------------------------------------------------") + } + } + + // Return content of the buffer resulting from printing theses informations + return out.String() +} + +// Get error name +func (self *GoError) GetName() string { + return self.err_type.PkgPath() + "." + self.err_type.Name() +} + +// Get cause error (parent error) +func (self *GoError) GetSource() error { + return self.source +} + +// Get error message +func (self *GoError) GetMessage() string { + return self.message +} + +// Get custon data +func (self *GoError) GetData() interface{} { + return self.data +} + +// Complete try/catch/finally block +func (self *GoError) Try(try, catch, finally ErrorHandler) (err error) { + defer self.Catch(&err, catch, finally) + + return try(self.get_reference()) +} + +// Catch error (used as a defered call) +func (self *GoError) Catch(err *error, catch, finally ErrorHandler) { + var res_err error = nil + + defer func() { + if finally != nil { + ierr, _ := res_err.(IError) + + res_err = finally(ierr) + } + + if err != nil { + *err = res_err + } + }() + + recovered := recover() + if recovered == nil { + return + } + + var ok bool + + if res_err, ok = recovered.(error); !ok { + panic(recovered) + } + + if this := self.get_reference(); !this.IsParentOf(res_err) { + if err == nil { + panic(recovered) + } + + return + } + + if catch != nil { + res_err = catch(res_err.(IError)) + } +} + +// Raise error +func (self *GoError) Raise() { + self.raise(1) +} + +// Test if this error is one of parents of error `err` passed in parameter +func (self *GoError) IsParentOf(err error) bool { + gerr, ok := err.(IError) + if !ok { + return false + } + + name := self.GetName() + + for _, parent := range gerr.get_parents() { + if parent == name { + return true + } + } + + return false +} + +func (self *GoError) Init(value interface{}, message string, data interface{}, source error, prune_levels uint) IError { + if self.err_type == nil { + self.set_type(value) + + self.message = message + self.data = data + self.source = source + + self.populateStackTrace(prune_levels + 1) + } + + return self +} + +func (self *GoError) raise(prune_levels uint) { + if prune_levels < 0 { + prune_levels = 0 + } + + res := self.get_reference() + res.populateStackTrace(prune_levels + 1) + + panic(res) +} + +func (self *GoError) set_type(value interface{}) { + err_type := reflect.ValueOf(value).Type() + if err_type.Kind() == reflect.Ptr { + err_type = err_type.Elem() + } + + self.err_type = err_type +} + +// Get the real reference on this error +func (self *GoError) get_reference() IError { + if self.err_type == nil { + self.set_type(self) + } + + ptr := unsafe.Pointer(reflect.ValueOf(self).Pointer()) + + return reflect.NewAt(self.err_type, ptr).Interface().(IError) +} + +// This method construct the stack trace only in 'Debug' Mode +func (self *GoError) populateStackTrace(prune_levels uint) { + // If we aren't in debugging mode, + if !errDebug { + // Do nothing + return + } + + self.trace = getTrace(prune_levels + 1) +} + +// Get type of this error +func (self *GoError) get_parents() []string { + name := self.GetName() + res, ok := error_hierarchies[name] + + if !ok { + res = _getTypeHierarchy(self.err_type, reflect.TypeOf(self).Elem()) + error_hierarchies[name] = res + } + + return res +} + +// GetSource +func GetSource(err error) error { + ierr, ok := err.(IError) + if !ok { + return nil + } + + return ierr.GetSource() } diff --git a/errors_test.go b/errors_test.go old mode 100644 new mode 100755 index 5cc9a87..56e98fa --- a/errors_test.go +++ b/errors_test.go @@ -1,82 +1,103 @@ -package goerrors - -import ( - "fmt" - "math/rand" - "testing" - "time" - - "github.com/go-xweb/uuid" -) - -func TestDecorateError(t *testing.T) { - if DecorateError(nil) != nil { - t.Fail() - } -} - -func TestAddInfo(t *testing.T) { - err := fmt.Errorf("") - - moment := time.Now() - rand.Seed(moment.UnixNano()) - - r := rand.Int63() - id := uuid.NewUUID() - - err = AddInfo(err, "%s:%d", id, r) - - raw_err, ok := err.(*tError) - if !ok { - t.Error("Failed on convesion") - } - - if raw_err.infos.Len() == 0 { - t.Error("Failed on has-infos") - } - - if raw_err.infos.String() != fmt.Sprintf("%s:%d\n", id, r) { - t.Error("Failed on set-infos:", raw_err.infos.String(), "!=", fmt.Sprintf("%s:%d\n", id, r)) - } -} - -func TestMakeErrorNoDebug(t *testing.T) { - SetDebug(false) - - moment := time.Now() - rand.Seed(moment.UnixNano()) - - r := rand.Int63() - id := uuid.NewUUID() - - err := MakeError("%s:%d", id, r).(*tError) - if err.source.Error() != fmt.Sprintf("%s:%d", id, r) { - t.Error("Failed on MakeError:", err.source.Error(), "!=", fmt.Sprintf("%s:%d", id, r)) - } - - if len(err.trace) != 0 { - t.Error("Failed on empty trace") - } - - if err.infos.Len() != 0 { - t.Error("Failed on no-infos") - } -} - -func TestMakeErrorDebug(t *testing.T) { - SetDebug(true) - - err := MakeError("").(*tError) - if len(err.trace) == 0 { - t.Error("Failed on stack trace") - } -} - -func TestError(t *testing.T) { - SetDebug(true) - - err := MakeError("") - if len(err.Error()) == 0 { - t.Error("Failed on infos") - } -} +package goerrors + +import ( + "fmt" + "math/rand" + "reflect" + "testing" + "time" + + "github.com/go-xweb/uuid" +) + +type MyError struct { + GoError +} + +var ( + __err_test MyError + __err_type = reflect.TypeOf(__err_test) +) + +func same_ptr(v1, v2 interface{}) bool { + return reflect.ValueOf(v1).Pointer() == reflect.ValueOf(v2).Pointer() +} + +func TestSetType(t *testing.T) { + gerr := new(MyError) + gerr.set_type(gerr) + + if gerr.err_type != __err_type { + t.Fail() + } +} + +func TestInitError(t *testing.T) { + src := fmt.Errorf("") + data := new(int) + + gerr := new(MyError) + gerr.Init(gerr, "--message--", data, src, 0) + + if gerr.err_type != __err_type { + t.Error("Bad type") + } + + if gerr.message != "--message--" { + t.Error("Bad message") + } + + if !same_ptr(gerr.data, data) { + t.Error("Bad data") + } + + if !same_ptr(gerr.source, src) { + t.Error("Bad data") + } +} + +func TestShowErrorNoDebug(t *testing.T) { + SetDebug(false) + + moment := time.Now() + rand.Seed(moment.UnixNano()) + + r := rand.Int63() + id := uuid.NewUUID() + + err := MakeError("%s:%d", id, r).(*tStandardError) + if err.message != fmt.Sprintf("%s:%d", id, r) { + t.Error("Failed on MakeError:", err.message, "!=", fmt.Sprintf("%s:%d", id, r)) + } + + if len(err.trace) != 0 { + t.Error("Failed on empty trace: ", err.trace) + } + + if err.infos.Len() != 0 { + t.Error("Failed on no-infos") + } +} + +func TestShowErrorDebug(t *testing.T) { + SetDebug(true) + + err := MakeError("").(*tStandardError) + if len(err.trace) == 0 { + t.Error("Failed on stack trace") + } +} + +func TestErrorMethods(t *testing.T) { + SetDebug(true) + + err := MakeError("") + if len(err.Error()) == 0 { + t.Error("Failed on infos") + } +} + +func TestGetSource(t *testing.T) { + GetSource(nil) + GetSource(DecorateError(fmt.Errorf("Error"))) +} diff --git a/example_test.go b/example_test.go old mode 100644 new mode 100755 index b7ccb2a..29f8c18 --- a/example_test.go +++ b/example_test.go @@ -1,59 +1,67 @@ -package goerrors_test +package goerrors import ( - "fmt" - "os" - - "github.com/corebreaker/goerrors" + "fmt" + "os" ) func Example() { - // A function which open a file - open_file := func(name string) (*os.File, error) { - f, err := os.Open(name) + // A function which open a file + open_file := func(name string) (*os.File, error) { + f, err := os.Open(name) + + // Decorate the opening error + if err != nil { + return nil, DecorateError(err) + } + + return f, nil + } - // Decorate the opening error - if err != nil { - return nil, goerrors.DecorateError(err) - } + // A function which read one byte in the opened file + read_file := func(f *os.File) (byte, error) { + var b [1]byte - return f, nil - } + n, err := f.Read(b[:]) - // A function which read one byte in the opened file - read_file := func(f *os.File) (byte, error) { - var b [1]byte + // Decorate the read error + if err != nil { + return 0, DecorateError(err) + } - n, err := f.Read(b[:]) + // Return custom error + if n == 0 { + return 0, MakeError("No data to read") + } - // Decorate the read error - if err != nil { - return 0, goerrors.DecorateError(err) - } + return b[0], nil + } - // Return custom error - if n == 0 { - return 0, goerrors.MakeError("No data to read") - } + // Deactivate stack trace + // (cause stacktrace produced for testing package is specific to go installation and may change with Go version) + SetDebug(false) - return b[0], nil - } + // Make an unfindable filename + const name = ".a_file_5123351069599224559.txt" - // Activate stack trace - goerrors.SetDebug(true) + // Call the checked open function + f, err := open_file(name) + if err != nil { + fmt.Println(err) - // Call the checked open function - f, err := open_file("a_file.txt") - if err != nil { - fmt.Fprintln(os.Stderr, err) + return + } - return - } + // Here, in this example, this code won't never be executed if the file can't be opened + defer f.Close() - // Here, in this example, this code won't never be executed if the file can't be opened - defer f.Close() + _, err = read_file(f) + if err != nil { + fmt.Println(err) - _, err = read_file(f) + return + } - // Output: + // Output: + // github.com/corebreaker/goerrors.tStandardError: open .a_file_5123351069599224559.txt: no such file or directory } diff --git a/filler.go b/filler.go deleted file mode 100644 index 3fcb5ca..0000000 --- a/filler.go +++ /dev/null @@ -1,194 +0,0 @@ -package goerrors - -// Filler for changing language stats on Github -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ -// ___________________________________________________________________________ diff --git a/globals.go b/globals.go new file mode 100755 index 0000000..1de37e4 --- /dev/null +++ b/globals.go @@ -0,0 +1,48 @@ +package goerrors + +import "log" + +var ( + uncatchedErrorHandler ErrorHandler = func(err IError) error { + if err != nil { + log.Fatal(err) + } + + return nil + } +) + +type MainHandler func() error + +func CheckedMain(handler MainHandler) { + defer func() { + var sent_err error = nil + + err := new(GoError) + err.Init(err, "", nil, nil, 1) + + err.Catch(&sent_err, uncatchedErrorHandler, nil) + + if sent_err != nil { + log.Fatal(sent_err) + } + }() + + err := handler() + + if err != nil { + log.Fatal(err) + } +} + +func SetUncatchedErrorHandler(handler ErrorHandler) ErrorHandler { + old_handler := uncatchedErrorHandler + + uncatchedErrorHandler = handler + + return old_handler +} + +func DiscardPanic() { + recover() +} diff --git a/globals_test.go b/globals_test.go new file mode 100755 index 0000000..14b37a0 --- /dev/null +++ b/globals_test.go @@ -0,0 +1,13 @@ +package goerrors + +import "testing" + +func TestSetUncatchedErrorHandler(t *testing.T) { + SetUncatchedErrorHandler(func(err IError) error { return err }) +} + +func TestCheckedMain(t *testing.T) { + CheckedMain(func() error { + return nil + }) +} diff --git a/index.md b/index.md deleted file mode 100644 index 3db62ac..0000000 --- a/index.md +++ /dev/null @@ -1,14 +0,0 @@ -### Welcome to GitHub Pages. -This automatic page generator is the easiest way to create beautiful pages for all of your projects. Author your page content here [using GitHub Flavored Markdown](https://guides.github.com/features/mastering-markdown/), select a template crafted by a designer, and publish. After your page is generated, you can check out the new `gh-pages` branch locally. If you’re using GitHub Desktop, simply sync your repository and you’ll see the new branch. - -### Designer Templates -We’ve crafted some handsome templates for you to use. Go ahead and click 'Continue to layouts' to browse through them. You can easily go back to edit your page before publishing. After publishing your page, you can revisit the page generator and switch to another theme. Your Page content will be preserved. - -### Creating pages manually -If you prefer to not use the automatic generator, push a branch named `gh-pages` to your repository to create a page manually. In addition to supporting regular HTML content, GitHub Pages support Jekyll, a simple, blog aware static site generator. Jekyll makes it easy to create site-wide headers and footers without having to copy them across every page. It also offers intelligent blog support and other advanced templating features. - -### Authors and Contributors -You can @mention a GitHub username to generate a link to their profile. The resulting `` element will link to the contributor’s GitHub Profile. For example: In 2007, Chris Wanstrath (@defunkt), PJ Hyett (@pjhyett), and Tom Preston-Werner (@mojombo) founded GitHub. - -### Support or Contact -Having trouble with Pages? Check out our [documentation](https://help.github.com/pages) or [contact support](https://github.com/contact) and we’ll help you sort it out. diff --git a/stacktrace_go1.8.go b/stacktrace_go1.8.go new file mode 100644 index 0000000..c40ac9c --- /dev/null +++ b/stacktrace_go1.8.go @@ -0,0 +1,44 @@ +// +build !go1.9 + +package goerrors + +import ( + "fmt" + "runtime" + "strings" +) + +// Construct formated stack trace. +func getTrace(start uint) []string { + // Program Counter initialisation. + var pc uintptr = 1 + + // The resulting stack trace. + var trace []string + + // Populates the stack trace. + for i := 1 + start; pc != 0; i++ { + // Retreives runtime informations. + ptr, file, line, ok := runtime.Caller(int(i)) + pc = ptr + + // If there isn't significant information, go to next level. + if (pc == 0) || (!ok) { + continue + } + + // Retreives the called function. + f := runtime.FuncForPC(pc) + + // If the file is from `runtime` package, so go to the next frame. + if strings.Contains(file, "runtime/") { + continue + } + + // Adds the stack trace entry. + trace = append(trace, fmt.Sprintf("%s (%s:%d)", f.Name(), file, line)) + } + + // Returns stack trace. + return trace +} diff --git a/stacktrace_go1.9.go b/stacktrace_go1.9.go new file mode 100644 index 0000000..7bf899b --- /dev/null +++ b/stacktrace_go1.9.go @@ -0,0 +1,50 @@ +// +build go1.9 + +package goerrors + +import ( + "fmt" + "runtime" + "strings" +) + +// This version of stack trace asks to have a limit which arbitrary set. +const STACKTRACE_MAXLEN = 65536 + +// Construct formated stack trace. +func getTrace(start uint) []string { + // The resulting stack trace. + var trace []string + + // The caller list. + callers := make([]uintptr, STACKTRACE_MAXLEN) + + // Gets the caller list, and returns an empty stack trace if there is no caller. + n := runtime.Callers(int(start+1), callers) + if n == 0 { + return trace + } + + // Get frames from callers. + frames := runtime.CallersFrames(callers[:n]) + + // Populates the stack trace. + for has_more := true; has_more; { + // A stack frame. + var frame runtime.Frame + + // Gets the next frame/ + frame, has_more = frames.Next() + + // If the file is from `runtime` package, so go to the next frame. + if strings.Contains(frame.File, "runtime/") { + continue + } + + // Adds the stack trace entry. + trace = append(trace, fmt.Sprintf("%s (%s:%d)", frame.Function, frame.File, frame.Line)) + } + + // Returns stack trace. + return trace +} diff --git a/standard.go b/standard.go new file mode 100755 index 0000000..3dd67f6 --- /dev/null +++ b/standard.go @@ -0,0 +1,208 @@ +package goerrors + +import ( + "bytes" + "fmt" +) + +// Interface for a standard error which decorate another basic go error (`error` go interface) +// with an error code, message and other additionnal informations +type IStandardError interface { + // Base interface + IError + + // Add more informations on that error + AddInfo(info string, args ...interface{}) IStandardError + + // Get error code + GetCode() int64 +} + +// Internal structure type for the standard error +type tStandardError struct { + GoError + + code int64 // Error code + infos bytes.Buffer // Additionnal informations +} + +// Add informations in standard error +func (self *tStandardError) AddInfo(info string, args ...interface{}) IStandardError { + // Just prints into internal buffer the informations passed as parameters + fmt.Fprintln(&self.infos, fmt.Sprintf(info, args...)) + + return self +} + +func (self *tStandardError) GetCode() int64 { + return self.code +} + +// «Decorate» the error passed as "err" parameter. +// The error returned will be an standard error with additionnal informations and stack trace. +func DecorateError(err error) IStandardError { + if err == nil { + return nil + } + + ierr, ok := err.(IStandardError) + if !ok { + res := new(tStandardError) + res.Init(res, "", nil, err, 1) + + ierr = res + } + + return ierr +} + +// Like `DecorateError` with error code and custom data +func DecorateErrorWithDatas(err error, code int64, data interface{}, message string, args ...interface{}) IStandardError { + if err == nil { + return nil + } + + ierr, ok := err.(IStandardError) + if ok { + ierr.AddInfo("Recorate for code=%d and message=%s", code, fmt.Sprintf(message, args...)) + } else { + res := &tStandardError{code: code} + res.Init(res, fmt.Sprintf(message, args...), data, err, 1) + + ierr = res + } + + return ierr +} + +// Make an standard error from a message passed as "message" parameter +func MakeError(message string, args ...interface{}) IStandardError { + res := new(tStandardError) + res.Init(res, fmt.Sprintf(message, args...), nil, nil, 1) + + return res +} + +// Like `MakeError` with error code and custom data +func MakeErrorWithDatas(code int64, data interface{}, message string, args ...interface{}) IStandardError { + res := &tStandardError{code: code} + res.Init(res, fmt.Sprintf(message, args...), data, nil, 1) + + return res +} + +// Global function to add information in an error whatever. +// This function just call the "AddInfo" method of an standard error. +func AddInfo(err error, info string, args ...interface{}) IStandardError { + // Check if "err" is already an standard error + go_err, ok := err.(IStandardError) + if !ok { + // Otherwise decorate that unknown error + go_err = DecorateError(err) + } + + // Delegate call to "AddInfo" method + return go_err.AddInfo(info, args...) +} + +func Catch(err *error, catch, finally ErrorHandler) { + var res_err error = nil + + defer func() { + if finally != nil { + ierr, _ := res_err.(IError) + + res_err = finally(ierr) + } + + if err != nil { + *err = res_err + } + }() + + recovered := recover() + if recovered == nil { + return + } + + var ok bool + + res_err, ok = recovered.(error) + if !ok { + panic(recovered) + } + + if catch == nil { + return + } + + ierr, ok := res_err.(IError) + if !ok { + ierr = DecorateError(res_err) + } + + cerr := catch(ierr) + if cerr != nil { + res_err = cerr + } +} + +func Try(try, catch, finally ErrorHandler) (err error) { + defer func() { + if finally == nil { + return + } + + ierr, _ := err.(IError) + + ferr := finally(ierr) + if ferr != nil { + err = ferr + } + }() + + defer func() { + recovered := recover() + if ((recovered == nil) || (catch == nil)) && (err == nil) { + return + } + + if err == nil { + recovered_error, ok := recovered.(error) + if ok { + err = recovered_error + } else { + err = fmt.Errorf("Error: %s", recovered) + } + } + + ierr, ok := err.(IError) + if !ok { + ierr = DecorateError(err) + } + + cerr := catch(ierr) + if cerr != nil { + err = cerr + } + }() + + return try(nil) +} + +func Raise(message string, args ...interface{}) { + MakeError(message, args...).raise(1) +} + +func RaiseWithInfos(error_code int64, data interface{}, message string, args ...interface{}) { + MakeErrorWithDatas(error_code, data, message, args...).raise(1) +} + +func RaiseError(err error) { + gerr, ok := err.(IError) + if !ok { + gerr = DecorateError(err) + } + + gerr.raise(1) +} diff --git a/standard_test.go b/standard_test.go new file mode 100755 index 0000000..bdc1673 --- /dev/null +++ b/standard_test.go @@ -0,0 +1,173 @@ +package goerrors + +import ( + "fmt" + "math/rand" + "testing" + "time" + + "github.com/go-xweb/uuid" +) + +func TestDecorateError(t *testing.T) { + if DecorateError(nil) != nil { + t.Fail() + } +} + +func TestAddInfo(t *testing.T) { + err := fmt.Errorf("") + + moment := time.Now() + rand.Seed(moment.UnixNano()) + + r := rand.Int63() + id := uuid.NewUUID() + + err = AddInfo(err, "%s:%d", id, r) + + raw_err, ok := err.(*tStandardError) + if !ok { + t.Error("Failed on convesion") + } + + if raw_err.infos.Len() == 0 { + t.Error("Failed on has-infos") + } + + if raw_err.infos.String() != fmt.Sprintf("%s:%d\n", id, r) { + t.Error("Failed on set-infos:", raw_err.infos.String(), "!=", fmt.Sprintf("%s:%d\n", id, r)) + } +} + +func TestMakeErrorNoDebug(t *testing.T) { + SetDebug(false) + + moment := time.Now() + rand.Seed(moment.UnixNano()) + + r := rand.Int63() + id := uuid.NewUUID() + + err := MakeError("%s:%d", id, r).(*tStandardError) + if err.message != fmt.Sprintf("%s:%d", id, r) { + t.Error("Failed on MakeError:", err.message, "!=", fmt.Sprintf("%s:%d", id, r)) + } + + if len(err.trace) != 0 { + t.Error("Failed on empty trace") + } + + if err.infos.Len() != 0 { + t.Error("Failed on no-infos") + } +} + +func TestMakeErrorDebug(t *testing.T) { + SetDebug(true) + + err := MakeError("").(*tStandardError) + if len(err.trace) == 0 { + t.Error("Failed on stack trace") + } +} + +func TestError(t *testing.T) { + SetDebug(true) + + err := MakeError("") + if len(err.Error()) == 0 { + t.Error("Failed on infos") + } +} + +func TestCatch(t *testing.T) { + var err error + + func() { + Catch(&err, func(err IError) error { + return err + }, func(err IError) error { + return err + }) + }() + + func() { + defer Catch(&err, func(err IError) error { + return err + }, func(err IError) error { + return err + }) + + panic(fmt.Errorf("Error")) + }() + + func() { + defer DiscardPanic() + defer Catch(&err, nil, nil) + + panic("Error") + }() + + func() { + defer Catch(&err, nil, nil) + + panic(fmt.Errorf("Error")) + }() + + Catch(&err, func(err IError) error { + return nil + }, func(err IError) error { + return nil + }) +} + +func TestTry(t *testing.T) { + Try(func(err IError) error { + return nil + }, nil, nil) + + Try(func(err IError) error { + return fmt.Errorf("Error") + }, func(err IError) error { + return err + }, func(err IError) error { + return err + }) + + Try(func(err IError) error { + panic(fmt.Errorf("Error")) + }, func(err IError) error { + return err + }, nil) + + Try(func(err IError) error { + panic("Error") + }, func(err IError) error { + return err + }, nil) +} + +func TestRaise(t *testing.T) { + func() { + defer DiscardPanic() + + Raise("") + }() +} + +func TestRaiseWithInfos(t *testing.T) { + func() { + defer DiscardPanic() + + RaiseWithInfos(0, nil, "") + }() +} + +func TestRaiseError(t *testing.T) { + func() { + defer DiscardPanic() + + RaiseError(fmt.Errorf("Error")) + }() +} diff --git a/utils.go b/utils.go new file mode 100755 index 0000000..b5a10fd --- /dev/null +++ b/utils.go @@ -0,0 +1,61 @@ +package goerrors + +import ( + "reflect" +) + +// Concatenate 2 string lists +func _concat(l1, l2 []string) []string { + if (l1 == nil) && (l2 == nil) { + return nil + } + + sz1 := len(l1) + sz2 := len(l2) + res := make([]string, sz1+sz2) + + if sz1 != 0 { + copy(res, l1) + } + + if sz2 != 0 { + copy(res[sz1:], l2) + } + + return res +} + +// Get type hierarchy from an error type passed as `this_type` parameter. +// The `final_type` parameter represents type of `GoError` structure. +func _getTypeHierarchy(this_type, final_type reflect.Type) []string { + if (this_type == nil) || (this_type.Kind() != reflect.Struct) { + return []string{} + } + + res := []string{this_type.PkgPath() + "." + this_type.Name()} + + if this_type == final_type { + return res + } + + list := make([]string, 0) + + n := this_type.NumField() + for i := 0; i < n; i++ { + field := this_type.Field(i) + if !field.Anonymous { + continue + } + + parents := _getTypeHierarchy(field.Type, final_type) + if len(parents) > 0 { + list = _concat(list, parents) + } + } + + if len(list) == 0 { + return list + } + + return _concat(res, list) +} diff --git a/utils_test.go b/utils_test.go new file mode 100755 index 0000000..232ba9b --- /dev/null +++ b/utils_test.go @@ -0,0 +1,182 @@ +package goerrors + +import ( + "reflect" + "testing" +) + +func TestConcat(t *testing.T) { + if _concat(nil, nil) != nil { + t.Error("Concatenation of 2 nil lists should be nil") + } + + lst := _concat([]string{}, nil) + if (lst == nil) || (len(lst) > 0) { + t.Error("Concatenation of 2 empty list should be should: ", lst) + } + + lst0 := make([]string, 0) + lst1 := []string{"A", "B"} + + lst = _concat(lst0, nil) + if (lst == nil) || (len(lst) > 0) { + t.Error("Concat with RHS nil list should not return nil and non empty list (LHS list)") + } + + if len(_concat(nil, lst0)) != 0 { + t.Error("Concat with RHS empty list should have length as zero") + } + + if len(_concat(lst0, lst0)) != 0 { + t.Error("Concat same empty list should have length as zero") + } + + if len(_concat(lst0, make([]string, 0))) != 0 { + t.Error("Concat two empty lists should have length as zero") + } + + if len(_concat(lst1, lst1)) != (2 * len(lst1)) { + t.Error("Concat same list should have length as twice the length of the list") + } + + if len(_concat(lst1, []string{"X"})) != 3 { + t.Error("Concat two list should have the good length") + } +} + +func TestStructHierarchy(t *testing.T) { + type X struct { + int + j float32 + } + + type A struct { + } + + type B struct { + A + X + } + + type C struct { + B + } + + type D struct { + A + B + X + } + + type E struct { + D + C + } + + tA := reflect.TypeOf(A{}) + tB := reflect.TypeOf(B{}) + tC := reflect.TypeOf(C{}) + tD := reflect.TypeOf(D{}) + tE := reflect.TypeOf(E{}) + + n1 := "github.com/corebreaker/goerrors.A" + n2 := "github.com/corebreaker/goerrors.B" + n3 := "github.com/corebreaker/goerrors.C" + n4 := "github.com/corebreaker/goerrors.D" + n5 := "github.com/corebreaker/goerrors.E" + + res := _getTypeHierarchy(tA, tA) + + if res == nil { + t.Error("With zero level, it returns nil") + } + + if len(res) != 1 { + t.Fatal("With zero level, it should return a list with length 1; the return is:", res) + } + + if res[0] != n1 { + t.Errorf("With zero level, it should return a list one type %v; the return is: %v", n1, res) + } + + res = _getTypeHierarchy(tB, tA) + + if res == nil { + t.Error("With one level, it returns nil") + } + + if len(res) != 2 { + t.Fatal("With one level, it should return a list with length 2; the return is:", res) + } + + if (res[0] != n2) || (res[1] != n1) { + t.Errorf("With one levels, it should return a list with types %v and %v; the return is: %v", n2, n1, res) + } + + res = _getTypeHierarchy(tC, tA) + + if res == nil { + t.Error("With two levels, it returns nil") + } + + if len(res) != 3 { + t.Fatal("With two levels, it should return a list with length 3; the return is:", res) + } + + if (res[0] != n3) || (res[1] != n2) || (res[2] != n1) { + t.Errorf("With two levels, it should return a list with types %v, %v and %v; the return is: %v", + n3, + n2, + n1, + res) + } + + res = _getTypeHierarchy(tD, tA) + + if res == nil { + t.Error("With three levels, it returns nil") + } + + if len(res) != 4 { + t.Fatal("With three levels, it should return a list with length 4; the return is:", res) + } + + if (res[0] != n4) || (res[1] != n1) || (res[2] != n2) || (res[3] != n1) { + t.Errorf("With two levels, it should return a list with types %v, %v, %v and %v; the return is: %v", + n4, + n1, + n2, + n1, + res) + } + + res = _getTypeHierarchy(tE, tA) + + if res == nil { + t.Error("With four levels, it returns nil") + } + + if len(res) != 8 { + t.Fatal("With four levels, it should return a list with length 8; the return is:", res) + } + + if (res[0] != n5) || + (res[1] != n4) || + (res[2] != n1) || + (res[3] != n2) || + (res[4] != n1) || + (res[5] != n3) || + (res[6] != n2) || + (res[7] != n1) { + t.Errorf("With two levels, it should return a list with types %v, %v, %v, %v, %v, %v, %v and %v; the return is: %v", + n5, + n4, + n1, + n2, + n1, + n3, + n2, + n1, + res) + } +}