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
33 changes: 31 additions & 2 deletions src/Platform/Microsoft.Testing.Platform/Messages/PropertyBag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,38 @@ public TProperty[] OfType<TProperty>()
}

// We don't want to allocate an array if we know that we're looking for a TestNodeStateProperty
return typeof(TestNodeStateProperty).IsAssignableFrom(typeof(TProperty))
if (typeof(TestNodeStateProperty).IsAssignableFrom(typeof(TProperty)) || _property is null)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Coverage] The || _property is null branch is new (the original ternary had _property is null ? [] as its own arm). There's currently no test for calling OfType<SomeDummyProperty>() on a PropertyBag that contains only a TestNodeStateProperty (i.e., _testNodeStateProperty is set but _property is null).

Impact: The early-return path added here goes unverified. If a future refactor accidentally removes it, a silent linear walk over a null reference would produce incorrect results.

Suggestion: Add a test case to PropertyBagTests:

[TestMethod]
public void OfType_WithOnlyTestNodeStateProperty_ReturnsEmpty()
{
    PropertyBag property = new();
    property.Add(PassedTestNodeStateProperty.CachedInstance);
    // _property is null; _testNodeStateProperty is set — exercises the new early-return path
    Assert.IsEmpty(property.OfType<DummyProperty>());
}

{
return [];
}

// Direct linked-list walk: avoids allocating a yield-iterator state machine
// (the original code called _property.OfType<TProperty>() which uses yield return).
TProperty? first = default;
bool foundAny = false;
List<TProperty>? overflow = null;
Property? current = _property;
while (current is not null)
{
if (current.Current is TProperty match)
{
if (!foundAny)
{
first = match;
foundAny = true;
}
else
{
(overflow ??= [first!]).Add(match);
}
}

current = current.Next;
}

return !foundAny
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Coverage] The !foundAny ? [] return at the end of the while-loop (no matching property found in a non-empty bag) is not tested. DummyProperty2 is used in Single / SingleOrDefault tests, but OfType<DummyProperty2>() is never called on a bag that has other properties.

Impact: The new manual traversal diverges from the old LINQ-based _property.OfType<TProperty>(). A bug that incorrectly returns null or throws instead of [] when no match is found would go undetected.

Suggestion: Add one assertion to the existing OfType_Should_Return_CorrectObject test:

// No DummyProperty2 in the bag — exercises the "no match found" path in the while-loop
Assert.IsEmpty(property.OfType<DummyProperty2>());

? []
: _property is null ? [] : [.. _property.OfType<TProperty>()];
: overflow is not null ? [.. overflow] : [first!];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Coverage] The single-item fast path ([first!] when overflow is null) is not exercised by any existing test. OfType_Should_Return_CorrectObject only tests with 2 DummyProperty instances, which goes through the overflow branch. The [first!] path needs its own case.

Impact: A regression where a single-result bag returns [] or throws would not be caught.

Suggestion: Extend OfType_Should_Return_CorrectObject or add a dedicated test:

PropertyBag bag = new();
DummyProperty single = new();
bag.Add(single);
bag.Add(PassedTestNodeStateProperty.CachedInstance);
TProperty[] result = bag.OfType<DummyProperty>();
Assert.HasCount(1, result);
Assert.AreSame(single, result[0]);

}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,33 @@ public void OfType_Should_Return_CorrectObject()

Assert.AreEqual(PassedTestNodeStateProperty.CachedInstance, property.OfType<TestNodeStateProperty>().Single());
Assert.HasCount(2, property.OfType<DummyProperty>());

// No DummyProperty2 in the bag — exercises the "no match found" path in the while-loop
Assert.IsEmpty(property.OfType<DummyProperty2>());
}

[TestMethod]
public void OfType_WithSingleMatch_ReturnsSingleItemArray()
{
PropertyBag property = new();
DummyProperty singleProperty = new();
property.Add(singleProperty);
property.Add(PassedTestNodeStateProperty.CachedInstance);

DummyProperty[] result = property.OfType<DummyProperty>();

Assert.HasCount(1, result);
Assert.AreSame(singleProperty, result[0]);
}

[TestMethod]
public void OfType_WithOnlyTestNodeStateProperty_ReturnsEmpty()
{
PropertyBag property = new();
property.Add(PassedTestNodeStateProperty.CachedInstance);

// _property is null; _testNodeStateProperty is set — exercises the new early-return path
Assert.IsEmpty(property.OfType<DummyProperty>());
}

[TestMethod]
Expand Down
Loading