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 @@ -13,12 +13,13 @@ namespace Microsoft.Testing.Platform.IPC.Serializers;
* TestHostCompletedRequestSerializer: 1
* TestHostProcessPIDRequestSerializer: 2
* CommandLineOptionMessagesSerializer: 3
* ModuleSerializer: 4
* DiscoveredTestMessageSerializer: 5
* TestResultMessageSerializer: 6
* FileArtifactMessageSerializer: 7
* (4 is reserved - previously used by a removed serializer)
* DiscoveredTestMessagesSerializer: 5
* TestResultMessagesSerializer: 6
* FileArtifactMessagesSerializer: 7
* TestSessionEventSerializer: 8
* HandshakeMessageSerializer: 9
* TestInProgressMessagesSerializer: 10
*/

[Embedded]
Expand All @@ -35,5 +36,6 @@ public static void RegisterAllSerializers(this NamedPipeBase namedPipeBase)
namedPipeBase.RegisterSerializer(new FileArtifactMessagesSerializer(), typeof(FileArtifactMessages));
namedPipeBase.RegisterSerializer(new TestSessionEventSerializer(), typeof(TestSessionEvent));
namedPipeBase.RegisterSerializer(new HandshakeMessageSerializer(), typeof(HandshakeMessage));
namedPipeBase.RegisterSerializer(new TestInProgressMessagesSerializer(), typeof(TestInProgressMessages));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,21 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella
await _dotnetTestConnection.SendMessageAsync(testResultMessages).ConfigureAwait(false);
break;

case TestStates.InProgress:
// Non-IDE consumers (e.g. `dotnet test` with MTP) render in-progress events as a
// separate "currently running tests" panel; they don't expect them as TestResultMessages.
TestInProgressMessages inProgressMessages = new(
ExecutionId,
DotnetTestConnection.InstanceId,
[
new TestInProgressMessage(
testNodeUpdateMessage.TestNode.Uid.Value,
testNodeUpdateMessage.TestNode.DisplayName),
]);

await _dotnetTestConnection.SendMessageAsync(inProgressMessages).ConfigureAwait(false);
break;

case TestStates.Failed:
case TestStates.Error:
case TestStates.Timeout:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.Testing.Platform.IPC.Models;

internal sealed record TestInProgressMessage(string? Uid, string? DisplayName);

internal sealed record TestInProgressMessages(string? ExecutionId, string? InstanceId, TestInProgressMessage[] InProgressMessages) : IRequest;
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,18 @@ internal static class HandshakeMessageFieldsId
{
public const int MessagesSerializerId = 9;
}

internal static class TestInProgressMessagesFieldsId
{
public const int MessagesSerializerId = 10;

public const ushort ExecutionId = 1;
public const ushort InstanceId = 2;
public const ushort TestInProgressMessageList = 3;
}

internal static class TestInProgressMessageFieldsId
{
public const ushort Uid = 1;
public const ushort DisplayName = 2;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Testing.Platform.IPC.Models;

namespace Microsoft.Testing.Platform.IPC.Serializers;

/*
|---FieldCount---| 2 bytes

|---ExecutionId Id---| (2 bytes)
|---ExecutionId Size---| (4 bytes)
|---ExecutionId Value---| (n bytes)

|---InstanceId Id---| (2 bytes)
|---InstanceId Size---| (4 bytes)
|---InstanceId Value---| (n bytes)

|---TestInProgressMessageList Id---| (2 bytes)
|---TestInProgressMessageList Size---| (4 bytes)
|---TestInProgressMessageList Value---| (n bytes)
|---TestInProgressMessageList Length---| (4 bytes)

|---TestInProgressMessageList[0] FieldCount---| 2 bytes

|---TestInProgressMessageList[0].Uid Id---| (2 bytes)
|---TestInProgressMessageList[0].Uid Size---| (4 bytes)
|---TestInProgressMessageList[0].Uid Value---| (n bytes)

|---TestInProgressMessageList[0].DisplayName Id---| (2 bytes)
|---TestInProgressMessageList[0].DisplayName Size---| (4 bytes)
|---TestInProgressMessageList[0].DisplayName Value---| (n bytes)
*/

internal sealed class TestInProgressMessagesSerializer : NamedPipeSerializer<TestInProgressMessages>, INamedPipeSerializer
{
public override int Id => TestInProgressMessagesFieldsId.MessagesSerializerId;

protected override TestInProgressMessages DeserializeCore(Stream stream)
{
string? executionId = null;
string? instanceId = null;
TestInProgressMessage[]? inProgressMessages = [];

ushort fieldCount = ReadUShort(stream);

for (int i = 0; i < fieldCount; i++)
{
int fieldId = ReadUShort(stream);
int fieldSize = ReadInt(stream);

switch (fieldId)
{
case TestInProgressMessagesFieldsId.ExecutionId:
executionId = ReadStringValue(stream, fieldSize);
break;

case TestInProgressMessagesFieldsId.InstanceId:
instanceId = ReadStringValue(stream, fieldSize);
break;

case TestInProgressMessagesFieldsId.TestInProgressMessageList:
inProgressMessages = ReadInProgressMessagesPayload(stream);
break;

default:
// If we don't recognize the field id, skip the payload corresponding to that field
SetPosition(stream, stream.Position + fieldSize);
break;
}
}

return new(executionId, instanceId, inProgressMessages);
}

private static TestInProgressMessage[] ReadInProgressMessagesPayload(Stream stream)
{
int length = ReadInt(stream);
var inProgressMessages = new TestInProgressMessage[length];
for (int i = 0; i < length; i++)
{
string? uid = null;
string? displayName = null;

int fieldCount = ReadUShort(stream);

for (int j = 0; j < fieldCount; j++)
{
int fieldId = ReadUShort(stream);
int fieldSize = ReadInt(stream);

switch (fieldId)
{
case TestInProgressMessageFieldsId.Uid:
uid = ReadStringValue(stream, fieldSize);
break;

case TestInProgressMessageFieldsId.DisplayName:
displayName = ReadStringValue(stream, fieldSize);
break;

default:
SetPosition(stream, stream.Position + fieldSize);
break;
}
}

inProgressMessages[i] = new TestInProgressMessage(uid, displayName);
}

return inProgressMessages;
}

protected override void SerializeCore(TestInProgressMessages objectToSerialize, Stream stream)
{
RoslynDebug.Assert(stream.CanSeek, "We expect a seekable stream.");

WriteUShort(stream, GetFieldCount(objectToSerialize));

WriteField(stream, TestInProgressMessagesFieldsId.ExecutionId, objectToSerialize.ExecutionId);
WriteField(stream, TestInProgressMessagesFieldsId.InstanceId, objectToSerialize.InstanceId);
WriteInProgressMessagesPayload(stream, objectToSerialize.InProgressMessages);
}

private static void WriteInProgressMessagesPayload(Stream stream, TestInProgressMessage[]? inProgressMessageList)
{
if (inProgressMessageList is null || inProgressMessageList.Length == 0)
{
return;
}

WriteUShort(stream, TestInProgressMessagesFieldsId.TestInProgressMessageList);

// We will reserve an int (4 bytes)
// so that we fill the size later, once we write the payload
WriteInt(stream, 0);

long before = stream.Position;
WriteInt(stream, inProgressMessageList.Length);
foreach (TestInProgressMessage inProgressMessage in inProgressMessageList)
{
WriteUShort(stream, GetFieldCount(inProgressMessage));

WriteField(stream, TestInProgressMessageFieldsId.Uid, inProgressMessage.Uid);
WriteField(stream, TestInProgressMessageFieldsId.DisplayName, inProgressMessage.DisplayName);
}

// NOTE: We are able to seek only if we are using a MemoryStream
// thus, the seek operation is fast as we are only changing the value of a property
WriteAtPosition(stream, (int)(stream.Position - before), before - sizeof(int));
}

private static ushort GetFieldCount(TestInProgressMessages inProgressMessages) =>
(ushort)((inProgressMessages.ExecutionId is null ? 0 : 1) +
(inProgressMessages.InstanceId is null ? 0 : 1) +
(IsNullOrEmpty(inProgressMessages.InProgressMessages) ? 0 : 1));

private static ushort GetFieldCount(TestInProgressMessage inProgressMessage) =>
(ushort)((inProgressMessage.Uid is null ? 0 : 1) +
(inProgressMessage.DisplayName is null ? 0 : 1));
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,53 @@ public void HandshakeMessageWithNullProperties()
Assert.IsEmpty(actual.Properties);
}

[TestMethod]
public void TestInProgressMessagesSerializeDeserialize()
{
object serializer = new TestInProgressMessagesSerializer();

var message = new TestInProgressMessages(
"MyExecId",
"MyInstId",
[
new TestInProgressMessage("uid-1", "Display 1"),
new TestInProgressMessage("uid-2", null),
new TestInProgressMessage(null, "Display 3"),
]);

var stream = new MemoryStream();
Serialize(serializer, message, stream);
stream.Seek(0, SeekOrigin.Begin);
var actual = (TestInProgressMessages)Deserialize(serializer, stream);

Assert.AreEqual(message.ExecutionId, actual.ExecutionId);
Assert.AreEqual(message.InstanceId, actual.InstanceId);
Assert.HasCount(message.InProgressMessages.Length, actual.InProgressMessages);
for (int i = 0; i < message.InProgressMessages.Length; i++)
{
Assert.AreEqual(message.InProgressMessages[i].Uid, actual.InProgressMessages[i].Uid);
Assert.AreEqual(message.InProgressMessages[i].DisplayName, actual.InProgressMessages[i].DisplayName);
}
}

[TestMethod]
public void TestInProgressMessagesSerializeDeserialize_EmptyList()
{
object serializer = new TestInProgressMessagesSerializer();

var message = new TestInProgressMessages("execId", "instId", []);

var stream = new MemoryStream();
Serialize(serializer, message, stream);
stream.Seek(0, SeekOrigin.Begin);
var actual = (TestInProgressMessages)Deserialize(serializer, stream);

Assert.AreEqual(message.ExecutionId, actual.ExecutionId);
Assert.AreEqual(message.InstanceId, actual.InstanceId);
Assert.IsNotNull(actual.InProgressMessages);
Assert.HasCount(0, actual.InProgressMessages);
}

private static void Serialize<TMessage>(object serializer, TMessage message, Stream stream)
=> serializer.GetType()
.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
Expand Down