// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Immutable;
using System.Runtime.Loader;
using Microsoft.MSTestV2.CLIAutomation;
using Microsoft.Testing.Platform.Acceptance.IntegrationTests;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using TestResult = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult;
namespace MSTest.Acceptance.IntegrationTests;
///
/// Regression guard for filtering over the neutral UnitTestElement. Discovery matches
/// element.ToTestCase(), while execution reconstructs a UnitTestElement from each incoming
/// before matching. These tests preserve the object-model assertions around the
/// TestCase to UnitTestElement round trip for the two identity properties most sensitive to it.
///
[TestClass]
[DoNotParallelize]
public sealed class TestCaseFilteringTests : AcceptanceTestBase
{
private const string AssetName = "DiscoverInternalsFiltering";
private const string TargetDisplayName = "TopLevelInternalClass_TestMethod1";
private static readonly Lock AssetAssemblyLock = new();
private static Assembly? s_assetAssembly;
[TestMethod]
public void FilterByFullyQualifiedNameSelectsExactlyTheMatchingTestDuringDiscovery()
{
TestCase target = GetTargetTestCase();
ImmutableArray filtered = DiscoverTests($"FullyQualifiedName={target.FullyQualifiedName}");
Assert.HasCount(1, filtered);
Assert.AreEqual(target.FullyQualifiedName, filtered[0].FullyQualifiedName);
Assert.AreEqual(target.Id, filtered[0].Id);
}
[TestMethod]
public void FilterByIdSelectsExactlyTheMatchingTestDuringDiscovery()
{
TestCase target = GetTargetTestCase();
ImmutableArray filtered = DiscoverTests($"Id={target.Id}");
Assert.HasCount(1, filtered);
Assert.AreEqual(target.FullyQualifiedName, filtered[0].FullyQualifiedName);
Assert.AreEqual(target.Id, filtered[0].Id);
}
[TestMethod]
public async Task FilterByFullyQualifiedNameSelectsExactlyTheMatchingTestDuringExecution()
{
ImmutableArray allTests = DiscoverTests();
TestCase target = allTests.First(testCase => testCase.DisplayName == TargetDisplayName);
SimulateCrossProcessTestCases(allTests);
ImmutableArray results = await CLITestBase.RunTestsAsync(
allTests,
$"FullyQualifiedName={target.FullyQualifiedName}");
Assert.HasCount(1, results);
Assert.AreEqual(target.FullyQualifiedName, results[0].TestCase.FullyQualifiedName);
Assert.AreEqual(TestOutcome.Passed, results[0].Outcome);
}
[TestMethod]
public async Task FilterByIdSelectsExactlyTheMatchingTestDuringExecution()
{
ImmutableArray allTests = DiscoverTests();
Guid targetId = allTests.First(testCase => testCase.DisplayName == TargetDisplayName).Id;
SimulateCrossProcessTestCases(allTests);
ImmutableArray results = await CLITestBase.RunTestsAsync(allTests, $"Id={targetId}");
Assert.HasCount(1, results);
Assert.AreEqual(targetId, results[0].TestCase.Id);
Assert.AreEqual(TestOutcome.Passed, results[0].Outcome);
}
private static TestCase GetTargetTestCase()
=> DiscoverTests().First(testCase => testCase.DisplayName == TargetDisplayName);
private static ImmutableArray DiscoverTests(string? testCaseFilter = null)
{
EnsureAssetAssemblyIsLoaded();
return CLITestBase.DiscoverTests(AssetFixture.AssemblyPath, testCaseFilter);
}
private static void EnsureAssetAssemblyIsLoaded()
{
lock (AssetAssemblyLock)
{
s_assetAssembly ??= AssemblyLoadContext.Default.LoadFromAssemblyPath(AssetFixture.AssemblyPath);
}
}
// Deserialized VSTest test cases carry no in-process adapter payload. Clearing LocalExtensionData
// deliberately forces execution to reconstruct UnitTestElement and regenerate TestCase identity.
private static void SimulateCrossProcessTestCases(ImmutableArray testCases)
{
foreach (TestCase testCase in testCases)
{
testCase.LocalExtensionData = null;
}
}
public sealed class TestAssetFixture : TestAssetFixtureBase
{
public string TargetAssetPath => GetAssetPath(AssetName);
public string AssemblyPath
=> Path.Combine(TargetAssetPath, "bin", "Release", TargetFrameworks.NetCurrent, $"{AssetName}.dll");
public override (string ID, string Name, string Code) GetAssetsToGenerate() => (AssetName, AssetName,
SourceCode
.PatchTargetFrameworks(TargetFrameworks.NetCurrent)
.PatchCodeWithReplace("$MSTestVersion$", MSTestVersion));
private const string SourceCode = """
#file DiscoverInternalsFiltering.csproj
true
$TargetFrameworks$
#file UnitTest1.cs
using Microsoft.VisualStudio.TestTools.UnitTesting;
[assembly: DiscoverInternals]
namespace DiscoverInternalsFiltering;
[TestClass]
internal class TopLevelInternalClass
{
[TestMethod]
public void TopLevelInternalClass_TestMethod1()
{
}
[TestClass]
internal class NestedInternalClass
{
[TestMethod]
public void NestedInternalClass_TestMethod1()
{
}
}
}
""";
}
}