Skip to content

Latest commit

 

History

History
391 lines (342 loc) · 13 KB

0009-Editors-API-RevisionUpdate.md

File metadata and controls

391 lines (342 loc) · 13 KB

0009 Editors API Revision Update

Summary

This note outlines the proposed changes for the JSON protocol between an Editor/IDE and the test platform.

Here is the link to v1 specs: Editors-API-Specification

Motivation

Here are the key factors:

  • Reducing size of the payload of message responses to improve performance. Refer to microsoft#396
  • Improvements to versioning and ability to handle future breaking changes.

Overview of changes

  • New data structure for communication for v2 and above.
  • Negotiation for protocol version: Editor and Test Runner, Test Runner and Test Host.
  • Json optimization and changes to payloads.

Messages for communication

Protocol related communication messages between Editor and Test Platform can have either of the following structures.

Message

This is existing Message structure used for communication in v1.

API Payload
Key Type Description
MessageType string Type of message
Payload object Payload for an operation. Can be null.
Example
{
    "MessageType": "ProtocolVersion",
    "Payload": 1
}

Versioned Message

This is the new data structure for Message introduced in v2. It has an additional version field.

API Payload
Key Type Description
MessageType string Type of message
Version int Version based on which message should be deserialized
Payload object Payload for an operation input or output. Can be null.
Example
{
  "MessageType": "Extensions.Initialize",
  "Version": 2,
  "Payload": [
    "E:\\UnitTest1\\UnitTest1\\bin\\Debug\\net452\\Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.dll"
  ]
}

Need for Versioned Message

A solution can contain projects that depend on different versions of Test Host. The tests from these projects can be run in parallel, which means Editor will be receiving test cases/results from both the Test Hosts. Editor will be able to deserialize both the messages, based on the version stamped on the responses.

Using Correct Message Structures

Message(v1) is used for protocol version negotiation in the beginning. For rest of the communication, the structure for messages is decided based on negotiated version.

  • If negotiated version = 1, Message (v1) structure will be used.
  • If negotiated version >= 2, Versioned Message (v2) will be used.

Since now we have two kind of messages, implementation should be capable of deserializing both kind of messages. For Versioned Messages, the embedded version will be used to choose the implementation for serializing and deserializing the payloads.

Version Negotiations

Editor uses the version operation to negotiate for the highest common version supported by the available Test platform. Similarly, Test Runner uses version operation to negotiate for the highest common version supported by the available Test host. All the components will have a range of versions they support.

Negotiation Between IDE and Test Runner

Editor sends version request with highest version. Test Runner then sends back the highest common version.

For Example:

  • Editor with range (1-3) sends Version(Request) with version = 3, and Test Runner with range (1-2) will send back version as 2.
  • Editor with range (1-2) sends version(Request) with version = 2, and Test Runner with range (1-3) will send back version as 2.

If Test Runner does not support the Editor, it will send ProtocolError message as discussed in the section below.

At this point the initial handshake between Editor and Test Runner is complete. Version operation is required only once per launch of test runner. Editor stamps the highest common version on the subsequent requests.

Note: For v1 though, since structure Message(v1) will be used, version is implicit.

Negotiation Between Test Runner and Test Host

Similarly, when the Test Runner receives run/discovery request, it launches and negotiates version with Test Host.

Example: Let us say, Editor supports range (1-3) and Test Platform Runner supports range (1-3), so the version stamped on the run/discovery request will be 3.

  • Test Host which supports range (1-2), will send back version as 2.
  • Test Host which supports range (1-3), will send back version as 3.

If the TestHost does not support the version, it will send ProtocolError message to Editor via Runner. Hence the version stamped on the response from Test Host will be the highest common version for all the three components.

Version (Request)

Editor will send this protocol version request with the highest version that it supports.

API Payload
Key Type Description
MessageType string ProtocolVersion
Payload int Highest supported version
Example
{
    "MessageType": "ProtocolVersion",
    "Payload": 1
}

Version (Response)

Test Runner sends back the highest common version as a response.

API Payload
Key Type Description
MessageType string ProtocolVersion
Payload int Highest common version
Example
{
    "MessageType": "ProtocolVersion",
    "Payload": 2
}

Version Error (Response)

If there is a mismatch, Test Runner or Test Host can send ProtocolError message.

API Payload
Key Type Description
MessageType string ProtocolError
Payload object String containing the supported range
Example
{
    "MessageType": "ProtocolError",
    "Payload": null
}

Note : The Payload should contain the following information

  • Component that reported the mismatch
  • The version range supported by the component

Json optimization

There are two objects that largely contribute to the size of the payload in the responses.

  • TestCase object
  • TestResult object

Both these objects contain properties that are well-known and few others that are added by the adapters. For the well-known properties, we have reduced the verbosity and made them self-describing. Please checkout the following requests and responses for details and examples.

Discover Tests

Start Discovery (Request)

For v2, TestDiscovery.Start request will have the negotiated version (i.e. 2 currently) as part of the message.

API Payload
Key Type Description
MessageType string TestDiscovery.Start
Version int Version of the message
Payload object See below
Message Payload
Key Type Description
Sources array Array of test container paths.
RunSettings string Run settings xml
Example
{
  "MessageType": "TestDiscovery.Start",
  "Version" : 2,
  "Payload": {
    "Sources": [
      ".\\samples\\UnitTestProject\\bin\\Debug\\netcoreapp1.0\\UnitTestProject.dll"
    ],
    "RunSettings": null
  }
}

Tests Found (Response)

TestFound response will also have the version based on which Editor will be able to deserialize the message. Verbosity for the json of TestCase object inside the TestFound payload has been reduced significantly.

API Payload
Key Type Description
MessageType string TestDiscovery.TestFound
Version string Version of the message
Payload array List of TestCases
Example
{
  "MessageType": "TestDiscovery.TestFound",
  "Version": 2,
  "Payload": [
    {
      "Id": "850ad69f-0dc9-fb92-8500-8d2f8d8dfe2a",
      "FullyQualifiedName": "_20TestProject.UnitTest1.Test1",
      "DisplayName": "Test1",
      "ExecutorUri": "executor://MSTestAdapter/v2",
      "Source": ".\\samples\\UnitTestProject\\bin\\Debug\\netcoreapp1.0\\UnitTestProject.dll",
      "CodeFilePath": null,
      "LineNumber": 0,
      "Properties": [
        {
          "Key": {
            "Id": "MSTestDiscovererv2.IsEnabled",
            "Label": "IsEnabled",
            "Category": "",
            "Description": "",
            "Attributes": 1,
            "ValueType": "System.Boolean"
          },
          "Value": true
        },
        {
          "Key": {
            "Id": "MSTestDiscovererv2.TestClassName",
            "Label": "ClassName",
            "Category": "",
            "Description": "",
            "Attributes": 1,
            "ValueType": "System.String"
          },
          "Value": "TestProject.UnitTest1"
        },
        {
          "Key": {
            "Id": "TestObject.Traits",
            "Label": "Traits",
            "Category": "",
            "Description": "",
            "Attributes": 5,
            "ValueType": "System.Collections.Generic.KeyValuePair`2[[System.String],[System.String]][]"
          },
          "Value": []
        }
      ]
    }
  ]
}

Similarly, DiscoveryComplete result will also have the version stamping.

Run Tests

Similar to discovery requests, run tests request will also have the version as part of the message. Here are the examples.

Run Tests (Request) Example

{
  "MessageType": "TestExecution.RunAllWithDefaultHost",
  "Version": 2,
  "Payload": {
    "Sources": [
      ".\\samples\\UnitTestProject\\bin\\Debug\\netcoreapp1.0\\UnitTestProject.dll"
    ],
    "TestCases": null,
    "RunSettings": null,
    "KeepAlive": false,
    "DebuggingEnabled": false
  }
}

Test Run Statistics (Response) Example

{
  "MessageType": "TestExecution.Completed",
  "Version": 2,
  "Payload": {
    "TestRunCompleteArgs": {
      "TestRunStatistics": {
        "ExecutedTests": 1,
        "Stats": {
          "Passed": 1
        }
      },
      "IsCanceled": false,
      "IsAborted": false,
      "Error": null,
      "AttachmentSets": [],
      "ElapsedTimeInRunningTests": "00:00:00.8677523"
    },
    "LastRunTests": {
      "NewTestResults": [
        {
          "TestCase": {
            "Id": "850ad69f-0dc9-fb92-8500-8d2f8d8dfe2a",
            "FullyQualifiedName": "_20TestProject.UnitTest1.Test1",
            "DisplayName": "Test1",
            "ExecutorUri": "executor://MSTestAdapter/v2",
            "Source": "C:\\\\Users\\\\sasin\\\\Documents\\\\Visual Studio 2017\\\\Projects\\\\20TestProject\\\\20TestProject\\\\bin\\\\Debug\\\\net452\\\\20TestProject.dll",
            "CodeFilePath": null,
            "LineNumber": 0,
            "Properties": [
              {
                "Key": {
                  "Id": "MSTestDiscovererv2.IsEnabled",
                  "Label": "IsEnabled",
                  "Category": "",
                  "Description": "",
                  "Attributes": 1,
                  "ValueType": "System.Boolean"
                },
                "Value": true
              },
              {
                "Key": {
                  "Id": "MSTestDiscovererv2.TestClassName",
                  "Label": "ClassName",
                  "Category": "",
                  "Description": "",
                  "Attributes": 1,
                  "ValueType": "System.String"
                },
                "Value": "_20TestProject.UnitTest1"
              },
              {
                "Key": {
                  "Id": "TestObject.Traits",
                  "Label": "Traits",
                  "Category": "",
                  "Description": "",
                  "Attributes": 5,
                  "ValueType": "System.Collections.Generic.KeyValuePair`2[[System.String],[System.String]][]"
                },
                "Value": []
              }
            ]
          },
          "Attachments": [],
          "Outcome": 1,
          "ErrorMessage": null,
          "ErrorStackTrace": null,
          "DisplayName": null,
          "Messages": [],
          "ComputerName": null,
          "Duration": "00:00:00.0072901",
          "StartTime": "2017-03-20T19:57:18.2262042+05:30",
          "EndTime": "2017-03-20T19:57:18.2921987+05:30",
          "Properties": []
        }
      ],
      "TestRunStatistics": {
        "ExecutedTests": 1,
        "Stats": {
          "Passed": 1
        }
      },
      "ActiveTests": []
    },
    "RunAttachments": [],
    "ExecutorUris": [
      "executor://mstestadapter/v2"
    ]
  }
}

All the other messages like Cancel, Abort or debugging related messages will follow the same pattern.