From abb6871b21d8b94cdd892c295a95d2f056c3d7a2 Mon Sep 17 00:00:00 2001 From: Kieron Lanning Date: Mon, 12 Oct 2026 22:54:13 +0100 Subject: [PATCH 1/5] chore: take Purview.BuildSdk 1.0.5 and Purview.Telemetry.SourceGenerator 5.0.3 Update Purview.BuildSdk from 1.0.4 to 1.0.5 and Purview.Telemetry.SourceGenerator from 5.0.1 to 5.0.3. Drop the redundant purview- prefix from the package.json name and add a description. --- Directory.Packages.props | 2 +- global.json | 2 +- package.json | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index f69bcfb..deddac5 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,7 +7,7 @@ - + diff --git a/global.json b/global.json index 10d45ff..791bc0e 100644 --- a/global.json +++ b/global.json @@ -4,7 +4,7 @@ "allowPrerelease": true }, "msbuild-sdks": { - "Purview.BuildSdk": "1.0.4" + "Purview.BuildSdk": "1.0.5" }, "test": { "runner": "Microsoft.Testing.Platform" diff --git a/package.json b/package.json index 1b0a74b..ffbacfd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { - "name": "purview-containers", + "name": "containers", "version": "1.0.0-prerelease.6", + "description": "Testcontainers-style library for .NET that runs throwaway Linux containers for integration testing, on Microsoft WSL Containers or Docker.", "license": "MIT", "author": { "name": "Kieron Lanning", From 0815ba6f2ea8ebaeb76f423104eee669d00a3424 Mon Sep 17 00:00:00 2001 From: Kieron Lanning Date: Mon, 12 Oct 2026 22:54:24 +0100 Subject: [PATCH 2/5] test(docker): move the CreateContainer tests into the integration tier DockerContainerBackend.CreateContainer builds the underlying Testcontainers container, which connects to the daemon, so the two tests covering it were integration tests sitting in Docker.UnitTests. They failed the always-on fast tier on any machine without Docker. Move them to Docker.IntegrationTests behind the existing DockerTest.SkipIfUnavailableAsync guard, and leave the genuinely daemon-free surface (Name and Create) in the unit project. The new class is named DockerContainerBackendFactoryTests because the unit class keeps the DockerContainerBackendTests name and both projects share the Purview.Containers.Docker namespace. --- .../DockerContainerBackendFactoryTests.cs | 39 +++++++++++++++++++ .../DockerContainerBackendTests.cs | 27 ++----------- 2 files changed, 42 insertions(+), 24 deletions(-) create mode 100644 src/tests/Docker.IntegrationTests/DockerContainerBackendFactoryTests.cs diff --git a/src/tests/Docker.IntegrationTests/DockerContainerBackendFactoryTests.cs b/src/tests/Docker.IntegrationTests/DockerContainerBackendFactoryTests.cs new file mode 100644 index 0000000..0817e13 --- /dev/null +++ b/src/tests/Docker.IntegrationTests/DockerContainerBackendFactoryTests.cs @@ -0,0 +1,39 @@ +namespace Purview.Containers.Docker; + +/// +/// Covers against a real +/// daemon. Creating a container builds the underlying Testcontainers container, which connects to the +/// daemon, so this is an integration concern: with no daemon reachable the call throws +/// DockerUnavailableException rather than returning a container. The daemon-free surface of the +/// backend (its name and Create) stays in Docker.UnitTests. +/// +public class DockerContainerBackendFactoryTests +{ + static Task SkipIfUnavailableAsync() => DockerTest.SkipIfUnavailableAsync(); + + [Test] + public async Task CreateContainer_AdaptsTheConfiguredImageAndName() + { + await SkipIfUnavailableAsync(); + + ContainerConfiguration configuration = new() { Image = "alpine:3.19", Name = "purview-unit-test" }; + + var container = new DockerContainerBackend().CreateContainer(configuration); + + await Assert.That(container).IsTypeOf(); + await Assert.That(container.Image).IsEqualTo("alpine:3.19"); + await Assert.That(container.Name).IsEqualTo("purview-unit-test"); + } + + [Test] + public async Task CreateContainer_GeneratesNoName_WhenTheConfigurationDoesNotSetOne() + { + await SkipIfUnavailableAsync(); + + ContainerConfiguration configuration = new() { Image = "alpine:3.19" }; + + var container = new DockerContainerBackend().CreateContainer(configuration); + + await Assert.That(container.Name).IsNotEmpty(); + } +} diff --git a/src/tests/Docker.UnitTests/DockerContainerBackendTests.cs b/src/tests/Docker.UnitTests/DockerContainerBackendTests.cs index 68525ab..c8d7dce 100644 --- a/src/tests/Docker.UnitTests/DockerContainerBackendTests.cs +++ b/src/tests/Docker.UnitTests/DockerContainerBackendTests.cs @@ -1,8 +1,9 @@ namespace Purview.Containers.Docker; /// -/// Unit coverage for the Docker backend that needs no daemon: identity, registration metadata and the -/// configuration translation. Anything that talks to a daemon lives in Docker.IntegrationTests. +/// Unit coverage for the Docker backend that needs no daemon: identity and registration metadata. +/// Anything that talks to a daemon lives in Docker.IntegrationTests — including +/// CreateContainer, which builds the underlying Testcontainers container and therefore connects. /// public class DockerContainerBackendTests { @@ -17,26 +18,4 @@ public async Task Create_ReturnsANewBackend() { await Assert.That(DockerContainerBackend.Create()).IsNotNull(); } - - [Test] - public async Task CreateContainer_AdaptsTheConfiguredImageAndName() - { - ContainerConfiguration configuration = new() { Image = "alpine:3.19", Name = "purview-unit-test" }; - - var container = new DockerContainerBackend().CreateContainer(configuration); - - await Assert.That(container).IsTypeOf(); - await Assert.That(container.Image).IsEqualTo("alpine:3.19"); - await Assert.That(container.Name).IsEqualTo("purview-unit-test"); - } - - [Test] - public async Task CreateContainer_GeneratesNoName_WhenTheConfigurationDoesNotSetOne() - { - ContainerConfiguration configuration = new() { Image = "alpine:3.19" }; - - var container = new DockerContainerBackend().CreateContainer(configuration); - - await Assert.That(container.Name).IsNotEmpty(); - } } From cdd8f7464c5865b141e7c6f9054e4b5f77e1c03e Mon Sep 17 00:00:00 2001 From: Kieron Lanning Date: Mon, 12 Oct 2026 23:03:57 +0100 Subject: [PATCH 3/5] build: install Purview.Build as a .NET local tool Add Purview.Build 0.5.2 to .config/dotnet-tools.json and replace the hand-rolled dotnet tool install --tool-path .tools/purview-build in the Justfile with dotnet tool restore, running the pipeline as dotnet purview-build. The version is now pinned in the tool manifest alongside CSharpier, so it is restored by the same command as every other tool and needs no bespoke install step, no --add-source feed argument and no platform-specific executable suffix. The dead pipeline_feed variable is removed, and the docs describing the old .tools/purview-build path are updated to match. --- .config/dotnet-tools.json | 9 ++++++++- Justfile | 21 +++++++++------------ docs/wiki/Release-Flow.md | 4 ++-- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 8e890cc..e11c4a4 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,6 +15,13 @@ "dotnet-inspect" ], "rollForward": false + }, + "purview.build": { + "version": "0.5.2", + "commands": [ + "purview-build" + ], + "rollForward": false } } -} \ No newline at end of file +} diff --git a/Justfile b/Justfile index debbde5..f2e21d0 100644 --- a/Justfile +++ b/Justfile @@ -10,8 +10,7 @@ solution_file := root_folder + "Containers.slnx" test_solution := solution_file build_configuration := "Debug" -pipeline_feed := "https://api.nuget.org/v3/index.json" -pipeline_tool := ".tools/purview-build/purview-build" +pipeline_tool := "dotnet purview-build" artifact_folder := "./artifacts/" @@ -21,33 +20,31 @@ artifact_folder := "./artifacts/" default: just --list -# Install the shared Purview.Build tool (authenticated to the Purview-Dev feed) if not present +# Restore the repository's .NET local tools (.config/dotnet-tools.json) [private] ensure-pipeline-tool: - if [ ! -x "{{ pipeline_tool }}" ]; then \ - dotnet tool install Purview.Build --tool-path .tools/purview-build --add-source "{{ pipeline_feed }}"; \ - fi + dotnet tool restore # Run the PR pipeline (restore, build, lint, tests, pack, validate) [group('Pipeline')] pipeline-pr *args: just ensure-pipeline-tool echo "Running PR pipeline..." - "{{ pipeline_tool }}" {{ args }} + {{ pipeline_tool }} {{ args }} # Run the build pipeline (restore, build, lint) [group('Pipeline')] pipeline-build *args: just ensure-pipeline-tool echo "Running build pipeline..." - "{{ pipeline_tool }}" --Build:RunTests=false --Release:Mode=None {{ args }} + {{ pipeline_tool }} --Build:RunTests=false --Release:Mode=None {{ args }} # Run the release pipeline (restore, build, lint, tests, pack, publish, GitHub release) [group('Pipeline')] pipeline-release *args: just ensure-pipeline-tool echo "Running release pipeline..." - "{{ pipeline_tool }}" --Release:Mode=NuGet {{ args }} + {{ pipeline_tool }} --Release:Mode=NuGet {{ args }} # Run the release pipeline (restore, build, lint, tests, pack, local nuget publish) # Note: `just` runs recipes through the shell, which strips backslashes from unquoted arguments. @@ -57,21 +54,21 @@ pipeline-release *args: pipeline-local-release *args: just ensure-pipeline-tool echo "Running local release pipeline..." - "{{ pipeline_tool }}" --Release:Mode=LocalNuGet {{ args }} + {{ pipeline_tool }} --Release:Mode=LocalNuGet {{ args }} # Run the pipeline with tests enabled [group('Pipeline')] pipeline-tests *args: just ensure-pipeline-tool echo "Running tests pipeline..." - "{{ pipeline_tool }}" --Build:RunTests=true --Release:Mode=None {{ args }} + {{ pipeline_tool }} --Build:RunTests=true --Release:Mode=None {{ args }} # Run the pipeline through pack + validate (restore, build, lint, tests, pack, validate pack contents) without publishing/releasing [group('Pipeline')] pipeline-pack-validate *args: just ensure-pipeline-tool echo "Running pack + validate pipeline..." - "{{ pipeline_tool }}" --Build:RunPack=true --Build:ValidatePack=true --Release:Mode=None {{ args }} + {{ pipeline_tool }} --Build:RunPack=true --Build:ValidatePack=true --Release:Mode=None {{ args }} # ----------------------------------------------------------------------------- # Build and Test diff --git a/docs/wiki/Release-Flow.md b/docs/wiki/Release-Flow.md index 087458f..bf0eb49 100644 --- a/docs/wiki/Release-Flow.md +++ b/docs/wiki/Release-Flow.md @@ -29,8 +29,8 @@ The `Justfile` wraps the common steps: ## The shared pipeline -All pipeline recipes first install the `Purview.Build` tool into `.tools/purview-build` and then run it -against `purview-build.json`: +All pipeline recipes first run `dotnet tool restore` (installing the pinned `Purview.Build` from +`.config/dotnet-tools.json`) and then run it against `purview-build.json`: | Recipe | Pipeline arguments | | --- | --- | From 4e2dc3829a4d71ab66316474d947ca8723b3f23e Mon Sep 17 00:00:00 2001 From: Kieron Lanning Date: Tue, 13 Oct 2026 08:42:07 +0100 Subject: [PATCH 4/5] build: declare RequiredCompilerFlags explicitly Purview.Build 0.5.3 defaults RequiredCompilerFlags to empty. Earlier versions forced the optimization=release check on and could not be configured off, because binding an empty JSON array contributes no configuration keys and so never overwrote the tool's non-empty code default. These packages have therefore always been validated against that flag. Declaring it keeps that behaviour rather than silently losing the check on the move to 0.5.3. Verified with just pipeline-pack-validate: all packages still valid. --- .config/purview-build.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.config/purview-build.json b/.config/purview-build.json index 3f08598..e00ff8a 100644 --- a/.config/purview-build.json +++ b/.config/purview-build.json @@ -7,6 +7,10 @@ "TestFilter": "/*/*/*/*[Category=Unit]" }, "PackValidation": { + // Explicit because Purview.Build 0.5.3 defaults RequiredCompilerFlags to empty. Earlier + // versions forced this check on and could not be configured off, so these packages have + // always been validated against it; declaring it keeps that behaviour. + "RequiredCompilerFlags": ["optimization=release"], "RequireSymbolPackage": true, "RequireSymbolFiles": true, // RequireExplicitContent defaults to true, so this is the exhaustive declaration of what each @@ -110,4 +114,4 @@ "Policy": "TrunkReservesMinor" } } -} \ No newline at end of file +} From 236e46c0df576d65a3801243d8340f7a7494523d Mon Sep 17 00:00:00 2001 From: Kieron Lanning Date: Tue, 13 Oct 2026 09:15:51 +0100 Subject: [PATCH 5/5] build: take Purview.Build 0.5.3 Picks up the fix that makes RequiredCompilerFlags configurable: earlier versions forced the compiler-flag check on because binding an empty JSON array contributes no configuration keys, so the tool's non-empty code default could never be overridden. This repository declares "RequiredCompilerFlags": ["optimization=release"] explicitly, so the check it has always run under is preserved. Verified with just pipeline-pack-validate against 0.5.3. --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index e11c4a4..94a32bb 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -17,7 +17,7 @@ "rollForward": false }, "purview.build": { - "version": "0.5.2", + "version": "0.5.3", "commands": [ "purview-build" ],