diff --git a/.backlog/completed/task-12 - Eliminate-intermediate-array-allocations.md b/.backlog/completed/task-12 - Eliminate-intermediate-array-allocations.md index 26009614..7365fc58 100644 --- a/.backlog/completed/task-12 - Eliminate-intermediate-array-allocations.md +++ b/.backlog/completed/task-12 - Eliminate-intermediate-array-allocations.md @@ -77,7 +77,7 @@ and allow reuse in both branches. The change: - Eliminates all three intermediate object allocations present in the original interface-type branch - Kills the equivalent Stryker mutation by removing the conditional that had no observable effect -- Keeps the same logical behaviour: concrete types are found via their interfaces; types +- Keeps the same logical behavior: concrete types are found via their interfaces; types that are themselves `IEnumerable` are found via the fallback check - Build passes with zero warnings; all tests pass across `net8.0`, `net472`, and `net48` diff --git a/.backlog/tasks/task-15 - Add-backlog-CLI-reference-to-AGENTS.md.md b/.backlog/completed/task-15 - Add-backlog-CLI-reference-to-AGENTS.md.md similarity index 95% rename from .backlog/tasks/task-15 - Add-backlog-CLI-reference-to-AGENTS.md.md rename to .backlog/completed/task-15 - Add-backlog-CLI-reference-to-AGENTS.md.md index 1649da30..70629046 100644 --- a/.backlog/tasks/task-15 - Add-backlog-CLI-reference-to-AGENTS.md.md +++ b/.backlog/completed/task-15 - Add-backlog-CLI-reference-to-AGENTS.md.md @@ -1,19 +1,19 @@ ---- -id: TASK-15 -title: Add backlog CLI reference to AGENTS.md -status: Done -assignee: - - claude - - piotrzajac -created_date: '2026-04-17 12:13' -updated_date: '2026-04-17 12:18' -labels: [] -dependencies: [] -priority: low ---- - -## Acceptance Criteria - -- [x] #1 Backlog CLI Reference section added to AGENTS.md before the MCP guidelines block -- [x] #2 Section survives future backlog agents --update-instructions runs - +--- +id: TASK-15 +title: Add backlog CLI reference to AGENTS.md +status: Done +assignee: + - claude + - piotrzajac +created_date: '2026-04-17 12:13' +updated_date: '2026-04-17 12:18' +labels: [] +dependencies: [] +priority: low +--- + +## Acceptance Criteria + +- [x] #1 Backlog CLI Reference section added to AGENTS.md before the MCP guidelines block +- [x] #2 Section survives future backlog agents --update-instructions runs + diff --git a/.backlog/tasks/task-16 - Fix-CodeQL-warnings.md b/.backlog/completed/task-16 - Fix-CodeQL-warnings.md similarity index 100% rename from .backlog/tasks/task-16 - Fix-CodeQL-warnings.md rename to .backlog/completed/task-16 - Fix-CodeQL-warnings.md diff --git a/.backlog/tasks/task-17 - Consolidate-duplicate-Facts-into-Theories.md b/.backlog/completed/task-17 - Consolidate-duplicate-Facts-into-Theories.md similarity index 100% rename from .backlog/tasks/task-17 - Consolidate-duplicate-Facts-into-Theories.md rename to .backlog/completed/task-17 - Consolidate-duplicate-Facts-into-Theories.md diff --git a/.backlog/config.yml b/.backlog/config.yml index 87f416f1..e6bd274c 100644 --- a/.backlog/config.yml +++ b/.backlog/config.yml @@ -1,7 +1,7 @@ project_name: "AutoFixture.XUnit2.AutoMock" default_status: "To Do" statuses: ["To Do", "In Progress", "Done"] -labels: ["sec", "feature", "fix", "ci-cd", "dx", "doc", "agent"] +labels: ["sec", "feature", "fix", "ci-cd", "dx", "doc", "agent", "performance"] date_format: yyyy-mm-dd max_column_width: 20 auto_open_browser: true diff --git a/.backlog/decisions/decision-1 - Share-fixture-across-member-data-rows-by-default.md b/.backlog/decisions/decision-1 - Share-fixture-across-member-data-rows-by-default.md new file mode 100644 index 00000000..845e7902 --- /dev/null +++ b/.backlog/decisions/decision-1 - Share-fixture-across-member-data-rows-by-default.md @@ -0,0 +1,19 @@ +--- +id: decision-1 +title: Share fixture across member data rows by default +date: '2017-01-20' +status: accepted +--- +## Context + +`[MemberAutoMockData]` combines static member data with auto-generated parameters. When a member provides multiple data rows, each row could receive its own fresh `IFixture` instance (producing different mock objects per row) or share one fixture across all rows (preserving mock identity). The right default was non-obvious. + +## Decision + +Default `MemberAutoDataBaseAttribute.ShareFixture = true`. The same `IFixture` instance — with all its customizations and registered mock objects — is reused across every data row produced by the member. Users can opt out with `ShareFixture = false` when independent fixtures per row are required. + +## Consequences + +- Related test cases receive consistent mock instances, which is the expected behavior in the majority of scenarios where member data provides complementary inputs. +- This is the unique differentiator of `[MemberAutoMockData]` over plain `[MemberData]` with manual fixture setup. +- Tests that need isolation between rows require explicit `ShareFixture = false`. diff --git a/.backlog/decisions/decision-10 - Adopt-GitHub-Actions-as-CI-CD-platform.md b/.backlog/decisions/decision-10 - Adopt-GitHub-Actions-as-CI-CD-platform.md new file mode 100644 index 00000000..bdee9faa --- /dev/null +++ b/.backlog/decisions/decision-10 - Adopt-GitHub-Actions-as-CI-CD-platform.md @@ -0,0 +1,19 @@ +--- +id: decision-10 +title: Adopt GitHub Actions as CI/CD platform +date: '2023-03-24' +status: accepted +--- +## Context + +The project previously used Travis CI. Travis CI's free tier for open-source projects was reduced, and it lacked deep integration with GitHub security features (CodeQL, Dependabot, secret scanning). GitHub Actions offered native integration, a richer ecosystem of community actions, and free minutes for public repositories. + +## Decision + +Migrate all CI/CD pipelines from Travis CI to GitHub Actions. Organize workflows into reusable modules: `init.yml` (GitVersion, module detection), `build-test-pack.yml`, `publish.yml`, `tag.yml`. Run all jobs on `windows-latest` to support `net472`/`net48` test slices. Security and quality tools could run as parallel jobs in the same pipeline. + +## Consequences + +- No external CI service dependency; pipeline definition lives in the same repository. +- Native access to GitHub security tab, Dependabot alerts, and environment secrets. +- `windows-latest` runner is required for every job due to `net472`/`net48` constraints, which limits Linux-only optimizations. diff --git a/.backlog/decisions/decision-11 - Integrate-FOSSA-for-license-compliance-scanning.md b/.backlog/decisions/decision-11 - Integrate-FOSSA-for-license-compliance-scanning.md new file mode 100644 index 00000000..220b4813 --- /dev/null +++ b/.backlog/decisions/decision-11 - Integrate-FOSSA-for-license-compliance-scanning.md @@ -0,0 +1,19 @@ +--- +id: decision-11 +title: Integrate FOSSA for license compliance scanning +date: '2023-03-31' +status: accepted +--- +## Context + +Open-source libraries carry licensing obligations for both maintainers and consumers. Transitive NuGet dependencies may introduce copyleft or commercially incompatible licenses that must be detected before distribution. Manual license auditing does not scale as the dependency graph grows. + +## Decision + +Integrate FOSSA into the CI pipeline to automatically scan all direct and transitive NuGet dependencies for license compatibility on every build. FOSSA generates a compliance report and raises alerts when new dependencies introduce problematic licenses. + +## Consequences + +- License violations are caught before packages are published to NuGet.org. +- A machine-readable compliance report is produced automatically. +- Reduces legal risk for enterprise consumers who have strict license policies. diff --git a/.backlog/decisions/decision-12 - Use-GitVersion-in-ContinuousDelivery-mode.md b/.backlog/decisions/decision-12 - Use-GitVersion-in-ContinuousDelivery-mode.md new file mode 100644 index 00000000..6e494f4f --- /dev/null +++ b/.backlog/decisions/decision-12 - Use-GitVersion-in-ContinuousDelivery-mode.md @@ -0,0 +1,19 @@ +--- +id: decision-12 +title: Use GitVersion in ContinuousDelivery mode +date: '2023-08-30' +status: accepted +--- +## Context + +Manual version management in `.csproj` files is error-prone: versions can be forgotten, duplicated across projects, or incremented inconsistently. The project needed an automated versioning strategy aligned with semantic versioning and the git workflow. + +## Decision + +Adopt GitVersion in `ContinuousDelivery` mode. Versions are computed entirely from git history: commits on `master` produce stable semantic versions; feature branches produce pre-release suffixes. No `` element is set manually in any `.csproj`. The `Directory.Build.props` default of `1.0.0.0` is a local fallback only, never used in CI. + +## Consequences + +- Version is always derivable from git history and requires no manual intervention. +- CI sets the version automatically before build and pack steps. +- Developers must follow branching conventions for GitVersion to compute the correct version; ad-hoc commits directly to master advance the stable version. diff --git a/.backlog/decisions/decision-13 - Exclude-Moq-from-Dependabot-group-remaining-NuGet-updates.md b/.backlog/decisions/decision-13 - Exclude-Moq-from-Dependabot-group-remaining-NuGet-updates.md new file mode 100644 index 00000000..43567881 --- /dev/null +++ b/.backlog/decisions/decision-13 - Exclude-Moq-from-Dependabot-group-remaining-NuGet-updates.md @@ -0,0 +1,21 @@ +--- +id: decision-13 +title: Exclude Moq from Dependabot; group remaining NuGet updates +date: '2023-09-21' +status: accepted +--- +## Context + +Moq introduced SponsorLink in a controversial update that embedded telemetry and caused significant community backlash. Automatically upgrading Moq via Dependabot could silently introduce this behavior without a deliberate review. + +## Decision + +Explicitly exclude `Moq` from Dependabot NuGet updates — it requires manual review and a deliberate upgrade decision. +Group all other NuGet updates into logical Dependabot groups: `xUnit`, `AutoFixture`, `Analyzers`, `Testing`, `Common`, and `Other`. +GitHub Actions dependencies are also grouped and updated weekly with a `chore(github-actions):` prefix. + +## Consequences + +- Moq version is frozen until explicitly and consciously upgraded. +- Grouped updates reduce weekly PR noise from many individual bumps to a handful of grouped PRs. +- Any future Moq upgrade must be reviewed for licensing, telemetry, and breaking changes before merging. diff --git a/.backlog/decisions/decision-14 - Enable-CodeQL-for-C-static-security-analysis.md b/.backlog/decisions/decision-14 - Enable-CodeQL-for-C-static-security-analysis.md new file mode 100644 index 00000000..3c467c88 --- /dev/null +++ b/.backlog/decisions/decision-14 - Enable-CodeQL-for-C-static-security-analysis.md @@ -0,0 +1,18 @@ +--- +id: decision-14 +title: Enable CodeQL for C# static security analysis +date: '2023-09-21' +status: accepted +--- +## Context + +Manual code review is insufficient for systematically catching security vulnerabilities. As a NuGet library consumed by other projects, a vulnerability in this library propagates to all consumers. An automated static analysis tool integrated with GitHub's security infrastructure would provide continuous protection at no operational cost for public repos. + +## Decision + +Enable GitHub CodeQL for C# as a required CI check. CodeQL runs on push and pull request to master, analyzing the full C# codebase for vulnerabilities. Results surface in the GitHub Security tab and can block merges when configured as a required status check. + +## Consequences + +- Continuous vulnerability scanning without additional tooling setup for contributors. +- Security findings are visible to maintainers in the GitHub Security tab. diff --git a/.backlog/decisions/decision-15 - Integrate-Stryker-mutation-testing-into-CI.md b/.backlog/decisions/decision-15 - Integrate-Stryker-mutation-testing-into-CI.md new file mode 100644 index 00000000..7c477674 --- /dev/null +++ b/.backlog/decisions/decision-15 - Integrate-Stryker-mutation-testing-into-CI.md @@ -0,0 +1,20 @@ +--- +id: decision-15 +title: Integrate Stryker mutation testing into CI +date: '2023-09-30' +status: accepted +--- +## Context + +Traditional code coverage metrics verify that tests execute code paths, not that they detect bugs. A test suite with 100% coverage can still pass while missing every meaningful regression. The project needed a higher-confidence quality gate. + +## Decision + +Integrate Stryker.NET mutation testing into the CI pipeline as a separate, slow-running stage. Configuration lives in `stryker-config.yml` at the repository root; the tool is invoked from the `src/` directory. Stryker runs are expected before raising a PR rather +than on every commit. + +## Consequences + +- Tests are verified to actually kill mutations, raising confidence beyond coverage alone. +- Slow step kept out of the fast feedback loop; developers opt in before submitting a PR. +- Surviving mutants become explicit action items, not silent gaps. diff --git a/.backlog/decisions/decision-16 - Publish-symbols-and-enable-SourceLink.md b/.backlog/decisions/decision-16 - Publish-symbols-and-enable-SourceLink.md new file mode 100644 index 00000000..f7d2ed15 --- /dev/null +++ b/.backlog/decisions/decision-16 - Publish-symbols-and-enable-SourceLink.md @@ -0,0 +1,20 @@ +--- +id: decision-16 +title: Publish symbols and enable SourceLink +date: '2023-10-13' +status: accepted +--- +## Context + +Developers using the library could not step into library code during debugging because no symbol packages were published. Stack traces showed only method names without source lines, making it difficult to diagnose failures inside the attribute pipeline. + +## Decision + +Publish `.snupkg` symbol packages to NuGet.org alongside the main `.nupkg` packages. +Enable `Microsoft.SourceLink.GitHub` so that debuggers automatically fetch the corresponding source code from GitHub at the exact commit matching the installed version. + +## Consequences + +- Consumers can step into library code in their debugger without any manual setup. +- Stack traces show full source file paths and line numbers for library frames. +- Symbol packages are published automatically as part of the same CI release step as the main packages. diff --git a/.backlog/decisions/decision-17 - Add-data-narrowing-parameter-attributes.md b/.backlog/decisions/decision-17 - Add-data-narrowing-parameter-attributes.md new file mode 100644 index 00000000..73fa5c4f --- /dev/null +++ b/.backlog/decisions/decision-17 - Add-data-narrowing-parameter-attributes.md @@ -0,0 +1,26 @@ +--- +id: decision-17 +title: Add data-narrowing parameter attributes +date: '2023-12-05' +status: accepted +--- +## Context + +AutoFixture generates arbitrary values for parameters. Tests for boundary conditions, enum subsets, or constrained numeric domains needed values from a restricted range, requiring manual fixture setup that negated the boilerplate reduction the library provides. + +## Decision + +Add four parameter-level attributes to Core, all implemented via `IParameterCustomizationSource`: + +- `[Except(v1, v2)]` — generate values excluding the specified set +- `[PickFromValues(v1, v2)]` — pick randomly from a fixed set +- `[PickFromRange(min, max)]` — generate within a numeric range +- `[PickNegative]` — generate only negative numeric values + +All four are backed by new specimen builders (`RandomExceptValuesGenerator`, `RandomFixedValuesGenerator`) and request types (`ExceptValuesRequest`, `FixedValuesRequest`). + +## Consequences + +- Constrained test data without any manual fixture configuration. +- All three mock modules benefit automatically via Core. +- Attributes are combinable with `[Frozen]` and `[CustomizeWith]` in the same parameter list. diff --git a/.backlog/decisions/decision-18 - Integrate-Semgrep-for-SAST-scanning.md b/.backlog/decisions/decision-18 - Integrate-Semgrep-for-SAST-scanning.md new file mode 100644 index 00000000..1cb1f321 --- /dev/null +++ b/.backlog/decisions/decision-18 - Integrate-Semgrep-for-SAST-scanning.md @@ -0,0 +1,19 @@ +--- +id: decision-18 +title: Integrate Semgrep for SAST scanning +date: '2023-12-20' +status: accepted +--- +## Context + +CodeQL provides GitHub-native SAST with strong C# support, but its rule coverage is focused on common vulnerability classes. Semgrep offers complementary community-maintained rule sets and custom pattern matching that can catch additional issues CodeQL does not cover. + +## Decision + +Add Semgrep as a parallel SAST scanning step in CI alongside CodeQL. Use the default Semgrep ruleset for C# plus any community rulesets relevant to the project. Semgrep runs on push and pull request to master. + +## Consequences + +- Broader SAST coverage through two independent tools with different rule philosophies. +- Some overlap with CodeQL is intentional — independent tools reaching the same conclusion increases confidence. +- Semgrep findings are reviewed in CI alongside CodeQL results; both must pass before merge. diff --git a/.backlog/decisions/decision-19 - Adopt-Qodana-for-code-quality-scanning.md b/.backlog/decisions/decision-19 - Adopt-Qodana-for-code-quality-scanning.md new file mode 100644 index 00000000..94b1a031 --- /dev/null +++ b/.backlog/decisions/decision-19 - Adopt-Qodana-for-code-quality-scanning.md @@ -0,0 +1,19 @@ +--- +id: decision-19 +title: Adopt Qodana for code quality scanning +date: '2024-01-16' +status: accepted +--- +## Context + +The in-build analyzer stack (StyleCop, Roslynator, SonarAnalyzer) catches issues at compile time but cannot provide holistic quality scoring, trend analysis across the codebase, or a consolidated view of maintainability and reliability findings separate from the build log. + +## Decision + +Integrate JetBrains Qodana into CI as a quality gate. Qodana runs a full Roslyn-based inspection pass independently of the build and reports results in a structured format. It runs on push and pull request to master and complements CodeQL (security focus) and Semgrep (SAST focus) with maintainability and reliability checks. + +## Consequences + +- Quality metrics are visible in CI as a distinct step, separate from build warnings. +- Qodana's inspection coverage overlaps partially with the in-build analyzers; discrepancies surface issues that build-time suppression may have hidden. +- JetBrains Qodana supports .NET and is compatible with the `windows-latest` CI runner. diff --git a/.backlog/decisions/decision-2 - Decouple-xUnit-from-AutoFixture-via-provider-pattern.md b/.backlog/decisions/decision-2 - Decouple-xUnit-from-AutoFixture-via-provider-pattern.md new file mode 100644 index 00000000..0371c2bd --- /dev/null +++ b/.backlog/decisions/decision-2 - Decouple-xUnit-from-AutoFixture-via-provider-pattern.md @@ -0,0 +1,20 @@ +--- +id: decision-2 +title: Decouple xUnit from AutoFixture via provider pattern +date: '2017-01-22' +status: accepted +--- +## Context + +The library originally derived directly from AutoFixture's `AutoDataAttribute`. This created tight coupling between the xUnit integration and AutoFixture internals, making it difficult to inject custom behavior, intercept the data-generation pipeline, or add parameter-level customization without forking AutoFixture itself. + +## Decision + +Stop inheriting from AutoFixture attributes. Derive from xUnit's `DataAttribute` instead and drive fixture customization through a dedicated `IAutoFixtureAttributeProvider` abstraction. The provider receives the configured `IFixture` and returns a `DataAttribute` whose +`GetData()` resolves test parameters. This separates xUnit's data-supply contract from AutoFixture's specimen-creation pipeline. + +## Consequences + +- Clear boundary between the xUnit layer and AutoFixture: each can evolve independently. +- The provider pattern enables per-parameter customization via `IParameterCustomizationSource` attributes applied later in the pipeline. +- All three mock modules (AutoMoq, AutoFakeItEasy, AutoNSubstitute) override only `Customize(IFixture)` — the rest of the pipeline is inherited from Core. diff --git a/.backlog/decisions/decision-20 - Integrate-Snyk-for-dependency-vulnerability-scanning.md b/.backlog/decisions/decision-20 - Integrate-Snyk-for-dependency-vulnerability-scanning.md new file mode 100644 index 00000000..e86676ac --- /dev/null +++ b/.backlog/decisions/decision-20 - Integrate-Snyk-for-dependency-vulnerability-scanning.md @@ -0,0 +1,19 @@ +--- +id: decision-20 +title: Integrate Snyk for dependency vulnerability scanning +date: '2024-04-18' +status: accepted +--- +## Context + +CodeQL scans first-party C# code for vulnerabilities. + +## Decision + +Integrate Snyk into the CI pipeline to scan NuGet package dependencies for known vulnerabilities using Snyk's continuously updated database. Snyk runs on push and pull request to master and raises alerts when a dependency version matches a known CVE. + +## Consequences + +- Supply chain risk is monitored continuously alongside code quality and license compliance. +- Snyk findings block builds when severity exceeds the configured threshold. +- Intentional overlap with FOSSA on license scanning is acceptable given the low operational cost of running both. diff --git a/.backlog/decisions/decision-21 - Enforce-Conventional-Commits.md b/.backlog/decisions/decision-21 - Enforce-Conventional-Commits.md new file mode 100644 index 00000000..072cedd7 --- /dev/null +++ b/.backlog/decisions/decision-21 - Enforce-Conventional-Commits.md @@ -0,0 +1,20 @@ +--- +id: decision-21 +title: Enforce Conventional Commits +date: '2026-04-10' +status: accepted +--- +## Context + +Commit messages were inconsistently formatted across contributors. + +## Decision + +Enforce Conventional Commits format (`(): `) on every commit, where scope is optional. +Enforcement uses a Husky.NET `commit-msg` hook for local validation and CommitLint.Net in CI to block merges that contain non-conforming commit messages. + +## Consequences + +- Consistent, machine-readable commit history across all contributors. +- Automated changelog generation becomes feasible as a follow-on step. +- Contributors receive immediate feedback at commit time rather than discovering the issue at CI. diff --git a/.backlog/decisions/decision-22 - Adopt-CodeRabbit-for-AI-powered-PR-review.md b/.backlog/decisions/decision-22 - Adopt-CodeRabbit-for-AI-powered-PR-review.md new file mode 100644 index 00000000..36668add --- /dev/null +++ b/.backlog/decisions/decision-22 - Adopt-CodeRabbit-for-AI-powered-PR-review.md @@ -0,0 +1,19 @@ +--- +id: decision-22 +title: Adopt CodeRabbit for AI-powered PR review +date: '2026-04-12' +status: accepted +--- +## Context + +Human code review is thorough for logic but inconsistent at enforcing coding conventions, catching subtle anti-patterns, and ensuring every changed file receives attention. A first-pass automated reviewer would raise quality before human reviewers engage. + +## Decision + +Integrate CodeRabbit as an automated AI reviewer on all pull requests. CodeRabbit posts inline review comments and a PR summary alongside human reviewers. It is configured to understand the project's conventions (BDD naming, AAA structure, attribute ordering rules). + +## Consequences + +- Every PR receives a first-pass review that catches convention violations and straightforward issues before human review. +- Human reviewers focus on higher-level concerns: design, correctness, and intent. +- AI review comments are advisory; maintainers retain final merge authority. diff --git a/.backlog/decisions/decision-23 - Performance-benchmark-tooling-and-approach.md b/.backlog/decisions/decision-23 - Performance-benchmark-tooling-and-approach.md new file mode 100644 index 00000000..ea4322d5 --- /dev/null +++ b/.backlog/decisions/decision-23 - Performance-benchmark-tooling-and-approach.md @@ -0,0 +1,86 @@ +--- +id: decision-23 +title: Performance benchmark tooling and approach +date: '2026-04-22' +status: accepted +--- +## Context + +The library has no performance baseline. Future changes to specimen builders, customizations, +or dependency upgrades could regress generation speed or allocation behavior with no means of +detection. A benchmark suite was proposed to establish that baseline. + +Four decisions needed to be made before implementation: + +1. Which benchmarking tool to use +2. Whether to benchmark only fixture creation or the full attribute pipeline +3. Whether to use one benchmark project or one per mock module +4. Whether to invoke the xUnit runner or call `GetData()` directly + +## Decision + +### 1. Tooling: BenchmarkDotNet + +| Tool | Category | Verdict | +| --- | --- | --- | +| BenchmarkDotNet | Micro-benchmark | **Accepted** — handles JIT warm-up, GC pressure, allocation tracking (`[MemoryDiagnoser]`), and Markdown/JSON export; de-facto standard for .NET | +| dotnet-counters | Runtime diagnostic | Supplementary only — useful for investigating GC pressure after a benchmark reveals a problem | +| PerfView | ETW profiler | Supplementary only — right tool to pinpoint which specimen builder caused a regression; Windows-only (consistent with CI) | +| MiniProfiler | ASP.NET app profiler | Rejected — requires a running web host; cannot measure in-process library calls | +| k6 | HTTP load testing | Rejected — no HTTP surface in this library | +| Gatling | HTTP load testing | Rejected — same reason as k6 | + +Benchmarks are an opt-in `dotnet run --configuration Release` step, never part of `dotnet test`. + +### 2. Benchmark scope: full attribute pipeline, not fixture-only + +**Fixture-only** (`fixture.Create()` directly): simple, one project, no attribute +involvement. Measures AutoFixture's overhead rather than this library's unique contribution. + +**Full attribute pipeline** (`attribute.GetData(MethodInfo)`): exercises attribute wiring, +customization composition, and per-parameter specimen resolution — the cost uniquely +attributable to this library on top of AutoFixture. + +**Decision: full attribute pipeline.** + +### 3. Project structure: three projects, one per mock module + +Benchmarking the full attribute pipeline requires referencing each module's attribute types. +Placing all three in a single project creates namespace ambiguity and mixed `using` directives +that obscure which attribute is under test. + +Three separate projects mirror the existing test project structure and keep each benchmark +file unambiguous. The projects are **not** added to any existing `.sln` file. + +```text +src/ + Objectivity.AutoFixture.XUnit2.AutoMoq.Benchmarks/ + Objectivity.AutoFixture.XUnit2.AutoFakeItEasy.Benchmarks/ + Objectivity.AutoFixture.XUnit2.AutoNSubstitute.Benchmarks/ +``` + +### 4. Execution model: direct `GetData()` calls, not the xUnit runner + +xUnit v2's `TheoryDiscoverer` calls `GetData(MethodInfo)` **once** during the discovery +phase. For non-serializable data (AutoFixture objects containing Castle.Core mock proxies), +xUnit holds the resolved objects in memory and does not call `GetData()` a second time during +execution. + +The double-call concern is real at the **IDE layer**: Visual Studio Test Explorer and Rider +each trigger an independent discovery pass on project load and again on "Run". Each benchmark +iteration therefore calls `GetData()` **twice** to reflect this reality. + +`xunit.runner.utility` was considered but rejected: xUnit's invocation machinery dominates +the numbers and is not attributable to this library, and it requires a circular build +dependency on a separately compiled test assembly. + +Benchmarks use a real `MethodInfo` obtained via reflection from a representative test method +defined in the benchmark project itself — not a synthetic stub — so that parameter type +resolution and per-parameter customization attribute scanning are exercised realistically. + +## Consequences + +- Three new projects to create and maintain. +- `BenchmarkDotNet.Artifacts/` must be added to `.gitignore`. +- `CONTRIBUTING.md` needs a `## Running benchmarks` section. +- Future CI benchmarking (PR delta comments) is a separate follow-on task. diff --git a/.backlog/decisions/decision-3 - Extract-shared-logic-into-a-Core-assembly.md b/.backlog/decisions/decision-3 - Extract-shared-logic-into-a-Core-assembly.md new file mode 100644 index 00000000..38737e09 --- /dev/null +++ b/.backlog/decisions/decision-3 - Extract-shared-logic-into-a-Core-assembly.md @@ -0,0 +1,19 @@ +--- +id: decision-3 +title: Extract shared logic into a Core assembly +date: '2017-10-28' +status: accepted +--- +## Context + +As AutoNSubstitute was being added alongside AutoMoq, shared logic (base attributes, customizations, provider interfaces, specimen builders) was being duplicated across both projects. A single source of truth was needed before the duplication compounded further. + +## Decision + +Create `Objectivity.AutoFixture.XUnit2.Core` as a shared assembly containing all common infrastructure: `AutoDataBaseAttribute`, `InlineAutoDataBaseAttribute`, `MemberAutoDataBaseAttribute`, `AutoDataCommonCustomization`, provider interfaces, specimen builders, and parameter attributes. Core is bundled into each mock module package but is not published as a standalone NuGet package. + +## Consequences + +- Hub-and-spoke architecture: each mock module overrides only `Customize(IFixture)`; all shared behavior lives in one place. +- Bugs and improvements to Core benefit all three modules simultaneously. +- Core cannot be installed by consumers directly — it is an implementation detail. diff --git a/.backlog/decisions/decision-4 - Support-multiple-mocking-frameworks-as-separate-modules.md b/.backlog/decisions/decision-4 - Support-multiple-mocking-frameworks-as-separate-modules.md new file mode 100644 index 00000000..beefbe2f --- /dev/null +++ b/.backlog/decisions/decision-4 - Support-multiple-mocking-frameworks-as-separate-modules.md @@ -0,0 +1,19 @@ +--- +id: decision-4 +title: Support multiple mocking frameworks as separate modules +date: '2017-11-07' +status: accepted +--- +## Context + +AutoFixture supports multiple mock backends (Moq, NSubstitute, FakeItEasy). Teams using only one mocking framework should not be forced to take a transitive dependency on the others, and each framework should be independently versionable. + +## Decision + +Build a separate NuGet package for each mocking framework — `AutoMoq`, `AutoFakeItEasy`, `AutoNSubstitute` — each depending on Core. Every module exposes equivalent three attribute shapes (`[AutoMockData]`, `[InlineAutoMockData]`, `[MemberAutoMockData]`) within its own module namespace, and overrides only `Customize(IFixture)` to apply the framework-specific `ICustomization`. + +## Consequences + +- Users install only the package matching their mocking framework of choice. +- Each module is independently publishable and versionable. +- Three separate project pairs must be maintained, but the surface area per project is minimal (one `Customize` override each). diff --git a/.backlog/decisions/decision-5 - Target-netstandard2.0-2.1-and-net472-net48.md b/.backlog/decisions/decision-5 - Target-netstandard2.0-2.1-and-net472-net48.md new file mode 100644 index 00000000..797b288e --- /dev/null +++ b/.backlog/decisions/decision-5 - Target-netstandard2.0-2.1-and-net472-net48.md @@ -0,0 +1,21 @@ +--- +id: decision-5 +title: Target netstandard2.0/2.1 and net472/net48 +date: '2018-04-17' +status: accepted +--- +## Context + +Many enterprise teams still run test suites on .NET Framework 4.7.2 and 4.8. Targeting only modern .NET would exclude those users, while targeting only .NET Framework would prevent adoption on .NET Core and later runtimes. + +## Decision + +Library projects target `netstandard2.0`, `netstandard2.1`, `net472`, and `net48`. +Test projects target `.NET Core`, `net472`, and `net48` to validate the full compatibility matrix on each CI run. + +## Consequences + +- Maximum compatibility across the .NET ecosystem. +- CI must run on `windows-latest` to execute the `net472`/`net48` test slices. +- The test matrix is larger but covers the full surface claimed by the library. +- When .NET Framework support is eventually dropped, all four framework targets in library `.csproj` files must be updated simultaneously. diff --git a/.backlog/decisions/decision-6 - Delay-sign-assemblies-full-signing-in-CI-only.md b/.backlog/decisions/decision-6 - Delay-sign-assemblies-full-signing-in-CI-only.md new file mode 100644 index 00000000..e052b8d0 --- /dev/null +++ b/.backlog/decisions/decision-6 - Delay-sign-assemblies-full-signing-in-CI-only.md @@ -0,0 +1,19 @@ +--- +id: decision-6 +title: Delay-sign assemblies; full signing in CI only +date: '2018-07-10' +status: accepted +--- +## Context + +Strong-name signing is required for .NET Framework compatibility and NuGet package trust. Committing the private signing key to source control would expose it to every contributor and fork, creating a security risk. + +## Decision + +Use delay signing locally: only `public.snk` (the public key) is committed to source control. Full signing is performed in CI using the `SIGNING_KEY` secret stored in GitHub Secrets. Developers can build and run tests locally without the private key; the fully signed assemblies are produced only on the CI server during the release pipeline. + +## Consequences + +- The private signing key is never exposed in source control. +- Local builds produce delay-signed assemblies that work for development and testing but not for direct NuGet consumption. +- Loss of the `SIGNING_KEY` secret would break releases and require re-establishing trust with a new key. diff --git a/.backlog/decisions/decision-7 - Publish-Core-bundled-into-module-packages-not-standalone.md b/.backlog/decisions/decision-7 - Publish-Core-bundled-into-module-packages-not-standalone.md new file mode 100644 index 00000000..9e2b9134 --- /dev/null +++ b/.backlog/decisions/decision-7 - Publish-Core-bundled-into-module-packages-not-standalone.md @@ -0,0 +1,19 @@ +--- +id: decision-7 +title: 'Publish Core bundled into module packages, not standalone' +date: '2018-07-17' +status: accepted +--- +## Context + +Core contains all shared infrastructure but has no value in isolation — it requires a mock backend to function. Publishing it as a standalone NuGet package would invite consumers to install it alone, producing a broken setup with no data generation capability. + +## Decision + +Do not publish `Objectivity.AutoFixture.XUnit2.Core` as a standalone NuGet package. Bundle it into each of the three mock module packages via project reference so that installing any one module brings Core along transparently. + +## Consequences + +- Consumers see exactly three packages on NuGet.org; no partial installations are possible. +- The NuGet dependency graph is clean: one package per mocking framework, nothing else required. +- Core is an implementation detail; its public API is effectively internal to the three published packages. diff --git a/.backlog/decisions/decision-8 - Suppress-virtual-member-population-via-customization.md b/.backlog/decisions/decision-8 - Suppress-virtual-member-population-via-customization.md new file mode 100644 index 00000000..a9c13da8 --- /dev/null +++ b/.backlog/decisions/decision-8 - Suppress-virtual-member-population-via-customization.md @@ -0,0 +1,19 @@ +--- +id: decision-8 +title: Suppress virtual member population via customization +date: '2018-09-20' +status: accepted +--- +## Context + +When AutoFixture creates objects for classes that implement interfaces, the CLR marks the interface method implementations as `virtual sealed`. AutoFixture attempts to populate those virtual properties, which conflicts with mock proxy behavior: the proxy already owns those members, and AutoFixture's attempt to set them can throw or produce unexpected state. + +## Decision + +Introduce `IgnoreVirtualMembersSpecimenBuilder` (returning `OmitSpecimen` for virtual `PropertyInfo` requests) and expose it through `IgnoreVirtualMembersCustomization`. All three base attributes accept `IgnoreVirtualMembers = true` to activate this behavior per attribute instance. A `[IgnoreVirtualMembers]` parameter-level attribute applies it to a specific parameter. + +## Consequences + +- Mock proxies are not interfered with by AutoFixture's property-population pass. +- Opt-in per attribute — tests that genuinely need virtual properties populated are unaffected. +- Reduces the most common setup error for users working with interface-implementing types. diff --git a/.backlog/decisions/decision-9 - Add-CustomizeWith-parameter-attributes.md b/.backlog/decisions/decision-9 - Add-CustomizeWith-parameter-attributes.md new file mode 100644 index 00000000..8c1aa19d --- /dev/null +++ b/.backlog/decisions/decision-9 - Add-CustomizeWith-parameter-attributes.md @@ -0,0 +1,24 @@ +--- +id: decision-9 +title: Add CustomizeWith parameter attributes +date: '2019-08-24' +status: accepted +--- +## Context + +Users occasionally need to apply a full `ICustomization` to a single parameter without affecting the fixture configuration for other parameters. No mechanism existed for per-parameter customization injection beyond the built-in `[Frozen]` attribute. + +## Decision + +Add two parameter-level attributes to Core: + +- `[CustomizeWith(typeof(T))]` — activates the specified `ICustomization` for a parameter +- `[CustomizeWith]` — generic variant; equivalent in behavior, reduces casting boilerplate + +Both are implemented via `IParameterCustomizationSource` and apply the customization to the fixture immediately before the parameter is resolved. + +## Consequences + +- Fine-grained customization without polluting the fixture globally for all parameters. +- The generic variant is the preferred form; the `typeof` variant exists for contexts where generic attributes are unavailable. +- Combinable with `[Frozen]` in the same parameter list. diff --git a/.backlog/tasks/task-19 - Add-performance-benchmarks.md b/.backlog/tasks/task-19 - Add-performance-benchmarks.md new file mode 100644 index 00000000..03ecf536 --- /dev/null +++ b/.backlog/tasks/task-19 - Add-performance-benchmarks.md @@ -0,0 +1,59 @@ +--- +id: TASK-19 +title: Add performance benchmarks +status: To Do +assignee: + - piotrzajac + - claude +created_date: '2026-04-22' +updated_date: '2026-04-22' +labels: + - performance +dependencies: [] +priority: low +--- + +## Context + +Tooling selection, benchmark scope, project structure, and xUnit execution model analysis are +recorded in [DECISION-23 — Performance benchmark tooling and approach](../decisions/decision-23%20-%20Performance-benchmark-tooling-and-approach.md). + +**Summary of decisions:** + +- **Tool:** BenchmarkDotNet with `[MemoryDiagnoser]` +- **Scope:** full attribute pipeline (`GetData(MethodInfo)`) — not fixture-only +- **Structure:** three projects, one per mock module; not added to any `.sln` file +- **Execution model:** call `GetData()` directly twice per iteration (IDE double-discovery); + use a real `MethodInfo` from a representative method defined in the benchmark project + +### Benchmark scenarios (identical across all three projects, different attribute types) + +| Benchmark class | Scenario | +| --- | --- | +| `PocoGenerationBenchmark` | Flat POCO with 5 primitive properties | +| `DeepGraphGenerationBenchmark` | Object graph 4 levels deep with collections | +| `FrozenVsUnfrozenBenchmark` | Same type with and without `[Frozen]` | +| `VirtualMembersBenchmark` | Many virtual properties, suppressed vs not | + +### Running benchmarks + +```bash +dotnet run --project src/Objectivity.AutoFixture.XUnit2.AutoMoq.Benchmarks \ + --configuration Release -- --filter '*' +``` + +(Repeat for `AutoFakeItEasy.Benchmarks` and `AutoNSubstitute.Benchmarks`.) + +--- + +## Acceptance Criteria + + +- [ ] #1 Three benchmark projects exist under `src/`, one per mock module, each building with `dotnet build --configuration Release` +- [ ] #2 BenchmarkDotNet is the only benchmarking dependency; no test framework packages are added +- [ ] #3 All four benchmark classes are implemented in each project using that module's own attribute types, each carrying `[MemoryDiagnoser]` +- [ ] #4 Each benchmark calls `GetData()` twice per iteration using a real `MethodInfo` resolved in `[GlobalSetup]` +- [ ] #5 Running any project with `--configuration Release -- --filter '*'` produces a valid Markdown summary table +- [ ] #6 `BenchmarkDotNet.Artifacts/` is added to `.gitignore` +- [ ] #7 A `## Running benchmarks` section is added to `CONTRIBUTING.md` explaining the invocation command for each project +