// 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.Testing.Platform.CommandLine;
using Microsoft.Testing.Platform.Extensions.Messages;
using Microsoft.Testing.Platform.Helpers;
using Microsoft.Testing.Platform.OutputDevice;
using Microsoft.Testing.Platform.Services;
using Microsoft.Testing.Platform.Telemetry;

using Moq;

namespace Microsoft.Testing.Platform.UnitTests;

[TestClass]
public sealed class TestApplicationResultTests : IDisposable
{
    private readonly TestApplicationResult _testApplicationResult
        = new(new Mock<IOutputDevice>().Object, new Mock<ICommandLineOptions>().Object, new Mock<IEnvironment>().Object, new Mock<IStopPoliciesService>().Object, null);

    public void Dispose() => _testApplicationResult.Dispose();

    [TestMethod]
    public async Task ConsumeAsync_ExecutionCompleted_ClosesOnlyOldestActivityWithoutOutcome()
    {
        var firstActivity = new Mock<IPlatformActivity>();
        var secondActivity = new Mock<IPlatformActivity>();
        var otelService = new Mock<IPlatformOpenTelemetryService>();
        otelService
            .Setup(service => service.CreateCounter<int>(It.IsAny<string>(), It.IsAny<string?>(), It.IsAny<string?>(), It.IsAny<IEnumerable<KeyValuePair<string, object?>>?>()))
            .Returns(Mock.Of<ICounter<int>>());
        otelService
            .Setup(service => service.CreateUpDownCounter<int>(It.IsAny<string>(), It.IsAny<string?>(), It.IsAny<string?>(), It.IsAny<IEnumerable<KeyValuePair<string, object?>>?>()))
            .Returns(Mock.Of<IUpDownCounter<int>>());
        otelService
            .Setup(service => service.CreateHistogram<double>(It.IsAny<string>(), It.IsAny<string?>(), It.IsAny<string?>(), It.IsAny<IEnumerable<KeyValuePair<string, object?>>?>()))
            .Returns(Mock.Of<IHistogram<double>>());
        otelService
            .SetupSequence(service => service.StartActivity(
                It.IsAny<string>(),
                It.IsAny<IEnumerable<KeyValuePair<string, object?>>?>(),
                It.IsAny<string?>(),
                It.IsAny<DateTimeOffset>()))
            .Returns(firstActivity.Object)
            .Returns(secondActivity.Object);
        using TestApplicationResult testApplicationResult = new(
            Mock.Of<IOutputDevice>(),
            Mock.Of<ICommandLineOptions>(),
            Mock.Of<IEnvironment>(),
            Mock.Of<IStopPoliciesService>(),
            otelService.Object);
        static TestNode CreateNode(IProperty property) => new()
        {
            Uid = "shared-uid",
            DisplayName = "Test",
            Properties = new PropertyBag(property),
        };

        await testApplicationResult.ConsumeAsync(
            new DummyProducer(),
            new TestNodeUpdateMessage(default, CreateNode(InProgressTestNodeStateProperty.CachedInstance)),
            CancellationToken.None);
        await testApplicationResult.ConsumeAsync(
            new DummyProducer(),
            new TestNodeUpdateMessage(default, CreateNode(InProgressTestNodeStateProperty.CachedInstance)),
            CancellationToken.None);

        await testApplicationResult.ConsumeAsync(
            new DummyProducer(),
            new TestNodeUpdateMessage(default, CreateNode(TestNodeExecutionCompletedProperty.CachedInstance)),
            CancellationToken.None);

        firstActivity.Verify(activity => activity.Dispose(), Times.Once);
        firstActivity.Verify(activity => activity.SetTag("test.result", It.IsAny<object?>()), Times.Never);
        secondActivity.Verify(activity => activity.Dispose(), Times.Never);

        await testApplicationResult.ConsumeAsync(
            new DummyProducer(),
            new TestNodeUpdateMessage(default, CreateNode(PassedTestNodeStateProperty.CachedInstance)),
            CancellationToken.None);

        secondActivity.Verify(activity => activity.SetTag("test.result", "passed"), Times.Once);
        secondActivity.Verify(activity => activity.Dispose(), Times.Once);
    }

    [TestMethod]
    public async Task GetProcessExitCode_WithCoverageThresholdFailure_ReturnsCoverageThresholdFailed()
    {
        Mock<ITestCoverageResult> coverageResult = new();
        coverageResult.SetupGet(result => result.HasThresholdFailure).Returns(true);
        using TestApplicationResult testApplicationResult = new(
            new Mock<IOutputDevice>().Object,
            new CommandLineOption(PlatformCommandLineProvider.ZeroTestsPolicyOptionKey, [PlatformCommandLineProvider.ZeroTestsPolicyAllowSkippedArgument]),
            new Mock<IEnvironment>().Object,
            new Mock<IStopPoliciesService>().Object,
            null,
            coverageResult.Object);
        await testApplicationResult.ConsumeAsync(new DummyProducer(), new TestNodeUpdateMessage(
            default,
            new TestNode
            {
                Uid = new TestNodeUid("id"),
                DisplayName = "DisplayName",
                Properties = new PropertyBag(PassedTestNodeStateProperty.CachedInstance),
            }), CancellationToken.None);

        Assert.AreEqual((int)ExitCode.CoverageThresholdFailed, testApplicationResult.GetProcessExitCode());
    }

    [TestMethod]
    public void GetProcessExitCode_WithNoTestsAndCoverageThresholdFailure_ReturnsZeroTests()
    {
        Mock<ITestCoverageResult> coverageResult = new();
        coverageResult.SetupGet(result => result.HasThresholdFailure).Returns(true);
        using TestApplicationResult testApplicationResult = new(
            new Mock<IOutputDevice>().Object,
            new Mock<ICommandLineOptions>().Object,
            new Mock<IEnvironment>().Object,
            new Mock<IStopPoliciesService>().Object,
            null,
            coverageResult.Object);

        Assert.AreEqual((int)ExitCode.ZeroTests, testApplicationResult.GetProcessExitCode());
    }

    [TestMethod]
    public async Task ConsumeAsync_SupersededRetryAttempt_DoesNotCloseTheTestActivity()
    {
        // A test framework that retries in-process reports one in-progress update (which opens the OpenTelemetry
        // activity) followed by one result per attempt. Only the final attempt is the test's outcome, so only it
        // may close the activity - otherwise the first superseded result would close it and the real outcome would
        // find nothing in flight, reporting the retried test as failed in traces even though it passed.
        var activity = new Mock<IPlatformActivity>();
        var otelService = new Mock<IPlatformOpenTelemetryService>();
        otelService
            .Setup(service => service.CreateCounter<int>(It.IsAny<string>(), It.IsAny<string?>(), It.IsAny<string?>(), It.IsAny<IEnumerable<KeyValuePair<string, object?>>?>()))
            .Returns(Mock.Of<ICounter<int>>());
        otelService
            .Setup(service => service.CreateUpDownCounter<int>(It.IsAny<string>(), It.IsAny<string?>(), It.IsAny<string?>(), It.IsAny<IEnumerable<KeyValuePair<string, object?>>?>()))
            .Returns(Mock.Of<IUpDownCounter<int>>());
        otelService
            .Setup(service => service.CreateHistogram<double>(It.IsAny<string>(), It.IsAny<string?>(), It.IsAny<string?>(), It.IsAny<IEnumerable<KeyValuePair<string, object?>>?>()))
            .Returns(Mock.Of<IHistogram<double>>());
        otelService
            .Setup(service => service.StartActivity(
                It.IsAny<string>(),
                It.IsAny<IEnumerable<KeyValuePair<string, object?>>?>(),
                It.IsAny<string?>(),
                It.IsAny<DateTimeOffset>()))
            .Returns(activity.Object);
        using TestApplicationResult testApplicationResult = new(
            Mock.Of<IOutputDevice>(),
            Mock.Of<ICommandLineOptions>(),
            Mock.Of<IEnvironment>(),
            Mock.Of<IStopPoliciesService>(),
            otelService.Object);

        static TestNode CreateNode(params IProperty[] properties) => new()
        {
            Uid = "flaky-uid",
            DisplayName = "FlakyTest",
            Properties = new PropertyBag(properties),
        };

        // The in-progress update is reported once per test (not once per attempt) and opens the activity.
        await testApplicationResult.ConsumeAsync(
            new DummyProducer(),
            new TestNodeUpdateMessage(default, CreateNode(InProgressTestNodeStateProperty.CachedInstance)),
            CancellationToken.None);

        // Attempt 1 failed but was superseded: it must neither close the activity nor mark it failed.
        await testApplicationResult.ConsumeAsync(
            new DummyProducer(),
            new TestNodeUpdateMessage(default, CreateNode(new FailedTestNodeStateProperty("boom"), new RetryAttemptProperty(1, isSuperseded: true))),
            CancellationToken.None);

        activity.Verify(a => a.Dispose(), Times.Never);

        // Attempt 2 passed and is the final outcome: it closes the activity exactly once.
        await testApplicationResult.ConsumeAsync(
            new DummyProducer(),
            new TestNodeUpdateMessage(default, CreateNode(PassedTestNodeStateProperty.CachedInstance, new RetryAttemptProperty(2, isSuperseded: false))),
            CancellationToken.None);

        activity.Verify(a => a.Dispose(), Times.Once);
        Assert.AreEqual((int)ExitCode.Success, testApplicationResult.GetProcessExitCode());
    }

    [TestMethod]
    public async Task ConsumeAsync_SupersededRetryAttempt_IsNotCountedAsFailure()
    {
        static TestNode CreateNode(IProperty state, IProperty retryAttempt) => new()
        {
            Uid = new TestNodeUid("flaky-test"),
            DisplayName = "FlakyTest",
            Properties = new PropertyBag(state, retryAttempt),
        };

        // Attempt 1 failed but was retried, attempt 2 passed: the test's outcome is "passed".
        await _testApplicationResult.ConsumeAsync(
            new DummyProducer(),
            new TestNodeUpdateMessage(default, CreateNode(new FailedTestNodeStateProperty("boom"), new RetryAttemptProperty(1, isSuperseded: true))),
            CancellationToken.None);
        await _testApplicationResult.ConsumeAsync(
            new DummyProducer(),
            new TestNodeUpdateMessage(default, CreateNode(PassedTestNodeStateProperty.CachedInstance, new RetryAttemptProperty(2, isSuperseded: false))),
            CancellationToken.None);

        Assert.AreEqual((int)ExitCode.Success, _testApplicationResult.GetProcessExitCode());
        Assert.AreEqual(0, _testApplicationResult.GetStatistics().TotalFailedTests);
    }

    [TestMethod]
    public async Task ConsumeAsync_FinalRetryAttemptFailed_IsCountedAsFailure()
    {
        static TestNode CreateNode(IProperty state, IProperty retryAttempt) => new()
        {
            Uid = new TestNodeUid("always-failing-test"),
            DisplayName = "AlwaysFailingTest",
            Properties = new PropertyBag(state, retryAttempt),
        };

        await _testApplicationResult.ConsumeAsync(
            new DummyProducer(),
            new TestNodeUpdateMessage(default, CreateNode(new FailedTestNodeStateProperty("boom"), new RetryAttemptProperty(1, isSuperseded: true))),
            CancellationToken.None);
        await _testApplicationResult.ConsumeAsync(
            new DummyProducer(),
            new TestNodeUpdateMessage(default, CreateNode(new FailedTestNodeStateProperty("boom"), new RetryAttemptProperty(2, isSuperseded: false))),
            CancellationToken.None);

        Assert.AreEqual((int)ExitCode.AtLeastOneTestFailed, _testApplicationResult.GetProcessExitCode());
        Assert.AreEqual(1, _testApplicationResult.GetStatistics().TotalFailedTests);
    }

    [TestMethod]
    public async Task GetProcessExitCodeAsync_If_All_Skipped_ByDefault_Returns_Success()
    {
        await _testApplicationResult.ConsumeAsync(new DummyProducer(), new TestNodeUpdateMessage(
            default,
            new TestNode
            {
                Uid = new TestNodeUid("id"),
                DisplayName = "DisplayName",
                Properties = new PropertyBag(SkippedTestNodeStateProperty.CachedInstance),
            }), CancellationToken.None);

        // Default policy is 'allow-skipped', so an all-skipped run is treated as successful.
        Assert.AreEqual((int)ExitCode.Success, _testApplicationResult.GetProcessExitCode());
    }

    [TestMethod]
    public async Task GetProcessExitCodeAsync_If_All_Skipped_With_StrictPolicy_Returns_ZeroTestsRan()
    {
        using TestApplicationResult testApplicationResult
            = new(
                new Mock<IOutputDevice>().Object,
                new CommandLineOption(PlatformCommandLineProvider.ZeroTestsPolicyOptionKey, [PlatformCommandLineProvider.ZeroTestsPolicyStrictArgument]),
                new Mock<IEnvironment>().Object,
                new Mock<IStopPoliciesService>().Object,
                null);

        await testApplicationResult.ConsumeAsync(new DummyProducer(), new TestNodeUpdateMessage(
            default,
            new TestNode
            {
                Uid = new TestNodeUid("id"),
                DisplayName = "DisplayName",
                Properties = new PropertyBag(SkippedTestNodeStateProperty.CachedInstance),
            }), CancellationToken.None);

        Assert.AreEqual((int)ExitCode.ZeroTests, testApplicationResult.GetProcessExitCode());
    }

    [TestMethod]
    public async Task GetProcessExitCodeAsync_If_No_Tests_Ran_Returns_ZeroTestsRan()
    {
        await _testApplicationResult.ConsumeAsync(new DummyProducer(), new TestNodeUpdateMessage(
            default,
            new TestNode
            {
                Uid = new TestNodeUid("id"),
                DisplayName = "DisplayName",
                Properties = new PropertyBag(),
            }), CancellationToken.None);

        Assert.AreEqual((int)ExitCode.ZeroTests, _testApplicationResult.GetProcessExitCode());
    }

    [TestMethod]
    public async Task GetProcessExitCodeAsync_If_All_Skipped_With_AllowSkippedPolicy_Returns_Success()
    {
        using TestApplicationResult testApplicationResult
            = new(
                new Mock<IOutputDevice>().Object,
                new CommandLineOption(PlatformCommandLineProvider.ZeroTestsPolicyOptionKey, [PlatformCommandLineProvider.ZeroTestsPolicyAllowSkippedArgument]),
                new Mock<IEnvironment>().Object,
                new Mock<IStopPoliciesService>().Object,
                null);

        await testApplicationResult.ConsumeAsync(new DummyProducer(), new TestNodeUpdateMessage(
            default,
            new TestNode
            {
                Uid = new TestNodeUid("id"),
                DisplayName = "DisplayName",
                Properties = new PropertyBag(SkippedTestNodeStateProperty.CachedInstance),
            }), CancellationToken.None);

        Assert.AreEqual((int)ExitCode.Success, testApplicationResult.GetProcessExitCode());
    }

    [TestMethod]
    public async Task GetProcessExitCodeAsync_If_No_Tests_With_AllowSkippedPolicy_Returns_ZeroTestsRan()
    {
        using TestApplicationResult testApplicationResult
            = new(
                new Mock<IOutputDevice>().Object,
                new CommandLineOption(PlatformCommandLineProvider.ZeroTestsPolicyOptionKey, [PlatformCommandLineProvider.ZeroTestsPolicyAllowSkippedArgument]),
                new Mock<IEnvironment>().Object,
                new Mock<IStopPoliciesService>().Object,
                null);

        await testApplicationResult.ConsumeAsync(new DummyProducer(), new TestNodeUpdateMessage(
            default,
            new TestNode
            {
                Uid = new TestNodeUid("id"),
                DisplayName = "DisplayName",
                Properties = new PropertyBag(),
            }), CancellationToken.None);

        Assert.AreEqual((int)ExitCode.ZeroTests, testApplicationResult.GetProcessExitCode());
    }

    [TestMethod]
    [DynamicData(nameof(FailedState))]
    public async Task GetProcessExitCodeAsync_If_Failed_Tests_Returns_AtLeastOneTestFailed(TestNodeStateProperty testNodeStateProperty)
    {
        await _testApplicationResult.ConsumeAsync(new DummyProducer(), new TestNodeUpdateMessage(
            default,
            new TestNode
            {
                Uid = new TestNodeUid("id"),
                DisplayName = "DisplayName",
                Properties = new PropertyBag(testNodeStateProperty),
            }), CancellationToken.None);

        Assert.AreEqual((int)ExitCode.AtLeastOneTestFailed, _testApplicationResult.GetProcessExitCode());
    }

    [TestMethod]
    public async Task GetProcessExitCodeAsync_If_Canceled_Returns_TestSessionAborted()
    {
        Mock<ITestApplicationCancellationTokenSource> testApplicationCancellationTokenSource = new();
        // CTS should not be created in SetupGet so that the mocked ITestApplicationCancellationTokenSource returns the same instance on every access
        // which is the case in the real production implementation.
        CancellationTokenSource cancellationTokenSource = new();
        testApplicationCancellationTokenSource.SetupGet(x => x.CancellationToken).Returns(() =>
        {
            cancellationTokenSource.Cancel();
            return cancellationTokenSource.Token;
        });

        TestApplicationResult testApplicationResult
            = new(new Mock<IOutputDevice>().Object, new Mock<ICommandLineOptions>().Object, new Mock<IEnvironment>().Object,
                new StopPoliciesService(testApplicationCancellationTokenSource.Object), null);

        await testApplicationResult.ConsumeAsync(new DummyProducer(), new TestNodeUpdateMessage(
            default,
            new TestNode
            {
                Uid = new TestNodeUid("id"),
                DisplayName = "DisplayName",
                Properties = new PropertyBag(),
            }), CancellationToken.None);

        Assert.AreEqual((int)ExitCode.TestSessionAborted, testApplicationResult.GetProcessExitCode());
    }

    [TestMethod]
    public async Task GetProcessExitCodeAsync_If_TestAdapter_Returns_TestAdapterTestSessionFailure()
    {
        await _testApplicationResult.SetTestAdapterTestSessionFailureAsync("Adapter error", CancellationToken.None);
        await _testApplicationResult.ConsumeAsync(new DummyProducer(), new TestNodeUpdateMessage(
            default,
            new TestNode
            {
                Uid = new TestNodeUid("id"),
                DisplayName = "DisplayName",
                Properties = new PropertyBag(PassedTestNodeStateProperty.CachedInstance),
            }), CancellationToken.None);

        Assert.AreEqual((int)ExitCode.TestAdapterTestSessionFailure, _testApplicationResult.GetProcessExitCode());
    }

    [TestMethod]
    public async Task GetProcessExitCodeAsync_If_MinimumExpectedTests_Violated_Returns_MinimumExpectedTestsPolicyViolation()
    {
        TestApplicationResult testApplicationResult
            = new(
                new Mock<IOutputDevice>().Object,
                new CommandLineOption(PlatformCommandLineProvider.MinimumExpectedTestsOptionKey, ["2"]),
                new Mock<IEnvironment>().Object,
                new Mock<IStopPoliciesService>().Object,
                null);

        await testApplicationResult.ConsumeAsync(new DummyProducer(), new TestNodeUpdateMessage(
            default,
            new TestNode
            {
                Uid = new TestNodeUid("id"),
                DisplayName = "DisplayName",
                Properties = new PropertyBag(PassedTestNodeStateProperty.CachedInstance),
            }), CancellationToken.None);

        await testApplicationResult.ConsumeAsync(new DummyProducer(), new TestNodeUpdateMessage(
            default,
            new TestNode
            {
                Uid = new TestNodeUid("id"),
                DisplayName = "DisplayName",
                Properties = new PropertyBag(InProgressTestNodeStateProperty.CachedInstance),
            }), CancellationToken.None);

        Assert.AreEqual((int)ExitCode.MinimumExpectedTestsPolicyViolation, testApplicationResult.GetProcessExitCode());
    }

    [TestMethod]
    public void GetProcessExitCodeAsync_If_MinimumExpectedTests_Set_And_No_Tests_Ran_Returns_MinimumExpectedTestsPolicyViolation()
    {
        // When an explicit minimum is set, a zero-test run is reported as a minimum-expected violation (9)
        // rather than ZeroTests (8), so an orchestrator can tell a stricter-local-minimum violation apart
        // from a plain "ran nothing" module. See issue #7457.
        TestApplicationResult testApplicationResult
            = new(
                new Mock<IOutputDevice>().Object,
                new CommandLineOption(PlatformCommandLineProvider.MinimumExpectedTestsOptionKey, ["2"]),
                new Mock<IEnvironment>().Object,
                new Mock<IStopPoliciesService>().Object,
                null);

        Assert.AreEqual((int)ExitCode.MinimumExpectedTestsPolicyViolation, testApplicationResult.GetProcessExitCode());
    }

    [TestMethod]
    public async Task GetProcessExitCodeAsync_OnDiscovery_No_Tests_Discovered_Returns_ZeroTests()
    {
        TestApplicationResult testApplicationResult
            = new(
                new Mock<IOutputDevice>().Object,
                new CommandLineOption(PlatformCommandLineProvider.DiscoverTestsOptionKey, []),
                new Mock<IEnvironment>().Object, new Mock<IStopPoliciesService>().Object, null);

        await testApplicationResult.ConsumeAsync(new DummyProducer(), new TestNodeUpdateMessage(
            default,
            new TestNode
            {
                Uid = new TestNodeUid("id"),
                DisplayName = "DisplayName",
            }), CancellationToken.None);

        Assert.AreEqual((int)ExitCode.ZeroTests, testApplicationResult.GetProcessExitCode());
    }

    [TestMethod]
    public async Task GetProcessExitCodeAsync_OnDiscovery_Some_Tests_Discovered_Returns_Success()
    {
        TestApplicationResult testApplicationResult
            = new(
                new Mock<IOutputDevice>().Object,
                new CommandLineOption(PlatformCommandLineProvider.DiscoverTestsOptionKey, []),
                new Mock<IEnvironment>().Object, new Mock<IStopPoliciesService>().Object, null);

        await testApplicationResult.ConsumeAsync(new DummyProducer(), new TestNodeUpdateMessage(
            default,
            new TestNode
            {
                Uid = new TestNodeUid("id"),
                DisplayName = "DisplayName",
                Properties = new PropertyBag(DiscoveredTestNodeStateProperty.CachedInstance),
            }), CancellationToken.None);

        Assert.AreEqual((int)ExitCode.Success, testApplicationResult.GetProcessExitCode());
    }

    [DataRow("8", (int)ExitCode.Success)]
    [DataRow("8;2", (int)ExitCode.Success)]
    [DataRow("8;", (int)ExitCode.Success)]
    [DataRow("8;2;", (int)ExitCode.Success)]
    [DataRow(" 8 ", (int)ExitCode.Success)]
    [DataRow("+8", (int)ExitCode.Success)]
    [DataRow("2;8", (int)ExitCode.Success)]
    [DataRow("5", (int)ExitCode.ZeroTests)]
    [DataRow("5;7", (int)ExitCode.ZeroTests)]
    [DataRow("5;", (int)ExitCode.ZeroTests)]
    [DataRow("5;7;", (int)ExitCode.ZeroTests)]
    [DataRow(";", (int)ExitCode.ZeroTests)]
    [DataRow(null, (int)ExitCode.ZeroTests)]
    [DataRow("", (int)ExitCode.ZeroTests)]
    [DataRow("8abc", (int)ExitCode.ZeroTests)]
    [DataRow("-8", (int)ExitCode.ZeroTests)]
    [DataRow("2147483648", (int)ExitCode.ZeroTests)]
    [DataRow("18446744073709551624", (int)ExitCode.ZeroTests)]
    [TestMethod]
    public void GetProcessExitCodeAsync_IgnoreExitCodes(string? argument, int expectedExitCode)
    {
        Mock<IEnvironment> environment = new();
        environment.Setup(x => x.GetEnvironmentVariable(EnvironmentVariableConstants.TESTINGPLATFORM_EXITCODE_IGNORE)).Returns(argument);

        foreach (TestApplicationResult testApplicationResult in new TestApplicationResult[]
        {
            new(
                new Mock<IOutputDevice>().Object,
                new CommandLineOption(PlatformCommandLineProvider.IgnoreExitCodeOptionKey, argument is null ? [] : [argument]),
                new Mock<IEnvironment>().Object, new Mock<IStopPoliciesService>().Object, null),
            new(
                new Mock<IOutputDevice>().Object,
                new Mock<ICommandLineOptions>().Object,
                environment.Object,
                new Mock<IStopPoliciesService>().Object, null),
        })
        {
            Assert.AreEqual(expectedExitCode, testApplicationResult.GetProcessExitCode());
        }
    }

    internal static IEnumerable<object[]> FailedState()
    {
        yield return [new FailedTestNodeStateProperty()];
        yield return [new ErrorTestNodeStateProperty()];
#pragma warning disable CS0618, MTP0001 // Type or member is obsolete
        yield return [new CancelledTestNodeStateProperty()];
#pragma warning restore CS0618, MTP0001 // Type or member is obsolete
        yield return [new TimeoutTestNodeStateProperty()];
    }

    private sealed class CommandLineOption : ICommandLineOptions
    {
        private readonly string _optionName;
        private readonly string[] _arguments;

        public CommandLineOption(string optionName, string[] arguments)
        {
            _optionName = optionName;
            _arguments = arguments;
        }

        public bool IsOptionSet(string optionName) => _optionName == optionName;

#pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes).
        public bool TryGetOptionArgumentList(string optionName, out string[]? arguments)
#pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes).
        {
            arguments = _arguments;
            return IsOptionSet(optionName);
        }
    }

    private sealed class DummyProducer : IDataProducer
    {
        public Type[] DataTypesProduced => throw new NotImplementedException();

        public string Uid => nameof(DummyProducer);

        public string Version => "1.0.0";

        public string DisplayName => string.Empty;

        public string Description => string.Empty;

        public Task<bool> IsEnabledAsync() => throw new NotImplementedException();
    }
}
