// 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); }