diff --git a/Gopkg.lock b/Gopkg.lock index a742ddb79..734ac3021 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -269,6 +269,12 @@ revision = "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab" version = "v1.1.1" +[[projects]] + name = "go.uber.org/dig" + packages = [".","internal/digreflect"] + revision = "d100de9c8cc8358591507951141bc107713ba671" + version = "v1.3.0" + [[projects]] branch = "master" name = "golang.org/x/net" @@ -302,6 +308,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "33ad3d85008a917d866cd976a91425c36d7ab1843c17ba2abc2fd82d1377edcd" + inputs-digest = "a39908b9df36d1a1909b3ebcf56155f6919c4e7e9b09bd681b0a20c3a312467a" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Makefile b/Makefile index 564ea27c1..438d21729 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ build-deps: go get -u github.com/golang/lint/golint go get golang.org/x/tools/cmd/goimports --dep-deps: +dep-deps: go get -u github.com/golang/dep/cmd/dep docker: @@ -62,8 +62,9 @@ _mockgen: mockgen: _mockgen fmt -dep: - dep ensure +deps: + dep ensure -v; dep prune -v + fmt: goimports -w pkg diff --git a/pkg/cli/root.go b/pkg/cli/root.go index 9fbbf8807..201ff7cd5 100644 --- a/pkg/cli/root.go +++ b/pkg/cli/root.go @@ -4,11 +4,10 @@ import ( "fmt" "os" - "context" - "strings" - "github.com/pkg/errors" + "context" + "github.com/replicatedcom/ship/pkg/cli/devtool_releaser" "github.com/replicatedcom/ship/pkg/e2e" "github.com/replicatedcom/ship/pkg/ship" @@ -33,15 +32,7 @@ application specs to be used in on-prem installations. SilenceUsage: true, SilenceErrors: true, RunE: func(cmd *cobra.Command, args []string) error { - rc, err := ship.FromViper(viper.GetViper()) - if err != nil { - return errors.Wrap(err, "initialize") - } - err = rc.Execute(context.Background()) - if err != nil { - rc.ExitWithError(err) - } - return nil // let ExitWithError handle it ^ + return ship.RunE(context.Background()) }, } cobra.OnInitialize(initConfig) diff --git a/pkg/e2e/case.go b/pkg/e2e/case.go index f132420c5..c2749a692 100644 --- a/pkg/e2e/case.go +++ b/pkg/e2e/case.go @@ -3,22 +3,22 @@ package e2e import ( "testing" - "context" "encoding/json" "io/ioutil" "net/url" "time" + "context" + "github.com/replicatedcom/ship/pkg/api" "github.com/replicatedcom/ship/pkg/ship" - "github.com/spf13/viper" "github.com/stretchr/testify/require" ) type CaseRunner struct { t *testing.T - assert *require.Assertions + req *require.Assertions testcase testcase } @@ -32,14 +32,14 @@ type testcase struct { func (r *CaseRunner) promoteRelease() { gqlServer, err := url.Parse(r.testcase.config.GQL) - r.assert.NoError(err) + r.req.NoError(err) client := &GraphQLClient{ GQLServer: gqlServer, Token: r.testcase.config.Token, } spec, err := json.Marshal(r.testcase.Spec) - r.assert.NoError(err) + r.req.NoError(err) _, err = client.PromoteRelease( string(spec), @@ -47,31 +47,28 @@ func (r *CaseRunner) promoteRelease() { r.testcase.config.Semver, `Integration test run on `+time.Now().String(), ) - r.assert.NoError(err) + r.req.NoError(err) } func (r *CaseRunner) Run() { - r.promoteRelease() r.runShipForCustomer() r.validateFiles() - } + func (r *CaseRunner) runShipForCustomer() { - // todo do each testcase in its own tmp directory, - // also maybe fork or docker run or something - s, err := ship.FromViper(viper.GetViper()) - r.assert.NoError(err) + s, err := ship.Get() + r.req.NoError(err) err = s.Execute(context.Background()) - r.assert.NoError(err) + r.req.NoError(err) } func (r *CaseRunner) validateFiles() { for path, expected := range r.testcase.Expect { actual, err := ioutil.ReadFile(path) - r.assert.NoError(err) - r.assert.Equal(expected, string(actual)) + r.req.NoError(err) + r.req.Equal(expected, string(actual)) } } diff --git a/pkg/e2e/suite.go b/pkg/e2e/suite.go index 0b2790b7f..1b46b39e6 100644 --- a/pkg/e2e/suite.go +++ b/pkg/e2e/suite.go @@ -47,7 +47,7 @@ func (r *Runner) TestCase(test testcase) func(t *testing.T) { return func(t *testing.T) { (&CaseRunner{ t: t, - assert: require.New(t), + req: require.New(t), testcase: test, }).Run() } diff --git a/pkg/lifecycle/message/api.go b/pkg/lifecycle/message/api.go index cdf411ae6..73fbda8a6 100644 --- a/pkg/lifecycle/message/api.go +++ b/pkg/lifecycle/message/api.go @@ -13,9 +13,11 @@ import ( "github.com/replicatedcom/ship/pkg/lifecycle/render/config" "github.com/replicatedcom/ship/pkg/templates" "github.com/spf13/viper" + "go.uber.org/dig" ) type DaemonMessenger struct { + dig.In Logger log.Logger UI cli.Ui Viper *viper.Viper diff --git a/pkg/lifecycle/message/cli.go b/pkg/lifecycle/message/cli.go index 55647e23b..f42f5a3c2 100644 --- a/pkg/lifecycle/message/cli.go +++ b/pkg/lifecycle/message/cli.go @@ -12,11 +12,14 @@ import ( "github.com/replicatedcom/ship/pkg/lifecycle/render/config" "github.com/replicatedcom/ship/pkg/templates" "github.com/spf13/viper" + "go.uber.org/dig" ) var _ Messenger = &CLIMessenger{} type CLIMessenger struct { + dig.In + Logger log.Logger UI cli.Ui Viper *viper.Viper @@ -50,7 +53,7 @@ func (e *CLIMessenger) Execute(ctx context.Context, release *api.Release, step * func (e *CLIMessenger) getBuilder(release *api.Release) templates.Builder { builder := e.BuilderBuilder.NewBuilder( - templates.NewStaticContext(), + e.BuilderBuilder.NewStaticContext(), builderContext{ logger: e.Logger, viper: e.Viper, diff --git a/pkg/lifecycle/message/messenger.go b/pkg/lifecycle/message/messenger.go index 792706e69..fda0b3829 100644 --- a/pkg/lifecycle/message/messenger.go +++ b/pkg/lifecycle/message/messenger.go @@ -5,9 +5,7 @@ import ( "github.com/replicatedcom/ship/pkg/api" "github.com/replicatedcom/ship/pkg/lifecycle/render/config" - "github.com/replicatedcom/ship/pkg/logger" "github.com/replicatedcom/ship/pkg/templates" - "github.com/replicatedcom/ship/pkg/ui" "github.com/spf13/viper" ) @@ -16,22 +14,16 @@ type Messenger interface { WithDaemon(d config.Daemon) Messenger } -func FromViper(v *viper.Viper) Messenger { +func NewMessenger( + v *viper.Viper, + cli CLIMessenger, + daemon DaemonMessenger, +) Messenger { if v.GetBool("headless") { - return &CLIMessenger{ - Logger: logger.FromViper(v), - UI: ui.FromViper(v), - Viper: v, - BuilderBuilder: templates.BuilderBuilderFromViper(v), - } + return &cli } - return &DaemonMessenger{ - Logger: logger.FromViper(v), - UI: ui.FromViper(v), - Viper: v, - BuilderBuilder: templates.BuilderBuilderFromViper(v), - } + return &daemon } func (m *DaemonMessenger) WithDaemon(d config.Daemon) Messenger { diff --git a/pkg/lifecycle/render/config/daemon.go b/pkg/lifecycle/render/config/daemon.go index fd10cb36b..5535974a7 100644 --- a/pkg/lifecycle/render/config/daemon.go +++ b/pkg/lifecycle/render/config/daemon.go @@ -45,7 +45,7 @@ type ShipDaemon struct { Fs afero.Afero Viper *viper.Viper UI cli.Ui - StateManager *state.StateManager + StateManager *state.Manager ConfigRenderer *APIConfigRenderer sync.Mutex diff --git a/pkg/lifecycle/render/config/headless_daemon.go b/pkg/lifecycle/render/config/headless_daemon.go index 061d1172c..5a67ec080 100644 --- a/pkg/lifecycle/render/config/headless_daemon.go +++ b/pkg/lifecycle/render/config/headless_daemon.go @@ -14,7 +14,7 @@ import ( ) type HeadlessDaemon struct { - StateManager *state.StateManager + StateManager *state.Manager Logger log.Logger UI cli.Ui ConfigRenderer *APIConfigRenderer diff --git a/pkg/lifecycle/render/config/headless_daemon_test.go b/pkg/lifecycle/render/config/headless_daemon_test.go index 7841cd291..921b57df1 100644 --- a/pkg/lifecycle/render/config/headless_daemon_test.go +++ b/pkg/lifecycle/render/config/headless_daemon_test.go @@ -466,7 +466,7 @@ func TestHeadlessDaemon(t *testing.T) { Viper: v, } - manager := &state.StateManager{ + manager := &state.Manager{ Logger: testLogger, FS: fakeFS, } diff --git a/pkg/lifecycle/render/config/resolver.go b/pkg/lifecycle/render/config/resolver.go index f429a8c3d..acccaa246 100644 --- a/pkg/lifecycle/render/config/resolver.go +++ b/pkg/lifecycle/render/config/resolver.go @@ -3,12 +3,13 @@ package config import ( "context" + "github.com/go-kit/kit/log" + "github.com/mitchellh/cli" "github.com/replicatedcom/ship/pkg/api" - "github.com/replicatedcom/ship/pkg/fs" "github.com/replicatedcom/ship/pkg/lifecycle/render/state" - "github.com/replicatedcom/ship/pkg/logger" "github.com/replicatedcom/ship/pkg/templates" "github.com/replicatedcom/ship/pkg/ui" + "github.com/spf13/afero" "github.com/spf13/viper" ) @@ -18,9 +19,9 @@ type Resolver interface { WithDaemon(d Daemon) Resolver } -func ResolverFromViper(v *viper.Viper) Resolver { +func NewResolver(logger log.Logger) Resolver { return &DaemonResolver{ - Logger: logger.FromViper(v), + Logger: logger, } } @@ -29,31 +30,60 @@ func (r *DaemonResolver) WithDaemon(d Daemon) Resolver { return r } -func DaemonFromViper(v *viper.Viper) Daemon { +func NewDaemon( + v *viper.Viper, + headless *HeadlessDaemon, + headed *ShipDaemon, +) Daemon { + if v.GetBool("headless") { + return headless + } + return headed +} - renderer := &APIConfigRenderer{ - Logger: logger.FromViper(v), +func NewRenderer( + logger log.Logger, + v *viper.Viper, + builderBuilder *templates.BuilderBuilder, +) *APIConfigRenderer { + return &APIConfigRenderer{ + Logger: logger, Viper: v, - BuilderBuilder: templates.BuilderBuilderFromViper(v), + BuilderBuilder: builderBuilder, } +} - if v.GetBool("headless") { - return &HeadlessDaemon{ - StateManager: state.ManagerFromViper(v), - Logger: logger.FromViper(v), - UI: ui.FromViper(v), - ConfigRenderer: renderer, - } +func NewHeadlessDaemon( + v *viper.Viper, + logger log.Logger, + renderer *APIConfigRenderer, + stateManager *state.Manager, +) *HeadlessDaemon { + return &HeadlessDaemon{ + StateManager: stateManager, + Logger: logger, + UI: ui.FromViper(v), + ConfigRenderer: renderer, } +} +func NewHeadedDaemon( + v *viper.Viper, + renderer *APIConfigRenderer, + stateManager *state.Manager, + logger log.Logger, + ui cli.Ui, + fs afero.Afero, +) *ShipDaemon { return &ShipDaemon{ - Logger: logger.FromViper(v), - Fs: fs.FromViper(v), - UI: ui.FromViper(v), - StateManager: state.ManagerFromViper(v), + Logger: logger, + Fs: fs, + UI: ui, + StateManager: stateManager, Viper: v, ConfigSaved: make(chan interface{}), MessageConfirmed: make(chan string, 1), ConfigRenderer: renderer, } + } diff --git a/pkg/lifecycle/render/docker/image.go b/pkg/lifecycle/render/docker/image.go index 6808fd2eb..1cc36551a 100644 --- a/pkg/lifecycle/render/docker/image.go +++ b/pkg/lifecycle/render/docker/image.go @@ -11,8 +11,6 @@ import ( "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/replicatedcom/ship/pkg/api" - "github.com/replicatedcom/ship/pkg/logger" - "github.com/spf13/viper" ) type PullURLResolver interface { @@ -25,9 +23,9 @@ type URLResolver struct { Logger log.Logger } -func URLResolverFromViper(v *viper.Viper) PullURLResolver { +func URLResolverFromViper(logger log.Logger) PullURLResolver { return &URLResolver{ - Logger: logger.FromViper(v), + Logger: logger, } } diff --git a/pkg/lifecycle/render/docker/save.go b/pkg/lifecycle/render/docker/save.go index 96ed037ec..afdf17c85 100644 --- a/pkg/lifecycle/render/docker/save.go +++ b/pkg/lifecycle/render/docker/save.go @@ -13,8 +13,6 @@ import ( docker "github.com/docker/docker/client" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" - "github.com/replicatedcom/ship/pkg/logger" - "github.com/spf13/viper" ) // ImageSaver saves an image @@ -48,16 +46,11 @@ type DockerSaver struct { client ImageManager } -func SaverFromViper(v *viper.Viper) (*DockerSaver, error) { - client, err := docker.NewEnvClient() - if err != nil { - return nil, errors.Wrap(err, "initialize docker client") - } - +func SaverFromViper(logger log.Logger, client *docker.Client) ImageSaver { return &DockerSaver{ - Logger: logger.FromViper(v), + Logger: logger, client: client, - }, nil + } } func (s *DockerSaver) SaveImage(ctx context.Context, saveOpts SaveOpts) chan interface{} { diff --git a/pkg/lifecycle/render/planner/build.go b/pkg/lifecycle/render/planner/build.go index 07f7b15ed..ec5e3e382 100644 --- a/pkg/lifecycle/render/planner/build.go +++ b/pkg/lifecycle/render/planner/build.go @@ -60,15 +60,13 @@ func (p *CLIPlanner) inlineStep(inline *api.InlineAsset, configGroups []libyaml. Execute: func(ctx context.Context) error { debug.Log("event", "execute") - configCtx, err := templates.NewConfigContext( - p.Viper, p.Logger, - configGroups, templateContext) + configCtx, err := templates.NewConfigContext(p.Logger, configGroups, templateContext) if err != nil { return errors.Wrap(err, "getting config context") } builder := p.BuilderBuilder.NewBuilder( - templates.NewStaticContext(), + p.BuilderBuilder.NewStaticContext(), configCtx, &templates.InstallationContext{ Meta: meta, diff --git a/pkg/lifecycle/render/planner/planner.go b/pkg/lifecycle/render/planner/planner.go index c8339934f..931da6f18 100644 --- a/pkg/lifecycle/render/planner/planner.go +++ b/pkg/lifecycle/render/planner/planner.go @@ -6,14 +6,10 @@ import ( "github.com/go-kit/kit/log" "github.com/mitchellh/cli" "github.com/replicatedcom/ship/pkg/api" - "github.com/replicatedcom/ship/pkg/fs" - "github.com/replicatedcom/ship/pkg/logger" - "github.com/replicatedcom/ship/pkg/ui" "github.com/replicatedhq/libyaml" "github.com/spf13/afero" "github.com/spf13/viper" - "github.com/pkg/errors" "github.com/replicatedcom/ship/pkg/lifecycle/render/config" "github.com/replicatedcom/ship/pkg/lifecycle/render/docker" "github.com/replicatedcom/ship/pkg/templates" @@ -57,21 +53,24 @@ type CLIPlanner struct { URLResolver docker.PullURLResolver } -func FromViper(v *viper.Viper) (Planner, error) { - saver, err := docker.SaverFromViper(v) - if err != nil { - return nil, errors.Wrap(err, "initialize docker saver") - } - +func NewPlanner( + v *viper.Viper, + logger log.Logger, + fs afero.Afero, + ui cli.Ui, + builderBuilder *templates.BuilderBuilder, + saver docker.ImageSaver, + urlResolver docker.PullURLResolver, +) Planner { return &CLIPlanner{ - Logger: logger.FromViper(v), - Fs: fs.FromViper(v), - UI: ui.FromViper(v), + Logger: logger, + Fs: fs, + UI: ui, Viper: v, - BuilderBuilder: templates.BuilderBuilderFromViper(v), + BuilderBuilder: builderBuilder, Saver: saver, - URLResolver: docker.URLResolverFromViper(v), - }, nil + URLResolver: urlResolver, + } } func (p *CLIPlanner) WithDaemon(d config.Daemon) Planner { diff --git a/pkg/lifecycle/render/render.go b/pkg/lifecycle/render/render.go index 810affdbb..2553fd50a 100644 --- a/pkg/lifecycle/render/render.go +++ b/pkg/lifecycle/render/render.go @@ -8,14 +8,10 @@ import ( "github.com/mitchellh/cli" "github.com/pkg/errors" "github.com/replicatedcom/ship/pkg/api" - "github.com/replicatedcom/ship/pkg/fs" "github.com/replicatedcom/ship/pkg/lifecycle/render/config" "github.com/replicatedcom/ship/pkg/lifecycle/render/planner" "github.com/replicatedcom/ship/pkg/lifecycle/render/state" - "github.com/replicatedcom/ship/pkg/logger" - "github.com/replicatedcom/ship/pkg/ui" "github.com/spf13/afero" - "github.com/spf13/viper" ) // StateFilePath is a placeholder for the default spot we'll store state. todo this should be a param or something @@ -34,27 +30,28 @@ type Renderer struct { Logger log.Logger ConfigResolver config.Resolver Planner planner.Planner - StateManager *state.StateManager + StateManager *state.Manager Fs afero.Afero UI cli.Ui Daemon config.Daemon } -func FromViper(v *viper.Viper) (*Renderer, error) { - - pln, err := planner.FromViper(v) - if err != nil { - return nil, errors.Wrap(err, "initialize planner") - } - +func NewRenderer( + logger log.Logger, + fs afero.Afero, + ui cli.Ui, + stateManager *state.Manager, + planner planner.Planner, + resolver config.Resolver, +) *Renderer { return &Renderer{ - Logger: logger.FromViper(v), - ConfigResolver: config.ResolverFromViper(v), - Planner: pln, - StateManager: state.ManagerFromViper(v), - Fs: fs.FromViper(v), - UI: ui.FromViper(v), - }, nil + Logger: logger, + ConfigResolver: resolver, + Planner: planner, + StateManager: stateManager, + Fs: fs, + UI: ui, + } } func (r *Renderer) WithDaemon(d config.Daemon) *Renderer { diff --git a/pkg/lifecycle/render/render_test.go b/pkg/lifecycle/render/render_test.go index 4c2cc39c7..9dddc6a66 100644 --- a/pkg/lifecycle/render/render_test.go +++ b/pkg/lifecycle/render/render_test.go @@ -53,7 +53,7 @@ func TestRender(t *testing.T) { renderer.UI = mockUI renderer.ConfigResolver = configResolver renderer.Planner = p - renderer.StateManager = &state.StateManager{ + renderer.StateManager = &state.Manager{ Logger: renderer.Logger, FS: mockFS, } diff --git a/pkg/lifecycle/render/state/serialize.go b/pkg/lifecycle/render/state/serialize.go index d0906e15e..637940c6c 100644 --- a/pkg/lifecycle/render/state/serialize.go +++ b/pkg/lifecycle/render/state/serialize.go @@ -10,27 +10,27 @@ import ( "github.com/go-kit/kit/log/level" "github.com/pkg/errors" "github.com/replicatedcom/ship/pkg/api" - "github.com/replicatedcom/ship/pkg/fs" - "github.com/replicatedcom/ship/pkg/logger" "github.com/spf13/afero" - "github.com/spf13/viper" ) -// StateManager is the saved output of a plan run to load on future runs -type StateManager struct { +// Manager is the saved output of a plan run to load on future runs +type Manager struct { Logger log.Logger FS afero.Afero } -func ManagerFromViper(v *viper.Viper) *StateManager { - return &StateManager{ - Logger: logger.FromViper(v), - FS: fs.FromViper(v), +func NewManager( + logger log.Logger, + fs afero.Afero, +) *Manager { + return &Manager{ + Logger: logger, + FS: fs, } } // Serialize takes the application data and input params and serializes a state file to disk -func (s StateManager) Serialize(assets []api.Asset, meta api.ReleaseMetadata, templateContext map[string]interface{}) error { +func (s Manager) Serialize(assets []api.Asset, meta api.ReleaseMetadata, templateContext map[string]interface{}) error { serialized, err := json.Marshal(templateContext) if err != nil { return errors.Wrap(err, "serialize state") @@ -49,7 +49,7 @@ func (s StateManager) Serialize(assets []api.Asset, meta api.ReleaseMetadata, te } // TryLoad will attempt to load a state file from disk, if present -func (s *StateManager) TryLoad() (map[string]interface{}, error) { +func (s *Manager) TryLoad() (map[string]interface{}, error) { if _, err := s.FS.Stat(Path); os.IsNotExist(err) { level.Debug(s.Logger).Log("msg", "no saved state exists", "path", Path) return make(map[string]interface{}), nil diff --git a/pkg/lifecycle/render/state/serialize_test.go b/pkg/lifecycle/render/state/serialize_test.go index 291e4565e..9cb4810ce 100644 --- a/pkg/lifecycle/render/state/serialize_test.go +++ b/pkg/lifecycle/render/state/serialize_test.go @@ -26,7 +26,7 @@ func TestTryLoad(t *testing.T) { templateContext := make(map[string]interface{}) templateContext["key"] = "value" - state := StateManager{ + state := Manager{ Logger: log.NewNopLogger(), FS: afero.Afero{Fs: afero.NewMemMapFs()}, } diff --git a/pkg/lifecycle/runner.go b/pkg/lifecycle/runner.go index 1d41d99ec..042d32190 100644 --- a/pkg/lifecycle/runner.go +++ b/pkg/lifecycle/runner.go @@ -9,11 +9,7 @@ import ( "github.com/go-kit/kit/log/level" "github.com/pkg/errors" "github.com/replicatedcom/ship/pkg/api" - "github.com/replicatedcom/ship/pkg/lifecycle/message" - "github.com/replicatedcom/ship/pkg/lifecycle/render" "github.com/replicatedcom/ship/pkg/lifecycle/render/config" - "github.com/replicatedcom/ship/pkg/logger" - "github.com/spf13/viper" ) // A Runner runs a lifecycle using the passed Spec @@ -55,35 +51,18 @@ type Runner struct { } */ -func RunnerFromViper(v *viper.Viper) (*Runner, error) { - executor, err := ExecutorFromViper(v) - if err != nil { - return nil, errors.Wrap(err, "initialize executor") - } - +func NewRunner(logger log.Logger, executor StepExecutor) *Runner { return &Runner{ - Logger: logger.FromViper(v), - Executor: executor, - }, nil + Logger: logger, + Executor: &executor, + } } + func (r *Runner) WithDaemon(d config.Daemon) *Runner { r.Executor = r.Executor.WithDaemon(d) return r } -func ExecutorFromViper(v *viper.Viper) (*StepExecutor, error) { - renderer, err := render.FromViper(v) - - if err != nil { - return nil, errors.Wrap(err, "initialize renderer") - } - - return &StepExecutor{ - Logger: logger.FromViper(v), - Renderer: renderer, - Messenger: message.FromViper(v), - }, nil -} func (e *StepExecutor) WithDaemon(d config.Daemon) *StepExecutor { e.Daemon = d e.Renderer = e.Renderer.WithDaemon(d) diff --git a/pkg/lifecycle/step.go b/pkg/lifecycle/step.go index 141abfe33..d824592b4 100644 --- a/pkg/lifecycle/step.go +++ b/pkg/lifecycle/step.go @@ -10,9 +10,12 @@ import ( "github.com/replicatedcom/ship/pkg/lifecycle/message" "github.com/replicatedcom/ship/pkg/lifecycle/render" "github.com/replicatedcom/ship/pkg/lifecycle/render/config" + "go.uber.org/dig" ) type StepExecutor struct { + dig.In + Logger log.Logger Renderer *render.Renderer Messenger message.Messenger diff --git a/pkg/ship/dig.go b/pkg/ship/dig.go new file mode 100644 index 000000000..74a47e992 --- /dev/null +++ b/pkg/ship/dig.go @@ -0,0 +1,93 @@ +package ship + +import ( + "context" + + dockercli "github.com/docker/docker/client" + "github.com/pkg/errors" + "github.com/replicatedcom/ship/pkg/fs" + "github.com/replicatedcom/ship/pkg/lifecycle" + "github.com/replicatedcom/ship/pkg/lifecycle/message" + "github.com/replicatedcom/ship/pkg/lifecycle/render" + "github.com/replicatedcom/ship/pkg/lifecycle/render/config" + "github.com/replicatedcom/ship/pkg/lifecycle/render/docker" + "github.com/replicatedcom/ship/pkg/lifecycle/render/planner" + "github.com/replicatedcom/ship/pkg/lifecycle/render/state" + "github.com/replicatedcom/ship/pkg/logger" + "github.com/replicatedcom/ship/pkg/specs" + "github.com/replicatedcom/ship/pkg/templates" + "github.com/replicatedcom/ship/pkg/ui" + "github.com/spf13/viper" + "go.uber.org/dig" +) + +func buildInjector() (*dig.Container, error) { + providers := []interface{}{ + + viper.GetViper, + logger.FromViper, + ui.FromViper, + fs.FromViper, + + templates.NewBuilderBuilder, + message.NewMessenger, + config.NewDaemon, + config.NewRenderer, + config.NewHeadedDaemon, + config.NewHeadlessDaemon, + config.NewResolver, + state.NewManager, + planner.NewPlanner, + render.NewRenderer, + specs.NewResolver, + specs.NewGraphqlClient, + lifecycle.NewRunner, + + docker.URLResolverFromViper, + docker.SaverFromViper, + dockercli.NewEnvClient, + + NewShip, + } + + container := dig.New() + + for _, provider := range providers { + err := container.Provide(provider) + if err != nil { + return nil, errors.Wrap(err, "register providers") + } + } + + return container, nil +} + +func Get() (*Ship, error) { + + injector, err := buildInjector() + if err != nil { + return nil, errors.Wrap(err, "build injector") + } + + var ship *Ship + + // we return nil below , so the error will only ever be a construction error + errorWhenConstructingShip := injector.Invoke(func(s *Ship) error { + ship = s + return nil + }) + + if errorWhenConstructingShip != nil { + return nil, errors.Wrap(err, "resolve dependencies") + } + return ship, nil +} + +func RunE(ctx context.Context) error { + s, err := Get() + if err != nil { + return err + } + s.ExecuteAndMaybeExit(ctx) + return nil +} diff --git a/pkg/ship/dig_test.go b/pkg/ship/dig_test.go new file mode 100644 index 000000000..946fe1212 --- /dev/null +++ b/pkg/ship/dig_test.go @@ -0,0 +1,25 @@ +package ship + +import ( + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/require" +) + +// Make sure we can get an instance of ship +func TestDI(t *testing.T) { + req := require.New(t) + viper.Set("headless", true) + viper.Set("customer-endpoint", "https://g.replicated.com") + + container, err := buildInjector() + req.NoError(err) + + err = container.Invoke(func(s *Ship) error { + // don't do anything with it, just make sure we can get one + return nil + }) + + req.NoError(err) +} diff --git a/pkg/ship/ship.go b/pkg/ship/ship.go index ce4e2c3c6..15c1ead29 100644 --- a/pkg/ship/ship.go +++ b/pkg/ship/ship.go @@ -4,20 +4,18 @@ import ( "context" "fmt" "os" + "time" + "os/signal" "syscall" - "time" - "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/mitchellh/cli" "github.com/pkg/errors" "github.com/replicatedcom/ship/pkg/lifecycle" "github.com/replicatedcom/ship/pkg/lifecycle/render/config" - pkglogger "github.com/replicatedcom/ship/pkg/logger" "github.com/replicatedcom/ship/pkg/specs" - "github.com/replicatedcom/ship/pkg/ui" "github.com/replicatedcom/ship/pkg/version" "github.com/spf13/viper" ) @@ -47,58 +45,38 @@ type Ship struct { Runner *lifecycle.Runner } -// FromViper gets an instance using viper to pull config -func FromViper(v *viper.Viper) (*Ship, error) { - logger := pkglogger.FromViper(v) - debug := level.Debug(log.With(logger, "phase", "ship.build", "source", "viper")) - - daemon := config.DaemonFromViper(v) - - debug.Log("event", "specresolver.build") - resolver, err := specs.ResolverFromViper(v) - if err != nil { - return nil, errors.Wrap(err, "get spec resolver") - } - - debug.Log("event", "graphqlclient.build") - graphql, err := specs.GraphQLClientFromViper(v) - if err != nil { - return nil, errors.Wrap(err, "get graphql client") - } - - debug.Log("event", "lifecycle.build") - runner, err := lifecycle.RunnerFromViper(v) - if err != nil { - return nil, errors.Wrap(err, "initialize lifecycle runner") - } - runner = runner.WithDaemon(daemon) +// NewShip gets an instance using viper to pull config +func NewShip( + logger log.Logger, + v *viper.Viper, + daemon config.Daemon, + resolver *specs.Resolver, + graphql *specs.GraphQLClient, + runner *lifecycle.Runner, + ui cli.Ui, +) (*Ship, error) { - debug.Log("event", "ui.build") return &Ship{ - Viper: v, - - Logger: logger, - Resolver: resolver, - Client: graphql, - - APIPort: v.GetInt("api-port"), - Headless: v.GetBool("headless"), - - CustomerID: v.GetString("customer-id"), - + APIPort: v.GetInt("api-port"), + Headless: v.GetBool("headless"), + CustomerID: v.GetString("customer-id"), ReleaseID: v.GetString("release-id"), ReleaseSemver: v.GetString("release-semver"), ChannelID: v.GetString("channel-id"), InstallationID: v.GetString("installation-id"), StudioFile: v.GetString("studio-file"), - Daemon: daemon, - UI: ui.FromViper(v), - Runner: runner, + Viper: v, + Logger: logger, + Resolver: resolver, + Client: graphql, + Daemon: daemon, + UI: ui, + Runner: runner.WithDaemon(daemon), }, nil } -func (s *Ship) shutdown(cancelFunc context.CancelFunc) { +func (s *Ship) Shutdown(cancelFunc context.CancelFunc) { // need to pause beforce canceling the context, because we need // the daemon to stay up for a few seconds so the UI can know its // time to show the "You're all done" page @@ -113,12 +91,18 @@ func (s *Ship) shutdown(cancelFunc context.CancelFunc) { level.Info(s.Logger).Log("event", "shutdown.complete") } -// Execute starts ship +// ExecuteAndMaybeExit runs ship to completion, and os.Exit()'s if it fails +func (s *Ship) ExecuteAndMaybeExit(ctx context.Context) { + if err := s.Execute(ctx); err != nil { + s.ExitWithError(err) + } +} + func (s *Ship) Execute(ctx context.Context) error { ctx, cancelFunc := context.WithCancel(ctx) - defer s.shutdown(cancelFunc) + defer s.Shutdown(cancelFunc) - debug := level.Debug(log.With(s.Logger, "method", "execute")) + debug := level.Debug(log.With(s.Logger, "method", "Execute")) debug.Log("method", "configure", "phase", "initialize", "version", version.Version(), @@ -180,10 +164,12 @@ func (s *Ship) Execute(ctx context.Context) error { select { case sig := <-signalChan: level.Info(s.Logger).Log("event", "shutdown", "reason", "signal", "signal", sig) - return nil + s.UI.Warn(fmt.Sprintf("%s received...", sig)) + return errors.New(fmt.Sprintf("received signal %s", sig)) case result := <-runResultCh: return result } + } // ExitWithError should be called by the parent cobra commands if something goes wrong. diff --git a/pkg/specs/graphql.go b/pkg/specs/graphql.go index 2bb48493c..901e4efee 100644 --- a/pkg/specs/graphql.go +++ b/pkg/specs/graphql.go @@ -125,8 +125,8 @@ func (r *ShipRelease) apiImages() []api.Image { return result } -// GraphQLClientFromViper builds a new client using a viper instance -func GraphQLClientFromViper(v *viper.Viper) (*GraphQLClient, error) { +// NewGraphqlClient builds a new client using a viper instance +func NewGraphqlClient(v *viper.Viper) (*GraphQLClient, error) { addr := v.GetString("customer-endpoint") server, err := url.ParseRequestURI(addr) if err != nil { diff --git a/pkg/specs/resolver.go b/pkg/specs/resolver.go index 57be63a98..685abd808 100644 --- a/pkg/specs/resolver.go +++ b/pkg/specs/resolver.go @@ -10,7 +10,6 @@ import ( "github.com/pkg/errors" "github.com/replicatedcom/ship/pkg/api" "github.com/replicatedcom/ship/pkg/lifecycle/render/state" - "github.com/replicatedcom/ship/pkg/logger" "github.com/spf13/viper" "gopkg.in/yaml.v2" ) @@ -34,26 +33,27 @@ type Selector struct { type Resolver struct { Logger log.Logger Client *GraphQLClient - StateManager *state.StateManager + StateManager *state.Manager StudioFile string StudioChannelName string StudioChannelIcon string } -// ResolverFromViper builds a resolver from a Viper instance -func ResolverFromViper(v *viper.Viper) (*Resolver, error) { - graphql, err := GraphQLClientFromViper(v) - if err != nil { - return nil, errors.Wrap(err, "get graphql client") - } +// NewResolver builds a resolver from a Viper instance +func NewResolver( + v *viper.Viper, + logger log.Logger, + graphql *GraphQLClient, + stateManager *state.Manager, +) *Resolver { return &Resolver{ - Logger: logger.FromViper(v), + Logger: logger, Client: graphql, - StateManager: state.ManagerFromViper(v), + StateManager: stateManager, StudioFile: v.GetString("studio-file"), StudioChannelName: v.GetString("studio-channel-name"), StudioChannelIcon: v.GetString("studio-channel-icon"), - }, nil + } } // ResolveRelease uses the passed config options to get specs from pg.replicated.com or diff --git a/pkg/specs/resolver_test.go b/pkg/specs/resolver_test.go index 335b8b6f7..9e47e4c08 100644 --- a/pkg/specs/resolver_test.go +++ b/pkg/specs/resolver_test.go @@ -13,7 +13,7 @@ import ( func TestPersistSpec(t *testing.T) { r := &Resolver{ - StateManager: &state.StateManager{ + StateManager: &state.Manager{ Logger: log.NewNopLogger(), FS: afero.Afero{Fs: afero.NewMemMapFs()}, }, diff --git a/pkg/templates/builder.go b/pkg/templates/builder.go index acd9284f1..cd30cc3aa 100644 --- a/pkg/templates/builder.go +++ b/pkg/templates/builder.go @@ -12,7 +12,6 @@ import ( "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" - "github.com/replicatedcom/ship/pkg/logger" "github.com/spf13/viper" ) @@ -31,9 +30,9 @@ type BuilderBuilder struct { Viper *viper.Viper } -func BuilderBuilderFromViper(v *viper.Viper) *BuilderBuilder { +func NewBuilderBuilder(logger log.Logger) *BuilderBuilder { return &BuilderBuilder{ - Logger: logger.FromViper(v), + Logger: logger, } } diff --git a/pkg/templates/config_context.go b/pkg/templates/config_context.go index 54c1cf21a..772ae106b 100644 --- a/pkg/templates/config_context.go +++ b/pkg/templates/config_context.go @@ -5,13 +5,10 @@ import ( "fmt" "text/template" - "github.com/replicatedcom/ship/pkg/lifecycle/render/state" - "github.com/replicatedhq/libyaml" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" - "github.com/spf13/viper" ) func (bb *BuilderBuilder) NewConfigContext( @@ -26,7 +23,6 @@ func (bb *BuilderBuilder) NewConfigContext( configCtx := &ConfigCtx{ ItemValues: templateContext, Logger: bb.Logger, - Viper: bb.Viper, } for _, configGroup := range configGroups { @@ -60,14 +56,13 @@ func (bb *BuilderBuilder) NewConfigContext( // Once we have state (for upgrades) it should be a parameter here. // deprecated -- use BuilderBuilder func NewConfigContext( - v *viper.Viper, logger log.Logger, configGroups []libyaml.ConfigGroup, templateContext map[string]interface{}, ) (*ConfigCtx, error) { // Get a static context to render static template functions - builderBuilder := BuilderBuilderFromViper(v) + builderBuilder := NewBuilderBuilder(logger) builder := builderBuilder.NewBuilder( builderBuilder.NewStaticContext(), ) @@ -75,7 +70,6 @@ func NewConfigContext( configCtx := &ConfigCtx{ ItemValues: templateContext, Logger: logger, - Viper: v, } for _, configGroup := range configGroups { @@ -109,7 +103,6 @@ func NewConfigContext( type ConfigCtx struct { ItemValues map[string]interface{} Logger log.Logger - Viper *viper.Viper } // FuncMap represents the available functions in the ConfigCtx. @@ -120,25 +113,6 @@ func (ctx ConfigCtx) FuncMap() template.FuncMap { "ConfigOptionData": ctx.configOptionData, "ConfigOptionEquals": ctx.configOptionEquals, "ConfigOptionNotEquals": ctx.configOptionNotEquals, - - // this should probably go somewhere else eventually - // Install should have all the details about this ship installation, - // including customer Id, customer name release notes, version, etc. - "Installation": ctx.Install, - // old, remove - "context": ctx.Install, - } -} - -func (ctx ConfigCtx) Install(name string) string { - switch name { - case "state_file_path": - return state.Path - case "customer_id": - return ctx.Viper.GetString("customer-id") - default: - level.Warn(ctx.Logger).Log("event", "ConfigCtx.context.unsuppported", "name", name) - return "" } } diff --git a/vendor/github.com/Microsoft/go-winio/ea.go b/vendor/github.com/Microsoft/go-winio/ea.go index 4051c1b33..b37e930d6 100644 --- a/vendor/github.com/Microsoft/go-winio/ea.go +++ b/vendor/github.com/Microsoft/go-winio/ea.go @@ -1,137 +1,137 @@ -package winio - -import ( - "bytes" - "encoding/binary" - "errors" -) - -type fileFullEaInformation struct { - NextEntryOffset uint32 - Flags uint8 - NameLength uint8 - ValueLength uint16 -} - -var ( - fileFullEaInformationSize = binary.Size(&fileFullEaInformation{}) - - errInvalidEaBuffer = errors.New("invalid extended attribute buffer") - errEaNameTooLarge = errors.New("extended attribute name too large") - errEaValueTooLarge = errors.New("extended attribute value too large") -) - -// ExtendedAttribute represents a single Windows EA. -type ExtendedAttribute struct { - Name string - Value []byte - Flags uint8 -} - -func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) { - var info fileFullEaInformation - err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info) - if err != nil { - err = errInvalidEaBuffer - return - } - - nameOffset := fileFullEaInformationSize - nameLen := int(info.NameLength) - valueOffset := nameOffset + int(info.NameLength) + 1 - valueLen := int(info.ValueLength) - nextOffset := int(info.NextEntryOffset) - if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) { - err = errInvalidEaBuffer - return - } - - ea.Name = string(b[nameOffset : nameOffset+nameLen]) - ea.Value = b[valueOffset : valueOffset+valueLen] - ea.Flags = info.Flags - if info.NextEntryOffset != 0 { - nb = b[info.NextEntryOffset:] - } - return -} - -// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION -// buffer retrieved from BackupRead, ZwQueryEaFile, etc. -func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) { - for len(b) != 0 { - ea, nb, err := parseEa(b) - if err != nil { - return nil, err - } - - eas = append(eas, ea) - b = nb - } - return -} - -func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error { - if int(uint8(len(ea.Name))) != len(ea.Name) { - return errEaNameTooLarge - } - if int(uint16(len(ea.Value))) != len(ea.Value) { - return errEaValueTooLarge - } - entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value)) - withPadding := (entrySize + 3) &^ 3 - nextOffset := uint32(0) - if !last { - nextOffset = withPadding - } - info := fileFullEaInformation{ - NextEntryOffset: nextOffset, - Flags: ea.Flags, - NameLength: uint8(len(ea.Name)), - ValueLength: uint16(len(ea.Value)), - } - - err := binary.Write(buf, binary.LittleEndian, &info) - if err != nil { - return err - } - - _, err = buf.Write([]byte(ea.Name)) - if err != nil { - return err - } - - err = buf.WriteByte(0) - if err != nil { - return err - } - - _, err = buf.Write(ea.Value) - if err != nil { - return err - } - - _, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize]) - if err != nil { - return err - } - - return nil -} - -// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION -// buffer for use with BackupWrite, ZwSetEaFile, etc. -func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) { - var buf bytes.Buffer - for i := range eas { - last := false - if i == len(eas)-1 { - last = true - } - - err := writeEa(&buf, &eas[i], last) - if err != nil { - return nil, err - } - } - return buf.Bytes(), nil -} +package winio + +import ( + "bytes" + "encoding/binary" + "errors" +) + +type fileFullEaInformation struct { + NextEntryOffset uint32 + Flags uint8 + NameLength uint8 + ValueLength uint16 +} + +var ( + fileFullEaInformationSize = binary.Size(&fileFullEaInformation{}) + + errInvalidEaBuffer = errors.New("invalid extended attribute buffer") + errEaNameTooLarge = errors.New("extended attribute name too large") + errEaValueTooLarge = errors.New("extended attribute value too large") +) + +// ExtendedAttribute represents a single Windows EA. +type ExtendedAttribute struct { + Name string + Value []byte + Flags uint8 +} + +func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) { + var info fileFullEaInformation + err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info) + if err != nil { + err = errInvalidEaBuffer + return + } + + nameOffset := fileFullEaInformationSize + nameLen := int(info.NameLength) + valueOffset := nameOffset + int(info.NameLength) + 1 + valueLen := int(info.ValueLength) + nextOffset := int(info.NextEntryOffset) + if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) { + err = errInvalidEaBuffer + return + } + + ea.Name = string(b[nameOffset : nameOffset+nameLen]) + ea.Value = b[valueOffset : valueOffset+valueLen] + ea.Flags = info.Flags + if info.NextEntryOffset != 0 { + nb = b[info.NextEntryOffset:] + } + return +} + +// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION +// buffer retrieved from BackupRead, ZwQueryEaFile, etc. +func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) { + for len(b) != 0 { + ea, nb, err := parseEa(b) + if err != nil { + return nil, err + } + + eas = append(eas, ea) + b = nb + } + return +} + +func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error { + if int(uint8(len(ea.Name))) != len(ea.Name) { + return errEaNameTooLarge + } + if int(uint16(len(ea.Value))) != len(ea.Value) { + return errEaValueTooLarge + } + entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value)) + withPadding := (entrySize + 3) &^ 3 + nextOffset := uint32(0) + if !last { + nextOffset = withPadding + } + info := fileFullEaInformation{ + NextEntryOffset: nextOffset, + Flags: ea.Flags, + NameLength: uint8(len(ea.Name)), + ValueLength: uint16(len(ea.Value)), + } + + err := binary.Write(buf, binary.LittleEndian, &info) + if err != nil { + return err + } + + _, err = buf.Write([]byte(ea.Name)) + if err != nil { + return err + } + + err = buf.WriteByte(0) + if err != nil { + return err + } + + _, err = buf.Write(ea.Value) + if err != nil { + return err + } + + _, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize]) + if err != nil { + return err + } + + return nil +} + +// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION +// buffer for use with BackupWrite, ZwSetEaFile, etc. +func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) { + var buf bytes.Buffer + for i := range eas { + last := false + if i == len(eas)-1 { + last = true + } + + err := writeEa(&buf, &eas[i], last) + if err != nil { + return nil, err + } + } + return buf.Bytes(), nil +} diff --git a/vendor/github.com/Microsoft/go-winio/ea_test.go b/vendor/github.com/Microsoft/go-winio/ea_test.go index 27db14ff8..92d9d4572 100644 --- a/vendor/github.com/Microsoft/go-winio/ea_test.go +++ b/vendor/github.com/Microsoft/go-winio/ea_test.go @@ -1,89 +1,89 @@ -package winio - -import ( - "io/ioutil" - "os" - "reflect" - "syscall" - "testing" - "unsafe" -) - -var ( - testEas = []ExtendedAttribute{ - {Name: "foo", Value: []byte("bar")}, - {Name: "fizz", Value: []byte("buzz")}, - } - - testEasEncoded = []byte{16, 0, 0, 0, 0, 3, 3, 0, 102, 111, 111, 0, 98, 97, 114, 0, 0, 0, 0, 0, 0, 4, 4, 0, 102, 105, 122, 122, 0, 98, 117, 122, 122, 0, 0, 0} - testEasNotPadded = testEasEncoded[0 : len(testEasEncoded)-3] - testEasTruncated = testEasEncoded[0:20] -) - -func Test_RoundTripEas(t *testing.T) { - b, err := EncodeExtendedAttributes(testEas) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(testEasEncoded, b) { - t.Fatalf("encoded mismatch %v %v", testEasEncoded, b) - } - eas, err := DecodeExtendedAttributes(b) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(testEas, eas) { - t.Fatalf("mismatch %+v %+v", testEas, eas) - } -} - -func Test_EasDontNeedPaddingAtEnd(t *testing.T) { - eas, err := DecodeExtendedAttributes(testEasNotPadded) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(testEas, eas) { - t.Fatalf("mismatch %+v %+v", testEas, eas) - } -} - -func Test_TruncatedEasFailCorrectly(t *testing.T) { - _, err := DecodeExtendedAttributes(testEasTruncated) - if err == nil { - t.Fatal("expected error") - } -} - -func Test_NilEasEncodeAndDecodeAsNil(t *testing.T) { - b, err := EncodeExtendedAttributes(nil) - if err != nil { - t.Fatal(err) - } - if len(b) != 0 { - t.Fatal("expected empty") - } - eas, err := DecodeExtendedAttributes(nil) - if err != nil { - t.Fatal(err) - } - if len(eas) != 0 { - t.Fatal("expected empty") - } -} - -// Test_SetFileEa makes sure that the test buffer is actually parsable by NtSetEaFile. -func Test_SetFileEa(t *testing.T) { - f, err := ioutil.TempFile("", "winio") - if err != nil { - t.Fatal(err) - } - defer os.Remove(f.Name()) - defer f.Close() - ntdll := syscall.MustLoadDLL("ntdll.dll") - ntSetEaFile := ntdll.MustFindProc("NtSetEaFile") - var iosb [2]uintptr - r, _, _ := ntSetEaFile.Call(f.Fd(), uintptr(unsafe.Pointer(&iosb[0])), uintptr(unsafe.Pointer(&testEasEncoded[0])), uintptr(len(testEasEncoded))) - if r != 0 { - t.Fatalf("NtSetEaFile failed with %08x", r) - } -} +package winio + +import ( + "io/ioutil" + "os" + "reflect" + "syscall" + "testing" + "unsafe" +) + +var ( + testEas = []ExtendedAttribute{ + {Name: "foo", Value: []byte("bar")}, + {Name: "fizz", Value: []byte("buzz")}, + } + + testEasEncoded = []byte{16, 0, 0, 0, 0, 3, 3, 0, 102, 111, 111, 0, 98, 97, 114, 0, 0, 0, 0, 0, 0, 4, 4, 0, 102, 105, 122, 122, 0, 98, 117, 122, 122, 0, 0, 0} + testEasNotPadded = testEasEncoded[0 : len(testEasEncoded)-3] + testEasTruncated = testEasEncoded[0:20] +) + +func Test_RoundTripEas(t *testing.T) { + b, err := EncodeExtendedAttributes(testEas) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(testEasEncoded, b) { + t.Fatalf("encoded mismatch %v %v", testEasEncoded, b) + } + eas, err := DecodeExtendedAttributes(b) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(testEas, eas) { + t.Fatalf("mismatch %+v %+v", testEas, eas) + } +} + +func Test_EasDontNeedPaddingAtEnd(t *testing.T) { + eas, err := DecodeExtendedAttributes(testEasNotPadded) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(testEas, eas) { + t.Fatalf("mismatch %+v %+v", testEas, eas) + } +} + +func Test_TruncatedEasFailCorrectly(t *testing.T) { + _, err := DecodeExtendedAttributes(testEasTruncated) + if err == nil { + t.Fatal("expected error") + } +} + +func Test_NilEasEncodeAndDecodeAsNil(t *testing.T) { + b, err := EncodeExtendedAttributes(nil) + if err != nil { + t.Fatal(err) + } + if len(b) != 0 { + t.Fatal("expected empty") + } + eas, err := DecodeExtendedAttributes(nil) + if err != nil { + t.Fatal(err) + } + if len(eas) != 0 { + t.Fatal("expected empty") + } +} + +// Test_SetFileEa makes sure that the test buffer is actually parsable by NtSetEaFile. +func Test_SetFileEa(t *testing.T) { + f, err := ioutil.TempFile("", "winio") + if err != nil { + t.Fatal(err) + } + defer os.Remove(f.Name()) + defer f.Close() + ntdll := syscall.MustLoadDLL("ntdll.dll") + ntSetEaFile := ntdll.MustFindProc("NtSetEaFile") + var iosb [2]uintptr + r, _, _ := ntSetEaFile.Call(f.Fd(), uintptr(unsafe.Pointer(&iosb[0])), uintptr(unsafe.Pointer(&testEasEncoded[0])), uintptr(len(testEasEncoded))) + if r != 0 { + t.Fatalf("NtSetEaFile failed with %08x", r) + } +} diff --git a/vendor/github.com/armon/go-radix/radix_test.go b/vendor/github.com/armon/go-radix/radix_test.go index 285369379..a7a4eab33 100644 --- a/vendor/github.com/armon/go-radix/radix_test.go +++ b/vendor/github.com/armon/go-radix/radix_test.go @@ -106,18 +106,18 @@ func TestDelete(t *testing.T) { func TestDeletePrefix(t *testing.T) { type exp struct { - inp []string - prefix string - out []string + inp[] string + prefix string + out[] string numDeleted int } cases := []exp{ - {[]string{"", "A", "AB", "ABC", "R", "S"}, "A", []string{"", "R", "S"}, 3}, - {[]string{"", "A", "AB", "ABC", "R", "S"}, "ABC", []string{"", "A", "AB", "R", "S"}, 1}, - {[]string{"", "A", "AB", "ABC", "R", "S"}, "", []string{}, 6}, - {[]string{"", "A", "AB", "ABC", "R", "S"}, "S", []string{"", "A", "AB", "ABC", "R"}, 1}, - {[]string{"", "A", "AB", "ABC", "R", "S"}, "SS", []string{"", "A", "AB", "ABC", "R", "S"}, 0}, + {[]string{"", "A", "AB", "ABC", "R", "S"}, "A", []string{"", "R", "S"}, 3}, + {[]string{"", "A", "AB", "ABC", "R", "S"}, "ABC", []string{"", "A", "AB", "R", "S"}, 1}, + {[]string{"", "A", "AB", "ABC", "R", "S"}, "", []string{}, 6}, + {[]string{"", "A", "AB", "ABC", "R", "S"}, "S", []string{"", "A", "AB", "ABC", "R"}, 1}, + {[]string{"", "A", "AB", "ABC", "R", "S"}, "SS", []string{"", "A", "AB", "ABC", "R", "S"}, 0}, } for _, test := range cases { diff --git a/vendor/go.uber.org/dig/.codecov.yml b/vendor/go.uber.org/dig/.codecov.yml new file mode 100644 index 000000000..c474df1c0 --- /dev/null +++ b/vendor/go.uber.org/dig/.codecov.yml @@ -0,0 +1,19 @@ +coverage: + range: 70..98 + round: down + precision: 2 + + status: + project: # measuring the overall project coverage + default: # context, you can create multiple ones with custom titles + enabled: yes # must be yes|true to enable this status + target: 98 # specify the target coverage for each commit status + # option: "auto" (must increase from parent commit or pull request base) + # option: "X%" a static target percentage to hit + if_not_found: success # if parent is not found report status as success, error, or failure + if_ci_failed: error # if ci fails report status as success, error, or failure + + patch: + default: + enabled: yes + target: 70 diff --git a/vendor/go.uber.org/dig/.gitignore b/vendor/go.uber.org/dig/.gitignore new file mode 100644 index 000000000..23249243e --- /dev/null +++ b/vendor/go.uber.org/dig/.gitignore @@ -0,0 +1,11 @@ +/vendor +/.bench +*.mem +*.cpu +*.test +*.log +*.out +*.html +*.coverprofile +coverage.txt +*.pprof diff --git a/vendor/go.uber.org/dig/.travis.yml b/vendor/go.uber.org/dig/.travis.yml new file mode 100644 index 000000000..4bc44b7ec --- /dev/null +++ b/vendor/go.uber.org/dig/.travis.yml @@ -0,0 +1,19 @@ +language: go +sudo: false +go_import_path: go.uber.org/dig + +matrix: + include: + - go: 1.7 + - go: 1.8 + - go: 1.9 + env: LINT=1 + +cache: + directories: + - vendor +install: + - make dependencies +script: + - test "$LINT" -eq 1 && make lint || echo "Skipping lint" + - make ci diff --git a/vendor/go.uber.org/dig/CHANGELOG.md b/vendor/go.uber.org/dig/CHANGELOG.md new file mode 100644 index 000000000..f4ba924a2 --- /dev/null +++ b/vendor/go.uber.org/dig/CHANGELOG.md @@ -0,0 +1,88 @@ +# Changelog + +## v1.3.0 (2017-12-04) + +- Improved messages for errors thrown by Dig under a many scenarios to be more + informative. + +## v1.2.0 (2017-11-07) + +- `dig.In` and `dig.Out` now support value groups, making it possible to + produce many values of the same type from different constructors. See package + documentation for more information. + +## v1.1.0 (2017-09-15) + +- Added the `dig.RootCause` function which allows retrieving the original + constructor error that caused an `Invoke` failure. +- Errors from `Invoke` now attempt to hint to the user a presence of a similar + type, for example a pointer to the requested type and vice versa. + +## v1.0.0 (2017-07-31) + +First stable release: no breaking changes will be made in the 1.x series. + +- `Provide` and `Invoke` will now fail if `dig.In` or `dig.Out` structs + contain unexported fields. Previously these fields were ignored which often + led to confusion. + +## v1.0.0-rc2 (2017-07-21) + +- Added variadic options to all public APIS so that new functionality can be + introduced post v1.0.0 without introducing breaking changes. +- Functions with variadic arguments can now be passed to `dig.Provide` and + `dig.Invoke`. Previously this caused an error, whereas now the args will be ignored. +- Exported `dig.IsIn` and `dig.IsOut` so that consuming libraries can check if + a params or return struct embeds the `dig.In` and `dig.Out` types, respectively. + +## v1.0.0-rc1 (2017-06-21) + +- First release candidate. + +## v0.5.0 (2017-06-19) + +- `dig.In` and `dig.Out` now support named instances, i.e.: + + ```go + type param struct { + dig.In + + DB1 DB.Connection `name:"primary"` + DB2 DB.Connection `name:"secondary"` + } + ``` + +- Structs compatible with `dig.In` and `dig.Out` may now be generated using + `reflect.StructOf`. + +## v0.4.0 (2017-06-12) + +- **[Breaking]** Remove `Must*` funcs to greatly reduce API surface area. +- **[Breaking]** Restrict the API surface to only `Provide` and `Invoke`. +- Providing constructors with common returned types results in an error. +- **[Breaking]** Update `Provide` method to accept variadic arguments. +- Add `dig.In` embeddable type for advanced use-cases of specifying dependencies. +- Add `dig.Out` embeddable type for advanced use-cases of constructors + inserting types in the container. +- Add support for optional parameters through `optional:"true"` tag on `dig.In` objects. +- Add support for value types and many built-ins (maps, slices, channels). + +## v0.3 (2017-05-02) + +- Rename `RegisterAll` and `MustRegisterAll` to `ProvideAll` and + `MustProvideAll`. +- Add functionality to `Provide` to support constructor with `n` return + objects to be resolved into the `dig.Graph` +- Add `Invoke` function to invoke provided function and insert return + objects into the `dig.Graph` + +## v0.2 (2017-03-27) + +- Rename `Register` to `Provide` for clarity and to recude clash with other + Register functions. +- Rename `dig.Graph` to `dig.Container`. +- Remove the package-level functions and the `DefaultGraph`. + +## v0.1 (2017-03-23) + +Initial release. diff --git a/vendor/go.uber.org/dig/LICENSE b/vendor/go.uber.org/dig/LICENSE new file mode 100644 index 000000000..858e02475 --- /dev/null +++ b/vendor/go.uber.org/dig/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2017 Uber Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/go.uber.org/dig/Makefile b/vendor/go.uber.org/dig/Makefile new file mode 100644 index 000000000..222da5845 --- /dev/null +++ b/vendor/go.uber.org/dig/Makefile @@ -0,0 +1,46 @@ +BENCH_FLAGS ?= -cpuprofile=cpu.pprof -memprofile=mem.pprof -benchmem +PKGS ?= $(shell glide novendor | grep -v examples) +PKG_FILES ?= *.go +GO_VERSION := $(shell go version | cut -d " " -f 3) + +.PHONY: all +all: lint test + +.PHONY: dependencies +dependencies: + @echo "Installing Glide and locked dependencies..." + glide --version || go get -u -f github.com/Masterminds/glide + glide install + @echo "Installing uber-license tool..." + update-license || go get -u -f go.uber.org/tools/update-license + +.PHONY: lint +lint: + @rm -rf lint.log + @echo "Checking formatting..." + @gofmt -d -s $(PKG_FILES) 2>&1 | tee lint.log + @echo "Installing test dependencies for vet..." + @go test -i $(PKGS) + @echo "Checking vet..." + @$(foreach dir,$(PKG_FILES),go tool vet $(VET_RULES) $(dir) 2>&1 | tee -a lint.log;) + @echo "Checking lint..." + @$(foreach dir,$(PKGS),golint $(dir) 2>&1 | tee -a lint.log;) + @echo "Checking for unresolved FIXMEs..." + @git grep -i fixme | grep -v -e vendor -e Makefile | tee -a lint.log + @echo "Checking for license headers..." + @DRY_RUN=1 ./check_license.sh | tee -a lint.log + @[ ! -s lint.log ] + +.PHONY: test +test: + @.build/test.sh + +.PHONY: ci +ci: SHELL := /bin/bash +ci: test + bash <(curl -s https://codecov.io/bash) + +.PHONY: bench +BENCH ?= . +bench: + @$(foreach pkg,$(PKGS),go test -bench=$(BENCH) -run="^$$" $(BENCH_FLAGS) $(pkg);) diff --git a/vendor/go.uber.org/dig/README.md b/vendor/go.uber.org/dig/README.md new file mode 100644 index 000000000..f23fa1acb --- /dev/null +++ b/vendor/go.uber.org/dig/README.md @@ -0,0 +1,43 @@ +# :hammer: dig [![GoDoc][doc-img]][doc] [![GitHub release][release-img]][release] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![Go Report Card][report-card-img]][report-card] + +A reflection based dependency injection toolkit for Go. + +### Good for: + +* Powering an application framework, e.g. [Fx](https://github.com/uber-go/fx). +* Resolving the object graph during process startup. + +### Bad for: + +* Using in place of an application framework, e.g. [Fx](https://github.com/uber-go/fx). +* Resolving dependencies after the process has already started. +* Exposing to user-land code as a [Service Locator](https://martinfowler.com/articles/injection.html#UsingAServiceLocator). + +## Installation + +We recommend locking to [SemVer](http://semver.org/) range `^1` using [Glide](https://github.com/Masterminds/glide): + +``` +glide get 'go.uber.org/dig#^1' +``` + +## Stability + +This library is `v1` and follows [SemVer](http://semver.org/) strictly. + +No breaking changes will be made to exported APIs before `v2.0.0`. + +[doc-img]: http://img.shields.io/badge/GoDoc-Reference-blue.svg +[doc]: https://godoc.org/go.uber.org/dig + +[release-img]: https://img.shields.io/github/release/uber-go/dig.svg +[release]: https://github.com/uber-go/dig/releases + +[ci-img]: https://img.shields.io/travis/uber-go/dig/master.svg +[ci]: https://travis-ci.org/uber-go/dig/branches + +[cov-img]: https://codecov.io/gh/uber-go/dig/branch/master/graph/badge.svg +[cov]: https://codecov.io/gh/uber-go/dig/branch/master + +[report-card-img]: https://goreportcard.com/badge/github.com/uber-go/dig +[report-card]: https://goreportcard.com/report/github.com/uber-go/dig diff --git a/vendor/go.uber.org/dig/check_license.sh b/vendor/go.uber.org/dig/check_license.sh new file mode 100755 index 000000000..e8d9f4b58 --- /dev/null +++ b/vendor/go.uber.org/dig/check_license.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -eo pipefail + +run_update_license() { + # doing this because of SC2046 warning + for file in $(find . -name '*.go' | grep -v \.\/vendor); do + update-license $@ "${file}" + done +} + +if [ -z "${DRY_RUN}" ]; then + run_update_license +else + DRY_OUTPUT="$(run_update_license --dry)" + if [ -n "${DRY_OUTPUT}" ]; then + echo "The following files do not have correct license headers." + echo "Please run make license and amend your commit." + echo + echo "${DRY_OUTPUT}" + exit 1 + fi +fi diff --git a/vendor/go.uber.org/dig/cycle.go b/vendor/go.uber.org/dig/cycle.go new file mode 100644 index 000000000..779663ae4 --- /dev/null +++ b/vendor/go.uber.org/dig/cycle.go @@ -0,0 +1,107 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package dig + +import ( + "bytes" + "fmt" + + "go.uber.org/dig/internal/digreflect" +) + +type cycleEntry struct { + Key key + Func *digreflect.Func +} + +type errCycleDetected struct { + Path []cycleEntry +} + +func (e errCycleDetected) Error() string { + // We get something like, + // + // foo provided by "path/to/package".NewFoo (path/to/file.go:42) + // depends on bar provided by "another/package".NewBar (somefile.go:1) + // depends on baz provided by "somepackage".NewBar (anotherfile.go:2) + // depends on foo provided by "path/to/package".NewFoo (path/to/file.go:42) + // + b := new(bytes.Buffer) + + for i, entry := range e.Path { + if i > 0 { + b.WriteString("\n\tdepends on ") + } + fmt.Fprintf(b, "%v provided by %v", entry.Key, entry.Func) + } + return b.String() +} + +func verifyAcyclic(c *Container, n *node, k key) error { + err := detectCycles(n, c.providers, []cycleEntry{ + {Key: k, Func: n.Func}, + }) + if err != nil { + err = errWrapf(err, "this function introduces a cycle") + } + return err +} + +func detectCycles(n *node, graph map[key][]*node, path []cycleEntry) error { + var err error + walkParam(n.Params, paramVisitorFunc(func(param param) bool { + if err != nil { + return false + } + + var k key + switch p := param.(type) { + case paramSingle: + k = key{name: p.Name, t: p.Type} + case paramGroupedSlice: + // NOTE: The key uses the element type, not the slice type. + k = key{group: p.Group, t: p.Type.Elem()} + default: + // Recurse for non-edge params. + return true + } + + entry := cycleEntry{Func: n.Func, Key: k} + + for _, p := range path { + if p.Key == k { + err = errCycleDetected{Path: append(path, entry)} + return false + } + } + + for _, n := range graph[k] { + if e := detectCycles(n, graph, append(path, entry)); e != nil { + err = e + return false + } + } + + return true + })) + + return err +} diff --git a/vendor/go.uber.org/dig/dig.go b/vendor/go.uber.org/dig/dig.go new file mode 100644 index 000000000..8542ebf46 --- /dev/null +++ b/vendor/go.uber.org/dig/dig.go @@ -0,0 +1,487 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package dig + +import ( + "errors" + "fmt" + "math/rand" + "reflect" + "strconv" + "strings" + "time" + + "go.uber.org/dig/internal/digreflect" +) + +const ( + _optionalTag = "optional" + _nameTag = "name" + _groupTag = "group" +) + +// Unique identification of an object in the graph. +type key struct { + t reflect.Type + + // Only one of name or group will be set. + name string + group string +} + +// Option configures a Container. It's included for future functionality; +// currently, there are no concrete implementations. +type Option interface { + applyOption(*Container) +} + +type optionFunc func(*Container) + +func (f optionFunc) applyOption(c *Container) { f(c) } + +// A ProvideOption modifies the default behavior of Provide. It's included for +// future functionality; currently, there are no concrete implementations. +type ProvideOption interface { + unimplemented() +} + +// An InvokeOption modifies the default behavior of Invoke. It's included for +// future functionality; currently, there are no concrete implementations. +type InvokeOption interface { + unimplemented() +} + +// Container is a directed acyclic graph of types and their dependencies. +type Container struct { + // Mapping from key to all the nodes that can provide a value for that + // key. + providers map[key][]*node + + // Values that have already been generated in the container. + values map[key]reflect.Value + + // Values groups that have already been generated in the container. + groups map[key][]reflect.Value + + // Source of randomness. + rand *rand.Rand +} + +// New constructs a Container. +func New(opts ...Option) *Container { + c := &Container{ + providers: make(map[key][]*node), + values: make(map[key]reflect.Value), + groups: make(map[key][]reflect.Value), + rand: rand.New(rand.NewSource(time.Now().UnixNano())), + } + + for _, opt := range opts { + opt.applyOption(c) + } + return c +} + +// Changes the source of randomness for the container. +// +// This will help provide determinism during tests. +func setRand(r *rand.Rand) Option { + return optionFunc(func(c *Container) { + c.rand = r + }) +} + +// Provide teaches the container how to build values of one or more types and +// expresses their dependencies. +// +// The first argument of Provide is a function that accepts zero or more +// parameters and returns one or more results. The function may optionally +// return an error to indicate that it failed to build the value. This +// function will be treated as the constructor for all the types it returns. +// This function will be called AT MOST ONCE when a type produced by it, or a +// type that consumes this function's output, is requested via Invoke. If the +// same types are requested multiple times, the previously produced value will +// be reused. +// +// In addition to accepting constructors that accept dependencies as separate +// arguments and produce results as separate return values, Provide also +// accepts constructors that specify dependencies as dig.In structs and/or +// specify results as dig.Out structs. +func (c *Container) Provide(constructor interface{}, opts ...ProvideOption) error { + ctype := reflect.TypeOf(constructor) + if ctype == nil { + return errors.New("can't provide an untyped nil") + } + if ctype.Kind() != reflect.Func { + return fmt.Errorf("must provide constructor function, got %v (type %v)", constructor, ctype) + } + if err := c.provide(constructor); err != nil { + return errProvide{ + Func: digreflect.InspectFunc(constructor), + Reason: err, + } + } + return nil +} + +// Invoke runs the given function after instantiating its dependencies. +// +// Any arguments that the function has are treated as its dependencies. The +// dependencies are instantiated in an unspecified order along with any +// dependencies that they might have. +// +// The function may return an error to indicate failure. The error will be +// returned to the caller as-is. +func (c *Container) Invoke(function interface{}, opts ...InvokeOption) error { + ftype := reflect.TypeOf(function) + if ftype == nil { + return errors.New("can't invoke an untyped nil") + } + if ftype.Kind() != reflect.Func { + return fmt.Errorf("can't invoke non-function %v (type %v)", function, ftype) + } + + pl, err := newParamList(ftype) + if err != nil { + return err + } + + if err := shallowCheckDependencies(c, pl); err != nil { + return errMissingDependencies{Func: digreflect.InspectFunc(function), Reason: err} + } + + args, err := pl.BuildList(c) + if err != nil { + return errArgumentsFailed{ + Func: digreflect.InspectFunc(function), + Reason: err, + } + } + + returned := reflect.ValueOf(function).Call(args) + if len(returned) == 0 { + return nil + } + if last := returned[len(returned)-1]; isError(last.Type()) { + if err, _ := last.Interface().(error); err != nil { + return err + } + } + return nil +} + +func (c *Container) provide(ctor interface{}) error { + n, err := newNode(ctor) + if err != nil { + return err + } + + keys, err := c.findAndValidateResults(n) + if err != nil { + return err + } + + ctype := reflect.TypeOf(ctor) + if len(keys) == 0 { + return fmt.Errorf("%v must provide at least one non-error type", ctype) + } + + for k := range keys { + oldProducers := c.providers[k] + c.providers[k] = append(oldProducers, n) + if err := verifyAcyclic(c, n, k); err != nil { + c.providers[k] = oldProducers + return err + } + } + + return nil +} + +// Builds a collection of all result types produced by this node. +func (c *Container) findAndValidateResults(n *node) (map[key]struct{}, error) { + var err error + keyPaths := make(map[key]string) + walkResult(n.Results, connectionVisitor{ + c: c, + n: n, + err: &err, + keyPaths: keyPaths, + }) + + if err != nil { + return nil, err + } + + keys := make(map[key]struct{}, len(keyPaths)) + for k := range keyPaths { + keys[k] = struct{}{} + } + return keys, nil +} + +// Visits the results of a node and compiles a collection of all the keys +// produced by that node. +type connectionVisitor struct { + c *Container + n *node + + // If this points to a non-nil value, we've already encountered an error + // and should stop traversing. + err *error + + // Map of keys provided to path that provided this. The path is a string + // documenting which positional return value or dig.Out attribute is + // providing this particular key. + // + // For example, "[0].Foo" indicates that the value was provided by the Foo + // attribute of the dig.Out returned as the first result of the + // constructor. + keyPaths map[key]string + + // We track the path to the current result here. For example, this will + // be, ["[1]", "Foo", "Bar"] when we're visiting Bar in, + // + // func() (io.Writer, struct { + // dig.Out + // + // Foo struct { + // dig.Out + // + // Bar io.Reader + // } + // }) + currentResultPath []string +} + +func (cv connectionVisitor) AnnotateWithField(f resultObjectField) resultVisitor { + cv.currentResultPath = append(cv.currentResultPath, f.FieldName) + return cv +} + +func (cv connectionVisitor) AnnotateWithPosition(i int) resultVisitor { + cv.currentResultPath = append(cv.currentResultPath, fmt.Sprintf("[%d]", i)) + return cv +} + +func (cv connectionVisitor) Visit(res result) resultVisitor { + // Already failed. Stop looking. + if *cv.err != nil { + return nil + } + + path := strings.Join(cv.currentResultPath, ".") + + switch r := res.(type) { + case resultSingle: + k := key{name: r.Name, t: r.Type} + + if conflict, ok := cv.keyPaths[k]; ok { + *cv.err = fmt.Errorf( + "cannot provide %v from %v: already provided by %v", + k, path, conflict) + return nil + } + + if ps := cv.c.providers[k]; len(ps) > 0 { + cons := make([]string, len(ps)) + for i, p := range ps { + cons[i] = fmt.Sprint(p.Func) + } + + *cv.err = fmt.Errorf( + "cannot provide %v from %v: already provided by %v", + k, path, strings.Join(cons, "; ")) + return nil + } + + cv.keyPaths[k] = path + + case resultGrouped: + // we don't really care about the path for this since conflicts are + // okay for group results. We'll track it for the sake of having a + // value there. + k := key{group: r.Group, t: r.Type} + cv.keyPaths[k] = path + } + + return cv +} + +// node is a node in the dependency graph. Each node maps to a single +// constructor provided by the user. +// +// Nodes can produce zero or more values that they store into the container. +// For the Provide path, we verify that nodes produce at least one value, +// otherwise the function will never be called. +type node struct { + ctor interface{} + ctype reflect.Type + Func *digreflect.Func + + // Whether the constructor owned by this node was already called. + called bool + + // Type information about constructor parameters. + Params paramList + + // Type information about constructor results. + Results resultList +} + +func newNode(ctor interface{}) (*node, error) { + ctype := reflect.TypeOf(ctor) + + params, err := newParamList(ctype) + if err != nil { + return nil, err + } + + results, err := newResultList(ctype) + if err != nil { + return nil, err + } + + return &node{ + ctor: ctor, + ctype: ctype, + Func: digreflect.InspectFunc(ctor), + Params: params, + Results: results, + }, err +} + +// Call calls this node's constructor if it hasn't already been called and +// injects any values produced by it into the provided container. +func (n *node) Call(c *Container) error { + if n.called { + return nil + } + + if err := shallowCheckDependencies(c, n.Params); err != nil { + return errMissingDependencies{Func: n.Func, Reason: err} + } + + args, err := n.Params.BuildList(c) + if err != nil { + return errArgumentsFailed{Func: n.Func, Reason: err} + } + + receiver := newStagingReceiver() + results := reflect.ValueOf(n.ctor).Call(args) + n.Results.ExtractList(receiver, results) + + if err := receiver.Commit(c); err != nil { + return errConstructorFailed{Func: n.Func, Reason: err} + } + + n.called = true + return nil +} + +// Checks if a field of an In struct is optional. +func isFieldOptional(f reflect.StructField) (bool, error) { + tag := f.Tag.Get(_optionalTag) + if tag == "" { + return false, nil + } + + optional, err := strconv.ParseBool(tag) + if err != nil { + err = errWrapf(err, + "invalid value %q for %q tag on field %v", + tag, _optionalTag, f.Name) + } + + return optional, err +} + +// Checks that all direct dependencies of the provided param are present in +// the container. Returns an error if not. +func shallowCheckDependencies(c *Container, p param) error { + var missing errMissingManyTypes + walkParam(p, paramVisitorFunc(func(p param) bool { + ps, ok := p.(paramSingle) + if !ok { + return true + } + + k := key{name: ps.Name, t: ps.Type} + if ns := c.providers[k]; len(ns) == 0 && !ps.Optional { + missing = append(missing, newErrMissingType(c, k)) + } + + return true + })) + + if len(missing) > 0 { + return missing + } + return nil +} + +type stagingReceiver struct { + err error + values map[key]reflect.Value + groups map[key][]reflect.Value +} + +func newStagingReceiver() *stagingReceiver { + return &stagingReceiver{ + values: make(map[key]reflect.Value), + groups: make(map[key][]reflect.Value), + } +} + +func (sr *stagingReceiver) SubmitError(err error) { + // record failure only if we haven't already failed + if sr.err == nil { + sr.err = err + } +} + +func (sr *stagingReceiver) SubmitValue(name string, t reflect.Type, v reflect.Value) { + sr.values[key{t: t, name: name}] = v +} + +func (sr *stagingReceiver) SubmitGroupValue(group string, t reflect.Type, v reflect.Value) { + k := key{t: t, group: group} + sr.groups[k] = append(sr.groups[k], v) +} + +// Commit commits the received results to the provided container. +// +// If the resultReceiver failed, no changes are committed to the container. +func (sr *stagingReceiver) Commit(c *Container) error { + if sr.err != nil { + return sr.err + } + + for k, v := range sr.values { + c.values[k] = v + } + + for k, vs := range sr.groups { + c.groups[k] = append(c.groups[k], vs...) + } + + return nil +} diff --git a/vendor/go.uber.org/dig/dig_go19_test.go b/vendor/go.uber.org/dig/dig_go19_test.go new file mode 100644 index 000000000..b3c151e6d --- /dev/null +++ b/vendor/go.uber.org/dig/dig_go19_test.go @@ -0,0 +1,118 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// +build go1.9 + +package dig + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEndToEndSuccessWithAliases(t *testing.T) { + t.Run("pointer constructor", func(t *testing.T) { + type Buffer = *bytes.Buffer + + c := New() + + var b Buffer + require.NoError(t, c.Provide(func() *bytes.Buffer { + b = &bytes.Buffer{} + return b + }), "provide failed") + + require.NoError(t, c.Invoke(func(got Buffer) { + require.NotNil(t, got, "invoke got nil buffer") + require.True(t, got == b, "invoke got wrong buffer") + }), "invoke failed") + }) + + t.Run("duplicate provide", func(t *testing.T) { + type A struct{} + type B = A + + c := New() + require.NoError(t, c.Provide(func() A { + return A{} + }), "A should not fail to provide") + + err := c.Provide(func() B { return B{} }) + require.Error(t, err, "B should fail to provide") + assertErrorMatches(t, err, + `function "go.uber.org/dig".TestEndToEndSuccessWithAliases\S+ \(\S+:\d+\) cannot be provided:`, + `cannot provide dig.A from \[0\]:`, + `already provided by "go.uber.org/dig".TestEndToEndSuccessWithAliases\S+`, + ) + }) + + t.Run("named instances", func(t *testing.T) { + c := New() + type A1 struct{ s string } + type A2 = A1 + type A3 = A2 + + type ret struct { + Out + + A A1 `name:"a"` + B A2 `name:"b"` + C A3 `name:"c"` + } + + type param struct { + In + + A1 A1 `name:"a"` + B1 A2 `name:"b"` + C1 A3 `name:"c"` + + A2 A3 `name:"a"` + B2 A1 `name:"b"` + C2 A2 `name:"c"` + + A3 A2 `name:"a"` + B3 A3 `name:"b"` + C3 A1 `name:"c"` + } + require.NoError(t, c.Provide(func() ret { + return ret{A: A2{"a"}, B: A3{"b"}, C: A1{"c"}} + }), "provide for three named instances should succeed") + + require.NoError(t, c.Invoke(func(p param) { + assert.Equal(t, "a", p.A1.s, "A1 should match") + assert.Equal(t, "b", p.B1.s, "B1 should match") + assert.Equal(t, "c", p.C1.s, "C1 should match") + + assert.Equal(t, "a", p.A2.s, "A2 should match") + assert.Equal(t, "b", p.B2.s, "B2 should match") + assert.Equal(t, "c", p.C2.s, "C2 should match") + + assert.Equal(t, "a", p.A3.s, "A3 should match") + assert.Equal(t, "b", p.B3.s, "B3 should match") + assert.Equal(t, "c", p.C3.s, "C3 should match") + + }), "invoke should succeed, pulling out two named instances") + }) + +} diff --git a/vendor/go.uber.org/dig/dig_test.go b/vendor/go.uber.org/dig/dig_test.go new file mode 100644 index 000000000..7eab75dfb --- /dev/null +++ b/vendor/go.uber.org/dig/dig_test.go @@ -0,0 +1,2324 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package dig + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "math/rand" + "os" + "reflect" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEndToEndSuccess(t *testing.T) { + t.Parallel() + + t.Run("pointer constructor", func(t *testing.T) { + c := New() + var b *bytes.Buffer + require.NoError(t, c.Provide(func() *bytes.Buffer { + b = &bytes.Buffer{} + return b + }), "provide failed") + require.NoError(t, c.Invoke(func(got *bytes.Buffer) { + require.NotNil(t, got, "invoke got nil buffer") + require.True(t, got == b, "invoke got wrong buffer") + }), "invoke failed") + }) + + t.Run("nil pointer constructor", func(t *testing.T) { + // Dig shouldn't forbid this - it's perfectly reasonable to explicitly + // provide a typed nil, since that's often a convenient way to supply a + // default no-op implementation. + c := New() + require.NoError(t, c.Provide(func() *bytes.Buffer { return nil }), "provide failed") + require.NoError(t, c.Invoke(func(b *bytes.Buffer) { + require.Nil(t, b, "expected to get nil buffer") + }), "invoke failed") + }) + + t.Run("struct constructor", func(t *testing.T) { + c := New() + var buf bytes.Buffer + buf.WriteString("foo") + require.NoError(t, c.Provide(func() bytes.Buffer { return buf }), "provide failed") + require.NoError(t, c.Invoke(func(b bytes.Buffer) { + // ensure we're getting back the buffer we put in + require.Equal(t, "foo", buf.String(), "invoke got new buffer") + }), "invoke failed") + }) + + t.Run("slice constructor", func(t *testing.T) { + c := New() + b1 := &bytes.Buffer{} + b2 := &bytes.Buffer{} + require.NoError(t, c.Provide(func() []*bytes.Buffer { + return []*bytes.Buffer{b1, b2} + }), "provide failed") + require.NoError(t, c.Invoke(func(bs []*bytes.Buffer) { + require.Equal(t, 2, len(bs), "invoke got unexpected number of buffers") + require.True(t, b1 == bs[0], "first item did not match") + require.True(t, b2 == bs[1], "second item did not match") + }), "invoke failed") + }) + + t.Run("array constructor", func(t *testing.T) { + c := New() + bufs := [1]*bytes.Buffer{{}} + require.NoError(t, c.Provide(func() [1]*bytes.Buffer { return bufs }), "provide failed") + require.NoError(t, c.Invoke(func(bs [1]*bytes.Buffer) { + require.NotNil(t, bs[0], "invoke got new array") + }), "invoke failed") + }) + + t.Run("map constructor", func(t *testing.T) { + c := New() + require.NoError(t, c.Provide(func() map[string]string { + return map[string]string{} + }), "provide failed") + require.NoError(t, c.Invoke(func(m map[string]string) { + require.NotNil(t, m, "invoke got zero value map") + }), "invoke failed") + }) + + t.Run("channel constructor", func(t *testing.T) { + c := New() + require.NoError(t, c.Provide(func() chan int { + return make(chan int) + }), "provide failed") + require.NoError(t, c.Invoke(func(ch chan int) { + require.NotNil(t, ch, "invoke got nil chan") + }), "invoke failed") + }) + + t.Run("func constructor", func(t *testing.T) { + c := New() + require.NoError(t, c.Provide(func() func(int) { + return func(int) {} + }), "provide failed") + require.NoError(t, c.Invoke(func(f func(int)) { + require.NotNil(t, f, "invoke got nil function pointer") + }), "invoke failed") + }) + + t.Run("interface constructor", func(t *testing.T) { + c := New() + require.NoError(t, c.Provide(func() io.Writer { + return &bytes.Buffer{} + }), "provide failed") + require.NoError(t, c.Invoke(func(w io.Writer) { + require.NotNil(t, w, "invoke got nil interface") + }), "invoke failed") + }) + + t.Run("param", func(t *testing.T) { + c := New() + type contents string + type Args struct { + In + + Contents contents + } + + require.NoError(t, + c.Provide(func(args Args) *bytes.Buffer { + require.NotEmpty(t, args.Contents, "contents must not be empty") + return bytes.NewBufferString(string(args.Contents)) + }), "provide constructor failed") + + require.NoError(t, + c.Provide(func() contents { return "hello world" }), + "provide value failed") + + require.NoError(t, c.Invoke(func(buff *bytes.Buffer) { + out, err := ioutil.ReadAll(buff) + require.NoError(t, err, "read from buffer failed") + require.Equal(t, "hello world", string(out), "contents don't match") + })) + }) + + t.Run("invoke param", func(t *testing.T) { + c := New() + require.NoError(t, c.Provide(func() *bytes.Buffer { + return new(bytes.Buffer) + }), "provide failed") + + type Args struct { + In + + *bytes.Buffer + } + + require.NoError(t, c.Invoke(func(args Args) { + require.NotNil(t, args.Buffer, "invoke got nil buffer") + })) + }) + + t.Run("param wrapper", func(t *testing.T) { + var ( + buff *bytes.Buffer + called bool + ) + + c := New() + require.NoError(t, c.Provide(func() *bytes.Buffer { + require.False(t, called, "constructor must be called exactly once") + called = true + buff = new(bytes.Buffer) + return buff + }), "provide failed") + + type MyParam struct{ In } + + type Args struct { + MyParam + + Buffer *bytes.Buffer + } + + require.NoError(t, c.Invoke(func(args Args) { + require.True(t, called, "constructor must be called first") + require.NotNil(t, args.Buffer, "invoke got nil buffer") + require.True(t, args.Buffer == buff, "buffer must match constructor's return value") + })) + }) + + t.Run("param recurse", func(t *testing.T) { + type anotherParam struct { + In + + Buffer *bytes.Buffer + } + + type someParam struct { + In + + Buffer *bytes.Buffer + Another anotherParam + } + + var ( + buff *bytes.Buffer + called bool + ) + + c := New() + require.NoError(t, c.Provide(func() *bytes.Buffer { + require.False(t, called, "constructor must be called exactly once") + called = true + buff = new(bytes.Buffer) + return buff + }), "provide must not fail") + + require.NoError(t, c.Invoke(func(p someParam) { + require.True(t, called, "constructor must be called first") + + require.NotNil(t, p.Buffer, "someParam.Buffer must not be nil") + require.NotNil(t, p.Another.Buffer, "anotherParam.Buffer must not be nil") + + require.True(t, p.Buffer == p.Another.Buffer, "buffers fields must match") + require.True(t, p.Buffer == buff, "buffer must match constructor's return value") + }), "invoke must not fail") + }) + + t.Run("multiple-type constructor", func(t *testing.T) { + c := New() + constructor := func() (*bytes.Buffer, []int, error) { + return &bytes.Buffer{}, []int{42}, nil + } + consumer := func(b *bytes.Buffer, nums []int) { + assert.NotNil(t, b, "invoke got nil buffer") + assert.Equal(t, 1, len(nums), "invoke got empty slice") + } + require.NoError(t, c.Provide(constructor), "provide failed") + require.NoError(t, c.Invoke(consumer), "invoke failed") + }) + + t.Run("multiple-type constructor is called once", func(t *testing.T) { + c := New() + type A struct{} + type B struct{} + count := 0 + constructor := func() (*A, *B, error) { + count++ + return &A{}, &B{}, nil + } + getA := func(a *A) { + assert.NotNil(t, a, "got nil A") + } + getB := func(b *B) { + assert.NotNil(t, b, "got nil B") + } + require.NoError(t, c.Provide(constructor), "provide failed") + require.NoError(t, c.Invoke(getA), "A invoke failed") + require.NoError(t, c.Invoke(getB), "B invoke failed") + require.NoError(t, c.Invoke(func(a *A, b *B) {}), "AB invoke failed") + require.Equal(t, 1, count, "Constructor must be called once") + }) + + t.Run("method invocation inside Invoke", func(t *testing.T) { + c := New() + type A struct{} + type B struct{} + cA := func() (*A, error) { + return &A{}, nil + } + cB := func() (*B, error) { + return &B{}, nil + } + getA := func(a *A) { + c.Invoke(func(b *B) { + assert.NotNil(t, b, "got nil B") + }) + assert.NotNil(t, a, "got nil A") + } + + require.NoError(t, c.Provide(cA), "provide failed") + require.NoError(t, c.Provide(cB), "provide failed") + require.NoError(t, c.Invoke(getA), "A invoke failed") + }) + + t.Run("collections and instances of same type", func(t *testing.T) { + c := New() + require.NoError(t, c.Provide(func() []*bytes.Buffer { + return []*bytes.Buffer{{}} + }), "providing collection failed") + require.NoError(t, c.Provide(func() *bytes.Buffer { + return &bytes.Buffer{} + }), "providing pointer failed") + }) + + t.Run("optional param field", func(t *testing.T) { + type type1 struct{} + type type2 struct{} + type type3 struct{} + type type4 struct{} + type type5 struct{} + constructor := func() (*type1, *type3, *type4) { + return &type1{}, &type3{}, &type4{} + } + + c := New() + type param struct { + In + + T1 *type1 // regular 'ol type + T2 *type2 `optional:"true" useless_tag:"false"` // optional type NOT in the graph + T3 *type3 `unrelated:"foo=42, optional"` // type in the graph with unrelated tag + T4 *type4 `optional:"true"` // optional type present in the graph + T5 *type5 `optional:"t"` // optional type NOT in the graph with "yes" + } + require.NoError(t, c.Provide(constructor)) + require.NoError(t, c.Invoke(func(p param) { + require.NotNil(t, p.T1, "whole param struct should not be nil") + assert.Nil(t, p.T2, "optional type not in the graph should return nil") + assert.NotNil(t, p.T3, "required type with unrelated tag not in the graph") + assert.NotNil(t, p.T4, "optional type in the graph should not return nil") + assert.Nil(t, p.T5, "optional type not in the graph should return nil") + })) + }) + + t.Run("out type inserts multiple objects into the graph", func(t *testing.T) { + type A struct{ name string } + type B struct{ name string } + type Ret struct { + Out + A // value type A + *B // pointer type *B + } + myA := A{"string A"} + myB := &B{"string B"} + + c := New() + require.NoError(t, c.Provide(func() Ret { + return Ret{A: myA, B: myB} + }), "provide for the Ret struct should succeed") + require.NoError(t, c.Invoke(func(a A, b *B) { + assert.Equal(t, a.name, "string A", "value type should work for dig.Out") + assert.Equal(t, b.name, "string B", "pointer should work for dig.Out") + assert.True(t, myA == a, "should get the same pointer for &A") + assert.Equal(t, b, myB, "b and myB should be uqual") + })) + }) + + t.Run("constructor with optional", func(t *testing.T) { + type type1 struct{} + type type2 struct{} + + type param struct { + In + + T1 *type1 `optional:"true"` + } + + c := New() + + var gave *type2 + require.NoError(t, c.Provide(func(p param) *type2 { + require.Nil(t, p.T1, "T1 must be nil") + gave = &type2{} + return gave + }), "provide failed") + + require.NoError(t, c.Invoke(func(got *type2) { + require.True(t, got == gave, "type2 reference must be the same") + }), "invoke failed") + }) + + t.Run("nested dependencies", func(t *testing.T) { + c := New() + + type A struct{ name string } + type B struct{ name string } + type C struct{ name string } + + require.NoError(t, c.Provide(func() A { return A{"->A"} })) + require.NoError(t, c.Provide(func(A) B { return B{"A->B"} })) + require.NoError(t, c.Provide(func(A, B) C { return C{"AB->C"} })) + require.NoError(t, c.Invoke(func(a A, b B, c C) { + assert.Equal(t, a, A{"->A"}) + assert.Equal(t, b, B{"A->B"}) + assert.Equal(t, c, C{"AB->C"}) + }), "invoking should succeed") + }) + + t.Run("primitives", func(t *testing.T) { + c := New() + require.NoError(t, c.Provide(func() string { return "piper" }), "string provide failed") + require.NoError(t, c.Provide(func() int { return 42 }), "int provide failed") + require.NoError(t, c.Provide(func() int64 { return 24 }), "int provide failed") + require.NoError(t, c.Provide(func() time.Duration { + return 10 * time.Second + }), "time.Duration provide failed") + require.NoError(t, c.Invoke(func(i64 int64, i int, s string, d time.Duration) { + assert.Equal(t, 42, i) + assert.Equal(t, int64(24), i64) + assert.Equal(t, "piper", s) + assert.Equal(t, 10*time.Second, d) + })) + }) + + t.Run("out types recurse", func(t *testing.T) { + type A struct{} + type B struct{} + type C struct{} + // Contains A + type Ret1 struct { + Out + *A + } + // Contains *A (through Ret1), *B and C + type Ret2 struct { + Ret1 + *B + C + } + c := New() + + require.NoError(t, c.Provide(func() Ret2 { + return Ret2{ + Ret1: Ret1{ + A: &A{}, + }, + B: &B{}, + C: C{}, + } + }), "provide for the Ret struct should succeed") + require.NoError(t, c.Invoke(func(a *A, b *B, c C) { + require.NotNil(t, a, "*A should be part of the container through Ret2->Ret1") + })) + }) + + t.Run("named instances", func(t *testing.T) { + c := New() + type A struct{ idx int } + + // returns three named instances of A + type ret struct { + Out + + A1 A `name:"first"` + A2 A `name:"second"` + A3 A `name:"third"` + } + + // requires two specific named instances + type param struct { + In + + A1 A `name:"first"` + A3 A `name:"third"` + } + require.NoError(t, c.Provide(func() ret { + return ret{A1: A{1}, A2: A{2}, A3: A{3}} + }), "provide for three named instances should succeed") + require.NoError(t, c.Invoke(func(p param) { + assert.Equal(t, 1, p.A1.idx) + assert.Equal(t, 3, p.A3.idx) + }), "invoke should succeed, pulling out two named instances") + }) + + t.Run("named and unnamed instances coexist", func(t *testing.T) { + c := New() + type A struct{ idx int } + + type out struct { + Out + + A `name:"foo"` + } + + require.NoError(t, c.Provide(func() out { return out{A: A{1}} })) + require.NoError(t, c.Provide(func() A { return A{2} })) + + type in struct { + In + + A1 A `name:"foo"` + A2 A + } + require.NoError(t, c.Invoke(func(i in) { + assert.Equal(t, 1, i.A1.idx) + assert.Equal(t, 2, i.A2.idx) + })) + }) + + t.Run("named instances recurse", func(t *testing.T) { + c := New() + type A struct{ idx int } + + type Ret1 struct { + Out + + A1 A `name:"first"` + } + type Ret2 struct { + Ret1 + + A2 A `name:"second"` + } + type param struct { + In + + A1 A `name:"first"` // should come from ret1 through ret2 + A2 A `name:"second"` // should come from ret2 + } + require.NoError(t, c.Provide(func() Ret2 { + return Ret2{ + Ret1: Ret1{ + A1: A{1}, + }, + A2: A{2}, + } + })) + require.NoError(t, c.Invoke(func(p param) { + assert.Equal(t, 1, p.A1.idx) + assert.Equal(t, 2, p.A2.idx) + }), "invoke should succeed, pulling out two named instances") + }) + + t.Run("named instances do not cause cycles", func(t *testing.T) { + c := New() + type A struct{ idx int } + type param struct { + In + A `name:"uno"` + } + type paramBoth struct { + In + + A1 A `name:"uno"` + A2 A `name:"dos"` + } + type retUno struct { + Out + A `name:"uno"` + } + type retDos struct { + Out + A `name:"dos"` + } + + require.NoError(t, c.Provide(func() retUno { + return retUno{A: A{1}} + }), `should be able to provide A[name="uno"]`) + require.NoError(t, c.Provide(func(p param) retDos { + return retDos{A: A{2}} + }), `A[name="dos"] should be able to rely on A[name="uno"]`) + require.NoError(t, c.Invoke(func(p paramBoth) { + assert.Equal(t, 1, p.A1.idx) + assert.Equal(t, 2, p.A2.idx) + }), "both objects should be successfully resolved on Invoke") + }) + + t.Run("invoke on a type that depends on named parameters", func(t *testing.T) { + c := New() + type A struct{ idx int } + type B struct{ sum int } + type param struct { + In + + A1 *A `name:"foo"` + A2 *A `name:"bar"` + A3 *A `name:"baz" optional:"true"` + } + type ret struct { + Out + + A1 *A `name:"foo"` + A2 *A `name:"bar"` + } + require.NoError(t, c.Provide(func() (ret, error) { + return ret{ + A1: &A{1}, + A2: &A{2}, + }, nil + }), "should be able to provide A1 and A2 into the graph") + require.NoError(t, c.Provide(func(p param) *B { + return &B{sum: p.A1.idx + p.A2.idx} + }), "should be able to provide *B that relies on two named types") + require.NoError(t, c.Invoke(func(b *B) { + require.Equal(t, 3, b.sum) + })) + }) + + t.Run("dynamically generated dig.In", func(t *testing.T) { + // This test verifies that a dig.In generated using reflect.StructOf + // works with our dig.In detection logic. + c := New() + + type type1 struct{} + type type2 struct{} + + var gave *type1 + new1 := func() *type1 { + require.Nil(t, gave, "constructor must be called only once") + gave = &type1{} + return gave + } + + require.NoError(t, c.Provide(new1), "failed to provide constructor") + + // We generate a struct that embeds dig.In. + // + // Note that the fix for https://github.com/golang/go/issues/18780 + // requires that StructField.Name is always set but versions of Go + // older than 1.9 expect Name to be empty for embedded fields. + // + // We use utils_for_go19_test and utils_for_pre_go19_test with build + // tags to implement this behavior differently in the two Go versions. + + inType := reflect.StructOf([]reflect.StructField{ + anonymousField(reflect.TypeOf(In{})), + { + Name: "Foo", + Type: reflect.TypeOf(&type1{}), + }, + { + Name: "Bar", + Type: reflect.TypeOf(&type2{}), + Tag: `optional:"true"`, + }, + }) + + // We generate a function that relies on that struct and validates the + // result. + fn := reflect.MakeFunc( + reflect.FuncOf([]reflect.Type{inType}, nil /* returns */, false /* variadic */), + func(args []reflect.Value) []reflect.Value { + require.Len(t, args, 1, "expected only one argument") + require.Equal(t, reflect.Struct, args[0].Kind(), "argument must be a struct") + require.Equal(t, 3, args[0].NumField(), "struct must have two fields") + + t1, ok := args[0].Field(1).Interface().(*type1) + require.True(t, ok, "field must be a type1") + require.NotNil(t, t1, "value must not be nil") + require.True(t, t1 == gave, "value must match constructor's return value") + + require.True(t, args[0].Field(2).IsNil(), "type2 must be nil") + return nil + }, + ) + + require.NoError(t, c.Invoke(fn.Interface()), "invoke failed") + }) + + t.Run("dynamically generated dig.Out", func(t *testing.T) { + // This test verifies that a dig.Out generated using reflect.StructOf + // works with our dig.Out detection logic. + + c := New() + + type A struct{ Value int } + + outType := reflect.StructOf([]reflect.StructField{ + anonymousField(reflect.TypeOf(Out{})), + { + Name: "Foo", + Type: reflect.TypeOf(&A{}), + Tag: `name:"foo"`, + }, + { + Name: "Bar", + Type: reflect.TypeOf(&A{}), + Tag: `name:"bar"`, + }, + }) + + fn := reflect.MakeFunc( + reflect.FuncOf(nil /* params */, []reflect.Type{outType}, false /* variadic */), + func([]reflect.Value) []reflect.Value { + result := reflect.New(outType).Elem() + result.Field(1).Set(reflect.ValueOf(&A{Value: 1})) + result.Field(2).Set(reflect.ValueOf(&A{Value: 2})) + return []reflect.Value{result} + }, + ) + require.NoError(t, c.Provide(fn.Interface()), "provide failed") + + type params struct { + In + + Foo *A `name:"foo"` + Bar *A `name:"bar"` + Baz *A `name:"baz" optional:"true"` + } + + require.NoError(t, c.Invoke(func(p params) { + assert.Equal(t, &A{Value: 1}, p.Foo, "Foo must match") + assert.Equal(t, &A{Value: 2}, p.Bar, "Bar must match") + assert.Nil(t, p.Baz, "Baz must be unset") + }), "invoke failed") + }) + + t.Run("variadic arguments invoke", func(t *testing.T) { + c := New() + + type A struct{} + + var gaveA *A + require.NoError(t, c.Provide(func() *A { + gaveA = &A{} + return gaveA + }), "failed to provide A") + + require.NoError(t, c.Provide(func() []*A { + panic("[]*A constructor must not be called.") + }), "failed to provide A slice") + + require.NoError(t, c.Invoke(func(a *A, as ...*A) { + require.NotNil(t, a, "A must not be nil") + require.True(t, a == gaveA, "A must match") + require.Empty(t, as, "varargs must be empty") + }), "failed to invoke") + }) + + t.Run("variadic arguments dependency", func(t *testing.T) { + c := New() + + type A struct{} + type B struct{} + + var gaveA *A + require.NoError(t, c.Provide(func() *A { + gaveA = &A{} + return gaveA + }), "failed to provide A") + + require.NoError(t, c.Provide(func() []*A { + panic("[]*A constructor must not be called.") + }), "failed to provide A slice") + + var gaveB *B + require.NoError(t, c.Provide(func(a *A, as ...*A) *B { + require.NotNil(t, a, "A must not be nil") + require.True(t, a == gaveA, "A must match") + require.Empty(t, as, "varargs must be empty") + gaveB = &B{} + return gaveB + }), "failed to provide B") + + require.NoError(t, c.Invoke(func(b *B) { + require.NotNil(t, b, "B must not be nil") + require.True(t, b == gaveB, "B must match") + }), "failed to invoke") + }) + + t.Run("non-error return arguments from invoke are ignored", func(t *testing.T) { + c := New() + type A struct{} + type B struct{} + + require.NoError(t, c.Provide(func() A { return A{} })) + require.NoError(t, c.Invoke(func(A) B { return B{} })) + + err := c.Invoke(func(B) {}) + require.Error(t, err, "invoking with B param should error out") + assertErrorMatches(t, err, + `missing dependencies for function "go.uber.org/dig".TestEndToEndSuccess.func\S+ \(\S+/src/go.uber.org/dig/dig_test.go:\d+\):`, + "type dig.B is not in the container,", + "did you mean to Provide it?", + ) + }) +} + +func TestGroups(t *testing.T) { + t.Run("empty slice received without provides", func(t *testing.T) { + c := New() + + type in struct { + In + + Values []int `group:"foo"` + } + + require.NoError(t, c.Invoke(func(i in) { + require.Empty(t, i.Values) + }), "failed to invoke") + }) + + t.Run("values are provided", func(t *testing.T) { + c := New(setRand(rand.New(rand.NewSource(0)))) + + type out struct { + Out + + Value int `group:"val"` + } + + provide := func(i int) { + require.NoError(t, c.Provide(func() out { + return out{Value: i} + }), "failed to provide ") + } + + provide(1) + provide(2) + provide(3) + + type in struct { + In + + Values []int `group:"val"` + } + + require.NoError(t, c.Invoke(func(i in) { + assert.Equal(t, []int{2, 3, 1}, i.Values) + }), "invoke failed") + }) + + t.Run("constructor is called at most once", func(t *testing.T) { + c := New(setRand(rand.New(rand.NewSource(0)))) + + type out struct { + Out + + Result string `group:"s"` + } + + calls := make(map[string]int) + + provide := func(i string) { + require.NoError(t, c.Provide(func() out { + calls[i]++ + return out{Result: i} + }), "failed to provide") + } + + provide("foo") + provide("bar") + provide("baz") + + type in struct { + In + + Results []string `group:"s"` + } + + // Expected value of in.Results in consecutive calls. + expected := [][]string{ + {"bar", "baz", "foo"}, + {"foo", "baz", "bar"}, + {"baz", "bar", "foo"}, + {"bar", "foo", "baz"}, + } + + for i, want := range expected { + require.NoError(t, c.Invoke(func(i in) { + require.Equal(t, want, i.Results) + }), "invoke %d failed", i) + } + + for s, v := range calls { + assert.Equal(t, 1, v, "constructor for %q called too many times", s) + } + }) + + t.Run("consume groups in constructor", func(t *testing.T) { + c := New(setRand(rand.New(rand.NewSource(0)))) + + type out struct { + Out + + Result []string `group:"hi"` + } + + provideStrings := func(strings ...string) { + require.NoError(t, c.Provide(func() out { + return out{Result: strings} + }), "failed to provide") + } + + provideStrings("1", "2") + provideStrings("3", "4", "5") + provideStrings("6") + provideStrings("7", "8", "9", "10") + + type setParams struct { + In + + Strings [][]string `group:"hi"` + } + require.NoError(t, c.Provide(func(p setParams) map[string]struct{} { + m := make(map[string]struct{}) + for _, ss := range p.Strings { + for _, s := range ss { + m[s] = struct{}{} + } + } + return m + }), "failed to provide set constructor") + + require.NoError(t, c.Invoke(func(got map[string]struct{}) { + assert.Equal(t, map[string]struct{}{ + "1": {}, "2": {}, "3": {}, "4": {}, "5": {}, + "6": {}, "7": {}, "8": {}, "9": {}, "10": {}, + }, got) + }), "failed to invoke") + }) + + t.Run("provide multiple values", func(t *testing.T) { + c := New(setRand(rand.New(rand.NewSource(0)))) + + type outInt struct { + Out + Int int `group:"foo"` + } + + provideInt := func(i int) { + require.NoError(t, c.Provide(func() (outInt, error) { + return outInt{Int: i}, nil + }), "failed to provide int") + } + + type outString struct { + Out + String string `group:"foo"` + } + + provideString := func(s string) { + require.NoError(t, c.Provide(func() outString { + return outString{String: s} + }), "failed to provide string") + } + + type outBoth struct { + Out + + Int int `group:"foo"` + String string `group:"foo"` + } + + provideBoth := func(i int, s string) { + require.NoError(t, c.Provide(func() (outBoth, error) { + return outBoth{Int: i, String: s}, nil + }), "failed to provide both") + } + + provideInt(1) + provideString("foo") + provideBoth(2, "bar") + provideString("baz") + provideInt(3) + provideBoth(4, "qux") + provideBoth(5, "quux") + provideInt(6) + provideInt(7) + + type in struct { + In + + Ints []int `group:"foo"` + Strings []string `group:"foo"` + } + + require.NoError(t, c.Invoke(func(got in) { + assert.Equal(t, in{ + Ints: []int{5, 3, 4, 1, 6, 7, 2}, + Strings: []string{"foo", "bar", "baz", "quux", "qux"}, + }, got) + }), "failed to invoke") + }) + + t.Run("duplicate values are supported", func(t *testing.T) { + c := New(setRand(rand.New(rand.NewSource(0)))) + + type out struct { + Out + + Result string `group:"s"` + } + + provide := func(i string) { + require.NoError(t, c.Provide(func() out { + return out{Result: i} + }), "failed to provide") + } + + provide("a") + provide("b") + provide("c") + provide("a") + provide("d") + provide("d") + provide("a") + provide("e") + + type stringSlice []string + + type in struct { + In + + Strings stringSlice `group:"s"` + } + + require.NoError(t, c.Invoke(func(i in) { + assert.Equal(t, + stringSlice{"d", "c", "a", "a", "d", "e", "b", "a"}, + i.Strings) + }), "failed to invoke") + }) + + t.Run("failure to build a grouped value fails everything", func(t *testing.T) { + c := New(setRand(rand.New(rand.NewSource(0)))) + + type out struct { + Out + + Result string `group:"x"` + } + + require.NoError(t, c.Provide(func() (out, error) { + return out{Result: "foo"}, nil + }), "failed to provide") + + var gaveErr error + require.NoError(t, c.Provide(func() (out, error) { + gaveErr = errors.New("great sadness") + return out{}, gaveErr + }), "failed to provide") + + require.NoError(t, c.Provide(func() out { + return out{Result: "bar"} + }), "failed to provide") + + type in struct { + In + + Strings []string `group:"x"` + } + + err := c.Invoke(func(i in) { + require.FailNow(t, "this function must not be called") + }) + require.Error(t, err, "expected failure") + assertErrorMatches(t, err, + `could not build arguments for function "go.uber.org/dig".TestGroups`, + `could not build value group string\[group="x"\]:`, + `function "go.uber.org/dig".TestGroups\S+ \(\S+:\d+\) returned a non-nil error:`, + "great sadness", + ) + assert.Equal(t, gaveErr, RootCause(err)) + }) +} + +// --- END OF END TO END TESTS + +func TestProvideConstructorErrors(t *testing.T) { + t.Run("multiple-type constructor returns multiple objects of same type", func(t *testing.T) { + c := New() + type A struct{} + constructor := func() (*A, *A, error) { + return &A{}, &A{}, nil + } + require.Error(t, c.Provide(constructor), "provide failed") + }) + + t.Run("constructor consumes a dig.Out", func(t *testing.T) { + c := New() + type out struct { + Out + + Reader io.Reader + } + + type outPtr struct { + *Out + + Reader io.Reader + } + + tests := []struct { + desc string + constructor interface{} + msg string + }{ + { + desc: "dig.Out", + constructor: func(out) io.Writer { return nil }, + msg: "dig.out embeds a dig.Out", + }, + { + desc: "*dig.Out", + constructor: func(*out) io.Writer { return nil }, + msg: `\*dig.out embeds a dig.Out`, + }, + { + desc: "embeds *dig.Out", + constructor: func(outPtr) io.Writer { return nil }, + msg: `dig.outPtr embeds a dig.Out`, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + err := c.Provide(tt.constructor) + require.Error(t, err, "provide should fail") + assertErrorMatches(t, err, + `function "go.uber.org/dig".TestProvideConstructorErrors\S+ \(\S+:\d+\) cannot be provided:`, + `bad argument 1:`, + `cannot depend on result objects:`, + tt.msg) + }) + } + }) +} + +func TestProvideRespectsConstructorErrors(t *testing.T) { + t.Run("constructor succeeds", func(t *testing.T) { + c := New() + require.NoError(t, c.Provide(func() (*bytes.Buffer, error) { + return &bytes.Buffer{}, nil + }), "provide failed") + require.NoError(t, c.Invoke(func(b *bytes.Buffer) { + require.NotNil(t, b, "invoke got nil buffer") + }), "invoke failed") + }) + t.Run("constructor fails", func(t *testing.T) { + c := New() + require.NoError(t, c.Provide(func() (*bytes.Buffer, error) { + return nil, errors.New("oh no") + }), "provide failed") + + var called bool + err := c.Invoke(func(b *bytes.Buffer) { called = true }) + assertErrorMatches(t, err, + `could not build arguments for function "go.uber.org/dig".TestProvideRespectsConstructorErrors\S+ \(\S+:\d+\):`, + `failed to build \*bytes.Buffer:`, + `function "go.uber.org/dig".TestProvideRespectsConstructorErrors\S+ \(\S+:\d+\) returned a non-nil error:`, + `oh no`) + assert.False(t, called, "shouldn't call invoked function when deps aren't available") + }) +} + +func TestCantProvideObjects(t *testing.T) { + t.Parallel() + + var writer io.Writer = &bytes.Buffer{} + tests := []struct { + object interface{} + typeDesc string + }{ + {&bytes.Buffer{}, "pointer"}, + {bytes.Buffer{}, "struct"}, + {writer, "interface"}, + {map[string]string{}, "map"}, + {[]string{}, "slice"}, + {[1]string{}, "array"}, + {make(chan struct{}), "channel"}, + } + + for _, tt := range tests { + t.Run(tt.typeDesc, func(t *testing.T) { + c := New() + assert.Error(t, c.Provide(tt.object)) + }) + } +} + +func TestCantProvideUntypedNil(t *testing.T) { + t.Parallel() + c := New() + assert.Error(t, c.Provide(nil)) +} + +func TestCantProvideErrorLikeType(t *testing.T) { + t.Parallel() + + tests := []interface{}{ + func() *os.PathError { return &os.PathError{} }, + func() error { return &os.PathError{} }, + func() (*os.PathError, error) { return &os.PathError{}, nil }, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("%T", tt), func(t *testing.T) { + c := New() + assert.Error(t, c.Provide(tt), "providing errors should fail") + }) + } +} + +func TestCantProvideParameterObjects(t *testing.T) { + t.Parallel() + + t.Run("constructor", func(t *testing.T) { + type Args struct{ In } + + c := New() + err := c.Provide(func() (Args, error) { + panic("great sadness") + }) + require.Error(t, err, "provide should fail") + assertErrorMatches(t, err, + `function "go.uber.org/dig".TestCantProvideParameterObjects\S+ \(\S+:\d+\) cannot be provided:`, + "bad result 1:", + `cannot provide parameter objects:`, + `dig.Args embeds a dig.In`, + ) + }) + + t.Run("pointer from constructor", func(t *testing.T) { + c := New() + type Args struct{ In } + + args := &Args{} + + err := c.Provide(func() (*Args, error) { return args, nil }) + require.Error(t, err) + assertErrorMatches(t, err, + `function "go.uber.org/dig".TestCantProvideParameterObjects\S+ \(\S+:\d+\) cannot be provided:`, + "bad result 1:", + `cannot provide parameter objects:`, + `\*dig.Args embeds a dig.In`, + ) + }) +} + +func TestProvideKnownTypesFails(t *testing.T) { + t.Parallel() + + provideArgs := []interface{}{ + func() *bytes.Buffer { return nil }, + func() (*bytes.Buffer, error) { return nil, nil }, + } + + for _, first := range provideArgs { + t.Run(fmt.Sprintf("%T", first), func(t *testing.T) { + c := New() + require.NoError(t, c.Provide(first), "first provide must not fail") + + for _, second := range provideArgs { + assert.Error(t, c.Provide(second), "second provide must fail") + } + }) + } + t.Run("provide constructor twice", func(t *testing.T) { + c := New() + assert.NoError(t, c.Provide(func() *bytes.Buffer { return nil })) + assert.Error(t, c.Provide(func() *bytes.Buffer { return nil })) + }) +} + +func TestProvideCycleFails(t *testing.T) { + t.Parallel() + + t.Run("parameters only", func(t *testing.T) { + // A <- B <- C + // | ^ + // |_________| + type A struct{} + type B struct{} + type C struct{} + newA := func(*C) *A { return &A{} } + newB := func(*A) *B { return &B{} } + newC := func(*B) *C { return &C{} } + + c := New() + assert.NoError(t, c.Provide(newA)) + assert.NoError(t, c.Provide(newB)) + err := c.Provide(newC) + require.Error(t, err, "expected error when introducing cycle") + assertErrorMatches(t, err, + `function "go.uber.org/dig".TestProvideCycleFails.\S+ \(\S+:\d+\) cannot be provided:`, + `this function introduces a cycle:`, + `\*dig.C provided by "go.uber.org/dig".TestProvideCycleFails\S+ \(\S+\)`, + `depends on \*dig.B provided by "go.uber.org/dig".TestProvideCycleFails.\S+ \(\S+\)`, + `depends on \*dig.A provided by "go.uber.org/dig".TestProvideCycleFails.\S+ \(\S+\)`, + `depends on \*dig.C provided by "go.uber.org/dig".TestProvideCycleFails.\S+ \(\S+\)`, + ) + }) + + t.Run("dig.In based cycle", func(t *testing.T) { + // Same cycle as before but in terms of dig.Ins. + + type A struct{} + type B struct{} + type C struct{} + + type AParams struct { + In + + C C + } + newA := func(AParams) A { return A{} } + + type BParams struct { + In + + A A + } + newB := func(BParams) B { return B{} } + + type CParams struct { + In + + B B + W io.Writer + } + newC := func(CParams) C { return C{} } + + c := New() + require.NoError(t, c.Provide(newA)) + require.NoError(t, c.Provide(newB)) + + err := c.Provide(newC) + require.Error(t, err, "expected error when introducing cycle") + assertErrorMatches(t, err, + `function "go.uber.org/dig".TestProvideCycleFails.\S+ \(\S+:\d+\) cannot be provided:`, + `this function introduces a cycle:`, + `dig.C provided by "go.uber.org/dig".TestProvideCycleFails\S+ \(\S+\)`, + `depends on dig.B provided by "go.uber.org/dig".TestProvideCycleFails.\S+ \(\S+\)`, + `depends on dig.A provided by "go.uber.org/dig".TestProvideCycleFails.\S+ \(\S+\)`, + `depends on dig.C provided by "go.uber.org/dig".TestProvideCycleFails.\S+ \(\S+\)`, + ) + }) + + t.Run("group based cycle", func(t *testing.T) { + type D struct{} + + type outA struct { + Out + + Foo string `group:"foo"` + Bar int `group:"bar"` + } + newA := func() outA { + require.FailNow(t, "must not be called") + return outA{} + } + + type outB struct { + Out + + Foo string `group:"foo"` + } + newB := func(*D) outB { + require.FailNow(t, "must not be called") + return outB{} + } + + type inC struct { + In + + Foos []string `group:"foo"` + } + + type outC struct { + Out + + Bar int `group:"bar"` + } + + newC := func(i inC) outC { + require.FailNow(t, "must not be called") + return outC{} + } + + type inD struct { + In + + Bars []int `group:"bar"` + } + + newD := func(inD) *D { + require.FailNow(t, "must not be called") + return nil + } + + c := New() + require.NoError(t, c.Provide(newA)) + require.NoError(t, c.Provide(newB)) + require.NoError(t, c.Provide(newC)) + + err := c.Provide(newD) + require.Error(t, err) + assertErrorMatches(t, err, + `function "go.uber.org/dig".TestProvideCycleFails.\S+ \(\S+:\d+\) cannot be provided:`, + `this function introduces a cycle:`, + `\*dig.D provided by "go.uber.org/dig".TestProvideCycleFails\S+ \(\S+\)`, + `depends on int\[group="bar"\] provided by "go.uber.org/dig".TestProvideCycleFails.\S+ \(\S+\)`, + `depends on string\[group="foo"\] provided by "go.uber.org/dig".TestProvideCycleFails.\S+ \(\S+\)`, + `depends on \*dig.D provided by "go.uber.org/dig".TestProvideCycleFails.\S+ \(\S+\)`, + ) + }) +} + +func TestIncompleteGraphIsOkay(t *testing.T) { + t.Parallel() + + // A <- B <- C + // Even if we don't provide B, we should be able to resolve A. + type A struct{} + type B struct{} + type C struct{} + newA := func() *A { return &A{} } + newC := func(*B) *C { return &C{} } + + c := New() + assert.NoError(t, c.Provide(newA), "provide failed") + assert.NoError(t, c.Provide(newC), "provide failed") + assert.NoError(t, c.Invoke(func(*A) {}), "invoke failed") +} + +func TestProvideFuncsWithoutReturnsFails(t *testing.T) { + t.Parallel() + + c := New() + assert.Error(t, c.Provide(func(*bytes.Buffer) {})) +} + +func TestTypeCheckingEquality(t *testing.T) { + type A struct{} + type B struct { + Out + A + } + type in struct { + In + A + } + type out struct { + B + } + tests := []struct { + item interface{} + isIn bool + isOut bool + }{ + {in{}, true, false}, + {out{}, false, true}, + {A{}, false, false}, + {B{}, false, true}, + {nil, false, false}, + } + for _, tt := range tests { + require.Equal(t, tt.isIn, IsIn(tt.item)) + require.Equal(t, tt.isOut, IsOut(tt.item)) + } +} + +func TestInvokesUseCachedObjects(t *testing.T) { + t.Parallel() + + c := New() + + constructorCalls := 0 + buf := &bytes.Buffer{} + require.NoError(t, c.Provide(func() *bytes.Buffer { + assert.Equal(t, 0, constructorCalls, "constructor must not have been called before") + constructorCalls++ + return buf + })) + + calls := 0 + for i := 0; i < 3; i++ { + assert.NoError(t, c.Invoke(func(b *bytes.Buffer) { + calls++ + require.Equal(t, 1, constructorCalls, "constructor must be called exactly once") + require.Equal(t, buf, b, "invoke got different buffer pointer") + }), "invoke %d failed", i) + require.Equal(t, i+1, calls, "invoked function not called") + } +} + +func TestProvideFailures(t *testing.T) { + t.Run("out returning multiple instances of the same type", func(t *testing.T) { + c := New() + type A struct{ idx int } + type ret struct { + Out + + A1 A // sampe type A provided three times + A2 A + A3 A + } + + err := c.Provide(func() ret { + return ret{ + A1: A{idx: 1}, + A2: A{idx: 2}, + A3: A{idx: 3}, + } + }) + require.Error(t, err, "provide must return error") + assertErrorMatches(t, err, + `function "go.uber.org/dig".TestProvideFailures\S+ \(\S+:\d+\) cannot be provided:`, + `cannot provide dig.A from \[0\].A2:`, + `already provided by \[0\].A1`, + ) + }) + + t.Run("provide multiple instances with the same name", func(t *testing.T) { + c := New() + type A struct{} + type ret1 struct { + Out + *A `name:"foo"` + } + type ret2 struct { + Out + *A `name:"foo"` + } + require.NoError(t, c.Provide(func() ret1 { + return ret1{A: &A{}} + })) + err := c.Provide(func() ret2 { + return ret2{A: &A{}} + }) + require.Error(t, err, "expected error on the second provide") + assertErrorMatches(t, err, + `function "go.uber.org/dig".TestProvideFailures\S+ \(\S+:\d+\) cannot be provided:`, + `cannot provide \*dig.A\[name="foo"\] from \[0\].A:`, + `already provided by "go.uber.org/dig".TestProvideFailures\S+`, + ) + }) + + t.Run("out with unexported field should error", func(t *testing.T) { + c := New() + + type A struct{ idx int } + type out1 struct { + Out + + A1 A // should be ok + a2 A // oops, unexported field. should generate an error + } + err := c.Provide(func() out1 { return out1{a2: A{77}} }) + require.Error(t, err) + assertErrorMatches(t, err, + `function "go.uber.org/dig".TestProvideFailures\S+ \(\S+:\d+\) cannot be provided:`, + "bad result 1:", + `bad field "a2" of dig.out1:`, + `unexported fields not allowed in dig.Out, did you mean to export "a2" \(dig.A\)\?`, + ) + }) + + t.Run("providing pointer to out should fail", func(t *testing.T) { + c := New() + type out struct { + Out + + String string + } + err := c.Provide(func() *out { return &out{String: "foo"} }) + require.Error(t, err) + assertErrorMatches(t, err, + `function "go.uber.org/dig".TestProvideFailures\S+ \(\S+:\d+\) cannot be provided:`, + "bad result 1:", + `cannot return a pointer to a result object, use a value instead:`, + `\*dig.out is a pointer to a struct that embeds dig.Out`, + ) + }) + + t.Run("embedding pointer to out should fail", func(t *testing.T) { + c := New() + + type out struct { + *Out + + String string + } + + err := c.Provide(func() out { return out{String: "foo"} }) + require.Error(t, err) + assertErrorMatches(t, err, + `function "go.uber.org/dig".TestProvideFailures\S+ \(\S+:\d+\) cannot be provided:`, + "bad result 1:", + `cannot build a result object by embedding \*dig.Out, embed dig.Out instead:`, + `dig.out embeds \*dig.Out`, + ) + }) +} + +func TestInvokeFailures(t *testing.T) { + t.Parallel() + + t.Run("invoke a non-function", func(t *testing.T) { + c := New() + err := c.Invoke("foo") + require.Error(t, err) + assertErrorMatches(t, err, `can't invoke non-function foo \(type string\)`) + }) + + t.Run("untyped nil", func(t *testing.T) { + c := New() + err := c.Invoke(nil) + require.Error(t, err) + assertErrorMatches(t, err, `can't invoke an untyped nil`) + }) + + t.Run("unmet dependency", func(t *testing.T) { + c := New() + + err := c.Invoke(func(*bytes.Buffer) {}) + require.Error(t, err, "expected failure") + assertErrorMatches(t, err, + `missing dependencies for function "go.uber.org/dig".TestInvokeFailures\S+ \(\S+\):`, + `type \*bytes.Buffer is not in the container, did you mean to Provide it\?`, + ) + }) + + t.Run("unmet required dependency", func(t *testing.T) { + type type1 struct{} + type type2 struct{} + + type args struct { + In + + T1 *type1 `optional:"true"` + T2 *type2 `optional:"0"` + } + + c := New() + err := c.Invoke(func(a args) { + t.Fatal("function must not be called") + }) + + require.Error(t, err, "expected invoke error") + assertErrorMatches(t, err, + `missing dependencies for function "go.uber.org/dig".TestInvokeFailures\S+ \(\S+\):`, + `type \*dig.type2 is not in the container, did you mean to Provide it\?`, + ) + }) + + t.Run("unmet named dependency", func(t *testing.T) { + c := New() + type param struct { + In + + *bytes.Buffer `name:"foo"` + } + err := c.Invoke(func(p param) { + t.Fatal("function should not be called") + }) + require.Error(t, err, "invoke should fail") + assertErrorMatches(t, err, + `missing dependencies for function "go.uber.org/dig".TestInvokeFailures.\S+ \(\S+\):`, + `type \*bytes.Buffer\[name="foo"\] is not in the container, did you mean to Provide it\?`, + ) + }) + + t.Run("unmet constructor dependency", func(t *testing.T) { + type type1 struct{} + type type2 struct{} + type type3 struct{} + + type param struct { + In + + T1 *type1 + T2 *type2 `optional:"true"` + } + + c := New() + + require.NoError(t, c.Provide(func(p param) *type3 { + panic("function must not be called") + }), "provide failed") + + err := c.Invoke(func(*type3) { + t.Fatal("function must not be called") + }) + require.Error(t, err, "invoke must fail") + assertErrorMatches(t, err, + `could not build arguments for function "go.uber.org/dig".TestInvokeFailures\S+ \(\S+:\d+\):`, + `failed to build \*dig.type3:`, + `missing dependencies for function "go.uber.org/dig".TestInvokeFailures.\S+ \(\S+\):`, + `type \*dig.type1 is not in the container, did you mean to Provide it\?`, + ) + // We don't expect type2 to be mentioned in the list because it's + // optional + }) + + t.Run("multiple unmet constructor dependencies", func(t *testing.T) { + type type1 struct{} + type type2 struct{} + type type3 struct{} + + c := New() + + require.NoError(t, c.Provide(func() type2 { + panic("function must not be called") + }), "provide should not fail") + + require.NoError(t, c.Provide(func(type1, *type2) type3 { + panic("function must not be called") + }), "provide should not fail") + + err := c.Invoke(func(type3) { + t.Fatal("function must not be called") + }) + + require.Error(t, err, "invoke must fail") + assertErrorMatches(t, err, + `could not build arguments for function "go.uber.org/dig".TestInvokeFailures\S+ \(\S+:\d+\):`, + `failed to build dig.type3:`, + `missing dependencies for function "go.uber.org/dig".TestInvokeFailures.\S+ \(\S+\):`, + `the following types are not in the container:`, + "dig.type1;", + `\*dig.type2 \(did you mean dig.type2\?\)`, + ) + }) + + t.Run("invalid optional tag", func(t *testing.T) { + type args struct { + In + + Buffer *bytes.Buffer `optional:"no"` + } + + c := New() + err := c.Invoke(func(a args) { + t.Fatal("function must not be called") + }) + + require.Error(t, err, "expected invoke error") + assertErrorMatches(t, err, + `bad field "Buffer" of dig.args:`, + `invalid value "no" for "optional" tag on field Buffer:`, + ) + }) + + t.Run("constructor invalid optional tag", func(t *testing.T) { + type type1 struct{} + + type nestedArgs struct { + In + + Buffer *bytes.Buffer `optional:"no"` + } + + type args struct { + In + + Args nestedArgs + } + + c := New() + err := c.Provide(func(a args) *type1 { + panic("function must not be called") + }) + + require.Error(t, err, "expected provide error") + assertErrorMatches(t, err, + `function "go.uber.org/dig".TestInvokeFailures\S+ \(\S+:\d+\) cannot be provided:`, + "bad argument 1:", + `bad field "Args" of dig.args:`, + `bad field "Buffer" of dig.nestedArgs:`, + `invalid value "no" for "optional" tag on field Buffer:`, + ) + }) + + t.Run("optional dep with unmet transitive dep", func(t *testing.T) { + type missing struct{} + type dep struct{} + + type params struct { + In + + Dep *dep `optional:"true"` + } + + c := New() + + // Container has a constructor for *dep, but that constructor has unmet + // dependencies. + err := c.Provide(func(missing) *dep { + panic("constructor for *dep should not be called") + }) + require.NoError(t, err, "unexpected provide error") + + // Should still be able to invoke a function that takes params, since *dep + // is optional. + var count int + err = c.Invoke(func(p params) { + count++ + assert.Nil(t, p.Dep, "expected optional dependency to be unmet") + }) + assert.NoError(t, err, "unexpected invoke error") + assert.Equal(t, 1, count, "expected invoke function to be called") + }) + + t.Run("optional dep with failed transitive dep", func(t *testing.T) { + type failed struct{} + type dep struct{} + + type params struct { + In + + Dep *dep `optional:"true"` + } + + c := New() + + errFailed := errors.New("failed") + err := c.Provide(func() (*failed, error) { + return nil, errFailed + }) + require.NoError(t, err, "unexpected provide error") + + err = c.Provide(func(*failed) *dep { + panic("constructor for *dep should not be called") + }) + require.NoError(t, err, "unexpected provide error") + + // Should still be able to invoke a function that takes params, since *dep + // is optional. + err = c.Invoke(func(p params) { + panic("shouldn't execute invoked function") + }) + require.Error(t, err, "expected invoke error") + assertErrorMatches(t, err, + `could not build arguments for function "go.uber.org/dig".TestInvokeFailures\S+ \(\S+:\d+\):`, + `failed to build \*dig.dep:`, + `could not build arguments for function "go.uber.org/dig".TestInvokeFailures.\S+ \(\S+:\d+\):`, + `failed to build \*dig.failed:`, + `function "go.uber.org/dig".TestInvokeFailures.\S+ \(\S+:\d+\) returned a non-nil error:`, + `failed`, + ) + assert.Equal(t, errFailed, RootCause(err), "root cause must match") + }) + + t.Run("returned error", func(t *testing.T) { + c := New() + err := c.Invoke(func() error { return errors.New("oh no") }) + require.Equal(t, errors.New("oh no"), err, "error must match") + }) + + t.Run("many returns", func(t *testing.T) { + c := New() + err := c.Invoke(func() (int, error) { return 42, errors.New("oh no") }) + require.Equal(t, errors.New("oh no"), err, "error must match") + }) + + t.Run("named instances are case sensitive", func(t *testing.T) { + c := New() + type A struct{} + type ret struct { + Out + A `name:"CamelCase"` + } + type param1 struct { + In + A `name:"CamelCase"` + } + type param2 struct { + In + A `name:"camelcase"` + } + require.NoError(t, c.Provide(func() ret { return ret{A: A{}} })) + require.NoError(t, c.Invoke(func(param1) {})) + err := c.Invoke(func(param2) {}) + require.Error(t, err, "provide should return error since cases don't match") + assertErrorMatches(t, err, + `missing dependencies for function "go.uber.org/dig".TestInvokeFailures\S+ \(\S+:\d+\):`, + `type dig.A\[name="camelcase"\] is not in the container, did you mean to Provide it`) + }) + + t.Run("in unexported member gets an error", func(t *testing.T) { + c := New() + type A struct{} + type in struct { + In + + A1 A // all is good + a2 A // oops, unexported type + } + require.NoError(t, c.Provide(func() A { return A{} })) + + err := c.Invoke(func(i in) { assert.Fail(t, "should never get in here") }) + require.Error(t, err) + assertErrorMatches(t, err, + "bad argument 1:", + `bad field "a2" of dig.in:`, + `unexported fields not allowed in dig.In, did you mean to export "a2" \(dig.A\)\?`, + ) + }) + + t.Run("in unexported member gets an error on Provide", func(t *testing.T) { + c := New() + type in struct { + In + + foo string + } + + err := c.Provide(func(in) int { return 0 }) + require.Error(t, err, "Provide must fail") + assertErrorMatches(t, err, + `function "go.uber.org/dig".TestInvokeFailures\S+ \(\S+:\d+\) cannot be provided:`, + "bad argument 1:", + `bad field "foo" of dig.in:`, + `unexported fields not allowed in dig.In, did you mean to export "foo" \(string\)\?`, + ) + }) + + t.Run("embedded unexported member gets an error", func(t *testing.T) { + c := New() + type A struct{} + type Embed struct { + In + + A1 A // all is good + a2 A // oops, unexported type + } + type in struct { + Embed + } + require.NoError(t, c.Provide(func() A { return A{} })) + + err := c.Invoke(func(i in) { assert.Fail(t, "should never get in here") }) + require.Error(t, err) + assertErrorMatches(t, err, + "bad argument 1:", + `bad field "Embed" of dig.in:`, + `bad field "a2" of dig.Embed:`, + `unexported fields not allowed in dig.In, did you mean to export "a2" \(dig.A\)\?`, + ) + }) + + t.Run("embedded unexported member gets an error", func(t *testing.T) { + c := New() + type param struct { + In + + string // embed an unexported std type + } + err := c.Invoke(func(p param) { assert.Fail(t, "should never get here") }) + require.Error(t, err) + assertErrorMatches(t, err, + "bad argument 1:", + `bad field "string" of dig.param:`, + `unexported fields not allowed in dig.In, did you mean to export "string" \(string\)\?`, + ) + }) + + t.Run("pointer in dependency is not supported", func(t *testing.T) { + c := New() + type in struct { + In + + String string + Num int + } + err := c.Invoke(func(i *in) { assert.Fail(t, "should never get here") }) + require.Error(t, err) + assertErrorMatches(t, err, + "bad argument 1:", + "cannot depend on a pointer to a parameter object, use a value instead:", + `\*dig.in is a pointer to a struct that embeds dig.In`, + ) + }) + + t.Run("embedding dig.In and dig.Out is not supported", func(t *testing.T) { + c := New() + type in struct { + In + Out + + String string + } + + err := c.Invoke(func(in) { + assert.Fail(t, "should never get here") + }) + require.Error(t, err) + assertErrorMatches(t, err, + "bad argument 1:", + "cannot depend on result objects:", + "dig.in embeds a dig.Out", + ) + }) + + t.Run("embedding in pointer is not supported", func(t *testing.T) { + c := New() + type in struct { + *In + + String string + Num int + } + err := c.Invoke(func(i in) { assert.Fail(t, "should never get here") }) + require.Error(t, err) + assertErrorMatches(t, err, + "bad argument 1:", + `cannot build a parameter object by embedding \*dig.In, embed dig.In instead:`, + `dig.in embeds \*dig.In`, + ) + }) + + t.Run("requesting a value or pointer when other is present", func(t *testing.T) { + type A struct{} + type outA struct { + Out + + A `name:"hello"` + } + type B struct{} + + cases := []struct { + name string + provide interface{} + invoke interface{} + errContains string + }{ + { + name: "value missing, pointer present", + provide: func() *A { return &A{} }, + invoke: func(A) {}, + errContains: `type dig.A is not in the container, did you mean to use \*dig.A\?`, + }, + { + name: "pointer missing, value present", + provide: func() A { return A{} }, + invoke: func(*A) {}, + errContains: `type \*dig.A is not in the container, did you mean to use dig.A?`, + }, + { + name: "named pointer missing, value present", + provide: func() outA { return outA{A: A{}} }, + invoke: func(struct { + In + + *A `name:"hello"` + }) { + }, + errContains: `type \*dig.A\[name="hello"\] is not in the container, did you mean to use dig.A\[name="hello"\]?`, + }, + } + + for _, tc := range cases { + c := New() + t.Run(tc.name, func(t *testing.T) { + require.NoError(t, c.Provide(tc.provide)) + + err := c.Invoke(tc.invoke) + require.Error(t, err) + assertErrorMatches(t, err, + `missing dependencies for function "go.uber.org/dig".TestInvokeFailures.\S+ \(\S+\):`, + tc.errContains, + ) + }) + } + }) + + t.Run("requesting an interface when an implementation is available", func(t *testing.T) { + c := New() + require.NoError(t, c.Provide(bytes.NewReader)) + err := c.Invoke(func(io.Reader) { + t.Fatalf("this function should not be called") + }) + require.Error(t, err) + assertErrorMatches(t, err, + `missing dependencies for function "go.uber.org/dig".TestInvokeFailures.\S+ \(\S+\):`, + `type io.Reader is not in the container, did you mean to use \*bytes.Reader\?`, + ) + }) + + t.Run("requesting an interface when multiple implementations are available", func(t *testing.T) { + c := New() + + require.NoError(t, c.Provide(bytes.NewReader)) + require.NoError(t, c.Provide(bytes.NewBufferString)) + + err := c.Invoke(func(io.Reader) { + t.Fatalf("this function should not be called") + }) + require.Error(t, err) + assertErrorMatches(t, err, + `missing dependencies for function "go.uber.org/dig".TestInvokeFailures.\S+ \(\S+\):`, + `type io.Reader is not in the container, did you mean to use one of \*bytes.Buffer, or \*bytes.Reader\?`, + ) + }) + + t.Run("requesting multiple interfaces when multiple implementations are available", func(t *testing.T) { + c := New() + + require.NoError(t, c.Provide(bytes.NewReader)) + require.NoError(t, c.Provide(bytes.NewBufferString)) + + err := c.Invoke(func(io.Reader, io.Writer) { + t.Fatalf("this function should not be called") + }) + require.Error(t, err) + assertErrorMatches(t, err, + `missing dependencies for function "go.uber.org/dig".TestInvokeFailures.\S+ \(\S+\):`, + `the following types are not in the container:`, + `io.Reader \(did you mean \*bytes.Buffer, or \*bytes.Reader\?\);`, + `io.Writer \(did you mean \*bytes.Buffer\?\)`, + ) + }) + + t.Run("requesting a type when an interface is available", func(t *testing.T) { + c := New() + + require.NoError(t, c.Provide(func() io.Writer { return nil })) + err := c.Invoke(func(*bytes.Buffer) { + t.Fatalf("this function should not be called") + }) + + require.Error(t, err) + assertErrorMatches(t, err, + `missing dependencies for function "go.uber.org/dig".TestInvokeFailures.\S+ \(\S+\):`, + `type \*bytes.Buffer is not in the container, did you mean to use io.Writer\?`, + ) + }) + + t.Run("requesting a type when multiple interfaces are available", func(t *testing.T) { + c := New() + + require.NoError(t, c.Provide(func() io.Writer { return nil })) + require.NoError(t, c.Provide(func() io.Reader { return nil })) + + err := c.Invoke(func(*bytes.Buffer) { + t.Fatalf("this function should not be called") + }) + + require.Error(t, err) + assertErrorMatches(t, err, + `missing dependencies for function "go.uber.org/dig".TestInvokeFailures.\S+ \(\S+\):`, + `type \*bytes.Buffer is not in the container, did you mean to use one of io.Reader, or io.Writer\?`, + ) + }) + + t.Run("direct dependency error", func(t *testing.T) { + type A struct{} + + c := New() + + require.NoError(t, c.Provide(func() (A, error) { + return A{}, errors.New("great sadness") + }), "Provide failed") + + err := c.Invoke(func(A) { panic("impossible") }) + + require.Error(t, err, "expected Invoke error") + assertErrorMatches(t, err, + `function "go.uber.org/dig".TestInvokeFailures.func\S+ \(\S+\) returned a non-nil error:`, + "great sadness", + ) + assert.Equal(t, errors.New("great sadness"), RootCause(err)) + }) + + t.Run("transitive dependency error", func(t *testing.T) { + type A struct{} + type B struct{} + + c := New() + + require.NoError(t, c.Provide(func() (A, error) { + return A{}, errors.New("great sadness") + }), "Provide failed") + + require.NoError(t, c.Provide(func(A) (B, error) { + return B{}, nil + }), "Provide failed") + + err := c.Invoke(func(B) { panic("impossible") }) + + require.Error(t, err, "expected Invoke error") + assertErrorMatches(t, err, + `could not build arguments for function "go.uber.org/dig".TestInvokeFailures\S+`, + "failed to build dig.B", + `could not build arguments for function "go.uber.org/dig".TestInvokeFailures\S+`, + "failed to build dig.A", + `function "go.uber.org/dig".TestInvokeFailures.func\S+ \(\S+\) returned a non-nil error:`, + "great sadness", + ) + assert.Equal(t, errors.New("great sadness"), RootCause(err)) + }) + + t.Run("direct parameter object error", func(t *testing.T) { + type A struct{} + + c := New() + + require.NoError(t, c.Provide(func() (A, error) { + return A{}, errors.New("great sadness") + }), "Provide failed") + + type params struct { + In + + A A + } + + err := c.Invoke(func(params) { panic("impossible") }) + + require.Error(t, err, "expected Invoke error") + assertErrorMatches(t, err, + `could not build arguments for function "go.uber.org/dig".TestInvokeFailures.func\S+`, + "failed to build dig.A:", + `function "go.uber.org/dig".TestInvokeFailures.func\S+ \(\S+\) returned a non-nil error:`, + "great sadness", + ) + assert.Equal(t, errors.New("great sadness"), RootCause(err)) + }) + + t.Run("transitive parameter object error", func(t *testing.T) { + type A struct{} + type B struct{} + + c := New() + + require.NoError(t, c.Provide(func() (A, error) { + return A{}, errors.New("great sadness") + }), "Provide failed") + + type params struct { + In + + A A + } + + require.NoError(t, c.Provide(func(params) (B, error) { + return B{}, nil + }), "Provide failed") + + err := c.Invoke(func(B) { panic("impossible") }) + + require.Error(t, err, "expected Invoke error") + assertErrorMatches(t, err, + `could not build arguments for function "go.uber.org/dig".TestInvokeFailures.func\S+ \(\S+:\d+\):`, + "failed to build dig.B:", + `could not build arguments for function "go.uber.org/dig".TestInvokeFailures.func\S+`, + "failed to build dig.A:", + `function "go.uber.org/dig".TestInvokeFailures.func\S+ \(\S+\) returned a non-nil error:`, + "great sadness", + ) + assert.Equal(t, errors.New("great sadness"), RootCause(err)) + }) + + t.Run("unmet dependency of a group value", func(t *testing.T) { + c := New() + + type A struct{} + type B struct{} + + type out struct { + Out + + B B `group:"b"` + } + + require.NoError(t, c.Provide(func(A) out { + require.FailNow(t, "must not be called") + return out{} + })) + + type in struct { + In + + Bs []B `group:"b"` + } + + err := c.Invoke(func(in) { + require.FailNow(t, "must not be called") + }) + require.Error(t, err, "expected failure") + assertErrorMatches(t, err, + `could not build arguments for function "go.uber.org/dig".TestInvokeFailures.\S+ \(\S+:\d+\):`, + `could not build value group dig.B\[group="b"\]:`, + `missing dependencies for function "go.uber.org/dig".TestInvokeFailures.\S+ \(\S+:\d+\):`, + "type dig.A is not in the container, did you mean to Provide it?", + ) + }) +} + +func TestNodeAlreadyCalled(t *testing.T) { + type type1 struct{} + f := func() type1 { return type1{} } + + n, err := newNode(f) + require.NoError(t, err, "failed to build node") + require.False(t, n.called, "node must not have been called") + + c := New() + require.NoError(t, n.Call(c), "invoke failed") + require.True(t, n.called, "node must be called") + require.NoError(t, n.Call(c), "calling again should be okay") +} + +func TestFailingFunctionDoesNotCreateInvalidState(t *testing.T) { + type type1 struct{} + + c := New() + require.NoError(t, c.Provide(func() (type1, error) { + return type1{}, errors.New("great sadness") + }), "provide failed") + + require.Error(t, c.Invoke(func(type1) { + require.FailNow(t, "first invoke must not call the function") + }), "first invoke must fail") + + require.Error(t, c.Invoke(func(type1) { + require.FailNow(t, "second invoke must not call the function") + }), "second invoke must fail") +} diff --git a/vendor/go.uber.org/dig/doc.go b/vendor/go.uber.org/dig/doc.go new file mode 100644 index 000000000..e8d927bf5 --- /dev/null +++ b/vendor/go.uber.org/dig/doc.go @@ -0,0 +1,318 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package dig provides an opinionated way of resolving object dependencies. +// +// Status +// +// STABLE. No breaking changes will be made in this major version. +// +// Container +// +// Dig exposes type Container as an object capable of resolving a directed +// acyclic dependency graph. Use the New function to create one. +// +// c := dig.New() +// +// Provide +// +// Constructors for different types are added to the container by using the +// Provide method. A constructor can declare a dependency on another type by +// simply adding it as a function parameter. Dependencies for a type can be +// added to the graph both, before and after the type was added. +// +// err := c.Provide(func(conn *sql.DB) (*UserGateway, error) { +// // ... +// }) +// if err != nil { +// // ... +// } +// +// if err := c.Provide(newDBConnection); err != nil { +// // ... +// } +// +// Multiple constructors can rely on the same type. The container creates a +// singleton for each retained type, instantiating it at most once when +// requested directly or as a dependency of another type. +// +// err := c.Provide(func(conn *sql.DB) *CommentGateway { +// // ... +// }) +// if err != nil { +// // ... +// } +// +// Constructors can declare any number of dependencies as parameters and +// optionally, return errors. +// +// err := c.Provide(func(u *UserGateway, c *CommentGateway) (*RequestHandler, error) { +// // ... +// }) +// if err != nil { +// // ... +// } +// +// if err := c.Provide(newHTTPServer); err != nil { +// // ... +// } +// +// Constructors can also return multiple results to add multiple types to the +// container. +// +// err := c.Provide(func(conn *sql.DB) (*UserGateway, *CommentGateway, error) { +// // ... +// }) +// if err != nil { +// // ... +// } +// +// Constructors that accept a variadic number of arguments are treated as if +// they don't have those arguments. That is, +// +// func NewVoteGateway(db *sql.DB, options ...Option) *VoteGateway +// +// Is treated the same as, +// +// func NewVoteGateway(db *sql.DB) *VoteGateway +// +// The constructor will be called with all other dependencies and no variadic +// arguments. +// +// Invoke +// +// Types added to to the container may be consumed by using the Invoke method. +// Invoke accepts any function that accepts one or more parameters and +// optionally, returns an error. Dig calls the function with the requested +// type, instantiating only those types that were requested by the function. +// The call fails if any type or its dependencies (both direct and transitive) +// were not available in the container. +// +// err := c.Invoke(func(l *log.Logger) { +// // ... +// }) +// if err != nil { +// // ... +// } +// +// err := c.Invoke(func(server *http.Server) error { +// // ... +// }) +// if err != nil { +// // ... +// } +// +// Any error returned by the invoked function is propagated back to the +// caller. +// +// Parameter Objects +// +// Constructors declare their dependencies as function parameters. This can +// very quickly become unreadable if the constructor has a lot of +// dependencies. +// +// func NewHandler(users *UserGateway, comments *CommentGateway, posts *PostGateway, votes *VoteGateway, authz *AuthZGateway) *Handler { +// // ... +// } +// +// A pattern employed to improve readability in a situation like this is to +// create a struct that lists all the parameters of the function as fields and +// changing the function to accept that struct instead. This is referred to as +// a parameter object. +// +// Dig has first class support for parameter objects: any struct embedding +// dig.In gets treated as a parameter object. The following is equivalent to +// the constructor above. +// +// type HandlerParams struct { +// dig.In +// +// Users *UserGateway +// Comments *CommentGateway +// Posts *PostGateway +// Votes *VoteGateway +// AuthZ *AuthZGateway +// } +// +// func NewHandler(p HandlerParams) *Handler { +// // ... +// } +// +// Handlers can receive any combination of parameter objects and parameters. +// +// func NewHandler(p HandlerParams, l *log.Logger) *Handler { +// // ... +// } +// +// Result Objects +// +// Result objects are the flip side of parameter objects. These are structs +// that represent multiple outputs from a single function as fields in the +// struct. Structs embedding dig.Out get treated as result objects. +// +// func SetupGateways(conn *sql.DB) (*UserGateway, *CommentGateway, *PostGateway, error) { +// // ... +// } +// +// The above is equivalent to, +// +// type Gateways struct { +// dig.Out +// +// Users *UserGateway +// Comments *CommentGateway +// Posts *PostGateway +// } +// +// func SetupGateways(conn *sql.DB) (Gateways, error) { +// // ... +// } +// +// Optional Dependencies +// +// Constructors often don't have a hard dependency on some types and +// are able to operate in a degraded state when that dependency is missing. +// Dig supports declaring dependencies as optional by adding an +// `optional:"true"` tag to fields of a dig.In struct. +// +// Fields in a dig.In structs that have the `optional:"true"` tag are treated +// as optional by Dig. +// +// type UserGatewayParams struct { +// dig.In +// +// Conn *sql.DB +// Cache *redis.Client `optional:"true"` +// } +// +// If an optional field is not available in the container, the constructor +// will receive a zero value for the field. +// +// func NewUserGateway(p UserGatewayParams, log *log.Logger) (*UserGateway, error) { +// if p.Cache != nil { +// log.Print("Logging disabled") +// } +// // ... +// } +// +// Constructors that declare dependencies as optional MUST handle the case of +// those dependencies being absent. +// +// The optional tag also allows adding new dependencies without breaking +// existing consumers of the constructor. +// +// Named Values +// +// Some use cases call for multiple values of the same type. Dig allows adding +// multiple values of the same type to the container with the use of +// `name:".."` tags on fields of dig.In and dig.Out structs. +// +// A constructor that produces a dig.Out struct can tag any field with +// `name:".."` to have the corresponding value added to the graph under the +// specified name. +// +// type ConnectionResult struct { +// dig.Out +// +// ReadWrite *sql.DB `name:"rw"` +// ReadOnly *sql.DB `name:"ro"` +// } +// +// func ConnectToDatabase(...) (ConnectionResult, error) { +// // ... +// return ConnectionResult{ReadWrite: rw, ReadOnly: ro}, nil +// } +// +// Another constructor can consume these values by adding fields to a dig.In +// struct with the same name AND type. +// +// type GatewayParams struct { +// dig.In +// +// WriteToConn *sql.DB `name:"rw"` +// ReadFromConn *sql.DB `name:"ro"` +// } +// +// The name tag may be combined with the optional tag to declare the +// dependency optional. +// +// type GatewayParams struct { +// dig.In +// +// WriteToConn *sql.DB `name:"rw"` +// ReadFromConn *sql.DB `name:"ro" optional:"true"` +// } +// +// func NewCommentGateway(p GatewayParams, log *log.Logger) (*CommentGateway, error) { +// if p.ReadFromConn == nil { +// log.Print("Warning: Using RW connection for reads") +// p.ReadFromConn = p.WriteToConn +// } +// // ... +// } +// +// Value Groups +// +// Added in Dig 1.2. +// +// Dig provides value groups to allow producing and consuming many values of +// the same type. Value groups allow constructors to send values to a named, +// unordered collection in the container. Other constructors can request all +// values in this collection as a slice. +// +// Constructors can send values into value groups by returning a dig.Out +// struct tagged with `group:".."`. +// +// type HandlerResult struct { +// dig.Out +// +// Handler Handler `group:"server"` +// } +// +// func NewHelloHandler() HandlerResult { +// .. +// } +// +// func NewEchoHandler() HandlerResult { +// .. +// } +// +// Any number of constructors may provide values to this named collection. +// Other constructors can request all values for this collection by requesting +// a slice tagged with `group:".."`. This will execute all constructors that +// provide a value to that group in an unspecified order. +// +// type ServerParams struct { +// dig.In +// +// Handlers []Handler `group:"server"` +// } +// +// func NewServer(p ServerParams) *Server { +// server := newServer() +// for _, h := range p.Handlers { +// server.Register(h) +// } +// return server +// } +// +// Note that values in a value group are unordered. Dig makes no guarantees +// about the order in which these values will be produced. +package dig diff --git a/vendor/go.uber.org/dig/error.go b/vendor/go.uber.org/dig/error.go new file mode 100644 index 000000000..181d310a7 --- /dev/null +++ b/vendor/go.uber.org/dig/error.go @@ -0,0 +1,300 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package dig + +import ( + "bytes" + "fmt" + "reflect" + "sort" + + "go.uber.org/dig/internal/digreflect" +) + +// Errors which know their underlying cause should implement this interface to +// be compatible with RootCause. +// +// We use an unexported "cause" method instead of "Cause" because we don't +// want dig-internal causes to be confused with the cause of the user-provided +// errors. (For example, if the users are using github.com/pkg/errors.) +type causer interface { + cause() error +} + +// RootCause returns the original error that caused the provided dig failure. +// +// RootCause may be used on errors returned by Invoke to get the original +// error returned by a constructor or invoked function. +func RootCause(err error) error { + for { + if e, ok := err.(causer); ok { + err = e.cause() + } else { + return err + } + } +} + +// errWrapf wraps an existing error with more contextual information. +// +// The given error is treated as the cause of the returned error (see causer). +// +// RootCause(errWrapf(errWrapf(err, ...), ...)) == err +// +// Use errWrapf instead of fmt.Errorf if the message ends with ": ". +func errWrapf(err error, msg string, args ...interface{}) error { + if err == nil { + return nil + } + + if len(args) > 0 { + msg = fmt.Sprintf(msg, args...) + } + + return wrappedError{err: err, msg: msg} +} + +type wrappedError struct { + err error + msg string +} + +func (e wrappedError) cause() error { return e.err } + +func (e wrappedError) Error() string { + return fmt.Sprintf("%v: %v", e.msg, e.err) +} + +// errProvide is returned when a constructor could not be Provided into the +// container. +type errProvide struct { + Func *digreflect.Func + Reason error +} + +func (e errProvide) cause() error { return e.Reason } + +func (e errProvide) Error() string { + return fmt.Sprintf("function %v cannot be provided: %v", e.Func, e.Reason) +} + +// errConstructorFailed is returned when a user-provided constructor failed +// with a non-nil error. +type errConstructorFailed struct { + Func *digreflect.Func + Reason error +} + +func (e errConstructorFailed) cause() error { return e.Reason } + +func (e errConstructorFailed) Error() string { + return fmt.Sprintf("function %v returned a non-nil error: %v", e.Func, e.Reason) +} + +// errArgumentsFailed is returned when a function could not be run because one +// of its dependencies failed to build for any reason. +type errArgumentsFailed struct { + Func *digreflect.Func + Reason error +} + +func (e errArgumentsFailed) cause() error { return e.Reason } + +func (e errArgumentsFailed) Error() string { + return fmt.Sprintf("could not build arguments for function %v: %v", e.Func, e.Reason) +} + +// errMissingDependencies is returned when the dependencies of a function are +// not available in the container. +type errMissingDependencies struct { + Func *digreflect.Func + Reason error +} + +func (e errMissingDependencies) cause() error { return e.Reason } + +func (e errMissingDependencies) Error() string { + return fmt.Sprintf("missing dependencies for function %v: %v", e.Func, e.Reason) +} + +// errParamSingleFailed is returned when a paramSingle could not be built. +type errParamSingleFailed struct { + Key key + Reason error +} + +func (e errParamSingleFailed) cause() error { return e.Reason } + +func (e errParamSingleFailed) Error() string { + return fmt.Sprintf("failed to build %v: %v", e.Key, e.Reason) +} + +// errParamGroupFailed is returned when a value group cannot be built because +// any of the values in the group failed to build. +type errParamGroupFailed struct { + Key key + Reason error +} + +func (e errParamGroupFailed) cause() error { return e.Reason } + +func (e errParamGroupFailed) Error() string { + return fmt.Sprintf("could not build value group %v: %v", e.Key, e.Reason) +} + +// errMissingType is returned when a single value that was expected in the +// container was not available. +type errMissingType struct { + Key key + + // If non-empty, we will include suggestions for what the user may have + // meant. + suggestions []key +} + +func newErrMissingType(c *Container, k key) errMissingType { + // Possible types we will look for in the container. We will always look + // for pointers to the requested type and some extras on a per-Kind basis. + + suggestions := []reflect.Type{reflect.PtrTo(k.t)} + if k.t.Kind() == reflect.Ptr { + // The user requested a pointer but maybe we have a value. + suggestions = append(suggestions, k.t.Elem()) + } + + if k.t.Kind() == reflect.Interface { + // Maybe we have an implementation of the interface. + for p := range c.providers { + if p.t.Implements(k.t) { + suggestions = append(suggestions, p.t) + } + } + } else { + // Maybe we have an interface that this type implements. + for p := range c.providers { + if p.t.Kind() == reflect.Interface { + if k.t.Implements(p.t) { + suggestions = append(suggestions, p.t) + } + } + } + } + + // range through c.providers is non-deterministic. Let's sort the list of + // suggestions. + sort.Sort(byTypeName(suggestions)) + + err := errMissingType{Key: k} + for _, t := range suggestions { + k.t = t + if len(c.providers[k]) > 0 { + err.suggestions = append(err.suggestions, k) + } + } + + return err +} + +func (e errMissingType) Error() string { + // Sample messages: + // + // type io.Reader is not in the container, did you mean to Provide it? + // type io.Reader is not in the container, did you mean to use one of *bytes.Buffer, *MyBuffer + // type bytes.Buffer is not in the container, did you mean to use *bytes.Buffer? + // type *foo[name="bar"] is not in the container, did you mean to use foo[name="bar"]? + + b := new(bytes.Buffer) + + fmt.Fprintf(b, "type %v is not in the container", e.Key) + switch len(e.suggestions) { + case 0: + b.WriteString(", did you mean to Provide it?") + case 1: + fmt.Fprintf(b, ", did you mean to use %v?", e.suggestions[0]) + default: + b.WriteString(", did you mean to use one of ") + for i, k := range e.suggestions { + if i > 0 { + b.WriteString(", ") + if i == len(e.suggestions)-1 { + b.WriteString("or ") + } + } + fmt.Fprint(b, k) + } + b.WriteString("?") + } + + return b.String() +} + +// errMissingManyTypes combines multiple errMissingType errors. +type errMissingManyTypes []errMissingType // length must be non-zero + +func (e errMissingManyTypes) Error() string { + if len(e) == 1 { + return e[0].Error() + } + + b := new(bytes.Buffer) + + b.WriteString("the following types are not in the container: ") + for i, err := range e { + if i > 0 { + b.WriteString("; ") + } + fmt.Fprintf(b, "%v", err.Key) + switch len(err.suggestions) { + case 0: + // do nothing + case 1: + fmt.Fprintf(b, " (did you mean %v?)", err.suggestions[0]) + default: + b.WriteString(" (did you mean ") + for i, k := range err.suggestions { + if i > 0 { + b.WriteString(", ") + if i == len(err.suggestions)-1 { + b.WriteString("or ") + } + } + fmt.Fprint(b, k) + } + b.WriteString("?)") + } + } + + return b.String() +} + +type byTypeName []reflect.Type + +func (bs byTypeName) Len() int { + return len(bs) +} + +func (bs byTypeName) Less(i int, j int) bool { + return fmt.Sprint(bs[i]) < fmt.Sprint(bs[j]) +} + +func (bs byTypeName) Swap(i int, j int) { + bs[i], bs[j] = bs[j], bs[i] +} diff --git a/vendor/go.uber.org/dig/error_test.go b/vendor/go.uber.org/dig/error_test.go new file mode 100644 index 000000000..9d2090087 --- /dev/null +++ b/vendor/go.uber.org/dig/error_test.go @@ -0,0 +1,147 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package dig + +import ( + "errors" + "regexp" + "strconv" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestErrWrapf(t *testing.T) { + t.Run("nil", func(t *testing.T) { + err := errWrapf(nil, "hi") + assert.NoError(t, err, "expected no error") + assert.NoError(t, RootCause(err), "root cause must be nil") + }) + + t.Run("single wrap", func(t *testing.T) { + err := errors.New("great sadness") + werr := errWrapf(err, "something went %s", "wrong") + + assert.Equal(t, err, RootCause(werr), "root cause must match") + assert.Equal(t, "something went wrong: great sadness", werr.Error(), + "error message must match") + }) + + t.Run("double wrap", func(t *testing.T) { + err := errors.New("great sadness") + + werr := errWrapf(err, "something went %s", "wrong") + werr = errWrapf(werr, "something else went wrong") + + assert.Equal(t, err, RootCause(werr), "root cause must match") + assert.Equal(t, "something else went wrong: something went wrong: great sadness", werr.Error(), + "error message must match") + }) +} + +// assertErrorMatches matches error messages against the provided list of +// strings. +// +// The error must match each string in-order. That is, the following is valid, +// +// assertErrorMatches(t, errors.New("foo bar baz"), "foo", "baz") +// +// But not, +// +// assertErrorMatches(t, errors.New("foo bar baz"), "foo", "baz", "bar") +// +// Because "bar" is not after "baz" in the error message. +// +// Messages will be treated as regular expressions. +func assertErrorMatches(t testing.TB, err error, msg string, msgs ...string) { + // We have one positional argument in addition to the variadic argument to + // ensure that there's at least one string to match against. + if err == nil { + t.Errorf("expected error but got nil") + return + } + + var finders []consumingFinder + for _, m := range append([]string{msg}, msgs...) { + if r, err := regexp.Compile(m); err == nil { + finders = append(finders, regexpFinder{r}) + } else { + finders = append(finders, stringFinder(m)) + } + } + + original := err.Error() + remaining := original + for _, f := range finders { + if newRemaining, ok := f.Find(remaining); ok { + remaining = newRemaining + continue + } + + // Match not found. Check if the order was wrong. + if _, ok := f.Find(original); ok { + // We won't use %q for the error message itself because we want it + // to be printed to the console as it would actually show. + t.Errorf(`"%v" contains %v in the wrong place`, original, f) + } else { + t.Errorf(`"%v" does not contain %v`, original, f) + } + } +} + +// consumingFinder matches a string and returns the rest of the string *after* +// the match. +type consumingFinder interface { + // Attempt to match against the given string and return false if a match + // could not be found. + // + // If a match was found, return the remaining string after the entire + // match. So if the finder matches "oo" in "foobar", the returned string + // must be just "bar". + Find(got string) (rest string, ok bool) +} + +type regexpFinder struct{ r *regexp.Regexp } + +func (r regexpFinder) String() string { + return "`" + r.r.String() + "`" +} + +func (r regexpFinder) Find(got string) (rest string, ok bool) { + loc := r.r.FindStringIndex(got) + if len(loc) == 0 { + return got, false + } + return got[loc[1]:], true +} + +type stringFinder string + +func (s stringFinder) String() string { return strconv.Quote(string(s)) } + +func (s stringFinder) Find(got string) (rest string, ok bool) { + i := strings.Index(got, string(s)) + if i < 0 { + return got, false + } + return got[i+len(s):], true +} diff --git a/vendor/go.uber.org/dig/example_test.go b/vendor/go.uber.org/dig/example_test.go new file mode 100644 index 000000000..9c7419d8b --- /dev/null +++ b/vendor/go.uber.org/dig/example_test.go @@ -0,0 +1,69 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package dig_test + +import ( + "encoding/json" + "log" + "os" + + "go.uber.org/dig" +) + +func Example_minimal() { + type Config struct { + Prefix string + } + + c := dig.New() + + // Provide a Config object. This can fail to decode. + err := c.Provide(func() (*Config, error) { + // In a real program, the configuration will probably be read from a + // file. + var cfg Config + err := json.Unmarshal([]byte(`{"prefix": "[foo] "}`), &cfg) + return &cfg, err + }) + if err != nil { + panic(err) + } + + // Provide a way to build the logger based on the configuration. + err = c.Provide(func(cfg *Config) *log.Logger { + return log.New(os.Stdout, cfg.Prefix, 0) + }) + if err != nil { + panic(err) + } + + // Invoke a function that requires the logger, which in turn builds the + // Config first. + err = c.Invoke(func(l *log.Logger) { + l.Print("You've been invoked") + }) + if err != nil { + panic(err) + } + + // Output: + // [foo] You've been invoked +} diff --git a/vendor/go.uber.org/dig/glide.lock b/vendor/go.uber.org/dig/glide.lock new file mode 100644 index 000000000..f9d278460 --- /dev/null +++ b/vendor/go.uber.org/dig/glide.lock @@ -0,0 +1,17 @@ +hash: b6270b9f130f8c89f82156ca8d44cb16a3311c7a04746f2805f02c2f72bb58ec +updated: 2017-10-25T09:30:43.315666535-07:00 +imports: [] +testImports: +- name: github.com/davecgh/go-spew + version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d + subpackages: + - spew +- name: github.com/pmezard/go-difflib + version: d8ed2627bdf02c080bf22230dbb337003b7aba2d + subpackages: + - difflib +- name: github.com/stretchr/testify + version: d77da356e56a7428ad25149ca77381849a6a5232 + subpackages: + - assert + - require diff --git a/vendor/go.uber.org/dig/glide.yaml b/vendor/go.uber.org/dig/glide.yaml new file mode 100644 index 000000000..972b804cd --- /dev/null +++ b/vendor/go.uber.org/dig/glide.yaml @@ -0,0 +1,7 @@ +package: go.uber.org/dig +license: MIT +testImport: +- package: github.com/stretchr/testify + subpackages: + - assert + - require diff --git a/vendor/go.uber.org/dig/internal/digreflect/func.go b/vendor/go.uber.org/dig/internal/digreflect/func.go new file mode 100644 index 000000000..86d1ae8b5 --- /dev/null +++ b/vendor/go.uber.org/dig/internal/digreflect/func.go @@ -0,0 +1,103 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package digreflect + +import ( + "fmt" + "net/url" + "reflect" + "runtime" + "strings" +) + +// Func contains runtime information about a function. +type Func struct { + // Name of the function. + Name string + + // Name of the package in which this function is defined. + Package string + + // Path to the file in which this function is defined. + File string + + // Line number in the file at which this function is defined. + Line int +} + +// String returns a string representation of the function. +func (f *Func) String() string { + // "path/to/package".MyFunction (path/to/file.go:42) + return fmt.Sprintf("%q.%v (%v:%v)", f.Package, f.Name, f.File, f.Line) +} + +// InspectFunc inspects and returns runtime information about the given +// function. +func InspectFunc(function interface{}) *Func { + fptr := reflect.ValueOf(function).Pointer() + f := runtime.FuncForPC(fptr) + pkgName, funcName := splitFuncName(f.Name()) + fileName, lineNum := f.FileLine(fptr) + return &Func{ + Name: funcName, + Package: pkgName, + File: fileName, + Line: lineNum, + } +} + +const _vendor = "/vendor/" + +func splitFuncName(function string) (pname string, fname string) { + if len(function) == 0 { + return + } + + // We have something like "path.to/my/pkg.MyFunction". If the function is + // a closure, it is something like, "path.to/my/pkg.MyFunction.func1". + + idx := 0 + + // Everything up to the first "." after the last "/" is the package name. + // Everything after the "." is the full function name. + if i := strings.LastIndex(function, "/"); i >= 0 { + idx = i + } + if i := strings.Index(function[idx:], "."); i >= 0 { + idx += i + } + pname, fname = function[:idx], function[idx+1:] + + // The package may be vendored. + if i := strings.Index(pname, _vendor); i > 0 { + pname = pname[i+len(_vendor):] + } + + // Package names are URL-encoded to avoid ambiguity in the case where the + // package name contains ".git". Otherwise, "foo/bar.git.MyFunction" would + // mean that "git" is the top-level function and "MyFunction" is embedded + // inside it. + if unescaped, err := url.QueryUnescape(pname); err == nil { + pname = unescaped + } + + return +} diff --git a/vendor/go.uber.org/dig/internal/digreflect/func_test.go b/vendor/go.uber.org/dig/internal/digreflect/func_test.go new file mode 100644 index 000000000..f65c99b20 --- /dev/null +++ b/vendor/go.uber.org/dig/internal/digreflect/func_test.go @@ -0,0 +1,135 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package digreflect + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + myrepository "go.uber.org/dig/internal/digreflect/tests/myrepository.git" + mypackage "go.uber.org/dig/internal/digreflect/tests/myrepository.git/mypackage" +) + +func SomeExportedFunction() {} + +func unexportedFunction() {} + +func nestedFunctions() (nested1, nested2, nested3 func()) { + // we call the functions to satisfy the linter. + nested1 = func() {} + nested2 = func() { + nested3 = func() {} + } + nested2() // set nested3 + return +} + +func TestInspectFunc(t *testing.T) { + nested1, nested2, nested3 := nestedFunctions() + + tests := []struct { + desc string + give interface{} + wantName string + wantPackage string + + // We don't match the exact file name because $GOPATH can be anywhere + // on someone's system. Instead we'll match the suffix. + wantFileSuffix string + wantStringLike string + }{ + { + desc: "exported function", + give: SomeExportedFunction, + wantName: "SomeExportedFunction", + wantPackage: "go.uber.org/dig/internal/digreflect", + wantFileSuffix: "go.uber.org/dig/internal/digreflect/func_test.go", + }, + { + desc: "unexported function", + give: unexportedFunction, + wantName: "unexportedFunction", + wantPackage: "go.uber.org/dig/internal/digreflect", + wantFileSuffix: "go.uber.org/dig/internal/digreflect/func_test.go", + }, + { + desc: "nested function", + give: nested1, + wantName: "nestedFunctions.func1", + wantPackage: "go.uber.org/dig/internal/digreflect", + wantFileSuffix: "go.uber.org/dig/internal/digreflect/func_test.go", + }, + { + desc: "second nested function", + give: nested2, + wantName: "nestedFunctions.func2", + wantPackage: "go.uber.org/dig/internal/digreflect", + wantFileSuffix: "go.uber.org/dig/internal/digreflect/func_test.go", + }, + { + desc: "nested inside a nested function", + give: nested3, + wantName: "nestedFunctions.func2.1", + wantPackage: "go.uber.org/dig/internal/digreflect", + wantFileSuffix: "go.uber.org/dig/internal/digreflect/func_test.go", + }, + { + desc: "inside a .git package", + give: myrepository.Hello, + wantName: "Hello", + wantPackage: "go.uber.org/dig/internal/digreflect/tests/myrepository.git", + wantFileSuffix: "go.uber.org/dig/internal/digreflect/tests/myrepository.git/hello.go", + }, + { + desc: "subpackage of a .git package", + give: mypackage.Add, + wantName: "Add", + wantPackage: "go.uber.org/dig/internal/digreflect/tests/myrepository.git/mypackage", + wantFileSuffix: "go.uber.org/dig/internal/digreflect/tests/myrepository.git/mypackage/add.go", + }, + { + desc: "vendored dependency", + give: myrepository.VendoredDependencyFunction(), + wantName: "Panic", + wantPackage: "mydependency", + wantFileSuffix: "go.uber.org/dig/internal/digreflect/tests/myrepository.git/vendor/mydependency/panic.go", + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + f := InspectFunc(tt.give) + assert.Equal(t, tt.wantName, f.Name, "function name did not match") + assert.Equal(t, tt.wantPackage, f.Package, "package name did not match") + + assert.True(t, strings.HasSuffix(f.File, "src/"+tt.wantFileSuffix), + "file path %q does not end with src/%v", f.File, tt.wantFileSuffix) + assert.Contains(t, f.String(), tt.wantFileSuffix, "file path not in String output") + }) + } +} + +func TestSplitFuncEmptyString(t *testing.T) { + pname, fname := splitFuncName("") + assert.Empty(t, pname, "package name must be empty") + assert.Empty(t, fname, "function name must be empty") +} diff --git a/vendor/go.uber.org/dig/param.go b/vendor/go.uber.org/dig/param.go new file mode 100644 index 000000000..007d629b0 --- /dev/null +++ b/vendor/go.uber.org/dig/param.go @@ -0,0 +1,411 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package dig + +import ( + "errors" + "fmt" + "math/rand" + "reflect" +) + +// The param interface represents a dependency for a constructor. +// +// The following implementations exist: +// paramList All arguments of the constructor. +// paramSingle An explicitly requested type. +// paramObject dig.In struct where each field in the struct can be another +// param. +// paramGroupedSlice +// A slice consuming a value group. This will receive all +// values produced with a `group:".."` tag with the same name +// as a slice. +type param interface { + fmt.Stringer + + // Builds this dependency and any of its dependencies from the provided + // Container. + // + // This MAY panic if the param does not produce a single value. + Build(*Container) (reflect.Value, error) +} + +var ( + _ param = paramSingle{} + _ param = paramObject{} + _ param = paramList{} + _ param = paramGroupedSlice{} +) + +// newParam builds a param from the given type. If the provided type is a +// dig.In struct, an paramObject will be returned. +func newParam(t reflect.Type) (param, error) { + switch { + case IsOut(t) || (t.Kind() == reflect.Ptr && IsOut(t.Elem())) || embedsType(t, _outPtrType): + return nil, fmt.Errorf("cannot depend on result objects: %v embeds a dig.Out", t) + case IsIn(t): + return newParamObject(t) + case embedsType(t, _inPtrType): + return nil, fmt.Errorf( + "cannot build a parameter object by embedding *dig.In, embed dig.In instead: "+ + "%v embeds *dig.In", t) + case t.Kind() == reflect.Ptr && IsIn(t.Elem()): + return nil, fmt.Errorf( + "cannot depend on a pointer to a parameter object, use a value instead: "+ + "%v is a pointer to a struct that embeds dig.In", t) + default: + return paramSingle{Type: t}, nil + } +} + +// paramVisitor visits every param in a param tree, allowing tracking state at +// each level. +type paramVisitor interface { + // Visit is called on the param being visited. + // + // If Visit returns a non-nil paramVisitor, that paramVisitor visits all + // the child params of this param. + Visit(param) paramVisitor + + // We can implement AnnotateWithField and AnnotateWithPosition like + // resultVisitor if we need to track that information in the future. +} + +// paramVisitorFunc is a paramVisitor that visits param in a tree with the +// return value deciding whether the descendants of this param should be +// recursed into. +type paramVisitorFunc func(param) (recurse bool) + +func (f paramVisitorFunc) Visit(p param) paramVisitor { + if f(p) { + return f + } + return nil +} + +// walkParam walks the param tree for the given param with the provided +// visitor. +// +// paramVisitor.Visit will be called on the provided param and if a non-nil +// paramVisitor is received, this param's descendants will be walked with that +// visitor. +// +// This is very similar to how go/ast.Walk works. +func walkParam(p param, v paramVisitor) { + v = v.Visit(p) + if v == nil { + return + } + + switch par := p.(type) { + case paramSingle, paramGroupedSlice: + // No sub-results + case paramObject: + for _, f := range par.Fields { + walkParam(f.Param, v) + } + case paramList: + for _, p := range par.Params { + walkParam(p, v) + } + default: + panic(fmt.Sprintf( + "It looks like you have found a bug in dig. "+ + "Please file an issue at https://github.com/uber-go/dig/issues/ "+ + "and provide the following message: "+ + "received unknown param type %T", p)) + } +} + +// paramList holds all arguments of the constructor as params. +// +// NOTE: Build() MUST NOT be called on paramList. Instead, BuildList +// must be called. +type paramList struct { + ctype reflect.Type // type of the constructor + + Params []param +} + +// newParamList builds a paramList from the provided constructor type. +// +// Variadic arguments of a constructor are ignored and not included as +// dependencies. +func newParamList(ctype reflect.Type) (paramList, error) { + numArgs := ctype.NumIn() + if ctype.IsVariadic() { + // NOTE: If the function is variadic, we skip the last argument + // because we're not filling variadic arguments yet. See #120. + numArgs-- + } + + pl := paramList{ + ctype: ctype, + Params: make([]param, 0, numArgs), + } + + for i := 0; i < numArgs; i++ { + p, err := newParam(ctype.In(i)) + if err != nil { + return pl, errWrapf(err, "bad argument %d", i+1) + } + pl.Params = append(pl.Params, p) + } + + return pl, nil +} + +func (pl paramList) Build(*Container) (reflect.Value, error) { + panic("It looks like you have found a bug in dig. " + + "Please file an issue at https://github.com/uber-go/dig/issues/ " + + "and provide the following message: " + + "paramList.Build() must never be called") +} + +// BuildList returns an ordered list of values which may be passed directly +// to the underlying constructor. +func (pl paramList) BuildList(c *Container) ([]reflect.Value, error) { + args := make([]reflect.Value, len(pl.Params)) + for i, p := range pl.Params { + var err error + args[i], err = p.Build(c) + if err != nil { + return nil, err + } + } + return args, nil +} + +// paramSingle is an explicitly requested type, optionally with a name. +// +// This object must be present in the graph as-is unless it's specified as +// optional. +type paramSingle struct { + Name string + Optional bool + Type reflect.Type +} + +func (ps paramSingle) Build(c *Container) (reflect.Value, error) { + k := key{name: ps.Name, t: ps.Type} + if v, ok := c.values[k]; ok { + return v, nil + } + + nodes := c.providers[k] + if len(nodes) == 0 { + if ps.Optional { + return reflect.Zero(ps.Type), nil + } + + return _noValue, newErrMissingType(c, k) + } + + for _, n := range nodes { + err := n.Call(c) + if err == nil { + continue + } + + // If we're missing dependencies but the parameter itself is optional, + // we can just move on. + if _, ok := err.(errMissingDependencies); ok && ps.Optional { + return reflect.Zero(ps.Type), nil + } + + return _noValue, errParamSingleFailed{Key: k, Reason: err} + } + + return c.values[k], nil +} + +// paramObject is a dig.In struct where each field is another param. +// +// This object is not expected in the graph as-is. +type paramObject struct { + Type reflect.Type + Fields []paramObjectField +} + +// newParamObject builds an paramObject from the provided type. The type MUST +// be a dig.In struct. +func newParamObject(t reflect.Type) (paramObject, error) { + po := paramObject{Type: t} + + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + if f.Type == _inType { + // Skip over the dig.In embed. + continue + } + + pof, err := newParamObjectField(i, f) + if err != nil { + return po, errWrapf(err, "bad field %q of %v", f.Name, t) + } + + po.Fields = append(po.Fields, pof) + } + + return po, nil +} + +func (po paramObject) Build(c *Container) (reflect.Value, error) { + dest := reflect.New(po.Type).Elem() + for _, f := range po.Fields { + v, err := f.Build(c) + if err != nil { + return dest, err + } + dest.Field(f.FieldIndex).Set(v) + } + return dest, nil +} + +// paramObjectField is a single field of a dig.In struct. +type paramObjectField struct { + // Name of the field in the struct. + FieldName string + + // Index of this field in the target struct. + // + // We need to track this separately because not all fields of the + // struct map to params. + FieldIndex int + + // The dependency requested by this field. + Param param +} + +func newParamObjectField(idx int, f reflect.StructField) (paramObjectField, error) { + pof := paramObjectField{ + FieldName: f.Name, + FieldIndex: idx, + } + + var p param + switch { + case f.PkgPath != "": + return pof, fmt.Errorf( + "unexported fields not allowed in dig.In, did you mean to export %q (%v)?", + f.Name, f.Type) + + case f.Tag.Get(_groupTag) != "": + var err error + p, err = newParamGroupedSlice(f) + if err != nil { + return pof, err + } + + default: + var err error + p, err = newParam(f.Type) + if err != nil { + return pof, err + } + } + + if ps, ok := p.(paramSingle); ok { + ps.Name = f.Tag.Get(_nameTag) + + var err error + ps.Optional, err = isFieldOptional(f) + if err != nil { + return pof, err + } + + p = ps + } + + pof.Param = p + return pof, nil +} + +func (pof paramObjectField) Build(c *Container) (reflect.Value, error) { + v, err := pof.Param.Build(c) + if err != nil { + return v, err + } + return v, nil +} + +// paramGroupedSlice is a param which produces a slice of values with the same +// group name. +type paramGroupedSlice struct { + // Name of the group as specified in the `group:".."` tag. + Group string + + // Type of the slice. + Type reflect.Type +} + +// newParamGroupedSlice builds a paramGroupedSlice from the provided type with +// the given name. +// +// The type MUST be a slice type. +func newParamGroupedSlice(f reflect.StructField) (paramGroupedSlice, error) { + pg := paramGroupedSlice{Group: f.Tag.Get(_groupTag), Type: f.Type} + + name := f.Tag.Get(_nameTag) + optional, _ := isFieldOptional(f) + switch { + case f.Type.Kind() != reflect.Slice: + return pg, fmt.Errorf("value groups may be consumed as slices only: "+ + "field %q (%v) is not a slice", f.Name, f.Type) + case name != "": + return pg, fmt.Errorf( + "cannot use named values with value groups: name:%q requested with group:%q", name, pg.Group) + + case optional: + return pg, errors.New("value groups cannot be optional") + } + + return pg, nil +} + +func (pt paramGroupedSlice) Build(c *Container) (reflect.Value, error) { + k := key{group: pt.Group, t: pt.Type.Elem()} + + for _, n := range c.providers[k] { + if err := n.Call(c); err != nil { + return _noValue, errParamGroupFailed{Key: k, Reason: err} + } + } + + items := c.groups[k] + + // shuffle the list so users don't rely on the ordering of grouped values + items = shuffledCopy(c.rand, items) + + result := reflect.MakeSlice(pt.Type, len(items), len(items)) + for i, v := range items { + result.Index(i).Set(v) + } + return result, nil +} + +func shuffledCopy(rand *rand.Rand, items []reflect.Value) []reflect.Value { + newItems := make([]reflect.Value, len(items)) + for i, j := range rand.Perm(len(items)) { + newItems[i] = items[j] + } + return newItems +} diff --git a/vendor/go.uber.org/dig/param_test.go b/vendor/go.uber.org/dig/param_test.go new file mode 100644 index 000000000..2064cf53a --- /dev/null +++ b/vendor/go.uber.org/dig/param_test.go @@ -0,0 +1,165 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package dig + +import ( + "io" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParamListBuild(t *testing.T) { + p, err := newParamList(reflect.TypeOf(func() io.Writer { return nil })) + require.NoError(t, err) + assert.Panics(t, func() { + p.Build(New()) + }) +} + +func TestParamObjectSuccess(t *testing.T) { + type type1 struct{} + type type2 struct{} + type type3 struct{} + + type in struct { + In + + T1 type1 + T2 type2 `optional:"true"` + T3 type3 `name:"foo"` + + Nested struct { + In + + A string + B int32 + } `name:"bar"` + } + + po, err := newParamObject(reflect.TypeOf(in{})) + require.NoError(t, err) + + require.Len(t, po.Fields, 4) + + t.Run("no tags", func(t *testing.T) { + require.Equal(t, "T1", po.Fields[0].FieldName) + t1, ok := po.Fields[0].Param.(paramSingle) + require.True(t, ok, "T1 must be a paramSingle") + assert.Empty(t, t1.Name) + assert.False(t, t1.Optional) + + }) + + t.Run("optional field", func(t *testing.T) { + require.Equal(t, "T2", po.Fields[1].FieldName) + + t2, ok := po.Fields[1].Param.(paramSingle) + require.True(t, ok, "T2 must be a paramSingle") + assert.Empty(t, t2.Name) + assert.True(t, t2.Optional) + + }) + + t.Run("named value", func(t *testing.T) { + require.Equal(t, "T3", po.Fields[2].FieldName) + t3, ok := po.Fields[2].Param.(paramSingle) + require.True(t, ok, "T3 must be a paramSingle") + assert.Equal(t, "foo", t3.Name) + assert.False(t, t3.Optional) + }) + + t.Run("tags don't apply to nested dig.In", func(t *testing.T) { + require.Equal(t, "Nested", po.Fields[3].FieldName) + nested, ok := po.Fields[3].Param.(paramObject) + require.True(t, ok, "Nested must be a paramObject") + + assert.Len(t, nested.Fields, 2) + a, ok := nested.Fields[0].Param.(paramSingle) + require.True(t, ok, "Nested.A must be a paramSingle") + assert.Empty(t, a.Name, "Nested.A must not have a name") + }) +} + +func TestParamObjectFailure(t *testing.T) { + t.Run("unexported field gets an error", func(t *testing.T) { + type A struct{} + type in struct { + In + + A1 A + a2 A + } + + _, err := newParamObject(reflect.TypeOf(in{})) + require.Error(t, err) + assert.Contains(t, err.Error(), + `bad field "a2" of dig.in: unexported fields not allowed in dig.In, did you mean to export "a2" (dig.A)`) + }) +} + +func TestParamGroupSliceErrors(t *testing.T) { + tests := []struct { + desc string + shape interface{} + wantErr string + }{ + { + desc: "non-slice type are disallowed", + shape: struct { + In + + Foo string `group:"foo"` + }{}, + wantErr: "value groups may be consumed as slices only: " + + `field "Foo" (string) is not a slice`, + }, + { + desc: "cannot provide name for a group", + shape: struct { + In + + Foo []string `group:"foo" name:"bar"` + }{}, + wantErr: "cannot use named values with value groups: " + + `name:"bar" requested with group:"foo"`, + }, + { + desc: "cannot be optional", + shape: struct { + In + + Foo []string `group:"foo" optional:"true"` + }{}, + wantErr: "value groups cannot be optional", + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + _, err := newParamObject(reflect.TypeOf(tt.shape)) + require.Error(t, err, "expected failure") + assert.Contains(t, err.Error(), tt.wantErr) + }) + } +} diff --git a/vendor/go.uber.org/dig/result.go b/vendor/go.uber.org/dig/result.go new file mode 100644 index 000000000..b99a1de35 --- /dev/null +++ b/vendor/go.uber.org/dig/result.go @@ -0,0 +1,346 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package dig + +import ( + "errors" + "fmt" + "reflect" +) + +// The result interface represents a result produced by a constructor. +// +// The following implementations exist: +// resultList All values returned by the constructor. +// resultSingle A single value produced by a constructor. +// resultError An error returned by the constructor. +// resultObject dig.Out struct where each field in the struct can be +// another result. +// resultGrouped A value produced by a constructor that is part of a value +// group. +type result interface { + // Extracts the values for this result from the provided value and + // stores them into the provided resultReceiver. + // + // This MAY panic if the result does not consume a single value. + Extract(resultReceiver, reflect.Value) +} + +// resultReceiver receives the values or failures produced by constructors. +type resultReceiver interface { + // Notifies the receiver that the constructor failed with the given error. + SubmitError(error) + + // Submits a new value to the receiver. + SubmitValue(name string, t reflect.Type, v reflect.Value) + + // Submits a new value to a value group. + SubmitGroupValue(group string, t reflect.Type, v reflect.Value) +} + +var ( + _ result = resultSingle{} + _ result = resultError{} + _ result = resultObject{} + _ result = resultList{} + _ result = resultGrouped{} +) + +// newResult builds a result from the given type. +func newResult(t reflect.Type) (result, error) { + switch { + case IsIn(t) || (t.Kind() == reflect.Ptr && IsIn(t.Elem())) || embedsType(t, _inPtrType): + return nil, fmt.Errorf("cannot provide parameter objects: %v embeds a dig.In", t) + case isError(t): + return resultError{}, nil + case IsOut(t): + return newResultObject(t) + case embedsType(t, _outPtrType): + return nil, fmt.Errorf( + "cannot build a result object by embedding *dig.Out, embed dig.Out instead: "+ + "%v embeds *dig.Out", t) + case t.Kind() == reflect.Ptr && IsOut(t.Elem()): + return nil, fmt.Errorf( + "cannot return a pointer to a result object, use a value instead: "+ + "%v is a pointer to a struct that embeds dig.Out", t) + default: + return resultSingle{Type: t}, nil + } +} + +// resultVisitor visits every result in a result tree, allowing tracking state +// at each level. +type resultVisitor interface { + // Visit is called on the result being visited. + // + // If Visit returns a non-nil resultVisitor, that resultVisitor visits all + // the child results of this result. + Visit(result) resultVisitor + + // AnnotateWithField is called on each field of a resultObject after + // visiting it but before walking its descendants. + // + // The same resultVisitor is used for all fields: the one returned upon + // visiting the resultObject. + // + // For each visited field, if AnnotateWithField returns a non-nil + // resultVisitor, it will be used to walk the result of that field. + AnnotateWithField(resultObjectField) resultVisitor + + // AnnotateWithPosition is called with the index of each result of a + // resultList after vising it but before walking its descendants. + // + // The same resultVisitor is used for all results: the one returned upon + // visiting the resultList. + // + // For each position, if AnnotateWithPosition returns a non-nil + // resultVisitor, it will be used to walk the result at that index. + AnnotateWithPosition(idx int) resultVisitor +} + +// walkResult walks the result tree for the given result with the provided +// visitor. +// +// resultVisitor.Visit will be called on the provided result and if a non-nil +// resultVisitor is received, it will be used to walk its descendants. If a +// resultObject or resultList was visited, AnnotateWithField and +// AnnotateWithPosition respectively will be called before visiting the +// descendants of that resultObject/resultList. +// +// This is very similar to how go/ast.Walk works. +func walkResult(r result, v resultVisitor) { + v = v.Visit(r) + if v == nil { + return + } + + switch res := r.(type) { + case resultSingle, resultError, resultGrouped: + // No sub-results + case resultObject: + w := v + for _, f := range res.Fields { + if v := w.AnnotateWithField(f); v != nil { + walkResult(f.Result, v) + } + } + case resultList: + w := v + for i, r := range res.Results { + if v := w.AnnotateWithPosition(i); v != nil { + walkResult(r, v) + } + } + default: + panic(fmt.Sprintf( + "It looks like you have found a bug in dig. "+ + "Please file an issue at https://github.com/uber-go/dig/issues/ "+ + "and provide the following message: "+ + "received unknown result type %T", res)) + } +} + +// resultList holds all values returned by the constructor as results. +type resultList struct { + ctype reflect.Type + + Results []result +} + +func newResultList(ctype reflect.Type) (resultList, error) { + rl := resultList{ + ctype: ctype, + Results: make([]result, ctype.NumOut()), + } + + for i := 0; i < ctype.NumOut(); i++ { + r, err := newResult(ctype.Out(i)) + if err != nil { + return rl, errWrapf(err, "bad result %d", i+1) + } + rl.Results[i] = r + } + + return rl, nil +} + +func (resultList) Extract(resultReceiver, reflect.Value) { + panic("It looks like you have found a bug in dig. " + + "Please file an issue at https://github.com/uber-go/dig/issues/ " + + "and provide the following message: " + + "resultList.Extract() must never be called") +} + +func (rl resultList) ExtractList(rr resultReceiver, values []reflect.Value) { + for i, r := range rl.Results { + r.Extract(rr, values[i]) + } +} + +// resultError is an error returned by a constructor. +type resultError struct{} + +func (resultError) Extract(rr resultReceiver, v reflect.Value) { + if err, _ := v.Interface().(error); err != nil { + rr.SubmitError(err) + } +} + +// resultSingle is an explicit value produced by a constructor, optionally +// with a name. +// +// This object will be added to the graph as-is. +type resultSingle struct { + Name string + Type reflect.Type +} + +func (rs resultSingle) Extract(rr resultReceiver, v reflect.Value) { + rr.SubmitValue(rs.Name, rs.Type, v) +} + +// resultObject is a dig.Out struct where each field is another result. +// +// This object is not added to the graph. Its fields are interpreted as +// results and added to the graph if needed. +type resultObject struct { + Type reflect.Type + Fields []resultObjectField +} + +func newResultObject(t reflect.Type) (resultObject, error) { + ro := resultObject{Type: t} + + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + if f.Type == _outType { + // Skip over the dig.Out embed. + continue + } + + rof, err := newResultObjectField(i, f) + if err != nil { + return ro, errWrapf(err, "bad field %q of %v", f.Name, t) + } + + ro.Fields = append(ro.Fields, rof) + } + return ro, nil +} + +func (ro resultObject) Extract(rr resultReceiver, v reflect.Value) { + for _, f := range ro.Fields { + f.Result.Extract(rr, v.Field(f.FieldIndex)) + } +} + +// resultObjectField is a single field inside a dig.Out struct. +type resultObjectField struct { + // Name of the field in the struct. + FieldName string + + // Index of the field in the struct. + // + // We need to track this separately because not all fields of the struct + // map to results. + FieldIndex int + + // Result produced by this field. + Result result +} + +// newResultObjectField(i, f) builds a resultObjectField from the field f at +// index i. +func newResultObjectField(idx int, f reflect.StructField) (resultObjectField, error) { + rof := resultObjectField{ + FieldName: f.Name, + FieldIndex: idx, + } + + var r result + switch { + case f.PkgPath != "": + return rof, fmt.Errorf( + "unexported fields not allowed in dig.Out, did you mean to export %q (%v)?", f.Name, f.Type) + + case isError(f.Type): + return rof, fmt.Errorf( + "cannot return errors from dig.Out, return it from the constructor instead: "+ + "field %q (%v) is an error field", + f.Name, f.Type) + + case f.Tag.Get(_groupTag) != "": + var err error + r, err = newResultGrouped(f) + if err != nil { + return rof, err + } + + default: + var err error + r, err = newResult(f.Type) + if err != nil { + return rof, err + } + } + + if rs, ok := r.(resultSingle); ok { + // Field tags apply only if the result is "simple" + rs.Name = f.Tag.Get(_nameTag) + r = rs + } + + rof.Result = r + return rof, nil +} + +// resultGrouped is a value produced by a constructor that is part of a result +// group. +// +// These will be produced as fields of a dig.Out struct. +type resultGrouped struct { + // Name of the group as specified in the `group:".."` tag. + Group string + + // Type of value produced. + Type reflect.Type +} + +// newResultGrouped(f) builds a new resultGrouped from the provided field. +func newResultGrouped(f reflect.StructField) (resultGrouped, error) { + rg := resultGrouped{Group: f.Tag.Get(_groupTag), Type: f.Type} + + name := f.Tag.Get(_nameTag) + optional, _ := isFieldOptional(f) + switch { + case name != "": + return rg, fmt.Errorf( + "cannot use named values with value groups: name:%q provided with group:%q", name, rg.Group) + case optional: + return rg, errors.New("value groups cannot be optional") + } + + return rg, nil +} + +func (rt resultGrouped) Extract(rr resultReceiver, v reflect.Value) { + rr.SubmitGroupValue(rt.Group, rt.Type, v) +} diff --git a/vendor/go.uber.org/dig/result_test.go b/vendor/go.uber.org/dig/result_test.go new file mode 100644 index 000000000..4799adbcc --- /dev/null +++ b/vendor/go.uber.org/dig/result_test.go @@ -0,0 +1,321 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package dig + +import ( + "fmt" + "io" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewResultListErrors(t *testing.T) { + tests := []struct { + desc string + give interface{} + }{ + { + desc: "returns dig.In", + give: func() struct{ In } { panic("invalid") }, + }, + { + desc: "returns dig.Out+dig.In", + give: func() struct { + Out + In + } { + panic("invalid") + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + _, err := newResultList(reflect.TypeOf(tt.give)) + require.Error(t, err) + assertErrorMatches(t, err, + "bad result 1:", + "cannot provide parameter objects:", + "embeds a dig.In") + }) + } +} + +func TestResultListExtractFails(t *testing.T) { + rl, err := newResultList(reflect.TypeOf(func() (io.Writer, error) { + panic("function should not be called") + })) + require.NoError(t, err) + assert.Panics(t, func() { + rl.Extract(newStagingReceiver(), reflect.ValueOf("irrelevant")) + }) +} + +func TestNewResultErrors(t *testing.T) { + type outPtr struct{ *Out } + type out struct{ Out } + type in struct{ In } + type inOut struct { + In + Out + } + + tests := []struct { + give interface{} + err string + }{ + { + give: outPtr{}, + err: "cannot build a result object by embedding *dig.Out, embed dig.Out instead: dig.outPtr embeds *dig.Out", + }, + { + give: (*out)(nil), + err: "cannot return a pointer to a result object, use a value instead: *dig.out is a pointer to a struct that embeds dig.Out", + }, + { + give: in{}, + err: "cannot provide parameter objects: dig.in embeds a dig.In", + }, + { + give: inOut{}, + err: "cannot provide parameter objects: dig.inOut embeds a dig.In", + }, + } + + for _, tt := range tests { + give := reflect.TypeOf(tt.give) + t.Run(fmt.Sprint(give), func(t *testing.T) { + _, err := newResult(give) + require.Error(t, err) + assert.Contains(t, err.Error(), tt.err) + }) + } +} + +func TestNewResultObjectErrors(t *testing.T) { + tests := []struct { + desc string + give interface{} + err string + }{ + { + desc: "unexported fields", + give: struct { + Out + + writer io.Writer + }{}, + err: `unexported fields not allowed in dig.Out, did you mean to export "writer" (io.Writer)`, + }, + { + desc: "error field", + give: struct { + Out + + Error error + }{}, + err: `cannot return errors from dig.Out, return it from the constructor instead: field "Error" (error)`, + }, + { + desc: "nested dig.In", + give: struct { + Out + + Nested struct{ In } + }{}, + err: `bad field "Nested"`, + }, + { + desc: "group with name should fail", + give: struct { + Out + + Foo string `group:"foo" name:"bar"` + }{}, + err: "cannot use named values with value groups: " + + `name:"bar" provided with group:"foo"`, + }, + { + desc: "group marked as optional", + give: struct { + Out + + Foo string `group:"foo" optional:"true"` + }{}, + err: "value groups cannot be optional", + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + _, err := newResultObject(reflect.TypeOf(tt.give)) + require.Error(t, err) + assert.Contains(t, err.Error(), tt.err) + }) + } +} + +type fakeResultVisit struct { + Visit result + AnnotateWithField *resultObjectField + AnnotateWithPosition int + Return fakeResultVisits +} + +func (fv fakeResultVisit) String() string { + switch { + case fv.Visit != nil: + return fmt.Sprintf("Visit(%#v) -> %v", fv.Visit, fv.Return) + case fv.AnnotateWithField != nil: + return fmt.Sprintf("AnnotateWithField(%#v) -> %v", *fv.AnnotateWithField, fv.Return) + default: + return fmt.Sprintf("AnnotateWithPosition(%v) -> %v", fv.AnnotateWithPosition, fv.Return) + } +} + +type fakeResultVisits []fakeResultVisit + +func (vs fakeResultVisits) Visitor(t *testing.T) resultVisitor { + return &fakeResultVisitor{t: t, visits: vs} +} + +type fakeResultVisitor struct { + t *testing.T + visits fakeResultVisits +} + +func (fv *fakeResultVisitor) popNext(call string) fakeResultVisit { + if len(fv.visits) == 0 { + fv.t.Fatalf("received unexpected call %v: no more calls were expected", call) + } + + visit := fv.visits[0] + fv.visits = fv.visits[1:] + return visit +} + +func (fv *fakeResultVisitor) Visit(r result) resultVisitor { + v := fv.popNext(fmt.Sprintf("Visit(%#v)", r)) + if !reflect.DeepEqual(r, v.Visit) { + fv.t.Fatalf("received unexpected call Visit(%#v)\nexpected %v", r, v) + } + return &fakeResultVisitor{t: fv.t, visits: v.Return} +} + +func (fv *fakeResultVisitor) AnnotateWithField(f resultObjectField) resultVisitor { + v := fv.popNext(fmt.Sprintf("AnnotateWithField(%#v)", f)) + if v.AnnotateWithField == nil || !reflect.DeepEqual(f, *v.AnnotateWithField) { + fv.t.Fatalf("received unexpected call AnnotateWithField(%#v)\nexpected %v", f, v) + } + return &fakeResultVisitor{t: fv.t, visits: v.Return} +} + +func (fv *fakeResultVisitor) AnnotateWithPosition(i int) resultVisitor { + v := fv.popNext(fmt.Sprintf("AnnotateWithPosition(%v)", i)) + if i != v.AnnotateWithPosition { + fv.t.Fatalf("received unexpected call AnnotateWithPosition(%v)\nexpected %v", i, v) + } + return &fakeResultVisitor{t: fv.t, visits: v.Return} +} + +func TestWalkResult(t *testing.T) { + t.Run("invalid result type", func(t *testing.T) { + type badResult struct{ result } + visitor := fakeResultVisits{ + {Visit: badResult{}, Return: fakeResultVisits{}}, + }.Visitor(t) + assert.Panics(t, + func() { + walkResult(badResult{}, visitor) + }) + }) + + t.Run("resultObject ordering", func(t *testing.T) { + type type1 struct{} + type type2 struct{} + type type3 struct{} + type type4 struct{} + + typ := reflect.TypeOf(struct { + Out + + T1 type1 + T2 type2 + + Nested struct { + Out + + T3 type3 + T4 type4 + } + }{}) + + ro, err := newResultObject(typ) + require.NoError(t, err) + + v := fakeResultVisits{ + { + Visit: ro, + Return: fakeResultVisits{ + { + AnnotateWithField: &ro.Fields[0], + Return: fakeResultVisits{ + {Visit: ro.Fields[0].Result}, + }, + }, + { + AnnotateWithField: &ro.Fields[1], + Return: fakeResultVisits{ + {Visit: ro.Fields[1].Result}, + }, + }, + { + AnnotateWithField: &ro.Fields[2], + Return: fakeResultVisits{ + { + Visit: ro.Fields[2].Result, + Return: fakeResultVisits{ + { + AnnotateWithField: &ro.Fields[2].Result.(resultObject).Fields[0], + Return: fakeResultVisits{ + {Visit: ro.Fields[2].Result.(resultObject).Fields[0].Result}, + }, + }, + { + AnnotateWithField: &ro.Fields[2].Result.(resultObject).Fields[1], + Return: fakeResultVisits{ + {Visit: ro.Fields[2].Result.(resultObject).Fields[1].Result}, + }, + }, + }, + }, + }, + }, + }, + }, + }.Visitor(t) + + walkResult(ro, v) + }) +} diff --git a/vendor/go.uber.org/dig/stringer.go b/vendor/go.uber.org/dig/stringer.go new file mode 100644 index 000000000..4eb31d46d --- /dev/null +++ b/vendor/go.uber.org/dig/stringer.go @@ -0,0 +1,106 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package dig + +import ( + "bytes" + "fmt" + "strings" +) + +// String representation of the entire Container +func (c *Container) String() string { + b := &bytes.Buffer{} + fmt.Fprintln(b, "nodes: {") + for k, vs := range c.providers { + for _, v := range vs { + fmt.Fprintln(b, "\t", k, "->", v) + } + } + fmt.Fprintln(b, "}") + + fmt.Fprintln(b, "values: {") + for k, v := range c.values { + fmt.Fprintln(b, "\t", k, "=>", v) + } + for k, vs := range c.groups { + for _, v := range vs { + fmt.Fprintln(b, "\t", k, "=>", v) + } + } + fmt.Fprintln(b, "}") + + return b.String() +} + +func (n *node) String() string { + return fmt.Sprintf("deps: %v, ctor: %v", n.Params, n.ctype) +} + +func (k key) String() string { + if k.name != "" { + return fmt.Sprintf("%v[name=%q]", k.t, k.name) + } + if k.group != "" { + return fmt.Sprintf("%v[group=%q]", k.t, k.group) + } + return k.t.String() +} + +func (pl paramList) String() string { + args := make([]string, len(pl.Params)) + for i, p := range pl.Params { + args[i] = p.String() + } + return fmt.Sprint(args) +} + +func (sp paramSingle) String() string { + // tally.Scope[optional] means optional + // tally.Scope[optional, name="foo"] means named optional + + var opts []string + if sp.Optional { + opts = append(opts, "optional") + } + if sp.Name != "" { + opts = append(opts, fmt.Sprintf("name=%q", sp.Name)) + } + + if len(opts) == 0 { + return fmt.Sprint(sp.Type) + } + + return fmt.Sprintf("%v[%v]", sp.Type, strings.Join(opts, ", ")) +} + +func (op paramObject) String() string { + fields := make([]string, len(op.Fields)) + for i, f := range op.Fields { + fields[i] = f.Param.String() + } + return strings.Join(fields, " ") +} + +func (pt paramGroupedSlice) String() string { + // io.Reader[group="foo"] refers to a group of io.Readers called 'foo' + return fmt.Sprintf("%v[group=%q]", pt.Type.Elem(), pt.Group) +} diff --git a/vendor/go.uber.org/dig/stringer_test.go b/vendor/go.uber.org/dig/stringer_test.go new file mode 100644 index 000000000..05e10fa33 --- /dev/null +++ b/vendor/go.uber.org/dig/stringer_test.go @@ -0,0 +1,108 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package dig + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStringer(t *testing.T) { + type A struct{} + type B struct{} + type C struct{} + type D struct{} + + type in struct { + In + + A A `name:"foo"` + B B `optional:"true"` + C C `name:"bar" optional:"true"` + + Strings []string `group:"baz"` + } + + type out struct { + Out + + A A `name:"foo"` + C C `name:"bar"` + } + + type stringOut struct { + Out + + S string `group:"baz"` + } + + c := New(setRand(rand.New(rand.NewSource(0)))) + + require.NoError(t, c.Provide(func(i in) D { + assert.Equal(t, []string{"bar", "baz", "foo"}, i.Strings) + return D{} + })) + + require.NoError(t, c.Provide(func() out { + return out{ + A: A{}, + C: C{}, + } + })) + + require.NoError(t, c.Provide(func() A { return A{} })) + require.NoError(t, c.Provide(func() B { return B{} })) + require.NoError(t, c.Provide(func() C { return C{} })) + + require.NoError(t, c.Provide(func(A) stringOut { return stringOut{S: "foo"} })) + require.NoError(t, c.Provide(func(B) stringOut { return stringOut{S: "bar"} })) + require.NoError(t, c.Provide(func(C) stringOut { return stringOut{S: "baz"} })) + + require.NoError(t, c.Invoke(func(D) { + })) + + s := c.String() + + // All nodes + assert.Contains(t, s, `dig.A[name="foo"] -> deps: []`) + assert.Contains(t, s, "dig.A -> deps: []") + assert.Contains(t, s, "dig.B -> deps: []") + assert.Contains(t, s, "dig.C -> deps: []") + assert.Contains(t, s, `dig.C[name="bar"] -> deps: []`) + assert.Contains(t, s, `dig.D -> deps: [dig.A[name="foo"] dig.B[optional] dig.C[optional, name="bar"] string[group="baz"]]`) + assert.Contains(t, s, `string[group="baz"] -> deps: [dig.A]`) + assert.Contains(t, s, `string[group="baz"] -> deps: [dig.B]`) + assert.Contains(t, s, `string[group="baz"] -> deps: [dig.C]`) + + // Values + assert.Contains(t, s, "dig.A => {}") + assert.Contains(t, s, "dig.B => {}") + assert.Contains(t, s, "dig.C => {}") + assert.Contains(t, s, "dig.D => {}") + assert.Contains(t, s, `dig.A[name="foo"] => {}`) + assert.Contains(t, s, `dig.C[name="bar"] => {}`) + assert.Contains(t, s, `string[group="baz"] => foo`) + assert.Contains(t, s, `string[group="baz"] => bar`) + assert.Contains(t, s, `string[group="baz"] => baz`) +} diff --git a/vendor/go.uber.org/dig/types.go b/vendor/go.uber.org/dig/types.go new file mode 100644 index 000000000..5a0d23dce --- /dev/null +++ b/vendor/go.uber.org/dig/types.go @@ -0,0 +1,153 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package dig + +import ( + "container/list" + "reflect" +) + +var ( + _noValue reflect.Value + _errType = reflect.TypeOf((*error)(nil)).Elem() + _inPtrType = reflect.TypeOf((*In)(nil)) + _inType = reflect.TypeOf(In{}) + _outPtrType = reflect.TypeOf((*Out)(nil)) + _outType = reflect.TypeOf(Out{}) +) + +// Special interface embedded inside dig sentinel values (dig.In, dig.Out) to +// make their special nature obvious in the godocs. Otherwise they will appear +// as plain empty structs. +type digSentinel interface { + digSentinel() +} + +// In may be embedded into structs to request dig to treat them as special +// parameter structs. When a constructor accepts such a struct, instead of the +// struct becoming a dependency for that constructor, all its fields become +// dependencies instead. See the section on Parameter Objects in the +// package-level documentation for more information. +// +// Fields of the struct may optionally be tagged to customize the behavior of +// dig. The following tags are supported, +// +// name Requests a value with the same name and type from the +// container. See Named Values for more information. +// optional If set to true, indicates that the dependency is optional and +// the constructor gracefully handles its absence. +type In struct{ digSentinel } + +// Out is an embeddable type that signals to dig that the returned +// struct should be treated differently. Instead of the struct itself +// becoming part of the container, all members of the struct will. + +// Out may be embedded into structs to request dig to treat them as special +// result structs. When a constructor returns such a struct, instead of the +// struct becoming a result of the constructor, all its fields become results +// of the constructor. See the section on Result Objects in the package-level +// documentation for more information. +// +// Fields of the struct may optionally be tagged to customize the behavior of +// dig. The following tags are supported, +// +// name Specifies the name of the value. Only a field on a dig.In +// struct with the same 'name' annotation can receive this +// value. See Named Values for more information. +type Out struct{ digSentinel } + +func isError(t reflect.Type) bool { + return t.Implements(_errType) +} + +// IsIn checks whether the given struct is a dig.In struct. A struct qualifies +// as a dig.In struct if it embeds the dig.In type or if any struct that it +// embeds is a dig.In struct. The parameter may be the reflect.Type of the +// struct rather than the struct itself. +// +// A struct MUST qualify as a dig.In struct for its fields to be treated +// specially by dig. +// +// See the documentation for dig.In for a comprehensive list of supported +// tags. +func IsIn(o interface{}) bool { + return embedsType(o, _inType) +} + +// IsOut checks whether the given struct is a dig.Out struct. A struct +// qualifies as a dig.Out struct if it embeds the dig.Out type or if any +// struct that it embeds is a dig.Out struct. The parameter may be the +// reflect.Type of the struct rather than the struct itself. +// +// A struct MUST qualify as a dig.Out struct for its fields to be treated +// specially by dig. +// +// See the documentation for dig.Out for a comprehensive list of supported +// tags. +func IsOut(o interface{}) bool { + return embedsType(o, _outType) +} + +// Returns true if t embeds e or if any of the types embedded by t embed e. +func embedsType(i interface{}, e reflect.Type) bool { + // TODO: this function doesn't consider e being a pointer. + // given `type A foo { *In }`, this function would return false for + // embedding dig.In, which makes for some extra error checking in places + // that call this funciton. Might be worthwhile to consider reflect.Indirect + // usage to clean up the callers. + + if i == nil { + return false + } + + // maybe it's already a reflect.Type + t, ok := i.(reflect.Type) + if !ok { + // take the type if it's not + t = reflect.TypeOf(i) + } + + // We are going to do a breadth-first search of all embedded fields. + types := list.New() + types.PushBack(t) + for types.Len() > 0 { + t := types.Remove(types.Front()).(reflect.Type) + + if t == e { + return true + } + + if t.Kind() != reflect.Struct { + continue + } + + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + if f.Anonymous { + types.PushBack(f.Type) + } + } + } + + // If perf is an issue, we can cache known In objects and Out objects in a + // map[reflect.Type]struct{}. + return false +} diff --git a/vendor/go.uber.org/dig/utils_for_go19_test.go b/vendor/go.uber.org/dig/utils_for_go19_test.go new file mode 100644 index 000000000..3e9b77295 --- /dev/null +++ b/vendor/go.uber.org/dig/utils_for_go19_test.go @@ -0,0 +1,29 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// +build go1.9 + +package dig + +import "reflect" + +func anonymousField(t reflect.Type) reflect.StructField { + return reflect.StructField{Name: t.Name(), Anonymous: true, Type: t} +} diff --git a/vendor/go.uber.org/dig/utils_for_pre_go19_test.go b/vendor/go.uber.org/dig/utils_for_pre_go19_test.go new file mode 100644 index 000000000..7e9764909 --- /dev/null +++ b/vendor/go.uber.org/dig/utils_for_pre_go19_test.go @@ -0,0 +1,29 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// +build !go1.9 + +package dig + +import "reflect" + +func anonymousField(t reflect.Type) reflect.StructField { + return reflect.StructField{Anonymous: true, Type: t} +} diff --git a/vendor/go.uber.org/dig/version.go b/vendor/go.uber.org/dig/version.go new file mode 100644 index 000000000..c35e6aa58 --- /dev/null +++ b/vendor/go.uber.org/dig/version.go @@ -0,0 +1,24 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package dig + +// Version of the library +const Version = "1.3.0"