From 732c8e6467bb1e8c3cad5862097d4528fb2a95c3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Sep 2025 01:24:30 +0000 Subject: [PATCH 1/3] Initial plan From a365f66160c0431b01f8e8dffc72fa57dc833612 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Sep 2025 01:29:05 +0000 Subject: [PATCH 2/3] Initial analysis - establish documentation improvement plan Co-authored-by: ziflex <1607148+ziflex@users.noreply.github.com> --- helpers_test.go | 16 ++++++------- options_test.go | 22 ++++++++--------- registry_test.go | 30 +++++++++++------------ waitfor_test.go | 62 ++++++++++++++++++++++++------------------------ 4 files changed, 65 insertions(+), 65 deletions(-) diff --git a/helpers_test.go b/helpers_test.go index 44893a4..3cb219e 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -16,13 +16,13 @@ func TestUse(t *testing.T) { } return schemes, factory } - + // Test Use function config := Use(mockModule) - + assert.Equal(t, []string{"mock", "test"}, config.Scheme) assert.NotNil(t, config.Factory) - + // Test that the factory works testURL, _ := url.Parse("mock://example") resource, err := config.Factory(testURL) @@ -39,9 +39,9 @@ func TestUse_WithEmptySchemes(t *testing.T) { } return schemes, factory } - + config := Use(mockModule) - + assert.Empty(t, config.Scheme) assert.NotNil(t, config.Factory) } @@ -52,9 +52,9 @@ func TestUse_WithNilFactory(t *testing.T) { schemes := []string{"nil"} return schemes, nil } - + config := Use(mockModule) - + assert.Equal(t, []string{"nil"}, config.Scheme) assert.Nil(t, config.Factory) -} \ No newline at end of file +} diff --git a/options_test.go b/options_test.go index 529aefd..cd5810f 100644 --- a/options_test.go +++ b/options_test.go @@ -9,7 +9,7 @@ import ( func TestNewOptions_Defaults(t *testing.T) { opts := newOptions([]Option{}) - + assert.Equal(t, time.Duration(5)*time.Second, opts.interval) assert.Equal(t, time.Duration(60)*time.Second, opts.maxInterval) assert.Equal(t, uint64(5), opts.attempts) @@ -21,9 +21,9 @@ func TestNewOptions_WithSetters(t *testing.T) { WithMaxInterval(120), WithAttempts(15), } - + opts := newOptions(setters) - + assert.Equal(t, time.Duration(10)*time.Second, opts.interval) assert.Equal(t, time.Duration(120)*time.Second, opts.maxInterval) assert.Equal(t, uint64(15), opts.attempts) @@ -32,27 +32,27 @@ func TestNewOptions_WithSetters(t *testing.T) { func TestWithInterval(t *testing.T) { option := WithInterval(30) opts := &Options{} - + option(opts) - + assert.Equal(t, time.Duration(30)*time.Second, opts.interval) } func TestWithMaxInterval(t *testing.T) { option := WithMaxInterval(90) opts := &Options{} - + option(opts) - + assert.Equal(t, time.Duration(90)*time.Second, opts.maxInterval) } func TestWithAttempts(t *testing.T) { option := WithAttempts(20) opts := &Options{} - + option(opts) - + assert.Equal(t, uint64(20), opts.attempts) } @@ -62,8 +62,8 @@ func TestCombinedOptions(t *testing.T) { WithMaxInterval(30), WithAttempts(8), }) - + assert.Equal(t, time.Duration(2)*time.Second, opts.interval) assert.Equal(t, time.Duration(30)*time.Second, opts.maxInterval) assert.Equal(t, uint64(8), opts.attempts) -} \ No newline at end of file +} diff --git a/registry_test.go b/registry_test.go index fa0baf3..d6fd2a4 100644 --- a/registry_test.go +++ b/registry_test.go @@ -40,14 +40,14 @@ func TestRegistry_Register(t *testing.T) { func TestRegistry_Register_NewScheme(t *testing.T) { r := newRegistry([]ResourceConfig{}) - + factory := func(_ *url.URL) (Resource, error) { return &TestResource{}, nil } - + err := r.Register("custom", factory) assert.NoError(t, err) - + // Verify the scheme was registered rsc, err := r.Resolve("custom://test") assert.NoError(t, err) @@ -58,14 +58,14 @@ func TestRegistry_Register_DuplicateScheme(t *testing.T) { factory := func(_ *url.URL) (Resource, error) { return &TestResource{}, nil } - + r := newRegistry([]ResourceConfig{ { Scheme: []string{"existing"}, Factory: factory, }, }) - + // Try to register the same scheme again err := r.Register("existing", factory) assert.Error(t, err) @@ -74,15 +74,15 @@ func TestRegistry_Register_DuplicateScheme(t *testing.T) { func TestRegistry_Register_WithWhitespace(t *testing.T) { r := newRegistry([]ResourceConfig{}) - + factory := func(_ *url.URL) (Resource, error) { return &TestResource{}, nil } - + // Register with whitespace - should be trimmed err := r.Register(" spaced ", factory) assert.NoError(t, err) - + // Verify it was registered with trimmed name rsc, err := r.Resolve("spaced://test") assert.NoError(t, err) @@ -91,7 +91,7 @@ func TestRegistry_Register_WithWhitespace(t *testing.T) { func TestRegistry_Resolve_InvalidURL(t *testing.T) { r := newRegistry([]ResourceConfig{}) - + // Test with invalid URL rsc, err := r.Resolve("://invalid-url") assert.Error(t, err) @@ -100,7 +100,7 @@ func TestRegistry_Resolve_InvalidURL(t *testing.T) { func TestRegistry_Resolve_UnknownScheme(t *testing.T) { r := newRegistry([]ResourceConfig{}) - + // Test with unknown scheme rsc, err := r.Resolve("unknown://test") assert.Error(t, err) @@ -112,14 +112,14 @@ func TestRegistry_Resolve_FactoryError(t *testing.T) { factory := func(_ *url.URL) (Resource, error) { return nil, errors.New("factory error") } - + r := newRegistry([]ResourceConfig{ { Scheme: []string{"error"}, Factory: factory, }, }) - + rsc, err := r.Resolve("error://test") assert.Error(t, err) assert.Nil(t, rsc) @@ -130,14 +130,14 @@ func TestRegistry_List(t *testing.T) { factory := func(_ *url.URL) (Resource, error) { return &TestResource{}, nil } - + r := newRegistry([]ResourceConfig{ { Scheme: []string{"http", "https", "custom"}, Factory: factory, }, }) - + schemes := r.List() assert.Len(t, schemes, 3) assert.Contains(t, schemes, "http") @@ -147,7 +147,7 @@ func TestRegistry_List(t *testing.T) { func TestRegistry_List_Empty(t *testing.T) { r := newRegistry([]ResourceConfig{}) - + schemes := r.List() assert.Empty(t, schemes) } diff --git a/waitfor_test.go b/waitfor_test.go index d2501c9..6c0ab15 100644 --- a/waitfor_test.go +++ b/waitfor_test.go @@ -63,7 +63,7 @@ func TestRunner_Resources(t *testing.T) { Factory: MockResourceFactory, } runner := New(config) - + registry := runner.Resources() assert.NotNil(t, registry) assert.Equal(t, runner.registry, registry) @@ -75,10 +75,10 @@ func TestRunner_Test_Success(t *testing.T) { Factory: MockResourceFactory, } runner := New(config) - + ctx := context.Background() resources := []string{"test://success"} - + err := runner.Test(ctx, resources) assert.NoError(t, err) } @@ -89,11 +89,11 @@ func TestRunner_Test_SingleFailure(t *testing.T) { Factory: MockResourceFactory, } runner := New(config) - + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() resources := []string{"test://failure"} - + err := runner.Test(ctx, resources, WithAttempts(1)) assert.Error(t, err) assert.Contains(t, err.Error(), ErrWait.Error()) @@ -105,14 +105,14 @@ func TestRunner_Test_MultipleResources(t *testing.T) { Factory: MockResourceFactory, } runner := New(config) - + ctx := context.Background() - + // Test multiple successful resources resources := []string{"test://success", "test://success"} err := runner.Test(ctx, resources) assert.NoError(t, err) - + // Test mix of success and failure with timeout ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() @@ -128,12 +128,12 @@ func TestRunner_Test_WithOptions(t *testing.T) { Factory: MockResourceFactory, } runner := New(config) - + ctx := context.Background() resources := []string{"test://success"} - + // Test with custom options - err := runner.Test(ctx, resources, + err := runner.Test(ctx, resources, WithAttempts(3), WithInterval(1), WithMaxInterval(10)) @@ -143,7 +143,7 @@ func TestRunner_Test_WithOptions(t *testing.T) { func TestRunner_Test_EmptyResources(t *testing.T) { runner := New() ctx := context.Background() - + // Test with empty resources slice err := runner.Test(ctx, []string{}) assert.NoError(t, err) @@ -155,14 +155,14 @@ func TestRunner_Run_Success(t *testing.T) { Factory: MockResourceFactory, } runner := New(config) - + ctx := context.Background() program := Program{ Executable: "echo", Args: []string{"hello"}, Resources: []string{"test://success"}, } - + output, err := runner.Run(ctx, program) assert.NoError(t, err) assert.Contains(t, string(output), "hello") @@ -174,7 +174,7 @@ func TestRunner_Run_ResourceFailure(t *testing.T) { Factory: MockResourceFactory, } runner := New(config) - + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() program := Program{ @@ -182,7 +182,7 @@ func TestRunner_Run_ResourceFailure(t *testing.T) { Args: []string{"hello"}, Resources: []string{"test://failure"}, } - + output, err := runner.Run(ctx, program, WithAttempts(1)) assert.Error(t, err) assert.Nil(t, output) @@ -195,14 +195,14 @@ func TestRunner_Run_CommandFailure(t *testing.T) { Factory: MockResourceFactory, } runner := New(config) - + ctx := context.Background() program := Program{ Executable: "nonexistent-command", Args: []string{}, Resources: []string{"test://success"}, } - + _, err := runner.Run(ctx, program) assert.Error(t, err) // The important thing is that we get an error when the command fails @@ -210,14 +210,14 @@ func TestRunner_Run_CommandFailure(t *testing.T) { func TestRunner_testInternal_ResolutionError(t *testing.T) { runner := New() // No resources registered - + ctx := context.Background() opts := Options{ interval: time.Second, maxInterval: time.Minute, attempts: 1, } - + err := runner.testInternal(ctx, "unknown://test", opts) assert.Error(t, err) assert.Contains(t, err.Error(), "resource with a given scheme is not found") @@ -229,14 +229,14 @@ func TestRunner_testInternal_ResourceTestError(t *testing.T) { Factory: MockResourceFactory, } runner := New(config) - + ctx := context.Background() opts := Options{ interval: 1 * time.Millisecond, // Very short for testing maxInterval: 2 * time.Millisecond, attempts: 1, // Only one attempt to avoid long test time } - + err := runner.testInternal(ctx, "test://failure", opts) assert.Error(t, err) } @@ -247,25 +247,25 @@ func TestRunner_testAllInternal(t *testing.T) { Factory: MockResourceFactory, } runner := New(config) - + ctx := context.Background() opts := Options{ interval: time.Millisecond, maxInterval: time.Millisecond * 10, attempts: 1, } - + // Test with multiple resources resources := []string{"test://success", "test://success"} output := runner.testAllInternal(ctx, resources, opts) - + errorCount := 0 for err := range output { if err != nil { errorCount++ } } - + assert.Equal(t, 0, errorCount) } @@ -275,24 +275,24 @@ func TestRunner_testAllInternal_WithErrors(t *testing.T) { Factory: MockResourceFactory, } runner := New(config) - + ctx := context.Background() opts := Options{ interval: time.Millisecond, maxInterval: time.Millisecond * 10, attempts: 1, } - + // Test with mix of success and failure resources := []string{"test://success", "test://failure"} output := runner.testAllInternal(ctx, resources, opts) - + errorCount := 0 for err := range output { if err != nil { errorCount++ } } - + assert.Equal(t, 1, errorCount) -} \ No newline at end of file +} From e1e03e677b93c4766fbf714a099df051557eded9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Sep 2025 01:35:24 +0000 Subject: [PATCH 3/3] Add comprehensive documentation for all exported types and functions Co-authored-by: ziflex <1607148+ziflex@users.noreply.github.com> --- errors.go | 5 +++ helpers.go | 24 +++++++++++++ options.go | 42 +++++++++++++++++++---- registry.go | 64 ++++++++++++++++++++++++++++++++--- waitfor.go | 90 +++++++++++++++++++++++++++++++++++++++++++++---- waitfor_test.go | 6 ++-- 6 files changed, 210 insertions(+), 21 deletions(-) diff --git a/errors.go b/errors.go index 21e65dc..e946cec 100644 --- a/errors.go +++ b/errors.go @@ -2,7 +2,12 @@ package waitfor import "errors" +// ErrWait is returned when resource availability testing fails. +// This error indicates that one or more resources did not become +// available within the configured timeout and retry parameters. var ( ErrWait = errors.New("failed to wait for resource availability") + // ErrInvalidArgument is returned when invalid arguments are passed + // to functions, such as empty resource URLs or invalid configuration. ErrInvalidArgument = errors.New("invalid argument") ) diff --git a/helpers.go b/helpers.go index f7b31f3..a462acc 100644 --- a/helpers.go +++ b/helpers.go @@ -1,7 +1,31 @@ package waitfor +// Module is a function type that returns resource configuration information. +// It provides a way for resource plugins to declare their supported URL schemes +// and factory function. This enables a plugin-like architecture where resource +// types can be developed and distributed independently. +// +// The function should return: +// - A slice of URL schemes the module supports (e.g., []string{"http", "https"}) +// - A ResourceFactory function that can create resource instances from URLs +// +// Example: +// +// func httpModule() ([]string, ResourceFactory) { +// return []string{"http", "https"}, httpResourceFactory +// } type Module func() ([]string, ResourceFactory) +// Use converts a Module function into a ResourceConfig that can be used +// with New() to register resource types. This provides a convenient way +// to integrate resource plugins into a waitfor Runner. +// +// Example: +// +// runner := waitfor.New( +// waitfor.Use(httpModule), +// waitfor.Use(postgresModule), +// ) func Use(mod Module) ResourceConfig { scheme, factory := mod() diff --git a/options.go b/options.go index 1062927..61a6be4 100644 --- a/options.go +++ b/options.go @@ -5,16 +5,26 @@ import ( ) type ( + // Options contains configuration parameters for resource testing behavior. + // These options control retry intervals, maximum wait times, and the number + // of attempts made when testing resource availability. Options struct { - interval time.Duration - maxInterval time.Duration - attempts uint64 + interval time.Duration // Initial retry interval between attempts + maxInterval time.Duration // Maximum interval for exponential backoff + attempts uint64 // Maximum number of retry attempts } + // Option is a function type used to configure Options through the functional + // options pattern. This allows flexible and extensible configuration of + // resource testing behavior. Option func(opts *Options) ) -// Create new options +// newOptions creates a new Options instance with default values and applies +// the provided option setters. Default values are: +// - interval: 5 seconds +// - maxInterval: 60 seconds +// - attempts: 5. func newOptions(setters []Option) *Options { opts := &Options{ interval: time.Duration(5) * time.Second, @@ -29,21 +39,39 @@ func newOptions(setters []Option) *Options { return opts } -// Set a custom test interval +// WithInterval creates an Option that sets the initial retry interval in seconds. +// This interval is used as the starting point for exponential backoff between +// retry attempts. The actual interval will increase exponentially up to maxInterval. +// +// Example: +// +// runner.Test(ctx, resources, waitfor.WithInterval(2)) // Start with 2 second intervals func WithInterval(interval uint64) Option { return func(opts *Options) { opts.interval = time.Duration(interval) * time.Second } } -// Set a custom maximum test interval +// WithMaxInterval creates an Option that sets the maximum retry interval in seconds. +// When using exponential backoff, the retry interval will not exceed this value. +// This prevents excessively long waits between retry attempts. +// +// Example: +// +// runner.Test(ctx, resources, waitfor.WithMaxInterval(30)) // Cap at 30 seconds func WithMaxInterval(interval uint64) Option { return func(opts *Options) { opts.maxInterval = time.Duration(interval) * time.Second } } -// Set a custom attempts count +// WithAttempts creates an Option that sets the maximum number of retry attempts. +// If a resource test fails this many times, the resource is considered unavailable. +// Set to 0 for unlimited attempts (not recommended without context timeout). +// +// Example: +// +// runner.Test(ctx, resources, waitfor.WithAttempts(10)) // Try up to 10 times func WithAttempts(attempts uint64) Option { return func(opts *Options) { opts.attempts = attempts diff --git a/registry.go b/registry.go index 9d87c39..ab562b5 100644 --- a/registry.go +++ b/registry.go @@ -8,22 +8,55 @@ import ( ) type ( + // ResourceFactory is a function type that creates Resource instances from URLs. + // Each factory is responsible for parsing the URL and creating the appropriate + // resource implementation that can test the specific resource type. + // + // Example: + // + // func httpResourceFactory(u *url.URL) (Resource, error) { + // return &HTTPResource{url: u}, nil + // } ResourceFactory func(u *url.URL) (Resource, error) + // ResourceConfig defines the configuration for a resource type, mapping + // URL schemes to their corresponding factory functions. This allows + // the system to support multiple resource types through a plugin-like architecture. + // + // Example: + // + // config := ResourceConfig{ + // Scheme: []string{"http", "https"}, + // Factory: httpResourceFactory, + // } ResourceConfig struct { - Scheme []string - Factory ResourceFactory + Scheme []string // URL schemes this config handles (e.g., "http", "postgres") + Factory ResourceFactory // Factory function to create resource instances } + // Resource defines the interface that all resource types must implement. + // The Test method should verify that the resource is available and ready, + // returning an error if the resource is not accessible or not ready. + // + // Implementations should be context-aware and respect cancellation signals. Resource interface { + // Test verifies that the resource is available and ready for use. + // Should return nil if the resource is ready, or an error describing + // why the resource is not available. Test(ctx context.Context) error } + // Registry manages the mapping between URL schemes and their corresponding + // resource factories. It provides methods to register new resource types + // and resolve URLs to resource instances. Registry struct { resources map[string]ResourceFactory } ) +// newRegistry creates a new Registry instance and populates it with the provided +// resource configurations. Each configuration maps one or more URL schemes to +// their corresponding factory functions. func newRegistry(configs []ResourceConfig) *Registry { resources := make(map[string]ResourceFactory) @@ -36,7 +69,16 @@ func newRegistry(configs []ResourceConfig) *Registry { return &Registry{resources} } -// Register adds a resource factory to the registry +// Register adds a resource factory to the registry for the specified URL scheme. +// The scheme is automatically trimmed of whitespace. Returns an error if a +// factory is already registered for the given scheme. +// +// Example: +// +// err := registry.Register("custom", myResourceFactory) +// if err != nil { +// // Handle registration conflict +// } func (r *Registry) Register(scheme string, factory ResourceFactory) error { scheme = strings.TrimSpace(scheme) _, exists := r.resources[scheme] @@ -50,7 +92,17 @@ func (r *Registry) Register(scheme string, factory ResourceFactory) error { return nil } -// Resolve returns a resource instance by a given url +// Resolve parses the location URL and creates a Resource instance using the +// appropriate factory for the URL's scheme. Returns an error if the URL +// cannot be parsed or if no factory is registered for the scheme. +// +// Example: +// +// resource, err := registry.Resolve("postgres://user:pass@localhost:5432/db") +// if err != nil { +// // Handle resolution error +// } +// err = resource.Test(ctx) func (r *Registry) Resolve(location string) (Resource, error) { u, err := url.Parse(location) @@ -67,7 +119,9 @@ func (r *Registry) Resolve(location string) (Resource, error) { return rf(u) } -// List returns a list of schemes of registered resources +// List returns a slice containing all registered URL schemes. +// The order of schemes in the returned slice is not guaranteed. +// This can be useful for debugging or displaying available resource types. func (r *Registry) List() []string { list := make([]string, 0, len(r.resources)) diff --git a/waitfor.go b/waitfor.go index 3af125f..6db0a14 100644 --- a/waitfor.go +++ b/waitfor.go @@ -1,3 +1,25 @@ +// Package waitfor provides utilities for testing and waiting for resource availability +// before executing programs. It supports various resource types through a plugin system +// and offers configurable retry mechanisms with exponential backoff. +// +// This package is designed for scenarios where applications need to wait for dependencies +// like databases, web services, files, or other external resources to be available +// before starting up. +// +// Basic usage: +// +// runner := waitfor.New( +// postgres.Use(), +// http.Use(), +// ) +// +// err := runner.Test(ctx, []string{ +// "postgres://user:pass@localhost:5432/db", +// "http://localhost:8080/health", +// }) +// +// The package supports custom resource types through the ResourceConfig interface +// and provides flexible configuration options for retry behavior and timeouts. package waitfor import ( @@ -11,17 +33,34 @@ import ( ) type ( + // Program represents a command to execute along with the resources + // that must be available before execution. It encapsulates the executable + // path, command arguments, and dependency resource URLs. Program struct { - Executable string - Args []string - Resources []string + Executable string // The path or name of the executable to run + Args []string // Command line arguments for the executable + Resources []string // List of resource URLs that must be available } + // Runner is the main component responsible for testing resource availability + // and executing programs. It maintains a registry of resource types and + // provides methods to test resources and run programs conditionally. Runner struct { registry *Registry } ) +// New creates a new Runner instance with the specified resource configurations. +// Each ResourceConfig defines how to handle specific resource URL schemes. +// Multiple configurations can be provided to support different resource types. +// +// Example: +// +// runner := waitfor.New( +// postgres.Use(), +// http.Use(), +// fs.Use(), +// ) func New(configurators ...ResourceConfig) *Runner { r := new(Runner) r.registry = newRegistry(configurators) @@ -29,12 +68,28 @@ func New(configurators ...ResourceConfig) *Runner { return r } -// Resources returns resource registry +// Resources returns the resource registry associated with this Runner. +// The registry can be used to manually register additional resource types +// or query available resource schemes. func (r *Runner) Resources() *Registry { return r.registry } -// Run runs resource availability tests and execute a given command +// Run tests resource availability and executes the given program if all resources are ready. +// It first validates that all resources specified in program.Resources are available, +// then executes the program's command if the tests pass. Returns the combined output +// from the executed command or an error if resources are not ready or execution fails. +// +// The setters parameter allows customization of retry behavior, timeouts, and intervals. +// +// Example: +// +// program := waitfor.Program{ +// Executable: "myapp", +// Args: []string{"--config", "prod.yaml"}, +// Resources: []string{"postgres://localhost:5432/db", "http://api:8080/health"}, +// } +// output, err := runner.Run(ctx, program, waitfor.WithAttempts(10)) func (r *Runner) Run(ctx context.Context, program Program, setters ...Option) ([]byte, error) { err := r.Test(ctx, program.Resources, setters...) @@ -47,7 +102,24 @@ func (r *Runner) Run(ctx context.Context, program Program, setters ...Option) ([ return cmd.CombinedOutput() } -// Test tests resource availability +// Test validates that all specified resources are available and responding correctly. +// It tests each resource concurrently using their respective Test implementations +// with configurable retry logic and exponential backoff. Returns an error if any +// resource fails its availability test after all retry attempts are exhausted. +// +// The setters parameter allows customization of retry behavior including: +// - Initial retry interval (WithInterval) +// - Maximum retry interval (WithMaxInterval) +// - Number of retry attempts (WithAttempts) +// +// Example: +// +// resources := []string{ +// "postgres://user:pass@localhost:5432/db", +// "http://localhost:8080/health", +// "file://./config.json", +// } +// err := runner.Test(ctx, resources, waitfor.WithAttempts(5), waitfor.WithInterval(2)) func (r *Runner) Test(ctx context.Context, resources []string, setters ...Option) error { opts := newOptions(setters) @@ -67,6 +139,9 @@ func (r *Runner) Test(ctx context.Context, resources []string, setters ...Option return nil } +// testAllInternal concurrently tests all provided resources and returns a channel +// of errors. Each resource is tested in its own goroutine with the specified options. +// The channel is closed when all tests complete. func (r *Runner) testAllInternal(ctx context.Context, resources []string, opts Options) <-chan error { var wg sync.WaitGroup wg.Add(len(resources)) @@ -91,6 +166,9 @@ func (r *Runner) testAllInternal(ctx context.Context, resources []string, opts O return output } +// testInternal tests a single resource with retry logic using exponential backoff. +// It resolves the resource from the registry and applies the configured retry +// strategy until the resource test passes or max attempts are reached. func (r *Runner) testInternal(ctx context.Context, resource string, opts Options) error { rsc, err := r.registry.Resolve(resource) diff --git a/waitfor_test.go b/waitfor_test.go index 6c0ab15..124b2e7 100644 --- a/waitfor_test.go +++ b/waitfor_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" ) -// TestResourceSuccess is a mock resource that always succeeds +// TestResourceSuccess is a mock resource that always succeeds. type TestResourceSuccess struct { calls int } @@ -20,7 +20,7 @@ func (t *TestResourceSuccess) Test(_ context.Context) error { return nil } -// TestResourceFailure is a mock resource that always fails +// TestResourceFailure is a mock resource that always fails. type TestResourceFailure struct { calls int } @@ -30,7 +30,7 @@ func (t *TestResourceFailure) Test(_ context.Context) error { return errors.New("resource not available") } -// MockResourceFactory creates test resources +// MockResourceFactory creates test resources. func MockResourceFactory(u *url.URL) (Resource, error) { if u.Host == "success" { return &TestResourceSuccess{}, nil