From eb5bb1daa87df6be45ee2b9fd56e3b58e94b1825 Mon Sep 17 00:00:00 2001 From: Brian Tiger Chow Date: Fri, 14 Nov 2014 15:18:32 -0800 Subject: [PATCH 1/5] feat(debugerror) License: MIT Signed-off-by: Brian Tiger Chow impl errorf License: MIT Signed-off-by: Brian Tiger Chow return a debug error License: MIT Signed-off-by: Brian Tiger Chow --- Godeps/Godeps.json | 4 + .../facebookgo/stackerr/.travis.yml | 24 +++++ .../github.com/facebookgo/stackerr/readme.md | 4 + .../facebookgo/stackerr/stackerr.go | 97 +++++++++++++++++++ .../facebookgo/stackerr/stackerr_test.go | 82 ++++++++++++++++ cmd/ipfs2/main.go | 1 + config/config.go | 4 +- util/debugerror/debugerror.go | 30 ++++++ 8 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/facebookgo/stackerr/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/facebookgo/stackerr/readme.md create mode 100644 Godeps/_workspace/src/github.com/facebookgo/stackerr/stackerr.go create mode 100644 Godeps/_workspace/src/github.com/facebookgo/stackerr/stackerr_test.go create mode 100644 util/debugerror/debugerror.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 53ce7e3c04c..bd0524e0921 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -60,6 +60,10 @@ "ImportPath": "github.com/coreos/go-semver/semver", "Rev": "6fe83ccda8fb9b7549c9ab4ba47f47858bc950aa" }, + { + "ImportPath": "github.com/facebookgo/stackerr", + "Rev": "060fbf9364c89acd41bf710e9e92915a90e7a5b5" + }, { "ImportPath": "github.com/gonuts/flag", "Rev": "741a6cbd37a30dedc93f817e7de6aaf0ca38a493" diff --git a/Godeps/_workspace/src/github.com/facebookgo/stackerr/.travis.yml b/Godeps/_workspace/src/github.com/facebookgo/stackerr/.travis.yml new file mode 100644 index 00000000000..2cc62c5e853 --- /dev/null +++ b/Godeps/_workspace/src/github.com/facebookgo/stackerr/.travis.yml @@ -0,0 +1,24 @@ +language: go + +go: + - 1.2 + - 1.3 + +matrix: + fast_finish: true + +before_install: + - go get -v code.google.com/p/go.tools/cmd/vet + - go get -v github.com/golang/lint/golint + - go get -v code.google.com/p/go.tools/cmd/cover + +install: + - go install -race -v std + - go get -race -t -v ./... + - go install -race -v ./... + +script: + - go vet ./... + - $HOME/gopath/bin/golint . + - go test -cpu=2 -race -v ./... + - go test -cpu=2 -covermode=atomic ./... diff --git a/Godeps/_workspace/src/github.com/facebookgo/stackerr/readme.md b/Godeps/_workspace/src/github.com/facebookgo/stackerr/readme.md new file mode 100644 index 00000000000..8837ed070b5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/facebookgo/stackerr/readme.md @@ -0,0 +1,4 @@ +stackerr [![Build Status](https://secure.travis-ci.org/facebookgo/stackerr.png)](http://travis-ci.org/facebookgo/stackerr) +======== + +Documentation: https://godoc.org/github.com/facebookgo/stackerr diff --git a/Godeps/_workspace/src/github.com/facebookgo/stackerr/stackerr.go b/Godeps/_workspace/src/github.com/facebookgo/stackerr/stackerr.go new file mode 100644 index 00000000000..e0d2e209ca1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/facebookgo/stackerr/stackerr.go @@ -0,0 +1,97 @@ +// Package stackerr provides a way to augment errors with one or more stack +// traces to allow for easier debugging. +package stackerr + +import ( + "errors" + "fmt" + + "github.com/facebookgo/stack" +) + +// Error provides the wrapper that adds multiple Stacks to an error. Each Stack +// represents a location in code thru which this error was wrapped. +type Error struct { + multiStack *stack.Multi + underlying error +} + +// Error provides a multi line error string that includes the stack trace. +func (e *Error) Error() string { + return fmt.Sprintf("%s\n%s", e.underlying, e.multiStack) +} + +// MultiStack identifies the locations this error was wrapped at. +func (e *Error) MultiStack() *stack.Multi { + return e.multiStack +} + +// Underlying returns the error that is being wrapped. +func (e *Error) Underlying() error { + return e.underlying +} + +type hasMultiStack interface { + MultiStack() *stack.Multi +} + +// WrapSkip the error and add the current Stack. The argument skip is the +// number of stack frames to ascend, with 0 identifying the caller of Wrap. If +// the error to be wrapped has a MultiStack, the current stack will be added to +// it. If the error to be wrapped is nil, a nil error is returned. +func WrapSkip(err error, skip int) error { + // nil errors are returned back as nil. + if err == nil { + return nil + } + + // we're adding another Stack to an already wrapped error. + if se, ok := err.(hasMultiStack); ok { + se.MultiStack().AddCallers(skip + 1) + return err + } + + // we're create a freshly wrapped error. + return &Error{ + multiStack: stack.CallersMulti(skip + 1), + underlying: err, + } +} + +// Wrap provides a convenience function that calls WrapSkip with skip=0. That +// is, the Stack starts with the caller of Wrap. +func Wrap(err error) error { + return WrapSkip(err, 1) +} + +// New returns a new error that includes the Stack. +func New(s string) error { + return WrapSkip(errors.New(s), 1) +} + +// Newf formats and returns a new error that includes the Stack. +func Newf(format string, args ...interface{}) error { + return WrapSkip(fmt.Errorf(format, args...), 1) +} + +type hasUnderlying interface { + Underlying() error +} + +// Underlying returns all the underlying errors by iteratively checking if the +// error has an Underlying error. If e is nil, the returned slice will be nil. +func Underlying(e error) []error { + var errs []error + for { + if e == nil { + return errs + } + errs = append(errs, e) + + if eh, ok := e.(hasUnderlying); ok { + e = eh.Underlying() + } else { + e = nil + } + } +} diff --git a/Godeps/_workspace/src/github.com/facebookgo/stackerr/stackerr_test.go b/Godeps/_workspace/src/github.com/facebookgo/stackerr/stackerr_test.go new file mode 100644 index 00000000000..a160e675b24 --- /dev/null +++ b/Godeps/_workspace/src/github.com/facebookgo/stackerr/stackerr_test.go @@ -0,0 +1,82 @@ +package stackerr_test + +import ( + "errors" + "fmt" + "regexp" + "strings" + "testing" + + "github.com/facebookgo/stackerr" +) + +func TestNew(t *testing.T) { + const errStr = "foo bar baz" + e := stackerr.New(errStr) + matches := []string{ + errStr, + "^github.com/facebookgo/stackerr/stackerr_test.go:15 +TestNew$", + } + match(t, e.Error(), matches) +} + +func TestNewf(t *testing.T) { + const fmtStr = "%s 42" + const errStr = "foo bar baz" + e := stackerr.Newf(fmtStr, errStr) + matches := []string{ + fmt.Sprintf(fmtStr, errStr), + "^github.com/facebookgo/stackerr/stackerr_test.go:26 +TestNewf$", + } + match(t, e.Error(), matches) +} + +func TestWrap(t *testing.T) { + const errStr = "foo bar baz" + e := stackerr.Wrap(errors.New(errStr)) + matches := []string{ + errStr, + "^github.com/facebookgo/stackerr/stackerr_test.go:36 +TestWrap$", + } + match(t, e.Error(), matches) +} + +func TestNilWrap(t *testing.T) { + if stackerr.WrapSkip(nil, 1) != nil { + t.Fatal("did not get nil error") + } +} + +func TestDoubleWrap(t *testing.T) { + e := stackerr.New("") + if stackerr.WrapSkip(e, 1) != e { + t.Fatal("double wrap failure") + } +} + +func TestLog(t *testing.T) { + t.Log(stackerr.New("hello")) +} + +func TestUnderlying(t *testing.T) { + e1 := errors.New("") + e2 := stackerr.Wrap(e1) + errs := stackerr.Underlying(e2) + if len(errs) != 2 || errs[0] != e2 || errs[1] != e1 { + t.Fatal("failed Underlying") + } +} + +func match(t testing.TB, s string, matches []string) { + lines := strings.Split(s, "\n") + for i, m := range matches { + if !regexp.MustCompile(m).MatchString(lines[i]) { + t.Fatalf( + "did not find expected match \"%s\" on line %d in:\n%s", + m, + i, + s, + ) + } + } +} diff --git a/cmd/ipfs2/main.go b/cmd/ipfs2/main.go index 4bbe1da92f4..d11127eed60 100644 --- a/cmd/ipfs2/main.go +++ b/cmd/ipfs2/main.go @@ -169,6 +169,7 @@ func (i *cmdInvocation) Parse(args []string) error { if err != nil { return err } + log.Debugf("config path is %s", configPath) // this sets up the function that will initialize the config lazily. ctx := i.req.Context() diff --git a/config/config.go b/config/config.go index d03a26b116b..1416bb139c0 100644 --- a/config/config.go +++ b/config/config.go @@ -5,11 +5,11 @@ import ( "crypto" "crypto/x509" "encoding/base64" - "errors" "os" "path/filepath" u "github.com/jbenet/go-ipfs/util" + "github.com/jbenet/go-ipfs/util/debugerror" ) var log = u.Logger("config") @@ -129,7 +129,7 @@ func (i *Identity) DecodePrivateKey(passphrase string) (crypto.PrivateKey, error func Load(filename string) (*Config, error) { // if nothing is there, fail. User must run 'ipfs init' if _, err := os.Stat(filename); os.IsNotExist(err) { - return nil, errors.New("ipfs not initialized, please run 'ipfs init'") + return nil, debugerror.New("ipfs not initialized, please run 'ipfs init'") } var cfg Config diff --git a/util/debugerror/debugerror.go b/util/debugerror/debugerror.go new file mode 100644 index 00000000000..2b6370f98fe --- /dev/null +++ b/util/debugerror/debugerror.go @@ -0,0 +1,30 @@ +// package debugerror provides a way to augment errors with additional +// information to allow for easier debugging. +package debugerror + +import ( + "errors" + "fmt" + + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/facebookgo/stackerr" + "github.com/jbenet/go-ipfs/util" +) + +func Errorf(format string, a ...interface{}) error { + return Wrap(fmt.Errorf(format, a...)) +} + +// New returns an error that contains a stack trace (in debug mode) +func New(s string) error { + if util.Debug { + return stackerr.New(s) + } + return errors.New(s) +} + +func Wrap(err error) error { + if util.Debug { + return stackerr.Wrap(err) + } + return err +} From 408fadc8beb952d67021244d4868228382fa58f4 Mon Sep 17 00:00:00 2001 From: Brian Tiger Chow Date: Fri, 14 Nov 2014 16:51:48 -0800 Subject: [PATCH 2/5] fix(2/main) don't check for updates when running init @jbenet @mappum Yeah, there's some duplicated work. But there's also a separation of concerns. In one case, we check to determine where the command should run. In the other case, we check to determine which hooks should run. Having these actions separated reduces complexity in a nice way. License: MIT Signed-off-by: Brian Tiger Chow --- cmd/ipfs2/ipfs.go | 15 ++++++--- cmd/ipfs2/main.go | 79 ++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 75 insertions(+), 19 deletions(-) diff --git a/cmd/ipfs2/ipfs.go b/cmd/ipfs2/ipfs.go index 5f349c19c1f..51999c9152a 100644 --- a/cmd/ipfs2/ipfs.go +++ b/cmd/ipfs2/ipfs.go @@ -55,6 +55,13 @@ type cmdDetails struct { cannotRunOnClient bool cannotRunOnDaemon bool doesNotUseRepo bool + + // initializesConfig describes commands that initialize the config. + // pre-command hooks that require configs must not be run before this + // command + initializesConfig bool + + preemptsUpdates bool } func (d *cmdDetails) String() string { @@ -71,14 +78,14 @@ func (d *cmdDetails) usesRepo() bool { return !d.doesNotUseRepo } // properties so that other code can make decisions about whether to invoke a // command or return an error to the user. var cmdDetailsMap = map[*cmds.Command]cmdDetails{ - initCmd: cmdDetails{cannotRunOnDaemon: true, doesNotUseRepo: true}, + initCmd: cmdDetails{initializesConfig: true, cannotRunOnDaemon: true, doesNotUseRepo: true}, daemonCmd: cmdDetails{cannotRunOnDaemon: true}, commandsClientCmd: cmdDetails{doesNotUseRepo: true}, commands.CommandsDaemonCmd: cmdDetails{doesNotUseRepo: true}, commands.DiagCmd: cmdDetails{cannotRunOnClient: true}, commands.VersionCmd: cmdDetails{doesNotUseRepo: true}, - commands.UpdateCmd: cmdDetails{cannotRunOnDaemon: true}, - commands.UpdateCheckCmd: cmdDetails{}, - commands.UpdateLogCmd: cmdDetails{}, + commands.UpdateCmd: cmdDetails{preemptsUpdates: true, cannotRunOnDaemon: true}, + commands.UpdateCheckCmd: cmdDetails{preemptsUpdates: true}, + commands.UpdateLogCmd: cmdDetails{preemptsUpdates: true}, commands.LogCmd: cmdDetails{cannotRunOnClient: true}, } diff --git a/cmd/ipfs2/main.go b/cmd/ipfs2/main.go index d11127eed60..955aadfde6a 100644 --- a/cmd/ipfs2/main.go +++ b/cmd/ipfs2/main.go @@ -20,6 +20,7 @@ import ( daemon "github.com/jbenet/go-ipfs/daemon2" updates "github.com/jbenet/go-ipfs/updates" u "github.com/jbenet/go-ipfs/util" + "github.com/jbenet/go-ipfs/util/debugerror" ) // log is the command logger @@ -201,21 +202,61 @@ func (i *cmdInvocation) requestedHelp() (short bool, long bool, err error) { return longHelp, shortHelp, nil } +func callPreCommandHooks(details cmdDetails, req cmds.Request, root *cmds.Command) error { + + log.Debug("Calling pre-command hooks...") + + // some hooks only run when the command is executed locally + daemon, err := commandShouldRunOnDaemon(details, req, root) + if err != nil { + return err + } + + // check for updates when 1) commands is going to be run locally, 2) the + // command does not initialize the config, and 3) the command does not + // pre-empt updates + if !daemon && !details.initializesConfig && !details.preemptsUpdates { + + log.Debug("Calling hook: Check for updates") + + cfg, err := req.Context().GetConfig() + if err != nil { + return err + } + // Check for updates and potentially install one. + if err := updates.CliCheckForUpdates(cfg, req.Context().ConfigRoot); err != nil { + return err + } + } + + return nil +} + func callCommand(req cmds.Request, root *cmds.Command) (cmds.Response, error) { var res cmds.Response - useDaemon, err := commandShouldRunOnDaemon(req, root) + details, err := commandDetails(req.Path(), root) + if err != nil { + return nil, err + } + + useDaemon, err := commandShouldRunOnDaemon(*details, req, root) if err != nil { return nil, err } - cfg, err := req.Context().GetConfig() + err = callPreCommandHooks(*details, req, root) if err != nil { return nil, err } if useDaemon { + cfg, err := req.Context().GetConfig() + if err != nil { + return nil, err + } + addr, err := ma.NewMultiaddr(cfg.Addresses.API) if err != nil { return nil, err @@ -237,11 +278,6 @@ func callCommand(req cmds.Request, root *cmds.Command) (cmds.Response, error) { } else { log.Info("Executing command locally") - // Check for updates and potentially install one. - if err := updates.CliCheckForUpdates(cfg, req.Context().ConfigRoot); err != nil { - return nil, err - } - // this sets up the function that will initialize the node // this is so that we can construct the node lazily. ctx := req.Context() @@ -267,13 +303,11 @@ func callCommand(req cmds.Request, root *cmds.Command) (cmds.Response, error) { return res, nil } -func commandShouldRunOnDaemon(req cmds.Request, root *cmds.Command) (bool, error) { - path := req.Path() - // root command. - if len(path) < 1 { - return false, nil - } - +// commandDetails returns a command's details for the command given by |path| +// within the |root| command tree. +// +// Returns an error if the command is not found in the Command tree. +func commandDetails(path []string, root *cmds.Command) (*cmdDetails, error) { var details cmdDetails // find the last command in path that has a cmdDetailsMap entry cmd := root @@ -281,7 +315,7 @@ func commandShouldRunOnDaemon(req cmds.Request, root *cmds.Command) (bool, error var found bool cmd, found = cmd.Subcommands[cmp] if !found { - return false, fmt.Errorf("subcommand %s should be in root", cmp) + return nil, debugerror.Errorf("subcommand %s should be in root", cmp) } if cmdDetails, found := cmdDetailsMap[cmd]; found { @@ -289,6 +323,21 @@ func commandShouldRunOnDaemon(req cmds.Request, root *cmds.Command) (bool, error } } log.Debugf("cmd perms for +%v: %s", path, details.String()) + return &details, nil +} + +// commandShouldRunOnDaemon determines, from commmand details, whether a +// command ought to be executed on an IPFS daemon. +// +// It returns true if the command should be executed on a daemon and false if +// it should be executed on a client. It returns an error if the command must +// NOT be executed on either. +func commandShouldRunOnDaemon(details cmdDetails, req cmds.Request, root *cmds.Command) (bool, error) { + path := req.Path() + // root command. + if len(path) < 1 { + return false, nil + } if details.cannotRunOnClient && details.cannotRunOnDaemon { return false, fmt.Errorf("command disabled: %s", path[0]) From 56d732f9b18228b5bff9fabbb5462dd5199c4c59 Mon Sep 17 00:00:00 2001 From: Brian Tiger Chow Date: Sat, 15 Nov 2014 00:39:28 -0800 Subject: [PATCH 3/5] fix(2/main) s/preemptsUpdates/preemptsAutoUpdate addresses... https://github.com/jbenet/go-ipfs/pull/336#discussion_r20397834 License: MIT Signed-off-by: Brian Tiger Chow --- cmd/ipfs2/ipfs.go | 8 ++++---- cmd/ipfs2/main.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/ipfs2/ipfs.go b/cmd/ipfs2/ipfs.go index 51999c9152a..c3a0588aff7 100644 --- a/cmd/ipfs2/ipfs.go +++ b/cmd/ipfs2/ipfs.go @@ -61,7 +61,7 @@ type cmdDetails struct { // command initializesConfig bool - preemptsUpdates bool + preemptsAutoUpdate bool } func (d *cmdDetails) String() string { @@ -84,8 +84,8 @@ var cmdDetailsMap = map[*cmds.Command]cmdDetails{ commands.CommandsDaemonCmd: cmdDetails{doesNotUseRepo: true}, commands.DiagCmd: cmdDetails{cannotRunOnClient: true}, commands.VersionCmd: cmdDetails{doesNotUseRepo: true}, - commands.UpdateCmd: cmdDetails{preemptsUpdates: true, cannotRunOnDaemon: true}, - commands.UpdateCheckCmd: cmdDetails{preemptsUpdates: true}, - commands.UpdateLogCmd: cmdDetails{preemptsUpdates: true}, + commands.UpdateCmd: cmdDetails{preemptsAutoUpdate: true, cannotRunOnDaemon: true}, + commands.UpdateCheckCmd: cmdDetails{preemptsAutoUpdate: true}, + commands.UpdateLogCmd: cmdDetails{preemptsAutoUpdate: true}, commands.LogCmd: cmdDetails{cannotRunOnClient: true}, } diff --git a/cmd/ipfs2/main.go b/cmd/ipfs2/main.go index 955aadfde6a..5647f552eb1 100644 --- a/cmd/ipfs2/main.go +++ b/cmd/ipfs2/main.go @@ -215,7 +215,7 @@ func callPreCommandHooks(details cmdDetails, req cmds.Request, root *cmds.Comman // check for updates when 1) commands is going to be run locally, 2) the // command does not initialize the config, and 3) the command does not // pre-empt updates - if !daemon && !details.initializesConfig && !details.preemptsUpdates { + if !daemon && !details.initializesConfig && !details.preemptsAutoUpdate { log.Debug("Calling hook: Check for updates") From 9f798e86b294b76a0059b5240c9580134f291db4 Mon Sep 17 00:00:00 2001 From: Brian Tiger Chow Date: Sat, 15 Nov 2014 00:46:25 -0800 Subject: [PATCH 4/5] fix(deps) transitive dependency was missing programmer error License: MIT Signed-off-by: Brian Tiger Chow --- Godeps/Godeps.json | 6 +- .../github.com/facebookgo/stack/.travis.yml | 24 +++ .../src/github.com/facebookgo/stack/readme.md | 4 + .../src/github.com/facebookgo/stack/stack.go | 197 ++++++++++++++++++ .../github.com/facebookgo/stack/stack_test.go | 102 +++++++++ .../facebookgo/stackerr/stackerr.go | 2 +- .../facebookgo/stackerr/stackerr_test.go | 2 +- 7 files changed, 334 insertions(+), 3 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/facebookgo/stack/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/facebookgo/stack/readme.md create mode 100644 Godeps/_workspace/src/github.com/facebookgo/stack/stack.go create mode 100644 Godeps/_workspace/src/github.com/facebookgo/stack/stack_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index bd0524e0921..62cc20c9bef 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,6 +1,6 @@ { "ImportPath": "github.com/jbenet/go-ipfs", - "GoVersion": "go1.3", + "GoVersion": "go1.3.3", "Packages": [ "./..." ], @@ -60,6 +60,10 @@ "ImportPath": "github.com/coreos/go-semver/semver", "Rev": "6fe83ccda8fb9b7549c9ab4ba47f47858bc950aa" }, + { + "ImportPath": "github.com/facebookgo/stack", + "Rev": "4da6d991fc3c389efa512151354d643eb5fae4e2" + }, { "ImportPath": "github.com/facebookgo/stackerr", "Rev": "060fbf9364c89acd41bf710e9e92915a90e7a5b5" diff --git a/Godeps/_workspace/src/github.com/facebookgo/stack/.travis.yml b/Godeps/_workspace/src/github.com/facebookgo/stack/.travis.yml new file mode 100644 index 00000000000..2cc62c5e853 --- /dev/null +++ b/Godeps/_workspace/src/github.com/facebookgo/stack/.travis.yml @@ -0,0 +1,24 @@ +language: go + +go: + - 1.2 + - 1.3 + +matrix: + fast_finish: true + +before_install: + - go get -v code.google.com/p/go.tools/cmd/vet + - go get -v github.com/golang/lint/golint + - go get -v code.google.com/p/go.tools/cmd/cover + +install: + - go install -race -v std + - go get -race -t -v ./... + - go install -race -v ./... + +script: + - go vet ./... + - $HOME/gopath/bin/golint . + - go test -cpu=2 -race -v ./... + - go test -cpu=2 -covermode=atomic ./... diff --git a/Godeps/_workspace/src/github.com/facebookgo/stack/readme.md b/Godeps/_workspace/src/github.com/facebookgo/stack/readme.md new file mode 100644 index 00000000000..680f7ad4188 --- /dev/null +++ b/Godeps/_workspace/src/github.com/facebookgo/stack/readme.md @@ -0,0 +1,4 @@ +stack [![Build Status](https://secure.travis-ci.org/facebookgo/stack.png)](http://travis-ci.org/facebookgo/stack) +===== + +Documentation: https://godoc.org/github.com/facebookgo/stack diff --git a/Godeps/_workspace/src/github.com/facebookgo/stack/stack.go b/Godeps/_workspace/src/github.com/facebookgo/stack/stack.go new file mode 100644 index 00000000000..c045daf57da --- /dev/null +++ b/Godeps/_workspace/src/github.com/facebookgo/stack/stack.go @@ -0,0 +1,197 @@ +// Package stack provides utilities to capture and pass around stack traces. +// +// This is useful for building errors that know where they originated from, to +// track where a certain log event occured and so on. +package stack + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" +) + +const maxStackSize = 32 + +// Frame identifies a file, line & function name in the stack. +type Frame struct { + File string + Line int + Name string +} + +// String provides the standard file:line representation. +func (f Frame) String() string { + return fmt.Sprintf("%s:%d %s", f.File, f.Line, f.Name) +} + +// Stack represents an ordered set of Frames. +type Stack []Frame + +// String provides the standard multi-line stack trace. +func (s Stack) String() string { + var b bytes.Buffer + writeStack(&b, s) + return b.String() +} + +// Multi represents a number of Stacks. This is useful to allow tracking a +// value as it travels thru code. +type Multi struct { + stacks []Stack +} + +// Stacks returns the tracked Stacks. +func (m *Multi) Stacks() []Stack { + return m.stacks +} + +// Add the given Stack to this Multi. +func (m *Multi) Add(s Stack) { + m.stacks = append(m.stacks, s) +} + +// AddCallers adds the Callers Stack to this Multi. The argument skip is +// the number of stack frames to ascend, with 0 identifying the caller of +// Callers. +func (m *Multi) AddCallers(skip int) { + m.Add(Callers(skip + 1)) +} + +// String provides a human readable multi-line stack trace. +func (m *Multi) String() string { + var b bytes.Buffer + for i, s := range m.stacks { + if i != 0 { + fmt.Fprintf(&b, "\n(Stack %d)\n", i+1) + } + writeStack(&b, s) + } + return b.String() +} + +// Caller returns a single Frame for the caller. The argument skip is the +// number of stack frames to ascend, with 0 identifying the caller of Callers. +func Caller(skip int) Frame { + pc, file, line, _ := runtime.Caller(skip + 1) + fun := runtime.FuncForPC(pc) + return Frame{ + File: StripGOPATH(file), + Line: line, + Name: StripPackage(fun.Name()), + } +} + +// Callers returns a Stack of Frames for the callers. The argument skip is the +// number of stack frames to ascend, with 0 identifying the caller of Callers. +func Callers(skip int) Stack { + pcs := make([]uintptr, maxStackSize) + num := runtime.Callers(skip+2, pcs) + stack := make(Stack, num) + for i, pc := range pcs[:num] { + fun := runtime.FuncForPC(pc) + file, line := fun.FileLine(pc) + stack[i].File = StripGOPATH(file) + stack[i].Line = line + stack[i].Name = StripPackage(fun.Name()) + } + return stack +} + +// CallersMulti returns a Multi which includes one Stack for the +// current callers. The argument skip is the number of stack frames to ascend, +// with 0 identifying the caller of CallersMulti. +func CallersMulti(skip int) *Multi { + m := new(Multi) + m.AddCallers(skip + 1) + return m +} + +func writeStack(b *bytes.Buffer, s Stack) { + var width int + for _, f := range s { + if l := len(f.File) + numDigits(f.Line) + 1; l > width { + width = l + } + } + last := len(s) - 1 + for i, f := range s { + b.WriteString(f.File) + b.WriteRune(rune(':')) + n, _ := fmt.Fprintf(b, "%d", f.Line) + for i := width - len(f.File) - n; i != 0; i-- { + b.WriteRune(rune(' ')) + } + b.WriteString(f.Name) + if i != last { + b.WriteRune(rune('\n')) + } + } +} + +func numDigits(i int) int { + var n int + for { + n++ + i = i / 10 + if i == 0 { + return n + } + } +} + +// This can be set by a build script. It will be the colon separated equivalent +// of the environment variable. +var gopath string + +// This is the processed version based on either the above variable set by the +// build or from the GOPATH environment variable. +var gopaths []string + +func init() { + // prefer the variable set at build time, otherwise fallback to the + // environment variable. + if gopath == "" { + gopath = os.Getenv("GOPATH") + } + + for _, p := range strings.Split(gopath, ":") { + if p != "" { + gopaths = append(gopaths, filepath.Join(p, "src")+"/") + } + } + + // Also strip GOROOT for maximum cleanliness + gopaths = append(gopaths, filepath.Join(runtime.GOROOT(), "src", "pkg")+"/") +} + +// StripGOPATH strips the GOPATH prefix from the file path f. +// In development, this will be done using the GOPATH environment variable. +// For production builds, where the GOPATH environment will not be set, the +// GOPATH can be included in the binary by passing ldflags, for example: +// +// GO_LDFLAGS="$GO_LDFLAGS -X github.com/facebookgo/stack.gopath $GOPATH" +// go install "-ldflags=$GO_LDFLAGS" my/pkg +func StripGOPATH(f string) string { + for _, p := range gopaths { + if strings.HasPrefix(f, p) { + return f[len(p):] + } + } + return f +} + +// StripPackage strips the package name from the given Func.Name. +func StripPackage(n string) string { + slashI := strings.LastIndex(n, "/") + if slashI == -1 { + slashI = 0 // for built-in packages + } + dotI := strings.Index(n[slashI:], ".") + if dotI == -1 { + return n + } + return n[slashI+dotI+1:] +} diff --git a/Godeps/_workspace/src/github.com/facebookgo/stack/stack_test.go b/Godeps/_workspace/src/github.com/facebookgo/stack/stack_test.go new file mode 100644 index 00000000000..a6bdd880a60 --- /dev/null +++ b/Godeps/_workspace/src/github.com/facebookgo/stack/stack_test.go @@ -0,0 +1,102 @@ +package stack_test + +import ( + "regexp" + "strings" + "testing" + + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/facebookgo/stack" +) + +func indirect1() stack.Stack { + return stack.Callers(0) +} + +func indirect2() stack.Stack { + return indirect1() +} + +func indirect3() stack.Stack { + return indirect2() +} + +func TestCallers(t *testing.T) { + s := indirect3() + matches := []string{ + "^github.com/facebookgo/stack/stack_test.go:12 +indirect1$", + "^github.com/facebookgo/stack/stack_test.go:16 +indirect2$", + "^github.com/facebookgo/stack/stack_test.go:20 +indirect3$", + "^github.com/facebookgo/stack/stack_test.go:24 +TestCallers$", + } + match(t, s.String(), matches) +} + +func TestCallersMulti(t *testing.T) { + m := stack.CallersMulti(0) + const expected = "github.com/facebookgo/stack/stack_test.go:35 TestCallersMulti" + first := m.Stacks()[0][0].String() + if first != expected { + t.Fatalf(`expected "%s" got "%s"`, expected, first) + } +} + +func TestCallersMultiWithTwo(t *testing.T) { + m := stack.CallersMulti(0) + m.AddCallers(0) + matches := []string{ + "^github.com/facebookgo/stack/stack_test.go:44 +TestCallersMultiWithTwo$", + "", + "", + `^\(Stack 2\)$`, + "^github.com/facebookgo/stack/stack_test.go:46 +TestCallersMultiWithTwo$", + } + match(t, m.String(), matches) +} + +type typ struct{} + +func (m typ) indirect1() stack.Stack { + return stack.Callers(0) +} + +func (m typ) indirect2() stack.Stack { + return m.indirect1() +} + +func (m typ) indirect3() stack.Stack { + return m.indirect2() +} + +func TestCallersWithStruct(t *testing.T) { + var m typ + s := m.indirect3() + matches := []string{ + "^github.com/facebookgo/stack/stack_test.go:59 +typ.indirect1$", + "^github.com/facebookgo/stack/stack_test.go:63 +typ.indirect2$", + "^github.com/facebookgo/stack/stack_test.go:67 +typ.indirect3$", + "^github.com/facebookgo/stack/stack_test.go:72 +TestCallersWithStruct$", + } + match(t, s.String(), matches) +} + +func TestCaller(t *testing.T) { + f := stack.Caller(0) + const expected = "github.com/facebookgo/stack/stack_test.go:83 TestCaller" + if f.String() != expected { + t.Fatalf(`expected "%s" got "%s"`, expected, f) + } +} + +func match(t testing.TB, s string, matches []string) { + lines := strings.Split(s, "\n") + for i, m := range matches { + if !regexp.MustCompile(m).MatchString(lines[i]) { + t.Fatalf( + "did not find expected match \"%s\" on line %d in:\n%s", + m, + i, + s, + ) + } + } +} diff --git a/Godeps/_workspace/src/github.com/facebookgo/stackerr/stackerr.go b/Godeps/_workspace/src/github.com/facebookgo/stackerr/stackerr.go index e0d2e209ca1..e5f1dde0537 100644 --- a/Godeps/_workspace/src/github.com/facebookgo/stackerr/stackerr.go +++ b/Godeps/_workspace/src/github.com/facebookgo/stackerr/stackerr.go @@ -6,7 +6,7 @@ import ( "errors" "fmt" - "github.com/facebookgo/stack" + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/facebookgo/stack" ) // Error provides the wrapper that adds multiple Stacks to an error. Each Stack diff --git a/Godeps/_workspace/src/github.com/facebookgo/stackerr/stackerr_test.go b/Godeps/_workspace/src/github.com/facebookgo/stackerr/stackerr_test.go index a160e675b24..fb9d07e6b20 100644 --- a/Godeps/_workspace/src/github.com/facebookgo/stackerr/stackerr_test.go +++ b/Godeps/_workspace/src/github.com/facebookgo/stackerr/stackerr_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/facebookgo/stackerr" + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/facebookgo/stackerr" ) func TestNew(t *testing.T) { From e940643f203ec02a1c8404237fb899e0ec9876c9 Mon Sep 17 00:00:00 2001 From: Brian Tiger Chow Date: Sat, 15 Nov 2014 01:00:09 -0800 Subject: [PATCH 5/5] docs License: MIT Signed-off-by: Brian Tiger Chow --- cmd/ipfs2/ipfs.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/ipfs2/ipfs.go b/cmd/ipfs2/ipfs.go index c3a0588aff7..45633df7ca7 100644 --- a/cmd/ipfs2/ipfs.go +++ b/cmd/ipfs2/ipfs.go @@ -61,6 +61,8 @@ type cmdDetails struct { // command initializesConfig bool + // preemptsAutoUpdate describes commands that must be executed without the + // pre-command update hook preemptsAutoUpdate bool }