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
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public Task ConsumeAsync(IDataProducer dataProducer, IData value, CancellationTo

// A node carries a single state property in practice; use FirstOrDefault rather than
// SingleOrDefault so a malformed producer can't throw out of the consumer pump.
TestNodeStateProperty? state = update.TestNode.Properties.OfType<TestNodeStateProperty>().FirstOrDefault();
TestNodeStateProperty? state = update.TestNode.Properties.FirstOrDefault<TestNodeStateProperty>();
if (state is null)
{
return Task.CompletedTask;
Expand Down Expand Up @@ -477,7 +477,7 @@ private static bool OverlapsAnyFailedWindow(VideoSegment segment, double[] faile

private (DateTimeOffset Start, DateTimeOffset End) ResolveTiming(TestNodeUpdateMessage update, string testUid)
{
TimingProperty? timing = update.TestNode.Properties.OfType<TimingProperty>().FirstOrDefault();
TimingProperty? timing = update.TestNode.Properties.FirstOrDefault<TimingProperty>();
if (timing is not null)
{
return (timing.GlobalTiming.StartTime, timing.GlobalTiming.EndTime);
Expand Down
38 changes: 38 additions & 0 deletions src/Platform/Microsoft.Testing.Platform/Messages/PropertyBag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,44 @@ public bool Any<TProperty>()
return found;
}

/// <summary>
/// Returns the first property of the <typeparamref name="TProperty"/> type, or default if none is found.
/// Unlike <see cref="SingleOrDefault{TProperty}"/>, this method does not throw when multiple properties of the
/// same type are present — it simply returns the first one encountered.
/// </summary>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <returns>The first property of the given type, or default if none is found.</returns>
public TProperty? FirstOrDefault<TProperty>()
Comment thread
Evangelink marked this conversation as resolved.
Comment thread
Evangelink marked this conversation as resolved.
where TProperty : IProperty
Comment thread
Evangelink marked this conversation as resolved.
{
if (_testNodeStateProperty is TProperty testNodeStateProperty)
{
return testNodeStateProperty;
}

// We don't want to walk the linked list if we know that we're looking for a TestNodeStateProperty.
if (typeof(TestNodeStateProperty).IsAssignableFrom(typeof(TProperty)))
{
return default;
}
Comment thread
Evangelink marked this conversation as resolved.

// Direct linked-list walk: avoids the array allocation from OfType<T>() and the subsequent
// LINQ FirstOrDefault() call. Early-returns on the first match so no duplicate tracking
// is needed (unlike SingleOrDefault<T>()).
Property? current = _property;
while (current is not null)
{
if (current.Current is TProperty match)
{
return match;
}

current = current.Next;
}

return default;
}

/// <summary>
/// Returns the only property of the <typeparamref name="TProperty"/> type, and throws an exception if there is not exactly one element.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#nullable enable
[TPEXP]Microsoft.Testing.Platform.Extensions.IBlockingDataConsumer
Microsoft.Testing.Platform.Extensions.Messages.PropertyBag.FirstOrDefault<TProperty>() -> TProperty?
[TPEXP]Microsoft.Testing.Platform.Extensions.TestHostControllers.ITestHostHandle
[TPEXP]Microsoft.Testing.Platform.Extensions.TestHostControllers.ITestHostHandle.ExitCode.get -> int
[TPEXP]Microsoft.Testing.Platform.Extensions.TestHostControllers.ITestHostHandle.HasExited.get -> bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,81 @@ public void SingleOrDefault_Should_Return_CorrectObject()
Assert.ThrowsExactly<InvalidOperationException>(property.SingleOrDefault<DummyProperty>);
}

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

// Exercises the _testNodeStateProperty fast path.
Assert.AreEqual(PassedTestNodeStateProperty.CachedInstance, property.FirstOrDefault<TestNodeStateProperty>());
}

[TestMethod]
public void FirstOrDefault_Should_Return_Null_WhenTestNodeStateProperty_NotPresent()
{
PropertyBag property = new();
property.Add(new DummyProperty());

// Exercises the IsAssignableFrom early-return without walking the linked list.
Assert.IsNull(property.FirstOrDefault<TestNodeStateProperty>());
}

[TestMethod]
public void FirstOrDefault_Should_Return_Null_WhenSubtype_NotPresent()
{
PropertyBag property = new();
property.Add(new FailedTestNodeStateProperty());

// Exercises the subtype guard: asking for a different TestNodeStateProperty subtype must return null.
Assert.IsNull(property.FirstOrDefault<PassedTestNodeStateProperty>());
}

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

Assert.AreEqual(prop, property.FirstOrDefault<DummyProperty>());
}

[TestMethod]
public void FirstOrDefault_Should_Return_FirstObject_WhenMultipleMatchesExist()
{
PropertyBag property = new();
DummyProperty prop1 = new();
DummyProperty prop2 = new();
property.Add(prop1);
property.Add(prop2);

// Unlike SingleOrDefault, FirstOrDefault must NOT throw when multiple matches exist.
DummyProperty? result = property.FirstOrDefault<DummyProperty>();
Assert.IsNotNull(result);
Assert.IsTrue(result == prop1 || result == prop2);
}

[TestMethod]
public void FirstOrDefault_Should_Return_Null_WhenNoMatchExists()
{
PropertyBag property = new();
property.Add(new DummyProperty());

// Exercises the linked-list miss path.
Assert.IsNull(property.FirstOrDefault<DummyProperty2>());
}

[TestMethod]
public void FirstOrDefault_Should_Return_Null_WhenBagIsEmpty()
{
PropertyBag property = new();

Assert.IsNull(property.FirstOrDefault<DummyProperty>());
Assert.IsNull(property.FirstOrDefault<TestNodeStateProperty>());
}

[TestMethod]
public void Single_Should_Return_CorrectObject()
{
Expand Down
Loading