Skip to content
Closed
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
50 changes: 50 additions & 0 deletions .agents/agents/test-author-writer.agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
name: Test Author Writer
description: "Specialist for Purview.SourceGeneratorFramework test suites — writing, fixing, and modernising TUnit tests for generators, diagnostic analyzers, code fixes, and refactorings, and for adding stage-by-stage incremental cache tests."
tools:
[
"search/codebase",
"edit/editFiles",
"search",
"execute/getTerminalOutput",
"execute/runInTerminal",
"read/terminalLastCommand",
"read/terminalSelection",
"execute/createAndRunTask",
"execute/runTask",
"read/getTaskOutput",
"vscodeTasks/createAndRunTask",
"vscodeTasks/getTaskOutput",
"vscodeTasks/runTask",
]
---

You are a specialist for `Purview.SourceGeneratorFramework` test authoring.

## Primary objective

Produce correct, maintainable TUnit tests for source generators, diagnostic analyzers, code fix
providers, and refactoring providers, and prove incremental pipelines cache correctly.

## Background knowledge

Before writing or changing any test, load and apply the `source-generator-testing` skill (runner layer,
result types, `CodeQuery`, options, cache testing) and the `tunit-test-authoring` skill (base classes,
methods, assertion extensions, modernisation checklist). For source-generator emission work, also load the
`source-generator-codewriter-modernization` skill.

Key rules:

- Pick the base class by the Roslyn component type: generator → `TUnitSourceGeneratorTestBase` +
`GenerateAsync`; analyzer → `TUnitDiagnosticAnalyzerTestBase` + `AnalyzeAsync`; code fix →
`TUnitCodeFixTestBase` + `ApplyCodeFixAsync`/`ApplyFixAllAsync`; refactor →
`TUnitRefactoringTestBase` + `RefactorAsync`.
- Prefer `CodeQuery` (`result.Generated()` / `result.FixedCode()` with `Get/Has/TryGet`) over
raw-string assertions.
- Prefer the terminal assertion extensions (`HasGeneratedMethod`, `HasGeneratedClass`, …) that return
syntax nodes.
- Derive a `SourceGeneratorTestOptions` record that seeds namespaces and additional assemblies.
- For incremental pipelines, add a stage-by-stage cache test with `RunIncrementalAsync` /
`GenerateIncrementalAsync`, asserting `New` on first run and `Cached`/`Unchanged` on an identical rerun,
and `Modified` only on the stages whose inputs changed.
- Keep generated-output assertions deterministic (no timestamps); enable CodeWriter scope validation.
48 changes: 48 additions & 0 deletions .agents/prompts/modernize-test-to-codequery-tunit.prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
agent: ask
description: "Modernise a Roslyn test suite to use CodeQuery + TUnit assertion extensions, and add a stage-by-stage incremental cache test."
---

You are modernising tests in this repository. Apply the guidance from the `source-generator-testing` and
`tunit-test-authoring` skills for picking the right base class, querying generated code with `CodeQuery`,
and asserting incremental caching.

## Inputs

- Target test file(s): `${input:targetFiles:Path(s) to test file(s)}`
- Roslyn component under test: `${input:componentType:generator|analyzer|codefix|refactor}` (inferred if blank)
- Generator/analyzer/code-fix/refactor type name: `${input:componentName:Component type name}`

## Task

Modernise each test so it uses the framework's `CodeQuery` syntax-lookup API and the TUnit assertion
extensions, and add a stage-by-stage cache test proving each incremental pipeline layer caches correctly.

### Requirements

1. Choose the correct base class and method for the component type:
- Generator → `TUnitSourceGeneratorTestBase<TGenerator>` → `GenerateAsync`.
- Analyzer → `TUnitDiagnosticAnalyzerTestBase<TAnalyzer>` → `AnalyzeAsync`.
- Code fix → `TUnitCodeFixTestBase<TAnalyzer, TCodeFix>` → `ApplyCodeFixAsync` / `ApplyFixAllAsync`.
- Refactor → `TUnitRefactoringTestBase<TRefactoring>` → `RefactorAsync`.
2. Replace `GetGeneratedTree(...)` + `string.Contains(...)` assertions with `CodeQuery`
(`result.Generated().Get/Has/TryGet…`) and the terminal assertion extensions
(`await Assert.That(result).HasGeneratedMethod/Class/Property/Field/SyntaxTree(…)`) that return the node.
3. Replace signature string checks with `TypeReference` parameter/return-type matching.
4. Ensure options come from a derived `SourceGeneratorTestOptions` record seeding the required namespaces
and additional assemblies; remove per-test duplication.
5. Add an incremental cache test using `RunIncrementalAsync` (or `GenerateIncrementalAsync` on the TUnit
base) with the four scenarios from the skills' "Incremental cache testing" sections
(`ServiceRegistrationCacheTests` / `IncrementalPipelineCacheTests` are the reference pattern):
- first run → every framework stage `New`;
- identical rerun (`RunIncrementalAsync(sources, …)` runs the same source twice) → framework stages
`Cached`/`Unchanged`;
- source-only change → `ForAttribute_*` `Modified`, property/config stages stay `Cached`;
- property-only change (`new IncrementalRunInput(sources, [("build_property.X", "value")])`) →
`GetMSBuildPropertyValue_*`/`GetGenerationConfiguration`/`GetGenerationContext_*` `Modified`,
`ForAttribute_*` stays `Cached`.
Use the `StepReasons(IncrementalCacheRun)` flattening helper; if the generator depends on its own
post-init output, assert on the framework-named stages rather than every tracked step.
6. Keep changes minimal and behavior equivalent; do not reformat unrelated tests.

Verify by building the test project and running its suite before finishing.
4 changes: 2 additions & 2 deletions .agents/skills/sdk-configuration-reference/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ These are the most important configurable properties exposed by the SDK:
- `NamespacePrefix` — required unless `DisableNamespacePrefixCheck=true`
- `DisableNamespacePrefixCheck` — default `false`
- `TargetFramework` — defaults to `net10.0` when neither `TargetFramework` nor `TargetFrameworks` is set; projects explicitly declaring `IsRoslynComponent=true` default to `netstandard2.0`
- `IsRoslynComponent` — when explicitly `true`, applies source-generator defaults: a single `netstandard2.0` target, extended analyzer rules, disabled SourceLink and untracked-source embedding, no dependency file, compiler-generated output under the framework-specific intermediate directory, `symbols.nupkg`, `PackSourceGeneratorSymbols`, telemetry exclusion, and excluded normal build output
- `IsRoslynComponent` — when explicitly `true`, applies source-generator defaults: a single `netstandard2.0` target, `LangVersion=latest`, `Nullable=enable`, `TreatWarningsAsErrors=true`, extended analyzer rules, disabled SourceLink and untracked-source embedding, no dependency file, compiler-generated output under the framework-specific intermediate directory, telemetry exclusion, excluded normal build output, `IncludeSymbols=false` (no `.symbols.nupkg`/`.snupkg` by default — the PDB ships in `analyzers/dotnet/cs/`), and `PrivateAssets=all` applied to `Microsoft.CodeAnalysis.*` / `Microsoft.CodeAnalysis.Analyzers` references. Packable Roslyn components automatically pack the built analyzer assembly and PDB into `analyzers/dotnet/cs/`
- `PackProjectReferencedSourceGenerators` — default `true`; packable projects automatically include analyzer `ProjectReference` outputs and runtime dependencies under `analyzers/dotnet/cs/`. Set it to `false` globally or use `Pack="false"` on one analyzer reference to opt out.
- `EnableAssemblyNameGeneration` — default `true`; when `true`, `AssemblyName` and default `PackageId` follow the fully evaluated `RootNamespace`. Set `false` before the SDK import to use the standard project-name behaviour
- `DisableProjectFileNamingConventionCheck` — default `false`; disables the directory-name/file-name match validation
Expand All @@ -60,7 +60,7 @@ These are the most important configurable properties exposed by the SDK:
- `ExcludeMSTelemetryExtension` — default `false`; removes `Microsoft.Extensions.Telemetry.Abstractions`, only relevant if `ExcludePurviewTelemetry` is also `true`
- `IsPackable` — defaults to `false` if not set elsewhere
- `PackageTags`, `IncludeSource`, `IncludeSymbols`, `PublishRepositoryUrl`, `SymbolPackageFormat` — standard pack-related settings the SDK participates in for packable projects
- Packable-project defaults (only applied when the consuming project has not supplied a value): `GenerateDocumentationFile=true`, `IncludeSymbols=true`, `SymbolPackageFormat=snupkg`, `PublishRepositoryUrl=true`, `EmbedUntrackedSources=true`, `DebugType=portable`. Portable PDBs are delivered through the `.snupkg`; the normal `.nupkg` does not receive PDB files unless the project opts in explicitly
- Packable-project defaults (only applied when the consuming project has not supplied a value): `GenerateDocumentationFile=true`, `IncludeSymbols=true`, `SymbolPackageFormat=snupkg`, `PublishRepositoryUrl=true`, `EmbedUntrackedSources=true`, `DebugType=portable`. Portable PDBs are delivered through the `.snupkg`; the normal `.nupkg` does not receive PDB files unless the project opts in explicitly. Roslyn components default `IncludeSymbols=false` and ship their PDB inside `analyzers/dotnet/cs/` instead
- If the repo root is discoverable, the repository-root `README.md` is packed automatically (and registered via `PackageReadmeFile`) when the file exists and `PackageReadmeFile` was not configured explicitly

## Test framework settings
Expand Down
7 changes: 7 additions & 0 deletions .agents/skills/sdk-project-behavior-and-detection/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ This is why consistent naming and placement matter so much in repos that use the
- Packs the repository-root `README.md` (registered via `PackageReadmeFile`) when the file exists and `PackageReadmeFile` is unset; skips when a README is already being packed
- Non-packable projects (including web apps) default `WarnOnPackingNonPackableProject=false` so solution-wide pack operations skip them silently

### For Roslyn component (analyzer/source-generator) projects

- Defaults a single `netstandard2.0` target, `LangVersion=latest`, `Nullable=enable`, `TreatWarningsAsErrors=true`, extended analyzer rules, disabled SourceLink, and excluded normal build output (`IncludeBuildOutput=false`)
- Defaults `IncludeSymbols=false` — no `.symbols.nupkg` or `.snupkg` is produced by default; the analyzer PDB ships inside the main `.nupkg` under `analyzers/dotnet/cs/` beside the analyzer assembly
- Packable Roslyn components automatically pack the built analyzer assembly (and PDB) into `analyzers/dotnet/cs/`; `SymbolPackageFormat` defaults to the modern `snupkg` if symbols are explicitly opted into
- `Microsoft.CodeAnalysis.*` and `Microsoft.CodeAnalysis.Analyzers` references are defaulted to `PrivateAssets=all` (development-only dependencies) so they never leak into the packed nuspec

### For test and shared-testing projects

- Applies test-friendly `NoWarn` defaults
Expand Down
8 changes: 8 additions & 0 deletions .agents/skills/source-generator-testing/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Ignore all files
*

# Don't ignore directories, so Git can traverse them
!*/

# Keep this file
!.gitignore
8 changes: 8 additions & 0 deletions .agents/skills/tunit-test-authoring/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Ignore all files
*

# Don't ignore directories, so Git can traverse them
!*/

# Keep this file
!.gitignore
11 changes: 0 additions & 11 deletions .changeset/README.md

This file was deleted.

14 changes: 0 additions & 14 deletions .changeset/config.json

This file was deleted.

39 changes: 39 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,42 @@ jobs:

- name: Run PR pipeline
run: dotnet run --project build/PipelineCLI/PipelineCLI.csproj --configuration Release

samples:
name: Build and test samples
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
fetch-tags: true

- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: "10.0.x"

- name: Build SampleApp
run: dotnet build samples/SampleApp/SampleApp.slnx --configuration Release

- name: Test SampleApp
run: dotnet test samples/SampleApp/SampleApp.slnx --configuration Release --no-build

samples-net48:
name: Build SampleApp.Net48
runs-on: windows-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
fetch-tags: true

- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: "10.0.x"

- name: Build SampleApp.Net48
run: dotnet build samples/SampleApp.Net48/SampleApp.Net48.slnx --configuration Release
30 changes: 25 additions & 5 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
Consumers on net48 or net8+ built with VS 2022 17.14+ or .NET 10+ SDK are fully supported. -->
<RoslynVersion>4.14.0</RoslynVersion>
<TUnitVersion>1.65.51</TUnitVersion>
<PurviewSGFVersion>1.0.0-prerelease.27</PurviewSGFVersion>
<ModularPipelinesVersion>[3.2.8,)</ModularPipelinesVersion>
<PurviewSGFVersion>1.0.0-prerelease.28</PurviewSGFVersion>
<ModularPipelinesVersion>3.2.8</ModularPipelinesVersion>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="ModularPipelines" Version="$(ModularPipelinesVersion)" />
Expand All @@ -28,13 +28,33 @@
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(RoslynVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="$(RoslynVersion)" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="5.9.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.10" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.10" />
<PackageVersion Include="Microsoft.Extensions.Telemetry.Abstractions" Version="10.8.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.Telemetry.Abstractions" Version="10.9.0" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.11" />
</ItemGroup>
<ItemGroup Label="Testing">
<PackageVersion Include="TUnit" Version="$(TUnitVersion)" />
<PackageVersion Include="TUnit.Mocks" Version="$(TUnitVersion)" />
<PackageVersion Include="Bogus" Version="35.6.5" />
</ItemGroup>
<ItemGroup Label="Samples">
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.9.0" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="10.9.0" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.18.0" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.18.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.18.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.GrpcNetClient" Version="1.11.0-beta.2" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.18.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Process" Version="1.11.0-beta.2" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.18.0" />
<PackageVersion Include="Scalar.AspNetCore" Version="2.17.2" />
<PackageVersion Include="Asp.Versioning.Mvc.ApiExplorer" Version="10.2.1" />
<PackageVersion Include="ErrorOr" Version="2.1.1" />
<!-- The Aspire AppHost SDK pulls MessagePack 2.5.192 transitively, which has known
advisories; pin the patched 2.5.302 so isolated AppHost builds stay clean. -->
<PackageVersion Include="MessagePack" Version="2.5.302" />
</ItemGroup>
</Project>
37 changes: 12 additions & 25 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,21 @@ pipeline-tests *args:
# Builds the solution with the specified configuration (default: Release)

[group('Build and Test')]
build:
build *args:
echo "Building {{ BLUE }}{{ solution_file }}{{ NORMAL }} with {{ YELLOW }}{{ build_configuration }}{{ NORMAL }}..."
dotnet build "{{ solution_file }}" --configuration "{{ build_configuration }}"
dotnet build "{{ solution_file }}" --configuration "{{ build_configuration }}" {{ args }}

# Runs tests for the solution with the specified configuration (default: Release)

[group('Build and Test')]
test:
test filter="/*/*/*/*/" *args:
echo "Running tests for {{ BLUE }}{{ test_solution }}{{ NORMAL }} with {{ YELLOW }}{{ build_configuration }}{{ NORMAL }}..."
dotnet test --solution "{{ test_solution }}" --configuration "{{ build_configuration }}"
dotnet test --solution "{{ test_solution }}" --configuration "{{ build_configuration }}" --treenode-filter={{ filter }} {{ args }}

# Cleans the solution with the specified configuration (default: Release)
[group('Build and Test')]
clean *args:
echo "Cleaning {{ BLUE }}{{ solution_file }}{{ NORMAL }} with {{ YELLOW }}{{ build_configuration }}{{ NORMAL }}..."
dotnet clean "{{ solution_file }}" --configuration "{{ build_configuration }}" {{ args }}

# Packs the source generator into a NuGet package

Expand Down Expand Up @@ -111,40 +116,22 @@ test-s:
# -----------------------------------------------------------------------------

# Formats the code in the root folder

format:
echo "Formatting {{ BLUE }}{{ root_folder }}{{ NORMAL }}..."
dotnet format "{{ root_folder }}"

# Checks for linting issues in the root folder
lint:
echo "Linting checking {{ BLUE }}{{ root_folder }}{{ NORMAL }}..."
dotnet csharpier check .

# Fixes linting issues in the root folder
lint-fix:
echo "Linting fixing {{ BLUE }}{{ root_folder }}{{ NORMAL }}..."
dotnet csharpier format .

# -----------------------------------------------------------------------------
# Versioning and Release
# -----------------------------------------------------------------------------

# Creates a new changeset to describe the changes in the current branch.
# Requires Bun.

[group('Versioning and Release')]
changeset:
bun changeset

# Shows pending changesets and the version bump they imply.
# Requires Bun.

[group('Versioning and Release')]
changeset-status:
bun changeset status

# Displays the current version of the project.
# Requires Bun.

[group('Versioning and Release')]
version:
bun -e "console.log('Current Version: {{ GREEN }}' + require('./package.json').version + '{{ NORMAL }}')"
Expand Down
3 changes: 2 additions & 1 deletion global.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"allowPrerelease": false
},
"msbuild-sdks": {
"Purview.DotNetProjectSdk": "1.0.0-prerelease.44"
"Purview.DotNetProjectSdk": "1.0.0-prerelease.45",
"Aspire.AppHost.Sdk": "13.2.0"
},
"test": {
"runner": "Microsoft.Testing.Platform"
Expand Down
Loading
Loading