-
-
Notifications
You must be signed in to change notification settings - Fork 586
docs: add AI coding agent guidelines #3446
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
mdelapenya
merged 6 commits into
testcontainers:main
from
mdelapenya:docs-ai-guidelines
Oct 10, 2025
+230
−0
Merged
Changes from 1 commit
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
ddae986
docs: add AI coding agent guidelines
mdelapenya 2c5e754
docs: add conventional commit validation details to AI guidelines
mdelapenya 538772d
docs: simplify AI guidelines by linking to detailed docs
mdelapenya d2cb4f4
docs: add language hint to commit message code fence
mdelapenya 906e2ca
docs: add gvm command for both arm64 and amd64
mdelapenya a2ad6b0
chore: exclude AI.md from triggering CI builds
mdelapenya File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,340 @@ | ||
| # AI Coding Agent Guidelines | ||
|
|
||
| This document provides guidelines for AI coding agents working on the Testcontainers for Go repository. | ||
|
|
||
| ## Repository Overview | ||
|
|
||
| This is a **Go monorepo** containing: | ||
| - **Core library**: Root directory contains the main testcontainers-go library | ||
| - **Modules**: `./modules/` directory with 50+ technology-specific modules (postgres, redis, kafka, etc.) | ||
| - **Examples**: `./examples/` directory with example implementations | ||
| - **Module generator**: `./modulegen/` directory with tools to generate new modules | ||
| - **Documentation**: `./docs/` directory with MkDocs-based documentation | ||
|
|
||
| ## Environment Setup | ||
|
|
||
| ### Go Version | ||
| - **Required**: Go 1.24.7 (arm64 for Apple Silicon, amd64 for others) | ||
| - **Tool**: Use [gvm](https://github.com/andrewkroh/gvm) for version management | ||
| - **CRITICAL**: Always run this before ANY Go command: | ||
| ```bash | ||
| eval "$(gvm 1.24.7 --arch=arm64)" | ||
| ``` | ||
|
|
||
| ### Project Structure | ||
| Each module in `./modules/` is a separate Go module with: | ||
| - `go.mod` / `go.sum` - Module dependencies | ||
| - `{module}.go` - Main module implementation | ||
| - `{module}_test.go` - Unit tests | ||
| - `examples_test.go` - Testable examples for documentation | ||
| - `Makefile` - Standard targets: `pre-commit`, `test-unit` | ||
|
|
||
| ## Development Workflow | ||
|
|
||
| ### Before Making Changes | ||
| 1. **Read existing code** in similar modules for patterns | ||
| 2. **Check documentation** in `docs/modules/index.md` for best practices | ||
| 3. **Run tests** to ensure baseline passes | ||
|
|
||
| ### Working with Modules | ||
| 1. **Change to module directory**: `cd modules/{module-name}` | ||
| 2. **Run pre-commit checks**: `make pre-commit` (linting, formatting, tidy) | ||
| 3. **Run tests**: `make test-unit` | ||
| 4. **Both together**: `make pre-commit test-unit` | ||
|
|
||
| ### Git Workflow | ||
| - **Branch naming**: Use descriptive names like `chore-module-use-run`, `feat-add-xyz`, `fix-module-issue` | ||
| - **Commit format**: Conventional commits | ||
| ``` | ||
| type(scope): description | ||
|
|
||
| Longer explanation if needed. | ||
|
|
||
| 🤖 Generated with [Claude Code](https://claude.com/claude-code) | ||
|
|
||
| Co-Authored-By: Claude <[email protected]> | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ``` | ||
| - **Commit types**: `feat`, `fix`, `chore`, `docs`, `test`, `refactor` | ||
| - **Always include co-author footer** when AI assists with changes | ||
|
|
||
| ### Pull Requests | ||
| - **Title format**: `type(scope): description` | ||
| - **Labels**: Use appropriate labels (`chore`, `breaking change`, etc.) | ||
| - **Body template**: | ||
| ```markdown | ||
| ## What does this PR do? | ||
|
|
||
| Brief description of changes. | ||
|
|
||
| ## Why is it important? | ||
|
|
||
| Context and rationale. | ||
|
|
||
| ## Related issues | ||
|
|
||
| - Relates to #issue-number | ||
| ``` | ||
|
|
||
| ## Module Development Best Practices | ||
|
|
||
| ### Container Struct Design | ||
| ```go | ||
| // ✅ Correct: Use "Container" not "ModuleContainer" or "PostgresContainer" | ||
| type Container struct { | ||
| testcontainers.Container | ||
| // Use private fields for state | ||
| dbName string | ||
| user string | ||
| password string | ||
| } | ||
| ``` | ||
|
|
||
| **Key principles:** | ||
| - **Name**: Use `Container`, not module-specific names (some legacy modules use old naming, they will be updated) | ||
| - **Fields**: Private fields for internal state, public only when needed for API | ||
| - **Embedding**: Always embed `testcontainers.Container` to promote methods | ||
|
|
||
| ### The Run Function Pattern | ||
|
|
||
| ```go | ||
| func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustomizer) (*Container, error) { | ||
| // 1. Process custom options first (if using intermediate settings struct) | ||
| var settings options | ||
| for _, opt := range opts { | ||
| if opt, ok := opt.(Option); ok { | ||
| if err := opt(&settings); err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // 2. Build moduleOpts with defaults | ||
| moduleOpts := []testcontainers.ContainerCustomizer{ | ||
| testcontainers.WithExposedPorts("5432/tcp"), | ||
| testcontainers.WithEnv(map[string]string{"DB": "default"}), | ||
| testcontainers.WithWaitStrategy(wait.ForListeningPort("5432/tcp")), | ||
| } | ||
|
|
||
| // 3. Add conditional options based on settings | ||
| if settings.tlsEnabled { | ||
| moduleOpts = append(moduleOpts, /* TLS config */) | ||
| } | ||
|
|
||
| // 4. Append user options (allows users to override defaults) | ||
| moduleOpts = append(moduleOpts, opts...) | ||
|
|
||
| // 5. Call testcontainers.Run | ||
| ctr, err := testcontainers.Run(ctx, img, moduleOpts...) | ||
| var c *Container | ||
| if ctr != nil { | ||
| c = &Container{Container: ctr, settings: settings} | ||
| } | ||
|
|
||
| // 6. Return with proper error wrapping | ||
| if err != nil { | ||
| return c, fmt.Errorf("run modulename: %w", err) | ||
| } | ||
|
|
||
| return c, nil | ||
| } | ||
| ``` | ||
|
|
||
| **Key patterns:** | ||
| - Process custom options **before** building moduleOpts | ||
| - Module defaults → user options → post-processing (order matters!) | ||
| - Always initialize container variable before error check | ||
| - Error format: `fmt.Errorf("run modulename: %w", err)` | ||
|
|
||
| ### Container Options | ||
|
|
||
| #### When to Use Built-in Options | ||
| Use `testcontainers.With*` for simple configuration: | ||
|
|
||
| ```go | ||
| // ✅ Good: Simple env var setting | ||
| func WithDatabase(dbName string) testcontainers.CustomizeRequestOption { | ||
| return testcontainers.WithEnv(map[string]string{"POSTGRES_DB": dbName}) | ||
| } | ||
| ``` | ||
|
|
||
| #### When to Use CustomizeRequestOption | ||
| Use for complex logic requiring multiple operations: | ||
|
|
||
| ```go | ||
| // ✅ Good: Multiple operations needed | ||
| func WithConfigFile(cfg string) testcontainers.CustomizeRequestOption { | ||
| return func(req *testcontainers.GenericContainerRequest) error { | ||
| if err := testcontainers.WithFiles(cfgFile)(req); err != nil { | ||
| return err | ||
| } | ||
| return testcontainers.WithCmdArgs("-c", "config_file=/etc/app.conf")(req) | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| #### When to Create Custom Option Types | ||
| Create custom `Option` type when you need to transfer state: | ||
|
|
||
| ```go | ||
| type options struct { | ||
| tlsEnabled bool | ||
| tlsConfig *tls.Config | ||
| } | ||
|
|
||
| type Option func(*options) error | ||
|
|
||
| func (o Option) Customize(req *testcontainers.GenericContainerRequest) error { | ||
| return nil // Can be empty if only setting internal state | ||
| } | ||
|
|
||
| func WithTLS() Option { | ||
| return func(opts *options) error { | ||
| opts.tlsEnabled = true | ||
| return nil | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| #### Critical Rules for Options | ||
|
|
||
| **✅ DO:** | ||
| - Return struct types (`testcontainers.CustomizeRequestOption` or custom `Option`) | ||
| - Call built-in options directly: `testcontainers.WithFiles(f)(req)` | ||
| - Use variadic arguments correctly: `WithCmd("arg1", "arg2")` | ||
| - Handle errors properly in option functions | ||
|
|
||
| **❌ DON'T:** | ||
| - Return interface types (`testcontainers.ContainerCustomizer`) | ||
| - Use `.Customize()` method: `WithFiles(f).Customize(req)` | ||
| - Pass slices to variadic functions: `WithCmd([]string{"arg1", "arg2"})` | ||
| - Ignore errors from built-in options | ||
|
|
||
| ### Container State Inspection | ||
|
|
||
| When reading environment variables after container creation: | ||
|
|
||
| ```go | ||
| inspect, err := ctr.Inspect(ctx) | ||
| if err != nil { | ||
| return c, fmt.Errorf("inspect modulename: %w", err) | ||
| } | ||
|
|
||
| // Use strings.CutPrefix with early exit optimization | ||
| var foundDB, foundUser, foundPass bool | ||
| for _, env := range inspect.Config.Env { | ||
| if v, ok := strings.CutPrefix(env, "POSTGRES_DB="); ok { | ||
| c.dbName, foundDB = v, true | ||
| } | ||
| if v, ok := strings.CutPrefix(env, "POSTGRES_USER="); ok { | ||
| c.user, foundUser = v, true | ||
| } | ||
| if v, ok := strings.CutPrefix(env, "POSTGRES_PASSWORD="); ok { | ||
| c.password, foundPass = v, true | ||
| } | ||
|
|
||
| // Early exit once all values found | ||
| if foundDB && foundUser && foundPass { | ||
| break | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| **Key techniques:** | ||
| - Use `strings.CutPrefix` (standard library) for prefix checking | ||
| - Set default values when creating container struct | ||
| - Use individual `found` flags for each variable | ||
| - Check all flags together and break early | ||
|
|
||
| ### Variadic Arguments | ||
|
|
||
| **Correct usage:** | ||
| ```go | ||
| // ✅ Pass arguments directly | ||
| testcontainers.WithCmd("redis-server", "--port", "6379") | ||
| testcontainers.WithFiles(file1, file2, file3) | ||
| testcontainers.WithWaitStrategy(wait1, wait2) | ||
|
|
||
| // For initial setup | ||
| testcontainers.WithCmd(...) // Sets command | ||
| testcontainers.WithEntrypoint(...) // Sets entrypoint | ||
|
|
||
| // For appending to user values | ||
| testcontainers.WithCmdArgs(...) // Appends to command | ||
| testcontainers.WithEntrypointArgs(...) // Appends to entrypoint | ||
| ``` | ||
|
|
||
| **Wrong usage:** | ||
| ```go | ||
| // ❌ Don't pass slices to variadic functions | ||
| testcontainers.WithCmd([]string{"redis-server", "--port", "6379"}) | ||
| testcontainers.WithFiles([]ContainerFile{file1, file2}) | ||
| ``` | ||
|
|
||
| ## Testing Guidelines | ||
|
|
||
| ### Running Tests | ||
| - **From module directory**: `cd modules/{module} && make test-unit` | ||
| - **Pre-commit checks**: `make pre-commit` (run this first to catch lint issues) | ||
| - **Full check**: `make pre-commit test-unit` | ||
|
|
||
| ### Test Patterns | ||
| - Use testable examples in `examples_test.go` | ||
| - Follow existing test patterns in similar modules | ||
| - Test both success and error cases | ||
| - Use `t.Parallel()` when tests are independent | ||
|
|
||
| ### When Tests Fail | ||
| 1. **Read the error message carefully** - it usually tells you exactly what's wrong | ||
| 2. **Check if it's a lint issue** - run `make pre-commit` first | ||
| 3. **Verify Go version** - ensure using Go 1.24.7 | ||
| 4. **Check Docker** - some tests require Docker daemon running | ||
|
|
||
| ## Common Pitfalls to Avoid | ||
|
|
||
| ### Code Issues | ||
| - ❌ Using interface types as return values | ||
| - ❌ Forgetting to run `eval "$(gvm 1.24.7 --arch=arm64)"` | ||
| - ❌ Not handling errors from built-in options | ||
| - ❌ Using module-specific container names (`PostgresContainer`) | ||
| - ❌ Calling `.Customize()` method instead of direct function call | ||
|
|
||
| ### Git Issues | ||
| - ❌ Forgetting co-author footer in commits | ||
| - ❌ Not running tests before committing | ||
| - ❌ Committing files outside module scope (use `git add modules/{module}/`) | ||
|
|
||
| ### Testing Issues | ||
| - ❌ Running tests without pre-commit checks first | ||
| - ❌ Not changing to module directory before running make | ||
| - ❌ Forgetting to set Go version before testing | ||
|
|
||
| ## Reference Documentation | ||
|
|
||
| For detailed information, see: | ||
| - **Module development**: `docs/modules/index.md` - Comprehensive best practices | ||
| - **Contributing**: `docs/contributing.md` - General contribution guidelines | ||
| - **Modules catalog**: [testcontainers.com/modules](https://testcontainers.com/modules/?language=go) | ||
| - **API docs**: [pkg.go.dev/github.com/testcontainers/testcontainers-go](https://pkg.go.dev/github.com/testcontainers/testcontainers-go) | ||
|
|
||
| ## Module Generator | ||
|
|
||
| To create a new module: | ||
|
|
||
| ```bash | ||
| cd modulegen | ||
| go run . new module --name mymodule --image "docker.io/myimage:tag" | ||
| ``` | ||
|
|
||
| This generates: | ||
| - Module scaffolding with proper structure | ||
| - Documentation template | ||
| - Test files with examples | ||
| - Makefile with standard targets | ||
|
|
||
| The generator uses templates in `modulegen/_template/` that follow current best practices. | ||
|
|
||
| ## Need Help? | ||
|
|
||
| - **Slack**: [testcontainers.slack.com](https://slack.testcontainers.org/) | ||
| - **GitHub Discussions**: [github.com/testcontainers/testcontainers-go/discussions](https://github.com/testcontainers/testcontainers-go/discussions) | ||
| - **Issues**: Check existing issues or create a new one with detailed context | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.