Skip to content
Open
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 @@ -3,7 +3,7 @@ title: Build extensions for Microsoft.Testing.Platform (MTP)
description: Learn how to create in-process and out-of-process extensions for Microsoft.Testing.Platform (MTP).
author: MarcoRossignoli
ms.author: mrossignoli
ms.date: 02/24/2026
ms.date: 06/02/2026
ai-usage: ai-assisted
---

Expand All @@ -17,6 +17,9 @@ For the full extension point summary and in-process/out-of-process concepts, see

The testing platform provides additional extensibility points that allow you to customize the behavior of the platform and the test framework. These extensibility points are optional and can be used to enhance the testing experience.

> [!TIP]
> Each extension shown in this article includes a manual registration snippet (for example, `builder.TestHost.AddDataConsumer(...)`). If you're shipping your extension as a NuGet package, you can let consumers skip the manual call by exposing a `TestingPlatformBuilderHook` and a small MSBuild props file. The auto-generated entry point will then invoke your hook automatically. For details, see [Auto-register your extension with `TestingPlatformBuilderHook`](#auto-register-your-extension-with-testingplatformbuilderhook).

### The `ICommandLineOptionsProvider` extensions

> [!NOTE]
Expand Down Expand Up @@ -499,6 +502,93 @@ The `ITestHostProcessInformation` interface provides the following details:
* `ExitCode`: The exit code of the process. This value is only available within the `OnTestHostProcessExitedAsync` method. Attempting to access it within the `OnTestHostProcessStartedAsync` method will result in an exception.
* `HasExitedGracefully`: A boolean value indicating whether the test host has crashed. If true, it signifies that the test host did not exit gracefully.

## Auto-register your extension with `TestingPlatformBuilderHook`

Every preceding extension section shows a *manual* registration call (for example, `builder.TestHost.AddDataConsumer(...)`). Asking consumers to edit their `Main` method is a poor onboarding experience. The [`Microsoft.Testing.Platform.MSBuild`](https://www.nuget.org/packages/Microsoft.Testing.Platform.MSBuild) package solves this by generating a `SelfRegisteredExtensions.AddSelfRegisteredExtensions(builder, args)` method that runs from the autogenerated entry point. To plug your extension into that generated method, ship two artifacts in your NuGet package:

- A public static `TestingPlatformBuilderHook` class with an `AddExtensions` method that registers your extension.
- An MSBuild props file that declares a `<TestingPlatformBuilderHook>` item pointing at that class.

When a consumer installs your package, the MSBuild integration picks up the item and generates the call into your hook, and your extension is registered with no code changes on the consumer side.

> [!NOTE]
> Auto-registration only works when the consumer has `Microsoft.Testing.Platform.MSBuild` in their project (it's included transitively by MSTest, NUnit, and xUnit runners) and hasn't opted out by setting `<GenerateTestingPlatformEntryPoint>false</GenerateTestingPlatformEntryPoint>`. Consumers who disable the autogenerated entry point still need to call your manual registration API from their `Main` method.

### Create the hook class

Add a `public static class TestingPlatformBuilderHook` in your extension assembly with an `AddExtensions(ITestApplicationBuilder, string[])` method that performs the same registration users would otherwise call manually:

```csharp
using Microsoft.Testing.Platform.Builder;

namespace Contoso.MyExtension;

public static class TestingPlatformBuilderHook
{
public static void AddExtensions(ITestApplicationBuilder testApplicationBuilder, string[] arguments)
=> testApplicationBuilder.AddMyExtension();
}
```

The class name doesn't have to be `TestingPlatformBuilderHook` — the MSBuild item points at it by full type name — but using that name keeps your code consistent with the in-box extensions like `Microsoft.Testing.Extensions.Retry` and `Microsoft.Testing.Extensions.HotReload`.

The method must:

* Be `public static`.
* Have a first parameter of type `Microsoft.Testing.Platform.Builder.ITestApplicationBuilder`.
* Have a second parameter of type `string[]` (the command-line arguments passed to the test host). You can ignore it if your extension doesn't need it.
* Return `void`.

### Declare the MSBuild item

Ship a props file under `buildMultiTargeting/<PackageId>.props` inside your NuGet package. Declare a `<TestingPlatformBuilderHook>` item that points the MSBuild task at your hook class:

```xml
<Project>
<ItemGroup>
<TestingPlatformBuilderHook Include="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx">
<DisplayName>Contoso.MyExtension</DisplayName>
<TypeFullName>Contoso.MyExtension.TestingPlatformBuilderHook</TypeFullName>
</TestingPlatformBuilderHook>
</ItemGroup>
</Project>
```

The metadata is as follows:

* `Include`: A GUID that uniquely identifies your hook. See [The `Include` GUID is a random identifier](#the-include-guid-is-a-random-identifier).
* `DisplayName`: The friendly name shown in MSBuild diagnostic messages when the entry point is generated. Use your package or extension name.
* `TypeFullName`: The fully qualified name of the `TestingPlatformBuilderHook` class you created previously. The MSBuild task uses this to emit `global::Contoso.MyExtension.TestingPlatformBuilderHook.AddExtensions(builder, args);` into the generated entry point.

### The `Include` GUID is a random identifier

The GUID in the `Include` attribute is **not** the same as your extension's [`IExtension.Uid`](./microsoft-testing-platform-architecture.md#the-iextension-interface). It's a registration identifier used by the MSBuild task to deduplicate hooks across NuGet references and (in a few well-known cases) to order them.

When you author a new extension, generate a brand new GUID and hard-code it in your props file. Some ways to generate one:

* PowerShell: `[guid]::NewGuid()`
* Visual Studio: **Tools** > **Create GUID**
* `uuidgen` on Linux and macOS

> [!IMPORTANT]
> Never copy a GUID from another extension's props file (whether shipped by Microsoft or by a third party). Two extensions that share the same `Include` value are treated as duplicates: only one hook is invoked, so your extension silently fails to register.

> [!NOTE]
> Once you ship a GUID, treat it as permanent. Changing it in a later release is harmless on its own, but reusing the *old* value for a different hook in a future package version can confuse consumers who have both versions in their dependency graph during an upgrade.

### Verify the hook is wired up

After installing your package in a test project that uses `Microsoft.Testing.Platform.MSBuild`, build the project and inspect the generated `SelfRegisteredExtensions.g.cs` file under `obj/<Configuration>/<TargetFramework>/`. You should see a call into your hook, for example:

```csharp
public static void AddSelfRegisteredExtensions(this global::Microsoft.Testing.Platform.Builder.ITestApplicationBuilder builder, string[] args)
{
global::Contoso.MyExtension.TestingPlatformBuilderHook.AddExtensions(builder, args);
}
```

If the call is missing, double-check that the props file is packaged under `buildMultiTargeting/` (not `build/`) inside the `.nupkg`, that `DisplayName` and `TypeFullName` metadata are present, and that the consumer hasn't set `<GenerateTestingPlatformEntryPoint>false</GenerateTestingPlatformEntryPoint>`.

## Extensions execution order

The testing platform consists of a [testing framework](./microsoft-testing-platform-architecture-test-framework.md#test-framework-extension) and any number of extensions that can operate [*in-process*](./microsoft-testing-platform-architecture.md#in-process-vs-out-of-process-extensions) or [*out-of-process*](./microsoft-testing-platform-architecture.md#in-process-vs-out-of-process-extensions). This document outlines the **sequence of calls** to all potential extensibility points to provide clarity on when a feature is anticipated to be invoked:
Expand Down
6 changes: 5 additions & 1 deletion docs/core/testing/unit-testing-with-dotnet-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ title: Testing with 'dotnet test'
description: Learn more about how 'dotnet test' works and its support for VSTest and Microsoft.Testing.Platform (MTP)
author: Youssef1313
ms.author: ygerges
ms.date: 03/26/2025
ms.date: 06/05/2026
ai-usage: ai-assisted
---

# Testing with 'dotnet test'
Expand Down Expand Up @@ -158,6 +159,9 @@ For users of MTP that are using the VSTest mode of `dotnet test`, there are few
1. If passing a specific project (or directory containing project), for example, `dotnet test MyProject.csproj`, this should become `dotnet test --project MyProject.csproj`.
1. If passing a specific dll, for example, `dotnet test path/to/UnitTests.dll`, this should become `dotnet test --test-modules path/to/UnitTests.dll`. Note that `--test-modules` also supports globbing.

> [!TIP]
> Even though `--` is no longer required in MTP mode, you can still use it to mark where test application arguments begin. The separator avoids a parser quirk where unrecognized arguments change meaning when interleaved with options that `dotnet test` understands. The separator also protects your scripts if `dotnet test` later starts recognizing one of those tokens. For more information, see [Forward arguments to the test application](../tools/dotnet-test-mtp.md#forward-arguments-to-the-test-application).

## Solutions with mixed test frameworks or extensions

When a solution contains test projects that use different test frameworks (for example, MSTest and xUnit.net) or different sets of extensions, running `dotnet test` with framework-specific or extension-specific command-line options can fail. Options that are valid for one project are unrecognized by another, causing exit code 5 (invalid command-line arguments). For example:
Expand Down
6 changes: 6 additions & 0 deletions docs/core/tools/dotnet-pack.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,12 @@ By default, `dotnet pack` builds the project first. If you wish to avoid this be
dotnet pack --runtime win-x64
```

- Pack the project in the current directory into a deterministic package (.NET 10.0.400 and later):

```dotnetcli
dotnet pack -p:Deterministic=true -p:DeterministicTimestamp="2026-12-19T16:39:57-08:00"
```

- Pack the project using a *.nuspec* file (MSBuild project-based approach):

```dotnetcli
Expand Down
25 changes: 24 additions & 1 deletion docs/core/tools/dotnet-run.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: dotnet run command
description: The dotnet run command provides a convenient option to run your application from the source code.
ms.date: 09/29/2025
ms.date: 06/05/2026
---
# dotnet run

Expand Down Expand Up @@ -61,6 +61,29 @@ To run the application, the `dotnet run` command resolves the dependencies of th

Any arguments that aren't recognized by `dotnet run` are passed to the application. To separate arguments for `dotnet run` from arguments for the application, use the `--` option.

## Forward arguments to the application

`dotnet run` forwards any token it doesn't recognize to the application. The forwarded tokens keep their original order, but `dotnet run` first removes the options it understands. When a recognized option appears between an unrecognized option name and its value, removing the recognized option can change the meaning of the leftover tokens.

For example, the following command interleaves the recognized option `--project` between tokens the application is meant to receive:

```dotnetcli
dotnet run --app-flag --app-name --project ConsoleApp.csproj A.txt
```

After `dotnet run` consumes `--project ConsoleApp.csproj`, the application receives `--app-flag --app-name A.txt`. The application then treats `A.txt` as the value of `--app-name`, which doesn't match the original command line.

To avoid this ambiguity, place application arguments after a literal `--`:

```dotnetcli
dotnet run --project ConsoleApp.csproj -- --app-flag --app-name A.txt
```

The `--` separator marks every following token as an application argument, so `dotnet run` doesn't reorder or reinterpret them. The separator also future-proofs scripts against new `dotnet run` options that might later match a token previously forwarded to the application.

> [!NOTE]
> The same behavior applies to `dotnet build` and to `dotnet test` in Microsoft.Testing.Platform (MTP) mode, which forward unrecognized tokens to MSBuild or to the test application respectively. For more information about `dotnet test`, see [Forward arguments to the test application](dotnet-test-mtp.md#forward-arguments-to-the-test-application).

## Options

- **`--`**
Expand Down
12 changes: 11 additions & 1 deletion docs/core/tools/dotnet-test-mtp.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: dotnet test command with Microsoft.Testing.Platform (MTP)
description: The dotnet test command is used to execute unit tests in a given project using MTP.
ms.date: 02/03/2026
ms.date: 06/05/2026
ai-usage: ai-assisted
---
# dotnet test with Microsoft.Testing.Platform (MTP)
Expand Down Expand Up @@ -168,6 +168,16 @@ With MTP, `dotnet test` operates faster than with VSTest. The test-related argum
> [!NOTE]
> To enable trace logging to a file, use the environment variable `DOTNET_CLI_TEST_TRACEFILE` to provide the path to the trace file.

## Forward arguments to the test application

`dotnet test` forwards any token it doesn't recognize to the test application. When a recognized option appears between an unrecognized option name and its value, removing the recognized option can change how the leftover tokens bind to options in the test application. To avoid this ambiguity, place test application arguments after a literal `--`:

```dotnetcli
dotnet test --results-directory TestResults -- --report-trx --report-trx-filename A.trx
```

The same parser behavior applies to `dotnet run` and `dotnet build`. For a detailed example, see [Forward arguments to the application](dotnet-run.md#forward-arguments-to-the-application) in the `dotnet run` reference.

## Examples

- Run the tests in the project or solution in the current directory:
Expand Down
2 changes: 1 addition & 1 deletion docs/csharp/fundamentals/object-oriented/inheritance.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: "Objected oriented programming - inheritance"
title: "Object-oriented programming - inheritance"
description: Inheritance in C# enables you to create new classes that reuse, extend, and modify the behavior defined in other classes.
ms.date: 05/14/2021
helpviewer_keywords:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// <ChangeCalendar>
using System.Globalization;

DateTime date1 = new(2011, 6, 20);

Console.OutputEncoding = System.Text.Encoding.UTF8;
DisplayCurrentInfo();
// Display the date using the current culture and calendar.
Console.WriteLine(date1.ToString("d"));
Console.WriteLine();

CultureInfo arSA = CultureInfo.CreateSpecificCulture("ar-SA");

// Change the current culture to Arabic (Saudi Arabia).
CultureInfo.CurrentCulture = arSA;
// Display date and information about the current culture.
DisplayCurrentInfo();
Console.WriteLine(date1.ToString("d"));
Console.WriteLine();

// Change the calendar to Hijri.
Calendar hijri = new HijriCalendar();
if (CalendarExists(arSA, hijri))
{
arSA.DateTimeFormat.Calendar = hijri;
// Display date and information about the current culture.
DisplayCurrentInfo();
Console.WriteLine(date1.ToString("d"));
}

static void DisplayCurrentInfo()
{
Console.WriteLine($"Current Culture: {CultureInfo.CurrentCulture.Name}");
Console.WriteLine($"Current Calendar: {DateTimeFormatInfo.CurrentInfo.Calendar}");
}

static bool CalendarExists(CultureInfo culture, Calendar cal) =>
culture.OptionalCalendars.Any(optional => optional.ToString() == cal.ToString());
// The example displays the following output:
// Current Culture: en-US
// Current Calendar: System.Globalization.GregorianCalendar
// 6/20/2011
//
// Current Culture: ar-SA
// Current Calendar: System.Globalization.UmAlQuraCalendar
// 18‏‏/7‏‏/1432 بعد الهجرة
//
// Current Culture: ar-SA
// Current Calendar: System.Globalization.HijriCalendar
// 19‏‏/7‏‏/1432 بعد الهجرة
// </ChangeCalendar>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>ChangeCalendar</RootNamespace>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
' Visual Basic .NET Document
Option Strict On

' <Snippet2>
' <ChangeCalendar>
Imports System.Globalization
Imports System.Threading

Module Example
Public Sub Main()
Dim date1 As Date = #6/20/2011#

Console.OutputEncoding = System.Text.Encoding.UTF8
DisplayCurrentInfo()
' Display the date using the current culture and calendar.
Console.WriteLine(date1.ToString("d"))
Expand All @@ -17,7 +14,7 @@ Module Example
Dim arSA As CultureInfo = CultureInfo.CreateSpecificCulture("ar-SA")

' Change the current culture to Arabic (Saudi Arabia).
Thread.CurrentThread.CurrentCulture = arSA
CultureInfo.CurrentCulture = arSA
' Display date and information about the current culture.
DisplayCurrentInfo()
Console.WriteLine(date1.ToString("d"))
Expand All @@ -34,18 +31,12 @@ Module Example
End Sub

Private Sub DisplayCurrentInfo()
Console.WriteLine("Current Culture: {0}",
CultureInfo.CurrentCulture.Name)
Console.WriteLine("Current Calendar: {0}",
DateTimeFormatInfo.CurrentInfo.Calendar)
Console.WriteLine($"Current Culture: {CultureInfo.CurrentCulture.Name}")
Console.WriteLine($"Current Calendar: {DateTimeFormatInfo.CurrentInfo.Calendar}")
End Sub

Private Function CalendarExists(ByVal culture As CultureInfo,
cal As Calendar) As Boolean
For Each optionalCalendar As Calendar In culture.OptionalCalendars
If cal.ToString().Equals(optionalCalendar.ToString()) Then Return True
Next
Return False
Private Function CalendarExists(culture As CultureInfo, cal As Calendar) As Boolean
Return culture.OptionalCalendars.Any(Function(optional1) optional1.ToString() = cal.ToString())
End Function
End Module
' The example displays the following output:
Expand All @@ -55,9 +46,9 @@ End Module
'
' Current Culture: ar-SA
' Current Calendar: System.Globalization.UmAlQuraCalendar
' 18/07/32
' 18‏‏/7‏‏/1432 بعد الهجرة
'
' Current Culture: ar-SA
' Current Calendar: System.Globalization.HijriCalendar
' 19/07/32
' </Snippet2>
' 19‏‏/7‏‏/1432 بعد الهجرة
' </ChangeCalendar>
6 changes: 4 additions & 2 deletions docs/standard/datetime/working-with-calendars.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,10 @@ The calendar currently in use by a particular <xref:System.Globalization.Culture

The following example changes the calendar used by the Arabic (Saudi Arabia) culture. It first instantiates a <xref:System.DateTime> value and displays it using the current culture - which, in this case, is English (United States) - and the current culture's calendar (which, in this case, is the Gregorian calendar). Next, it changes the current culture to Arabic (Saudi Arabia) and displays the date using its default Um Al-Qura calendar. It then calls the `CalendarExists` method to determine whether the Hijri calendar is supported by the Arabic (Saudi Arabia) culture. Because the calendar is supported, it changes the current calendar to Hijri and again displays the date. Note that in each case, the date is displayed using the current culture's current calendar.

[!code-csharp[Conceptual.Calendars#2](../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.calendars/cs/changecalendar2.cs#2)]
[!code-vb[Conceptual.Calendars#2](../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.calendars/vb/changecalendar2.vb#2)]
:::code language="csharp" source="./snippets/working-with-calendars/csharp/Program.cs" id="ChangeCalendar":::
:::code language="vb" source="./snippets/working-with-calendars/vb/Program.vb" id="ChangeCalendar":::

Before the example writes to the console, it changes the console's output encoding to UTF-8 so that Arabic letters display correctly.

## Dates and calendars

Expand Down
Loading
Loading