Skip to content

chore(ai): put test categories on the test classes only - #32

Closed
xperiandri wants to merge 26 commits into
mainfrom
chore/test-category-instructions
Closed

xperiandri wants to merge 26 commits into
mainfrom
chore/test-category-instructions

Conversation

@xperiandri

Copy link
Copy Markdown
Collaborator

Proposed Changes

The agent instructions contradicted each other on test categories:

  • .github/copilot-instructions.md, Testing section: asked for an operation category on every test method through [<TestMethod; TestCategory(nameof Queue.enqueue)>], with specialized attributes added next to it on the method.
  • .github/instructions/test-categories.instructions.md, path-scoped: forbids a category written as a string in [<TestCategory "...">]. nameof produces such a string. These rules put the attribute of the operation or component that a class examines on the class.

Changes:

  • Testing section: it no longer has its own category bullets. The path-scoped rules are the only source.
  • Path-scoped rules: the first rule now also says to declare each test category as a TestCategoryBaseAttribute descendant.

The rule after this change: a test class carries TestCategoryBaseAttribute descendants for the collection and for the component or area that it examines, such as [<TestClass; FlatListTestCategory; BuilderTestCategory>]. Test methods carry no categories.

Types of changes

What types of changes does your code introduce to FSharp.Collections.Immutable?
Put an x in the boxes that apply

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Agent instructions only; no code changes.

Checklist

Put an x in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code.

  • Build and tests pass locally (not applicable – two Markdown files)
  • I have added tests that prove my fix is effective or that my feature works (not applicable)
  • I have added necessary documentation (if appropriate)

Further comments

The tests on main carry no categories yet. The ADR 0001 pull requests (#25 onwards) still put TestCategory (nameof …) on their test methods and check it in TestCategoriesTests. They will follow this rule: categories on the classes only, and the convention test checks the classes.

🤖 Generated with Claude Code

xperiandri and others added 26 commits May 24, 2026 21:19
* Significantly restructured `copilot-instructions.md` to provide detailed project info, solution structure, library usage, and comprehensive F# coding standards.
* Added explicit guidance on language features, class instantiation, extension members, naming, and testing.
* Emphasized maintainability, code quality, and directory layout.
Update `Microsoft.SourceLink.GitHub` to `10.0.402` together with the SDK it ships with.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nfig

Batch files are checked out with CRLF and PowerShell scripts with LF on every platform,
and `.editorconfig` states the same instead of asking for CRLF in PowerShell scripts.

The inline comments of `.editorconfig` move onto their own lines:
the specification does not support inline comments, so they were read as part of the value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`.editorconfig` follows the settings of Fantomas 8:

- `fsharp_keep_indent_in_branch` became `fsharp_experimental_keep_indent_in_branch`;
  under its old name the setting was ignored.
- `fsharp_semicolon_at_end_of_line` and `fsharp_max_dot_get_expression_width` no longer exist.
- `end_of_line=lf` matches the line endings `.gitattributes` stores F# files with,
  so formatting on Windows no longer rewrites them as CRLF.

The comments state the current defaults and link to the current documentation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fantomas 8 no longer puts a space between a type application and its argument list:
`Create<'T> ()` becomes `Create<'T>()`. Whitespace only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
`ToolRestore` ran only in design-time builds, so a command-line build relied on the FAKE
`DotnetToolRestore` target, and that target was a dependency of `BuildDocs` alone:
on CI `CheckFormatCode` found no Fantomas and passed without checking anything.
`ToolRestore` now runs before every restore, and the FAKE target is gone.

`ToolUpdate` is removed: its `Outputs` used an undefined property, so MSBuild always skipped it.

Formatting is delegated to one instance of a project without a target framework,
so a project is formatted once per build however many target frameworks it has,
and Fantomas runs twice because one pass is not always idempotent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FAKE `6.2.0` adds `DotNet.testMTP`, which runs `dotnet test` with the MSTest runner and takes the project,
the configuration, the results directory and the code coverage as typed options.
It also contains `6.1.5`: the last entry of the changelog keeps its date and description,
and the message of a release commit keeps its quotes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- `Microsoft.NET.Test.Sdk` `18.5.1` -> `18.11.0`
- `Microsoft.Build.Framework` `18.6.3` -> `18.11.0`
- `System.Collections.Immutable` `10.0.8` -> `10.0.13`
- `System.Drawing.Common` and `System.Formats.Asn1` `10.0.8` -> `10.0.13`

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tCover leftovers

`DotnetTest` passed `/p:AltCover*` properties although no project references AltCover,
and `GenerateCoverageReport` looked for `tests/**/coverage*.xml`, which nothing produced,
so `ENABLE_COVERAGE` collected no coverage.

`DotnetTest` now runs `DotNet.testMTP` for each test project and lets the MSTest runner collect coverage
in the Cobertura format into `TestResults`, where `GenerateCoverageReport` reads it and writes HTML and Cobertura reports.
Coverage is collected when `ENABLE_COVERAGE` is set or `GenerateCoverageReport` is among the targets.

Also:

- the release commit is named `release: bump version to ...`;
- `quoted` is shared by the documentation target;
- the workflows no longer explain `ENABLE_COVERAGE: false` with AltCover.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
`src/Directory.Build.props` holds what every library project shares, next to the
`tests/Directory.Build.props` the test projects already have: it marks the project as packable,
generates the documentation file, publishes the repository URL, embeds untracked sources
and puts the PDB into the package next to the assembly.

The solution groups the projects into `src` and `tests` folders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y has no date

`GenerateAssemblyInfo` read `latestEntry.Date.Value` and failed with a `NullReferenceException`
for an entry without a date. The date is optional in a changelog entry,
so the `ReleaseDate` metadata is now written only when the entry has one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ersion

The package described itself as an F# API for Azure Cosmos DB and carried the
`AzureCosmosDisableNewtonsoftJsonCheck` property, both left over from the project file
this one was copied from. The description is `F# bindings for System.Collections.Immutable` again.

`GenerateAssemblyInfo` is switched off for the SDK because the FAKE target of the same name
writes `AssemblyInfo.fs`, but the project did not compile that file,
so the assembly had version `0.0.0.0`. The generated file is now the first compiled file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FsLangMCP gives agents semantic F# tools: symbol lookup, diagnostics, usages and refactoring previews.
It is a local tool pinned in the tool manifest, so `dotnet tool restore` installs it,
and `.mcp.json` starts it with `dotnet tool run fslangmcp`.

The servers in `.mcp.json` are sorted by name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `CLAUDE.md` imports `.github/copilot-instructions.md`, so GitHub Copilot and Claude Code share one
  set of instructions; `AGENTS.md` points other agents at the same file.
- `.mcp.json` lists the servers for Claude Code under `mcpServers` next to `servers`.
- The GitHub MCP server does not support the OAuth dynamic client registration Claude Code needs,
  so `.claude/scripts/github-mcp-headers.ps1` passes it the token Git Credential Manager already stores.
- `ClaudeSkillsLink` links `.claude/skills` to `.github/skills` when that folder exists;
  the link is local and git-ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The generic sections are replaced with the current shared ones:

- `MCP Servers` – when to prefer the F#, GitHub and Microsoft Docs servers;
- `F# Coding Guidelines` – tooling, asynchrony and cancellation, values and collections
  (`voption`, struct tuples, `ToLookup`, pipelines kept in one module, comparing `System.Type` values),
  functions, lambdas and strings (explicit `StringComparison`, spans), nullable reference types,
  XML documentation comments and the order of `open` statements;
- `Naming Conventions` – test names with spaces;
- `Testing` – `StringAssert`, `Assert.Contains`, `Assert.HasCount` and running tests with `--no-build`.

The project details, the solution structure, the libraries in use and the test category rules
are specific to this repository and stay as they are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GitHub Copilot applies `.github/instructions/test-categories.instructions.md`
to `tests/**/*.fs`, and `.claude/rules/test-categories.md` imports it for
Claude Code under the same path. The instructions say:

- No category is written as a string in `[<TestCategory "...">]`.
- Each attribute gives one category.
- Each attribute name has the `TestCategoryAttribute` suffix.
- The category is the name without the suffix, written as separate
  words; a product name or a type name keeps its spelling.
- Each test class has the attribute of the operation or component that
  it examines.
- The attributes of each `TestCategories.fs` file are sorted
  alphabetically by type name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `actions/configure-pages` `v5` -> `v6`
- `actions/upload-pages-artifact` `v4` -> `v5`
- `actions/deploy-pages` `v4` -> `v5`

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dev container installs the starship feature and enables it in zsh when the container is created.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e its sibling keys

`target` was indented by five spaces while the other safe outputs indent their keys by four.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
README:

- the required SDK is the `10.0.401` pinned in `global.json`, not `8.0 or higher`;
- `CONFIGURATION` defaults to `Debug`, and to `Release` only when `PublishToNuGet` runs;
- `ENABLE_COVERAGE` and `DotnetTest` describe the MSTest runner instead of AltCover and the solution file;
- `PublishToNuGet` pushes with `dotnet nuget push`, not `paket push`;
- the release commit is named ``release: bump version to `x.y.z` ``;
- the badge of the `.NET Core` workflow, which does not exist, is removed,
  and the NuGet badge is loaded over HTTPS.

The pull request template names this project instead of the one it was copied from.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the assembly info

Since FAKE `6.1.5` the changelog parser keeps the date and the description of the last entry of the file,
which `6.1.4` dropped. `1.0.0` is the only entry here, so the regenerated `AssemblyInfo.fs` carries
its `ReleaseDate` metadata again, and the comment on the date check no longer refers to the defect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A solution restores its projects in parallel, so `ToolRestore` ran `dotnet tool restore` in every project
at the same time and all of them touched the same marker file. The `Restore dependencies` step of
the Copilot setup, which restores the solution, failed on Linux after `ToolRestore` started to run
on every restore.

Every project now delegates to one instance of `Directory.Build.targets`, which MSBuild builds once per build,
so the tools are restored once and the other projects wait for that run.
The Copilot setup no longer restores the tools on its own before restoring the solution.

`Format` says why it runs Fantomas twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Testing section asked for an operation category on every test method through
`TestCategory (nameof …)`. That contradicts the path-scoped rules of
`.github/instructions/test-categories.instructions.md`, which forbid a category written
as a string in `[<TestCategory "...">]` and put the attribute of the examined operation
or component on the test class. The Testing section no longer repeats category rules,
and the path-scoped rules now also say that each category is declared as a
`TestCategoryBaseAttribute` descendant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 18, 2026 12:24
xperiandri added a commit that referenced this pull request Oct 18, 2026
D8 put an operation category on every test method through `TestCategory (nameof …)`.
The test category rules forbid categories written as strings and put the attribute of the
examined component on the class (#32), so the categories of the contract and
collection tests are the `TestCategories.fs` attributes on the test classes, and test
methods carry none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The documentation-only changes consistently clarify the category rules and have no unresolved findings.

No open issues 🎉

What changed in this PR

Resolves conflicting test-category guidance by making the path-scoped instructions the single source for class-level categories.

Changes:

  • Removes method-level category requirements from the general instructions.
  • Explicitly requires custom categories derived from TestCategoryBaseAttribute.
File Description
.github/​instructions/​test-categories.instructions.md Clarifies the required category base type.
.github/​copilot-instructions.md Removes conflicting category guidance.

Review effort: Balanced

@xperiandri

Copy link
Copy Markdown
Collaborator Author

Folded into main instead, in the commits that introduced the text:

  • the Testing section change went into chore(ai): expand and restructure Copilot instructions;
  • the path-scoped rule went into chore(ai): add path-scoped instructions for test categories.

The tree of main (17f93b7) is now identical to this branch, so this pull request is no longer needed.

@xperiandri xperiandri closed this Oct 18, 2026
xperiandri added a commit that referenced this pull request Oct 18, 2026
D8 put an operation category on every test method through `TestCategory (nameof …)`.
The test category rules forbid categories written as strings and put the attribute of the
examined component on the class (#32), so the categories of the contract and
collection tests are the `TestCategories.fs` attributes on the test classes, and test
methods carry none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants