Skip to content

Commit 83501a2

Browse files
authored
Merge pull request #6 from dragosv/mdfiles
Md files
2 parents 124a2e6 + a4bd422 commit 83501a2

9 files changed

Lines changed: 1213 additions & 15 deletions

AGENTS.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# AGENTS.md
2+
3+
Guidelines for AI agents working in the **testcontainers-perl5** repository.
4+
5+
## Project Overview
6+
7+
Perl 5 library for managing throwaway Docker containers in tests, inspired by [Testcontainers for Go](https://golang.testcontainers.org/). Uses [WWW::Docker](https://github.com/Getty/p5-www-docker) (vendored) as the Docker client. Functional API via `Testcontainers::run()` with Moo-based internals.
8+
9+
See [ARCHITECTURE.md](ARCHITECTURE.md) for design decisions and component diagrams.
10+
See [PROJECT_SUMMARY.md](PROJECT_SUMMARY.md) for a full feature inventory.
11+
12+
## Build & Test
13+
14+
```bash
15+
perl Build.PL && ./Build # Build
16+
prove -l t/ # Run unit tests (no Docker needed for t/01..03, t/06..12)
17+
TESTCONTAINERS_LIVE=1 prove -l t/ # Run all tests including integration (requires Docker)
18+
```
19+
20+
Linting:
21+
22+
```bash
23+
perlcritic --severity 4 lib/ # Check code quality
24+
```
25+
26+
## Code Style
27+
28+
- Perl 5.40+, `use strict; use warnings;` in every file.
29+
- 4-space indentation, ~120-character line limit.
30+
- `snake_case` for subroutines and variables, `PascalCase` for packages.
31+
- All public APIs must have POD documentation (`=head1`, `=func`, `=method`, `=attr`).
32+
- Use Moo for OO (`has`, `with`, `extends`). Use `Moo::Role` for shared behaviour.
33+
- Use `Carp::croak` for user-facing errors, never `die` with raw strings.
34+
- Use `Log::Any` (`$log->debugf(...)`) for debug/trace output.
35+
- Follow the rules in [CONTRIBUTING.md](CONTRIBUTING.md) for commit messages and PR conventions.
36+
37+
## Architecture Rules
38+
39+
- **Module boundary is strict**: `Testcontainers` depends on `WWW::Docker`, never the reverse. `WWW::Docker` must not reference test-container concepts.
40+
- **Roles over base classes**: `Testcontainers::Wait::Base` is a `Moo::Role` consumed by all wait strategies.
41+
- **Factory function pattern**: modules expose a factory function (e.g., `postgres_container()`) that returns a container wrapper object with convenience methods like `connection_string()` and `dsn()`.
42+
- **Labels specification**: `Testcontainers::Labels` manages `org.testcontainers.*` labels. User-supplied labels with the reserved prefix are rejected by `merge_custom_labels()`. Framework-internal labels (like `org.testcontainers.module`) use `_internal_labels` to bypass validation.
43+
44+
See [ARCHITECTURE.md](ARCHITECTURE.md) for diagrams and rationale.
45+
46+
## Key Paths
47+
48+
| Area | Path |
49+
|------|------|
50+
| Build manifest | `Build.PL`, `cpanfile` |
51+
| Main entry point | `lib/Testcontainers.pm` |
52+
| Container wrapper | `lib/Testcontainers/Container.pm` |
53+
| Request builder | `lib/Testcontainers/ContainerRequest.pm` |
54+
| Docker client wrapper | `lib/Testcontainers/DockerClient.pm` |
55+
| Labels specification | `lib/Testcontainers/Labels.pm` |
56+
| Wait strategy factory | `lib/Testcontainers/Wait.pm` |
57+
| Wait strategy impls | `lib/Testcontainers/Wait/*.pm` |
58+
| Pre-built modules | `lib/Testcontainers/Module/*.pm` |
59+
| Vendored Docker client | `lib/WWW/Docker.pm`, `lib/WWW/Docker/*.pm` |
60+
| Unit tests | `t/01-load.t` through `t/12-volumes.t` |
61+
| Integration tests | `t/04-integration.t`, `t/05-modules.t` |
62+
| CI workflows | `.github/workflows/` |
63+
64+
## Common Agent Tasks
65+
66+
### Adding a new container module
67+
68+
1. Create `lib/Testcontainers/Module/MyService.pm` following the existing `PostgreSQL.pm` / `Redis.pm` pattern.
69+
2. Export a factory function (e.g., `myservice_container(%opts)`).
70+
3. Pre-configure: image, default port, environment variables, and a wait strategy.
71+
4. Use `_internal_labels` (not `labels`) for `org.testcontainers.module`.
72+
5. Create an inner container class with convenience methods (e.g., `connection_string()`).
73+
6. Add integration tests in `t/05-modules.t` or a new test file.
74+
7. Update `README.md` with a usage example.
75+
76+
### Adding a new wait strategy
77+
78+
1. Create `lib/Testcontainers/Wait/MyStrategy.pm`.
79+
2. `use Moo; with 'Testcontainers::Wait::Base';` and implement `check($container)`.
80+
3. Add a factory function in `lib/Testcontainers/Wait.pm` (e.g., `for_my_strategy()`).
81+
4. Add a test in `t/03-wait-strategies.t`.
82+
83+
### Updating CI/CD
84+
85+
Workflow files live in `.github/workflows/`. The CI has 4 jobs: `lint` (perlcritic), `test-unit` (matrix: 5.40, 5.42), `test-integration` (requires Docker), and `ci-success` (gate).
86+
87+
## Testing Expectations
88+
89+
- Every new public API must have at least one test.
90+
- Tests use `Test::More` and `Test::Exception`.
91+
- Unit tests (t/01-03, t/06-12) run without Docker.
92+
- Integration tests (t/04, t/05) require Docker and `TESTCONTAINERS_LIVE=1`.
93+
- Always clean up containers with `$container->terminate` or `terminate_container()`.
94+
95+
## Additional Best Practices
96+
97+
- Prefer `//` (defined-or) over `||` for defaults.
98+
- Use `eval { ... }; if ($@) { ... }` for error handling, propagate errors with `croak`.
99+
- Keep `$log->debugf(...)` calls for traceability.
100+
- Use `Exporter` with `@EXPORT_OK` — never pollute the caller's namespace by default.
101+
- Keep Docker endpoint configuration via `DOCKER_HOST` env var; do not hardcode endpoints in tests.
102+
- Validate inputs at module boundaries with `croak`.

ARCHITECTURE.md

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# Testcontainers Perl 5 — Architecture
2+
3+
This document describes the internal design and architectural decisions of the library.
4+
For a feature inventory and implementation stats see [PROJECT_SUMMARY.md](PROJECT_SUMMARY.md).
5+
For usage examples and getting-started instructions see [QUICKSTART.md](QUICKSTART.md).
6+
7+
## Design Goals
8+
9+
| Goal | How |
10+
|------|-----|
11+
| Perl-first | Moo OO, roles, functional API, CPAN conventions |
12+
| Simplicity | Single `run()` entry point returns a ready-to-use container |
13+
| Extensibility | Moo roles for wait strategies, factory functions for modules |
14+
| Test-friendly | Works with `Test::More`, automatic cleanup via `DEMOLISH` |
15+
| Go-inspired | API mirrors [Testcontainers for Go](https://golang.testcontainers.org/) where practical |
16+
17+
## Module Dependency Graph
18+
19+
The library has two independent layers with a strict dependency direction:
20+
21+
```
22+
Testcontainers ──depends on──► WWW::Docker ──speaks to──► Docker Engine
23+
(test API) (HTTP client) (REST API)
24+
```
25+
26+
**WWW::Docker** is a self-contained Docker API client vendored from [Getty/p5-www-docker](https://github.com/Getty/p5-www-docker). It translates Perl method calls into Docker Engine REST requests over a Unix socket. It knows nothing about test containers, wait strategies, or modules.
27+
28+
**Testcontainers** owns all concepts meaningful to test authors: container lifecycle, wait strategies, pre-built modules, labels, and network management. It delegates all Docker I/O to `WWW::Docker` via the `Testcontainers::DockerClient` wrapper.
29+
30+
## Component Layout
31+
32+
```
33+
┌──────────────────────────────────────────────────────────────────┐
34+
│ Testcontainers module │
35+
│ │
36+
│ ┌──────────────┐ ┌───────────────────┐ ┌──────────────────┐ │
37+
│ │ Testcontainers│ │ ContainerRequest │ │ Container │ │
38+
│ │ ::run() │──│ (config builder) │──│ (running wrapper)│ │
39+
│ └──────────────┘ └───────────────────┘ └──────────────────┘ │
40+
│ │
41+
│ ┌────────────────────────────────────────────────────────────┐ │
42+
│ │ Wait Strategies (Moo::Role based) │ │
43+
│ │ HostPort │ HTTP │ Log │ HealthCheck │ Multi │ │
44+
│ └────────────────────────────────────────────────────────────┘ │
45+
│ │
46+
│ ┌────────────────────────────────────────────────────────────┐ │
47+
│ │ Modules (factory functions) │ │
48+
│ │ PostgreSQL │ MySQL │ Redis │ Nginx │ │
49+
│ └────────────────────────────────────────────────────────────┘ │
50+
│ │
51+
│ ┌──────────────┐ ┌───────────────────┐ │
52+
│ │ DockerClient │──│ Labels │ │
53+
│ │ (wrapper) │ │ (org.testcontainers│ │
54+
│ └──────┬───────┘ │ label mgmt) │ │
55+
│ │ └───────────────────┘ │
56+
├─────────┼────────────────────────────────────────────────────────┤
57+
│ ▼ │
58+
│ ┌──────────────────────────────────────────────────────────────┐│
59+
│ │ WWW::Docker (vendored) ││
60+
│ │ Docker.pm │ Request.pm │ Container.pm │ Image.pm │ etc. ││
61+
│ └──────────────────────────────────────────────────────────────┘│
62+
└──────────────────────────────────────────────────────────────────┘
63+
```
64+
65+
## Key Patterns
66+
67+
### Factory Function API
68+
69+
The primary API is a single exported function rather than an object constructor:
70+
71+
```perl
72+
use Testcontainers qw( run );
73+
use Testcontainers::Wait;
74+
75+
my $container = run('postgres:16-alpine',
76+
exposed_ports => ['5432/tcp'],
77+
env => { POSTGRES_PASSWORD => 'test' },
78+
wait_for => Testcontainers::Wait::for_log('ready to accept connections'),
79+
);
80+
```
81+
82+
This mirrors Go's `testcontainers.Run(ctx, req)` pattern. Internally, `run()` builds a `ContainerRequest`, creates the container via `DockerClient`, starts it, and executes the wait strategy.
83+
84+
### Moo-Based Object System
85+
86+
All classes use [Moo](https://metacpan.org/pod/Moo) for lightweight object construction:
87+
88+
- `has` declarations for attributes with defaults and validation
89+
- `Moo::Role` for shared behaviour (e.g., `Testcontainers::Wait::Base`)
90+
- `DEMOLISH` for automatic cleanup (container termination on object destruction)
91+
92+
### Wait Strategy Composition
93+
94+
Wait strategies follow the role pattern:
95+
96+
```perl
97+
package Testcontainers::Wait::HostPort;
98+
use Moo;
99+
with 'Testcontainers::Wait::Base';
100+
101+
sub check {
102+
my ($self, $container) = @_;
103+
# Attempt TCP connection, return 1 on success, 0 on failure
104+
}
105+
```
106+
107+
`Testcontainers::Wait::Base` provides the `wait_until_ready($container, $timeout)` polling loop that repeatedly calls `check()` until it returns true or the timeout expires.
108+
109+
`Testcontainers::Wait::Multi` composes multiple strategies — all must pass.
110+
111+
### Module Pattern
112+
113+
Pre-built modules follow a consistent factory pattern:
114+
115+
```perl
116+
package Testcontainers::Module::PostgreSQL;
117+
use Exporter 'import';
118+
our @EXPORT_OK = qw( postgres_container );
119+
120+
sub postgres_container {
121+
my (%opts) = @_;
122+
my $container = Testcontainers::run($image,
123+
exposed_ports => ['5432/tcp'],
124+
env => { POSTGRES_PASSWORD => $password, ... },
125+
_internal_labels => { 'org.testcontainers.module' => 'postgresql' },
126+
wait_for => Testcontainers::Wait::for_log('ready to accept connections',
127+
occurrences => 2),
128+
);
129+
return Testcontainers::Module::PostgreSQL::Container->new(
130+
_container => $container, ...
131+
);
132+
}
133+
```
134+
135+
The inner `::Container` class wraps the base container and adds service-specific methods like `dsn()` and `connection_string()`.
136+
137+
### Labels Specification
138+
139+
`Testcontainers::Labels` centralizes label management:
140+
141+
- `default_labels($session_id)` — returns the standard `org.testcontainers.*` labels
142+
- `merge_custom_labels(\%defaults, \%user_labels)` — merges user labels, rejecting any with the reserved `org.testcontainers` prefix
143+
- `_internal_labels` attribute on `ContainerRequest` — allows framework code (modules) to set reserved-prefix labels without triggering validation
144+
145+
## Container Lifecycle
146+
147+
```
148+
run($image, %opts)
149+
150+
├── Build ContainerRequest
151+
├── Pull image (unless no_pull)
152+
├── Create container (Docker API)
153+
├── Start container
154+
├── Refresh (get port mappings)
155+
├── Execute wait strategy
156+
│ └── Poll check() until ready or timeout
157+
└── Return Container object
158+
159+
├── host() / mapped_port() / endpoint()
160+
├── exec() / logs()
161+
└── terminate()
162+
└── Stop + Remove + Volumes
163+
```
164+
165+
## Error Handling
166+
167+
- User-facing errors use `Carp::croak` with descriptive messages.
168+
- Docker communication errors are caught with `eval { }` and logged via `Log::Any`.
169+
- Wait strategy timeouts produce a `croak` with the strategy name and elapsed time.
170+
- Container auto-cleanup in `DEMOLISH` uses `eval` to suppress errors during global destruction.
171+
172+
## Testing Architecture
173+
174+
Tests are organized by scope:
175+
176+
| File | Scope | Docker? |
177+
|------|-------|---------|
178+
| `t/01-load.t` | Module loading | No |
179+
| `t/02-container-request.t` | ContainerRequest unit tests | No |
180+
| `t/03-wait-strategies.t` | Wait strategy unit tests | No |
181+
| `t/04-integration.t` | Full container lifecycle | Yes |
182+
| `t/05-modules.t` | Module integration | Yes |
183+
| `t/06-basic.t``t/12-volumes.t` | WWW::Docker client tests | No |
184+
185+
Integration tests guard with:
186+
187+
```perl
188+
plan skip_all => 'Set TESTCONTAINERS_LIVE=1' unless $ENV{TESTCONTAINERS_LIVE};
189+
```
190+
191+
## References
192+
193+
- [Testcontainers for Go](https://golang.testcontainers.org/) — primary inspiration
194+
- [WWW::Docker](https://github.com/Getty/p5-www-docker) — vendored Docker client
195+
- [Moo](https://metacpan.org/pod/Moo) — object system
196+
- [Docker Engine API v1.44](https://docs.docker.com/engine/api/v1.44/) — Docker REST API reference

CLAUDE.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ Uses WWW::Docker as the Docker client library.
1212
- `lib/Testcontainers/Container.pm` - Running container wrapper
1313
- `lib/Testcontainers/ContainerRequest.pm` - Container configuration
1414
- `lib/Testcontainers/DockerClient.pm` - WWW::Docker wrapper
15+
- `lib/Testcontainers/Labels.pm` - Label management (org.testcontainers.*)
1516
- `lib/Testcontainers/Wait.pm` - Wait strategy factory
1617
- `lib/Testcontainers/Wait/*.pm` - Wait strategy implementations
1718
- `lib/Testcontainers/Module/*.pm` - Pre-built container modules
19+
- `lib/WWW/Docker.pm` - Vendored Docker client
20+
- `lib/WWW/Docker/*.pm` - Vendored Docker client components
1821

1922
## Test Architecture
2023

@@ -23,6 +26,7 @@ Uses WWW::Docker as the Docker client library.
2326
- `t/03-wait-strategies.t` - Wait strategy unit tests (no Docker)
2427
- `t/04-integration.t` - Integration tests (requires Docker, TESTCONTAINERS_LIVE=1)
2528
- `t/05-modules.t` - Module integration tests (requires Docker, TESTCONTAINERS_LIVE=1)
29+
- `t/06-basic.t` through `t/12-volumes.t` - WWW::Docker unit tests (no Docker)
2630

2731
## Running Tests
2832

@@ -36,8 +40,8 @@ TESTCONTAINERS_LIVE=1 prove -l t/
3640

3741
## Key Dependencies
3842

39-
- Perl 5.42+
43+
- Perl 5.40+
4044
- Moo (object system)
41-
- WWW::Docker (Docker client)
45+
- WWW::Docker (vendored Docker client)
4246
- Log::Any (logging)
4347
- HTTP::Tiny (optional, for HTTP wait strategy)

0 commit comments

Comments
 (0)