File: Serialization\TestResultConverter.cs
Web Access
Project: src\src\vstest\src\Microsoft.TestPlatform.CommunicationUtilities\Microsoft.TestPlatform.CommunicationUtilities.csproj (Microsoft.TestPlatform.CommunicationUtilities)
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#if NETCOREAPP

using System;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

using Microsoft.VisualStudio.TestPlatform.ObjectModel;

namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Serialization;

/// <summary>
/// Converter used by v1 protocol serializer to serialize TestResult object to and from v1 json
/// </summary>
internal class TestResultConverter : JsonConverter<TestResult>
{
    /// <inheritdoc/>
    public override TestResult Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        using var doc = JsonDocument.ParseValue(ref reader);
        var data = doc.RootElement;

        var testCaseElement = data.GetProperty("TestCase");
        var testCase = JsonSerializer.Deserialize<TestCase>(testCaseElement, options)!;
        var testResult = new TestResult(testCase);

        // Add attachments for the result
        if (data.TryGetProperty("Attachments", out var attachments) && attachments.GetArrayLength() > 0)
        {
            foreach (var attachment in attachments.EnumerateArray())
            {
                if (attachment.ValueKind != JsonValueKind.Null)
                {
                    testResult.Attachments.Add(JsonSerializer.Deserialize<AttachmentSet>(attachment, options)!);
                }
            }
        }

        // Add messages for the result
        if (data.TryGetProperty("Messages", out var messages) && messages.GetArrayLength() > 0)
        {
            foreach (var message in messages.EnumerateArray())
            {
                if (message.ValueKind != JsonValueKind.Null)
                {
                    testResult.Messages.Add(JsonSerializer.Deserialize<TestResultMessage>(message, options)!);
                }
            }
        }

        if (!data.TryGetProperty("Properties", out var properties) || properties.GetArrayLength() == 0)
        {
            return testResult;
        }

        // Every class that inherits from TestObject uses a properties store for <Property, Object>
        // key value pairs.
        foreach (var property in properties.EnumerateArray())
        {
            var testProperty = JsonSerializer.Deserialize<TestProperty>(property.GetProperty("Key"), options)!;

            // Let the null values be passed in as null data
            var token = property.GetProperty("Value");
            string? propertyData = null;
            if (token.ValueKind != JsonValueKind.Null)
            {
                // If the property is already a string. No need to convert again.
                if (token.ValueKind == JsonValueKind.String)
                {
                    propertyData = token.GetString();
                }
                else
                {
                    // On deserialization, the value for each TestProperty is always a string. It is up
                    // to the consumer to deserialize it further as appropriate.
                    propertyData = token.GetRawText().Trim('"');
                }
            }

            switch (testProperty.Id)
            {
                case "TestResult.DisplayName":
                    testResult.DisplayName = propertyData; break;
                case "TestResult.ComputerName":
                    testResult.ComputerName = propertyData ?? string.Empty; break;
                case "TestResult.Outcome":
                    testResult.Outcome = (TestOutcome)Enum.Parse(typeof(TestOutcome), propertyData!); break;
                case "TestResult.Duration":
                    testResult.Duration = TimeSpan.Parse(propertyData!, CultureInfo.InvariantCulture); break;
                case "TestResult.StartTime":
                    testResult.StartTime = DateTimeOffset.Parse(propertyData!, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); break;
                case "TestResult.EndTime":
                    testResult.EndTime = DateTimeOffset.Parse(propertyData!, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); break;
                case "TestResult.ErrorMessage":
                    testResult.ErrorMessage = propertyData; break;
                case "TestResult.ErrorStackTrace":
                    testResult.ErrorStackTrace = propertyData; break;
                default:
                    // No need to register member properties as they get registered as part of TestResultProperties class.
                    testProperty = TestProperty.Register(testProperty.Id, testProperty.Label, testProperty.GetValueType(), testProperty.Attributes, typeof(TestObject));
                    testResult.SetPropertyValue(testProperty, propertyData);
                    break;
            }
        }

        return testResult;
    }

    /// <inheritdoc/>
    public override void Write(Utf8JsonWriter writer, TestResult value, JsonSerializerOptions options)
    {
        // P2 to P1
        writer.WriteStartObject();
        writer.WritePropertyName("TestCase");
        JsonSerializer.Serialize(writer, value.TestCase, options);
        writer.WritePropertyName("Attachments");
        JsonSerializer.Serialize(writer, value.Attachments, options);
        writer.WritePropertyName("Messages");
        JsonSerializer.Serialize(writer, value.Messages, options);

        writer.WritePropertyName("Properties");
        writer.WriteStartArray();

        // Version Note: In 15.0.0, if some properties in TestResult were not set, they were not serialized.
        // Starting 15.1.0, test platform sends in default values for properties that were not set. This is not
        // a breaking change.

        // TestResult.Outcome
        writer.WriteStartObject();
        WriteProperty(writer, TestResultProperties.Outcome, options);
        writer.WriteNumberValue((int)value.Outcome);
        writer.WriteEndObject();

        // TestResult.ErrorMessage
        writer.WriteStartObject();
        WriteProperty(writer, TestResultProperties.ErrorMessage, options);
        writer.WriteStringValue(value.ErrorMessage);
        writer.WriteEndObject();

        // TestResult.ErrorStackTrace
        writer.WriteStartObject();
        WriteProperty(writer, TestResultProperties.ErrorStackTrace, options);
        writer.WriteStringValue(value.ErrorStackTrace);
        writer.WriteEndObject();

        // TestResult.DisplayName
        writer.WriteStartObject();
        WriteProperty(writer, TestResultProperties.DisplayName, options);
        writer.WriteStringValue(value.DisplayName);
        writer.WriteEndObject();

        // TestResult.ComputerName
        writer.WriteStartObject();
        WriteProperty(writer, TestResultProperties.ComputerName, options);
        writer.WriteStringValue(value.ComputerName ?? string.Empty);
        writer.WriteEndObject();

        // TestResult.Duration
        writer.WriteStartObject();
        WriteProperty(writer, TestResultProperties.Duration, options);
        writer.WriteStringValue(value.Duration.ToString());
        writer.WriteEndObject();

        // TestResult.StartTime
        writer.WriteStartObject();
        WriteProperty(writer, TestResultProperties.StartTime, options);
        writer.WriteStringValue(value.StartTime);
        writer.WriteEndObject();

        // TestResult.EndTime
        writer.WriteStartObject();
        WriteProperty(writer, TestResultProperties.EndTime, options);
        writer.WriteStringValue(value.EndTime);
        writer.WriteEndObject();

        foreach (var property in value.GetProperties())
        {
            JsonSerializer.Serialize(writer, property, options);
        }

        writer.WriteEndArray();
        writer.WriteEndObject();
    }

    private static void WriteProperty(Utf8JsonWriter writer, TestProperty property, JsonSerializerOptions options)
    {
        writer.WritePropertyName("Key");
        JsonSerializer.Serialize(writer, property, options);
        writer.WritePropertyName("Value");
    }
}

#endif