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
193 changes: 0 additions & 193 deletions .github/copilot-instructions.md

This file was deleted.

50 changes: 50 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Testably.Abstractions Agent Guide

Testably.Abstractions provides injectable abstractions for the static parts of the .NET base class library (file system, time, randomness) and in-memory mocks that behave like the real implementations. Read this file first; open the docs under [References](#references) only when a task needs them.

## Setup and build

- The SDK comes from `global.json` (.NET 10, `rollForward: latestMinor`), which also selects `Microsoft.Testing.Platform` as the `dotnet test` runner. CI installs the 6.0, 8.0, 9.0 and 10.0 runtimes; local test runs need the runtime of each framework you target.
- Build through the solution: `dotnet build Testably.Abstractions.slnx`. Source projects pack on build and resolve their package readmes via `$(SolutionDir)`, so `dotnet build` on a single source project fails in the pack step.
- Source projects target `net6.0;net8.0;net9.0;net10.0;netstandard2.1;netstandard2.0` (`Source/Directory.Build.props`), tests target `net8.0;net9.0;net10.0;net48` (`Tests/Directory.Build.props`). `-p:NetCoreOnly=true` drops `net48` from the tests; `-p:NetFrameworkOnly=true` reduces sources to `netstandard2.0` and tests to `net48`.
- API differences between frameworks are expressed with `FEATURE_*` constants defined in `Feature.Flags.props` (plus `NETFRAMEWORK` and `CAN_SIMULATE_OTHER_OS`). For a newly mirrored API, prefer adding a flag there over raw `NETx_0_OR_GREATER` checks.
- Package versions are central in `Directory.Packages.props`. Sources build with `TreatWarningsAsErrors`, nullable enabled and Meziantou.Analyzer.
- The build pipeline is `./build.ps1 <Target>` / `./build.sh <Target>` (a Fallout project in `Pipeline/`). GitVersion needs the full git history, so do not work in a shallow clone.

## Design goals

Correctness is a must. The other goals are strong defaults and win in this order when they conflict: stable and compatible, AOT and trimming friendly, a consistent and pleasant API, a fast happy path, extensible, errors at compile time, documented. [Docs/agents/design-goals.md](Docs/agents/design-goals.md) explains each goal, the code rules, the checks before a commit and the goals of this repository.

## Project constraints

- **Mirror the real API per target framework.** `IFileSystem`, `ITimeSystem` and `IRandomSystem` follow the shape of `System.IO`, `DateTime`/timers/`TimeProvider` and `Random`/`Guid` for each target framework. `Testably.Abstractions.Parity.Tests` fails when a member is missing; intentional gaps are excluded in `TestHelpers/Parity.cs` or a `Net*ParityTests` class.
- **The mock must behave like the real file system** on Windows, Linux and macOS, and on .NET Framework where it differs. Branch on the mock's `Execute` (`IsWindows`, `IsLinux`, `IsMac`, `IsNetFramework`), which honours `SimulationMode`, instead of on the host OS. Check behaviour against the real file system, not against assumptions.
- **Core and main packages are versioned separately.** Core = `Testably.Abstractions`, `Testably.Abstractions.Interface`, `Testably.Abstractions.FileSystem.Interface`, released from `core/v*` tags. Main = `Testably.Abstractions.Testing`, `.Compression`, `.AccessControl`, `.MemoryMappedFiles`, released from `v*` tags. Main projects consume `Testably.Abstractions.Interface` as a NuGet package (version range in `Directory.Packages.props`), so an interface change is invisible to them until a Core release is published and the range is bumped. For such a change set `BuildScope` in `Pipeline/Build.cs` to `CoreOnly`, release Core, bump the range, then reset it (`MainOnly` covers the reverse case).
- **Public API is reviewed.** Every public change shows up in the snapshot files under `Tests/Api/*/Expected/`; update them deliberately and keep them in the same PR. New public members need XML docs (`GenerateDocumentationFile` is on and warnings are errors).
- `netstandard2.0` sources have no polyfill package: records, `init` and `Index`/`Range` (`^1`, `..`) only compile behind a `FEATURE_*` or `NET*` guard there. Test projects use the `Polyfill` package and may use them on `net48`.

## Validation

- Tests use **TUnit on Microsoft.Testing.Platform** with aweXpect assertions and Mockolate. Test projects are executables; run them with `dotnet test --project <path>`.
- Run only the relevant project and framework: `dotnet test --project Tests/Testably.Abstractions.Tests -f net10.0 --treenode-filter "/*/*/ExistsTests/*"`. The full suite is large; leave it to CI.
- File system tests carry `[FileSystemTests]` and run against `MockFileSystem`, simulated Linux/macOS/Windows mocks (not on `net48`) and the `RealFileSystem`. In Debug the real file system rows are off by default (`Tests/Helpers/Testably.Abstractions.TestHelpers/Settings.cs`); Release and CI run them. A behaviour change is only verified once it passes against the real file system on every OS; CI covers Windows, Linux and macOS.
- Guard OS- or framework-specific expectations with `Skip.If(...)`/`Skip.IfNot(...)` on `Test.RunsOnWindows`, `RunsOnLinux`, `RunsOnMac` or `IsNetFramework`, and assert the behaviour of each platform rather than skipping the mock.
- Public API changes: build the solution, run `Tests/Api/Testably.Abstractions.Api.Tests` and `Tests/Api/Testably.Abstractions.Core.Api.Tests`, and if the change is intended run the explicit `ApiAcceptance.AcceptApiChanges` test to rewrite the expected files.
- New parity-relevant APIs: run `Tests/Testably.Abstractions.Parity.Tests` on each affected framework.
- Encoding and formatting: tabs, CRLF in the working tree (`.editorconfig`), LF in the index (`.gitattributes`). Most `.cs` files start with a UTF-8 BOM, some do not; keep each file's existing BOM state. PowerShell rewrites change it, so prefer targeted edits and check `git diff` for a changed first line (U+FEFF).
- Do not add em-dashes to docs, XML docs or other Markdown.

## Git workflow

- Work on a feature branch and open a pull request against `main`; PRs are squash-merged and the PR title becomes the commit subject.
- PR titles and commit subjects use conventional commit prefixes (`feat:`, `fix:`, `refactor:`, `docs:`, `test:`, `ci:`, `chore:` ...; see `CONTRIBUTING.md`). The body explains why the change is needed; write each paragraph on a single line and add no co-author or attribution trailers.
- CI (`.github/workflows/ci.yml`) runs unit tests on Ubuntu, Windows and macOS, the API tests and SonarCloud. Sonar issues must be resolved and coverage must stay above 90%.
- Never commit `Artifacts/`, `TestResults/` or local changes to the test `Settings` defaults.

## References

- [`Docs/agents/design-goals.md`](Docs/agents/design-goals.md): the goals and code rules for every change, the checks before a commit and the goals of this repository.
- [`Docs/agents/workflows.md`](Docs/agents/workflows.md): build, test, API, mutation and documentation commands, pipeline targets, CI and release workflows.
- [`Docs/agents/project-structure.md`](Docs/agents/project-structure.md): projects, architecture of the mocks and where things live.
- [`Docs/agents/testing.md`](Docs/agents/testing.md): writing tests against the real and the mock file system, test settings and OS-specific behaviour.
- [`CONTRIBUTING.md`](CONTRIBUTING.md) and [`Docs/Maintainer.md`](Docs/Maintainer.md): contributor rules and design notes on the mocks.
62 changes: 62 additions & 0 deletions Docs/agents/design-goals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Design goals

These goals apply to every Testably repository. They guide agents and contributors when a change has more than one reasonable solution. The goals of this repository at the end refine them.

Correctness is a hard requirement. Every other goal is a strong default: follow it, and when a change has to break one, say so in the pull request and explain why.

## Decision test

Before proposing a change, ask: does it keep the library correct, and does it make it faster, more consistent, easier to extend or more pleasant to use? If the answer is no, reconsider the change.

## Goals

When goals conflict, the earlier one wins.

1. **Correct, with clear failures (must).** Behaviour is exact and covered by tests. A failure states what was expected and what was found, and tests pin the complete message.
2. **Stable and compatible.** Follow semantic versioning: breaking changes only in a major version, on purpose and documented. Public API changes show up in the API snapshots and are reviewed. Keep dependencies minimal and their version floors low, and support the declared target frameworks.
3. **AOT and trimming friendly.** No unguarded reflection: prefer source generators, otherwise put reflection behind a feature guard with trimming annotations. A change that touches reflection runs the AOT smoke tests.
4. **A consistent, discoverable and pleasant API.** Extension methods exist only on the types they apply to, so IntelliSense shows what makes sense. Naming, option chaining and null handling follow the existing patterns (see the repository-specific rules). Every public member has XML documentation. Prefer minimal ceremony for the user: sensible defaults, no boilerplate, failure messages that read well.
5. **Fast, especially on the happy path.** Succeeding calls are the common case. Avoid allocations in hot paths, and back every performance claim or change with a benchmark before and after.
6. **Extensible.** Third parties build on the same extension points the library uses itself. New features use the public extension surface instead of internals, and the core stays small and stable.
7. **Errors at compile time rather than at run time.** Prefer analyzers, source generators and types that make misuse a compiler error over runtime checks.
8. **Documented and pleasant to read.** A public feature is not done until it is documented on the docs site, with examples that are tested where the repository can test them. The docs are rendered with Docusaurus: use its features where they help the reader, such as admonitions (`:::note`, `:::tip`, `:::warning`) for remarks, tabs for alternatives (for example synchronous and asynchronous usage, or one tab per test framework) and collapsible `<details>` for long output, and follow the style of the existing pages.

## Code

- **Modern C#.** Use current language features and APIs where the target frameworks allow them, with polyfills for netstandard2.0 and net48 instead of avoiding a feature. Check that the oldest target framework still compiles.
- **Async.**
- Use `ValueTask` for operations that often complete synchronously.
- Pass the `CancellationToken` through every async path.
- Use `ConfigureAwait(false)` on every `await` in library code, so that callers who block on a result cannot deadlock on a single-threaded synchronization context. Existing code does not follow this everywhere yet; new and changed code does.
- Do not block on async code (`.Result`, `.Wait()`, `.GetAwaiter().GetResult()`), except at a deliberate synchronous API boundary, and complete synchronously there where possible.
- Do not use `Task.Run` to make code look asynchronous; use it only where the real counterpart does work in the background.
- **Avoid.**
- Catching exceptions without handling or rethrowing them.
- LINQ, closures and boxing in hot paths; use loops and cached delegates there.
- String concatenation in loops; use a `StringBuilder`.

## Before you commit

Run the checks that match the change:

- **Any code change:** the tests of the affected projects, on the oldest and the newest target framework.
- **Public API:** the API snapshot tests; review and accept intentional changes, never commit pending snapshots.
- **Reflection, trimming or AOT:** the AOT smoke tests.
- **Hot path or a performance claim:** a benchmark before and after.
- **New public feature:** documentation and its examples.
- **Failure messages:** tests that pin the complete message.
- **Static analysis:** no new Sonar or analyzer findings. Avoid the common ones: high cognitive complexity, duplicated string literals, unused members and parameters, methods that could be static, long parameter lists. Do not suppress a finding without asking the maintainer first.

## Reviews

Verify every review finding against the code before changing anything. If you disagree, push back once with concrete reasoning; if the reviewer or the maintainer confirms the finding, implement it.

## Goals of this repository

1. **Behaves like the real system.** On the simulated OS and target framework the real file system is the oracle; differences are fixed in the mock, never in the test.
2. **Interfaces mirror the BCL exactly** per target framework, checked by the parity tests.
3. **Mocks fail like the real system.** Same exception type, message and HResult per OS.
4. **OS behaviour through the mock's own context**, so simulated operating systems stay correct.
5. **Consistent mock-only API.** `With…` configures, `Initialize…` seeds, `Intercept`/`Notify` observe.
6. **Deliberate package evolution.** Interface changes reach the mocks only after a Core release.
7. **Thread-safe mock state.**
Loading
Loading