From d06a5ec7d4cf37a7670a7c74080291bd99dcec30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Gro=C3=9Fmann?= Date: Tue, 17 Jun 2025 16:55:30 +0200 Subject: [PATCH 1/7] feat(sdk): friendlier error message --- pkg/api/util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/api/util.go b/pkg/api/util.go index f36fa14e..8240b8ba 100644 --- a/pkg/api/util.go +++ b/pkg/api/util.go @@ -25,7 +25,7 @@ type ErrInvalidPluginIndex struct { } func (e ErrInvalidPluginIndex) Error() string { - return fmt.Sprintf("invalid plugin index %s: %s", e.Actual, e.Msg) + return fmt.Sprintf("invalid plugin index %q: %s", e.Actual, e.Msg) } func (e ErrInvalidPluginIndex) Is(target error) bool { From ac2c5e106d0ba94f61867acd972fdbdd059d2402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Gro=C3=9Fmann?= Date: Tue, 17 Jun 2025 16:56:51 +0200 Subject: [PATCH 2/7] feat(sdk): shared default config variables --- pkg/adaptation/adaptation.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 pkg/adaptation/adaptation.go diff --git a/pkg/adaptation/adaptation.go b/pkg/adaptation/adaptation.go new file mode 100644 index 00000000..e7535769 --- /dev/null +++ b/pkg/adaptation/adaptation.go @@ -0,0 +1,17 @@ +package adaptation + +import "time" + +const ( + // DefaultSocketPath is the default socket path for external plugins. + DefaultSocketPath = "/var/run/secrets-engine/engine.sock" + // PluginNameEnvVar is used to inform engine-launched plugins about their name. + PluginNameEnvVar = "PROVIDER_PLUGIN_NAME" + // PluginIdxEnvVar is used to inform engine-launched plugins about their ID. + PluginIdxEnvVar = "PROVIDER_PLUGIN_IDX" + // PluginRegistrationTimeoutEnvVar is used to inform plugins about the registration timeout. + // (parsed via time.ParseDuration) + PluginRegistrationTimeoutEnvVar = "PROVIDER_PLUGIN_REGISTRATION_TIMEOUT" + // DefaultPluginRegistrationTimeout is the default timeout for plugin registration. + DefaultPluginRegistrationTimeout = 5 * time.Second +) From c9b448536ceb66bd33319651c9c4a3c430ed0f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Gro=C3=9Fmann?= Date: Tue, 17 Jun 2025 16:57:28 +0200 Subject: [PATCH 3/7] feat(sdk): static and default configuration --- pkg/stub/config.go | 170 +++++++++++++++++++++++++++++++++ pkg/stub/config_test.go | 203 ++++++++++++++++++++++++++++++++++++++++ pkg/stub/stub.go | 34 +++++++ 3 files changed, 407 insertions(+) create mode 100644 pkg/stub/config.go create mode 100644 pkg/stub/config_test.go diff --git a/pkg/stub/config.go b/pkg/stub/config.go new file mode 100644 index 00000000..ff678f12 --- /dev/null +++ b/pkg/stub/config.go @@ -0,0 +1,170 @@ +package stub + +import ( + "errors" + "fmt" + "net" + "os" + "path/filepath" + "time" + + "github.com/docker/secrets-engine/pkg/adaptation" + "github.com/docker/secrets-engine/pkg/api" +) + +// Option to apply to a plugin during its creation. +type Option func(*cfg) error + +// WithPluginName sets the name to use in plugin registration (for manually launched plugins only). +func WithPluginName(name string) Option { + return func(s *cfg) error { + if s.name != "" { + return fmt.Errorf("plugin name already set (%q)", s.name) + } + if name == "" { + return errors.New("plugin name cannot be empty") + } + s.name = name + return nil + } +} + +// WithPluginIdx sets the index to use in plugin registration (for manually launched plugins only). +func WithPluginIdx(idx string) Option { + return func(s *cfg) error { + if s.idx != "" { + return fmt.Errorf("plugin ID already set (%q)", s.idx) + } + if err := api.CheckPluginIndex(idx); err != nil { + return err + } + s.idx = idx + return nil + } +} + +// WithRegistrationTimeout sets custom registration timeout (for manually launched plugins only). +func WithRegistrationTimeout(timeout time.Duration) Option { + return func(s *cfg) error { + s.registrationTimeout = timeout + return nil + } +} + +// WithSocketPath sets the secrets engine socket path to connect to. +func WithSocketPath(path string) Option { + return func(s *cfg) error { + s.socketPath = path + return nil + } +} + +// WithConnection sets an existing secrets engine connection to use. +func WithConnection(conn net.Conn) Option { + return func(s *cfg) error { + s.conn = conn + return nil + } +} + +type cfg struct { + plugin Plugin + identity + socketPath string + conn net.Conn + registrationTimeout time.Duration +} + +func newCfg(p Plugin, opts ...Option) (*cfg, error) { + var identity identity + timeout := adaptation.DefaultPluginRegistrationTimeout + if isPluginEnvSet() { + i, t, err := getCfgFromEnv() + if err != nil { + return nil, err + } + identity = *i + timeout = t + } + cfg := &cfg{ + plugin: p, + identity: identity, + registrationTimeout: timeout, + socketPath: adaptation.DefaultSocketPath, + } + for _, o := range opts { + if err := o(cfg); err != nil { + return nil, err + } + } + i, err := complementIdentity(cfg.identity) + if err != nil { + return nil, err + } + cfg.identity = *i + return cfg, nil +} + +var ( + errPluginNameNotSet = errors.New("plugin name not set") + errPluginIdxNotSet = errors.New("plugin index not set") + errPluginRegistrationTimeoutNotSet = errors.New("plugin registration timeout not set") +) + +// Note: Partially set ENV based config as an error, as we expect the +// secret engine to always set all ENV based configuration. +func getCfgFromEnv() (*identity, time.Duration, error) { + name := os.Getenv(adaptation.PluginNameEnvVar) + idx := os.Getenv(adaptation.PluginIdxEnvVar) + timeoutStr := os.Getenv(adaptation.PluginRegistrationTimeoutEnvVar) + if name == "" { + return nil, 0, errPluginNameNotSet + } + if idx == "" { + return nil, 0, errPluginIdxNotSet + } + if err := api.CheckPluginIndex(idx); err != nil { + return nil, 0, fmt.Errorf("invalid plugin index %q: %w", idx, err) + } + if timeoutStr == "" { + return nil, 0, errPluginRegistrationTimeoutNotSet + } + timeout, err := time.ParseDuration(timeoutStr) + if err != nil { + return nil, 0, fmt.Errorf("invalid registration timeout %q: %w", timeoutStr, err) + } + return &identity{name: name, idx: idx}, timeout, nil +} + +func isPluginEnvSet() bool { + name := os.Getenv(adaptation.PluginNameEnvVar) + idx := os.Getenv(adaptation.PluginIdxEnvVar) + timeoutStr := os.Getenv(adaptation.PluginRegistrationTimeoutEnvVar) + return name != "" || idx != "" || timeoutStr != "" +} + +type identity struct { + name string + idx string +} + +func (i *identity) FullName() string { + return i.idx + "-" + i.name +} + +func complementIdentity(i identity) (*identity, error) { + if i.idx != "" && i.name != "" { + return &i, nil + } + if i.idx != "" && i.name == "" { + i.name = filepath.Base(os.Args[0]) + return &i, nil + } + idx, name, err := api.ParsePluginName(filepath.Base(os.Args[0])) + if err != nil { + return &i, err + } + i.idx = idx + i.name = name + return &i, nil +} diff --git a/pkg/stub/config_test.go b/pkg/stub/config_test.go new file mode 100644 index 00000000..6ee07f12 --- /dev/null +++ b/pkg/stub/config_test.go @@ -0,0 +1,203 @@ +package stub + +import ( + "context" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/docker/secrets-engine/pkg/adaptation" + "github.com/docker/secrets-engine/pkg/api" + "github.com/docker/secrets-engine/pkg/secrets" +) + +type mockPlugin struct { +} + +func (m mockPlugin) GetSecret(context.Context, secrets.Request) (secrets.Envelope, error) { + return secrets.Envelope{}, nil +} + +func (m mockPlugin) Shutdown(context.Context) { +} + +func Test_newCfg(t *testing.T) { + tests := []struct { + name string + test func(t *testing.T) + }{ + { + name: "incomplete env based config (secret engine always has to provide all values regardless if they get overwritten by opts)", + test: func(t *testing.T) { + defer cleanupEnv() + _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") + _, err := newCfg(mockPlugin{}, WithPluginIdx("10")) + assert.Error(t, errPluginIdxNotSet, err) + }, + }, + { + name: "invalid index from env based config", + test: func(t *testing.T) { + defer cleanupEnv() + _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") + _ = os.Setenv(adaptation.PluginIdxEnvVar, "invalid-index") + _, err := newCfg(mockPlugin{}) + assert.ErrorIs(t, err, &api.ErrInvalidPluginIndex{Actual: "invalid-index", Msg: "must be two digits"}) + }, + }, + { + name: "invalid duration from env based config", + test: func(t *testing.T) { + defer cleanupEnv() + _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") + _ = os.Setenv(adaptation.PluginIdxEnvVar, "10") + _ = os.Setenv(adaptation.PluginRegistrationTimeoutEnvVar, "10") + _, err := newCfg(mockPlugin{}) + assert.ErrorContains(t, err, "invalid registration timeout") + }, + }, + { + name: "valid env based config", + test: func(t *testing.T) { + defer cleanupEnv() + _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") + _ = os.Setenv(adaptation.PluginIdxEnvVar, "10") + _ = os.Setenv(adaptation.PluginRegistrationTimeoutEnvVar, "10s") + cfg, err := newCfg(mockPlugin{}) + assert.NoError(t, err) + assert.Equal(t, "test-plugin", cfg.identity.name) + assert.Equal(t, "10", cfg.identity.idx) + assert.Equal(t, 10*time.Second, cfg.registrationTimeout) + }, + }, + { + name: "name cannot be overwritten when launched by the secret engine", + test: func(t *testing.T) { + defer cleanupEnv() + _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") + _ = os.Setenv(adaptation.PluginIdxEnvVar, "10") + _ = os.Setenv(adaptation.PluginRegistrationTimeoutEnvVar, "10s") + _, err := newCfg(mockPlugin{}, WithPluginName("name")) + assert.ErrorContains(t, err, "plugin name already set") + }, + }, + { + name: "invalid name opt overwrite", + test: func(t *testing.T) { + _, err := newCfg(mockPlugin{}, WithPluginName("")) + assert.ErrorContains(t, err, "plugin name cannot be empty") + }, + }, + { + name: "valid name and index opt overwrite", + test: func(t *testing.T) { + cfg, err := newCfg(mockPlugin{}, WithPluginName("new-name"), WithPluginIdx("10")) + assert.NoError(t, err) + assert.Equal(t, "new-name", cfg.identity.name) + assert.Equal(t, "10", cfg.identity.idx) + assert.Equal(t, adaptation.DefaultPluginRegistrationTimeout, cfg.registrationTimeout) + }, + }, + { + name: "invalid index opt overwrite", + test: func(t *testing.T) { + _, err := newCfg(mockPlugin{}, WithPluginIdx("invalid-index")) + assert.ErrorIs(t, err, &api.ErrInvalidPluginIndex{Actual: "invalid-index", Msg: "must be two digits"}) + }, + }, + { + name: "overwrite socket path", + test: func(t *testing.T) { + cfg, err := newCfg(mockPlugin{}, WithPluginName("new-name"), WithPluginIdx("10"), WithSocketPath("/tmp/test.sock")) + assert.NoError(t, err) + assert.Equal(t, "/tmp/test.sock", cfg.socketPath) + }, + }, + { + name: "overwrite registration timeout", + test: func(t *testing.T) { + cfg, err := newCfg(mockPlugin{}, WithPluginName("new-name"), WithPluginIdx("10"), WithRegistrationTimeout(10*adaptation.DefaultPluginRegistrationTimeout)) + assert.NoError(t, err) + assert.Equal(t, 10*adaptation.DefaultPluginRegistrationTimeout, cfg.registrationTimeout) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.test(t) + }) + } +} + +func cleanupEnv() { + _ = os.Setenv(adaptation.PluginNameEnvVar, "") + _ = os.Setenv(adaptation.PluginIdxEnvVar, "") + _ = os.Setenv(adaptation.PluginRegistrationTimeoutEnvVar, "") +} + +func Test_complementIdentity(t *testing.T) { + tests := []struct { + name string + identity identity + expected identity + args []string + err error + }{ + { + name: "valid identity", + identity: identity{ + name: "test-plugin", + idx: "10", + }, + expected: identity{ + name: "test-plugin", + idx: "10", + }, + }, + { + name: "missing name pulled from args", + identity: identity{ + idx: "10", + }, + args: []string{"test-plugin"}, + expected: identity{ + name: "test-plugin", + idx: "10", + }, + }, + { + name: "invalid identity pulled from args", + args: []string{"test-plugin"}, + err: &api.ErrInvalidPluginIndex{Actual: "test", Msg: "must be two digits"}, + }, + { + name: "identity pulled from args", + args: []string{"10-test-plugin"}, + expected: identity{ + name: "test-plugin", + idx: "10", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.args != nil { + var args []string + copy(args, os.Args) + defer func() { + os.Args = args + }() + os.Args = tt.args + } + i, err := complementIdentity(tt.identity) + if tt.err != nil { + assert.ErrorIs(t, err, tt.err) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.expected, *i) + }) + } +} diff --git a/pkg/stub/stub.go b/pkg/stub/stub.go index 6f2c0c95..fac1e121 100644 --- a/pkg/stub/stub.go +++ b/pkg/stub/stub.go @@ -2,6 +2,9 @@ package stub import ( "context" + "time" + + "github.com/sirupsen/logrus" "github.com/docker/secrets-engine/pkg/secrets" ) @@ -11,3 +14,34 @@ type Plugin interface { Shutdown(context.Context) } + +// Stub is the interface the stub provides for the plugin implementation. +type Stub interface { + // RegistrationTimeout returns the registration timeout for the stub. + // This is the default timeout if the plugin has not been started or + // the timeout received in the Configure request otherwise. + RegistrationTimeout() time.Duration +} + +// stub implements Stub. +type stub struct { + cfg +} + +// New creates a stub with the given plugin and options. +func New(p Plugin, opts ...Option) (Stub, error) { + cfg, err := newCfg(p, opts...) + if err != nil { + return nil, err + } + stub := &stub{ + cfg: *cfg, + } + logrus.Infof("Created plugin %s", stub.FullName()) + + return stub, nil +} + +func (stub *stub) RegistrationTimeout() time.Duration { + return stub.registrationTimeout +} From 6724b681f6ba7fa1e6f8c0aeaafde5f0de3d1607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Gro=C3=9Fmann?= Date: Tue, 17 Jun 2025 17:56:34 +0200 Subject: [PATCH 4/7] refactor(sdk): minor simplification --- pkg/stub/config.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/pkg/stub/config.go b/pkg/stub/config.go index ff678f12..08ec8e6a 100644 --- a/pkg/stub/config.go +++ b/pkg/stub/config.go @@ -76,19 +76,18 @@ type cfg struct { } func newCfg(p Plugin, opts ...Option) (*cfg, error) { - var identity identity + identity := &identity{} timeout := adaptation.DefaultPluginRegistrationTimeout if isPluginEnvSet() { - i, t, err := getCfgFromEnv() + var err error + identity, timeout, err = getCfgFromEnv() if err != nil { return nil, err } - identity = *i - timeout = t } cfg := &cfg{ plugin: p, - identity: identity, + identity: *identity, registrationTimeout: timeout, socketPath: adaptation.DefaultSocketPath, } @@ -114,19 +113,21 @@ var ( // Note: Partially set ENV based config as an error, as we expect the // secret engine to always set all ENV based configuration. func getCfgFromEnv() (*identity, time.Duration, error) { - name := os.Getenv(adaptation.PluginNameEnvVar) - idx := os.Getenv(adaptation.PluginIdxEnvVar) - timeoutStr := os.Getenv(adaptation.PluginRegistrationTimeoutEnvVar) - if name == "" { + var ( + name string + idx string + timeoutStr string + ) + if name = os.Getenv(adaptation.PluginNameEnvVar); name == "" { return nil, 0, errPluginNameNotSet } - if idx == "" { + if idx = os.Getenv(adaptation.PluginIdxEnvVar); idx == "" { return nil, 0, errPluginIdxNotSet } if err := api.CheckPluginIndex(idx); err != nil { return nil, 0, fmt.Errorf("invalid plugin index %q: %w", idx, err) } - if timeoutStr == "" { + if timeoutStr = os.Getenv(adaptation.PluginRegistrationTimeoutEnvVar); timeoutStr == "" { return nil, 0, errPluginRegistrationTimeoutNotSet } timeout, err := time.ParseDuration(timeoutStr) From 7f6fff7a0f5fa214c8cacb4227f6d6c7e6a838c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Gro=C3=9Fmann?= Date: Wed, 18 Jun 2025 10:46:58 +0200 Subject: [PATCH 5/7] refactor(sdk): rewrite stub config creation to simplify and clarify logic --- pkg/adaptation/adaptation.go | 20 +- pkg/stub/config.go | 161 +- pkg/stub/config_test.go | 297 ++- pkg/stub/stub.go | 2 +- .../stretchr/testify/require/doc.go | 29 + .../testify/require/forward_requirements.go | 16 + .../stretchr/testify/require/require.go | 2124 +++++++++++++++++ .../stretchr/testify/require/require.go.tmpl | 6 + .../testify/require/require_forward.go | 1674 +++++++++++++ .../testify/require/require_forward.go.tmpl | 5 + .../stretchr/testify/require/requirements.go | 29 + vendor/modules.txt | 1 + 12 files changed, 4191 insertions(+), 173 deletions(-) create mode 100644 vendor/github.com/stretchr/testify/require/doc.go create mode 100644 vendor/github.com/stretchr/testify/require/forward_requirements.go create mode 100644 vendor/github.com/stretchr/testify/require/require.go create mode 100644 vendor/github.com/stretchr/testify/require/require.go.tmpl create mode 100644 vendor/github.com/stretchr/testify/require/require_forward.go create mode 100644 vendor/github.com/stretchr/testify/require/require_forward.go.tmpl create mode 100644 vendor/github.com/stretchr/testify/require/requirements.go diff --git a/pkg/adaptation/adaptation.go b/pkg/adaptation/adaptation.go index e7535769..9b6b35f8 100644 --- a/pkg/adaptation/adaptation.go +++ b/pkg/adaptation/adaptation.go @@ -1,10 +1,14 @@ package adaptation -import "time" +import ( + "os" + "path/filepath" + "time" +) const ( - // DefaultSocketPath is the default socket path for external plugins. - DefaultSocketPath = "/var/run/secrets-engine/engine.sock" + // PluginSocketEnvVar is used to inform plugins about pre-connected sockets. + PluginSocketEnvVar = "SECRETS_ENGINE_PLUGIN_SOCKET" // PluginNameEnvVar is used to inform engine-launched plugins about their name. PluginNameEnvVar = "PROVIDER_PLUGIN_NAME" // PluginIdxEnvVar is used to inform engine-launched plugins about their ID. @@ -15,3 +19,13 @@ const ( // DefaultPluginRegistrationTimeout is the default timeout for plugin registration. DefaultPluginRegistrationTimeout = 5 * time.Second ) + +func DefaultSocketPath() string { + if dir := os.Getenv("XDG_RUNTIME_DIR"); dir != "" { + return filepath.Join(dir, "secrets-engine", "engine.sock") + } + if home, err := os.UserHomeDir(); err == nil { + return filepath.Join(home, ".cache", "secrets-engine", "engine.sock") + } + return filepath.Join(os.TempDir(), "secrets-engine", "engine.sock") +} diff --git a/pkg/stub/config.go b/pkg/stub/config.go index 08ec8e6a..69e8fffa 100644 --- a/pkg/stub/config.go +++ b/pkg/stub/config.go @@ -6,21 +6,22 @@ import ( "net" "os" "path/filepath" + "strconv" "time" + "github.com/sirupsen/logrus" + "github.com/docker/secrets-engine/pkg/adaptation" "github.com/docker/secrets-engine/pkg/api" ) -// Option to apply to a plugin during its creation. -type Option func(*cfg) error +// ManualLaunchOption to apply to a plugin during its creation +// when it's manually launched (not by the secrets engine). +type ManualLaunchOption func(c *cfg) error -// WithPluginName sets the name to use in plugin registration (for manually launched plugins only). -func WithPluginName(name string) Option { +// WithPluginName sets the name to use in plugin registration. +func WithPluginName(name string) ManualLaunchOption { return func(s *cfg) error { - if s.name != "" { - return fmt.Errorf("plugin name already set (%q)", s.name) - } if name == "" { return errors.New("plugin name cannot be empty") } @@ -29,12 +30,9 @@ func WithPluginName(name string) Option { } } -// WithPluginIdx sets the index to use in plugin registration (for manually launched plugins only). -func WithPluginIdx(idx string) Option { +// WithPluginIdx sets the index to use in plugin registration. +func WithPluginIdx(idx string) ManualLaunchOption { return func(s *cfg) error { - if s.idx != "" { - return fmt.Errorf("plugin ID already set (%q)", s.idx) - } if err := api.CheckPluginIndex(idx); err != nil { return err } @@ -43,8 +41,8 @@ func WithPluginIdx(idx string) Option { } } -// WithRegistrationTimeout sets custom registration timeout (for manually launched plugins only). -func WithRegistrationTimeout(timeout time.Duration) Option { +// WithRegistrationTimeout sets custom registration timeout. +func WithRegistrationTimeout(timeout time.Duration) ManualLaunchOption { return func(s *cfg) error { s.registrationTimeout = timeout return nil @@ -52,16 +50,26 @@ func WithRegistrationTimeout(timeout time.Duration) Option { } // WithSocketPath sets the secrets engine socket path to connect to. -func WithSocketPath(path string) Option { +func WithSocketPath(path string) ManualLaunchOption { return func(s *cfg) error { - s.socketPath = path + if s.conn != nil { + return errors.New("cannot set socket path when a connection is already set") + } + conn, err := net.Dial("unix", path) + if err != nil { + return fmt.Errorf("failed to connect to socket %q: %w", path, err) + } + s.conn = conn return nil } } // WithConnection sets an existing secrets engine connection to use. -func WithConnection(conn net.Conn) Option { +func WithConnection(conn net.Conn) ManualLaunchOption { return func(s *cfg) error { + if s.conn != nil { + return errors.New("connection already set") + } s.conn = conn return nil } @@ -70,37 +78,59 @@ func WithConnection(conn net.Conn) Option { type cfg struct { plugin Plugin identity - socketPath string conn net.Conn registrationTimeout time.Duration } -func newCfg(p Plugin, opts ...Option) (*cfg, error) { - identity := &identity{} - timeout := adaptation.DefaultPluginRegistrationTimeout - if isPluginEnvSet() { - var err error - identity, timeout, err = getCfgFromEnv() +func newCfg(p Plugin, opts ...ManualLaunchOption) (*cfg, error) { + if ShouldHaveBeenLaunchedByEngine() { + logrus.Info("Plugin launched by engine, restoring config...") + if len(opts) > 0 { + return nil, errors.New("plugin launched by secrets engine, cannot use manual launch options") + } + engineCfg, err := restoreConfig() if err != nil { return nil, err } - } + return &cfg{ + plugin: p, + identity: engineCfg.identity, + conn: engineCfg.conn, + registrationTimeout: engineCfg.timeout, + }, nil + } + return newCfgForManualLaunch(p, opts...) +} + +func newCfgForManualLaunch(p Plugin, opts ...ManualLaunchOption) (*cfg, error) { cfg := &cfg{ plugin: p, - identity: *identity, - registrationTimeout: timeout, - socketPath: adaptation.DefaultSocketPath, + registrationTimeout: adaptation.DefaultPluginRegistrationTimeout, } for _, o := range opts { if err := o(cfg); err != nil { return nil, err } } - i, err := complementIdentity(cfg.identity) - if err != nil { - return nil, err + if cfg.conn == nil { + defaultSocketPath := adaptation.DefaultSocketPath() + conn, err := net.Dial("unix", defaultSocketPath) + if err != nil { + return nil, fmt.Errorf("failed to connect to default socket %q: %w", defaultSocketPath, err) + } + cfg.conn = conn + } + if cfg.idx != "" && cfg.name == "" { + cfg.name = filepath.Base(os.Args[0]) + } + if cfg.idx == "" && cfg.name == "" { + idx, name, err := api.ParsePluginName(filepath.Base(os.Args[0])) + if err != nil { + return nil, err + } + cfg.idx = idx + cfg.name = name } - cfg.identity = *i return cfg, nil } @@ -108,40 +138,69 @@ var ( errPluginNameNotSet = errors.New("plugin name not set") errPluginIdxNotSet = errors.New("plugin index not set") errPluginRegistrationTimeoutNotSet = errors.New("plugin registration timeout not set") + errPluginSocketNotSet = errors.New("plugin socket fd not set in environment variables") ) +type configFromEngine struct { + identity identity + timeout time.Duration + conn net.Conn +} + // Note: Partially set ENV based config as an error, as we expect the // secret engine to always set all ENV based configuration. -func getCfgFromEnv() (*identity, time.Duration, error) { +func restoreConfig() (*configFromEngine, error) { var ( name string idx string timeoutStr string + env string ) if name = os.Getenv(adaptation.PluginNameEnvVar); name == "" { - return nil, 0, errPluginNameNotSet + return nil, errPluginNameNotSet } if idx = os.Getenv(adaptation.PluginIdxEnvVar); idx == "" { - return nil, 0, errPluginIdxNotSet + return nil, errPluginIdxNotSet } if err := api.CheckPluginIndex(idx); err != nil { - return nil, 0, fmt.Errorf("invalid plugin index %q: %w", idx, err) + return nil, fmt.Errorf("invalid plugin index %q: %w", idx, err) } if timeoutStr = os.Getenv(adaptation.PluginRegistrationTimeoutEnvVar); timeoutStr == "" { - return nil, 0, errPluginRegistrationTimeoutNotSet + return nil, errPluginRegistrationTimeoutNotSet } timeout, err := time.ParseDuration(timeoutStr) if err != nil { - return nil, 0, fmt.Errorf("invalid registration timeout %q: %w", timeoutStr, err) + return nil, fmt.Errorf("invalid registration timeout %q: %w", timeoutStr, err) + } + if env = os.Getenv(adaptation.PluginSocketEnvVar); env == "" { + return nil, errPluginSocketNotSet + } + fd, err := strconv.Atoi(env) + if err != nil { + return nil, fmt.Errorf("invalid socket fd (%s=%q): %w", adaptation.PluginSocketEnvVar, env, err) } - return &identity{name: name, idx: idx}, timeout, nil + conn, err := connectionFromFileDescriptor(fd) + if err != nil { + return nil, fmt.Errorf("invalid socket (%d) in environment: %w", fd, err) + } + return &configFromEngine{ + identity: identity{name: name, idx: idx}, + timeout: timeout, + conn: conn, + }, nil } -func isPluginEnvSet() bool { +// ShouldHaveBeenLaunchedByEngine checks if the plugin was launched by the secrets engine. +// Note: There's no 100% guarantee that this really happened, but we take the custom internal +// environment variables as indication that it should have happened. +func ShouldHaveBeenLaunchedByEngine() bool { + // In theory, all variables should always be set by the engine and we'd just need to check one. + // But there could be a bug, so we check for any and verify all values are set correctly later. name := os.Getenv(adaptation.PluginNameEnvVar) idx := os.Getenv(adaptation.PluginIdxEnvVar) timeoutStr := os.Getenv(adaptation.PluginRegistrationTimeoutEnvVar) - return name != "" || idx != "" || timeoutStr != "" + env := os.Getenv(adaptation.PluginSocketEnvVar) + return name != "" || idx != "" || timeoutStr != "" || env != "" } type identity struct { @@ -153,19 +212,15 @@ func (i *identity) FullName() string { return i.idx + "-" + i.name } -func complementIdentity(i identity) (*identity, error) { - if i.idx != "" && i.name != "" { - return &i, nil - } - if i.idx != "" && i.name == "" { - i.name = filepath.Base(os.Args[0]) - return &i, nil +func connectionFromFileDescriptor(fd int) (net.Conn, error) { + f := os.NewFile(uintptr(fd), "fd #"+strconv.Itoa(fd)) + if f == nil { + return nil, fmt.Errorf("failed to open FD %d", fd) } - idx, name, err := api.ParsePluginName(filepath.Base(os.Args[0])) + defer f.Close() + conn, err := net.FileConn(f) if err != nil { - return &i, err + return nil, fmt.Errorf("failed to create net.Conn for fd #%d: %w", fd, err) } - i.idx = idx - i.name = name - return &i, nil + return conn, nil } diff --git a/pkg/stub/config_test.go b/pkg/stub/config_test.go index 6ee07f12..e9e34fc7 100644 --- a/pkg/stub/config_test.go +++ b/pkg/stub/config_test.go @@ -2,11 +2,16 @@ package stub import ( "context" + "net" "os" + "path/filepath" + "strconv" "testing" "time" + nriNet "github.com/containerd/nri/pkg/net" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/docker/secrets-engine/pkg/adaptation" "github.com/docker/secrets-engine/pkg/api" @@ -29,97 +34,121 @@ func Test_newCfg(t *testing.T) { test func(t *testing.T) }{ { - name: "incomplete env based config (secret engine always has to provide all values regardless if they get overwritten by opts)", + name: "no options allowed when launched from secrets engine", test: func(t *testing.T) { defer cleanupEnv() _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") - _, err := newCfg(mockPlugin{}, WithPluginIdx("10")) - assert.Error(t, errPluginIdxNotSet, err) - }, - }, - { - name: "invalid index from env based config", - test: func(t *testing.T) { - defer cleanupEnv() - _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") - _ = os.Setenv(adaptation.PluginIdxEnvVar, "invalid-index") - _, err := newCfg(mockPlugin{}) - assert.ErrorIs(t, err, &api.ErrInvalidPluginIndex{Actual: "invalid-index", Msg: "must be two digits"}) - }, - }, - { - name: "invalid duration from env based config", - test: func(t *testing.T) { - defer cleanupEnv() - _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") - _ = os.Setenv(adaptation.PluginIdxEnvVar, "10") - _ = os.Setenv(adaptation.PluginRegistrationTimeoutEnvVar, "10") - _, err := newCfg(mockPlugin{}) - assert.ErrorContains(t, err, "invalid registration timeout") - }, - }, - { - name: "valid env based config", - test: func(t *testing.T) { - defer cleanupEnv() - _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") - _ = os.Setenv(adaptation.PluginIdxEnvVar, "10") - _ = os.Setenv(adaptation.PluginRegistrationTimeoutEnvVar, "10s") - cfg, err := newCfg(mockPlugin{}) - assert.NoError(t, err) - assert.Equal(t, "test-plugin", cfg.identity.name) - assert.Equal(t, "10", cfg.identity.idx) - assert.Equal(t, 10*time.Second, cfg.registrationTimeout) - }, - }, - { - name: "name cannot be overwritten when launched by the secret engine", - test: func(t *testing.T) { - defer cleanupEnv() - _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") - _ = os.Setenv(adaptation.PluginIdxEnvVar, "10") - _ = os.Setenv(adaptation.PluginRegistrationTimeoutEnvVar, "10s") - _, err := newCfg(mockPlugin{}, WithPluginName("name")) - assert.ErrorContains(t, err, "plugin name already set") - }, - }, - { - name: "invalid name opt overwrite", - test: func(t *testing.T) { - _, err := newCfg(mockPlugin{}, WithPluginName("")) - assert.ErrorContains(t, err, "plugin name cannot be empty") + _, err := newCfg(mockPlugin{}, WithPluginName("test-plugin")) + assert.ErrorContains(t, err, "cannot use manual launch options") }, }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.test(t) + }) + } +} + +func cleanupEnv() { + _ = os.Setenv(adaptation.PluginNameEnvVar, "") + _ = os.Setenv(adaptation.PluginIdxEnvVar, "") + _ = os.Setenv(adaptation.PluginRegistrationTimeoutEnvVar, "") +} + +func Test_newCfgForManualLaunch(t *testing.T) { + tests := []struct { + name string + test func(t *testing.T) + }{ { - name: "valid name and index opt overwrite", + name: "no options just defaults", test: func(t *testing.T) { - cfg, err := newCfg(mockPlugin{}, WithPluginName("new-name"), WithPluginIdx("10")) + var args []string + copy(args, os.Args) + defer func() { + os.Args = args + }() + os.Args = []string{"10-test-plugin"} + os.Setenv("XDG_RUNTIME_DIR", os.TempDir()) + socketPath := adaptation.DefaultSocketPath() + os.Remove(socketPath) + require.NoError(t, os.MkdirAll(filepath.Dir(socketPath), 0755)) + listener, err := net.Listen("unix", socketPath) + if err != nil { + t.Fatalf("listen failed: %v", err) + } + defer listener.Close() + defer os.Remove(socketPath) + m := mockPlugin{} + + cfg, err := newCfgForManualLaunch(m) assert.NoError(t, err) - assert.Equal(t, "new-name", cfg.identity.name) assert.Equal(t, "10", cfg.identity.idx) + assert.Equal(t, "test-plugin", cfg.identity.name) assert.Equal(t, adaptation.DefaultPluginRegistrationTimeout, cfg.registrationTimeout) + assert.NotNil(t, cfg.conn) + assert.Equal(t, m, cfg.plugin) }, }, { - name: "invalid index opt overwrite", + name: "no options just defaults but filename does not contain index", test: func(t *testing.T) { - _, err := newCfg(mockPlugin{}, WithPluginIdx("invalid-index")) - assert.ErrorIs(t, err, &api.ErrInvalidPluginIndex{Actual: "invalid-index", Msg: "must be two digits"}) + var args []string + copy(args, os.Args) + defer func() { + os.Args = args + }() + os.Args = []string{"test-plugin"} + os.Setenv("XDG_RUNTIME_DIR", os.TempDir()) + socketPath := adaptation.DefaultSocketPath() + os.Remove(socketPath) + require.NoError(t, os.MkdirAll(filepath.Dir(socketPath), 0755)) + listener, err := net.Listen("unix", socketPath) + if err != nil { + t.Fatalf("listen failed: %v", err) + } + defer listener.Close() + defer os.Remove(socketPath) + + _, err = newCfgForManualLaunch(mockPlugin{}) + assert.ErrorIs(t, err, &api.ErrInvalidPluginIndex{Actual: "test", Msg: "must be two digits"}) }, }, { - name: "overwrite socket path", + name: "without name", test: func(t *testing.T) { - cfg, err := newCfg(mockPlugin{}, WithPluginName("new-name"), WithPluginIdx("10"), WithSocketPath("/tmp/test.sock")) + var args []string + copy(args, os.Args) + defer func() { + os.Args = args + }() + os.Args = []string{"test-plugin"} + client, server := net.Pipe() + defer client.Close() + defer server.Close() + cfg, err := newCfgForManualLaunch(mockPlugin{}, WithPluginIdx("10"), WithConnection(client)) assert.NoError(t, err) - assert.Equal(t, "/tmp/test.sock", cfg.socketPath) + assert.Equal(t, "10", cfg.identity.idx) + assert.Equal(t, "test-plugin", cfg.identity.name) + assert.Equal(t, client, cfg.conn) }, }, { - name: "overwrite registration timeout", + name: "with all custom options", test: func(t *testing.T) { - cfg, err := newCfg(mockPlugin{}, WithPluginName("new-name"), WithPluginIdx("10"), WithRegistrationTimeout(10*adaptation.DefaultPluginRegistrationTimeout)) + client, server := net.Pipe() + defer client.Close() + defer server.Close() + cfg, err := newCfgForManualLaunch(mockPlugin{}, + WithPluginName("test-plugin"), + WithPluginIdx("10"), + WithRegistrationTimeout(10*adaptation.DefaultPluginRegistrationTimeout), + WithConnection(client), + ) assert.NoError(t, err) + assert.Equal(t, "10", cfg.identity.idx) + assert.Equal(t, "test-plugin", cfg.identity.name) assert.Equal(t, 10*adaptation.DefaultPluginRegistrationTimeout, cfg.registrationTimeout) }, }, @@ -131,73 +160,109 @@ func Test_newCfg(t *testing.T) { } } -func cleanupEnv() { - _ = os.Setenv(adaptation.PluginNameEnvVar, "") - _ = os.Setenv(adaptation.PluginIdxEnvVar, "") - _ = os.Setenv(adaptation.PluginRegistrationTimeoutEnvVar, "") -} - -func Test_complementIdentity(t *testing.T) { +func Test_restoreConfig(t *testing.T) { tests := []struct { - name string - identity identity - expected identity - args []string - err error + name string + test func(t *testing.T) }{ { - name: "valid identity", - identity: identity{ - name: "test-plugin", - idx: "10", + name: "name missing", + test: func(t *testing.T) { + defer cleanupEnv() + _, err := restoreConfig() + assert.ErrorIs(t, err, errPluginNameNotSet) }, - expected: identity{ - name: "test-plugin", - idx: "10", + }, + { + name: "index missing", + test: func(t *testing.T) { + defer cleanupEnv() + _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") + _, err := restoreConfig() + assert.ErrorIs(t, err, errPluginIdxNotSet) }, }, { - name: "missing name pulled from args", - identity: identity{ - idx: "10", + name: "invalid index", + test: func(t *testing.T) { + defer cleanupEnv() + _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") + _ = os.Setenv(adaptation.PluginIdxEnvVar, "invalid-index") + _, err := restoreConfig() + assert.ErrorIs(t, err, &api.ErrInvalidPluginIndex{Actual: "invalid-index", Msg: "must be two digits"}) + }, + }, + { + name: "registration timeout missing", + test: func(t *testing.T) { + defer cleanupEnv() + _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") + _ = os.Setenv(adaptation.PluginIdxEnvVar, "10") + _, err := restoreConfig() + assert.ErrorIs(t, err, errPluginRegistrationTimeoutNotSet) }, - args: []string{"test-plugin"}, - expected: identity{ - name: "test-plugin", - idx: "10", + }, + { + name: "invalid registration timeout", + test: func(t *testing.T) { + defer cleanupEnv() + _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") + _ = os.Setenv(adaptation.PluginIdxEnvVar, "10") + _ = os.Setenv(adaptation.PluginRegistrationTimeoutEnvVar, "10") + _, err := restoreConfig() + assert.ErrorContains(t, err, "invalid registration timeout") }, }, { - name: "invalid identity pulled from args", - args: []string{"test-plugin"}, - err: &api.ErrInvalidPluginIndex{Actual: "test", Msg: "must be two digits"}, + name: "socket fd missing", + test: func(t *testing.T) { + defer cleanupEnv() + _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") + _ = os.Setenv(adaptation.PluginIdxEnvVar, "10") + _ = os.Setenv(adaptation.PluginRegistrationTimeoutEnvVar, "10s") + _, err := restoreConfig() + assert.ErrorIs(t, err, errPluginSocketNotSet) + }, }, { - name: "identity pulled from args", - args: []string{"10-test-plugin"}, - expected: identity{ - name: "test-plugin", - idx: "10", + name: "valid config", + test: func(t *testing.T) { + defer cleanupEnv() + _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") + _ = os.Setenv(adaptation.PluginIdxEnvVar, "10") + _ = os.Setenv(adaptation.PluginRegistrationTimeoutEnvVar, "10s") + + sockets, err := nriNet.NewSocketPair() + require.NoError(t, err) + defer sockets.Close() + conn, err := sockets.LocalConn() + require.NoError(t, err) + defer conn.Close() + peerFile := sockets.PeerFile() + defer peerFile.Close() + _ = os.Setenv(adaptation.PluginSocketEnvVar, strconv.Itoa(int(peerFile.Fd()))) + + cfg, err := restoreConfig() + assert.NoError(t, err) + assert.Equal(t, "test-plugin", cfg.identity.name) + assert.Equal(t, "10", cfg.identity.idx) + assert.Equal(t, 10*time.Second, cfg.timeout) + defer cfg.conn.Close() + msg := []byte("hello test") + go func() { + _, err := conn.Write(msg) + assert.NoError(t, err) + }() + buf := make([]byte, len(msg)) + n, err := cfg.conn.Read(buf) + assert.NoError(t, err) + assert.Equal(t, msg, buf[:n]) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.args != nil { - var args []string - copy(args, os.Args) - defer func() { - os.Args = args - }() - os.Args = tt.args - } - i, err := complementIdentity(tt.identity) - if tt.err != nil { - assert.ErrorIs(t, err, tt.err) - return - } - assert.NoError(t, err) - assert.Equal(t, tt.expected, *i) + tt.test(t) }) } } diff --git a/pkg/stub/stub.go b/pkg/stub/stub.go index fac1e121..9a51d2b4 100644 --- a/pkg/stub/stub.go +++ b/pkg/stub/stub.go @@ -29,7 +29,7 @@ type stub struct { } // New creates a stub with the given plugin and options. -func New(p Plugin, opts ...Option) (Stub, error) { +func New(p Plugin, opts ...ManualLaunchOption) (Stub, error) { cfg, err := newCfg(p, opts...) if err != nil { return nil, err diff --git a/vendor/github.com/stretchr/testify/require/doc.go b/vendor/github.com/stretchr/testify/require/doc.go new file mode 100644 index 00000000..96843472 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/doc.go @@ -0,0 +1,29 @@ +// Package require implements the same assertions as the `assert` package but +// stops test execution when a test fails. +// +// # Example Usage +// +// The following is a complete example using require in a standard test function: +// +// import ( +// "testing" +// "github.com/stretchr/testify/require" +// ) +// +// func TestSomething(t *testing.T) { +// +// var a string = "Hello" +// var b string = "Hello" +// +// require.Equal(t, a, b, "The two words should be the same.") +// +// } +// +// # Assertions +// +// The `require` package have same global functions as in the `assert` package, +// but instead of returning a boolean result they call `t.FailNow()`. +// +// Every assertion function also takes an optional string message as the final argument, +// allowing custom error messages to be appended to the message the assertion method outputs. +package require diff --git a/vendor/github.com/stretchr/testify/require/forward_requirements.go b/vendor/github.com/stretchr/testify/require/forward_requirements.go new file mode 100644 index 00000000..1dcb2338 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/forward_requirements.go @@ -0,0 +1,16 @@ +package require + +// Assertions provides assertion methods around the +// TestingT interface. +type Assertions struct { + t TestingT +} + +// New makes a new Assertions object for the specified TestingT. +func New(t TestingT) *Assertions { + return &Assertions{ + t: t, + } +} + +//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require_forward.go.tmpl -include-format-funcs" diff --git a/vendor/github.com/stretchr/testify/require/require.go b/vendor/github.com/stretchr/testify/require/require.go new file mode 100644 index 00000000..d8921950 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require.go @@ -0,0 +1,2124 @@ +// Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT. + +package require + +import ( + assert "github.com/stretchr/testify/assert" + http "net/http" + url "net/url" + time "time" +) + +// Condition uses a Comparison to assert a complex condition. +func Condition(t TestingT, comp assert.Comparison, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Condition(t, comp, msgAndArgs...) { + return + } + t.FailNow() +} + +// Conditionf uses a Comparison to assert a complex condition. +func Conditionf(t TestingT, comp assert.Comparison, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Conditionf(t, comp, msg, args...) { + return + } + t.FailNow() +} + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// require.Contains(t, "Hello World", "World") +// require.Contains(t, ["Hello", "World"], "World") +// require.Contains(t, {"Hello": "World"}, "Hello") +func Contains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Contains(t, s, contains, msgAndArgs...) { + return + } + t.FailNow() +} + +// Containsf asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// require.Containsf(t, "Hello World", "World", "error message %s", "formatted") +// require.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted") +// require.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted") +func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Containsf(t, s, contains, msg, args...) { + return + } + t.FailNow() +} + +// DirExists checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func DirExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.DirExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// DirExistsf checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func DirExistsf(t TestingT, path string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.DirExistsf(t, path, msg, args...) { + return + } + t.FailNow() +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// require.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2]) +func ElementsMatch(t TestingT, listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ElementsMatch(t, listA, listB, msgAndArgs...) { + return + } + t.FailNow() +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// require.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ElementsMatchf(t, listA, listB, msg, args...) { + return + } + t.FailNow() +} + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// require.Empty(t, obj) +func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Empty(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// require.Emptyf(t, obj, "error message %s", "formatted") +func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Emptyf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// Equal asserts that two objects are equal. +// +// require.Equal(t, 123, 123) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func Equal(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Equal(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// require.EqualError(t, err, expectedErrorString) +func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualError(t, theError, errString, msgAndArgs...) { + return + } + t.FailNow() +} + +// EqualErrorf asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// require.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted") +func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualErrorf(t, theError, errString, msg, args...) { + return + } + t.FailNow() +} + +// EqualExportedValues asserts that the types of two objects are equal and their public +// fields are also equal. This is useful for comparing structs that have private fields +// that could potentially differ. +// +// type S struct { +// Exported int +// notExported int +// } +// require.EqualExportedValues(t, S{1, 2}, S{1, 3}) => true +// require.EqualExportedValues(t, S{1, 2}, S{2, 3}) => false +func EqualExportedValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualExportedValues(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// EqualExportedValuesf asserts that the types of two objects are equal and their public +// fields are also equal. This is useful for comparing structs that have private fields +// that could potentially differ. +// +// type S struct { +// Exported int +// notExported int +// } +// require.EqualExportedValuesf(t, S{1, 2}, S{1, 3}, "error message %s", "formatted") => true +// require.EqualExportedValuesf(t, S{1, 2}, S{2, 3}, "error message %s", "formatted") => false +func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualExportedValuesf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// EqualValues asserts that two objects are equal or convertible to the larger +// type and equal. +// +// require.EqualValues(t, uint32(123), int32(123)) +func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualValues(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// EqualValuesf asserts that two objects are equal or convertible to the larger +// type and equal. +// +// require.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted") +func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualValuesf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Equalf asserts that two objects are equal. +// +// require.Equalf(t, 123, 123, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Equalf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if require.Error(t, err) { +// require.Equal(t, expectedError, err) +// } +func Error(t TestingT, err error, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Error(t, err, msgAndArgs...) { + return + } + t.FailNow() +} + +// ErrorAs asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. +// This is a wrapper for errors.As. +func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorAs(t, err, target, msgAndArgs...) { + return + } + t.FailNow() +} + +// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. +// This is a wrapper for errors.As. +func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorAsf(t, err, target, msg, args...) { + return + } + t.FailNow() +} + +// ErrorContains asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +// +// actualObj, err := SomeFunction() +// require.ErrorContains(t, err, expectedErrorSubString) +func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorContains(t, theError, contains, msgAndArgs...) { + return + } + t.FailNow() +} + +// ErrorContainsf asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +// +// actualObj, err := SomeFunction() +// require.ErrorContainsf(t, err, expectedErrorSubString, "error message %s", "formatted") +func ErrorContainsf(t TestingT, theError error, contains string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorContainsf(t, theError, contains, msg, args...) { + return + } + t.FailNow() +} + +// ErrorIs asserts that at least one of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func ErrorIs(t TestingT, err error, target error, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorIs(t, err, target, msgAndArgs...) { + return + } + t.FailNow() +} + +// ErrorIsf asserts that at least one of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorIsf(t, err, target, msg, args...) { + return + } + t.FailNow() +} + +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if require.Errorf(t, err, "error message %s", "formatted") { +// require.Equal(t, expectedErrorf, err) +// } +func Errorf(t TestingT, err error, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Errorf(t, err, msg, args...) { + return + } + t.FailNow() +} + +// Eventually asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// require.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond) +func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Eventually(t, condition, waitFor, tick, msgAndArgs...) { + return + } + t.FailNow() +} + +// EventuallyWithT asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. In contrast to Eventually, +// it supplies a CollectT to the condition function, so that the condition +// function can use the CollectT to call other assertions. +// The condition is considered "met" if no errors are raised in a tick. +// The supplied CollectT collects all errors from one tick (if there are any). +// If the condition is not met before waitFor, the collected errors of +// the last tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// require.EventuallyWithT(t, func(c *require.CollectT) { +// // add assertions as needed; any assertion failure will fail the current tick +// require.True(c, externalValue, "expected 'externalValue' to be true") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") +func EventuallyWithT(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EventuallyWithT(t, condition, waitFor, tick, msgAndArgs...) { + return + } + t.FailNow() +} + +// EventuallyWithTf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. In contrast to Eventually, +// it supplies a CollectT to the condition function, so that the condition +// function can use the CollectT to call other assertions. +// The condition is considered "met" if no errors are raised in a tick. +// The supplied CollectT collects all errors from one tick (if there are any). +// If the condition is not met before waitFor, the collected errors of +// the last tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// require.EventuallyWithTf(t, func(c *require.CollectT, "error message %s", "formatted") { +// // add assertions as needed; any assertion failure will fail the current tick +// require.True(c, externalValue, "expected 'externalValue' to be true") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") +func EventuallyWithTf(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EventuallyWithTf(t, condition, waitFor, tick, msg, args...) { + return + } + t.FailNow() +} + +// Eventuallyf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// require.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Eventuallyf(t, condition, waitFor, tick, msg, args...) { + return + } + t.FailNow() +} + +// Exactly asserts that two objects are equal in value and type. +// +// require.Exactly(t, int32(123), int64(123)) +func Exactly(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Exactly(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// Exactlyf asserts that two objects are equal in value and type. +// +// require.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted") +func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Exactlyf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Fail reports a failure through +func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Fail(t, failureMessage, msgAndArgs...) { + return + } + t.FailNow() +} + +// FailNow fails test +func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FailNow(t, failureMessage, msgAndArgs...) { + return + } + t.FailNow() +} + +// FailNowf fails test +func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FailNowf(t, failureMessage, msg, args...) { + return + } + t.FailNow() +} + +// Failf reports a failure through +func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Failf(t, failureMessage, msg, args...) { + return + } + t.FailNow() +} + +// False asserts that the specified value is false. +// +// require.False(t, myBool) +func False(t TestingT, value bool, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.False(t, value, msgAndArgs...) { + return + } + t.FailNow() +} + +// Falsef asserts that the specified value is false. +// +// require.Falsef(t, myBool, "error message %s", "formatted") +func Falsef(t TestingT, value bool, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Falsef(t, value, msg, args...) { + return + } + t.FailNow() +} + +// FileExists checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func FileExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FileExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// FileExistsf checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func FileExistsf(t TestingT, path string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FileExistsf(t, path, msg, args...) { + return + } + t.FailNow() +} + +// Greater asserts that the first element is greater than the second +// +// require.Greater(t, 2, 1) +// require.Greater(t, float64(2), float64(1)) +// require.Greater(t, "b", "a") +func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Greater(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// GreaterOrEqual asserts that the first element is greater than or equal to the second +// +// require.GreaterOrEqual(t, 2, 1) +// require.GreaterOrEqual(t, 2, 2) +// require.GreaterOrEqual(t, "b", "a") +// require.GreaterOrEqual(t, "b", "b") +func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.GreaterOrEqual(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// GreaterOrEqualf asserts that the first element is greater than or equal to the second +// +// require.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted") +// require.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted") +// require.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted") +// require.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted") +func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.GreaterOrEqualf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// Greaterf asserts that the first element is greater than the second +// +// require.Greaterf(t, 2, 1, "error message %s", "formatted") +// require.Greaterf(t, float64(2), float64(1), "error message %s", "formatted") +// require.Greaterf(t, "b", "a", "error message %s", "formatted") +func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Greaterf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// require.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyContains(t, handler, method, url, values, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPBodyContainsf asserts that a specified handler returns a +// body that contains a string. +// +// require.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyContainsf(t, handler, method, url, values, str, msg, args...) { + return + } + t.FailNow() +} + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// require.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyNotContains(t, handler, method, url, values, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPBodyNotContainsf asserts that a specified handler returns a +// body that does not contain a string. +// +// require.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyNotContainsf(t, handler, method, url, values, str, msg, args...) { + return + } + t.FailNow() +} + +// HTTPError asserts that a specified handler returns an error status code. +// +// require.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPError(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPError(t, handler, method, url, values, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPErrorf asserts that a specified handler returns an error status code. +// +// require.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPErrorf(t, handler, method, url, values, msg, args...) { + return + } + t.FailNow() +} + +// HTTPRedirect asserts that a specified handler returns a redirect status code. +// +// require.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPRedirect(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPRedirect(t, handler, method, url, values, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPRedirectf asserts that a specified handler returns a redirect status code. +// +// require.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPRedirectf(t, handler, method, url, values, msg, args...) { + return + } + t.FailNow() +} + +// HTTPStatusCode asserts that a specified handler returns a specified status code. +// +// require.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501) +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPStatusCode(t, handler, method, url, values, statuscode, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPStatusCodef asserts that a specified handler returns a specified status code. +// +// require.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPStatusCodef(t, handler, method, url, values, statuscode, msg, args...) { + return + } + t.FailNow() +} + +// HTTPSuccess asserts that a specified handler returns a success status code. +// +// require.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPSuccess(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPSuccess(t, handler, method, url, values, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPSuccessf asserts that a specified handler returns a success status code. +// +// require.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPSuccessf(t, handler, method, url, values, msg, args...) { + return + } + t.FailNow() +} + +// Implements asserts that an object is implemented by the specified interface. +// +// require.Implements(t, (*MyInterface)(nil), new(MyObject)) +func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Implements(t, interfaceObject, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// Implementsf asserts that an object is implemented by the specified interface. +// +// require.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Implementsf(t, interfaceObject, object, msg, args...) { + return + } + t.FailNow() +} + +// InDelta asserts that the two numerals are within delta of each other. +// +// require.InDelta(t, math.Pi, 22/7.0, 0.01) +func InDelta(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDelta(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValues(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaMapValues(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaMapValuesf(t, expected, actual, delta, msg, args...) { + return + } + t.FailNow() +} + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func InDeltaSlice(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaSlice(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// InDeltaSlicef is the same as InDelta, except it compares two slices. +func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaSlicef(t, expected, actual, delta, msg, args...) { + return + } + t.FailNow() +} + +// InDeltaf asserts that the two numerals are within delta of each other. +// +// require.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted") +func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaf(t, expected, actual, delta, msg, args...) { + return + } + t.FailNow() +} + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +func InEpsilon(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilon(t, expected, actual, epsilon, msgAndArgs...) { + return + } + t.FailNow() +} + +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlice(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilonSlice(t, expected, actual, epsilon, msgAndArgs...) { + return + } + t.FailNow() +} + +// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilonSlicef(t, expected, actual, epsilon, msg, args...) { + return + } + t.FailNow() +} + +// InEpsilonf asserts that expected and actual have a relative error less than epsilon +func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilonf(t, expected, actual, epsilon, msg, args...) { + return + } + t.FailNow() +} + +// IsDecreasing asserts that the collection is decreasing +// +// require.IsDecreasing(t, []int{2, 1, 0}) +// require.IsDecreasing(t, []float{2, 1}) +// require.IsDecreasing(t, []string{"b", "a"}) +func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsDecreasing(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// IsDecreasingf asserts that the collection is decreasing +// +// require.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted") +// require.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted") +// require.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted") +func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsDecreasingf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// IsIncreasing asserts that the collection is increasing +// +// require.IsIncreasing(t, []int{1, 2, 3}) +// require.IsIncreasing(t, []float{1, 2}) +// require.IsIncreasing(t, []string{"a", "b"}) +func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsIncreasing(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// IsIncreasingf asserts that the collection is increasing +// +// require.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted") +// require.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted") +// require.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted") +func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsIncreasingf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// IsNonDecreasing asserts that the collection is not decreasing +// +// require.IsNonDecreasing(t, []int{1, 1, 2}) +// require.IsNonDecreasing(t, []float{1, 2}) +// require.IsNonDecreasing(t, []string{"a", "b"}) +func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsNonDecreasing(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// IsNonDecreasingf asserts that the collection is not decreasing +// +// require.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted") +// require.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted") +// require.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted") +func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsNonDecreasingf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// IsNonIncreasing asserts that the collection is not increasing +// +// require.IsNonIncreasing(t, []int{2, 1, 1}) +// require.IsNonIncreasing(t, []float{2, 1}) +// require.IsNonIncreasing(t, []string{"b", "a"}) +func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsNonIncreasing(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// IsNonIncreasingf asserts that the collection is not increasing +// +// require.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted") +// require.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted") +// require.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted") +func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsNonIncreasingf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// IsType asserts that the specified objects are of the same type. +func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsType(t, expectedType, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// IsTypef asserts that the specified objects are of the same type. +func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsTypef(t, expectedType, object, msg, args...) { + return + } + t.FailNow() +} + +// JSONEq asserts that two JSON strings are equivalent. +// +// require.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.JSONEq(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// JSONEqf asserts that two JSON strings are equivalent. +// +// require.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.JSONEqf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// require.Len(t, mySlice, 3) +func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Len(t, object, length, msgAndArgs...) { + return + } + t.FailNow() +} + +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// require.Lenf(t, mySlice, 3, "error message %s", "formatted") +func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Lenf(t, object, length, msg, args...) { + return + } + t.FailNow() +} + +// Less asserts that the first element is less than the second +// +// require.Less(t, 1, 2) +// require.Less(t, float64(1), float64(2)) +// require.Less(t, "a", "b") +func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Less(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// LessOrEqual asserts that the first element is less than or equal to the second +// +// require.LessOrEqual(t, 1, 2) +// require.LessOrEqual(t, 2, 2) +// require.LessOrEqual(t, "a", "b") +// require.LessOrEqual(t, "b", "b") +func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.LessOrEqual(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// LessOrEqualf asserts that the first element is less than or equal to the second +// +// require.LessOrEqualf(t, 1, 2, "error message %s", "formatted") +// require.LessOrEqualf(t, 2, 2, "error message %s", "formatted") +// require.LessOrEqualf(t, "a", "b", "error message %s", "formatted") +// require.LessOrEqualf(t, "b", "b", "error message %s", "formatted") +func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.LessOrEqualf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// Lessf asserts that the first element is less than the second +// +// require.Lessf(t, 1, 2, "error message %s", "formatted") +// require.Lessf(t, float64(1), float64(2), "error message %s", "formatted") +// require.Lessf(t, "a", "b", "error message %s", "formatted") +func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Lessf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// Negative asserts that the specified element is negative +// +// require.Negative(t, -1) +// require.Negative(t, -1.23) +func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Negative(t, e, msgAndArgs...) { + return + } + t.FailNow() +} + +// Negativef asserts that the specified element is negative +// +// require.Negativef(t, -1, "error message %s", "formatted") +// require.Negativef(t, -1.23, "error message %s", "formatted") +func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Negativef(t, e, msg, args...) { + return + } + t.FailNow() +} + +// Never asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// require.Never(t, func() bool { return false; }, time.Second, 10*time.Millisecond) +func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Never(t, condition, waitFor, tick, msgAndArgs...) { + return + } + t.FailNow() +} + +// Neverf asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// require.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Neverf(t, condition, waitFor, tick, msg, args...) { + return + } + t.FailNow() +} + +// Nil asserts that the specified object is nil. +// +// require.Nil(t, err) +func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Nil(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// Nilf asserts that the specified object is nil. +// +// require.Nilf(t, err, "error message %s", "formatted") +func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Nilf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// NoDirExists checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func NoDirExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoDirExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// NoDirExistsf checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func NoDirExistsf(t TestingT, path string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoDirExistsf(t, path, msg, args...) { + return + } + t.FailNow() +} + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if require.NoError(t, err) { +// require.Equal(t, expectedObj, actualObj) +// } +func NoError(t TestingT, err error, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoError(t, err, msgAndArgs...) { + return + } + t.FailNow() +} + +// NoErrorf asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if require.NoErrorf(t, err, "error message %s", "formatted") { +// require.Equal(t, expectedObj, actualObj) +// } +func NoErrorf(t TestingT, err error, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoErrorf(t, err, msg, args...) { + return + } + t.FailNow() +} + +// NoFileExists checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func NoFileExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoFileExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// NoFileExistsf checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func NoFileExistsf(t TestingT, path string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoFileExistsf(t, path, msg, args...) { + return + } + t.FailNow() +} + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// require.NotContains(t, "Hello World", "Earth") +// require.NotContains(t, ["Hello", "World"], "Earth") +// require.NotContains(t, {"Hello": "World"}, "Earth") +func NotContains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotContains(t, s, contains, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// require.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted") +// require.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted") +// require.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted") +func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotContainsf(t, s, contains, msg, args...) { + return + } + t.FailNow() +} + +// NotElementsMatch asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// require.NotElementsMatch(t, [1, 1, 2, 3], [1, 1, 2, 3]) -> false +// +// require.NotElementsMatch(t, [1, 1, 2, 3], [1, 2, 3]) -> true +// +// require.NotElementsMatch(t, [1, 2, 3], [1, 2, 4]) -> true +func NotElementsMatch(t TestingT, listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotElementsMatch(t, listA, listB, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotElementsMatchf asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// require.NotElementsMatchf(t, [1, 1, 2, 3], [1, 1, 2, 3], "error message %s", "formatted") -> false +// +// require.NotElementsMatchf(t, [1, 1, 2, 3], [1, 2, 3], "error message %s", "formatted") -> true +// +// require.NotElementsMatchf(t, [1, 2, 3], [1, 2, 4], "error message %s", "formatted") -> true +func NotElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotElementsMatchf(t, listA, listB, msg, args...) { + return + } + t.FailNow() +} + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if require.NotEmpty(t, obj) { +// require.Equal(t, "two", obj[1]) +// } +func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEmpty(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if require.NotEmptyf(t, obj, "error message %s", "formatted") { +// require.Equal(t, "two", obj[1]) +// } +func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEmptyf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// NotEqual asserts that the specified values are NOT equal. +// +// require.NotEqual(t, obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func NotEqual(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqual(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotEqualValues asserts that two objects are not equal even when converted to the same type +// +// require.NotEqualValues(t, obj1, obj2) +func NotEqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqualValues(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotEqualValuesf asserts that two objects are not equal even when converted to the same type +// +// require.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted") +func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqualValuesf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// NotEqualf asserts that the specified values are NOT equal. +// +// require.NotEqualf(t, obj1, obj2, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqualf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// NotErrorAs asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotErrorAs(t, err, target, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotErrorAsf asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func NotErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotErrorAsf(t, err, target, msg, args...) { + return + } + t.FailNow() +} + +// NotErrorIs asserts that none of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func NotErrorIs(t TestingT, err error, target error, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotErrorIs(t, err, target, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotErrorIsf asserts that none of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotErrorIsf(t, err, target, msg, args...) { + return + } + t.FailNow() +} + +// NotImplements asserts that an object does not implement the specified interface. +// +// require.NotImplements(t, (*MyInterface)(nil), new(MyObject)) +func NotImplements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotImplements(t, interfaceObject, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotImplementsf asserts that an object does not implement the specified interface. +// +// require.NotImplementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +func NotImplementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotImplementsf(t, interfaceObject, object, msg, args...) { + return + } + t.FailNow() +} + +// NotNil asserts that the specified object is not nil. +// +// require.NotNil(t, err) +func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotNil(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotNilf asserts that the specified object is not nil. +// +// require.NotNilf(t, err, "error message %s", "formatted") +func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotNilf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// require.NotPanics(t, func(){ RemainCalm() }) +func NotPanics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotPanics(t, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// require.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted") +func NotPanicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotPanicsf(t, f, msg, args...) { + return + } + t.FailNow() +} + +// NotRegexp asserts that a specified regexp does not match a string. +// +// require.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") +// require.NotRegexp(t, "^start", "it's not starting") +func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotRegexp(t, rx, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotRegexpf asserts that a specified regexp does not match a string. +// +// require.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") +// require.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") +func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotRegexpf(t, rx, str, msg, args...) { + return + } + t.FailNow() +} + +// NotSame asserts that two pointers do not reference the same object. +// +// require.NotSame(t, ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func NotSame(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSame(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotSamef asserts that two pointers do not reference the same object. +// +// require.NotSamef(t, ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSamef(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// NotSubset asserts that the specified list(array, slice...) or map does NOT +// contain all elements given in the specified subset list(array, slice...) or +// map. +// +// require.NotSubset(t, [1, 3, 4], [1, 2]) +// require.NotSubset(t, {"x": 1, "y": 2}, {"z": 3}) +func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSubset(t, list, subset, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotSubsetf asserts that the specified list(array, slice...) or map does NOT +// contain all elements given in the specified subset list(array, slice...) or +// map. +// +// require.NotSubsetf(t, [1, 3, 4], [1, 2], "error message %s", "formatted") +// require.NotSubsetf(t, {"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted") +func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSubsetf(t, list, subset, msg, args...) { + return + } + t.FailNow() +} + +// NotZero asserts that i is not the zero value for its type. +func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotZero(t, i, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotZerof asserts that i is not the zero value for its type. +func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotZerof(t, i, msg, args...) { + return + } + t.FailNow() +} + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// require.Panics(t, func(){ GoCrazy() }) +func Panics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Panics(t, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// PanicsWithError asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// require.PanicsWithError(t, "crazy error", func(){ GoCrazy() }) +func PanicsWithError(t TestingT, errString string, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithError(t, errString, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// require.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func PanicsWithErrorf(t TestingT, errString string, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithErrorf(t, errString, f, msg, args...) { + return + } + t.FailNow() +} + +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// require.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) +func PanicsWithValue(t TestingT, expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithValue(t, expected, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// require.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func PanicsWithValuef(t TestingT, expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithValuef(t, expected, f, msg, args...) { + return + } + t.FailNow() +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// require.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") +func Panicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Panicsf(t, f, msg, args...) { + return + } + t.FailNow() +} + +// Positive asserts that the specified element is positive +// +// require.Positive(t, 1) +// require.Positive(t, 1.23) +func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Positive(t, e, msgAndArgs...) { + return + } + t.FailNow() +} + +// Positivef asserts that the specified element is positive +// +// require.Positivef(t, 1, "error message %s", "formatted") +// require.Positivef(t, 1.23, "error message %s", "formatted") +func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Positivef(t, e, msg, args...) { + return + } + t.FailNow() +} + +// Regexp asserts that a specified regexp matches a string. +// +// require.Regexp(t, regexp.MustCompile("start"), "it's starting") +// require.Regexp(t, "start...$", "it's not starting") +func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Regexp(t, rx, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// Regexpf asserts that a specified regexp matches a string. +// +// require.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") +// require.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") +func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Regexpf(t, rx, str, msg, args...) { + return + } + t.FailNow() +} + +// Same asserts that two pointers reference the same object. +// +// require.Same(t, ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func Same(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Same(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// Samef asserts that two pointers reference the same object. +// +// require.Samef(t, ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func Samef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Samef(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Subset asserts that the specified list(array, slice...) or map contains all +// elements given in the specified subset list(array, slice...) or map. +// +// require.Subset(t, [1, 2, 3], [1, 2]) +// require.Subset(t, {"x": 1, "y": 2}, {"x": 1}) +func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Subset(t, list, subset, msgAndArgs...) { + return + } + t.FailNow() +} + +// Subsetf asserts that the specified list(array, slice...) or map contains all +// elements given in the specified subset list(array, slice...) or map. +// +// require.Subsetf(t, [1, 2, 3], [1, 2], "error message %s", "formatted") +// require.Subsetf(t, {"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted") +func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Subsetf(t, list, subset, msg, args...) { + return + } + t.FailNow() +} + +// True asserts that the specified value is true. +// +// require.True(t, myBool) +func True(t TestingT, value bool, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.True(t, value, msgAndArgs...) { + return + } + t.FailNow() +} + +// Truef asserts that the specified value is true. +// +// require.Truef(t, myBool, "error message %s", "formatted") +func Truef(t TestingT, value bool, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Truef(t, value, msg, args...) { + return + } + t.FailNow() +} + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// require.WithinDuration(t, time.Now(), time.Now(), 10*time.Second) +func WithinDuration(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.WithinDuration(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// WithinDurationf asserts that the two times are within duration delta of each other. +// +// require.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.WithinDurationf(t, expected, actual, delta, msg, args...) { + return + } + t.FailNow() +} + +// WithinRange asserts that a time is within a time range (inclusive). +// +// require.WithinRange(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) +func WithinRange(t TestingT, actual time.Time, start time.Time, end time.Time, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.WithinRange(t, actual, start, end, msgAndArgs...) { + return + } + t.FailNow() +} + +// WithinRangef asserts that a time is within a time range (inclusive). +// +// require.WithinRangef(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") +func WithinRangef(t TestingT, actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.WithinRangef(t, actual, start, end, msg, args...) { + return + } + t.FailNow() +} + +// YAMLEq asserts that two YAML strings are equivalent. +func YAMLEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.YAMLEq(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// YAMLEqf asserts that two YAML strings are equivalent. +func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.YAMLEqf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Zero asserts that i is the zero value for its type. +func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Zero(t, i, msgAndArgs...) { + return + } + t.FailNow() +} + +// Zerof asserts that i is the zero value for its type. +func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Zerof(t, i, msg, args...) { + return + } + t.FailNow() +} diff --git a/vendor/github.com/stretchr/testify/require/require.go.tmpl b/vendor/github.com/stretchr/testify/require/require.go.tmpl new file mode 100644 index 00000000..8b328368 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require.go.tmpl @@ -0,0 +1,6 @@ +{{ replace .Comment "assert." "require."}} +func {{.DocInfo.Name}}(t TestingT, {{.Params}}) { + if h, ok := t.(tHelper); ok { h.Helper() } + if assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { return } + t.FailNow() +} diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go b/vendor/github.com/stretchr/testify/require/require_forward.go new file mode 100644 index 00000000..1bd87304 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require_forward.go @@ -0,0 +1,1674 @@ +// Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT. + +package require + +import ( + assert "github.com/stretchr/testify/assert" + http "net/http" + url "net/url" + time "time" +) + +// Condition uses a Comparison to assert a complex condition. +func (a *Assertions) Condition(comp assert.Comparison, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Condition(a.t, comp, msgAndArgs...) +} + +// Conditionf uses a Comparison to assert a complex condition. +func (a *Assertions) Conditionf(comp assert.Comparison, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Conditionf(a.t, comp, msg, args...) +} + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Contains("Hello World", "World") +// a.Contains(["Hello", "World"], "World") +// a.Contains({"Hello": "World"}, "Hello") +func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Contains(a.t, s, contains, msgAndArgs...) +} + +// Containsf asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Containsf("Hello World", "World", "error message %s", "formatted") +// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted") +// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted") +func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Containsf(a.t, s, contains, msg, args...) +} + +// DirExists checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + DirExists(a.t, path, msgAndArgs...) +} + +// DirExistsf checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + DirExistsf(a.t, path, msg, args...) +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatch([1, 3, 2, 3], [1, 3, 3, 2]) +func (a *Assertions) ElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ElementsMatch(a.t, listA, listB, msgAndArgs...) +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatchf([1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ElementsMatchf(a.t, listA, listB, msg, args...) +} + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Empty(obj) +func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Empty(a.t, object, msgAndArgs...) +} + +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Emptyf(obj, "error message %s", "formatted") +func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Emptyf(a.t, object, msg, args...) +} + +// Equal asserts that two objects are equal. +// +// a.Equal(123, 123) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Equal(a.t, expected, actual, msgAndArgs...) +} + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// a.EqualError(err, expectedErrorString) +func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualError(a.t, theError, errString, msgAndArgs...) +} + +// EqualErrorf asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted") +func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualErrorf(a.t, theError, errString, msg, args...) +} + +// EqualExportedValues asserts that the types of two objects are equal and their public +// fields are also equal. This is useful for comparing structs that have private fields +// that could potentially differ. +// +// type S struct { +// Exported int +// notExported int +// } +// a.EqualExportedValues(S{1, 2}, S{1, 3}) => true +// a.EqualExportedValues(S{1, 2}, S{2, 3}) => false +func (a *Assertions) EqualExportedValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualExportedValues(a.t, expected, actual, msgAndArgs...) +} + +// EqualExportedValuesf asserts that the types of two objects are equal and their public +// fields are also equal. This is useful for comparing structs that have private fields +// that could potentially differ. +// +// type S struct { +// Exported int +// notExported int +// } +// a.EqualExportedValuesf(S{1, 2}, S{1, 3}, "error message %s", "formatted") => true +// a.EqualExportedValuesf(S{1, 2}, S{2, 3}, "error message %s", "formatted") => false +func (a *Assertions) EqualExportedValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualExportedValuesf(a.t, expected, actual, msg, args...) +} + +// EqualValues asserts that two objects are equal or convertible to the larger +// type and equal. +// +// a.EqualValues(uint32(123), int32(123)) +func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualValues(a.t, expected, actual, msgAndArgs...) +} + +// EqualValuesf asserts that two objects are equal or convertible to the larger +// type and equal. +// +// a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted") +func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualValuesf(a.t, expected, actual, msg, args...) +} + +// Equalf asserts that two objects are equal. +// +// a.Equalf(123, 123, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Equalf(a.t, expected, actual, msg, args...) +} + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Error(err) { +// assert.Equal(t, expectedError, err) +// } +func (a *Assertions) Error(err error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Error(a.t, err, msgAndArgs...) +} + +// ErrorAs asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. +// This is a wrapper for errors.As. +func (a *Assertions) ErrorAs(err error, target interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorAs(a.t, err, target, msgAndArgs...) +} + +// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. +// This is a wrapper for errors.As. +func (a *Assertions) ErrorAsf(err error, target interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorAsf(a.t, err, target, msg, args...) +} + +// ErrorContains asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +// +// actualObj, err := SomeFunction() +// a.ErrorContains(err, expectedErrorSubString) +func (a *Assertions) ErrorContains(theError error, contains string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorContains(a.t, theError, contains, msgAndArgs...) +} + +// ErrorContainsf asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +// +// actualObj, err := SomeFunction() +// a.ErrorContainsf(err, expectedErrorSubString, "error message %s", "formatted") +func (a *Assertions) ErrorContainsf(theError error, contains string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorContainsf(a.t, theError, contains, msg, args...) +} + +// ErrorIs asserts that at least one of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) ErrorIs(err error, target error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorIs(a.t, err, target, msgAndArgs...) +} + +// ErrorIsf asserts that at least one of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) ErrorIsf(err error, target error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorIsf(a.t, err, target, msg, args...) +} + +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Errorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } +func (a *Assertions) Errorf(err error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Errorf(a.t, err, msg, args...) +} + +// Eventually asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// a.Eventually(func() bool { return true; }, time.Second, 10*time.Millisecond) +func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Eventually(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// EventuallyWithT asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. In contrast to Eventually, +// it supplies a CollectT to the condition function, so that the condition +// function can use the CollectT to call other assertions. +// The condition is considered "met" if no errors are raised in a tick. +// The supplied CollectT collects all errors from one tick (if there are any). +// If the condition is not met before waitFor, the collected errors of +// the last tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// a.EventuallyWithT(func(c *assert.CollectT) { +// // add assertions as needed; any assertion failure will fail the current tick +// assert.True(c, externalValue, "expected 'externalValue' to be true") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") +func (a *Assertions) EventuallyWithT(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EventuallyWithT(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// EventuallyWithTf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. In contrast to Eventually, +// it supplies a CollectT to the condition function, so that the condition +// function can use the CollectT to call other assertions. +// The condition is considered "met" if no errors are raised in a tick. +// The supplied CollectT collects all errors from one tick (if there are any). +// If the condition is not met before waitFor, the collected errors of +// the last tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// a.EventuallyWithTf(func(c *assert.CollectT, "error message %s", "formatted") { +// // add assertions as needed; any assertion failure will fail the current tick +// assert.True(c, externalValue, "expected 'externalValue' to be true") +// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false") +func (a *Assertions) EventuallyWithTf(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EventuallyWithTf(a.t, condition, waitFor, tick, msg, args...) +} + +// Eventuallyf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// a.Eventuallyf(func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func (a *Assertions) Eventuallyf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Eventuallyf(a.t, condition, waitFor, tick, msg, args...) +} + +// Exactly asserts that two objects are equal in value and type. +// +// a.Exactly(int32(123), int64(123)) +func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Exactly(a.t, expected, actual, msgAndArgs...) +} + +// Exactlyf asserts that two objects are equal in value and type. +// +// a.Exactlyf(int32(123), int64(123), "error message %s", "formatted") +func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Exactlyf(a.t, expected, actual, msg, args...) +} + +// Fail reports a failure through +func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Fail(a.t, failureMessage, msgAndArgs...) +} + +// FailNow fails test +func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FailNow(a.t, failureMessage, msgAndArgs...) +} + +// FailNowf fails test +func (a *Assertions) FailNowf(failureMessage string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FailNowf(a.t, failureMessage, msg, args...) +} + +// Failf reports a failure through +func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Failf(a.t, failureMessage, msg, args...) +} + +// False asserts that the specified value is false. +// +// a.False(myBool) +func (a *Assertions) False(value bool, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + False(a.t, value, msgAndArgs...) +} + +// Falsef asserts that the specified value is false. +// +// a.Falsef(myBool, "error message %s", "formatted") +func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Falsef(a.t, value, msg, args...) +} + +// FileExists checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FileExists(a.t, path, msgAndArgs...) +} + +// FileExistsf checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FileExistsf(a.t, path, msg, args...) +} + +// Greater asserts that the first element is greater than the second +// +// a.Greater(2, 1) +// a.Greater(float64(2), float64(1)) +// a.Greater("b", "a") +func (a *Assertions) Greater(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Greater(a.t, e1, e2, msgAndArgs...) +} + +// GreaterOrEqual asserts that the first element is greater than or equal to the second +// +// a.GreaterOrEqual(2, 1) +// a.GreaterOrEqual(2, 2) +// a.GreaterOrEqual("b", "a") +// a.GreaterOrEqual("b", "b") +func (a *Assertions) GreaterOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + GreaterOrEqual(a.t, e1, e2, msgAndArgs...) +} + +// GreaterOrEqualf asserts that the first element is greater than or equal to the second +// +// a.GreaterOrEqualf(2, 1, "error message %s", "formatted") +// a.GreaterOrEqualf(2, 2, "error message %s", "formatted") +// a.GreaterOrEqualf("b", "a", "error message %s", "formatted") +// a.GreaterOrEqualf("b", "b", "error message %s", "formatted") +func (a *Assertions) GreaterOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + GreaterOrEqualf(a.t, e1, e2, msg, args...) +} + +// Greaterf asserts that the first element is greater than the second +// +// a.Greaterf(2, 1, "error message %s", "formatted") +// a.Greaterf(float64(2), float64(1), "error message %s", "formatted") +// a.Greaterf("b", "a", "error message %s", "formatted") +func (a *Assertions) Greaterf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Greaterf(a.t, e1, e2, msg, args...) +} + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...) +} + +// HTTPBodyContainsf asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...) +} + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...) +} + +// HTTPBodyNotContainsf asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...) +} + +// HTTPError asserts that a specified handler returns an error status code. +// +// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPError(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPErrorf asserts that a specified handler returns an error status code. +// +// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPErrorf(a.t, handler, method, url, values, msg, args...) +} + +// HTTPRedirect asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPRedirect(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPRedirectf asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPRedirectf(a.t, handler, method, url, values, msg, args...) +} + +// HTTPStatusCode asserts that a specified handler returns a specified status code. +// +// a.HTTPStatusCode(myHandler, "GET", "/notImplemented", nil, 501) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPStatusCode(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPStatusCode(a.t, handler, method, url, values, statuscode, msgAndArgs...) +} + +// HTTPStatusCodef asserts that a specified handler returns a specified status code. +// +// a.HTTPStatusCodef(myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPStatusCodef(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPStatusCodef(a.t, handler, method, url, values, statuscode, msg, args...) +} + +// HTTPSuccess asserts that a specified handler returns a success status code. +// +// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPSuccess(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPSuccessf asserts that a specified handler returns a success status code. +// +// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPSuccessf(a.t, handler, method, url, values, msg, args...) +} + +// Implements asserts that an object is implemented by the specified interface. +// +// a.Implements((*MyInterface)(nil), new(MyObject)) +func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Implements(a.t, interfaceObject, object, msgAndArgs...) +} + +// Implementsf asserts that an object is implemented by the specified interface. +// +// a.Implementsf((*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Implementsf(a.t, interfaceObject, object, msg, args...) +} + +// InDelta asserts that the two numerals are within delta of each other. +// +// a.InDelta(math.Pi, 22/7.0, 0.01) +func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDelta(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaMapValues(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaMapValuesf(a.t, expected, actual, delta, msg, args...) +} + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaSlicef is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaSlicef(a.t, expected, actual, delta, msg, args...) +} + +// InDeltaf asserts that the two numerals are within delta of each other. +// +// a.InDeltaf(math.Pi, 22/7.0, 0.01, "error message %s", "formatted") +func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaf(a.t, expected, actual, delta, msg, args...) +} + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...) +} + +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...) +} + +// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonSlicef(a.t, expected, actual, epsilon, msg, args...) +} + +// InEpsilonf asserts that expected and actual have a relative error less than epsilon +func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonf(a.t, expected, actual, epsilon, msg, args...) +} + +// IsDecreasing asserts that the collection is decreasing +// +// a.IsDecreasing([]int{2, 1, 0}) +// a.IsDecreasing([]float{2, 1}) +// a.IsDecreasing([]string{"b", "a"}) +func (a *Assertions) IsDecreasing(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsDecreasing(a.t, object, msgAndArgs...) +} + +// IsDecreasingf asserts that the collection is decreasing +// +// a.IsDecreasingf([]int{2, 1, 0}, "error message %s", "formatted") +// a.IsDecreasingf([]float{2, 1}, "error message %s", "formatted") +// a.IsDecreasingf([]string{"b", "a"}, "error message %s", "formatted") +func (a *Assertions) IsDecreasingf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsDecreasingf(a.t, object, msg, args...) +} + +// IsIncreasing asserts that the collection is increasing +// +// a.IsIncreasing([]int{1, 2, 3}) +// a.IsIncreasing([]float{1, 2}) +// a.IsIncreasing([]string{"a", "b"}) +func (a *Assertions) IsIncreasing(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsIncreasing(a.t, object, msgAndArgs...) +} + +// IsIncreasingf asserts that the collection is increasing +// +// a.IsIncreasingf([]int{1, 2, 3}, "error message %s", "formatted") +// a.IsIncreasingf([]float{1, 2}, "error message %s", "formatted") +// a.IsIncreasingf([]string{"a", "b"}, "error message %s", "formatted") +func (a *Assertions) IsIncreasingf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsIncreasingf(a.t, object, msg, args...) +} + +// IsNonDecreasing asserts that the collection is not decreasing +// +// a.IsNonDecreasing([]int{1, 1, 2}) +// a.IsNonDecreasing([]float{1, 2}) +// a.IsNonDecreasing([]string{"a", "b"}) +func (a *Assertions) IsNonDecreasing(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsNonDecreasing(a.t, object, msgAndArgs...) +} + +// IsNonDecreasingf asserts that the collection is not decreasing +// +// a.IsNonDecreasingf([]int{1, 1, 2}, "error message %s", "formatted") +// a.IsNonDecreasingf([]float{1, 2}, "error message %s", "formatted") +// a.IsNonDecreasingf([]string{"a", "b"}, "error message %s", "formatted") +func (a *Assertions) IsNonDecreasingf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsNonDecreasingf(a.t, object, msg, args...) +} + +// IsNonIncreasing asserts that the collection is not increasing +// +// a.IsNonIncreasing([]int{2, 1, 1}) +// a.IsNonIncreasing([]float{2, 1}) +// a.IsNonIncreasing([]string{"b", "a"}) +func (a *Assertions) IsNonIncreasing(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsNonIncreasing(a.t, object, msgAndArgs...) +} + +// IsNonIncreasingf asserts that the collection is not increasing +// +// a.IsNonIncreasingf([]int{2, 1, 1}, "error message %s", "formatted") +// a.IsNonIncreasingf([]float{2, 1}, "error message %s", "formatted") +// a.IsNonIncreasingf([]string{"b", "a"}, "error message %s", "formatted") +func (a *Assertions) IsNonIncreasingf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsNonIncreasingf(a.t, object, msg, args...) +} + +// IsType asserts that the specified objects are of the same type. +func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsType(a.t, expectedType, object, msgAndArgs...) +} + +// IsTypef asserts that the specified objects are of the same type. +func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsTypef(a.t, expectedType, object, msg, args...) +} + +// JSONEq asserts that two JSON strings are equivalent. +// +// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + JSONEq(a.t, expected, actual, msgAndArgs...) +} + +// JSONEqf asserts that two JSON strings are equivalent. +// +// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + JSONEqf(a.t, expected, actual, msg, args...) +} + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// a.Len(mySlice, 3) +func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Len(a.t, object, length, msgAndArgs...) +} + +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// a.Lenf(mySlice, 3, "error message %s", "formatted") +func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Lenf(a.t, object, length, msg, args...) +} + +// Less asserts that the first element is less than the second +// +// a.Less(1, 2) +// a.Less(float64(1), float64(2)) +// a.Less("a", "b") +func (a *Assertions) Less(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Less(a.t, e1, e2, msgAndArgs...) +} + +// LessOrEqual asserts that the first element is less than or equal to the second +// +// a.LessOrEqual(1, 2) +// a.LessOrEqual(2, 2) +// a.LessOrEqual("a", "b") +// a.LessOrEqual("b", "b") +func (a *Assertions) LessOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + LessOrEqual(a.t, e1, e2, msgAndArgs...) +} + +// LessOrEqualf asserts that the first element is less than or equal to the second +// +// a.LessOrEqualf(1, 2, "error message %s", "formatted") +// a.LessOrEqualf(2, 2, "error message %s", "formatted") +// a.LessOrEqualf("a", "b", "error message %s", "formatted") +// a.LessOrEqualf("b", "b", "error message %s", "formatted") +func (a *Assertions) LessOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + LessOrEqualf(a.t, e1, e2, msg, args...) +} + +// Lessf asserts that the first element is less than the second +// +// a.Lessf(1, 2, "error message %s", "formatted") +// a.Lessf(float64(1), float64(2), "error message %s", "formatted") +// a.Lessf("a", "b", "error message %s", "formatted") +func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Lessf(a.t, e1, e2, msg, args...) +} + +// Negative asserts that the specified element is negative +// +// a.Negative(-1) +// a.Negative(-1.23) +func (a *Assertions) Negative(e interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Negative(a.t, e, msgAndArgs...) +} + +// Negativef asserts that the specified element is negative +// +// a.Negativef(-1, "error message %s", "formatted") +// a.Negativef(-1.23, "error message %s", "formatted") +func (a *Assertions) Negativef(e interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Negativef(a.t, e, msg, args...) +} + +// Never asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// a.Never(func() bool { return false; }, time.Second, 10*time.Millisecond) +func (a *Assertions) Never(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Never(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// Neverf asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// a.Neverf(func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func (a *Assertions) Neverf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Neverf(a.t, condition, waitFor, tick, msg, args...) +} + +// Nil asserts that the specified object is nil. +// +// a.Nil(err) +func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Nil(a.t, object, msgAndArgs...) +} + +// Nilf asserts that the specified object is nil. +// +// a.Nilf(err, "error message %s", "formatted") +func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Nilf(a.t, object, msg, args...) +} + +// NoDirExists checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func (a *Assertions) NoDirExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoDirExists(a.t, path, msgAndArgs...) +} + +// NoDirExistsf checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func (a *Assertions) NoDirExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoDirExistsf(a.t, path, msg, args...) +} + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoError(err) { +// assert.Equal(t, expectedObj, actualObj) +// } +func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoError(a.t, err, msgAndArgs...) +} + +// NoErrorf asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoErrorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } +func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoErrorf(a.t, err, msg, args...) +} + +// NoFileExists checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func (a *Assertions) NoFileExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoFileExists(a.t, path, msgAndArgs...) +} + +// NoFileExistsf checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func (a *Assertions) NoFileExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoFileExistsf(a.t, path, msg, args...) +} + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContains("Hello World", "Earth") +// a.NotContains(["Hello", "World"], "Earth") +// a.NotContains({"Hello": "World"}, "Earth") +func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotContains(a.t, s, contains, msgAndArgs...) +} + +// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted") +// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted") +// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted") +func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotContainsf(a.t, s, contains, msg, args...) +} + +// NotElementsMatch asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// a.NotElementsMatch([1, 1, 2, 3], [1, 1, 2, 3]) -> false +// +// a.NotElementsMatch([1, 1, 2, 3], [1, 2, 3]) -> true +// +// a.NotElementsMatch([1, 2, 3], [1, 2, 4]) -> true +func (a *Assertions) NotElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotElementsMatch(a.t, listA, listB, msgAndArgs...) +} + +// NotElementsMatchf asserts that the specified listA(array, slice...) is NOT equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should not match. +// This is an inverse of ElementsMatch. +// +// a.NotElementsMatchf([1, 1, 2, 3], [1, 1, 2, 3], "error message %s", "formatted") -> false +// +// a.NotElementsMatchf([1, 1, 2, 3], [1, 2, 3], "error message %s", "formatted") -> true +// +// a.NotElementsMatchf([1, 2, 3], [1, 2, 4], "error message %s", "formatted") -> true +func (a *Assertions) NotElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotElementsMatchf(a.t, listA, listB, msg, args...) +} + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if a.NotEmpty(obj) { +// assert.Equal(t, "two", obj[1]) +// } +func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEmpty(a.t, object, msgAndArgs...) +} + +// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if a.NotEmptyf(obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEmptyf(a.t, object, msg, args...) +} + +// NotEqual asserts that the specified values are NOT equal. +// +// a.NotEqual(obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqual(a.t, expected, actual, msgAndArgs...) +} + +// NotEqualValues asserts that two objects are not equal even when converted to the same type +// +// a.NotEqualValues(obj1, obj2) +func (a *Assertions) NotEqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqualValues(a.t, expected, actual, msgAndArgs...) +} + +// NotEqualValuesf asserts that two objects are not equal even when converted to the same type +// +// a.NotEqualValuesf(obj1, obj2, "error message %s", "formatted") +func (a *Assertions) NotEqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqualValuesf(a.t, expected, actual, msg, args...) +} + +// NotEqualf asserts that the specified values are NOT equal. +// +// a.NotEqualf(obj1, obj2, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqualf(a.t, expected, actual, msg, args...) +} + +// NotErrorAs asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func (a *Assertions) NotErrorAs(err error, target interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotErrorAs(a.t, err, target, msgAndArgs...) +} + +// NotErrorAsf asserts that none of the errors in err's chain matches target, +// but if so, sets target to that error value. +func (a *Assertions) NotErrorAsf(err error, target interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotErrorAsf(a.t, err, target, msg, args...) +} + +// NotErrorIs asserts that none of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotErrorIs(a.t, err, target, msgAndArgs...) +} + +// NotErrorIsf asserts that none of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotErrorIsf(a.t, err, target, msg, args...) +} + +// NotImplements asserts that an object does not implement the specified interface. +// +// a.NotImplements((*MyInterface)(nil), new(MyObject)) +func (a *Assertions) NotImplements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotImplements(a.t, interfaceObject, object, msgAndArgs...) +} + +// NotImplementsf asserts that an object does not implement the specified interface. +// +// a.NotImplementsf((*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +func (a *Assertions) NotImplementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotImplementsf(a.t, interfaceObject, object, msg, args...) +} + +// NotNil asserts that the specified object is not nil. +// +// a.NotNil(err) +func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotNil(a.t, object, msgAndArgs...) +} + +// NotNilf asserts that the specified object is not nil. +// +// a.NotNilf(err, "error message %s", "formatted") +func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotNilf(a.t, object, msg, args...) +} + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanics(func(){ RemainCalm() }) +func (a *Assertions) NotPanics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotPanics(a.t, f, msgAndArgs...) +} + +// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted") +func (a *Assertions) NotPanicsf(f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotPanicsf(a.t, f, msg, args...) +} + +// NotRegexp asserts that a specified regexp does not match a string. +// +// a.NotRegexp(regexp.MustCompile("starts"), "it's starting") +// a.NotRegexp("^start", "it's not starting") +func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotRegexp(a.t, rx, str, msgAndArgs...) +} + +// NotRegexpf asserts that a specified regexp does not match a string. +// +// a.NotRegexpf(regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") +// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") +func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotRegexpf(a.t, rx, str, msg, args...) +} + +// NotSame asserts that two pointers do not reference the same object. +// +// a.NotSame(ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) NotSame(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSame(a.t, expected, actual, msgAndArgs...) +} + +// NotSamef asserts that two pointers do not reference the same object. +// +// a.NotSamef(ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) NotSamef(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSamef(a.t, expected, actual, msg, args...) +} + +// NotSubset asserts that the specified list(array, slice...) or map does NOT +// contain all elements given in the specified subset list(array, slice...) or +// map. +// +// a.NotSubset([1, 3, 4], [1, 2]) +// a.NotSubset({"x": 1, "y": 2}, {"z": 3}) +func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSubset(a.t, list, subset, msgAndArgs...) +} + +// NotSubsetf asserts that the specified list(array, slice...) or map does NOT +// contain all elements given in the specified subset list(array, slice...) or +// map. +// +// a.NotSubsetf([1, 3, 4], [1, 2], "error message %s", "formatted") +// a.NotSubsetf({"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted") +func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSubsetf(a.t, list, subset, msg, args...) +} + +// NotZero asserts that i is not the zero value for its type. +func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotZero(a.t, i, msgAndArgs...) +} + +// NotZerof asserts that i is not the zero value for its type. +func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotZerof(a.t, i, msg, args...) +} + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panics(func(){ GoCrazy() }) +func (a *Assertions) Panics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Panics(a.t, f, msgAndArgs...) +} + +// PanicsWithError asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// a.PanicsWithError("crazy error", func(){ GoCrazy() }) +func (a *Assertions) PanicsWithError(errString string, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithError(a.t, errString, f, msgAndArgs...) +} + +// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// a.PanicsWithErrorf("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) PanicsWithErrorf(errString string, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithErrorf(a.t, errString, f, msg, args...) +} + +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValue("crazy error", func(){ GoCrazy() }) +func (a *Assertions) PanicsWithValue(expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithValue(a.t, expected, f, msgAndArgs...) +} + +// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) PanicsWithValuef(expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithValuef(a.t, expected, f, msg, args...) +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) Panicsf(f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Panicsf(a.t, f, msg, args...) +} + +// Positive asserts that the specified element is positive +// +// a.Positive(1) +// a.Positive(1.23) +func (a *Assertions) Positive(e interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Positive(a.t, e, msgAndArgs...) +} + +// Positivef asserts that the specified element is positive +// +// a.Positivef(1, "error message %s", "formatted") +// a.Positivef(1.23, "error message %s", "formatted") +func (a *Assertions) Positivef(e interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Positivef(a.t, e, msg, args...) +} + +// Regexp asserts that a specified regexp matches a string. +// +// a.Regexp(regexp.MustCompile("start"), "it's starting") +// a.Regexp("start...$", "it's not starting") +func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Regexp(a.t, rx, str, msgAndArgs...) +} + +// Regexpf asserts that a specified regexp matches a string. +// +// a.Regexpf(regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") +// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") +func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Regexpf(a.t, rx, str, msg, args...) +} + +// Same asserts that two pointers reference the same object. +// +// a.Same(ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) Same(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Same(a.t, expected, actual, msgAndArgs...) +} + +// Samef asserts that two pointers reference the same object. +// +// a.Samef(ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) Samef(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Samef(a.t, expected, actual, msg, args...) +} + +// Subset asserts that the specified list(array, slice...) or map contains all +// elements given in the specified subset list(array, slice...) or map. +// +// a.Subset([1, 2, 3], [1, 2]) +// a.Subset({"x": 1, "y": 2}, {"x": 1}) +func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Subset(a.t, list, subset, msgAndArgs...) +} + +// Subsetf asserts that the specified list(array, slice...) or map contains all +// elements given in the specified subset list(array, slice...) or map. +// +// a.Subsetf([1, 2, 3], [1, 2], "error message %s", "formatted") +// a.Subsetf({"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted") +func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Subsetf(a.t, list, subset, msg, args...) +} + +// True asserts that the specified value is true. +// +// a.True(myBool) +func (a *Assertions) True(value bool, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + True(a.t, value, msgAndArgs...) +} + +// Truef asserts that the specified value is true. +// +// a.Truef(myBool, "error message %s", "formatted") +func (a *Assertions) Truef(value bool, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Truef(a.t, value, msg, args...) +} + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// a.WithinDuration(time.Now(), time.Now(), 10*time.Second) +func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinDuration(a.t, expected, actual, delta, msgAndArgs...) +} + +// WithinDurationf asserts that the two times are within duration delta of each other. +// +// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinDurationf(a.t, expected, actual, delta, msg, args...) +} + +// WithinRange asserts that a time is within a time range (inclusive). +// +// a.WithinRange(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) +func (a *Assertions) WithinRange(actual time.Time, start time.Time, end time.Time, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinRange(a.t, actual, start, end, msgAndArgs...) +} + +// WithinRangef asserts that a time is within a time range (inclusive). +// +// a.WithinRangef(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") +func (a *Assertions) WithinRangef(actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinRangef(a.t, actual, start, end, msg, args...) +} + +// YAMLEq asserts that two YAML strings are equivalent. +func (a *Assertions) YAMLEq(expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + YAMLEq(a.t, expected, actual, msgAndArgs...) +} + +// YAMLEqf asserts that two YAML strings are equivalent. +func (a *Assertions) YAMLEqf(expected string, actual string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + YAMLEqf(a.t, expected, actual, msg, args...) +} + +// Zero asserts that i is the zero value for its type. +func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Zero(a.t, i, msgAndArgs...) +} + +// Zerof asserts that i is the zero value for its type. +func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Zerof(a.t, i, msg, args...) +} diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl b/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl new file mode 100644 index 00000000..54124df1 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl @@ -0,0 +1,5 @@ +{{.CommentWithoutT "a"}} +func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) { + if h, ok := a.t.(tHelper); ok { h.Helper() } + {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) +} diff --git a/vendor/github.com/stretchr/testify/require/requirements.go b/vendor/github.com/stretchr/testify/require/requirements.go new file mode 100644 index 00000000..6b7ce929 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/requirements.go @@ -0,0 +1,29 @@ +package require + +// TestingT is an interface wrapper around *testing.T +type TestingT interface { + Errorf(format string, args ...interface{}) + FailNow() +} + +type tHelper = interface { + Helper() +} + +// ComparisonAssertionFunc is a common function prototype when comparing two values. Can be useful +// for table driven tests. +type ComparisonAssertionFunc func(TestingT, interface{}, interface{}, ...interface{}) + +// ValueAssertionFunc is a common function prototype when validating a single value. Can be useful +// for table driven tests. +type ValueAssertionFunc func(TestingT, interface{}, ...interface{}) + +// BoolAssertionFunc is a common function prototype when validating a bool value. Can be useful +// for table driven tests. +type BoolAssertionFunc func(TestingT, bool, ...interface{}) + +// ErrorAssertionFunc is a common function prototype when validating an error value. Can be useful +// for table driven tests. +type ErrorAssertionFunc func(TestingT, error, ...interface{}) + +//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require.go.tmpl -include-format-funcs" diff --git a/vendor/modules.txt b/vendor/modules.txt index 5c59fa3f..9891d930 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -54,6 +54,7 @@ github.com/sirupsen/logrus ## explicit; go 1.17 github.com/stretchr/testify/assert github.com/stretchr/testify/assert/yaml +github.com/stretchr/testify/require # github.com/tetratelabs/wazero v1.8.2-0.20241030035603-dc08732e57d5 ## explicit; go 1.21 github.com/tetratelabs/wazero From 1736c4d8617e3b0da64e79474807c632ee95f614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Gro=C3=9Fmann?= Date: Wed, 18 Jun 2025 16:34:26 +0200 Subject: [PATCH 6/7] refactor(sdk): further simplifications due to removed index field --- pkg/adaptation/adaptation.go | 46 ++++++-- pkg/adaptation/adaptation_test.go | 88 ++++++++++++++++ pkg/api/util.go | 48 --------- pkg/api/util_test.go | 83 --------------- pkg/stub/config.go | 149 +++++--------------------- pkg/stub/config_test.go | 169 +++++------------------------- pkg/stub/stub.go | 4 +- 7 files changed, 183 insertions(+), 404 deletions(-) create mode 100644 pkg/adaptation/adaptation_test.go delete mode 100644 pkg/api/util.go delete mode 100644 pkg/api/util_test.go diff --git a/pkg/adaptation/adaptation.go b/pkg/adaptation/adaptation.go index 9b6b35f8..8d793b67 100644 --- a/pkg/adaptation/adaptation.go +++ b/pkg/adaptation/adaptation.go @@ -1,21 +1,17 @@ package adaptation import ( + "encoding/json" + "errors" + "fmt" "os" "path/filepath" "time" ) const ( - // PluginSocketEnvVar is used to inform plugins about pre-connected sockets. - PluginSocketEnvVar = "SECRETS_ENGINE_PLUGIN_SOCKET" - // PluginNameEnvVar is used to inform engine-launched plugins about their name. - PluginNameEnvVar = "PROVIDER_PLUGIN_NAME" - // PluginIdxEnvVar is used to inform engine-launched plugins about their ID. - PluginIdxEnvVar = "PROVIDER_PLUGIN_IDX" - // PluginRegistrationTimeoutEnvVar is used to inform plugins about the registration timeout. - // (parsed via time.ParseDuration) - PluginRegistrationTimeoutEnvVar = "PROVIDER_PLUGIN_REGISTRATION_TIMEOUT" + // PluginLaunchedByEngineVar is used to inform engine-launched plugins about their name. + PluginLaunchedByEngineVar = "DOCKER_SECRETS_ENGINE_LAUNCH_CFG" // DefaultPluginRegistrationTimeout is the default timeout for plugin registration. DefaultPluginRegistrationTimeout = 5 * time.Second ) @@ -29,3 +25,35 @@ func DefaultSocketPath() string { } return filepath.Join(os.TempDir(), "secrets-engine", "engine.sock") } + +type PluginConfigFromEngine struct { + Name string `json:"name"` + RegistrationTimeout time.Duration `json:"timeout"` + Fd int `json:"fd"` +} + +func (c *PluginConfigFromEngine) ToString() (string, error) { + result, err := json.Marshal(c) + if err != nil { + return "", err + } + return string(result), nil +} + +func NewPluginConfigFromEngineFromString(in string) (*PluginConfigFromEngine, error) { + var result PluginConfigFromEngine + if err := json.Unmarshal([]byte(in), &result); err != nil { + return nil, fmt.Errorf("failed to decode plugin config from engine %q: %w", PluginLaunchedByEngineVar, err) + } + if result.Name == "" { + return nil, errors.New("plugin name is required") + } + if result.RegistrationTimeout == 0 { + return nil, errors.New("plugin registration timeout is required") + } + if result.Fd <= 2 { + // File descriptors 0, 1, and 2 are reserved for stdin, stdout, and stderr. + return nil, errors.New("invalid file descriptor for plugin connection") + } + return &result, nil +} diff --git a/pkg/adaptation/adaptation_test.go b/pkg/adaptation/adaptation_test.go new file mode 100644 index 00000000..c7175122 --- /dev/null +++ b/pkg/adaptation/adaptation_test.go @@ -0,0 +1,88 @@ +package adaptation + +import ( + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockPluginNameWithLength(n int) string { + var sb strings.Builder + sb.Grow(n) + for i := 0; i < n; i++ { + if i%2 == 0 { + sb.WriteByte('a') + } else { + sb.WriteByte('b') + } + } + return sb.String() +} + +func TestPluginConfigFromEngine_ToString(t *testing.T) { + in := PluginConfigFromEngine{ + Name: mockPluginNameWithLength(500), + RegistrationTimeout: 27 * time.Nanosecond, + Fd: 10, + } + out, err := in.ToString() + assert.NoError(t, err) + // This is coming from here: https://superuser.com/questions/1070272/why-does-windows-have-a-limit-on-environment-variables-at-all + // -> we verify that a plugin name of 500 characters is still within the limit + assert.LessOrEqual(t, len(out), 2048) + restored, err := NewPluginConfigFromEngineFromString(out) + assert.NoError(t, err) + assert.Equal(t, in, *restored) +} + +func TestNewPluginConfigFromEngineFromString(t *testing.T) { + tests := []struct { + name string + in PluginConfigFromEngine + err string + }{ + { + name: "name is empty", + err: "name is required", + }, + { + name: "registration timeout is zero", + in: PluginConfigFromEngine{ + Name: "test-plugin", + }, + err: "registration timeout is required", + }, + { + name: "fd is nonsense", + in: PluginConfigFromEngine{ + Name: "test-plugin", + RegistrationTimeout: 10 * time.Second, + Fd: 2, + }, + err: "invalid file descriptor", + }, + { + name: "valid config", + in: PluginConfigFromEngine{ + Name: "test-plugin", + RegistrationTimeout: 10 * time.Second, + Fd: 10, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + out, err := tt.in.ToString() + require.NoError(t, err) + _, err = NewPluginConfigFromEngineFromString(out) + if tt.err != "" { + assert.ErrorContains(t, err, tt.err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/pkg/api/util.go b/pkg/api/util.go deleted file mode 100644 index 8240b8ba..00000000 --- a/pkg/api/util.go +++ /dev/null @@ -1,48 +0,0 @@ -package api - -import ( - "errors" - "fmt" - "strings" -) - -func ParsePluginName(name string) (string, string, error) { - split := strings.SplitN(name, "-", 2) - if len(split) < 2 { - return "", "", fmt.Errorf("invalid plugin name %q, <[0-9][0-9]>- expected", name) - } - - if err := CheckPluginIndex(split[0]); err != nil { - return "", "", err - } - - return split[0], split[1], nil -} - -type ErrInvalidPluginIndex struct { - Actual string - Msg string -} - -func (e ErrInvalidPluginIndex) Error() string { - return fmt.Sprintf("invalid plugin index %q: %s", e.Actual, e.Msg) -} - -func (e ErrInvalidPluginIndex) Is(target error) bool { - var t *ErrInvalidPluginIndex - ok := errors.As(target, &t) - if !ok { - return false - } - return e.Actual == t.Actual && e.Msg == t.Msg -} - -func CheckPluginIndex(idx string) error { - if len(idx) != 2 { - return &ErrInvalidPluginIndex{Actual: idx, Msg: "must be two digits"} - } - if !('0' <= idx[0] && idx[0] <= '9') || !('0' <= idx[1] && idx[1] <= '9') { - return &ErrInvalidPluginIndex{Actual: idx, Msg: "pattern does not match [0-9][0-9]"} - } - return nil -} diff --git a/pkg/api/util_test.go b/pkg/api/util_test.go deleted file mode 100644 index e960855c..00000000 --- a/pkg/api/util_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package api - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestParsePluginName(t *testing.T) { - tests := []struct { - name string - input string - expectedIdx string - expectedName string - expectErr string - }{ - { - name: "empty", - expectErr: "invalid plugin name \"\"", - }, - { - name: "no index", - input: "pluginname", - expectErr: "invalid plugin name \"pluginname\"", - }, - { - name: "valid index and name", - input: "10-plugin-name", - expectedIdx: "10", - expectedName: "plugin-name", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - idx, name, err := ParsePluginName(tt.input) - if tt.expectErr != "" { - assert.ErrorContains(t, err, tt.expectErr) - return - } - assert.NoError(t, err) - assert.Equal(t, tt.expectedIdx, idx) - assert.Equal(t, tt.expectedName, name) - }) - } -} - -func TestCheckPluginIndex(t *testing.T) { - tests := []struct { - name string - index string - err error - }{ - { - name: "valid index", - index: "10", - }, - { - name: "invalid index - too short", - index: "1", - err: &ErrInvalidPluginIndex{Actual: "1", Msg: "must be two digits"}, - }, - { - name: "invalid index - too long", - index: "100", - err: &ErrInvalidPluginIndex{Actual: "100", Msg: "must be two digits"}, - }, - { - name: "invalid index - non-digit characters", - index: "1a", - err: &ErrInvalidPluginIndex{Actual: "1a", Msg: "pattern does not match [0-9][0-9]"}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := CheckPluginIndex(tt.index) - if tt.err != nil { - assert.ErrorIs(t, err, tt.err) - } else { - assert.NoError(t, err) - } - }) - } -} diff --git a/pkg/stub/config.go b/pkg/stub/config.go index 69e8fffa..f86c415c 100644 --- a/pkg/stub/config.go +++ b/pkg/stub/config.go @@ -9,10 +9,7 @@ import ( "strconv" "time" - "github.com/sirupsen/logrus" - "github.com/docker/secrets-engine/pkg/adaptation" - "github.com/docker/secrets-engine/pkg/api" ) // ManualLaunchOption to apply to a plugin during its creation @@ -30,17 +27,6 @@ func WithPluginName(name string) ManualLaunchOption { } } -// WithPluginIdx sets the index to use in plugin registration. -func WithPluginIdx(idx string) ManualLaunchOption { - return func(s *cfg) error { - if err := api.CheckPluginIndex(idx); err != nil { - return err - } - s.idx = idx - return nil - } -} - // WithRegistrationTimeout sets custom registration timeout. func WithRegistrationTimeout(timeout time.Duration) ManualLaunchOption { return func(s *cfg) error { @@ -49,21 +35,6 @@ func WithRegistrationTimeout(timeout time.Duration) ManualLaunchOption { } } -// WithSocketPath sets the secrets engine socket path to connect to. -func WithSocketPath(path string) ManualLaunchOption { - return func(s *cfg) error { - if s.conn != nil { - return errors.New("cannot set socket path when a connection is already set") - } - conn, err := net.Dial("unix", path) - if err != nil { - return fmt.Errorf("failed to connect to socket %q: %w", path, err) - } - s.conn = conn - return nil - } -} - // WithConnection sets an existing secrets engine connection to use. func WithConnection(conn net.Conn) ManualLaunchOption { return func(s *cfg) error { @@ -76,28 +47,19 @@ func WithConnection(conn net.Conn) ManualLaunchOption { } type cfg struct { - plugin Plugin - identity + plugin Plugin + name string conn net.Conn registrationTimeout time.Duration } func newCfg(p Plugin, opts ...ManualLaunchOption) (*cfg, error) { - if ShouldHaveBeenLaunchedByEngine() { - logrus.Info("Plugin launched by engine, restoring config...") - if len(opts) > 0 { - return nil, errors.New("plugin launched by secrets engine, cannot use manual launch options") - } - engineCfg, err := restoreConfig() - if err != nil { - return nil, err - } - return &cfg{ - plugin: p, - identity: engineCfg.identity, - conn: engineCfg.conn, - registrationTimeout: engineCfg.timeout, - }, nil + engineCfg, err := restoreConfig(p) + if err != nil && !errors.Is(err, errPluginNotLaunchedByEngine) { + return nil, err + } + if err == nil && engineCfg != nil { + return engineCfg, nil } return newCfgForManualLaunch(p, opts...) } @@ -120,98 +82,41 @@ func newCfgForManualLaunch(p Plugin, opts ...ManualLaunchOption) (*cfg, error) { } cfg.conn = conn } - if cfg.idx != "" && cfg.name == "" { - cfg.name = filepath.Base(os.Args[0]) - } - if cfg.idx == "" && cfg.name == "" { - idx, name, err := api.ParsePluginName(filepath.Base(os.Args[0])) - if err != nil { - return nil, err + if cfg.name == "" { + if len(os.Args) == 0 { + // This should never happen in practice but can happen in tests or when something else empties os.Args for whatever reason. + return nil, errors.New("plugin name must be specified (could not derive from os.Args)") } - cfg.idx = idx - cfg.name = name + cfg.name = filepath.Base(os.Args[0]) } return cfg, nil } var ( - errPluginNameNotSet = errors.New("plugin name not set") - errPluginIdxNotSet = errors.New("plugin index not set") - errPluginRegistrationTimeoutNotSet = errors.New("plugin registration timeout not set") - errPluginSocketNotSet = errors.New("plugin socket fd not set in environment variables") + errPluginNotLaunchedByEngine = errors.New("plugin not launched by secrets engine") ) -type configFromEngine struct { - identity identity - timeout time.Duration - conn net.Conn -} - -// Note: Partially set ENV based config as an error, as we expect the -// secret engine to always set all ENV based configuration. -func restoreConfig() (*configFromEngine, error) { - var ( - name string - idx string - timeoutStr string - env string - ) - if name = os.Getenv(adaptation.PluginNameEnvVar); name == "" { - return nil, errPluginNameNotSet - } - if idx = os.Getenv(adaptation.PluginIdxEnvVar); idx == "" { - return nil, errPluginIdxNotSet - } - if err := api.CheckPluginIndex(idx); err != nil { - return nil, fmt.Errorf("invalid plugin index %q: %w", idx, err) - } - if timeoutStr = os.Getenv(adaptation.PluginRegistrationTimeoutEnvVar); timeoutStr == "" { - return nil, errPluginRegistrationTimeoutNotSet +func restoreConfig(p Plugin) (*cfg, error) { + cfgString := os.Getenv(adaptation.PluginLaunchedByEngineVar) + if cfgString == "" { + return nil, errPluginNotLaunchedByEngine } - timeout, err := time.ParseDuration(timeoutStr) + c, err := adaptation.NewPluginConfigFromEngineFromString(cfgString) if err != nil { - return nil, fmt.Errorf("invalid registration timeout %q: %w", timeoutStr, err) + return nil, err } - if env = os.Getenv(adaptation.PluginSocketEnvVar); env == "" { - return nil, errPluginSocketNotSet - } - fd, err := strconv.Atoi(env) - if err != nil { - return nil, fmt.Errorf("invalid socket fd (%s=%q): %w", adaptation.PluginSocketEnvVar, env, err) - } - conn, err := connectionFromFileDescriptor(fd) + conn, err := connectionFromFileDescriptor(c.Fd) if err != nil { - return nil, fmt.Errorf("invalid socket (%d) in environment: %w", fd, err) + return nil, fmt.Errorf("invalid socket (%d) in environment: %w", c.Fd, err) } - return &configFromEngine{ - identity: identity{name: name, idx: idx}, - timeout: timeout, - conn: conn, + return &cfg{ + plugin: p, + name: c.Name, + conn: conn, + registrationTimeout: c.RegistrationTimeout, }, nil } -// ShouldHaveBeenLaunchedByEngine checks if the plugin was launched by the secrets engine. -// Note: There's no 100% guarantee that this really happened, but we take the custom internal -// environment variables as indication that it should have happened. -func ShouldHaveBeenLaunchedByEngine() bool { - // In theory, all variables should always be set by the engine and we'd just need to check one. - // But there could be a bug, so we check for any and verify all values are set correctly later. - name := os.Getenv(adaptation.PluginNameEnvVar) - idx := os.Getenv(adaptation.PluginIdxEnvVar) - timeoutStr := os.Getenv(adaptation.PluginRegistrationTimeoutEnvVar) - env := os.Getenv(adaptation.PluginSocketEnvVar) - return name != "" || idx != "" || timeoutStr != "" || env != "" -} - -type identity struct { - name string - idx string -} - -func (i *identity) FullName() string { - return i.idx + "-" + i.name -} - func connectionFromFileDescriptor(fd int) (net.Conn, error) { f := os.NewFile(uintptr(fd), "fd #"+strconv.Itoa(fd)) if f == nil { diff --git a/pkg/stub/config_test.go b/pkg/stub/config_test.go index e9e34fc7..04c4bd32 100644 --- a/pkg/stub/config_test.go +++ b/pkg/stub/config_test.go @@ -5,16 +5,13 @@ import ( "net" "os" "path/filepath" - "strconv" "testing" - "time" nriNet "github.com/containerd/nri/pkg/net" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/docker/secrets-engine/pkg/adaptation" - "github.com/docker/secrets-engine/pkg/api" "github.com/docker/secrets-engine/pkg/secrets" ) @@ -28,32 +25,8 @@ func (m mockPlugin) GetSecret(context.Context, secrets.Request) (secrets.Envelop func (m mockPlugin) Shutdown(context.Context) { } -func Test_newCfg(t *testing.T) { - tests := []struct { - name string - test func(t *testing.T) - }{ - { - name: "no options allowed when launched from secrets engine", - test: func(t *testing.T) { - defer cleanupEnv() - _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") - _, err := newCfg(mockPlugin{}, WithPluginName("test-plugin")) - assert.ErrorContains(t, err, "cannot use manual launch options") - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.test(t) - }) - } -} - func cleanupEnv() { - _ = os.Setenv(adaptation.PluginNameEnvVar, "") - _ = os.Setenv(adaptation.PluginIdxEnvVar, "") - _ = os.Setenv(adaptation.PluginRegistrationTimeoutEnvVar, "") + _ = os.Setenv(adaptation.PluginLaunchedByEngineVar, "") } func Test_newCfgForManualLaunch(t *testing.T) { @@ -63,36 +36,6 @@ func Test_newCfgForManualLaunch(t *testing.T) { }{ { name: "no options just defaults", - test: func(t *testing.T) { - var args []string - copy(args, os.Args) - defer func() { - os.Args = args - }() - os.Args = []string{"10-test-plugin"} - os.Setenv("XDG_RUNTIME_DIR", os.TempDir()) - socketPath := adaptation.DefaultSocketPath() - os.Remove(socketPath) - require.NoError(t, os.MkdirAll(filepath.Dir(socketPath), 0755)) - listener, err := net.Listen("unix", socketPath) - if err != nil { - t.Fatalf("listen failed: %v", err) - } - defer listener.Close() - defer os.Remove(socketPath) - m := mockPlugin{} - - cfg, err := newCfgForManualLaunch(m) - assert.NoError(t, err) - assert.Equal(t, "10", cfg.identity.idx) - assert.Equal(t, "test-plugin", cfg.identity.name) - assert.Equal(t, adaptation.DefaultPluginRegistrationTimeout, cfg.registrationTimeout) - assert.NotNil(t, cfg.conn) - assert.Equal(t, m, cfg.plugin) - }, - }, - { - name: "no options just defaults but filename does not contain index", test: func(t *testing.T) { var args []string copy(args, os.Args) @@ -111,27 +54,13 @@ func Test_newCfgForManualLaunch(t *testing.T) { defer listener.Close() defer os.Remove(socketPath) - _, err = newCfgForManualLaunch(mockPlugin{}) - assert.ErrorIs(t, err, &api.ErrInvalidPluginIndex{Actual: "test", Msg: "must be two digits"}) - }, - }, - { - name: "without name", - test: func(t *testing.T) { - var args []string - copy(args, os.Args) - defer func() { - os.Args = args - }() - os.Args = []string{"test-plugin"} - client, server := net.Pipe() - defer client.Close() - defer server.Close() - cfg, err := newCfgForManualLaunch(mockPlugin{}, WithPluginIdx("10"), WithConnection(client)) + m := mockPlugin{} + c, err := newCfgForManualLaunch(m) assert.NoError(t, err) - assert.Equal(t, "10", cfg.identity.idx) - assert.Equal(t, "test-plugin", cfg.identity.name) - assert.Equal(t, client, cfg.conn) + assert.Equal(t, "test-plugin", c.name) + assert.Equal(t, adaptation.DefaultPluginRegistrationTimeout, c.registrationTimeout) + assert.Equal(t, m, c.plugin) + assert.NotNil(t, c.conn) }, }, { @@ -142,14 +71,13 @@ func Test_newCfgForManualLaunch(t *testing.T) { defer server.Close() cfg, err := newCfgForManualLaunch(mockPlugin{}, WithPluginName("test-plugin"), - WithPluginIdx("10"), WithRegistrationTimeout(10*adaptation.DefaultPluginRegistrationTimeout), WithConnection(client), ) assert.NoError(t, err) - assert.Equal(t, "10", cfg.identity.idx) - assert.Equal(t, "test-plugin", cfg.identity.name) + assert.Equal(t, "test-plugin", cfg.name) assert.Equal(t, 10*adaptation.DefaultPluginRegistrationTimeout, cfg.registrationTimeout) + assert.Equal(t, client, cfg.conn) }, }, } @@ -166,72 +94,24 @@ func Test_restoreConfig(t *testing.T) { test func(t *testing.T) }{ { - name: "name missing", - test: func(t *testing.T) { - defer cleanupEnv() - _, err := restoreConfig() - assert.ErrorIs(t, err, errPluginNameNotSet) - }, - }, - { - name: "index missing", + name: "no config from the engine", test: func(t *testing.T) { - defer cleanupEnv() - _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") - _, err := restoreConfig() - assert.ErrorIs(t, err, errPluginIdxNotSet) + _, err := restoreConfig(mockPlugin{}) + assert.ErrorIs(t, err, errPluginNotLaunchedByEngine) }, }, { - name: "invalid index", + name: "invalid config from the engine", test: func(t *testing.T) { defer cleanupEnv() - _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") - _ = os.Setenv(adaptation.PluginIdxEnvVar, "invalid-index") - _, err := restoreConfig() - assert.ErrorIs(t, err, &api.ErrInvalidPluginIndex{Actual: "invalid-index", Msg: "must be two digits"}) - }, - }, - { - name: "registration timeout missing", - test: func(t *testing.T) { - defer cleanupEnv() - _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") - _ = os.Setenv(adaptation.PluginIdxEnvVar, "10") - _, err := restoreConfig() - assert.ErrorIs(t, err, errPluginRegistrationTimeoutNotSet) - }, - }, - { - name: "invalid registration timeout", - test: func(t *testing.T) { - defer cleanupEnv() - _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") - _ = os.Setenv(adaptation.PluginIdxEnvVar, "10") - _ = os.Setenv(adaptation.PluginRegistrationTimeoutEnvVar, "10") - _, err := restoreConfig() - assert.ErrorContains(t, err, "invalid registration timeout") - }, - }, - { - name: "socket fd missing", - test: func(t *testing.T) { - defer cleanupEnv() - _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") - _ = os.Setenv(adaptation.PluginIdxEnvVar, "10") - _ = os.Setenv(adaptation.PluginRegistrationTimeoutEnvVar, "10s") - _, err := restoreConfig() - assert.ErrorIs(t, err, errPluginSocketNotSet) + _ = os.Setenv(adaptation.PluginLaunchedByEngineVar, "test-plugin") + _, err := restoreConfig(mockPlugin{}) + assert.Error(t, err) }, }, { name: "valid config", test: func(t *testing.T) { - defer cleanupEnv() - _ = os.Setenv(adaptation.PluginNameEnvVar, "test-plugin") - _ = os.Setenv(adaptation.PluginIdxEnvVar, "10") - _ = os.Setenv(adaptation.PluginRegistrationTimeoutEnvVar, "10s") - sockets, err := nriNet.NewSocketPair() require.NoError(t, err) defer sockets.Close() @@ -240,13 +120,20 @@ func Test_restoreConfig(t *testing.T) { defer conn.Close() peerFile := sockets.PeerFile() defer peerFile.Close() - _ = os.Setenv(adaptation.PluginSocketEnvVar, strconv.Itoa(int(peerFile.Fd()))) + defer cleanupEnv() + engineCfg := adaptation.PluginConfigFromEngine{ + Name: "test-plugin", + RegistrationTimeout: 10 * adaptation.DefaultPluginRegistrationTimeout, + Fd: int(peerFile.Fd()), + } + cfgString, err := engineCfg.ToString() + require.NoError(t, err) + _ = os.Setenv(adaptation.PluginLaunchedByEngineVar, cfgString) - cfg, err := restoreConfig() + cfg, err := restoreConfig(mockPlugin{}) assert.NoError(t, err) - assert.Equal(t, "test-plugin", cfg.identity.name) - assert.Equal(t, "10", cfg.identity.idx) - assert.Equal(t, 10*time.Second, cfg.timeout) + assert.Equal(t, "test-plugin", cfg.name) + assert.Equal(t, 10*adaptation.DefaultPluginRegistrationTimeout, cfg.registrationTimeout) defer cfg.conn.Close() msg := []byte("hello test") go func() { diff --git a/pkg/stub/stub.go b/pkg/stub/stub.go index 9a51d2b4..4fc1c9be 100644 --- a/pkg/stub/stub.go +++ b/pkg/stub/stub.go @@ -29,6 +29,8 @@ type stub struct { } // New creates a stub with the given plugin and options. +// ManualLaunchOption only apply when the plugin is launched manually. +// If launched by the secrets engine, they are ignored. func New(p Plugin, opts ...ManualLaunchOption) (Stub, error) { cfg, err := newCfg(p, opts...) if err != nil { @@ -37,7 +39,7 @@ func New(p Plugin, opts ...ManualLaunchOption) (Stub, error) { stub := &stub{ cfg: *cfg, } - logrus.Infof("Created plugin %s", stub.FullName()) + logrus.Infof("Created plugin %s", stub.name) return stub, nil } From 292648e4a0b1ef64bc7c791b2bd6f859b3d480dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Gro=C3=9Fmann?= Date: Fri, 20 Jun 2025 09:32:19 +0200 Subject: [PATCH 7/7] refactor(sdk): minor polishing and cleanup --- pkg/adaptation/adaptation.go | 4 ++-- pkg/adaptation/adaptation_test.go | 19 +++------------- pkg/stub/config.go | 11 ++++------ pkg/stub/config_test.go | 36 +++++++++++++++---------------- 4 files changed, 26 insertions(+), 44 deletions(-) diff --git a/pkg/adaptation/adaptation.go b/pkg/adaptation/adaptation.go index 8d793b67..b8c2e4d8 100644 --- a/pkg/adaptation/adaptation.go +++ b/pkg/adaptation/adaptation.go @@ -11,7 +11,7 @@ import ( const ( // PluginLaunchedByEngineVar is used to inform engine-launched plugins about their name. - PluginLaunchedByEngineVar = "DOCKER_SECRETS_ENGINE_LAUNCH_CFG" + PluginLaunchedByEngineVar = "DOCKER_SECRETS_ENGINE_PLUGIN_LAUNCH_CFG" // DefaultPluginRegistrationTimeout is the default timeout for plugin registration. DefaultPluginRegistrationTimeout = 5 * time.Second ) @@ -40,7 +40,7 @@ func (c *PluginConfigFromEngine) ToString() (string, error) { return string(result), nil } -func NewPluginConfigFromEngineFromString(in string) (*PluginConfigFromEngine, error) { +func NewPluginConfigFromEngineEnv(in string) (*PluginConfigFromEngine, error) { var result PluginConfigFromEngine if err := json.Unmarshal([]byte(in), &result); err != nil { return nil, fmt.Errorf("failed to decode plugin config from engine %q: %w", PluginLaunchedByEngineVar, err) diff --git a/pkg/adaptation/adaptation_test.go b/pkg/adaptation/adaptation_test.go index c7175122..716271f6 100644 --- a/pkg/adaptation/adaptation_test.go +++ b/pkg/adaptation/adaptation_test.go @@ -9,22 +9,9 @@ import ( "github.com/stretchr/testify/require" ) -func mockPluginNameWithLength(n int) string { - var sb strings.Builder - sb.Grow(n) - for i := 0; i < n; i++ { - if i%2 == 0 { - sb.WriteByte('a') - } else { - sb.WriteByte('b') - } - } - return sb.String() -} - func TestPluginConfigFromEngine_ToString(t *testing.T) { in := PluginConfigFromEngine{ - Name: mockPluginNameWithLength(500), + Name: strings.Repeat("ab", 250), // 500 characters RegistrationTimeout: 27 * time.Nanosecond, Fd: 10, } @@ -33,7 +20,7 @@ func TestPluginConfigFromEngine_ToString(t *testing.T) { // This is coming from here: https://superuser.com/questions/1070272/why-does-windows-have-a-limit-on-environment-variables-at-all // -> we verify that a plugin name of 500 characters is still within the limit assert.LessOrEqual(t, len(out), 2048) - restored, err := NewPluginConfigFromEngineFromString(out) + restored, err := NewPluginConfigFromEngineEnv(out) assert.NoError(t, err) assert.Equal(t, in, *restored) } @@ -77,7 +64,7 @@ func TestNewPluginConfigFromEngineFromString(t *testing.T) { t.Run(tt.name, func(t *testing.T) { out, err := tt.in.ToString() require.NoError(t, err) - _, err = NewPluginConfigFromEngineFromString(out) + _, err = NewPluginConfigFromEngineEnv(out) if tt.err != "" { assert.ErrorContains(t, err, tt.err) } else { diff --git a/pkg/stub/config.go b/pkg/stub/config.go index f86c415c..90023f5d 100644 --- a/pkg/stub/config.go +++ b/pkg/stub/config.go @@ -55,13 +55,10 @@ type cfg struct { func newCfg(p Plugin, opts ...ManualLaunchOption) (*cfg, error) { engineCfg, err := restoreConfig(p) - if err != nil && !errors.Is(err, errPluginNotLaunchedByEngine) { - return nil, err - } - if err == nil && engineCfg != nil { - return engineCfg, nil + if errors.Is(err, errPluginNotLaunchedByEngine) { + return newCfgForManualLaunch(p, opts...) } - return newCfgForManualLaunch(p, opts...) + return engineCfg, err } func newCfgForManualLaunch(p Plugin, opts ...ManualLaunchOption) (*cfg, error) { @@ -101,7 +98,7 @@ func restoreConfig(p Plugin) (*cfg, error) { if cfgString == "" { return nil, errPluginNotLaunchedByEngine } - c, err := adaptation.NewPluginConfigFromEngineFromString(cfgString) + c, err := adaptation.NewPluginConfigFromEngineEnv(cfgString) if err != nil { return nil, err } diff --git a/pkg/stub/config_test.go b/pkg/stub/config_test.go index 04c4bd32..8e205632 100644 --- a/pkg/stub/config_test.go +++ b/pkg/stub/config_test.go @@ -25,10 +25,6 @@ func (m mockPlugin) GetSecret(context.Context, secrets.Request) (secrets.Envelop func (m mockPlugin) Shutdown(context.Context) { } -func cleanupEnv() { - _ = os.Setenv(adaptation.PluginLaunchedByEngineVar, "") -} - func Test_newCfgForManualLaunch(t *testing.T) { tests := []struct { name string @@ -39,11 +35,11 @@ func Test_newCfgForManualLaunch(t *testing.T) { test: func(t *testing.T) { var args []string copy(args, os.Args) - defer func() { + t.Cleanup(func() { os.Args = args - }() + }) os.Args = []string{"test-plugin"} - os.Setenv("XDG_RUNTIME_DIR", os.TempDir()) + t.Setenv("XDG_RUNTIME_DIR", os.TempDir()) socketPath := adaptation.DefaultSocketPath() os.Remove(socketPath) require.NoError(t, os.MkdirAll(filepath.Dir(socketPath), 0755)) @@ -51,8 +47,10 @@ func Test_newCfgForManualLaunch(t *testing.T) { if err != nil { t.Fatalf("listen failed: %v", err) } - defer listener.Close() - defer os.Remove(socketPath) + t.Cleanup(func() { + listener.Close() + os.Remove(socketPath) + }) m := mockPlugin{} c, err := newCfgForManualLaunch(m) @@ -67,8 +65,10 @@ func Test_newCfgForManualLaunch(t *testing.T) { name: "with all custom options", test: func(t *testing.T) { client, server := net.Pipe() - defer client.Close() - defer server.Close() + t.Cleanup(func() { + client.Close() + server.Close() + }) cfg, err := newCfgForManualLaunch(mockPlugin{}, WithPluginName("test-plugin"), WithRegistrationTimeout(10*adaptation.DefaultPluginRegistrationTimeout), @@ -103,8 +103,7 @@ func Test_restoreConfig(t *testing.T) { { name: "invalid config from the engine", test: func(t *testing.T) { - defer cleanupEnv() - _ = os.Setenv(adaptation.PluginLaunchedByEngineVar, "test-plugin") + t.Setenv(adaptation.PluginLaunchedByEngineVar, "test-plugin") _, err := restoreConfig(mockPlugin{}) assert.Error(t, err) }, @@ -114,13 +113,12 @@ func Test_restoreConfig(t *testing.T) { test: func(t *testing.T) { sockets, err := nriNet.NewSocketPair() require.NoError(t, err) - defer sockets.Close() + t.Cleanup(func() { sockets.Close() }) conn, err := sockets.LocalConn() require.NoError(t, err) - defer conn.Close() + t.Cleanup(func() { conn.Close() }) peerFile := sockets.PeerFile() - defer peerFile.Close() - defer cleanupEnv() + t.Cleanup(func() { peerFile.Close() }) engineCfg := adaptation.PluginConfigFromEngine{ Name: "test-plugin", RegistrationTimeout: 10 * adaptation.DefaultPluginRegistrationTimeout, @@ -128,13 +126,13 @@ func Test_restoreConfig(t *testing.T) { } cfgString, err := engineCfg.ToString() require.NoError(t, err) - _ = os.Setenv(adaptation.PluginLaunchedByEngineVar, cfgString) + t.Setenv(adaptation.PluginLaunchedByEngineVar, cfgString) cfg, err := restoreConfig(mockPlugin{}) assert.NoError(t, err) assert.Equal(t, "test-plugin", cfg.name) assert.Equal(t, 10*adaptation.DefaultPluginRegistrationTimeout, cfg.registrationTimeout) - defer cfg.conn.Close() + t.Cleanup(func() { cfg.conn.Close() }) msg := []byte("hello test") go func() { _, err := conn.Write(msg)