Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ Keep product, architecture, and general engineering standards centralized in `AG
- Treat work as incomplete until the relevant tests pass; unit tests need no WSLC host, integration tests do.
- Never weaken the WSLC session/store invariants (single shared session, serialised session mutations, the
shared-store fallback, `Secret` redaction) to make a test pass.
- Treat the published consumer contract as an invariant too: `.NET 11` + a Windows target framework,
the `buildTransitive` defaults, and the `PWC0001`/`PWC0002` guards documented in
`docs/wiki/Consumer-Requirements.md`. `just verify-consumers` must pass before a consumer-facing change
is complete.
- Consult the repository `.agents/` folder for additional skills/workflows that may improve execution
quality. Skill content is delivered by NuGet packages and is read-only here.
- Keep edits minimal, focused, and aligned with existing SDK and repository conventions.
35 changes: 32 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ installation, plus the service modules (`PostgreSql`, `Redis`, `MsSql`, `RabbitM
| `src/WSLTestContainers.slnx` | Canonical solution for restore, build, test and pack |
| `src/src/WslContainers` | Core runtime (`Purview.WslContainers`): `ContainerBuilder`, `WslContainer`, runtime, sessions, images, networking, mounts, wait strategies, diagnostics |
| `src/src/<Module>` | Service modules; each is a thin layer over the core and carries a bespoke `Sdk/README.md` |
| `src/src/<Project>/Sdk` | Package-only assets. `Sdk/README.md` is packed as the package README (suppressing the repo-root README); `Sdk/buildTransitive/**` would ship MSBuild assets |
| `src/src/<Project>/Sdk` | Package-only assets. `Sdk/README.md` is packed as the package README (suppressing the repo-root README); `Sdk/buildTransitive/**` ships MSBuild assets to consumers (the core package's consumer defaults and guards) |
| `src/tests` | TUnit unit (`*.UnitTests`) and WSLC integration (`*.IntegrationTests`) projects |
| `spikes/WslcSpikes` | Phase 0 investigation harness (`s1`..`s14` probes); not part of the test run |
| `docs/wiki` | User-facing documentation wiki, aggregated by the purview-dev website |
Expand Down Expand Up @@ -62,6 +62,23 @@ installation, plus the service modules (`PostgreSql`, `Redis`, `MsSql`, `RabbitM
session so its name is released.
- **Secrets use `Secret`** and must never reach logs, names, `ToString()` or diagnostics (they are redacted).

## Consumer requirements and compatibility

- The packages are **.NET 11, Windows-only**: every project under `src/` targets
`net11.0-windows10.0.19041.0` (`src/Directory.Build.props`) and a consumer must match. The contract,
the failures that enforce it and the CI workarounds are documented in
[Consumer Requirements](docs/wiki/Consumer-Requirements.md); update that page whenever the target
framework, the `buildTransitive` defaults or the `PWC0001`/`PWC0002` guards change.
- `Purview.WslContainers` ships `Sdk/buildTransitive/Purview.WslContainers.{props,targets}` so consumers
inherit `WindowsSdkPackageVersion`/`PlatformTarget` defaults and a clear error for an unsupported
target framework (`PWC0001`) or a 32-bit consumer (`PWC0002`). Those assets are framework-agnostic, so
NuGet no longer raises `NU1202` at restore time — the guards are the fail-fast path. Keep them, and
keep the `RequiredContent` entries that declare them.
- `src/Directory.Build.props` sets `EnableWindowsTargeting=true` because the shared CI agent is
`ubuntu-latest`; removing it fails the pipeline with `NETSDK1100`.
- The project is **experimental**: keep the experimental notice in `README.md`, `docs/wiki/Home.md` and
the package READMEs, and keep versions on a `-prerelease.N` suffix.

## Module rules

- A module supplies only defaults: image, ports, environment, module configuration (`WithXxx`), a readiness
Expand All @@ -79,7 +96,9 @@ installation, plus the service modules (`PostgreSql`, `Redis`, `MsSql`, `RabbitM
resolves `IsPackable` by scanning the project file, and the package metadata/icon conditions in
`src/Directory.Build.props` depend on it.
- Each package ships `lib/$(TFM)/<Assembly>.{dll,xml}`, `README.md` (from `Sdk/README.md`) and
`purview-logo-light.png`. PDBs ship only in the `.snupkg`.
`purview-logo-light.png`. PDBs ship only in the `.snupkg`. The core package additionally ships
`buildTransitive/Purview.WslContainers.{props,targets}` (see
[Consumer requirements and compatibility](#consumer-requirements-and-compatibility)).
- `PackValidation.RequireExplicitContent` defaults to `true`, so `purview-build.json`'s `RequiredContent` is
the **exhaustive** manifest: a produced package with no rule, or a packed entry matched by no glob, fails
validation. Adding or removing packaged content means updating that map.
Expand All @@ -94,6 +113,12 @@ installation, plus the service modules (`PostgreSql`, `Redis`, `MsSql`, `RabbitM
exclusively locks its image-store VHD, so parallel modules fail with `0x80070020`.
- Use the shared `WslcTest` helper in `src/tests/SharedTestingFramework` for integration skip/availability
checks. Unit tests may use the modules' `BuildConfigurationForTesting()` internal hook.
- `WslContainers.UnitTests/ConsumerRequirementsTests.cs` guards the published target framework
(`.NETCoreApp,Version=v11.0` plus `Windows10.0.19041.0`); keep it in step with
[Consumer Requirements](docs/wiki/Consumer-Requirements.md).
- `just verify-consumers` packs and then builds throwaway consumer projects to assert the documented
guard errors and workarounds. It restores from nuget.org, so it stays out of the `[Category=Unit]`
filter and is a local/explicit step.

## Local commands

Expand All @@ -102,6 +127,7 @@ just build # dotnet build (Debug)
just test '/*/*/*/*[Category=Unit]' # unit tests only
just lint-check / just lint-fix # CSharpier check / format
just pack # build + dotnet pack into ./artifacts
just verify-consumers # build throwaway consumers against the packed packages
just pipeline-pack-validate # restore, build, lint, test, pack, validate
just scrub # reset bin/obj, clean, forced restore, build-server shutdown
```
Expand All @@ -117,7 +143,8 @@ Commit messages follow Conventional Commits enforced by the `commit-msg` lefthoo
- `.github/workflows/release.yml` runs the shared release pipeline with `release-mode: NuGet` on a push to
`main`.
- Both workflows pin `dotnet-version` to `global.json`'s `sdk.version`; keep them in sync, and keep
`purview-build.json` pointing at `src/WSLTestContainers.slnx`.
`purview-build.json` pointing at `src/WSLTestContainers.slnx`. The shared workflow runs on
`ubuntu-latest`, which is why `EnableWindowsTargeting=true` must stay in `src/Directory.Build.props`.

## Completion checklist

Expand All @@ -127,6 +154,8 @@ Before handing work back:
- Review public API, package-content and dependency-direction implications.
- Update the affected package `Sdk/README.md`, the root `README.md`, `docs/wiki` (plus `_Sidebar.md` and
`mkdocs.yml` for new pages), `AGENTS.md` and `purview-build.json` when the change affects them.
`docs/wiki/Consumer-Requirements.md` is the contract every consumer-facing change has to keep
accurate, and `just verify-consumers` is the check that proves it.
- Watch for the packaging traps: a new packable project missing `<IsPackable>true</IsPackable>` or a
`RequiredContent` entry, and a new `Sdk/README.md` that does not describe its own package.
- Run the appropriate build, test, formatting and pack checks in proportion to risk.
Expand Down
10 changes: 10 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,16 @@ pack *args:
echo "Packing {{ BLUE }}{{ solution_file }}{{ NORMAL }} with {{ YELLOW }}{{ build_configuration }}{{ NORMAL }}..."
dotnet pack "{{ solution_file }}" --configuration "{{ build_configuration }}" --no-restore --output "{{ artifact_folder }}" {{ args }}

# Verifies the documented consumer requirements and workarounds (docs/wiki/Consumer-Requirements.md)
# by packing and then building throwaway consumer projects against the produced packages: the
# "just reference the package from a .NET 11 Windows project" path, the shipped buildTransitive
# defaults, the guard errors for unsupported target frameworks, and the non-.NET-11 escape hatch
# that deliberately does not work. Slower than unit tests (packs and restores from nuget.org).
[group('Build and Test')]
verify-consumers *args:
just pack
pwsh -NoProfile -File scripts/verify-consumers.ps1 {{ args }}

# -----------------------------------------------------------------------------
# Formatting
# -----------------------------------------------------------------------------
Expand Down
38 changes: 32 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
# Purview.WslContainers

[![NuGet version](https://img.shields.io/nuget/v/Purview.WslContainers.svg)](https://www.nuget.org/packages/Purview.WslContainers)
[![Release](https://github.com/purview-dev/wsl-containers/actions/workflows/release.yml/badge.svg)](https://github.com/purview-dev/wsl-containers/actions/workflows/release.yml)

A **WSLC-native** Testcontainers-style library for .NET that runs throwaway Linux containers for integration testing on **Microsoft WSL Containers (WSLC)** — with no Docker installation.

Built directly against the `Microsoft.WSL.Containers` NuGet package (the WSLC managed C# API). No `wslc.exe`/`wsl.exe`/`docker` CLI, no Docker.DotNet, no Testcontainers internally.

> **Experimental.** This project is an experiment in running throwaway containers through Microsoft
> WSL Containers. The public API, defaults and packaging rules can change between prereleases, and
> there is no production support guarantee. Pin the exact package version you build against, and read
> [Consumer Requirements](docs/wiki/Consumer-Requirements.md) before adopting it.

> **Status: Phase 8 in progress.** Core runtime, wait strategies, the `Image`/`Tag` parser, registry auth,
> observability, hardening, and **seven service modules** (PostgreSQL, Redis, SQL Server, RabbitMQ, Azurite,
> NATS, MySQL). **Images are shared by default** (`StorageMode.Shared`): sessions reuse a stable
> image store (`%LOCALAPPDATA%\Purview\WslContainers\images`) so images are pulled once, not per session;
> `StorageMode.PerSession` provides isolation. 69+ unit tests pass across all modules.
> `StorageMode.PerSession` provides isolation. 76 unit tests pass across all modules.

## Prerequisites

- Windows 10/11
- **WSL ≥ 3.0.1.0** with WSL Containers, installed via `wsl --install --no-distribution`
- .NET SDK 11 (the repo pins `11.0.100-rc.1`)
- Windows 10/11.
- **WSL Containers**, installed via `wsl --install --no-distribution` (verified against WSL 3.0.1.0).
- .NET SDK 11 (the repo pins `11.0.100-rc.1`).
- A **consuming project must be a .NET 11 project that targets Windows specifically** —
`net11.0-windows10.0.19041.0`, x64 or arm64. The packages ship MSBuild defaults for the supporting
settings; the full contract, the exact errors raised when it is not met, and the
`EnableWindowsTargeting` workaround for non-Windows CI agents are documented in
[Consumer Requirements](docs/wiki/Consumer-Requirements.md).

Verify:

```powershell
wsl --version # needs 3.0.1.0+
wslc version # needs 3.0.1.0+
wsl --version # WSL Containers installed (verified against 3.0.1.0)
wslc version # e.g. 3.0.1.0
```

The library reports missing prerequisites via `WslContainerRuntime.GetInfoAsync()`; it never installs or updates WSL on its own.
Expand Down Expand Up @@ -129,6 +142,7 @@ The project documentation lives in [`docs/wiki`](docs/wiki/Home.md) and is publi
(`mkdocs.yml`, `docs_dir: docs/wiki`, aggregated by the purview-dev website):

- [Getting Started](docs/wiki/Getting-Started.md) — prerequisites, first container, first module.
- [Consumer Requirements](docs/wiki/Consumer-Requirements.md) — the .NET 11 + Windows target framework contract, the `PWC0001`/`PWC0002` guards, and the CI workarounds.
- [Architecture](docs/wiki/Architecture.md) — the shared session model, concurrency and cleanup decisions.
- [Lifecycle](docs/wiki/Lifecycle.md), [Networking](docs/wiki/Networking.md), [Wait Strategies](docs/wiki/Wait-Strategies.md).
- [Modules](docs/wiki/Modules.md) — the module contract and every shipped module.
Expand Down Expand Up @@ -222,6 +236,18 @@ just test # dotnet test, one test module at a time
> The `WslContainers.IntegrationTests` module runs 27 real containers in one session and takes ~4
> minutes because WSLC serialises container operations; expect slow-test warnings while it runs.

### Verifying the consumer contract

```powershell
just verify-consumers # pack, then build 12 throwaway consumer projects against ./artifacts
just verify-consumers -Keep # same, keeping the generated projects for inspection
```

`just verify-consumers` asserts every claim in [Consumer Requirements](docs/wiki/Consumer-Requirements.md):
the happy path, the shipped `buildTransitive` defaults, the `PWC0001`/`PWC0002` guards, and the
non-.NET-11 escape hatch that deliberately does not work. It needs network access and is therefore a
local step rather than part of the `[Category=Unit]` pipeline filter.

## Running the Phase 0 spikes

```powershell
Expand Down
Loading
Loading