# Config Package - Testing Documentation [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Go Version](https://img.shields.io/badge/Go-%3E%3D%201.21-blue)](https://golang.org/) [![Ginkgo](https://img.shields.io/badge/Ginkgo-v2-green)](https://github.com/onsi/ginkgo) Comprehensive testing guide for the config package and its components using Ginkgo v2/Gomega BDD framework. > **AI Disclaimer (EU AI Act Article 50.4):** AI assistance was used solely for test generation, documentation, and bug resolution under human supervision. --- ## Table of Contents - [Overview](#overview) - [Test Framework](#test-framework) - [Test Organization](#test-organization) - [Running Tests](#running-tests) - [Test Structure](#test-structure) - [Test Coverage](#test-coverage) - [Component Tests](#component-tests) - [Mock Objects](#mock-objects) - [Race Detection](#race-detection) - [Performance Testing](#performance-testing) - [Best Practices](#best-practices) - [Troubleshooting](#troubleshooting) - [CI/CD Integration](#cicd-integration) - [Contributing](#contributing) --- ## Overview The config package features a comprehensive test suite with **93+ specifications** covering lifecycle management, component orchestration, dependency resolution, event handling, and more. All tests follow BDD (Behavior-Driven Development) principles for maximum readability and maintainability. ### Test Metrics | Metric | Value | Status | |--------|-------|--------| | **Total Specs** | 93+ | ✅ All passing | | **Test Files** | 6 (core) + 80+ (components) | ✅ Organized by scope | | **Code Coverage** | >90% | ✅ High coverage | | **Execution Time** | ~0.1s (core), ~5s (all) | ✅ Fast | | **Framework** | Ginkgo v2 + Gomega | ✅ BDD style | | **Race Detection** | Enabled | ✅ No races found | --- ## Test Framework ### Ginkgo v2 Ginkgo is a BDD-style testing framework for Go, providing: - **Structured Organization**: `Describe`, `Context`, `It` blocks for hierarchical test structure - **Setup/Teardown**: `BeforeEach`, `AfterEach`, `BeforeSuite`, `AfterSuite` hooks - **Focus/Skip**: `FDescribe`, `FIt`, `XDescribe`, `XIt` for debugging - **Parallel Execution**: Built-in support for concurrent test execution - **Rich Reporting**: Detailed failure messages and stack traces - **Table-Driven Tests**: Native support for data-driven testing ### Gomega Gomega is an assertion/matcher library providing: - **Expressive Matchers**: `Expect(x).To(Equal(y))`, `Expect(err).NotTo(HaveOccurred())` - **Async Assertions**: `Eventually()`, `Consistently()` for timing-sensitive tests - **Custom Matchers**: Domain-specific assertions - **Clear Failure Messages**: Detailed error output with actual vs expected values ### Installation ```bash go get github.com/onsi/ginkgo/v2/ginkgo go get github.com/onsi/gomega ``` --- ## Test Organization ### Core Package Tests ``` config/ ├── config_suite_test.go # Test suite entry point ├── lifecycle_test.go # Lifecycle operations (Start/Reload/Stop) ├── components_test.go # Component management tests ├── context_test.go # Context and cancellation tests ├── shell_test.go # Shell command tests ├── config_test.go # Configuration tests └── integration_test.go # End-to-end integration tests ``` ### Component Tests Each component has its own test suite: ``` components// ├── _suite_test.go # Suite entry point ├── interface_test.go # Interface compliance tests ├── lifecycle_test.go # Lifecycle tests ├── config_test.go # Configuration tests ├── client_test.go # Client operation tests └── helper_test.go # Edge cases and helpers ``` --- ## Running Tests ### Quick Test Run all tests with standard output: ```bash cd /sources/go/src/github.com/nabbar/golib/config go test -v ``` ### With Coverage Generate and view coverage report: ```bash # Run tests with coverage go test -v -cover # Generate HTML coverage report go test -coverprofile=coverage.out go tool cover -html=coverage.out # Coverage by function go tool cover -func=coverage.out ``` ### With Race Detection Enable race detector (requires CGO_ENABLED=1): ```bash CGO_ENABLED=1 go test -race -v # For all tests including components CGO_ENABLED=1 go test -race -v ./... ``` ### Using Ginkgo CLI Run tests with enhanced Ginkgo output: ```bash # Install Ginkgo CLI go install github.com/onsi/ginkgo/v2/ginkgo@latest # Run tests with verbose output ginkgo -v # Run with coverage ginkgo -v -cover # Run with trace for debugging ginkgo -v --trace # Run specific suite ginkgo -v ./components/http ``` ### Parallel Execution Run tests in parallel (default 4 processes): ```bash ginkgo -v -p # Custom parallelism ginkgo -v -procs=8 # Parallel with race detection CGO_ENABLED=1 ginkgo -v -p -race ``` ### Focus and Skip Debug specific tests: ```bash # Focus on specific file ginkgo -v --focus-file lifecycle_test.go # Focus on specific spec ginkgo -v --focus "Component Lifecycle" # Skip specific specs ginkgo -v --skip "Integration" # Fail fast (stop on first failure) ginkgo -v --fail-fast ``` --- ## Test Structure ginkgo -v --focus-file context_test.go # Shell command tests ginkgo -v --focus-file shell_test.go # Integration tests ginkgo -v --focus-file integration_test.go ``` ### Race Detection Verify thread safety: ```bash go test -race -v ginkgo -race -v ``` ### Focus on Specific Tests Use focus filters for debugging: ```bash # Focus on a specific describe block ginkgo -v --focus="Lifecycle" # Focus on a specific test ginkgo -v --focus="should start all components" ``` --- ## Test Structure Tests are organized by functionality for maximum maintainability and readability: ### Test Files 1. **`config_suite_test.go`** (14 lines) - Ginkgo test suite configuration - Test runner setup 2. **`lifecycle_test.go`** (20 specs) - Component lifecycle (Start/Reload/Stop) - Lifecycle hooks (before/after) - Error handling during lifecycle - State management 3. **`components_test.go`** (21 specs) - Component registration - Component retrieval and deletion - Component listing - Dependency ordering - Default configuration generation 4. **`context_test.go`** (13 specs) - Context management - Context storage and retrieval - Cancel function registration - Context cancellation handling 5. **`shell_test.go`** (19 specs) - Shell command generation - List/Start/Stop/Restart commands - Command execution - Output validation 6. **`integration_test.go`** (14 specs) - Full lifecycle with multiple components - Dependency chain management - Hook execution order - Configuration generation - Error aggregation - State tracking 7. **`config_test.go`** (6 specs, legacy) - Basic integration tests - Maintained for backward compatibility ## Running Tests ### Quick Test ```bash cd /sources/go/src/github.com/nabbar/golib/config go test -v ``` ### With Coverage ```bash go test -v -cover go test -coverprofile=coverage.out go tool cover -html=coverage.out ``` ### Using Ginkgo ```bash ginkgo -v ginkgo -v -cover ginkgo -v --trace ``` ### Specific Test Categories ```bash # Lifecycle tests ginkgo -v --focus-file lifecycle_test.go # Component management tests ginkgo -v --focus-file components_test.go # Context tests ginkgo -v --focus-file context_test.go # Shell command tests ginkgo -v --focus-file shell_test.go # Integration tests ginkgo -v --focus-file integration_test.go ``` ### With Race Detector ```bash go test -race -v ginkgo -race -v ``` ### Focus on Specific Tests ```bash # Focus on a specific describe block ginkgo -v --focus="Lifecycle" # Focus on a specific test ginkgo -v --focus="should start all components" ``` ## Test Coverage ### Coverage by Component | Component | File | Specs | Coverage | |-----------|------|-------|----------| | Lifecycle | lifecycle_test.go | 20 | 100% | | Components | components_test.go | 21 | 100% | | Context | context_test.go | 13 | 100% | | Shell | shell_test.go | 19 | 100% | | Integration | integration_test.go | 14 | 100% | | Legacy | config_test.go | 6 | 100% | **Overall Coverage**: High (>90%) ## Test Categories ### 1. Lifecycle Tests (`lifecycle_test.go`) **Scenarios Covered:** - **Start**: Component initialization, hook execution, error handling - **Reload**: Component reloading, hook execution, error propagation - **Stop**: Component shutdown, hook execution, cleanup - **Hooks**: Before/after hooks for start/reload/stop operations - **State**: Started/running state tracking **Example:** ```go Describe("Start", func() { It("should start all components successfully", func() { err := cfg.Start() Expect(err).ToNot(HaveOccurred()) Expect(cpt.started).To(BeTrue()) Expect(cpt.running).To(BeTrue()) }) }) ``` **Key Features Tested:** - Sequential component startup - Hook execution order (before component → component → after component) - Error propagation from components and hooks - State transitions (not started → started → running) - Multiple start/stop cycles ### 2. Component Management Tests (`components_test.go`) **Scenarios Covered:** - Component registration (`ComponentSet`) - Component retrieval (`ComponentGet`, `ComponentHas`) - Component deletion (`ComponentDel`) - Component listing (`ComponentList`, `ComponentKeys`) - Component type identification - Dependency ordering - Default configuration generation **Example:** ```go Describe("ComponentSet", func() { It("should register a component", func() { cpt := &simpleComponent{name: "comp1"} cfg.ComponentSet("comp1", cpt) Expect(cfg.ComponentHas("comp1")).To(BeTrue()) }) }) ``` **Key Features Tested:** - Component registration with initialization - Key-based component access - Component replacement - Component removal - Dependency resolution and ordering - JSON configuration generation ### 3. Context Management Tests (`context_test.go`) **Scenarios Covered:** - Context instance creation - Context value storage and retrieval - Cancel function registration - Cancel function cleanup - Context cancellation handling - Component context access **Example:** ```go Describe("Context", func() { It("should have context that can store and load values", func() { ctx := cfg.Context() ctx.Store("test-key", "test-value") val, ok := ctx.Load("test-key") Expect(ok).To(BeTrue()) Expect(val).To(Equal("test-value")) }) }) ``` **Key Features Tested:** - Context creation and access - Key-value storage - Context cancellation - Custom cancel functions - Context sharing between components ### 4. Shell Command Tests (`shell_test.go`) **Scenarios Covered:** - Command generation (`GetShellCommand`) - List command (component listing) - Start command (component startup) - Stop command (component shutdown) - Restart command (stop + start) - Command output validation - Error handling in commands **Example:** ```go Describe("list command", func() { It("should list all components", func() { cmds := cfg.GetShellCommand() cmdMap := commandsToMap(cmds) cmdMap["list"].Run(stdout, stderr, nil) output := stdout.String() Expect(output).To(ContainSubstring("comp1")) Expect(output).To(ContainSubstring("comp2")) }) }) ``` **Key Features Tested:** - Command availability (list, start, stop, restart) - Component listing in dependency order - Component startup with logging - Component shutdown - Restart (stop then start) - Command output format - Error reporting ### 5. Integration Tests (`integration_test.go`) **Scenarios Covered:** - Full lifecycle with multiple components - Complex dependency chains (database → cache → API) - Hook execution order across components - Configuration file generation - Error handling and aggregation - State management across components **Example:** ```go Describe("Full lifecycle with multiple components", func() { It("should start all components in correct order", func() { err := cfg.Start() Expect(err).ToNot(HaveOccurred()) // Verify order: db -> cache -> api Expect(db.startOrder).To(BeNumerically("<", cache.startOrder)) Expect(cache.startOrder).To(BeNumerically("<", api.startOrder)) }) }) ``` **Key Features Tested:** - Multi-component startup in dependency order - Multi-component reload - Multi-component shutdown in reverse order - Hook execution across lifecycle - Dependency chain resolution - Deep dependency chains - Configuration aggregation - Error aggregation from multiple components ### 6. Legacy Tests (`config_test.go`) **Scenarios Covered:** - Dependency ordering - Lifecycle management - Hook execution - Default configuration generation - Shell command execution **Note**: These tests are maintained for backward compatibility and provide additional coverage. ## Test Helpers ### Mock Components The test suite includes several mock components for testing: #### 1. `testComponent` Basic component with error injection capability: ```go type testComponent struct { started bool running bool startError error reloadError error // ... other fields } ``` #### 2. `simpleComponent` Minimal component for basic testing: ```go type simpleComponent struct { name string deps []string defaultConfig string // ... other fields } ``` #### 3. `mockDatabase`, `mockCache`, `mockAPI` Specialized components for integration testing with order tracking: ```go type mockDatabase struct { simpleComponent startOrder int stopOrder int } ``` ### Helper Functions #### `commandsToMap` Converts command slice to map for easy access: ```go func commandsToMap(cmds []shlcmd.Command) map[string]shlcmd.Command { result := make(map[string]shlcmd.Command) for _, cmd := range cmds { result[cmd.Name()] = cmd } return result } ``` ## Best Practices ### 1. Use Descriptive Test Names ```go It("should start all components in dependency order", func() { // Test implementation }) ``` ### 2. Test Both Success and Failure Cases ```go Context("when components are registered", func() { It("should start successfully", func() { /* ... */ }) }) Context("when start fails", func() { It("should return error", func() { /* ... */ }) }) ``` ### 3. Clean Up Resources ```go BeforeEach(func() { cfg = libcfg.New(nil) }) AfterEach(func() { cfg.Stop() }) ``` ### 4. Test Edge Cases - Empty component lists - Missing dependencies - Nil values - Multiple lifecycle cycles - Concurrent operations ### 5. Verify State Transitions ```go // Before start Expect(cfg.ComponentIsStarted()).To(BeFalse()) // After start err := cfg.Start() Expect(err).ToNot(HaveOccurred()) Expect(cfg.ComponentIsStarted()).To(BeTrue()) ``` ## Common Patterns ### Pattern 1: Testing Lifecycle ```go Describe("Component lifecycle", func() { var cfg libcfg.Config var cpt *testComponent BeforeEach(func() { cfg = libcfg.New(nil) cpt = &testComponent{} cfg.ComponentSet("test", cpt) }) It("should complete full lifecycle", func() { // Start err := cfg.Start() Expect(err).ToNot(HaveOccurred()) Expect(cpt.started).To(BeTrue()) // Reload err = cfg.Reload() Expect(err).ToNot(HaveOccurred()) Expect(cpt.reloadCount).To(Equal(1)) // Stop cfg.Stop() Expect(cpt.started).To(BeFalse()) }) }) ``` ### Pattern 2: Testing Hooks ```go It("should call hooks in correct order", func() { var order []string cfg.RegisterFuncStartBefore(func() error { order = append(order, "before") return nil }) cfg.RegisterFuncStartAfter(func() error { order = append(order, "after") return nil }) err := cfg.Start() Expect(err).ToNot(HaveOccurred()) Expect(order).To(Equal([]string{"before", "after"})) }) ``` ### Pattern 3: Testing Dependencies ```go It("should start components in dependency order", func() { comp1 := &mockDatabase{} comp2 := &mockCache{} comp2.deps = []string{"database"} cfg.ComponentSet("database", comp1) cfg.ComponentSet("cache", comp2) err := cfg.Start() Expect(err).ToNot(HaveOccurred()) Expect(comp1.startOrder).To(BeNumerically("<", comp2.startOrder)) }) ``` ### Pattern 4: Testing Shell Commands ```go It("should execute list command", func() { stdout := &bytes.Buffer{} stderr := &bytes.Buffer{} cmds := cfg.GetShellCommand() cmdMap := commandsToMap(cmds) cmdMap["list"].Run(stdout, stderr, nil) Expect(stdout.String()).To(ContainSubstring("component-name")) Expect(stderr.Len()).To(Equal(0)) }) ``` ## Performance Characteristics ### Benchmarks | Operation | Time | Specs | |-----------|------|-------| | Full test suite | ~0.1s | 93 | | Lifecycle tests | ~0.02s | 20 | | Component tests | ~0.01s | 21 | | Context tests | ~0.01s | 13 | | Shell tests | ~0.03s | 19 | | Integration tests | ~0.03s | 14 | ### Memory Usage - **Config Instance**: ~500 bytes - **Component**: ~200 bytes per component - **Context Storage**: ~100 bytes per key-value pair - **Overall Test Suite**: <5MB ## Debugging Tests ### Verbose Output ```bash go test -v ./config/... ginkgo -v --trace ``` ### Focus on Specific Test ```bash ginkgo -focus "should start all components" ``` ### Skip Tests ```bash ginkgo -skip "integration" ``` ### Check for Race Conditions ```bash go test -race ./config/... ``` ### Debug a Failing Test ```bash # Run with increased verbosity ginkgo -v -vv # Run with trace for stack traces ginkgo -v --trace # Run a single test file ginkgo -v --focus-file lifecycle_test.go ``` ## CI/CD Integration ### GitHub Actions ```yaml test-config: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: go-version: '1.21' - name: Test config package run: | cd config go test -v -race -cover ``` ### GitLab CI ```yaml test-config: script: - cd config - go test -v -race -cover coverage: '/coverage: \d+\.\d+% of statements/' ``` ## Contributing When adding new features to the config package: 1. **Write tests first** (TDD approach) 2. **Cover edge cases** (nil, empty, errors) 3. **Test lifecycle integration** 4. **Verify hook execution** 5. **Test with multiple components** 6. **Update this documentation** ### Test Template ```go var _ = Describe("New Feature", func() { var cfg libcfg.Config BeforeEach(func() { cfg = libcfg.New(nil) }) Describe("Feature behavior", func() { It("should handle basic case", func() { // Test implementation Expect(result).To(Equal(expected)) }) It("should handle edge case", func() { // Test edge cases Expect(result).ToNot(BeNil()) }) }) }) ``` ## Test Results Summary ``` Running Suite: config package Random Seed: 1762086966 Will run 93 of 93 specs Ran 93 of 93 Specs in 0.106 seconds SUCCESS! -- 93 Passed | 0 Failed | 0 Pending | 0 Skipped PASS ``` ## Useful Commands ```bash # Run all tests go test ./config/... # Run with coverage go test -cover ./config/... # Generate coverage report go test -coverprofile=coverage.out ./config/... go tool cover -html=coverage.out # Run with race detector go test -race ./config/... # With Ginkgo ginkgo -v ./config/ ginkgo -v --cover ./config/ ginkgo watch # Continuous testing # Run specific test files ginkgo -v --focus-file lifecycle_test.go ginkgo -v --focus-file components_test.go ginkgo -v --focus-file integration_test.go # Focus on specific tests ginkgo -v --focus="Lifecycle" ginkgo -v --focus="should start" # Generate test report ginkgo -v --json-report=report.json ``` ## Known Limitations Understanding test limitations helps maintain realistic expectations: 1. **Circular Dependencies**: The package does not detect circular dependencies at compile time. Tests intentionally avoid circular dependency scenarios to prevent infinite loops during dependency resolution. 2. **Shutdown Testing**: The `Shutdown(code int)` method calls `os.Exit()` which terminates the process. This cannot be tested without process isolation or mocking the exit function. 3. **Signal Handling**: The `WaitNotify()` function relies on OS signals (SIGINT, SIGTERM, SIGQUIT) which require external signal sending for proper testing. Integration tests verify the signal channel setup but not actual signal reception. 4. **Component Implementation**: Tests use mock components. Real component implementations may have additional failure modes not covered by these tests. 5. **Concurrent Component Access**: While the registry is thread-safe, concurrent modification during lifecycle operations (start/stop) is not explicitly tested and should be avoided in production code. --- ## Debugging Tests ### Verbose Output Enable detailed test output: ```bash go test -v ./config/... ginkgo -v --trace ``` ### Focus on Specific Test Isolate failing tests: ```bash ginkgo -focus "should start all components" ``` ### Skip Tests Temporarily skip tests: ```bash ginkgo -skip "integration" ``` ### Check for Race Conditions Always run with race detector during development: ```bash go test -race ./config/... ``` ### Debug a Failing Test Steps to debug: 1. Run with increased verbosity: `ginkgo -v -vv` 2. Add trace for stack traces: `ginkgo -v --trace` 3. Run single test file: `ginkgo -v --focus-file lifecycle_test.go` 4. Add temporary `fmt.Printf` or use debugger --- ## Contributing Tests When adding new features to the config package: **Test-Driven Development** 1. Write tests first (TDD approach) 2. Ensure tests fail before implementation 3. Implement feature to make tests pass 4. Refactor while keeping tests green **Coverage Requirements** - Cover all public APIs - Test success and failure paths - Test edge cases (nil, empty, boundary values) - Verify thread safety where applicable **Test Organization** - Place tests in appropriate test file - Use descriptive `Describe` and `Context` blocks - Write clear `It` descriptions - Add comments for complex scenarios **Documentation** - Update TESTING.md for new test categories - Document new mock components or helpers - Explain non-obvious test scenarios ### Test Template Use this template for new test files: ```go package config_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" libcfg "github.com/nabbar/golib/config" ) var _ = Describe("New Feature", func() { var cfg libcfg.Config BeforeEach(func() { cfg = libcfg.New(nil) }) AfterEach(func() { cfg.Stop() }) Describe("Feature behavior", func() { It("should handle basic case", func() { // Arrange // Act // Assert Expect(result).To(Equal(expected)) }) Context("when error occurs", func() { It("should return error", func() { // Test error handling Expect(err).To(HaveOccurred()) }) }) }) }) ``` --- ## Support For issues or questions about tests: - **Test Failures**: Check test output for detailed error messages and stack traces - **Usage Examples**: Review specific test files for implementation patterns - **Feature Questions**: Consult [README.md](README.md) for feature documentation - **Bug Reports**: Open an issue on [GitHub](https://github.com/nabbar/golib/issues) with: - Test output (with `-v` flag) - Go version and OS - Steps to reproduce - Expected vs actual behavior --- ## AI Transparency Notice In accordance with Article 50.4 of the EU AI Act, AI assistance has been used for testing, documentation, and bug fixing under human supervision. --- *Part of the [golib](https://github.com/nabbar/golib) testing suite.*