Skip to content
Open
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: 4 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,10 @@ A **.NET CLI tool** (`cswinrtimplgen.exe`) published as a **Native AOT** binary.
2. Creates a new empty assembly (the "impl" assembly)
3. Copies well-known assembly attributes (version, debug info, etc.)
4. Emits `[TypeForwarder]` entries for all public top-level types, routing to the appropriate projection assembly
5. Optionally signs with a strong-name key
5. Synthesizes a portable PDB for the forwarder and embeds it (see "Forwarder debug information" below)
6. Optionally signs with a strong-name key

**Forwarder debug information**: the forwarder is emitted as metadata rather than compiled, so it has no symbols of its own, and there are none to carry over either (the input is compiled with `ProduceOnlyReferenceAssembly`, and a reference-only compilation emits no debug information at all). Since the forwarder is the assembly that ships in `lib/<tfm>` of a projection NuGet package, that gap makes the whole package report as having no symbols. The tool therefore synthesizes the debug information itself (`Writers/PortablePdbWriter.cs`, `Writers/DebugDirectoryWriter.cs`), emitting the same debug directory a deterministic build with embedded symbols produces — `CodeView`, `PdbChecksum`, `Reproducible`, and `EmbeddedPortablePdb`. The embedded portable PDB carries a single, embedded, generated document (`/_/<AssemblyName>.TypeForwards.g.cs`, written by `Writers/TypeForwardsDocumentWriter.cs`) listing every type forward in the assembly as the C# that would produce it, plus the compilation options and metadata references that tooling expects. Everything is derived from the forwarder itself, so the output stays byte-for-byte deterministic.

**Debug repro support**: when `--debug-repro-directory` is provided, captures the output assembly and all reference assemblies along with a faithful `.rsp` into a self-contained `impl-debug-repro.zip`. The tool also accepts a `.zip` as input and replays the captured run.

Expand Down
19 changes: 18 additions & 1 deletion .github/skills/testing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ public async Task InvalidType_Warns()
- `RestoreSources` overrides all inherited NuGet sources: the local CsWinRT build output (`CsWinRTPackageSource`) plus the `CsWinRTDependencies` feed (`CsWinRTDependenciesSource`), which provides the preview Windows SDK ref pack and `Microsoft.Windows.SDK.Contracts`
- `CsWinRTPackageVersion`/`CsWinRTPackageSource` default to the local `build.cmd x64 Release` output and are overridden by the build/CI that produced the package

**How they run:** `run-smoke-tests.ps1` (parameterized by `-Test` and `-Runtime`) builds and runs the consumption app (asserting a clean exit code), builds the authoring component and verifies the generated `Authoring.winmd` defines `Authoring.Greeter`, and builds each reference-projection library (`Projection`, `WindowsSdkProjection`, `WindowsSdkXamlProjection`) verifying it produces both a forwarder and a `ref` reference assembly (shared verification). The consumption and authoring tests run on both CoreCLR and Native AOT (`-Runtime`); the three reference-projection tests are build-only and run on CoreCLR only. It is invoked after the `nuget pack` step in `src/build.cmd` (x64 only; skippable via `cswinrt_run_smoke_tests=false`) and as individual steps in `build/AzurePipelineTemplates/CsWinRT-PublishToNuGet-Steps.yml`.
**How they run:** `run-smoke-tests.ps1` (parameterized by `-Test` and `-Runtime`) builds and runs the consumption app (asserting a clean exit code), builds the authoring component and verifies the generated `Authoring.winmd` defines `Authoring.Greeter`, and builds each reference-projection library (`Projection`, `WindowsSdkProjection`, `WindowsSdkXamlProjection`) verifying it produces both a forwarder and a `ref` reference assembly, and that the forwarder ships embedded symbols (shared verification). The consumption and authoring tests run on both CoreCLR and Native AOT (`-Runtime`); the three reference-projection tests are build-only and run on CoreCLR only. It is invoked after the `nuget pack` step in `src/build.cmd` (x64 only; skippable via `cswinrt_run_smoke_tests=false`) and as individual steps in `build/AzurePipelineTemplates/CsWinRT-PublishToNuGet-Steps.yml`.

### 7. WinMD generator tests (`src/Tests/WinMDGeneratorTest/`)

Expand Down Expand Up @@ -283,6 +283,22 @@ public void RefArrayParameter_IsRejected()
- Each test is a single `AssertSuccess`/`AssertFailure` call; the runner makes the exit-code and error-output assertions
- Failure cases assert the tool exits non-zero and its output contains the expected `CSWINRTWINMDGEN` error id

### 8. Impl generator tests (`src/Tests/ImplGeneratorTest/`)

**What it tests:** End-to-end behavior of the `cswinrtimplgen` build tool (the forwarder generator), focusing on the **PE-level shape of the forwarder assembly** it produces. That assembly ships in `lib/<tfm>` of a projection NuGet package, so what it carries (in particular its debug information) is directly observable by consumers and by NuGet package health checks.

**When to add tests here:** For anything about the forwarder assembly itself — its debug directory, embedded portable PDB, determinism, or type forwards. Reference-projection *codegen* is covered by `ProjectionWriterTest/`, and the full packaging layout by `SmokeTests/`.

**Project settings:** Same shape as `WinMDGeneratorTest/` above, referencing `WinRT.Impl.Generator` instead, with the tool path passed via the `ImplGeneratorAssemblyPath` `AssemblyMetadata` item.

**Test classes:**
| Test class | What it tests |
|------------|---------------|
| `Test_DebugDirectory` | The forwarder carries the debug directory of a deterministic build with embedded symbols (`CodeView`, `PdbChecksum`, `Reproducible`, `EmbeddedPortablePdb`), that those entries are mutually consistent, that the embedded PDB has one embedded `/_` prefixed document describing the type forwards plus valid compiler flags, and that two runs are byte identical |

**Test helper (in `Helpers/`):**
- `ImplGeneratorRunner` — compiles a C# input assembly (optionally with symbols), runs the actual tool as a subprocess, and exposes PE/PDB inspection helpers (`GetDebugDirectoryEntryTypes`, `GetEmbeddedPortablePdbDocumentNames`, `GetEmbeddedPortablePdbDocumentText`, `GetCompilationOptionValue`, `IsCodeViewEntryConsistent`, `IsPdbChecksumValid`, `RunTwice`).

## Deciding where to add tests

| You want to test... | Add test to... |
Expand All @@ -298,6 +314,7 @@ public void RefArrayParameter_IsRejected()
| XAML visual tree element lifetime | `ObjectLifetimeTests/` |
| WinRT component authoring patterns | `AuthoringTest/` |
| A WinMD generator failure mode (a `CSWINRTWINMDGEN` error) | `WinMDGeneratorTest/` (add to `Test_ParameterConventions` or `Test_InvalidInputs`) |
| The forwarder assembly's PE shape, symbols, or determinism | `ImplGeneratorTest/` (add to `Test_DebugDirectory`) |
| The produced NuGet package works end-to-end (real `ref`/`lib` assemblies, generators) | `SmokeTests/` (`Consumption/`, `Authoring/`, or a reference-projection project) |
| Generated projection code patterns or cross-ABI control flow | Update `TestComponentCSharp/` and add tests in `UnitTest/` or `FunctionalTests/` |

Expand Down
7 changes: 7 additions & 0 deletions build/AzurePipelineTemplates/CsWinRT-PublishToNuGet-Steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ steps:
continueOnError: true
inputs:
targetType: filePath
pwsh: true
filePath: $(Build.SourcesDirectory)\src\Tests\SmokeTests\run-smoke-tests.ps1
arguments: -PackageSource "$(ob_outputDirectory)\packages" -PackageVersion "$(NugetVersion)" -Test Consumption -Runtime CoreCLR
workingDirectory: $(Build.SourcesDirectory)
Expand All @@ -158,6 +159,7 @@ steps:
continueOnError: true
inputs:
targetType: filePath
pwsh: true
filePath: $(Build.SourcesDirectory)\src\Tests\SmokeTests\run-smoke-tests.ps1
arguments: -PackageSource "$(ob_outputDirectory)\packages" -PackageVersion "$(NugetVersion)" -Test Consumption -Runtime NativeAot
workingDirectory: $(Build.SourcesDirectory)
Expand All @@ -167,6 +169,7 @@ steps:
continueOnError: true
inputs:
targetType: filePath
pwsh: true
filePath: $(Build.SourcesDirectory)\src\Tests\SmokeTests\run-smoke-tests.ps1
arguments: -PackageSource "$(ob_outputDirectory)\packages" -PackageVersion "$(NugetVersion)" -Test Authoring -Runtime CoreCLR
workingDirectory: $(Build.SourcesDirectory)
Expand All @@ -176,6 +179,7 @@ steps:
continueOnError: true
inputs:
targetType: filePath
pwsh: true
filePath: $(Build.SourcesDirectory)\src\Tests\SmokeTests\run-smoke-tests.ps1
arguments: -PackageSource "$(ob_outputDirectory)\packages" -PackageVersion "$(NugetVersion)" -Test Authoring -Runtime NativeAot
workingDirectory: $(Build.SourcesDirectory)
Expand All @@ -185,6 +189,7 @@ steps:
continueOnError: true
inputs:
targetType: filePath
pwsh: true
filePath: $(Build.SourcesDirectory)\src\Tests\SmokeTests\run-smoke-tests.ps1
arguments: -PackageSource "$(ob_outputDirectory)\packages" -PackageVersion "$(NugetVersion)" -Test Projection -Runtime CoreCLR
workingDirectory: $(Build.SourcesDirectory)
Expand All @@ -194,6 +199,7 @@ steps:
continueOnError: true
inputs:
targetType: filePath
pwsh: true
filePath: $(Build.SourcesDirectory)\src\Tests\SmokeTests\run-smoke-tests.ps1
arguments: -PackageSource "$(ob_outputDirectory)\packages" -PackageVersion "$(NugetVersion)" -Test WindowsSdkProjection -Runtime CoreCLR
workingDirectory: $(Build.SourcesDirectory)
Expand All @@ -203,6 +209,7 @@ steps:
continueOnError: true
inputs:
targetType: filePath
pwsh: true
filePath: $(Build.SourcesDirectory)\src\Tests\SmokeTests\run-smoke-tests.ps1
arguments: -PackageSource "$(ob_outputDirectory)\packages" -PackageVersion "$(NugetVersion)" -Test WindowsSdkXamlProjection -Runtime CoreCLR
workingDirectory: $(Build.SourcesDirectory)
Expand Down
14 changes: 14 additions & 0 deletions build/AzurePipelineTemplates/CsWinRT-Test-Steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ steps:
--no-build
testRunTitle: Projection Writer Tests

# Run Impl Generator Tests. Gated to x64 only for the same reason as the other generator tests: these
# run the 'cswinrtimplgen' tool end-to-end as a separate process (which is always built for the build
# host), so the target platform makes no difference to what they cover.
- task: DotNetCoreCLI@2
displayName: Run Impl Generator Tests
condition: and(succeeded(), eq(variables['BuildPlatform'], 'x64'))
inputs:
command: test
projects: 'src/Tests/ImplGeneratorTest/ImplGeneratorTest.csproj'
arguments: >
/p:platform=$(BuildPlatform);configuration=$(BuildConfiguration)
--no-build
testRunTitle: Impl Generator Tests

# Run Host Tests
- task: CmdLine@2
displayName: Run Host Tests
Expand Down
Loading