diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index bc9bc27..1978504 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -17,7 +17,7 @@ "rollForward": false }, "purview.build": { - "version": "0.9.0", + "version": "0.9.1", "commands": [ "purview-build" ], diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 76e1703..c287e7f 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,6 +13,9 @@ jobs: name: Build and test uses: purview-dev/build/.github/workflows/purview-build.yml@main with: + # Pinned to the version in .config/dotnet-tools.json, so CI runs exactly what `just` runs locally + # instead of whichever Purview.Build was latest when the job started (which warns on every run). + build-version: "0.9.0" # The shared workflow defaults to the 10.0.x SDK, but this repository targets net11.0 and pins # the .NET 11 SDK in global.json. Without the matching SDK the runner cannot resolve global.json # ("A compatible .NET SDK was not found") and every step fails, starting with `dotnet tool @@ -20,10 +23,45 @@ jobs: # NETSDK1045 on the net11.0 target, because the 10.0.x SDK cannot build it. # Keep in sync with `sdk.version` in global.json. dotnet-version: "11.0.100" + # Explicit, so PrWorkflow_GivenTheTestGates_RunsEveryTestInBothConfigurations can prove the + # Release gate still runs tests; the workflow default is true, but a default is invisible here. + run-tests: true run-pack: true validate-pack: true secrets: inherit + # The shared pipeline runs a single configuration (Release, the tool's default). A failure that only + # reproduces in Debug would therefore reach a developer's `just test` (which defaults to Debug) and + # never CI. The cross-platform job below also runs Debug, but filters to the parity producers; this + # job builds the whole solution and runs every test in Debug. + debug: + name: Build and test (Debug) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + # Same reason as the cross-platform job: the SDK comes from global.json, not a floating + # version plus a feed quality. + - uses: actions/setup-dotnet@v6 + with: + global-json-file: global.json + + # Repository.UnitTests drives the vitest suites from the .NET test run, so the JavaScript + # dependencies have to be installed first. + - uses: oven-sh/setup-bun@v2 + with: + bun-version-file: package.json + + - run: bun install --frozen-lockfile + + - name: Build the solution (Debug) + run: dotnet build src/ZodSharp.slnx --configuration Debug + + - name: Run every test (Debug) + run: >- + dotnet test --solution src/ZodSharp.slnx --configuration Debug + --no-restore --no-build --treenode-filter '/*/*/*/*' + # The shared pipeline runs the C# suite only, so the TypeScript/Zod side of the cross-platform # parity guarantee (docs/wiki/quality/Guarantees-and-Limitations.md) would otherwise never run in CI. # The vitest suite validates the JSON the C# cross-platform tests write, so the C# tests must run diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 01283a5..781a38a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,6 +28,9 @@ jobs: # file name the policy is registered against. - uses: purview-dev/build/actions/purview-release@main with: + # Pinned to the version in .config/dotnet-tools.json, so the release runs exactly what the + # pull request validated instead of whichever Purview.Build was latest when the job started. + build-version: "0.9.0" release-mode: NuGet trusted-publishing: true # The nuget.org profile name, not the sign-in email. diff --git a/docs/wiki/development/Release-Flow.md b/docs/wiki/development/Release-Flow.md index 35e021d..00ec031 100644 --- a/docs/wiki/development/Release-Flow.md +++ b/docs/wiki/development/Release-Flow.md @@ -27,7 +27,7 @@ rather than passing. | Gate | What it proves | Local | CI | |---|---|---|---| -| Build, lint and tests | The solution builds warning-free, CSharpier reports no change, and every test project passes (including the cross-platform vitest suites, through `Repository.UnitTests`). | `just build`, `just lint-check`, `just test` | `pr.yml` `build` | +| Build, lint and tests | The solution builds warning-free, CSharpier reports no change, and every test project passes (including the cross-platform vitest suites, through `Repository.UnitTests`). | `just build`, `just lint-check`, `just test` | `pr.yml` `build` (Release, with lint and pack) and `pr.yml` `debug` (the whole solution and every test in Debug) | | Pack and pack validation | Every package packs; package validation holds the public API to the 2.4.2 baseline; `.config/purview-build.json` `PackValidation` finds exactly the declared content (`RequiredContent`, `RequireExplicitContent`) and none of the `ForbiddenContent`. | `just pipeline-pack-validate` | `pr.yml` `build` (`run-pack`, `validate-pack`) | | Forbidden-content rule proof | Every `ForbiddenContent` rule rejects a package: a matching file is injected into scratch copies of each package and the real `purview-build` validation must fail, per package and per rule. | `just verify-pack-rules` | `pr.yml` `packed-consumers` | | Packed generator | The IL-merged generator in the `.nupkg` loads and generates in a net10.0 consumer (it has regressed twice while the in-repo tests, which use the unmerged generator, stayed green). | `just smoke-packed-generator` | `pr.yml` `packed-generator` | @@ -74,7 +74,7 @@ just publish-aot-sample # Native AOT minimal API sample an | Workflow | Trigger | Pipeline mode | |---|---|---| -| `pr.yml` | pull requests against `main` | `purview-build.yml` with `run-pack: true`, `validate-pack: true` | +| `pr.yml` | pull requests against `main` | `purview-build.yml` with `run-pack: true`, `validate-pack: true` (Release), plus a Debug build and full test run | | `release.yml` | pushes to `main` | `purview-release.yml` with `release-mode: NuGet` | Both consume `.config/purview-build.json`: diff --git a/src/tests/Blazor.UnitTests/GeneratedSchemaTests.cs b/src/tests/Blazor.UnitTests/GeneratedSchemaTests.cs index 59e5136..7da8898 100644 --- a/src/tests/Blazor.UnitTests/GeneratedSchemaTests.cs +++ b/src/tests/Blazor.UnitTests/GeneratedSchemaTests.cs @@ -236,9 +236,14 @@ public async Task DataAnnotationsValidator_GivenAsyncOnlyMemberFieldChange_IsPen var form = context.Render(editContext, FormHost.DataAnnotations()); FieldIdentifier field = new(cat, nameof(GeneratedCat.Name)); - // Act - await form.ChangeAsync(editContext, field); - var pending = editContext.IsValidationPending(field); + // Act - read the pending state inside the renderer's dispatch operation. The validation suspends at its + // first await, and its continuation is queued on that same dispatcher, so sampling after the call returns + // races the completion (a Release run on a busy Linux runner resolves before the test thread resumes). + var pending = await form.InvokeAsync(() => + { + editContext.NotifyFieldChanged(field); + return editContext.IsValidationPending(field); + }); await FormHost.WaitUntilAsync(() => !editContext.IsValidationPending(field), "resolved"); // Assert diff --git a/src/tests/Repository.UnitTests/ReleaseGateTests.cs b/src/tests/Repository.UnitTests/ReleaseGateTests.cs index cbabdf7..8e49680 100644 --- a/src/tests/Repository.UnitTests/ReleaseGateTests.cs +++ b/src/tests/Repository.UnitTests/ReleaseGateTests.cs @@ -265,6 +265,34 @@ CancellationToken cancellationToken await Assert.That(job).Contains("dotnet workload install wasm-tools"); } + /// + /// The shared pipeline runs a single configuration, so without the Debug job CI would only ever exercise + /// Release while `just test` runs Debug locally. A test that fails only in Debug would reach a developer and + /// never the pull request. Both configurations must build the solution and run every test. + /// + [Test] + public async Task PrWorkflow_GivenTheTestGates_RunsEveryTestInBothConfigurations( + CancellationToken cancellationToken + ) + { + // Arrange + var text = await File.ReadAllTextAsync( + Path.Combine(RepositoryRoot.Value, ".github", "workflows", "pr.yml"), + cancellationToken + ); + + // Act + var jobs = Jobs(text).ToArray(); + var release = jobs.SingleOrDefault(static j => j.Name == "build").Body ?? string.Empty; + var debug = jobs.SingleOrDefault(static j => j.Name == "debug").Body ?? string.Empty; + + // Assert + await Assert.That(release).Contains("run-tests: true"); + await Assert.That(debug).Contains("dotnet build src/ZodSharp.slnx --configuration Debug"); + await Assert.That(debug).Contains("dotnet test --solution src/ZodSharp.slnx --configuration Debug"); + await Assert.That(debug).Contains("--treenode-filter '/*/*/*/*'"); + } + /// /// Compiler-host assemblies must stay forbidden everywhere NuGet hands files to an application, and the generator /// and code fixes everywhere an application loads assemblies from. scripts/test-pack-validation-rules.ps1