-
Notifications
You must be signed in to change notification settings - Fork 293
Flow TestContext.Properties through Assembly/Class lifecycle #8386
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,8 @@ | |
| using System.Data.Common; | ||
| #endif | ||
|
|
||
| using System.Collections.ObjectModel; | ||
|
|
||
| using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter; | ||
| using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface; | ||
| using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; | ||
|
|
@@ -125,12 +127,15 @@ internal TestContextImplementation(ITestMethod? testMethod, string? testClassFul | |
|
|
||
| if (testClassFullName is not null) | ||
| { | ||
| _properties.Add(FullyQualifiedTestClassNameLabel, testClassFullName); | ||
| // Use indexer assignment instead of Add so that re-seeding from a parent property | ||
| // snapshot that may already contain this key does not throw. | ||
| _properties[FullyQualifiedTestClassNameLabel] = testClassFullName; | ||
| } | ||
|
|
||
| if (testMethod is not null) | ||
| { | ||
| _properties.Add(TestNameLabel, testMethod.Name); | ||
| // Use indexer assignment instead of Add for the same reason. | ||
| _properties[TestNameLabel] = testMethod.Name; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -281,6 +286,64 @@ public bool TryGetPropertyValue(string propertyName, out object? propertyValue) | |
| public void AddProperty(string propertyName, string propertyValue) | ||
| => _properties.Add(propertyName, propertyValue); | ||
|
|
||
| /// <summary> | ||
| /// Merges the given properties into this context's property bag using indexer semantics | ||
| /// (existing keys are overwritten, except the per-context labels | ||
| /// <see cref="TestContext.FullyQualifiedTestClassNameLabel"/> and | ||
| /// <see cref="TestContext.TestNameLabel"/>, which are preserved). | ||
| /// Used to flow properties set during <c>AssemblyInitialize</c> / <c>ClassInitialize</c> | ||
| /// into subsequent contexts. | ||
| /// </summary> | ||
| /// <param name="propertiesToMerge">The properties to merge in. May be <see langword="null"/>.</param> | ||
| internal void MergeProperties(IReadOnlyDictionary<string, object?>? propertiesToMerge) | ||
| { | ||
| if (propertiesToMerge is null) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| foreach (KeyValuePair<string, object?> kvp in propertiesToMerge) | ||
| { | ||
| // Never overwrite the per-context labels. | ||
| if (kvp.Key == FullyQualifiedTestClassNameLabel || kvp.Key == TestNameLabel) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| _properties[kvp.Key] = kvp.Value; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Captures a snapshot of the current property bag, excluding the per-context labels | ||
| /// (<see cref="TestContext.FullyQualifiedTestClassNameLabel"/> and | ||
| /// <see cref="TestContext.TestNameLabel"/>). The returned dictionary is intended to be | ||
| /// stored on a <c>TestAssemblyInfo</c> / <c>TestClassInfo</c> and later merged into other | ||
| /// contexts via <see cref="MergeProperties(IReadOnlyDictionary{string, object?}?)"/>. | ||
| /// <para> | ||
| /// The snapshot is shallow: keys and value references are copied as-is. Reference-type | ||
| /// values stored in the bag (e.g. a mocked file system, a connection pool, a list) are | ||
| /// shared across every context the snapshot is later merged into. Mutations of those | ||
| /// reference-type instances are visible everywhere. | ||
| /// </para> | ||
| /// </summary> | ||
| /// <returns>A read-only snapshot of the current properties.</returns> | ||
| internal IReadOnlyDictionary<string, object?> CaptureLifecycleProperties() | ||
| { | ||
| var snapshot = new Dictionary<string, object?>(_properties.Count); | ||
| foreach (KeyValuePair<string, object?> kvp in _properties) | ||
| { | ||
| if (kvp.Key == FullyQualifiedTestClassNameLabel || kvp.Key == TestNameLabel) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| snapshot[kvp.Key] = kvp.Value; | ||
| } | ||
|
|
||
| return new ReadOnlyDictionary<string, object?>(snapshot); | ||
|
Comment on lines
+333
to
+344
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed in the follow-up PR #8396: |
||
| } | ||
|
|
||
| /// <summary> | ||
| /// Result files attached. | ||
| /// </summary> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in the follow-up PR #8396:
PostAssemblyInitProperties(and the matchingPostClassInitPropertiesonTestClassInfo) now useVolatile.Read/Volatile.Write, replacing the temporaryTODOleft in the merged commit. The publishing thread does theVolatile.Writebefore theIsAssemblyInitializeExecutedflag flip; consumers Volatile-read the snapshot directly (the call site does not gate on the executed flag), so the snapshot field is the only thing that needs an acquire/release pair to be safely observed on the bypass-the-semaphore fast path.