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 @@ -37,10 +37,25 @@ private static void RegisterTestNodeSerializers()
});

// Serialize event types.
Serializers[typeof(TestNodeStateChangedEventArgs)] = new ObjectSerializer<TestNodeStateChangedEventArgs>(ev => new Dictionary<string, object?>
Serializers[typeof(TestNodeStateChangedEventArgs)] = new ObjectSerializer<TestNodeStateChangedEventArgs>(ev =>
{
[JsonRpcStrings.RunId] = ev.RunId,
[JsonRpcStrings.Changes] = ev.Changes?.Select(ch => Serialize(ch)).ToList<object>(),
List<object>? changes = null;
if (ev.Changes is not null)
{
#pragma warning disable IDE0028 // Collection initialization can be simplified - capacity hint is intentional.
changes = new(ev.Changes.Length);
#pragma warning restore IDE0028
for (int i = 0; i < ev.Changes.Length; i++)
{
changes.Add(Serialize(ev.Changes[i]));
}
}

return new Dictionary<string, object?>
{
[JsonRpcStrings.RunId] = ev.RunId,
[JsonRpcStrings.Changes] = changes,
};
});

Serializers[typeof(TestNode)] = new ObjectSerializer<TestNode>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,20 @@ public TestNodeStateChangedEventArgs BuildAggregatedChange()

TestNodeUpdateMessage[] changes = terminalTestNodeUids is null
? [.. _stateChanges]
: [.. _stateChanges.Where(stateChange =>
stateChange.TestNode.Properties.SingleOrDefault<TestNodeStateProperty>() is not InProgressTestNodeStateProperty
|| !terminalTestNodeUids.Contains(stateChange.TestNode.Uid))];
: FilterStateChanges(_stateChanges, terminalTestNodeUids);

return new(RunId, changes);
}

// Keep the captured LINQ predicate out of BuildAggregatedChange so batches without terminal states
// do not allocate its closure.
private static TestNodeUpdateMessage[] FilterStateChanges(
List<TestNodeUpdateMessage> stateChanges,
HashSet<TestNodeUid> terminalTestNodeUids)
=> [.. stateChanges.Where(stateChange =>
stateChange.TestNode.Properties.SingleOrDefault<TestNodeStateProperty>() is not InProgressTestNodeStateProperty
|| !terminalTestNodeUids.Contains(stateChange.TestNode.Uid))];

#pragma warning disable CS0618, MTP0001
private static bool IsTerminalState(TestNodeStateProperty? state)
=> state is PassedTestNodeStateProperty
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,57 @@ public FormatterUtilitiesTests()
Assert.AreEqual("Jsonite", _formatter.Id);
#endif

[TestMethod]
public async Task Serialize_TestNodeStateChangedEventArgs_NullChanges_PreservesNull()
{
TestNodeStateChangedEventArgs message = new(Guid.Empty, null);

IDictionary<string, object?> properties = SerializerUtilities.Serialize(message);
string serialized = (await _formatter.SerializeAsync(message)).Replace(" ", string.Empty);

Assert.IsNull(properties[JsonRpcStrings.Changes]);
Assert.AreEqual("""{"runId":"00000000-0000-0000-0000-000000000000","changes":null}""", serialized);
}

[TestMethod]
public async Task Serialize_TestNodeStateChangedEventArgs_EmptyChanges_PreservesEmptyObjectList()
{
TestNodeStateChangedEventArgs message = new(Guid.Empty, []);

IDictionary<string, object?> properties = SerializerUtilities.Serialize(message);
string serialized = (await _formatter.SerializeAsync(message)).Replace(" ", string.Empty);

List<object> changes = Assert.IsInstanceOfType<List<object>>(properties[JsonRpcStrings.Changes]);
Assert.IsEmpty(changes);
Assert.AreEqual("""{"runId":"00000000-0000-0000-0000-000000000000","changes":[]}""", serialized);
}

[TestMethod]
public async Task Serialize_TestNodeStateChangedEventArgs_Changes_PreservesOrderObjectTypesAndSource()
{
TestNodeUpdateMessage first = CreateTestNodeUpdate("first");
TestNodeUpdateMessage second = CreateTestNodeUpdate("second");
TestNodeUpdateMessage[] source = [first, second];
TestNodeStateChangedEventArgs message = new(Guid.Empty, source);

IDictionary<string, object?> properties = SerializerUtilities.Serialize(message);
string serialized = (await _formatter.SerializeAsync(message)).Replace(" ", string.Empty);

List<object> changes = Assert.IsInstanceOfType<List<object>>(properties[JsonRpcStrings.Changes]);
Assert.HasCount(2, changes);
IDictionary<string, object?> firstSerializedChange = Assert.IsInstanceOfType<IDictionary<string, object?>>(changes[0]);
IDictionary<string, object?> secondSerializedChange = Assert.IsInstanceOfType<IDictionary<string, object?>>(changes[1]);
IDictionary<string, object?> firstSerializedNode = Assert.IsInstanceOfType<IDictionary<string, object?>>(firstSerializedChange[JsonRpcStrings.Node]);
IDictionary<string, object?> secondSerializedNode = Assert.IsInstanceOfType<IDictionary<string, object?>>(secondSerializedChange[JsonRpcStrings.Node]);
Assert.AreEqual("first", firstSerializedNode[JsonRpcStrings.Uid]);
Assert.AreEqual("second", secondSerializedNode[JsonRpcStrings.Uid]);
Assert.AreSame(first, source[0]);
Assert.AreSame(second, source[1]);
Assert.AreEqual(
"""{"runId":"00000000-0000-0000-0000-000000000000","changes":[{"node":{"uid":"first","display-name":"first","node-type":"group"},"parent":null},{"node":{"uid":"second","display-name":"second","node-type":"group"},"parent":null}]}""",
serialized);
}

[TestMethod]
public void CanDeserializeTaskResponse()
{
Expand Down Expand Up @@ -667,6 +718,15 @@ static TestNode GetSampleTestNode()
}
}

private static TestNodeUpdateMessage CreateTestNodeUpdate(string uid)
=> new(
default,
new TestNode
{
Uid = new TestNodeUid(uid),
DisplayName = uid,
});

private object Deserialize(Type type, string instanceSerialized)
=> true switch
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,32 @@ public void BuildAggregatedChange_SeparateAggregatorBatches_DoNotInfluenceEachOt
Assert.AreSame(terminal, secondAggregatedChange.Changes[0]);
}

[TestMethod]
public void BuildAggregatedChange_DoesNotMutateBufferedChanges()
{
ServerTestHost.TestNodeStateChangeAggregator aggregator = new(Guid.NewGuid());
TestNodeUpdateMessage before = CreateUpdate("before");
TestNodeUpdateMessage inProgress = CreateUpdate("test", InProgressTestNodeStateProperty.CachedInstance);
TestNodeUpdateMessage terminal = CreateUpdate("test", PassedTestNodeStateProperty.CachedInstance);
aggregator.OnStateChange(before);
aggregator.OnStateChange(inProgress);
aggregator.OnStateChange(terminal);

TestNodeStateChangedEventArgs firstAggregatedChange = aggregator.BuildAggregatedChange();
terminal.TestNode.Properties._testNodeStateProperty = DiscoveredTestNodeStateProperty.CachedInstance;
TestNodeStateChangedEventArgs secondAggregatedChange = aggregator.BuildAggregatedChange();

Assert.IsNotNull(firstAggregatedChange.Changes);
Assert.IsNotNull(secondAggregatedChange.Changes);
Assert.HasCount(2, firstAggregatedChange.Changes);
Assert.HasCount(3, secondAggregatedChange.Changes);
Assert.AreSame(before, firstAggregatedChange.Changes[0]);
Assert.AreSame(terminal, firstAggregatedChange.Changes[1]);
Assert.AreSame(before, secondAggregatedChange.Changes[0]);
Assert.AreSame(inProgress, secondAggregatedChange.Changes[1]);
Assert.AreSame(terminal, secondAggregatedChange.Changes[2]);
}

public static IEnumerable<object[]> TerminalStates()
{
yield return [PassedTestNodeStateProperty.CachedInstance];
Expand Down