Skip to content

perf: skip receiver registration for ordinary objects - #6790

Merged
thomhurst merged 1 commit into
mainfrom
perf/blog-event-receivers
Sep 12, 2026
Merged

thomhurst merged 1 commit into
mainfrom
perf/blog-event-receivers

Conversation

@thomhurst

@thomhurst thomhurst commented Sep 12, 2026

Copy link
Copy Markdown
Owner

EventReceiverOrchestrator currently inserts every eligible attribute, argument, context and test instance into its concurrent deduplication set, then scans all event interfaces, even when an object implements none. Filter on IEventReceiver before deduplication in both registration entry points. Actual receivers retain the existing ordering and registration paths.

For a fresh 1,000-test session, registration of plain tests takes 117.3 us instead of 575.1 us (79.6% less) and allocates 112.01 KB instead of 965.11 KB (88.4% less). With a real ITestStartEventReceiver on every class instance, time falls 32.3%, with 10.6% fewer allocations. These measurements include eligible-object cache reconstruction and both registration stages; they exclude constructing TestContext objects and running test bodies.

Validation: all 290 net10.0 unit tests passed, including new checks that ordinary objects are not hashed and that attribute/class receivers still receive callbacks. The generated 10,000-test executable passed in source-generated and reflection modes.

Whole-executable check: 20 alternating AB/BA pairs after three warmups per variant, each run required exactly 10,000 successful tests. Before: mean 980.79 ms, median 969.34 ms. After: mean 963.89 ms, median 957.15 ms. Paired mean reduction: 16.89 ms; approximate 95% t interval [-1.04, 34.83] ms. This does not establish a significant end-to-end speedup. The strong result is the isolated registration cost reduction.

Baseline: 656b66e723; candidate: 57f4d2857e. BenchmarkDotNet 0.15.8, SDK 11.0.100-preview.7.26381.103, .NET 10.0.12, Windows 11, Intel i7-12700K. Both saved engine DLLs use the same Core/MTP dependencies and isolated AssemblyLoadContexts. InProcessEmitToolchain, 20 iterations and six warmups, sequential execution; no other builds/tests launched by this task during measurement. A fresh orchestrator is created per operation; every context's receiver caches are reset before registration, avoiding a warmed-dedup benchmark.


BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.9168/25H2/2025Update/HudsonValley2)
12th Gen Intel Core i7-12700K 3.60GHz, 1 CPU, 20 logical and 12 physical cores
.NET SDK 11.0.100-preview.7.26381.103
  [Host] : .NET 10.0.12 (10.0.12, 10.0.1226.42308), X64 RyuJIT x86-64-v3

Toolchain=InProcessEmitToolchain  IterationCount=20  WarmupCount=6  

Method WithReceivers Mean Error StdDev Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
Before False 575.1 μs 2.29 μs 2.25 μs 1.00 0.01 75.1953 24.4141 965.11 KB 1.00
After False 117.3 μs 0.68 μs 0.73 μs 0.20 0.00 8.6670 1.2207 112.01 KB 0.12
Before True 1,198.4 μs 18.32 μs 18.81 μs 1.00 0.02 404.2969 136.7188 5163.37 KB 1.00
After True 811.7 μs 22.06 μs 25.40 μs 0.68 0.02 361.3281 120.1172 4616.6 KB 0.89
Reproduce the microbenchmark

Save the project and source below in an external RuntimeBench directory. Replace the signing-key checkout path in the project. Build the baseline and PR into sibling baseline-runner and idea3-runner directories:

$env:PERF_ROOT = 'C:/path/to/evidence'
dotnet build C:/path/to/baseline/src/TUnit.Engine -c Release -f net10.0 -p:CopyLocalLockFileAssemblies=true -o "$env:PERF_ROOT/baseline-runner"
dotnet build C:/path/to/candidate/src/TUnit.Engine -c Release -f net10.0 -p:CopyLocalLockFileAssemblies=true -o "$env:PERF_ROOT/idea3-runner"
Set-Location "$env:PERF_ROOT/RuntimeBench"
dotnet run -c Release -- --filter '*ReceiverBench*' --job Dry --inProcess --artifacts ../dry
dotnet run -c Release --no-build -- --filter '*ReceiverBench*' --inProcess --iterationCount 20 --warmupCount 6 --artifacts ../results --exporters fulljson

RuntimeBench.csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <AssemblyName>TUnit.UnitTests</AssemblyName>
    <SignAssembly>true</SignAssembly>
    <AssemblyOriginatorKeyFile>C:/git/TUnit-perf-blog/eng/strongname.snk</AssemblyOriginatorKeyFile>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
    <Reference Include="TUnit.Core"><HintPath>../baseline-runner/TUnit.Core.dll</HintPath></Reference>
    <Reference Include="Microsoft.Testing.Platform"><HintPath>../baseline-runner/Microsoft.Testing.Platform.dll</HintPath></Reference>
    <Reference Include="Microsoft.Testing.Extensions.TrxReport.Abstractions"><HintPath>../baseline-runner/Microsoft.Testing.Extensions.TrxReport.Abstractions.dll</HintPath></Reference>
  </ItemGroup>

</Project>

Program.cs:

BenchmarkDotNet.Running.BenchmarkSwitcher.FromAssembly(typeof(ReceiverBench).Assembly).Run(args);

ReceiverBench.cs:

using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.Loader;
using BenchmarkDotNet.Attributes;
using TUnit.Core;
using TUnit.Core.Interfaces;

[MemoryDiagnoser]
public class ReceiverBench
{
    [Params(false, true)]
    public bool WithReceivers { get; set; }
    private TestContext[] _contexts = null!;
    private object[] _instances = null!;
    private Func<object> _beforeFactory = null!, _afterFactory = null!;
    private Action<object, TestContext> _beforeRegister = null!, _afterRegister = null!;
    private Action<object, TestContext> _beforeClass = null!, _afterClass = null!;

    [GlobalSetup]
    public void Setup()
    {
        var root = Environment.GetEnvironmentVariable("PERF_ROOT") ?? "C:/git/TUnit-perf-evidence-20260912";
        (_beforeFactory, _beforeRegister, _beforeClass) = Load(root + "/baseline-runner/TUnit.Engine.dll");
        (_afterFactory, _afterRegister, _afterClass) = Load(root + "/idea3-runner/TUnit.Engine.dll");
        _instances = Enumerable.Range(0, 1000).Select(_ => WithReceivers ? (object)new Receiver() : new object()).ToArray();
        _contexts = Enumerable.Range(0, 1000).Select(i =>
        {
            var context = new TestContext("Test", null!, null!, new TestBuilderContext { TestMetadata = null! }, CancellationToken.None);
            context.Metadata.TestDetails = new TestDetails([new TestAttribute()])
            {
                TestId = i.ToString(), TestName = "Test", ClassType = typeof(ReceiverBench), MethodName = "Test",
                ClassInstance = null!, TestMethodArguments = [], TestClassArguments = [], MethodMetadata = null!,
                ReturnType = typeof(void), AttributesByType = new Dictionary<Type, IReadOnlyList<Attribute>>()
            };
            return context;
        }).ToArray();
        Validate(Before());
        Validate(After());
    }

    private void Validate(object orchestrator)
    {
        var registry = orchestrator.GetType().GetField("_registry", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(orchestrator)!;
        var hasReceivers = (bool)registry.GetType().GetMethod("HasTestStartReceivers")!.Invoke(registry, null)!;
        if (hasReceivers != WithReceivers) throw new Exception("Incorrect receiver registration");
    }

    [GlobalCleanup]
    public void Cleanup()
    {
        foreach (var context in _contexts) { context.RemoveFromRegistry(); context.Dispose(); }
    }

    [Benchmark(Baseline = true)]
    public object Before() => Register(_beforeFactory, _beforeRegister, _beforeClass);
    [Benchmark]
    public object After() => Register(_afterFactory, _afterRegister, _afterClass);

    private object Register(Func<object> factory, Action<object, TestContext> register, Action<object, TestContext> registerClass)
    {
        var orchestrator = factory();
        // One operation registers a 1,000-test suite in a fresh session. The loop is
        // the actual workload, not artificial repetition of a single warmed registration.
        for (var i = 0; i < _contexts.Length; i++)
        {
            var context = _contexts[i];
            context.Metadata.TestDetails.ClassInstance = null!;
            context.InvalidateEventReceiverCaches();
            register(orchestrator, context);
            context.Metadata.TestDetails.ClassInstance = _instances[i];
            registerClass(orchestrator, context);
        }
        return orchestrator;
    }

    private static (Func<object>, Action<object, TestContext>, Action<object, TestContext>) Load(string path)
    {
        var loadContext = new AssemblyLoadContext(path, isCollectible: true);
        var type = loadContext.LoadFromAssemblyPath(Path.GetFullPath(path)).GetType("TUnit.Engine.Services.EventReceiverOrchestrator", true)!;
        var constructor = type.GetConstructors().Single();
        var factory = Expression.Lambda<Func<object>>(Expression.Convert(
            Expression.New(constructor, Expression.Constant(null, constructor.GetParameters()[0].ParameterType)), typeof(object))).Compile();
        return (factory, Bind("RegisterReceivers"), Bind("RegisterClassInstanceReceiver"));

        Action<object, TestContext> Bind(string name)
        {
            var instance = Expression.Parameter(typeof(object));
            var context = Expression.Parameter(typeof(TestContext));
            return Expression.Lambda<Action<object, TestContext>>(Expression.Call(
                Expression.Convert(instance, type), type.GetMethod(name)!, context), instance, context).Compile();
        }
    }

    public class Receiver : ITestStartEventReceiver
    {
        public ValueTask OnTestStart(TestContext context) => ValueTask.CompletedTask;
    }
}
Raw whole-executable samples (milliseconds) ```csv "Pair","Variant","Milliseconds" "1","Before","969.5005" "1","After","945.695" "2","After","939.51" "2","Before","994.2921" "3","Before","1059.775" "3","After","1011.7146" "4","After","935.254" "4","Before","949.9259" "5","Before","956.6343" "5","After","971.3426" "6","After","979.22" "6","Before","973.7481" "7","Before","986.6671" "7","After","951.7484" "8","After","955.8747" "8","Before","1047.395" "9","Before","990.9323" "9","After","961.0989" "10","After","934.141" "10","Before","954.4484" "11","Before","1014.3478" "11","After","950.412" "12","After","958.4172" "12","Before","948.5221" "13","Before","944.0799" "13","After","990.4228" "14","After","955.0782" "14","Before","969.1712" "15","Before","959.1917" "15","After","935.0701" "16","After","991.0562" "16","Before","957.9144" "17","Before","977.7508" "17","After","947.2239" "18","After","979.8796" "18","Before","1048.004" "19","Before","961.7752" "19","After","1002.1741" "20","After","982.5291" "20","Before","951.6738"
</details>


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **Bug Fixes**
  * Improved event receiver registration to ignore objects that do not support event receiving.
  * Prevented invalid objects and attributes from affecting receiver deduplication.
  * Ensured registered event receivers receive test-start notifications only once, including when registered repeatedly.

* **Tests**
  * Added coverage for receiver filtering, deduplication, and notification behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

@thomhurst
thomhurst deployed to Pull Requests September 12, 2026 09:14 — with GitHub Actions Active
@thomhurst
thomhurst deployed to Pull Requests September 12, 2026 09:14 — with GitHub Actions Active
@thomhurst
thomhurst deployed to Pull Requests September 12, 2026 09:14 — with GitHub Actions Active
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 12, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-12T09:16:23.987667Z 57f4d28 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: d2dac6db-285d-4986-af84-d927343079bd

📥 Commits

Reviewing files that changed from the base of the PR and between f7636c4 and 57f4d28.

📒 Files selected for processing (2)
  • src/TUnit.Engine/Services/EventReceiverOrchestrator.cs
  • tests/TUnit.UnitTests/EventReceiverRegistrationTests.cs

Included review availability: Your plan provides up to 8 included reviews per hour; 5 remain after this review.


📝 Walkthrough

Walkthrough

The change filters non-receiver objects during registration and validates class-instance receivers. New unit tests cover objects with throwing hash codes, receiver deduplication, and single event delivery.

Changes

Event receiver registration

Layer / File(s) Summary
Filter and validate event receivers
src/TUnit.Engine/Services/EventReceiverOrchestrator.cs, tests/TUnit.UnitTests/EventReceiverRegistrationTests.cs
RegisterReceivers and RegisterClassInstanceReceiver now ignore non-receiver objects. Tests verify safe handling, deduplication, and one OnTestStart call per receiver.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Refactor

Merge Risk: ⚪ Minimal · up to 57f4d

Receiver registration now skips ordinary objects before deduplication while preserving valid receiver delivery and deduplication behavior. No merge-blocking risk remains.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: a performance optimization that skips receiver registration for ordinary objects.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/blog-event-receivers

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit reads each line,
The patch grows clear beneath the moon,
Small changes hop in place,
Tests guard the garden path,
Reviews bloom before the dawn.

Comment @coderabbitai help to get the list of available commands.

@thomhurst
thomhurst enabled auto-merge (squash) September 12, 2026 09:15
@greptile-apps

greptile-apps Bot commented Sep 12, 2026

Copy link
Copy Markdown

Greptile Summary

This PR avoids receiver-registration work for objects that cannot receive events.

  • Filters eligible attributes, arguments, contexts, and instances before deduplication and interface scanning.
  • Applies the same guard to late class-instance registration while preserving receiver deduplication.
  • Adds regression tests for ordinary objects and attribute/class receiver callbacks.

Confidence Score: 5/5

The PR appears safe to merge; the optimization preserves all currently supported receiver registration paths.

Every receiver interface registered by the orchestrator inherits IEventReceiver, and the deduplication set has no responsibility beyond preventing duplicate registration. The added tests exercise both changed entry points and confirm callback behavior remains intact.

Important Files Changed

Filename Overview
src/TUnit.Engine/Services/EventReceiverOrchestrator.cs Adds an early marker-interface guard before receiver deduplication and registration without changing valid receiver behavior.
tests/TUnit.UnitTests/EventReceiverRegistrationTests.cs Verifies ordinary objects bypass hashing and valid attribute and class receivers remain deduplicated and invoked.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Eligible event object] --> B{Implements IEventReceiver?}
  B -->|No| C[Skip registration]
  B -->|Yes| D{Already initialized?}
  D -->|Yes| C
  D -->|No| E[Register implemented event interfaces]
Loading

Reviews (1): Last reviewed commit: "perf: skip receiver registration for ord..." | Re-trigger Greptile

@github-actions

Copy link
Copy Markdown
Contributor

Code Review

Reviewed the diff for #6790 (src/TUnit.Engine/Services/EventReceiverOrchestrator.cs, tests/TUnit.UnitTests/EventReceiverRegistrationTests.cs).

Summary

The change is well-targeted: it filters non-IEventReceiver objects out of RegisterReceivers/RegisterClassInstanceReceiver before they're inserted into the _initializedObjects dedup set, instead of after. I traced GetEligibleEventObjects()/BuildEligibleEventObjects (TestContextExtensions.cs) and confirmed the eligible-object set genuinely mixes attributes, constructor/method/class arguments, and injected properties with the rare actual receiver — so most entries were previously paying for a GetHashCode/Add into a ConcurrentHashSet<object> for nothing. Since every specific receiver interface (ITestStartEventReceiver, IFirstTestInClassEventReceiver, etc.) derives from IEventReceiver, the obj is not IEventReceiver filter is a correct superset check — no receiver type is excluded, and EventReceiverRegistry.RegisterReceiver/RegisterReceiverInternal was already a no-op for non-receivers, so this is a pure perf win with no behavior change for real receivers. This also isn't part of the dual-implementation (Engine/SourceGenerator) surface — EventReceiverOrchestrator only exists under TUnit.Engine, so no source-generator mirror is needed here.

The benchmark methodology in the description is unusually rigorous (isolated ALCs per baseline/candidate, AB/BA paired whole-executable runs, honest reporting that the end-to-end CI is [-1.04, 34.83] ms and not statistically significant). The new unit tests are a nice touch: OrdinaryObjectsDoNotParticipateInReceiverDeduplication uses objects whose GetHashCode() throws, which turns "was this object added to the dedup set" into a hard failure instead of a silent behavioral assumption — a good regression guard against this optimization being accidentally reverted.

Minor finding

Dead defensive code in RegisterClassInstanceReceiver (src/TUnit.Engine/Services/EventReceiverOrchestrator.cs, the block right after the new is not IEventReceiver early return):

if (classInstance is not IEventReceiver)
{
    return;
}

// Defense-in-depth: SkippedTestInstance is a sentinel singleton for tests skipped
// at registration time and should never be treated as an event receiver. Callers
// already short-circuit on this sentinel, but guard here too.
if (classInstance is SkippedTestInstance)
{
    return;
}

SkippedTestInstance (src/TUnit.Core/SkippedTestInstance.cs) implements no interfaces, so it's already excluded by the new is not IEventReceiver check above — this block can now never execute. It's harmless as-is, but the "defense-in-depth" comment is misleading now that it describes an unreachable branch; a future maintainer skimming this method could reasonably assume it's still doing something. Worth either deleting the block (the IEventReceiver filter is a strictly broader guard) or, if you want to keep an explicit signal for readability, replacing the comment to note it's now subsumed by the type check above. Not blocking.

No other issues found — filtering logic, dedup semantics, and the null-handling replacement (classInstance is not IEventReceiver correctly subsumes the old null check since null is IEventReceiver is always false) all check out.

@thomhurst
thomhurst merged commit c433e35 into main Sep 12, 2026
14 checks passed
@thomhurst
thomhurst deleted the perf/blog-event-receivers branch September 12, 2026 09:44
github-actions Bot pushed a commit to IntelliTect/CodingGuidelines that referenced this pull request Sep 14, 2026
Updated [TUnit.Core](https://github.com/thomhurst/TUnit) from 1.66.27 to
1.67.0.

<details>
<summary>Release notes</summary>

_Sourced from [TUnit.Core's
releases](https://github.com/thomhurst/TUnit/releases)._

## 1.67.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.67.0 -->

## What's Changed
### Other Changes
* docs: clarified and updated attributes comparison for xUnit 3 by
@​304NotModified in thomhurst/TUnit#6774
* perf: read inline argument metadata without reflection by @​thomhurst
in thomhurst/TUnit#6778
* perf: limit converter discovery to declarations by @​thomhurst in
thomhurst/TUnit#6779
* perf: skip teardown analysis when no disposable members need cleanup
by @​thomhurst in thomhurst/TUnit#6780
* perf: avoid line allocations when writing generated source by
@​thomhurst in thomhurst/TUnit#6781
* perf: avoid formatting interface names for data-source checks by
@​thomhurst in thomhurst/TUnit#6782
* perf: skip unannotated property data-source candidates by @​thomhurst
in thomhurst/TUnit#6784
* fix: fold inner exceptions into IDE test failure output by @​thomhurst
in thomhurst/TUnit#6777
* perf: reuse argument-free attribute initializer text by @​thomhurst in
thomhurst/TUnit#6788
* perf: extract test metadata in attribute transforms by @​thomhurst in
thomhurst/TUnit#6789
* perf: skip receiver registration for ordinary objects by @​thomhurst
in thomhurst/TUnit#6790
* perf: cache reporting properties on test contexts by @​thomhurst in
thomhurst/TUnit#6791
* fix: preserve executor registration, limiter precedence, and timeout
classification by @​Nice3point in
thomhurst/TUnit#6768
### Dependencies
* chore(deps): update tunit to 1.66.27 by @​thomhurst in
thomhurst/TUnit#6742
* chore(deps): update dependency bunit to 2.10.3 by @​thomhurst in
thomhurst/TUnit#6745
* chore(deps): update dependency imposter to 0.1.11 by @​thomhurst in
thomhurst/TUnit#6744
* chore(deps): update dependency microsoft.kiota.abstractions to 2.1.2
by @​thomhurst in thomhurst/TUnit#6747
* chore(deps): update dependency microsoft.templateengine.authoring.cli
to v10.0.401 by @​thomhurst in
thomhurst/TUnit#6750
* chore(deps): update dependency fsharp.core to 10.1.401 by @​thomhurst
in thomhurst/TUnit#6748
* chore(deps): update dependency
microsoft.templateengine.authoring.templateverifier to 10.0.401 by
@​thomhurst in thomhurst/TUnit#6751
* chore(deps): update dependency system.commandline to 2.0.12 by
@​thomhurst in thomhurst/TUnit#6752
* chore(deps): update dependency dotnet-sdk to v10.0.401 by @​thomhurst
in thomhurst/TUnit#6754
* chore(deps): update microsoft.extensions to 10.0.12 by @​thomhurst in
thomhurst/TUnit#6755
* chore(deps): update microsoft.aspnetcore to 10.0.12 by @​thomhurst in
thomhurst/TUnit#6753
* chore(deps): update dependency microsoft.entityframeworkcore to
10.0.12 by @​thomhurst in thomhurst/TUnit#6749
* chore(deps): update mcr.microsoft.com/dotnet/sdk docker tag to v11 by
@​thomhurst in thomhurst/TUnit#6756
* chore(deps): update dependency microsoft.net.test.sdk to 18.10.0 by
@​thomhurst in thomhurst/TUnit#6761
* chore(deps): update microsoft.extensions to 10.10.0 by @​thomhurst in
thomhurst/TUnit#6762
* chore(deps): update react to ^19.3.0 by @​thomhurst in
thomhurst/TUnit#6763
* chore(deps): update dependency awssdk.sqs to 4.0.100.13 by @​thomhurst
in thomhurst/TUnit#6764
* chore(deps): update dependency polyfill to 11.3.0 by @​thomhurst in
thomhurst/TUnit#6765
* chore(deps): update dependency polyfill to 11.3.0 by @​thomhurst in
thomhurst/TUnit#6766
* chore(deps): update dependency stackexchange.redis to 3.2.0 by
@​thomhurst in thomhurst/TUnit#6769
* chore(deps): update dependency microsoft.net.stringtools to 18.10.1 by
@​thomhurst in thomhurst/TUnit#6771
* chore(deps): update dependency dotnet-trace to v10.0.745401 by
@​thomhurst in thomhurst/TUnit#6773
* chore(deps): bump colord from 2.9.3 to 2.10.0 in /docs by
@​dependabot[bot] in thomhurst/TUnit#6759
* chore(deps): bump joi from 17.13.4 to 17.13.7 in /docs by
@​dependabot[bot] in thomhurst/TUnit#6758
* chore(deps): bump js-yaml from 4.3.1 to 4.3.2 in /docs by
@​dependabot[bot] in thomhurst/TUnit#6757
* chore(deps): update dependency yaml to v2.9.1 by @​thomhurst in
thomhurst/TUnit#6785
* chore(deps): update verify to 32.0.1 by @​thomhurst in
thomhurst/TUnit#6786
* chore(deps): update dependency nunit.analyzers to 4.15.0 by
@​thomhurst in thomhurst/TUnit#6792

## New Contributors
* @​304NotModified made their first contribution in
thomhurst/TUnit#6774
* @​Nice3point made their first contribution in
thomhurst/TUnit#6768
 ... (truncated)

Commits viewable in [compare
view](thomhurst/TUnit@v1.66.27...v1.67.0).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=TUnit.Core&package-manager=nuget&previous-version=1.66.27&new-version=1.67.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This was referenced Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant