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
5 changes: 3 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ updates:
- "PhoenixmlDb.*"

# Test tooling churns often and rarely interestingly. One PR, not six.
# Microsoft.NET.Test.Sdk and coverlet.collector were dropped with the move to
# Microsoft.Testing.Platform; the MTP packages themselves arrive transitively through
# xunit.v3, so "xunit*" is the whole surface Dependabot can see here.
test-tooling:
patterns:
- "xunit*"
- "Microsoft.NET.Test.Sdk"
- "coverlet*"

ignore:
# FluentAssertions 7.x and 8.x moved to the paid Xceed licence. docmd's core is
Expand Down
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,39 @@ stylesheet writes against is not stable until `1.0`.

## [Unreleased]

Test infrastructure only. **No product change**: nothing under `src/` was touched, and no shipped
package reference moved.

### Changed

- **xunit.v3 3.2.2 → 4.0.1**, which runs on Microsoft.Testing.Platform and drops VSTest. The
test platform moved with it; `dotnet test docmd.slnx` and the existing `--filter` expressions
are unchanged.

The opt-in is `"test": { "runner": "Microsoft.Testing.Platform" }` in `global.json`. The
property the error message points at, `TestingPlatformDotnetTestSupport`, is the pre-.NET-10
bridge and cannot help: MTP 2.4.0 raises the error from inside that bridge's own target,
guarded on the SDK major version alone.

Three VSTest-only packages were removed rather than bumped, because under MTP they do nothing:
`Microsoft.NET.Test.Sdk`, `xunit.runner.visualstudio` and `coverlet.collector`. Test projects
gained `<OutputType>Exe</OutputType>`, which `Microsoft.NET.Test.Sdk` used to supply.

Verified: 363 tests — 362 pass, 1 skip (the corpus audit) — which is the pre-migration count
of 362 plus the one performance test the old filter had excluded; `--locked-mode` restore
clean; the performance gate still runs in isolation.

A run that executes no tests now exits 8 rather than passing, so a filter that matches nothing
fails CI instead of going green. Full reasoning in
[`docs/deferred-work.md`](docs/deferred-work.md).

### Fixed

- `src/Docmd.Cli/packages.lock.json` recorded `Ooxml.Md.Core` at `0.2.3` after the 0.2.4 release.
Regenerating the lock files corrected it. Worth knowing that `--locked-mode` does not catch
this: it validates package versions, not the project-reference versions in the same file, so
that field goes stale on every release and nothing complains.

## [0.2.4] — 2026-09-29

Dependencies. **No behaviour change**: output is byte-identical across the 13-document corpus.
Expand Down
26 changes: 26 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,32 @@ deliberately not: `fixtures/real/sample.docx` is a genuine third-party-written d
`word-noise` carries Word's own mess — `w:proofErr`, rsids, bookmarks between runs, words split
mid-word at spell-check boundaries. Those two find bugs hand-written XML never does.

### The test platform

Tests run on **Microsoft.Testing.Platform**, not VSTest. `dotnet test docmd.slnx` is unchanged,
and so is `--filter "FullyQualifiedName~Something"` — xunit.v3 4.x accepts VSTest filter syntax.
Three things are worth knowing:

- **Each test project is an executable.** `OutputType` is `Exe`, set centrally in
`Directory.Build.props` for any project whose name ends in `.Tests`, and you can run one
without `dotnet test`:

```console
$ ./tests/Docmd.Word.Tests/bin/Release/net10.0/Docmd.Word.Tests
$ ./tests/Docmd.Word.Tests/bin/Release/net10.0/Docmd.Word.Tests -class Docmd.Word.Tests.TransformScalingTests
```

Run that way it is xunit's own in-process console runner, whose options are single-dash and
different (`-class`, `-method`, `-trait`; `--help` lists them). The MTP options that
`dotnet test` accepts, `--filter` included, are rejected there with `unknown option`.
- **A run that executes no tests fails**, with exit code 8. Under VSTest a filter that matched
nothing was a silent pass, which is the vacuous pass above with CI's name on it.
- **A VSTest filter cannot be combined** with xunit's own `--filter-class` / `--filter-method` /
`--filter-query` forms. Pick one style per invocation.

The opt-in is `"test": { "runner": "Microsoft.Testing.Platform" }` in `global.json`. Without it
the SDK runs the VSTest command, which on .NET 10 fails the build rather than falling back.

### Running the corpus audit

Against your own documents:
Expand Down
14 changes: 14 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@
<PackageProjectUrl>https://github.com/phoenixmldb/docmd</PackageProjectUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<!--
Test projects are executables under Microsoft.Testing.Platform: a test assembly is its own
runner, so there is no external host to load it. xunit.v3 4.x fails the build without this
rather than inferring it, which is the right call but does mean it has to be said somewhere.

Here rather than in each csproj so a test project added later cannot quietly miss it. The
condition matches on the project name because IsTestProject is set by the test SDK targets,
which are imported long after Directory.Build.props is evaluated: conditioning on it here
would silently never match.
-->
<PropertyGroup Condition="$(MSBuildProjectName.EndsWith('.Tests'))">
<OutputType>Exe</OutputType>
</PropertyGroup>

<!-- Deterministic in every configuration, not just Release: docmd's own output
determinism is a product guarantee, and a non-deterministic build is a
confusing thing to debug underneath it. -->
Expand Down
20 changes: 16 additions & 4 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,22 @@
<PackageVersion Include="YamlDotNet" Version="18.1.0" />
</ItemGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
<PackageVersion Include="xunit.v3" Version="3.2.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageVersion Include="coverlet.collector" Version="10.0.1" />
<!--
xunit.v3 4.x runs on Microsoft.Testing.Platform and nothing else. Three packages left
with 3.2.2, all of them VSTest-only and now inert:

Microsoft.NET.Test.Sdk the VSTest targets and test host
xunit.runner.visualstudio the VSTest adapter that discovered xunit tests
coverlet.collector a VSTest data collector, reached via the collect flag

Removing them is not tidying. MTP 2.4.0 errors out of the VSTest target on the .NET 10
SDK, so keeping the first two cannot work; and a collector that no longer collects is
worse than an absent one, because the collect flag would still be accepted and quietly
do nothing. Coverage under MTP comes from coverlet.mtp or from
Microsoft.Testing.Extensions.CodeCoverage. Neither is added here, because nothing in
this repo has ever asked for a coverage number. See docs/deferred-work.md.
-->
<PackageVersion Include="xunit.v3" Version="4.0.1" />
<!-- Intentionally pinned. 7.x/8.x moved to the Xceed licence (paid for commercial
use); 6.12.2 is the last MIT release, and docmd is a commercial product.
Do not bump without a licensing decision. -->
Expand Down
53 changes: 37 additions & 16 deletions docs/deferred-work.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,31 +60,52 @@ already dead, while the anchor text survives. Recorded so the corpus audit can r
- `docmd audit`, `-r`/`--recursive`, `--review`, `--style-map`, `--strict`, `--report` and
`--revisions` all currently fail cleanly as not-yet-supported.

## xunit v3 4.0 needs a test-platform migration, not a version bump
## xunit v3 4.0 needed a test-platform migration, not a version bump

**Status:** open, deliberately deferred 2026-09-10. Pinned at xunit.v3 3.2.2.
**Status:** closed 2026-09-29. On xunit.v3 4.0.1, running on Microsoft.Testing.Platform 2.4.0.

xunit.v3 4.0.0 drops VSTest support on the .NET 10 SDK and requires Microsoft.Testing.Platform:
xunit.v3 4.0 drops VSTest on the .NET 10 SDK. The error names a property that sounds like the
fix and is not:

```
error : Testing with VSTest target is no longer supported by Microsoft.Testing.Platform
on .NET 10 SDK and later. If you use dotnet test, you should opt-in to the new dotnet
test experience.
```

Setting `TestingPlatformDotnetTestSupport` is not sufficient on its own; the `dotnet test` CLI
needs its own opt-in as well. Beyond that, MTP changes filter syntax, and `--filter
"FullyQualifiedName~X"` appears in `.github/workflows/ci.yml` (which runs the performance gate as
its own step), in `CONTRIBUTING.md` twice, and in the corpus-audit instructions. All of them
change together or none do.

Deferred because it is orthogonal to shipping, and a hurried test-infrastructure migration
immediately before a first release is a poor trade: the thing that tells you whether the release
is sound is the last thing to rebuild in a hurry. Nothing about 3.2.2 is broken.

**Closes with:** `dotnet.config` selecting the MTP runner, the property above, the filter
expressions rewritten in all four places, and a CI run proving the performance gate still runs
in isolation.
`TestingPlatformDotnetTestSupport=true` is the pre-.NET-10 bridge: it redirects the VSTest
target into `InvokeTestingPlatform`. MTP 2.4.0 removed that escape hatch, and the removal is
what produces the error above. `Microsoft.Testing.Platform.MSBuild.targets` raises it from
`_MTPBeforeVSTest`, guarded on nothing but the SDK major version and a variable named
`_SupportsGlobalJsonTestRunner` — so the property cannot suppress it, and the refusal is
conditioned on a *better* opt-in being available. The SDK agrees: `dotnet test --help` on
10.0.401 says to opt in "via global.json".

**What it actually took**, which was less than this entry predicted:

- `global.json` gains `"test": { "runner": "Microsoft.Testing.Platform" }`. Not `dotnet.config`,
which this entry guessed at; that is a different mechanism and not what this SDK reads.
- Three packages removed rather than bumped, all VSTest-only and inert under MTP:
`Microsoft.NET.Test.Sdk`, `xunit.runner.visualstudio`, `coverlet.collector`. Reasoning is in
`Directory.Packages.props`. Coverage, if anyone ever wants a number, is `coverlet.mtp` or
`Microsoft.Testing.Extensions.CodeCoverage`; nothing here has asked for one.
- `<OutputType>Exe</OutputType>` for test projects, which `Microsoft.NET.Test.Sdk` used to supply
implicitly. It is in `Directory.Build.props` conditioned on the project name, because
`IsTestProject` is set long after `.props` is evaluated and a condition on it never matches.
- **The filter rewrite was unnecessary.** This entry expected `--filter
"FullyQualifiedName~X"` to change in four places. xunit.v3 4.x ships `--filter` accepting
VSTest syntax, so CI, `CONTRIBUTING.md` and the corpus-audit instructions are unchanged and
still correct. The restriction is that a VSTest filter cannot be combined with xunit's own
`--filter-class` / `--filter-query` forms, which nothing here does.

**What we gained rather than paid for:** a run that executes no tests now exits 8 instead of
passing. A filter typo used to be a silent green — measured, not assumed:
`--filter "FullyQualifiedName~NoSuchTestNameAtAll"` exits 8, and the real suite exits 0.

Verified on 2026-09-29, against this commit: 363 tests — 362 pass, 1 skip (the corpus audit),
which is the pre-migration count of 362 plus the one performance test the old filter excluded.
`--locked-mode` restore clean; the performance gate still runs alone. The count is recorded as a
dated measurement rather than as a standing fact; the suite is the fact.

## Formats

Expand Down
3 changes: 3 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@
"sdk": {
"version": "10.0.400",
"rollForward": "latestFeature"
},
"test": {
"runner": "Microsoft.Testing.Platform"
}
}
2 changes: 1 addition & 1 deletion src/Docmd.Cli/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"docmd.word": {
"type": "Project",
"dependencies": {
"Ooxml.Md.Core": "[0.2.3, )",
"Ooxml.Md.Core": "[0.2.4, )",
"PhoenixmlDb.Xslt": "[2.4.1, )"
}
},
Expand Down
3 changes: 0 additions & 3 deletions tests/Docmd.Word.Tests/Docmd.Word.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@
<EnableNETAnalyzers>false</EnableNETAnalyzers>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio" />
<PackageReference Include="coverlet.collector" />
<PackageReference Include="FluentAssertions" />
<!-- The text-preservation oracle reads docmd's output with an independent Markdown
implementation, so a bug in our own serialiser cannot hide a bug in the transform.
Expand Down
Loading
Loading