[perf-improver] perf: skip allocation in CaptureLifecycleProperties when no user properties set#9478
Draft
Evangelink wants to merge 1 commit into
Draft
Conversation
…erties set When AssemblyInitialize/ClassInitialize runs but sets no TestContext.Properties, CaptureLifecycleProperties previously allocated a new Dictionary + ReadOnlyDictionary wrapper unconditionally. This was wasted work in the common case. Changes: - CaptureLifecycleProperties now returns null when there are no non-label properties to snapshot, avoiding two heap allocations per lifecycle method execution. - The dictionary is lazily allocated on first non-label entry (single-pass iteration). - MergeProperties gains a Count==0 early-exit guard (defensive, since callers with the null-return change above never pass an empty dict from lifecycle paths). - PostAssemblyInitProperties/PostClassInitProperties are already nullable, so callers need no changes: MergeProperties(null) already returns immediately. Performance impact: - Saves 2 allocations per assembly/class that has [AssemblyInitialize]/[ClassInitialize] but does not set TestContext.Properties (the common case). - Saves a lock acquisition per test for the MergeProperties calls on both PostAssemblyInitProperties and PostClassInitProperties in RunSingleTestAsync. Tests: add CaptureLifecyclePropertiesShouldReturnNullWhenNoNonLabelPropertiesExist, MergePropertiesShouldIgnoreEmptyDictionary, and matching higher-level tests in TestAssemblyInfoTests and TestClassInfoTests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Optimizes MSTestAdapter.PlatformServices’ TestContextImplementation lifecycle property flow by avoiding allocations and locking when no user properties are set during AssemblyInitialize / ClassInitialize.
Changes:
- Make
CaptureLifecycleProperties()lazily allocate and returnnullwhen there are no non-label properties to snapshot. - Make
MergeProperties()return early fornullor empty dictionaries to avoid unnecessary locking. - Add/adjust unit tests to validate
nullsnapshot behavior and empty-merge short-circuit behavior.
Show a summary per file
| File | Description |
|---|---|
| src/Adapter/MSTestAdapter.PlatformServices/Services/TestContextImplementation.cs | Implements lazy snapshot allocation + nullable return, and adds empty-dictionary fast path in MergeProperties. |
| test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Services/TestContextImplementationTests.cs | Updates snapshot tests for nullable return and adds a test for merging an empty dictionary. |
| test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestClassInfoTests.cs | Adds coverage that PostClassInitProperties stays null when class init sets no properties. |
| test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestAssemblyInfoTests.cs | Adds coverage that PostAssemblyInitProperties stays null when assembly init sets no properties. |
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Low
Comment on lines
+512
to
516
| snapshot.Should().NotBeNull(); | ||
| snapshot.Should().ContainKey("UserKey"); | ||
| snapshot["UserKey"].Should().Be("UserValue"); | ||
| snapshot!["UserKey"].Should().Be("UserValue"); | ||
| snapshot.Should().ContainKey("AnotherKey"); | ||
| snapshot["AnotherKey"].Should().Be(7); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Goal and rationale
CaptureLifecycleProperties()is called once per[AssemblyInitialize]/[ClassInitialize]execution to snapshot anyTestContext.Propertiesentries set during init, so they can be propagated to each test's context. In the common case — where neither init method sets any properties — the method unconditionally allocated two objects:new Dictionary<string, object?>(capacity)— the snapshot buffernew ReadOnlyDictionary<string, object?>(snapshot)— the immutable wrapperThose two objects are then stored in
PostAssemblyInitProperties/PostClassInitPropertiesand passed to every test viaMergeProperties.MergePropertiesalready returned early fornull, but not for an empty dictionary, so it also acquired a lock per call before doing nothing.Approach
CaptureLifecycleProperties: change to lazy allocation. TheDictionaryis allocated only on the first non-label entry. If the loop finds nothing to snapshot, the method returnsnull(return type changed fromIReadOnlyDictionary<string,object?>toIReadOnlyDictionary<string,object?>?). This is a single-pass implementation — no extra iteration.MergeProperties: extend the null guard tonull or { Count: 0 }, so an empty dict is also short-circuited cheaply (defensive, and matches the new null-return convention).PostAssemblyInitPropertiesandPostClassInitPropertiesare already declared as nullable;MergeProperties(null)already short-circuits.Performance evidence
Every test class that has
[AssemblyInitialize]or[ClassInitialize](but doesn't write toTestContext.Propertiesin it) benefits:CaptureLifecycleProperties(empty)MergeProperties(emptySnapshot)per testMergeProperties(null)per testFor a test suite with an
[AssemblyInitialize]/[ClassInitialize]that sets no properties:PostAssemblyInitProperties, one forPostClassInitPropertiesmerge inRunSingleTestAsync)In a 1000-test suite, that's ~2000 lock acquisitions avoided.
Trade-offs
None significant. The change is backward-compatible: callers that store the snapshot in nullable fields already handle
null;MergeProperties(null)was already a no-op.Reproducibility
Test status
No SDK is available in this CI environment, so local build/test could not be run. The change is confined to the
MSTestAdapter.PlatformServicesproject and its unit tests; all callers already handlenullsnapshots.Add this agentic workflows to your repo
To install this agentic workflow, run