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
38 changes: 24 additions & 14 deletions docs/wiki/Assembly-Name-Generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,18 @@ Certain suffixes are automatically stripped from `RootNamespace` to avoid awkwar
`Acme.MyProject.Core.Something`.

The built-in stripping set is broader than the common examples and includes:
`SourceGeneration`, `SourceGenerators`, `SourceGenerator`, `ServiceDefaults`, `Infrastructure`,
`Abstractions`, `ClientShared`, `Persistence`, `CodeFixers`, `CodeFixes`, `DataAccess`, `Extensions`,
`Generators`, `Analyzers`, `Contracts`, `Framework`, `Utilities`, `AppHost`, `Helpers`, `Library`,
`Common`, `Shared`, `Infra`, `Utils`, `Core`, `Data`, `Host`, `Util`, `Lib`, `EF`, plus the known
shared and shared-testing project names (`Shared*`, `SharedTesting*`).
`ServiceDefaults`, `Infrastructure`, `Abstractions`, `ClientShared`, `Persistence`, `DataAccess`,
`Extensions`, `Contracts`, `Framework`, `Utilities`, `AppHost`, `Helpers`, `Library`, `Common`,
`Shared`, `Infra`, `Utils`, `Core`, `Data`, `Host`, `Util`, `Lib`, `EF`, plus the known shared and
shared-testing project names (`Shared*`, `SharedTesting*`).

**Roslyn component names are deliberately absent.** `SourceGenerator`, `SourceGenerators`,
`SourceGeneration`, `Generators`, `Analyzers`, `CodeFixers` and `CodeFixes` are part of a component's
identity rather than noise: a generator, its analyzers and its code fixes are separate assemblies that
each need their own namespace. Stripping them collapses that distinction and collides with the namespace
the sources actually declare, which produced `IDE0130` across every Roslyn-component repository
consuming this SDK. Add one back per project with `<NamespaceRemoveSuffix Include="Analyzers" />` if a
repository really does want it collapsed.

### Keeping suffixes

Expand All @@ -58,18 +65,21 @@ the items are declared there and only exist to be removed once the import has ev
<Import Sdk="Purview.BuildSdk" Project="Sdk.props" />

<ItemGroup>
<!-- Keep the full SourceGenerators, CodeFixers and CodeFixes namespaces. -->
<NamespaceRemoveSuffix Remove="SourceGenerators" />
<NamespaceRemoveSuffix Remove="CodeFixers" />
<NamespaceRemoveSuffix Remove="CodeFixes" />
<!-- Keep the full Abstractions and Contracts namespaces. -->
<NamespaceRemoveSuffix Remove="Abstractions" />
<NamespaceRemoveSuffix Remove="Contracts" />
</ItemGroup>
```

This is useful for analyser/source-generator repos that deliberately name projects after the role — for
example `Purview.ZodSharp.SourceGenerators`, `Purview.ZodSharp.CodeFixers`, and `Purview.ZodSharp.CodeFixes` —
and want those roles to survive in the namespace rather than collapse into the parent `Purview.ZodSharp`
namespace. Only suffixes
present in the built-in list need removing; a suffix the SDK does not ship is already kept as-is.
This is useful where the layer suffix is genuinely part of the public shape — a published
`Acme.Payments.Abstractions` package whose consumers expect to `using Acme.Payments.Abstractions;`,
rather than finding its interfaces in `Acme.Payments`.

**Only suffixes present in the built-in list need removing**; a suffix the SDK does not ship is already
kept as-is. That is why an analyser or source-generator repository that names projects after the role —
`Purview.ZodSharp.SourceGenerators`, `Purview.ZodSharp.CodeFixers`, `Purview.ZodSharp.CodeFixes` — needs
no `Remove` at all. Those roles survive in the namespace by default, and a `Remove` for them is a
harmless no-op rather than the thing keeping them.

See [Project Naming Conventions](Project-Naming-Conventions.md) and
[InternalsVisibleTo](InternalsVisibleTo.md) for related naming behaviour.
50 changes: 26 additions & 24 deletions docs/wiki/Engineering-Principles.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,32 +179,31 @@ Recommended common test project types:

- `UnitTests`
- `IntegrationTests`
- `E2ETests`
- `E2ETests` (or `EndToEndTests` — both are recognised, and give `Category=E2E` and `Category=EndToEnd`
respectively, so pick one per repository rather than mixing them)
- `FunctionalTests`
- `ContractTests`

The SDK also recognises broader suffixes when a repository genuinely needs them, including:

- `AcceptanceTests`
- `PerformanceTests`
- `LoadTests`
- `SmokeTests`
- `StressTests`
- `RegressionTests`
- `SecurityTests`
- `ScenarioTests`
- `SystemTests`
- `ArchitectureTests`
- `AccessibilityTests`
- `InteractiveTests`
- `EnvironmentTests`
- `WhiteBoxTests`
- `BlackBoxTests`
- `ChaosTests`
- `ThreatTests`

Prefer the smaller common set by default. Reach for the broader set only when the test type itself carries
important operational meaning.
A broader set is available when a repository genuinely needs it, grouped by what the suffix tells a
reader:

| Theme | Suffixes |
| -- | -- |
| Scope and style | `AcceptanceTests`, `ComponentTests`, `ScenarioTests`, `SystemTests`, `SanityTests`, `BlackBoxTests`, `WhiteBoxTests`, `GrayBoxTests`, `GreyBoxTests` |
| Performance and endurance | `PerformanceTests`, `LoadTests`, `StressTests`, `SoakTests`, `SpikeTests`, `CapacityTests`, `EnduranceTests`, `BenchmarkTests`, `ConcurrencyTests` |
| Resilience and operations | `ChaosTests`, `ResilienceTests`, `FailoverTests`, `RecoveryTests`, `DeploymentTests`, `EnvironmentTests`, `ConfigurationTests`, `SmokeTests` |
| Correctness techniques | `PropertyTests`, `FuzzTests`, `MutationTests`, `SnapshotTests`, `ApprovalTests`, `RegressionTests`, `ArchitectureTests` |
| Compatibility and change | `CompatibilityTests`, `InteroperabilityTests`, `MigrationTests`, `UpgradeTests` |
| Security and compliance | `SecurityTests`, `PenetrationTests`, `ThreatTests`, `ComplianceTests` |
| Experience and reach | `AccessibilityTests`, `UsabilityTests`, `VisualTests`, `InteractiveTests`, `LocalizationTests`, `GlobalizationTests` |

Prefer the smaller common set by default. Reach for a broader suffix only when the test type itself
carries important operational meaning — the suffix becomes the category a pipeline filters on, so it
should name something a pipeline would plausibly want to select or exclude.

Detection is a pattern rather than a lookup, so a suffix outside this set is still detected and still
categorised; what the set governs is `InternalsVisibleTo` generation. See
[Project Type Detection](Project-Type-Detection.md#detection-flags).

## Default and specialised test wiring

Expand All @@ -220,7 +219,10 @@ Shared testing projects receive test-support wiring rather than a runnable test
Specialised dependencies stay explicit and intentional:

- `TUnit.Aspire` for Aspire lifecycle or AppHost-backed integration tests
- `Testcontainers` for container-backed integration tests
- `Purview.Containers` for container-backed integration tests — one API over Microsoft WSL Containers
(no Docker installation on Windows) or Docker, with typed modules for SQL Server, PostgreSQL, Redis,
MySQL, RabbitMQ, Azurite and NATS. It is experimental and prerelease, so pin the version you adopt.
Reference `Testcontainers` directly only where a module does not exist yet

Do not manually duplicate the default stack in every test project unless the repository has explicitly opted
out of the SDK defaults.
Expand Down
20 changes: 15 additions & 5 deletions docs/wiki/Project-Naming-Conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,24 @@ MyProject.FunctionalTests → IsTestProject=true, TestingType=Functional
MyProject.ContractTests → IsTestProject=true, TestingType=Contract
```

Any suffix from the full list is recognised: `Unit`, `Integration`, `E2E`, `EndToEnd`, `Acceptance`,
`Functional`, `Performance`, `Load`, `Smoke`, `Stress`, `Regression`, `Security`, `Chaos`, `Scenario`,
`System`, `Threat`, `BlackBox`, `WhiteBox`, `Accessibility`, `Interactive`, `Environment`,
`Architecture`, `Contract`.

The detected test type becomes the baseline category for the project. Additional categories can still be
added when they improve discoverability or filtering.

Detection itself is a **pattern, not a lookup**: the project name is matched against
`^.*\.([\w|_]\w*)Tests?$` and the captured word becomes `TestingType`. So `MyProject.GoldenTests` is
detected exactly as readily as `MyProject.UnitTests`, and gets `TestingType=Golden` and
`Category=Golden`.

> **The name needs a target in front of it.** The pattern requires the dot, so a project named plainly
> `UnitTests` matches nothing: it is not detected as a test project at all, and gets no runner, no
> coverage and no category. Name it `<Target>.UnitTests`.

What the SDK *does* ship as an explicit list is the `TestType` item group, and it governs
[InternalsVisibleTo](InternalsVisibleTo.md) generation rather than detection — a non-test project grants
access to `{AssemblyName}.{TestType}Tests` for each name on it. A suffix outside that list still builds,
still runs and still gets its category; it just cannot reach the target project's `internal` members.
The full set is listed in [Project Type Detection](Project-Type-Detection.md#detection-flags).

## Shared testing projects

Projects named `SharedTestingFramework`, `SharedTestingInfrastructure`, `SharedTestingInfra`,
Expand Down
14 changes: 12 additions & 2 deletions docs/wiki/Project-Type-Detection.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ wiki.
| Flag | Condition |
| -- | -- |
| `IsCSharpProject` | Project file extension is `.csproj`. |
| `IsTestProject` | Project name ends with `Test`/`Tests` and carries a recognised `TestingType` suffix. |
| `IsTestProject` | Project name matches `^.*\.([\w|_]\w*)Tests?$` — a target name, a dot, then any word before `Test`/`Tests`. The captured word becomes `TestingType`. |
| `IsSharedTestingProject` | Project name is one of `SharedTestingFramework`, `SharedTestingInfrastructure`, `SharedTestingInfra`, `SharedTestingUtilities`, `SharedTestingUtils`, `SharedTestingLibrary`, `SharedTestingLib`, `SharedTestingHelpers`. |
| `IsSharedProject` | Project name is one of `Shared`, `SharedFramework`, `SharedInfrastructure`, `SharedInfra`, `SharedUtilities`, `SharedUtils`, `SharedLibrary`, `SharedLib`, `SharedHelpers`. |
| `IsContainerProject` | A `Dockerfile`, `dockerfile`, or `Dockerfile.dev` exists in the project directory. |
Expand All @@ -30,7 +30,17 @@ wiki.
targets. The detected `TestingType` also becomes the baseline test category, while additional categories
remain available to the test suite.

The SDK recognises the following full `TestType` set, in addition to the standard `Unit` and
Detection is a **pattern, not a lookup against the set below**. A project named `MyProject.GoldenTests`
is detected, and gets `TestingType=Golden` and `Category=Golden`, even though `Golden` appears nowhere in
the SDK. The pattern requires the dot, so a project named plainly `UnitTests` is **not** detected as a
test project at all — it gets no runner, no coverage and no category.

The `TestType` item group below governs [InternalsVisibleTo](InternalsVisibleTo.md) generation instead: a
non-test project grants access to `{AssemblyName}.{TestType}Tests` for each name on it. A suffix outside
the set still builds, still runs and still gets its category — it just cannot reach the target project's
`internal` members.

The SDK ships the following full `TestType` set, in addition to the standard `Unit` and
`Integration` values:
`Acceptance`, `Accessibility`, `Approval`, `Architecture`, `Benchmark`, `BlackBox`, `Capacity`,
`Chaos`, `Compatibility`, `Compliance`, `Component`, `Concurrency`, `Configuration`, `Contract`,
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "purview-build-sdk",
"version": "1.0.4",
"name": "build-sdk",
"version": "1.0.5",
"homepage": "https://purview.dev/projects/build-sdk/",
"bugs": {
"url": "https://github.com/purview-dev/build-sdk/issues"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ Help a repository move toward `Purview.BuildSdk` conventions without unnecessary
5. Prefer the smallest sequence of changes that improves predictability without forcing broad renames.
6. When recommending test changes, preserve readable behavior-oriented suites while tightening subject-based
suites toward `{SubjectName}Tests` and `{SubjectOrMemberUnderTest}_{Scenario}_{Expectation}`.
7. Confirm whether specialized dependencies such as `TUnit.Aspire` or `Testcontainers` are genuinely needed
rather than treating them as universal defaults.
7. Confirm whether specialized dependencies such as `TUnit.Aspire` or `Purview.Containers` are genuinely
needed rather than treating them as universal defaults. Where a repository references `Testcontainers`
directly, check whether a `Purview.Containers` typed module now covers it.

## Constraints

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ If specialized test categories exist (for example, analyzer diagnostics vs code-
The detected test type also becomes the baseline test category. Additional categories remain available and
should be added when they improve discoverability.

The SDK recognizes many test suffixes, including `Unit`, `Integration`, `E2E`, `EndToEnd`, `Acceptance`, `Functional`, `Performance`, `Load`, `Smoke`, `Stress`, `Regression`, `Security`, `Chaos`, `Scenario`, `System`, `Threat`, `BlackBox`, `WhiteBox`, `Accessibility`, `Interactive`, `Environment`, `Architecture`, and `Contract`.
Test detection is a pattern (`^.*\.([\w|_]\w*)Tests?$`), not a lookup against a fixed list of suffixes, so any word before `Test`/`Tests` is detected and becomes the category — `Unit`, `Integration`, `E2E` and `Contract` are simply the common ones. The dot is required: a project named plainly `UnitTests`, with no target in front of it, is not detected as a test project at all. Prefer a suffix the SDK ships (it is the set that drives `InternalsVisibleTo` generation) and name the project `<Target>.<Type>Tests`.

## Naming and namespace defaults

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ Default outcome for standard test projects:
- `Bogus`
- Microsoft.Testing.Platform integration

Specialised packages such as `TUnit.Aspire` and `Testcontainers` are not automatic defaults; they remain
explicit choices based on the project's purpose.
Specialised packages such as `TUnit.Aspire` and `Purview.Containers` are not automatic defaults; they
remain explicit choices based on the project's purpose. `Purview.Containers` is the container-backed
integration-test dependency; reference `Testcontainers` directly only where no typed module exists yet.

Related toggles and derived settings:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ Standard test projects receive these by default:
Specialized additions remain explicit:

- `TUnit.Aspire` for Aspire lifecycle/AppHost-backed integration tests
- `Testcontainers` for container-backed integration tests
- `Purview.Containers` for container-backed integration tests — one API over Microsoft WSL Containers (no
Docker installation on Windows) or Docker, with typed modules for SQL Server, PostgreSQL, Redis, MySQL,
RabbitMQ, Azurite and NATS. Experimental and prerelease, so pin the version. Reference `Testcontainers`
directly only where a module does not exist yet

## Test readability rules

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,13 @@ The SDK infers behavior from project names, project contents, and SDK declaratio

### Test detection

A project is treated as a test project when its name ends with `*Test` or `*Tests` and the suffix before `Test(s)` matches a supported testing type such as:

- `Unit`
- `Integration`
- `E2E`
- `EndToEnd`
- `Acceptance`
- `Functional`
- `Performance`
- `Load`
- `Smoke`
- `Stress`
- `Regression`
- `Security`
- `Chaos`
- `Scenario`
- `System`
- `Threat`
- `BlackBox`
- `WhiteBox`
- `Accessibility`
- `Interactive`
- `Environment`
- `Architecture`
- `Contract`
A project is treated as a test project when its name matches `^.*\.([\w|_]\w*)Tests?$` — a target name, a dot, then any word before `Test` or `Tests`. The captured word becomes `TestingType`.

This is a pattern, **not** a lookup against a fixed list. `MyProject.GoldenTests` is detected just as readily as `MyProject.UnitTests`, and gets `TestingType=Golden` and `Category=Golden`, even though `Golden` is not a name the SDK ships.

The dot is required. A project named plainly `UnitTests`, with no target in front of it, does **not** match: it is not detected as a test project at all, and silently gets no runner, no coverage, no `InternalsVisibleTo` and no category. Always name a test project `<Target>.<Type>Tests`.

The SDK does ship an explicit `TestType` item group of 53 names (`Unit`, `Integration`, `E2E`, `EndToEnd`, `Acceptance`, `Functional`, `Performance`, `Load`, `Smoke`, `Stress`, `Regression`, `Security`, `Chaos`, `Scenario`, `System`, `Threat`, `BlackBox`, `WhiteBox`, `GrayBox`, `GreyBox`, `Accessibility`, `Usability`, `Visual`, `Interactive`, `Localization`, `Globalization`, `Environment`, `Configuration`, `Deployment`, `Architecture`, `Contract`, `Component`, `Sanity`, `Property`, `Fuzz`, `Mutation`, `Snapshot`, `Approval`, `Benchmark`, `Capacity`, `Concurrency`, `Endurance`, `Soak`, `Spike`, `Failover`, `Recovery`, `Resilience`, `Compatibility`, `Interoperability`, `Migration`, `Upgrade`, `Penetration`, `Compliance`), but it governs **`InternalsVisibleTo` generation**, not detection: a non-test project grants access to `{AssemblyName}.{TestType}Tests` for each name on it. A suffix outside the group still builds, still runs and still gets its category — it just cannot reach the target project's `internal` members.

Derived properties:

Expand Down Expand Up @@ -170,8 +152,9 @@ For the default configuration, standard test projects receive:
- `Bogus`
- Microsoft.Testing.Platform integration

Specialized testing dependencies such as `TUnit.Aspire` and `Testcontainers` are still explicit additions by
project purpose.
Specialized testing dependencies such as `TUnit.Aspire` and `Purview.Containers` are still explicit
additions by project purpose. `Purview.Containers` is the container-backed integration-test dependency;
reference `Testcontainers` directly only where no typed module exists yet.

### For special project types

Expand Down
Loading