// 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.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
///
/// The unit test element.
///
#if NETFRAMEWORK
[Serializable]
#endif
[DebuggerDisplay("{TestMethod.DisplayName} ({TestMethod.ManagedTypeName})")]
internal sealed class UnitTestElement
{
///
/// Initializes a new instance of the class.
///
/// The test method.
/// Thrown when method is null.
public UnitTestElement(TestMethod testMethod)
{
if (testMethod is null)
{
throw new ArgumentNullException(nameof(testMethod));
}
DebugEx.Assert(testMethod.FullClassName != null, "Full className cannot be empty");
TestMethod = testMethod;
}
///
/// Gets the test method which should be executed as part of this test case.
///
public TestMethod TestMethod { get; private set; }
public TestDataSourceUnfoldingStrategy UnfoldingStrategy { get; set; } = TestDataSourceUnfoldingStrategy.Auto;
///
/// Gets or sets the test categories for test method.
///
public string[]? TestCategory { get; set; }
///
/// Gets or sets the traits for test method.
///
public TestTrait[]? Traits { get; set; }
///
/// Gets or sets the priority of the test method, if any.
///
public int? Priority { get; set; }
///
/// Gets or sets a value indicating whether this test method should not execute in parallel.
///
public bool DoNotParallelize { get; set; }
///
/// Gets or sets the resource locks declared on this test (via [ResourceLock] on the method and/or its
/// class), used by the parallel scheduler to serialize only the tests that contend on the same resource key.
/// The array is distinct by key and ordinally sorted by resource. when none are declared.
///
public ResourceLockInfo[]? ResourceLocks { get; set; }
///
/// Gets or sets the dependencies declared for this test (via [DependsOn] on the method and/or its
/// class, and/or the mstest:execution:dependencies section of testconfig.json), used by the
/// scheduler to run this test after its prerequisites and to skip it when they do not pass.
/// when none are declared.
///
public TestDependencyInfo[]? Dependencies { get; set; }
#if !WINDOWS_UWP && !WIN_UI
///
/// Gets or sets the deployment items for the test method.
///
public KeyValuePair[]? DeploymentItems { get; set; }
#endif
internal string? DeclaringFilePath { get; set; }
internal int? DeclaringLineNumber { get; set; }
///
/// Gets or sets the Work Item Ids for the test method.
///
internal string[]? WorkItemIds { get; set; }
///
/// Gets or sets host-provided execution context properties (for example the values historically read
/// from a test-case-management host: run id, plan id, build configuration, test point id, ...) that are
/// surfaced to the running test through TestContext. Keyed by the host property identifier so the
/// platform services layer does not depend on a specific test platform's property object model. This is
/// only populated when a host supplies such values (for internally discovered tests it stays null).
///
#if NETFRAMEWORK
// Consumed on the source-processing side before execution; the isolation host copy never reads it.
[field: NonSerialized]
#endif
internal IReadOnlyDictionary? ExecutionContextProperties { get; set; }
///
/// Gets or sets the optional host-provided lease that makes the canonical test activity ambient while user
/// code executes. The lease is process-local and is intentionally not serialized across AppDomain boundaries.
///
#if NETFRAMEWORK
[field: NonSerialized]
#endif
internal ITestExecutionActivityLease? ExecutionActivityLease { get; set; }
///
/// Gets or sets a value indicating whether the current execution path can carry the process-local activity
/// lease into the test runner. This is disabled when .NET Framework AppDomain isolation serializes the test
/// element across the boundary.
///
#if NETFRAMEWORK
[field: NonSerialized]
#endif
internal bool SupportsExecutionActivityLease { get; set; } = true;
///
/// Gets or sets the host's test case for this test, used to report its lifecycle and results back to the
/// host with full fidelity (preserving any host-injected data — such as test-case-management or
/// data-collector properties — that the neutral model does not otherwise carry) and to describe it to the
/// (still VSTest-based) deployment service. For tests handed to the adapter by a host it is that original
/// test case; for tests discovered internally by the platform services it is until
/// materialized on demand by the adapter's UnitTestElementExtensions.GetOrCreateHostTestCase. The execution engine treats it as an
/// opaque handle — it reads nothing VSTest-specific off it and only threads it into the result recorder and
/// the deployment boundary.
///
#if NETFRAMEWORK
// Result recording and deployment happen on the source-processing side; the isolation host copy never reads it.
[field: NonSerialized]
#endif
internal object? HostRecordingHandle { get; set; }
///
/// Gets or sets the lazily-computed stable test identifier (see UnitTestElementExtensions.GetTestId).
/// In the native Microsoft.Testing.Platform path GetTestId() is called twice per test per run (once when
/// recording the start and once when recording the result), both times with the same element instance, so the
/// computed value is cached here to avoid recomputing the hash. It must be cleared whenever a mutation changes a
/// hash input; the only such mutations after an element enters the execution pipeline change
/// via /
/// , which reset it.
///
#if NETFRAMEWORK
[field: NonSerialized]
#endif
internal Guid? CachedTestNodeUid { get; set; }
///
/// Gets or sets a value indicating whether native MTP discovery materialized this element from
/// a complete source-generated descriptor instead of the legacy runtime-method scan.
///
#if NETFRAMEWORK
[field: NonSerialized]
#endif
internal bool IsFromGeneratedDescriptor { get; set; }
internal UnitTestElement Clone()
{
var clone = (UnitTestElement)MemberwiseClone();
clone.TestMethod = TestMethod.Clone();
// Discovery specializes clones into individual data rows after cloning. Clear the cached id so changes to
// DataType/TestCaseIndex cannot reuse the parent test's identity when the parent was converted first.
clone.CachedTestNodeUid = null;
return clone;
}
// Legacy source-updating clone used only by the ToTestCase / test-case filter bridge
// (TestCaseExtensions.ToUnitTestElementWithUpdatedSource). It delegates to the buggy
// TestMethod.CloneWithUpdatedSource and is retained to preserve that path's exact current behavior; the
// execution engine uses WithUpdatedSource / CloneWithSource instead. Tracked by
// https://github.com/microsoft/testfx/issues/9573.
internal UnitTestElement CloneWithUpdatedSource(string source)
{
var clone = (UnitTestElement)MemberwiseClone();
clone.TestMethod = TestMethod.CloneWithUpdatedSource(source);
clone.CachedTestNodeUid = null;
return clone;
}
// Correct source-updating clone: the returned clone (only) targets the new source. Used by the execution
// engine via WithUpdatedSource. See https://github.com/microsoft/testfx/issues/9573.
internal UnitTestElement CloneWithSource(string source)
{
var clone = (UnitTestElement)MemberwiseClone();
clone.TestMethod = TestMethod.CloneWithSource(source);
clone.CachedTestNodeUid = null;
return clone;
}
///
/// Returns this element when it already targets , otherwise a clone whose test
/// method points at . This mirrors the source resolution the adapter previously
/// performed while converting a host test case, so a deployed source (relocated to the deployment
/// directory) is honored without reloading the assembly from its original location (see
/// https://github.com/microsoft/testfx/issues/6713).
///
/// The (possibly deployment-relocated) source of the test.
/// An element whose is .
internal UnitTestElement WithUpdatedSource(string source)
=> TestMethod.AssemblyName == source ? this : CloneWithSource(source);
}