From a930266a559f33a5a68e033450b93fb51c19619f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:22:13 +0000 Subject: [PATCH 01/16] Initial plan From aadadf261aefd1148c2d34536d372a7d070bf984 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:34:47 +0000 Subject: [PATCH 02/16] Add Microsoft.DotNet.XUnitExtensions and create TestConditions helper Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- eng/Versions.props | 1 + test/Shared/Shared.Tests.csproj | 1 + test/Shared/TestHelpers/TestConditions.cs | 31 +++++++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 test/Shared/TestHelpers/TestConditions.cs diff --git a/eng/Versions.props b/eng/Versions.props index f42eb55d01b..7fbbfaafa80 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -225,6 +225,7 @@ Starting with 3.0.0, xunit.runner.visualstudio only supports net472, but we target net462 --> 2.8.2 + 9.0.0-beta.25562.4 9.7.0 1.67.0-preview diff --git a/test/Shared/Shared.Tests.csproj b/test/Shared/Shared.Tests.csproj index 2764d5f5d5d..b47133341e6 100644 --- a/test/Shared/Shared.Tests.csproj +++ b/test/Shared/Shared.Tests.csproj @@ -21,6 +21,7 @@ + diff --git a/test/Shared/TestHelpers/TestConditions.cs b/test/Shared/TestHelpers/TestConditions.cs new file mode 100644 index 00000000000..a6774850b9f --- /dev/null +++ b/test/Shared/TestHelpers/TestConditions.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Linq; + +namespace Microsoft.Extensions.TestHelpers; + +/// +/// Provides static helper methods for conditional test execution with Microsoft.DotNet.XUnitExtensions. +/// +public static class TestConditions +{ + /// + /// Checks if the specified environment variable has one of the specified values. + /// + /// The name of the environment variable. + /// The values to check against. + /// True if the environment variable matches one of the values; otherwise, false. + public static bool IsEnvironmentVariableSet(string variableName, params string[] values) + { + var currentValue = Environment.GetEnvironmentVariable(variableName); + return values.Any(value => string.Equals(value, currentValue, StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Checks if AI_TEMPLATES_TEST_PROJECT_NAMES environment variable is set to "true" or "1". + /// + public static bool IsAITemplatesTestProjectNamesSet => + IsEnvironmentVariableSet("AI_TEMPLATES_TEST_PROJECT_NAMES", "true", "1"); +} From a460f85f540465590e38d7652720967c107ab527 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:43:46 +0000 Subject: [PATCH 03/16] Migrate test files to use Microsoft.DotNet.XUnitExtensions Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../FakeCertificateFactoryTests.cs | 9 ++- .../Microsoft.AspNetCore.Testing.Tests.csproj | 6 +- ...ft.Extensions.AI.Abstractions.Tests.csproj | 2 +- .../Utilities/AIJsonUtilitiesTests.cs | 1 - .../AgentQualityEvaluatorTests.cs | 2 +- ...ons.AI.Evaluation.Integration.Tests.csproj | 2 +- .../NLPEvaluatorTests.cs | 2 +- .../QualityEvaluatorTests.cs | 2 +- .../SafetyEvaluatorTests.cs | 2 +- .../EmbeddingTests.cs | 2 +- ...sions.AI.Evaluation.Reporting.Tests.csproj | 2 +- .../ResponseCacheTester.cs | 2 +- .../ResultStoreTester.cs | 2 +- .../ChatClientIntegrationTests.cs | 2 +- .../EmbeddingGeneratorIntegrationTests.cs | 2 +- ...ageGeneratingChatClientIntegrationTests.cs | 2 +- .../ImageGeneratorIntegrationTests.cs | 2 +- ...oft.Extensions.AI.Integration.Tests.csproj | 2 +- .../SpeechToTextClientIntegrationTests.cs | 2 +- ...soft.Extensions.DataIngestion.Tests.csproj | 2 +- .../Readers/DocumentReaderConformanceTests.cs | 2 +- .../Readers/MarkItDownConditionAttribute.cs | 2 +- .../Readers/MarkItDownReaderTests.cs | 2 +- .../Readers/MarkdownReaderTests.cs | 1 - .../Linux/LinuxResourceHealthCheckTests.cs | 1 - ...lthChecks.ResourceUtilization.Tests.csproj | 2 +- .../ResourceHealthCheckExtensionsTests.cs | 1 - .../Linux/AcceptanceTest.cs | 16 ++--- .../Linux/Disk/DiskStatsReaderTests.cs | 2 - .../Linux/Disk/LinuxSystemDiskMetricsTests.cs | 2 - .../Linux/LinuxNetworkMetricsTests.cs | 2 - .../LinuxUtilizationParserCgroupV1Tests.cs | 36 ++++++----- .../LinuxUtilizationParserCgroupV2Tests.cs | 60 +++++++++---------- .../Linux/LinuxUtilizationProviderTests.cs | 8 +-- .../Linux/OSFileSystemTests.cs | 10 ++-- ...iagnostics.ResourceMonitoring.Tests.csproj | 5 +- .../Disk/WindowsDiskIoRatePerfCounterTests.cs | 6 +- .../Disk/WindowsDiskIoTimePerfCounterTests.cs | 4 +- .../Windows/Disk/WindowsDiskMetricsTests.cs | 8 +-- .../Windows/MemoryInfoTests.cs | 4 +- .../Windows/PerformanceCounterFactoryTests.cs | 4 +- .../Windows/PerformanceCounterWrapperTests.cs | 4 +- .../Windows/ProcessInfoTests.cs | 1 - .../Windows/SystemInfoTests.cs | 4 +- .../Windows/Tcp6TableInfoTests.cs | 8 +-- .../Windows/TcpTableInfoTests.cs | 10 ++-- .../Windows/WindowsNetworkMetricsTests.cs | 4 +- .../Windows/WindowsSnapshotProviderTests.cs | 16 +++-- .../AIChatWebExecutionTests.cs | 5 +- ...osoft.Extensions.AI.Templates.Tests.csproj | 3 +- 50 files changed, 121 insertions(+), 162 deletions(-) diff --git a/test/Libraries/Microsoft.AspNetCore.Testing.Tests/FakeCertificateFactoryTests.cs b/test/Libraries/Microsoft.AspNetCore.Testing.Tests/FakeCertificateFactoryTests.cs index 1da6dc74ddc..72281c74233 100644 --- a/test/Libraries/Microsoft.AspNetCore.Testing.Tests/FakeCertificateFactoryTests.cs +++ b/test/Libraries/Microsoft.AspNetCore.Testing.Tests/FakeCertificateFactoryTests.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using System.Security.Cryptography.X509Certificates; -using Microsoft.TestUtilities; using Xunit; namespace Microsoft.AspNetCore.Testing.Test; @@ -23,8 +22,8 @@ public void Create_CreatesCertificate() Assert.False(certificate.Extensions.OfType().Single().Critical); } - [ConditionalTheory] - [OSSkipCondition(OperatingSystems.Linux)] + [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.OSX)] + [Theory] [InlineData(false)] [InlineData(true)] public void GenerateRsa_RunsOnWindows_GeneratesRsa(bool runsOnWindows) @@ -32,8 +31,8 @@ public void GenerateRsa_RunsOnWindows_GeneratesRsa(bool runsOnWindows) Assert.NotNull(FakeSslCertificateFactory.GenerateRsa(runsOnWindows)); } - [ConditionalFact] - [OSSkipCondition(OperatingSystems.Windows)] + [PlatformSpecific(TestPlatforms.Linux | TestPlatforms.OSX)] + [Fact] public void GenerateRsa_DoesNotRunOnWindows_GeneratesRsa() { Assert.NotNull(FakeSslCertificateFactory.GenerateRsa(runsOnWindows: false)); diff --git a/test/Libraries/Microsoft.AspNetCore.Testing.Tests/Microsoft.AspNetCore.Testing.Tests.csproj b/test/Libraries/Microsoft.AspNetCore.Testing.Tests/Microsoft.AspNetCore.Testing.Tests.csproj index 11d60ebbaba..a2c3e84fa3f 100644 --- a/test/Libraries/Microsoft.AspNetCore.Testing.Tests/Microsoft.AspNetCore.Testing.Tests.csproj +++ b/test/Libraries/Microsoft.AspNetCore.Testing.Tests/Microsoft.AspNetCore.Testing.Tests.csproj @@ -11,6 +11,10 @@ - + + + + + diff --git a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Microsoft.Extensions.AI.Abstractions.Tests.csproj b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Microsoft.Extensions.AI.Abstractions.Tests.csproj index 4c275e54993..353f87f9260 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Microsoft.Extensions.AI.Abstractions.Tests.csproj +++ b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Microsoft.Extensions.AI.Abstractions.Tests.csproj @@ -29,6 +29,7 @@ + @@ -37,6 +38,5 @@ - diff --git a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Utilities/AIJsonUtilitiesTests.cs b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Utilities/AIJsonUtilitiesTests.cs index 8ebc20b957e..ebe3a4f91e8 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Utilities/AIJsonUtilitiesTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Utilities/AIJsonUtilitiesTests.cs @@ -15,7 +15,6 @@ using System.Text.Json.Serialization.Metadata; using System.Threading; using Microsoft.Extensions.AI.JsonSchemaExporter; -using Microsoft.TestUtilities; using Xunit; #pragma warning disable SA1114 // parameter list should follow declaration diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/AgentQualityEvaluatorTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/AgentQualityEvaluatorTests.cs index 238b805e867..1f56d9b980a 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/AgentQualityEvaluatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/AgentQualityEvaluatorTests.cs @@ -11,8 +11,8 @@ using Microsoft.Extensions.AI.Evaluation.Reporting; using Microsoft.Extensions.AI.Evaluation.Reporting.Storage; using Microsoft.Extensions.AI.Evaluation.Tests; -using Microsoft.TestUtilities; using Xunit; +using Microsoft.DotNet.XUnitExtensions; namespace Microsoft.Extensions.AI.Evaluation.Integration.Tests; diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Microsoft.Extensions.AI.Evaluation.Integration.Tests.csproj b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Microsoft.Extensions.AI.Evaluation.Integration.Tests.csproj index 8ee7f39ee1c..4bcc5f518fa 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Microsoft.Extensions.AI.Evaluation.Integration.Tests.csproj +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/Microsoft.Extensions.AI.Evaluation.Integration.Tests.csproj @@ -21,6 +21,7 @@ + @@ -35,7 +36,6 @@ - \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/NLPEvaluatorTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/NLPEvaluatorTests.cs index 9cd593a647a..12582bc4acd 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/NLPEvaluatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/NLPEvaluatorTests.cs @@ -8,8 +8,8 @@ using Microsoft.Extensions.AI.Evaluation.NLP; using Microsoft.Extensions.AI.Evaluation.Reporting; using Microsoft.Extensions.AI.Evaluation.Reporting.Storage; -using Microsoft.TestUtilities; using Xunit; +using Microsoft.DotNet.XUnitExtensions; namespace Microsoft.Extensions.AI.Evaluation.Integration.Tests; diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/QualityEvaluatorTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/QualityEvaluatorTests.cs index fde342a4161..8fef8120e9c 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/QualityEvaluatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/QualityEvaluatorTests.cs @@ -10,8 +10,8 @@ using Microsoft.Extensions.AI.Evaluation.Reporting; using Microsoft.Extensions.AI.Evaluation.Reporting.Storage; using Microsoft.Extensions.AI.Evaluation.Tests; -using Microsoft.TestUtilities; using Xunit; +using Microsoft.DotNet.XUnitExtensions; namespace Microsoft.Extensions.AI.Evaluation.Integration.Tests; diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/SafetyEvaluatorTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/SafetyEvaluatorTests.cs index 68b4a9d8ce0..d284524ec59 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/SafetyEvaluatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/SafetyEvaluatorTests.cs @@ -13,8 +13,8 @@ using Microsoft.Extensions.AI.Evaluation.Safety; using Microsoft.Extensions.AI.Evaluation.Tests; using Microsoft.Extensions.AI.Evaluation.Utilities; -using Microsoft.TestUtilities; using Xunit; +using Microsoft.DotNet.XUnitExtensions; namespace Microsoft.Extensions.AI.Evaluation.Integration.Tests; diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/EmbeddingTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/EmbeddingTests.cs index bf00efc6471..1bb55568346 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/EmbeddingTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/EmbeddingTests.cs @@ -3,8 +3,8 @@ using System; using Microsoft.Extensions.AI.Evaluation.Reporting.Formats.Html; -using Microsoft.TestUtilities; using Xunit; +using Microsoft.DotNet.XUnitExtensions; namespace Microsoft.Extensions.AI.Evaluation.Reporting.Tests; diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/Microsoft.Extensions.AI.Evaluation.Reporting.Tests.csproj b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/Microsoft.Extensions.AI.Evaluation.Reporting.Tests.csproj index 44b7e2f561b..73114273cd9 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/Microsoft.Extensions.AI.Evaluation.Reporting.Tests.csproj +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/Microsoft.Extensions.AI.Evaluation.Reporting.Tests.csproj @@ -10,6 +10,7 @@ + @@ -17,7 +18,6 @@ - \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResponseCacheTester.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResponseCacheTester.cs index 50793dfdb66..6134838ba33 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResponseCacheTester.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResponseCacheTester.cs @@ -5,8 +5,8 @@ using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; -using Microsoft.TestUtilities; using Xunit; +using Microsoft.DotNet.XUnitExtensions; namespace Microsoft.Extensions.AI.Evaluation.Reporting.Tests; diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResultStoreTester.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResultStoreTester.cs index 995b77a8c5e..3a37160f497 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResultStoreTester.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResultStoreTester.cs @@ -6,8 +6,8 @@ using System.IO; using System.Linq; using System.Threading.Tasks; -using Microsoft.TestUtilities; using Xunit; +using Microsoft.DotNet.XUnitExtensions; namespace Microsoft.Extensions.AI.Evaluation.Reporting.Tests; diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs index 992e86a1184..f35727eae9c 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs @@ -18,9 +18,9 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; -using Microsoft.TestUtilities; using OpenTelemetry.Trace; using Xunit; +using Microsoft.DotNet.XUnitExtensions; #pragma warning disable CA2000 // Dispose objects before losing scope #pragma warning disable CA2214 // Do not call overridable methods in constructors diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs index 20423ae9e8b..18904bd559f 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs @@ -16,9 +16,9 @@ using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Memory; -using Microsoft.TestUtilities; using OpenTelemetry.Trace; using Xunit; +using Microsoft.DotNet.XUnitExtensions; #pragma warning disable CA2214 // Do not call overridable methods in constructors #pragma warning disable S3967 // Multidimensional arrays should not be used diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratingChatClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratingChatClientIntegrationTests.cs index 2cbdcd96abf..f463a4c0598 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratingChatClientIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratingChatClientIntegrationTests.cs @@ -7,8 +7,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.TestUtilities; using Xunit; +using Microsoft.DotNet.XUnitExtensions; #pragma warning disable CA2000 // Dispose objects before losing scope #pragma warning disable CA2214 // Do not call overridable methods in constructors diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratorIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratorIntegrationTests.cs index 76b08941bc5..db47deecafe 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratorIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratorIntegrationTests.cs @@ -5,8 +5,8 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading.Tasks; -using Microsoft.TestUtilities; using Xunit; +using Microsoft.DotNet.XUnitExtensions; #pragma warning disable CA2214 // Do not call overridable methods in constructors diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/Microsoft.Extensions.AI.Integration.Tests.csproj b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/Microsoft.Extensions.AI.Integration.Tests.csproj index bd6c6b9ba2f..9c65fbc3414 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/Microsoft.Extensions.AI.Integration.Tests.csproj +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/Microsoft.Extensions.AI.Integration.Tests.csproj @@ -37,6 +37,7 @@ + @@ -52,6 +53,5 @@ - diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/SpeechToTextClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/SpeechToTextClientIntegrationTests.cs index f0ea6c1790e..141a9647a70 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/SpeechToTextClientIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/SpeechToTextClientIntegrationTests.cs @@ -6,8 +6,8 @@ using System.IO; using System.Text; using System.Threading.Tasks; -using Microsoft.TestUtilities; using Xunit; +using Microsoft.DotNet.XUnitExtensions; #pragma warning disable CA2214 // Do not call overridable methods in constructors diff --git a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Microsoft.Extensions.DataIngestion.Tests.csproj b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Microsoft.Extensions.DataIngestion.Tests.csproj index cc64014cad6..3d8bad77ca8 100644 --- a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Microsoft.Extensions.DataIngestion.Tests.csproj +++ b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Microsoft.Extensions.DataIngestion.Tests.csproj @@ -15,10 +15,10 @@ - + diff --git a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/DocumentReaderConformanceTests.cs b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/DocumentReaderConformanceTests.cs index d4993ad2cea..1cb98368224 100644 --- a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/DocumentReaderConformanceTests.cs +++ b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/DocumentReaderConformanceTests.cs @@ -8,8 +8,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DataIngestion.Tests.Utils; -using Microsoft.TestUtilities; using Xunit; +using Microsoft.DotNet.XUnitExtensions; namespace Microsoft.Extensions.DataIngestion.Readers.Tests; diff --git a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownConditionAttribute.cs b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownConditionAttribute.cs index b0169a54c1c..96b377e0937 100644 --- a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownConditionAttribute.cs +++ b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownConditionAttribute.cs @@ -5,7 +5,7 @@ using System.ComponentModel; using System.Diagnostics; using System.Text; -using Microsoft.TestUtilities; +using Microsoft.DotNet.XUnitExtensions; namespace Microsoft.Extensions.DataIngestion.Readers.Tests; diff --git a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs index e506ea15ca1..d62a6db1cb1 100644 --- a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs +++ b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs @@ -4,8 +4,8 @@ using System; using System.Linq; using System.Threading.Tasks; -using Microsoft.TestUtilities; using Xunit; +using Microsoft.DotNet.XUnitExtensions; namespace Microsoft.Extensions.DataIngestion.Readers.Tests; diff --git a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkdownReaderTests.cs b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkdownReaderTests.cs index dce6d996821..55a8994231e 100644 --- a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkdownReaderTests.cs +++ b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkdownReaderTests.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using System.Threading.Tasks; -using Microsoft.TestUtilities; using Xunit; namespace Microsoft.Extensions.DataIngestion.Readers.Tests; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Linux/LinuxResourceHealthCheckTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Linux/LinuxResourceHealthCheckTests.cs index 53bc425c6a8..268061a5b1a 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Linux/LinuxResourceHealthCheckTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Linux/LinuxResourceHealthCheckTests.cs @@ -9,7 +9,6 @@ using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux; using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Time.Testing; -using Microsoft.TestUtilities; using Moq; using Xunit; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests.csproj b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests.csproj index dcd6d4e40db..6d5bd67b4af 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests.csproj +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests.csproj @@ -6,11 +6,11 @@ - + diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/ResourceHealthCheckExtensionsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/ResourceHealthCheckExtensionsTests.cs index 2599be2dd9e..5ea82bfb10e 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/ResourceHealthCheckExtensionsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/ResourceHealthCheckExtensionsTests.cs @@ -13,7 +13,6 @@ using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Options; using Microsoft.Extensions.Time.Testing; -using Microsoft.TestUtilities; using Moq; using Xunit; using static Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Interop.JobObjectInfo; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs index 1dd11c1f3bb..a39b52fa6cb 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs @@ -19,15 +19,13 @@ using Microsoft.Extensions.Options; using Microsoft.Extensions.Time.Testing; using Microsoft.Shared.Instruments; -using Microsoft.TestUtilities; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; public sealed class AcceptanceTest { - [ConditionalFact] - [OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] + [LinuxOnlyFact] public void Adding_Linux_Resource_Utilization_Allows_To_Query_Snapshot_Provider() { using var services = new ServiceCollection() @@ -40,8 +38,7 @@ public void Adding_Linux_Resource_Utilization_Allows_To_Query_Snapshot_Provider( Assert.NotEqual(default, provider.GetSnapshot()); } - [ConditionalFact] - [OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] + [LinuxOnlyFact] [SuppressMessage("Minor Code Smell", "S3257:Declarations and initializations should be as concise as possible", Justification = "Broken analyzer.")] public void Adding_Linux_Resource_Utilization_Can_Be_Configured_With_Section() { @@ -70,8 +67,7 @@ public void Adding_Linux_Resource_Utilization_Can_Be_Configured_With_Section() Assert.Equal(memoryRefresh, options.Value.MemoryConsumptionRefreshInterval); } - [ConditionalFact] - [OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] + [LinuxOnlyFact] public void Adding_Linux_Resource_Utilization_Can_Be_Configured_With_Action() { var cpuRefresh = TimeSpan.FromMinutes(13); @@ -92,8 +88,7 @@ public void Adding_Linux_Resource_Utilization_Can_Be_Configured_With_Action() Assert.Equal(memoryRefresh, options.Value.MemoryConsumptionRefreshInterval); } - [ConditionalFact] - [OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] + [LinuxOnlyFact] [SuppressMessage("Minor Code Smell", "S3257:Declarations and initializations should be as concise as possible", Justification = "Broken analyzer.")] public void Adding_Linux_Resource_Utilization_With_Section_Registers_SnapshotProvider_Cgroupv1() { @@ -141,8 +136,7 @@ public void Adding_Linux_Resource_Utilization_With_Section_Registers_SnapshotPro Assert.Equal(100_000UL, provider.Resources.MaximumMemoryInBytes); } - [ConditionalFact] - [OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] + [LinuxOnlyFact] [SuppressMessage("Minor Code Smell", "S3257:Declarations and initializations should be as concise as possible", Justification = "Broken analyzer.")] public void Adding_Linux_Resource_Utilization_With_Section_Registers_SnapshotProvider_Cgroupv2() { diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/DiskStatsReaderTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/DiskStatsReaderTests.cs index 1f7738bb030..a669ef17743 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/DiskStatsReaderTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/DiskStatsReaderTests.cs @@ -5,12 +5,10 @@ using System.IO; using System.Linq; using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; -using Microsoft.TestUtilities; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Disk.Test; -[OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] public class DiskStatsReaderTests { private static readonly string[] _skipDevicePrefixes = new[] { "ram", "loop", "dm-" }; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/LinuxSystemDiskMetricsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/LinuxSystemDiskMetricsTests.cs index 80ebc818894..edb5d20bf00 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/LinuxSystemDiskMetricsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/LinuxSystemDiskMetricsTests.cs @@ -11,13 +11,11 @@ using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Time.Testing; using Microsoft.Shared.Instruments; -using Microsoft.TestUtilities; using Moq; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Disk.Test; -[OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] public class LinuxSystemDiskMetricsTests { private static readonly string[] _skipDevicePrefixes = new[] { "ram", "loop", "dm-" }; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs index 40536a3245c..b663b6f97fd 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs @@ -10,13 +10,11 @@ using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Test.Helpers; using Microsoft.Extensions.Time.Testing; using Microsoft.Shared.Instruments; -using Microsoft.TestUtilities; using Moq; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; -[OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] public class LinuxNetworkMetricsTests { private readonly Mock _tcpStateInfoProvider = new(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV1Tests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV1Tests.cs index 3820ec254e0..01da615cf13 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV1Tests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV1Tests.cs @@ -8,16 +8,14 @@ using System.IO; using System.Threading.Tasks; using Microsoft.Shared.Pools; -using Microsoft.TestUtilities; using Moq; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; -[OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] public sealed class LinuxUtilizationParserCgroupV1Tests { - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("DFIJEUWGHFWGBWEFWOMDOWKSLA")] [InlineData("")] [InlineData("________________________Asdasdasdas dd")] @@ -40,7 +38,7 @@ public void Parser_Throws_When_Data_Is_Invalid(string line) Assert.Throws(() => parser.GetCgroupRequestCpuV2()); } - [ConditionalFact] + [LinuxOnlyFact] public void Parser_Can_Read_Host_And_Cgroup_Available_Cpu_Count() { var parser = new LinuxUtilizationParserCgroupV1(new FileNamesOnlyFileSystem(TestResources.TestFilesLocation), new FakeUserHz(100)); @@ -51,7 +49,7 @@ public void Parser_Can_Read_Host_And_Cgroup_Available_Cpu_Count() Assert.Equal(1.0, cgroupCpuCount); } - [ConditionalFact] + [LinuxOnlyFact] public void Parser_Provides_Total_Available_Memory_In_Bytes() { var fs = new FileNamesOnlyFileSystem(TestResources.TestFilesLocation); @@ -62,7 +60,7 @@ public void Parser_Provides_Total_Available_Memory_In_Bytes() Assert.Equal(16_233_760UL * 1024, totalMem); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("----------------------")] [InlineData("@ @#dddada")] [InlineData("1231234124124")] @@ -93,7 +91,7 @@ public void When_Calling_GetMemoryUsageInBytes_Parser_Throws_When_MemoryStat_Doe Assert.Contains("total_inactive_file", r.Message); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("----------------------")] [InlineData("@ @#dddada")] [InlineData("_1231234124124")] @@ -119,7 +117,7 @@ public void When_Calling_GetMemoryUsageInBytes_Parser_Throws_When_UsageInBytes_D Assert.Contains("/sys/fs/cgroup/memory/memory.usage_in_bytes", r.Message); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData(10, 1)] [InlineData(23, 22)] [InlineData(100000, 10000)] @@ -138,7 +136,7 @@ public void When_Calling_GetMemoryUsageInBytes_Parser_Throws_When_Inactive_Memor Assert.Contains("lesser than", r.Message); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("Mem")] [InlineData("MemTotal:")] [InlineData("MemTotal: 120")] @@ -164,7 +162,7 @@ public void When_Calling_GetHostAvailableMemory_Parser_Throws_When_MemInfo_Does_ Assert.Contains("/proc/meminfo", r.Message); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("kB", 231, 236544)] [InlineData("MB", 287, 300_941_312)] [InlineData("GB", 372, 399_431_958_528)] @@ -183,7 +181,7 @@ public void When_Calling_GetHostAvailableMemory_Parser_Correctly_Transforms_Supp Assert.Equal(bytes, memory); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("0-11", 12)] [InlineData("0", 1)] [InlineData("1000", 1)] @@ -210,7 +208,7 @@ public void When_No_Cgroup_Cpu_Limits_Are_Not_Set_We_Get_Available_Cpus_From_Cpu Assert.Equal(result, cpus); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("-11")] [InlineData("0-")] [InlineData("d-22")] @@ -238,7 +236,7 @@ public void Parser_Throws_When_CpuSet_Has_Invalid_Content(string content) Assert.Contains("/sys/fs/cgroup/cpuset/cpuset.cpus", r.Message); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("-1", "18")] [InlineData("18", "-1")] [InlineData("18", "")] @@ -259,7 +257,7 @@ public void When_Quota_And_Period_Are_Minus_One_It_Fallbacks_To_Cpuset(string qu Assert.Contains("/sys/fs/cgroup/cpuset/cpuset.cpus", r.Message); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("dd1d", "18")] [InlineData("-18", "18")] [InlineData("\r\r\r\r\r", "18")] @@ -287,7 +285,7 @@ public void Parser_Throws_When_Cgroup_Cpu_Files_Contain_Invalid_Data(string quot Assert.Contains("/sys/fs/cgroup/cpu/cpu.cfs_", r.Message); } - [ConditionalFact] + [LinuxOnlyFact] public void ReadingCpuUsage_Does_Not_Throw_For_Valid_Input() { var f = new HardcodedValueFileSystem(new Dictionary @@ -301,7 +299,7 @@ public void ReadingCpuUsage_Does_Not_Throw_For_Valid_Input() Assert.Null(r); } - [ConditionalFact] + [LinuxOnlyFact] public void ReadingTotalMemory_Does_Not_Throw_For_Valid_Input() { var f = new HardcodedValueFileSystem(new Dictionary @@ -316,7 +314,7 @@ public void ReadingTotalMemory_Does_Not_Throw_For_Valid_Input() Assert.Null(r); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("2569530367000")] [InlineData(" 2569530 36700 245693 4860924 82283 0 4360 0dsa 0 0 asdasd @@@@")] [InlineData("asdasd 2569530 36700 245693 4860924 82283 0 4360 0 0 0")] @@ -337,7 +335,7 @@ public void ReadingCpuUsage_Does_Throws_For_Valid_Input(string content) Assert.Contains("proc/stat", r.Message); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("-1")] [InlineData("")] public void Parser_Throws_When_Cgroup_Cpu_Shares_Files_Contain_Invalid_Data(string content) @@ -354,7 +352,7 @@ public void Parser_Throws_When_Cgroup_Cpu_Shares_Files_Contain_Invalid_Data(stri Assert.Contains("/sys/fs/cgroup/cpu/cpu.shares", r.Message); } - [ConditionalFact] + [LinuxOnlyFact] public async Task ThreadSafetyAsync() { var f1 = new HardcodedValueFileSystem(new Dictionary diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV2Tests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV2Tests.cs index 8986ac6a8ac..3805e83e074 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV2Tests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV2Tests.cs @@ -7,19 +7,17 @@ using System.IO; using System.Threading.Tasks; using Microsoft.Shared.Pools; -using Microsoft.TestUtilities; using Moq; using VerifyXunit; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; -[OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] public sealed class LinuxUtilizationParserCgroupV2Tests { private const string VerifiedDataDirectory = "Verified"; - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("DFIJEUWGHFWGBWEFWOMDOWKSLA")] [InlineData("")] [InlineData("________________________Asdasdasdas dd")] @@ -43,7 +41,7 @@ public void Throws_When_Data_Is_Invalid(string line) Assert.Throws(() => parser.GetCgroupPeriodsIntervalInMicroSecondsV2()); } - [ConditionalFact] + [LinuxOnlyFact] public void Can_Read_Host_And_Cgroup_Available_Cpu_Count() { var parser = new LinuxUtilizationParserCgroupV2(new FileNamesOnlyFileSystem(TestResources.TestFilesLocation), new FakeUserHz(100)); @@ -54,7 +52,7 @@ public void Can_Read_Host_And_Cgroup_Available_Cpu_Count() Assert.Equal(2.0, cgroupCpuCount); } - [ConditionalFact] + [LinuxOnlyFact] public void Provides_Total_Available_Memory_In_Bytes() { var fs = new FileNamesOnlyFileSystem(TestResources.TestFilesLocation); @@ -65,7 +63,7 @@ public void Provides_Total_Available_Memory_In_Bytes() Assert.Equal(16_233_760UL * 1024, totalMem); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("----------------------")] [InlineData("@ @#dddada")] [InlineData("1231234124124")] @@ -95,7 +93,7 @@ public Task Throws_When_TotalInactiveFile_Is_Invalid(string content) return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("----------------------")] [InlineData("@ @#dddada")] [InlineData("_1231234124124")] @@ -120,7 +118,7 @@ public Task Throws_When_UsageInBytes_Is_Invalid(string content) return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("max\n", 134_796_910_592ul)] [InlineData("1000000\n", 1_000_000ul)] public void Returns_Available_Memory_When_AvailableMemoryInBytes_Is_Valid(string content, ulong expectedResult) @@ -137,7 +135,7 @@ public void Returns_Available_Memory_When_AvailableMemoryInBytes_Is_Valid(string Assert.Equal(expectedResult, result); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("Suspicious12312312")] [InlineData("string@")] [InlineData("string12312")] @@ -154,7 +152,7 @@ public Task Throws_When_AvailableMemoryInBytes_Doesnt_Contain_Just_A_Number(stri return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory); } - [ConditionalFact] + [LinuxOnlyFact] public Task Throws_When_UsageInBytes_Doesnt_Contain_A_Number() { var regexPatternforSlices = @"\w+.slice"; @@ -169,7 +167,7 @@ public Task Throws_When_UsageInBytes_Doesnt_Contain_A_Number() return Verifier.Verify(r).UseDirectory(VerifiedDataDirectory); } - [ConditionalFact] + [LinuxOnlyFact] public void Returns_Memory_Usage_When_Memory_Usage_Is_Valid() { // When memory usage is a positive number @@ -195,7 +193,7 @@ public void Returns_Memory_Usage_When_Memory_Usage_Is_Valid() Assert.Equal(0, r); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData(104343, 1)] [InlineData(23423, 22)] [InlineData(10000, 100)] @@ -213,7 +211,7 @@ public Task Throws_When_Inactive_Memory_Is_Bigger_Than_Total_Memory(int inactive return Verifier.Verify(r).UseParameters(inactive, total).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("Mem")] [InlineData("MemTotal:")] [InlineData("MemTotal: 120")] @@ -238,7 +236,7 @@ public Task Throws_When_MemInfo_Does_Not_Contain_TotalMemory(string totalMemory) return Verifier.Verify(r).UseParameters(totalMemory).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("kB", 231, 236_544)] [InlineData("MB", 287, 300_941_312)] [InlineData("GB", 372, 399_431_958_528)] @@ -256,7 +254,7 @@ public void Transforms_Supported_Units_To_Bytes(string unit, int value, ulong by Assert.Equal(bytes, memory); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("0-11", 12)] [InlineData("0", 1)] [InlineData("1000", 1)] @@ -282,7 +280,7 @@ public void Gets_Available_Cpus_From_CpuSetCpus_When_Cpu_Limits_Not_Set(string c Assert.Equal(result, cpus); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("0::/")] [InlineData("0::/fakeslice")] public void Gets_Available_Cpus_From_CpuSetCpusFromSlices_When_Cpu_Limits_Not_Set(string slicepath) @@ -300,7 +298,7 @@ public void Gets_Available_Cpus_From_CpuSetCpusFromSlices_When_Cpu_Limits_Not_Se Assert.Equal(2, cpus); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("2500", 64.0)] [InlineData("10000", 256.0)] public void Calculates_Cpu_Request_From_Cpu_WeightInSlices(string content, float result) @@ -317,7 +315,7 @@ public void Calculates_Cpu_Request_From_Cpu_WeightInSlices(string content, float Assert.Equal(result, r); } - [ConditionalFact] + [LinuxOnlyFact] public void Gets_Available_Cpus_From_CpuSetCpus_When_Cpu_Max_Set_To_Max_() { var f = new HardcodedValueFileSystem(new Dictionary @@ -332,7 +330,7 @@ public void Gets_Available_Cpus_From_CpuSetCpus_When_Cpu_Max_Set_To_Max_() Assert.Equal(3, cpus); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("-11")] [InlineData("0-")] [InlineData("d-22")] @@ -358,7 +356,7 @@ public Task Throws_When_CpuSet_Has_Invalid_Content(string content) return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory); } - [ConditionalFact] + [LinuxOnlyFact] public Task Fallsback_To_Cpuset_When_Quota_And_Period_Are_Minus_One_() { var f = new HardcodedValueFileSystem(new Dictionary @@ -373,7 +371,7 @@ public Task Fallsback_To_Cpuset_When_Quota_And_Period_Are_Minus_One_() return Verifier.Verify(r).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("dd1d", "18")] [InlineData("-18", "18")] [InlineData("\r\r\r\r\r", "18")] @@ -398,7 +396,7 @@ public Task Throws_When_Cgroup_Cpu_Files_Contain_Invalid_Data(string quota, stri return Verifier.Verify(r).UseParameters(quota, period).UseDirectory(VerifiedDataDirectory); } - [ConditionalFact] + [LinuxOnlyFact] public void Reads_CpuUsage_When_Valid_Input() { var f = new HardcodedValueFileSystem(new Dictionary @@ -412,7 +410,7 @@ public void Reads_CpuUsage_When_Valid_Input() Assert.Equal(77_994_900_000_000, r); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("0::/", "usage_usec 222222\nnr_periods 50", "222222000", "50")] [InlineData("0::/fakeslice", "usage_usec 222222\nnr_periods 75", "222222000", "75")] public void Reads_CpuUsageFromSlices_When_Valid_Input(string slicepath, string content, string expectedUsage, string expectedPeriods) @@ -433,7 +431,7 @@ public void Reads_CpuUsageFromSlices_When_Valid_Input(string slicepath, string c Assert.Equal(expectedPeriods, periods.ToString()); } - [ConditionalFact] + [LinuxOnlyFact] public void Reads_TotalMemory_When_Valid_Input() { var f = new HardcodedValueFileSystem(new Dictionary @@ -448,7 +446,7 @@ public void Reads_TotalMemory_When_Valid_Input() Assert.Null(r); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("2569530367000")] [InlineData(" 2569530 36700 245693 4860924 82283 0 4360 0dsa")] [InlineData("asdasd 2569530 36700 245693 4860924 82283 0 4360 0 0 0")] @@ -468,7 +466,7 @@ public Task Throws_When_CpuUsage_Invalid(string content) return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("usage_", 12222)] [InlineData("dasd", -1)] [InlineData("@#dddada", 342322)] @@ -485,7 +483,7 @@ public Task Throws_When_CpuAcctUsage_Has_Invalid_Content_Both_Parts(string conte return Verifier.Verify(r).UseParameters(content, value).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData(-32131)] [InlineData(-1)] [InlineData(-15.323)] @@ -502,7 +500,7 @@ public Task Throws_When_Usage_Usec_Has_Negative_Value(int value) return Verifier.Verify(r).UseParameters(value).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("-1")] [InlineData("dasrz3424")] [InlineData("0")] @@ -520,7 +518,7 @@ public Task Throws_When_Cgroup_Cpu_Weight_Files_Contain_Invalid_Data(string cont return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("2500", 64.0)] [InlineData("10000", 256.0)] public void Calculates_Cpu_Request_From_Cpu_Weight(string content, float result) @@ -536,7 +534,7 @@ public void Calculates_Cpu_Request_From_Cpu_Weight(string content, float result) Assert.Equal(result, r); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData("0::/", "filename", "/sys/fs/cgroup/filename")] [InlineData("0::/filesystem.slice", "filename", "/sys/fs/cgroup/filesystem.slice/filename")] [InlineData("0::/filesystem.slice/", "filename", "/sys/fs/cgroup/filesystem.slice/filename")] @@ -553,7 +551,7 @@ public void Create_Path_From_Proc_Self_Cgroup(string content, string filename, s Assert.Equal(result, r); } - [ConditionalFact] + [LinuxOnlyFact] public async Task Is_Thread_Safe_Async() { var f1 = new HardcodedValueFileSystem(new Dictionary diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs index c60ec5fa834..067f317da81 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs @@ -11,19 +11,17 @@ using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Time.Testing; using Microsoft.Shared.Instruments; -using Microsoft.TestUtilities; using Moq; using VerifyXunit; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; -[OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] public sealed class LinuxUtilizationProviderTests { private const string VerifiedDataDirectory = "Verified"; - [ConditionalFact] + [LinuxOnlyFact] [CombinatorialData] public void Provider_Registers_Instruments() { @@ -104,7 +102,7 @@ public void Provider_Registers_Instruments() Assert.Equal(0.5, samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ProcessMemoryUtilization).value); } - [ConditionalFact] + [LinuxOnlyFact] [CombinatorialData] public void Provider_Registers_Instruments_CgroupV2() { @@ -218,7 +216,7 @@ public void Provider_Creates_Meter_With_Correct_Name() Assert.Equal(ResourceUtilizationInstruments.MeterName, meter.Name); } - [ConditionalFact] + [LinuxOnlyFact] [CombinatorialData] public void Provider_Registers_Instruments_CgroupV2_WithoutHostCpu() { diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/OSFileSystemTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/OSFileSystemTests.cs index 9746d8395a5..5e7148079b3 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/OSFileSystemTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/OSFileSystemTests.cs @@ -6,15 +6,13 @@ using System.Linq; using System.Text; using Microsoft.Shared.Pools; -using Microsoft.TestUtilities; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; -[OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] public sealed class OSFileSystemTests { - [ConditionalFact] + [LinuxOnlyFact] public void GetDirectoryNames_ReturnsDirectoryNames() { var fileSystem = new OSFileSystem(); @@ -24,7 +22,7 @@ public void GetDirectoryNames_ReturnsDirectoryNames() Assert.Single(directoryNames); } - [ConditionalFact] + [LinuxOnlyFact] public void Reading_First_File_Line_Works() { const string Content = "Name: cat"; @@ -37,7 +35,7 @@ public void Reading_First_File_Line_Works() Assert.Equal(Content, s); } - [ConditionalFact] + [LinuxOnlyFact] public void Reading_The_Whole_File_Works() { const string Content = "user 1399428\nsystem 1124053\n"; @@ -51,7 +49,7 @@ public void Reading_The_Whole_File_Works() Assert.Equal(Content, s); } - [ConditionalTheory] + [LinuxOnlyTheory] [InlineData(128)] [InlineData(256)] [InlineData(512)] diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests.csproj b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests.csproj index ff2cd26412f..5ad6486db28 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests.csproj +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests.csproj @@ -19,6 +19,9 @@ - + + + + diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoRatePerfCounterTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoRatePerfCounterTests.cs index f2131afd3f5..4c7ea18c51e 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoRatePerfCounterTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoRatePerfCounterTests.cs @@ -5,19 +5,17 @@ using System.Runtime.Versioning; using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; using Microsoft.Extensions.Time.Testing; -using Microsoft.TestUtilities; using Moq; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Disk.Test; [SupportedOSPlatform("windows")] -[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows specific.")] public class WindowsDiskIoRatePerfCounterTests { private const string CategoryName = "LogicalDisk"; - [ConditionalFact] + [WindowsOnlyFact] public void DiskReadsPerfCounter_Per60Seconds() { const string CounterName = WindowsDiskPerfCounterNames.DiskReadsCounter; @@ -64,7 +62,7 @@ public void DiskReadsPerfCounter_Per60Seconds() Assert.Equal(660, ratePerfCounters.TotalCountDict["D:"]); // 450 + 3.5 * 60 = 660 } - [ConditionalFact] + [WindowsOnlyFact] public void DiskWriteBytesPerfCounter_Per30Seconds() { const string CounterName = WindowsDiskPerfCounterNames.DiskWriteBytesCounter; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoTimePerfCounterTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoTimePerfCounterTests.cs index 4daf808240b..519b884b44f 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoTimePerfCounterTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoTimePerfCounterTests.cs @@ -5,19 +5,17 @@ using System.Runtime.Versioning; using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; using Microsoft.Extensions.Time.Testing; -using Microsoft.TestUtilities; using Moq; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Disk.Test; [SupportedOSPlatform("windows")] -[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows specific.")] public class WindowsDiskIoTimePerfCounterTests { private const string CategoryName = "LogicalDisk"; - [ConditionalFact] + [WindowsOnlyFact] public void DiskReadsPerfCounter_Per60Seconds() { const string CounterName = WindowsDiskPerfCounterNames.DiskReadsCounter; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskMetricsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskMetricsTests.cs index a592aacce19..6ab504d39a8 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskMetricsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskMetricsTests.cs @@ -12,20 +12,18 @@ using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Time.Testing; using Microsoft.Shared.Instruments; -using Microsoft.TestUtilities; using Moq; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Disk.Test; [SupportedOSPlatform("windows")] -[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows specific.")] public class WindowsDiskMetricsTests { private const string CategoryName = "LogicalDisk"; private readonly FakeLogger _fakeLogger = new(); - [ConditionalFact] + [WindowsOnlyFact] public void Creates_Meter_With_Correct_Name() { using var meterFactory = new TestMeterFactory(); @@ -43,7 +41,7 @@ public void Creates_Meter_With_Correct_Name() Assert.Equal(ResourceUtilizationInstruments.MeterName, meter.Name); } - [ConditionalFact] + [WindowsOnlyFact] public void DiskOperationMetricsTest() { using var meterFactory = new TestMeterFactory(); @@ -117,7 +115,7 @@ public void DiskOperationMetricsTest() Assert.Equal(5700, measurements.Last(x => x.MatchesTags(writeTag, deviceTagD)).Value); // 3600 + 35 * 60 = 5700 } - [ConditionalFact] + [WindowsOnlyFact] public void DiskIoBytesMetricsTest() { using var meterFactory = new TestMeterFactory(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/MemoryInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/MemoryInfoTests.cs index ea20aad2d65..4454a957f9c 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/MemoryInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/MemoryInfoTests.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Interop; -using Microsoft.TestUtilities; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; @@ -12,10 +11,9 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; /// /// These tests are added for coverage reasons, but the code doesn't have /// the necessary environment predictability to really test it. -[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows specific.")] public sealed class MemoryInfoTests { - [ConditionalFact] + [WindowsOnlyFact] public void GetGlobalMemory() { var memoryStatus = new MemoryInfo().GetMemoryStatus(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterFactoryTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterFactoryTests.cs index 768fd268175..9a0f84678d8 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterFactoryTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterFactoryTests.cs @@ -2,16 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.Versioning; -using Microsoft.TestUtilities; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; [SupportedOSPlatform("windows")] -[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows specific.")] public class PerformanceCounterFactoryTests { - [ConditionalFact] + [WindowsOnlyFact] public void GetInstanceNameTest() { var performanceCounterFactory = new PerformanceCounterFactory(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterWrapperTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterWrapperTests.cs index f27646ae327..4022a62e600 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterWrapperTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterWrapperTests.cs @@ -2,16 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.Versioning; -using Microsoft.TestUtilities; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; [SupportedOSPlatform("windows")] -[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows specific.")] public class PerformanceCounterWrapperTests { - [ConditionalFact] + [WindowsOnlyFact] public void GetInstanceNameTest() { var wrapper = new PerformanceCounterWrapper("Processor", "% Processor Time", "_Total"); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/ProcessInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/ProcessInfoTests.cs index ab83f2677df..96f75b6ba7f 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/ProcessInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/ProcessInfoTests.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Interop; -using Microsoft.TestUtilities; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/SystemInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/SystemInfoTests.cs index a0e9485bd00..2051060d8f0 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/SystemInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/SystemInfoTests.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Interop; -using Microsoft.TestUtilities; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; @@ -12,13 +11,12 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; /// /// These tests are added for coverage reasons, but the code doesn't have /// the necessary environment predictability to really test it. -[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows specific.")] public sealed class SystemInfoTests { /// /// Get basic system info. /// - [ConditionalFact] + [WindowsOnlyFact] public void GetSystemInfo() { var sysInfo = new SystemInfo().GetSystemInfo(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs index a5dfdb5c170..812835780b1 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs @@ -6,7 +6,6 @@ using System.Runtime.InteropServices; using System.Threading; using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Network; -using Microsoft.TestUtilities; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; @@ -15,7 +14,6 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; /// Keep this Test to distinguish different tests for IPv6. /// [Collection("Tcp Connection Tests")] -[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows specific.")] public sealed class Tcp6TableInfoTests { public static readonly TimeSpan DefaultTimeSpan = TimeSpan.FromSeconds(5); @@ -226,7 +224,7 @@ public static uint FakeGetTcp6TableWithFakeInformation(IntPtr pTcp6Table, ref ui return (uint)NTSTATUS.Success; } - [ConditionalFact] + [WindowsOnlyFact] public void Test_Tcp6TableInfo_Get_UnsuccessfulStatus_All_The_Time() { var options = new ResourceMonitoringOptions @@ -243,7 +241,7 @@ public void Test_Tcp6TableInfo_Get_UnsuccessfulStatus_All_The_Time() }); } - [ConditionalFact] + [WindowsOnlyFact] public void Test_Tcp6TableInfo_Get_InsufficientBuffer_Then_Get_InvalidParameter() { var options = new ResourceMonitoringOptions @@ -259,7 +257,7 @@ public void Test_Tcp6TableInfo_Get_InsufficientBuffer_Then_Get_InvalidParameter( }); } - [ConditionalFact] + [WindowsOnlyFact] public void Test_Tcp6TableInfo_Get_Correct_Information() { StartTimestamp = DateTimeOffset.UtcNow; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs index 8c88fc123dd..6dc8aedf7e7 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs @@ -6,13 +6,11 @@ using System.Runtime.InteropServices; using System.Threading; using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Network; -using Microsoft.TestUtilities; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; [Collection("Tcp Connection Tests")] -[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows specific.")] public sealed class TcpTableInfoTests { public static readonly TimeSpan DefaultTimeSpan = TimeSpan.FromSeconds(5); @@ -169,7 +167,7 @@ public static unsafe uint FakeGetTcpTableWithFakeInformation(IntPtr pTcpTable, r return (uint)NTSTATUS.Success; } - [ConditionalFact] + [WindowsOnlyFact] public void Test_TcpTableInfo_Get_UnsuccessfulStatus_All_The_Time() { var options = new ResourceMonitoringOptions @@ -185,7 +183,7 @@ public void Test_TcpTableInfo_Get_UnsuccessfulStatus_All_The_Time() }); } - [ConditionalFact] + [WindowsOnlyFact] public void Test_TcpTableInfo_Get_InsufficientBuffer_Then_Get_InvalidParameter() { var options = new ResourceMonitoringOptions @@ -201,7 +199,7 @@ public void Test_TcpTableInfo_Get_InsufficientBuffer_Then_Get_InvalidParameter() }); } - [ConditionalFact] + [WindowsOnlyFact] public void Test_TcpTableInfo_Get_Correct_Information() { StartTimestamp = DateTimeOffset.UtcNow; @@ -262,7 +260,7 @@ public void Test_TcpTableInfo_Get_Correct_Information() Assert.Equal(2, tcpStateInfo.DeleteTcbCount); } - [ConditionalFact] + [WindowsOnlyFact] public void Test_TcpTableInfo_CalculateCount_default_branch() { TcpStateInfo tcpStateInfo = new(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsNetworkMetricsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsNetworkMetricsTests.cs index bafb5816eb9..2dd152a4f69 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsNetworkMetricsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsNetworkMetricsTests.cs @@ -6,16 +6,14 @@ using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Test.Helpers; using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Network; using Microsoft.Shared.Instruments; -using Microsoft.TestUtilities; using Moq; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; -[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows specific.")] public class WindowsNetworkMetricsTests { - [ConditionalFact] + [WindowsOnlyFact] public void Creates_Meter_With_Correct_Name() { using var meterFactory = new TestMeterFactory(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsSnapshotProviderTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsSnapshotProviderTests.cs index 32cf5117aac..18582d77c04 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsSnapshotProviderTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsSnapshotProviderTests.cs @@ -12,14 +12,12 @@ using Microsoft.Extensions.Options; using Microsoft.Extensions.Time.Testing; using Microsoft.Shared.Instruments; -using Microsoft.TestUtilities; using Moq; using VerifyXunit; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; -[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows specific.")] public sealed class WindowsSnapshotProviderTests { private const string VerifiedDataDirectory = "Verified"; @@ -39,7 +37,7 @@ public WindowsSnapshotProviderTests() _fakeLogger = new FakeLogger(); } - [ConditionalFact] + [WindowsOnlyFact] public void BasicConstructor() { var provider = new WindowsSnapshotProvider(_fakeLogger, _meterFactoryMock.Object, _options); @@ -51,7 +49,7 @@ public void BasicConstructor() Assert.Equal(memoryStatus.TotalPhys, provider.Resources.MaximumMemoryInBytes); } - [ConditionalFact] + [WindowsOnlyFact] public void GetSnapshot_DoesNotThrowExceptions() { var provider = new WindowsSnapshotProvider(_fakeLogger, _meterFactoryMock.Object, _options); @@ -60,7 +58,7 @@ public void GetSnapshot_DoesNotThrowExceptions() Assert.Null(exception); } - [ConditionalFact] + [WindowsOnlyFact] public Task SnapshotProvider_EmitsLogRecord() { var provider = new WindowsSnapshotProvider(_fakeLogger, _meterFactoryMock.Object, _options); @@ -71,7 +69,7 @@ public Task SnapshotProvider_EmitsLogRecord() return Verifier.Verify(logRecords[0]).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [WindowsOnlyTheory] [CombinatorialData] public void SnapshotProvider_EmitsCpuMetrics(bool useZeroToOneRange) { @@ -112,7 +110,7 @@ public void SnapshotProvider_EmitsCpuMetrics(bool useZeroToOneRange) Assert.Equal(0.05 * multiplier, metricCollector.LastMeasurement?.Value); // Still consuming 5% of the CPU } - [ConditionalTheory] + [WindowsOnlyTheory] [CombinatorialData] public void SnapshotProvider_EmitsMemoryMetrics(bool useZeroToOneRange) { @@ -162,7 +160,7 @@ public void SnapshotProvider_EmitsMemoryMetrics(bool useZeroToOneRange) Assert.Equal(1 * multiplier, Math.Round(metricCollector.LastMeasurement.Value)); // Consuming 100% of the memory } - [ConditionalFact] + [WindowsOnlyFact] public void Provider_Returns_MemoryConsumption() { // This is a synthetic test to have full test coverage: @@ -170,7 +168,7 @@ public void Provider_Returns_MemoryConsumption() Assert.InRange(usage, 0, long.MaxValue); } - [ConditionalFact] + [WindowsOnlyFact] public void Provider_Creates_Meter_With_Correct_Name() { using var meterFactory = new TestMeterFactory(); diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs index 1f82e25f135..2aed676a616 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.Extensions.TestHelpers; using Microsoft.Shared.ProjectTemplates.Tests; -using Microsoft.TestUtilities; using Xunit; using Xunit.Abstractions; @@ -95,8 +95,7 @@ public async Task CreateRestoreAndBuild_AspireProjectName() /// environment variable AI_TEMPLATES_TEST_PROJECT_NAMES to "true" or "1" /// to enable it. /// - [ConditionalTheory] - [EnvironmentVariableCondition("AI_TEMPLATES_TEST_PROJECT_NAMES", "true", "1")] + [ConditionalTheory(nameof(TestConditions.IsAITemplatesTestProjectNamesSet))] [MemberData(nameof(GetAspireProjectNameVariants))] public async Task CreateRestoreAndBuild_AspireProjectName_Variants(string provider, string projectName) { diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj index bd83d4fa93e..bf58c4de76e 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj @@ -11,12 +11,13 @@ + - + From 99ab0d3952f1f8e761a3ee26c51994d252e5046c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:56:07 +0000 Subject: [PATCH 04/16] Add Microsoft.DotNet.XUnitExtensions to CPM and fix remaining test files Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- eng/packages/TestOnly.props | 1 + .../Microsoft.AspNetCore.Testing.Tests.csproj | 4 ++-- .../OllamaSharpChatClientIntegrationTests.cs | 2 +- .../OllamaSharpEmbeddingGeneratorIntegrationTests.cs | 1 - .../OpenAIAssistantChatClientIntegrationTests.cs | 1 - .../OpenAIResponseClientIntegrationTests.cs | 2 +- .../ResourceMonitoringBuilderTests.cs | 1 - .../ResourceMonitoringExtensionsTests.cs | 1 - 8 files changed, 5 insertions(+), 8 deletions(-) diff --git a/eng/packages/TestOnly.props b/eng/packages/TestOnly.props index 513f82789d9..5e6188a0812 100644 --- a/eng/packages/TestOnly.props +++ b/eng/packages/TestOnly.props @@ -10,6 +10,7 @@ + diff --git a/test/Libraries/Microsoft.AspNetCore.Testing.Tests/Microsoft.AspNetCore.Testing.Tests.csproj b/test/Libraries/Microsoft.AspNetCore.Testing.Tests/Microsoft.AspNetCore.Testing.Tests.csproj index a2c3e84fa3f..14a3f443e5b 100644 --- a/test/Libraries/Microsoft.AspNetCore.Testing.Tests/Microsoft.AspNetCore.Testing.Tests.csproj +++ b/test/Libraries/Microsoft.AspNetCore.Testing.Tests/Microsoft.AspNetCore.Testing.Tests.csproj @@ -11,10 +11,10 @@ - + + - diff --git a/test/Libraries/Microsoft.Extensions.AI.OllamaSharp.Integration.Tests/OllamaSharpChatClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.OllamaSharp.Integration.Tests/OllamaSharpChatClientIntegrationTests.cs index 28d3e21fd65..682810ca1ad 100644 --- a/test/Libraries/Microsoft.Extensions.AI.OllamaSharp.Integration.Tests/OllamaSharpChatClientIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.OllamaSharp.Integration.Tests/OllamaSharpChatClientIntegrationTests.cs @@ -6,9 +6,9 @@ using System.ComponentModel; using System.Threading; using System.Threading.Tasks; -using Microsoft.TestUtilities; using OllamaSharp; using Xunit; +using Microsoft.DotNet.XUnitExtensions; namespace Microsoft.Extensions.AI; diff --git a/test/Libraries/Microsoft.Extensions.AI.OllamaSharp.Integration.Tests/OllamaSharpEmbeddingGeneratorIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.OllamaSharp.Integration.Tests/OllamaSharpEmbeddingGeneratorIntegrationTests.cs index f7775143c36..b5a21aa78ec 100644 --- a/test/Libraries/Microsoft.Extensions.AI.OllamaSharp.Integration.Tests/OllamaSharpEmbeddingGeneratorIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.OllamaSharp.Integration.Tests/OllamaSharpEmbeddingGeneratorIntegrationTests.cs @@ -3,7 +3,6 @@ using System; using System.Threading.Tasks; -using Microsoft.TestUtilities; using OllamaSharp; using Xunit; diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIAssistantChatClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIAssistantChatClientIntegrationTests.cs index ef9d6063ddd..0c85ea5c23f 100644 --- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIAssistantChatClientIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIAssistantChatClientIntegrationTests.cs @@ -12,7 +12,6 @@ using System.Net.Http; using System.Text.RegularExpressions; using System.Threading.Tasks; -using Microsoft.TestUtilities; using OpenAI.Assistants; using Xunit; diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientIntegrationTests.cs index 6a7f82302ea..d732bdbf95b 100644 --- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientIntegrationTests.cs @@ -5,8 +5,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Microsoft.TestUtilities; using Xunit; +using Microsoft.DotNet.XUnitExtensions; namespace Microsoft.Extensions.AI; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringBuilderTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringBuilderTests.cs index d2e3d5d8292..f5a32d277aa 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringBuilderTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringBuilderTests.cs @@ -4,7 +4,6 @@ using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Test.Publishers; -using Microsoft.TestUtilities; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Test; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringExtensionsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringExtensionsTests.cs index 875fbb67158..d0e1d50eb8b 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringExtensionsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringExtensionsTests.cs @@ -11,7 +11,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting.Testing; using Microsoft.Extensions.Options; -using Microsoft.TestUtilities; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Test; From 324328059afae72489eaa74f5e857da85843be82 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:59:28 +0000 Subject: [PATCH 05/16] Remove TestUtilities project - migration complete Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- test/TestUtilities/TestUtilities.csproj | 21 ---- .../XUnit/ConditionalFactAttribute.cs | 16 ---- .../XUnit/ConditionalFactDiscoverer.cs | 30 ------ .../XUnit/ConditionalTheoryAttribute.cs | 16 ---- .../XUnit/ConditionalTheoryDiscoverer.cs | 95 ------------------- .../EnvironmentVariableConditionAttribute.cs | 86 ----------------- test/TestUtilities/XUnit/ITestCondition.cs | 13 --- .../XUnit/OSSkipConditionAttribute.cs | 69 -------------- test/TestUtilities/XUnit/OperatingSystems.cs | 16 ---- test/TestUtilities/XUnit/SkipTestException.cs | 16 ---- .../XUnit/SkippedFactTestCase.cs | 42 -------- test/TestUtilities/XUnit/SkippedTestCase.cs | 52 ---------- .../XUnit/SkippedTestMessageBus.cs | 44 --------- .../XUnit/SkippedTheoryTestCase.cs | 49 ---------- .../XUnit/TestMethodExtensions.cs | 35 ------- .../WORKAROUND_SkippedDataRowTestCase.cs | 88 ----------------- 16 files changed, 688 deletions(-) delete mode 100644 test/TestUtilities/TestUtilities.csproj delete mode 100644 test/TestUtilities/XUnit/ConditionalFactAttribute.cs delete mode 100644 test/TestUtilities/XUnit/ConditionalFactDiscoverer.cs delete mode 100644 test/TestUtilities/XUnit/ConditionalTheoryAttribute.cs delete mode 100644 test/TestUtilities/XUnit/ConditionalTheoryDiscoverer.cs delete mode 100644 test/TestUtilities/XUnit/EnvironmentVariableConditionAttribute.cs delete mode 100644 test/TestUtilities/XUnit/ITestCondition.cs delete mode 100644 test/TestUtilities/XUnit/OSSkipConditionAttribute.cs delete mode 100644 test/TestUtilities/XUnit/OperatingSystems.cs delete mode 100644 test/TestUtilities/XUnit/SkipTestException.cs delete mode 100644 test/TestUtilities/XUnit/SkippedFactTestCase.cs delete mode 100644 test/TestUtilities/XUnit/SkippedTestCase.cs delete mode 100644 test/TestUtilities/XUnit/SkippedTestMessageBus.cs delete mode 100644 test/TestUtilities/XUnit/SkippedTheoryTestCase.cs delete mode 100644 test/TestUtilities/XUnit/TestMethodExtensions.cs delete mode 100644 test/TestUtilities/XUnit/WORKAROUND_SkippedDataRowTestCase.cs diff --git a/test/TestUtilities/TestUtilities.csproj b/test/TestUtilities/TestUtilities.csproj deleted file mode 100644 index d9e307f1e6d..00000000000 --- a/test/TestUtilities/TestUtilities.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - Microsoft.TestUtilities - Microsoft.TestUtilities - $(TestNetCoreTargetFrameworks)$(ConditionalNet462) - - - - - - - - diff --git a/test/TestUtilities/XUnit/ConditionalFactAttribute.cs b/test/TestUtilities/XUnit/ConditionalFactAttribute.cs deleted file mode 100644 index 92077e27dbc..00000000000 --- a/test/TestUtilities/XUnit/ConditionalFactAttribute.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// Borrowed from https://github.com/dotnet/aspnetcore/blob/95ed45c67/src/Testing/src/xunit/ - -using System; -using Xunit; -using Xunit.Sdk; - -namespace Microsoft.TestUtilities; - -[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] -[XunitTestCaseDiscoverer("Microsoft.TestUtilities." + nameof(ConditionalFactDiscoverer), "Microsoft.TestUtilities")] -public class ConditionalFactAttribute : FactAttribute -{ -} diff --git a/test/TestUtilities/XUnit/ConditionalFactDiscoverer.cs b/test/TestUtilities/XUnit/ConditionalFactDiscoverer.cs deleted file mode 100644 index e007d95860a..00000000000 --- a/test/TestUtilities/XUnit/ConditionalFactDiscoverer.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// Borrowed from https://github.com/dotnet/aspnetcore/blob/95ed45c67/src/Testing/src/xunit/ - -using Xunit.Abstractions; -using Xunit.Sdk; - -// Do not change this namespace without changing the usage in ConditionalFactAttribute -namespace Microsoft.TestUtilities; - -internal sealed class ConditionalFactDiscoverer : FactDiscoverer -{ - private readonly IMessageSink _diagnosticMessageSink; - - public ConditionalFactDiscoverer(IMessageSink diagnosticMessageSink) - : base(diagnosticMessageSink) - { - _diagnosticMessageSink = diagnosticMessageSink; - } - - protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) - { - var skipReason = testMethod.EvaluateSkipConditions(); - return skipReason != null - ? new SkippedTestCase(skipReason, _diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod) - : new SkippedFactTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), - discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod); // Test case skippable at runtime. - } -} diff --git a/test/TestUtilities/XUnit/ConditionalTheoryAttribute.cs b/test/TestUtilities/XUnit/ConditionalTheoryAttribute.cs deleted file mode 100644 index d5f23068dd0..00000000000 --- a/test/TestUtilities/XUnit/ConditionalTheoryAttribute.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// Borrowed from https://github.com/dotnet/aspnetcore/blob/95ed45c67/src/Testing/src/xunit/ - -using System; -using Xunit; -using Xunit.Sdk; - -namespace Microsoft.TestUtilities; - -[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] -[XunitTestCaseDiscoverer("Microsoft.TestUtilities." + nameof(ConditionalTheoryDiscoverer), "Microsoft.TestUtilities")] -public class ConditionalTheoryAttribute : TheoryAttribute -{ -} diff --git a/test/TestUtilities/XUnit/ConditionalTheoryDiscoverer.cs b/test/TestUtilities/XUnit/ConditionalTheoryDiscoverer.cs deleted file mode 100644 index e30b5206c8c..00000000000 --- a/test/TestUtilities/XUnit/ConditionalTheoryDiscoverer.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// Borrowed from https://github.com/dotnet/aspnetcore/blob/95ed45c67/src/Testing/src/xunit/ - -using System.Collections.Generic; -using Xunit.Abstractions; -using Xunit.Sdk; - -// Do not change this namespace without changing the usage in ConditionalTheoryAttribute -namespace Microsoft.TestUtilities; - -internal sealed class ConditionalTheoryDiscoverer : TheoryDiscoverer -{ - public ConditionalTheoryDiscoverer(IMessageSink diagnosticMessageSink) - : base(diagnosticMessageSink) - { - } - - private sealed class OptionsWithPreEnumerationEnabled : ITestFrameworkDiscoveryOptions - { - private const string PreEnumerateTheories = "xunit.discovery.PreEnumerateTheories"; - - private readonly ITestFrameworkDiscoveryOptions _original; - - public OptionsWithPreEnumerationEnabled(ITestFrameworkDiscoveryOptions original) - { - _original = original; - } - - public TValue GetValue(string name) - => (name == PreEnumerateTheories) ? (TValue)(object)true : _original.GetValue(name); - - public void SetValue(string name, TValue value) - => _original.SetValue(name, value); - } - - public override IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute) - => base.Discover(new OptionsWithPreEnumerationEnabled(discoveryOptions), testMethod, theoryAttribute); - - protected override IEnumerable CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute) - { - var skipReason = testMethod.EvaluateSkipConditions(); - return skipReason != null - ? new[] { new SkippedTestCase(skipReason, DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod) } - : base.CreateTestCasesForTheory(discoveryOptions, testMethod, theoryAttribute); - } - - protected override IEnumerable CreateTestCasesForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[]? dataRow) - { - var skipReason = testMethod.EvaluateSkipConditions(); - if (skipReason == null && dataRow?.Length > 0) - { - var obj = dataRow[0]; - if (obj != null) - { - var type = obj.GetType(); - var property = type.GetProperty("Skip"); - if (property != null && property.PropertyType.Equals(typeof(string))) - { - skipReason = property.GetValue(obj) as string; - } - } - } - - if (skipReason != null) - { - return base.CreateTestCasesForSkippedDataRow(discoveryOptions, testMethod, theoryAttribute, dataRow, skipReason); - } - - // Create test cases that can handle runtime SkipTestException - return new[] - { - new SkippedTheoryTestCase( - DiagnosticMessageSink, - discoveryOptions.MethodDisplayOrDefault(), - discoveryOptions.MethodDisplayOptionsOrDefault(), - testMethod, - dataRow) - }; - } - - protected override IEnumerable CreateTestCasesForSkippedDataRow( - ITestFrameworkDiscoveryOptions discoveryOptions, - ITestMethod testMethod, - IAttributeInfo theoryAttribute, - object[] dataRow, - string skipReason) - { - return new[] - { - new WORKAROUND_SkippedDataRowTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, skipReason, dataRow), - }; - } -} diff --git a/test/TestUtilities/XUnit/EnvironmentVariableConditionAttribute.cs b/test/TestUtilities/XUnit/EnvironmentVariableConditionAttribute.cs deleted file mode 100644 index 45a54409047..00000000000 --- a/test/TestUtilities/XUnit/EnvironmentVariableConditionAttribute.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Linq; - -namespace Microsoft.TestUtilities; - -/// -/// Skips a test based on the value of an environment variable. -/// -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] -public class EnvironmentVariableConditionAttribute : Attribute, ITestCondition -{ - private string? _currentValue; - - /// - /// Initializes a new instance of the class. - /// - /// Name of the environment variable. - /// Value(s) of the environment variable to match for the condition. - /// - /// By default, the test will be run if the value of the variable matches any of the supplied values. - /// Set to False to run the test only if the value does not match. - /// - public EnvironmentVariableConditionAttribute(string variableName, params string[] values) - { - if (string.IsNullOrEmpty(variableName)) - { - throw new ArgumentException("Value cannot be null or empty.", nameof(variableName)); - } - - if (values == null || values.Length == 0) - { - throw new ArgumentException("You must supply at least one value to match.", nameof(values)); - } - - VariableName = variableName; - Values = values; - } - - /// - /// Gets or sets a value indicating whether the test should run if the value of the variable matches any - /// of the supplied values. If False, the test runs only if the value does not match any of the - /// supplied values. Default is True. - /// - public bool RunOnMatch { get; set; } = true; - - /// - /// Gets the name of the environment variable. - /// - public string VariableName { get; } - - /// - /// Gets the value(s) of the environment variable to match for the condition. - /// - public string[] Values { get; } - - /// - /// Gets a value indicating whether the condition is met for the configured environment variable and values. - /// - public bool IsMet - { - get - { - _currentValue ??= Environment.GetEnvironmentVariable(VariableName); - var hasMatched = Values.Any(value => string.Equals(value, _currentValue, StringComparison.OrdinalIgnoreCase)); - - return RunOnMatch ? hasMatched : !hasMatched; - } - } - - /// - /// Gets a value indicating the reason the test was skipped. - /// - public string SkipReason - { - get - { - var value = _currentValue ?? "(null)"; - - return $"Test skipped on environment variable with name '{VariableName}' and value '{value}' " + - $"for the '{nameof(RunOnMatch)}' value of '{RunOnMatch}'."; - } - } -} diff --git a/test/TestUtilities/XUnit/ITestCondition.cs b/test/TestUtilities/XUnit/ITestCondition.cs deleted file mode 100644 index 347f3c69007..00000000000 --- a/test/TestUtilities/XUnit/ITestCondition.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// Borrowed from https://github.com/dotnet/aspnetcore/blob/95ed45c67/src/Testing/src/xunit/ - -namespace Microsoft.TestUtilities; - -public interface ITestCondition -{ - bool IsMet { get; } - - string SkipReason { get; } -} diff --git a/test/TestUtilities/XUnit/OSSkipConditionAttribute.cs b/test/TestUtilities/XUnit/OSSkipConditionAttribute.cs deleted file mode 100644 index 586b53d3fcb..00000000000 --- a/test/TestUtilities/XUnit/OSSkipConditionAttribute.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// Borrowed from https://github.com/dotnet/aspnetcore/blob/95ed45c67/src/Testing/src/xunit/ - -using System; -#if NETCOREAPP || NET471_OR_GREATER -using System.Runtime.InteropServices; -#endif - -namespace Microsoft.TestUtilities; - -#pragma warning disable CA1019 // Define accessors for attribute arguments -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] -public class OSSkipConditionAttribute : Attribute, ITestCondition -{ - private readonly OperatingSystems _excludedOperatingSystem; - private readonly OperatingSystems _osPlatform; - - public OSSkipConditionAttribute(OperatingSystems operatingSystem) - : this(operatingSystem, GetCurrentOS()) - { - } - - // to enable unit testing - internal OSSkipConditionAttribute(OperatingSystems operatingSystem, OperatingSystems osPlatform) - { - _excludedOperatingSystem = operatingSystem; - _osPlatform = osPlatform; - } - - public bool IsMet - { - get - { - var skip = (_excludedOperatingSystem & _osPlatform) == _osPlatform; - - // Since a test would be executed only if 'IsMet' is true, return false if we want to skip - return !skip; - } - } - - public string SkipReason { get; set; } = "Test cannot run on this operating system."; - - private static OperatingSystems GetCurrentOS() - { -#if NETCOREAPP || NET471_OR_GREATER - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return OperatingSystems.Windows; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return OperatingSystems.Linux; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return OperatingSystems.MacOSX; - } - - throw new PlatformNotSupportedException(); -#else - // RuntimeInformation API is only available in .NET Framework 4.7.1+ - // .NET Framework 4.7 and below can only run on Windows. - return OperatingSystems.Windows; -#endif - } -} -#pragma warning restore CA1019 // Define accessors for attribute arguments diff --git a/test/TestUtilities/XUnit/OperatingSystems.cs b/test/TestUtilities/XUnit/OperatingSystems.cs deleted file mode 100644 index 3bee3bac969..00000000000 --- a/test/TestUtilities/XUnit/OperatingSystems.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// Borrowed from https://github.com/dotnet/aspnetcore/blob/95ed45c67/src/Testing/src/xunit/ - -using System; - -namespace Microsoft.TestUtilities; - -[Flags] -public enum OperatingSystems -{ - Linux = 1, - MacOSX = 2, - Windows = 4, -} diff --git a/test/TestUtilities/XUnit/SkipTestException.cs b/test/TestUtilities/XUnit/SkipTestException.cs deleted file mode 100644 index 70f7d53c7d8..00000000000 --- a/test/TestUtilities/XUnit/SkipTestException.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// Borrowed from https://github.com/dotnet/aspnetcore/blob/95ed45c67/src/Testing/src/xunit/ - -using System; - -namespace Microsoft.TestUtilities; - -public class SkipTestException : Exception -{ - public SkipTestException(string reason) - : base(reason) - { - } -} diff --git a/test/TestUtilities/XUnit/SkippedFactTestCase.cs b/test/TestUtilities/XUnit/SkippedFactTestCase.cs deleted file mode 100644 index 79ace15ea6e..00000000000 --- a/test/TestUtilities/XUnit/SkippedFactTestCase.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Microsoft.TestUtilities; - -public class SkippedFactTestCase : XunitTestCase -{ - [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes", error: true)] - public SkippedFactTestCase() - { - } - - public SkippedFactTestCase( - IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, - ITestMethod testMethod, object[]? testMethodArguments = null) - : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments) - { - } - - public override async Task RunAsync(IMessageSink diagnosticMessageSink, - IMessageBus messageBus, - object[] constructorArguments, - ExceptionAggregator aggregator, - CancellationTokenSource cancellationTokenSource) - { - using SkippedTestMessageBus skipMessageBus = new(messageBus); - var result = await base.RunAsync(diagnosticMessageSink, skipMessageBus, constructorArguments, aggregator, cancellationTokenSource); - if (skipMessageBus.SkippedTestCount > 0) - { - result.Failed -= skipMessageBus.SkippedTestCount; - result.Skipped += skipMessageBus.SkippedTestCount; - } - - return result; - } -} diff --git a/test/TestUtilities/XUnit/SkippedTestCase.cs b/test/TestUtilities/XUnit/SkippedTestCase.cs deleted file mode 100644 index 7b59125ffb8..00000000000 --- a/test/TestUtilities/XUnit/SkippedTestCase.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// Borrowed from https://github.com/dotnet/aspnetcore/blob/95ed45c67/src/Testing/src/xunit/ - -#nullable disable - -using System; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Microsoft.TestUtilities; - -public class SkippedTestCase : XunitTestCase -{ - private string _skipReason; - - [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] - public SkippedTestCase() - { - } - - public SkippedTestCase( - string skipReason, - IMessageSink diagnosticMessageSink, - TestMethodDisplay defaultMethodDisplay, - TestMethodDisplayOptions defaultMethodDisplayOptions, - ITestMethod testMethod, - object[] testMethodArguments = null) - : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments) - { - _skipReason = skipReason; - } - - protected override string GetSkipReason(IAttributeInfo factAttribute) - => _skipReason ?? base.GetSkipReason(factAttribute); - - public override void Deserialize(IXunitSerializationInfo data) - { - _skipReason = data.GetValue(nameof(_skipReason)); - - // We need to call base after reading our value, because Deserialize will call - // into GetSkipReason. - base.Deserialize(data); - } - - public override void Serialize(IXunitSerializationInfo data) - { - base.Serialize(data); - data.AddValue(nameof(_skipReason), _skipReason); - } -} diff --git a/test/TestUtilities/XUnit/SkippedTestMessageBus.cs b/test/TestUtilities/XUnit/SkippedTestMessageBus.cs deleted file mode 100644 index 230586852b8..00000000000 --- a/test/TestUtilities/XUnit/SkippedTestMessageBus.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Linq; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Microsoft.TestUtilities; - -/// Implements message bus to communicate tests skipped via SkipTestException. -public sealed class SkippedTestMessageBus : IMessageBus -{ - private readonly IMessageBus _innerBus; - - public SkippedTestMessageBus(IMessageBus innerBus) - { - _innerBus = innerBus; - } - - public int SkippedTestCount { get; private set; } - - public void Dispose() - { - // nothing to dispose - } - - public bool QueueMessage(IMessageSinkMessage message) - { - var testFailed = message as ITestFailed; - - if (testFailed != null) - { - var exceptionType = testFailed.ExceptionTypes.FirstOrDefault(); - if (exceptionType == typeof(SkipTestException).FullName) - { - SkippedTestCount++; - return _innerBus.QueueMessage(new TestSkipped(testFailed.Test, testFailed.Messages.FirstOrDefault())); - } - } - - // Nothing we care about, send it on its way - return _innerBus.QueueMessage(message); - } -} diff --git a/test/TestUtilities/XUnit/SkippedTheoryTestCase.cs b/test/TestUtilities/XUnit/SkippedTheoryTestCase.cs deleted file mode 100644 index e91a8f762d5..00000000000 --- a/test/TestUtilities/XUnit/SkippedTheoryTestCase.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Microsoft.TestUtilities; - -/// -/// A test case for ConditionalTheory that can handle runtime SkipTestException -/// by wrapping the message bus with SkippedTestMessageBus. -/// -public class SkippedTheoryTestCase : XunitTestCase -{ - [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes", error: true)] - public SkippedTheoryTestCase() - { - } - - public SkippedTheoryTestCase( - IMessageSink diagnosticMessageSink, - TestMethodDisplay defaultMethodDisplay, - TestMethodDisplayOptions defaultMethodDisplayOptions, - ITestMethod testMethod, - object[]? testMethodArguments = null) - : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments) - { - } - - public override async Task RunAsync(IMessageSink diagnosticMessageSink, - IMessageBus messageBus, - object[] constructorArguments, - ExceptionAggregator aggregator, - CancellationTokenSource cancellationTokenSource) - { - using SkippedTestMessageBus skipMessageBus = new(messageBus); - var result = await base.RunAsync(diagnosticMessageSink, skipMessageBus, constructorArguments, aggregator, cancellationTokenSource); - if (skipMessageBus.SkippedTestCount > 0) - { - result.Failed -= skipMessageBus.SkippedTestCount; - result.Skipped += skipMessageBus.SkippedTestCount; - } - - return result; - } -} \ No newline at end of file diff --git a/test/TestUtilities/XUnit/TestMethodExtensions.cs b/test/TestUtilities/XUnit/TestMethodExtensions.cs deleted file mode 100644 index 88356330daf..00000000000 --- a/test/TestUtilities/XUnit/TestMethodExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// Borrowed from https://github.com/dotnet/aspnetcore/blob/95ed45c67/src/Testing/src/xunit/ - -using System.Linq; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Microsoft.TestUtilities; - -public static class TestMethodExtensions -{ - public static string? EvaluateSkipConditions(this ITestMethod testMethod) - { - var testClass = testMethod.TestClass.Class; - var assembly = testMethod.TestClass.TestCollection.TestAssembly.Assembly; - var conditionAttributes = testMethod.Method - .GetCustomAttributes(typeof(ITestCondition)) - .Concat(testClass.GetCustomAttributes(typeof(ITestCondition))) - .Concat(assembly.GetCustomAttributes(typeof(ITestCondition))) - .OfType() - .Select(attributeInfo => attributeInfo.Attribute); - - foreach (ITestCondition condition in conditionAttributes.OfType()) - { - if (!condition.IsMet) - { - return condition.SkipReason; - } - } - - return null; - } -} diff --git a/test/TestUtilities/XUnit/WORKAROUND_SkippedDataRowTestCase.cs b/test/TestUtilities/XUnit/WORKAROUND_SkippedDataRowTestCase.cs deleted file mode 100644 index 123dba2fa48..00000000000 --- a/test/TestUtilities/XUnit/WORKAROUND_SkippedDataRowTestCase.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// Borrowed from https://github.com/dotnet/aspnetcore/blob/95ed45c67/src/Testing/src/xunit/ - -using System; -using System.ComponentModel; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Microsoft.TestUtilities; - -// This is a workaround for https://github.com/xunit/xunit/issues/1782 - as such, this code is a copy-paste -// from xUnit with the exception of fixing the bug. -// -// This will only work with [ConditionalTheory]. -internal sealed class WORKAROUND_SkippedDataRowTestCase : XunitTestCase -{ - private string? _skipReason; - - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] - public WORKAROUND_SkippedDataRowTestCase() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The message sink used to send diagnostic messages. - /// Default method display to use (when not customized). - /// The test method this test case belongs to. - /// The reason that this test case will be skipped. - /// The arguments for the test method. - [Obsolete("Please call the constructor which takes TestMethodDisplayOptions")] - public WORKAROUND_SkippedDataRowTestCase(IMessageSink diagnosticMessageSink, - TestMethodDisplay defaultMethodDisplay, - ITestMethod testMethod, - string skipReason, - object[]? testMethodArguments = null) - : this(diagnosticMessageSink, defaultMethodDisplay, TestMethodDisplayOptions.None, testMethod, skipReason, testMethodArguments) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The message sink used to send diagnostic messages. - /// Default method display to use (when not customized). - /// Default method display options to use (when not customized). - /// The test method this test case belongs to. - /// The reason that this test case will be skipped. - /// The arguments for the test method. - public WORKAROUND_SkippedDataRowTestCase(IMessageSink diagnosticMessageSink, - TestMethodDisplay defaultMethodDisplay, - TestMethodDisplayOptions defaultMethodDisplayOptions, - ITestMethod testMethod, - string skipReason, - object[]? testMethodArguments = null) - : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments) - { - _skipReason = skipReason; - } - - /// - public override void Deserialize(IXunitSerializationInfo data) - { - // SkipReason has to be read before we call base.Deserialize, this is the workaround. - _skipReason = data.GetValue("SkipReason"); - - base.Deserialize(data); - } - - /// - protected override string? GetSkipReason(IAttributeInfo factAttribute) - { - return _skipReason; - } - - /// - public override void Serialize(IXunitSerializationInfo data) - { - base.Serialize(data); - - data.AddValue("SkipReason", _skipReason); - } -} From 4a50248f07e11de85afa7698c4d85cc1af89560b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:38:10 +0000 Subject: [PATCH 06/16] Address code review feedback - use PlatformSpecific with negation and fix ConditionalTheory Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../FakeCertificateFactoryTests.cs | 4 +- .../Linux/AcceptanceTest.cs | 22 ++++--- .../Linux/Disk/DiskStatsReaderTests.cs | 1 + .../Linux/Disk/LinuxSystemDiskMetricsTests.cs | 1 + .../Linux/LinuxCountersTests.cs | 1 + .../Linux/LinuxNetworkMetricsTests.cs | 1 + .../LinuxNetworkUtilizationParserTests.cs | 1 + .../LinuxUtilizationParserCgroupV1Tests.cs | 35 +++++------ .../LinuxUtilizationParserCgroupV2Tests.cs | 59 ++++++++++--------- .../Linux/LinuxUtilizationProviderTests.cs | 7 ++- .../Linux/OSFileSystemTests.cs | 9 +-- .../ResourceMonitoringBuilderTests.cs | 4 +- .../ResourceMonitoringExtensionsTests.cs | 44 +++++++------- .../Disk/WindowsDiskIoRatePerfCounterTests.cs | 5 +- .../Disk/WindowsDiskIoTimePerfCounterTests.cs | 3 +- .../Windows/Disk/WindowsDiskMetricsTests.cs | 7 ++- .../Windows/FakePerformanceCounter.cs | 2 + .../Windows/MemoryInfoTests.cs | 3 +- .../Windows/PerformanceCounterFactoryTests.cs | 3 +- .../Windows/PerformanceCounterWrapperTests.cs | 3 +- .../Windows/SystemInfoTests.cs | 3 +- .../Windows/Tcp6TableInfoTests.cs | 7 ++- .../Windows/TcpTableInfoTests.cs | 9 +-- .../WindowsContainerSnapshotProviderTests.cs | 1 + .../Windows/WindowsNetworkMetricsTests.cs | 3 +- .../Windows/WindowsSnapshotProviderTests.cs | 15 ++--- .../AIChatWebExecutionTests.cs | 2 +- ...osoft.Extensions.AI.Templates.Tests.csproj | 1 - 28 files changed, 142 insertions(+), 114 deletions(-) diff --git a/test/Libraries/Microsoft.AspNetCore.Testing.Tests/FakeCertificateFactoryTests.cs b/test/Libraries/Microsoft.AspNetCore.Testing.Tests/FakeCertificateFactoryTests.cs index 72281c74233..227485ee2ff 100644 --- a/test/Libraries/Microsoft.AspNetCore.Testing.Tests/FakeCertificateFactoryTests.cs +++ b/test/Libraries/Microsoft.AspNetCore.Testing.Tests/FakeCertificateFactoryTests.cs @@ -22,7 +22,7 @@ public void Create_CreatesCertificate() Assert.False(certificate.Extensions.OfType().Single().Critical); } - [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.OSX)] + [PlatformSpecific(~TestPlatforms.Linux)] [Theory] [InlineData(false)] [InlineData(true)] @@ -31,7 +31,7 @@ public void GenerateRsa_RunsOnWindows_GeneratesRsa(bool runsOnWindows) Assert.NotNull(FakeSslCertificateFactory.GenerateRsa(runsOnWindows)); } - [PlatformSpecific(TestPlatforms.Linux | TestPlatforms.OSX)] + [PlatformSpecific(~TestPlatforms.Windows)] [Fact] public void GenerateRsa_DoesNotRunOnWindows_GeneratesRsa() { diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs index a39b52fa6cb..4f3446dd5e0 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs @@ -23,9 +23,11 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; +[PlatformSpecific(TestPlatforms.Linux)] public sealed class AcceptanceTest { - [LinuxOnlyFact] + [ConditionalFact] + [PlatformSpecific(TestPlatforms.Linux)] public void Adding_Linux_Resource_Utilization_Allows_To_Query_Snapshot_Provider() { using var services = new ServiceCollection() @@ -38,7 +40,8 @@ public void Adding_Linux_Resource_Utilization_Allows_To_Query_Snapshot_Provider( Assert.NotEqual(default, provider.GetSnapshot()); } - [LinuxOnlyFact] + [ConditionalFact] + [PlatformSpecific(TestPlatforms.Linux)] [SuppressMessage("Minor Code Smell", "S3257:Declarations and initializations should be as concise as possible", Justification = "Broken analyzer.")] public void Adding_Linux_Resource_Utilization_Can_Be_Configured_With_Section() { @@ -67,7 +70,8 @@ public void Adding_Linux_Resource_Utilization_Can_Be_Configured_With_Section() Assert.Equal(memoryRefresh, options.Value.MemoryConsumptionRefreshInterval); } - [LinuxOnlyFact] + [ConditionalFact] + [PlatformSpecific(TestPlatforms.Linux)] public void Adding_Linux_Resource_Utilization_Can_Be_Configured_With_Action() { var cpuRefresh = TimeSpan.FromMinutes(13); @@ -88,7 +92,8 @@ public void Adding_Linux_Resource_Utilization_Can_Be_Configured_With_Action() Assert.Equal(memoryRefresh, options.Value.MemoryConsumptionRefreshInterval); } - [LinuxOnlyFact] + [ConditionalFact] + [PlatformSpecific(TestPlatforms.Linux)] [SuppressMessage("Minor Code Smell", "S3257:Declarations and initializations should be as concise as possible", Justification = "Broken analyzer.")] public void Adding_Linux_Resource_Utilization_With_Section_Registers_SnapshotProvider_Cgroupv1() { @@ -136,7 +141,8 @@ public void Adding_Linux_Resource_Utilization_With_Section_Registers_SnapshotPro Assert.Equal(100_000UL, provider.Resources.MaximumMemoryInBytes); } - [LinuxOnlyFact] + [ConditionalFact] + [PlatformSpecific(TestPlatforms.Linux)] [SuppressMessage("Minor Code Smell", "S3257:Declarations and initializations should be as concise as possible", Justification = "Broken analyzer.")] public void Adding_Linux_Resource_Utilization_With_Section_Registers_SnapshotProvider_Cgroupv2() { @@ -185,7 +191,7 @@ public void Adding_Linux_Resource_Utilization_With_Section_Registers_SnapshotPro [ConditionalFact] [CombinatorialData] - [OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] + [PlatformSpecific(TestPlatforms.Linux)] public Task ResourceUtilizationTracker_And_Metrics_Report_Same_Values_With_Cgroupsv1() { var cpuRefresh = TimeSpan.FromMinutes(13); @@ -283,7 +289,7 @@ public Task ResourceUtilizationTracker_And_Metrics_Report_Same_Values_With_Cgrou [ConditionalFact] [CombinatorialData] - [OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] + [PlatformSpecific(TestPlatforms.Linux)] public Task ResourceUtilizationTracker_And_Metrics_Report_Same_Values_With_Cgroupsv2() { var cpuRefresh = TimeSpan.FromMinutes(13); @@ -391,7 +397,7 @@ public Task ResourceUtilizationTracker_And_Metrics_Report_Same_Values_With_Cgrou [ConditionalFact] [CombinatorialData] - [OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux specific tests")] + [PlatformSpecific(TestPlatforms.Linux)] public Task ResourceUtilizationTracker_And_Metrics_Report_Same_Values_With_Cgroupsv2_Using_LinuxCalculationV2() { var fileSystem = new HardcodedValueFileSystem(new Dictionary diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/DiskStatsReaderTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/DiskStatsReaderTests.cs index a669ef17743..15ca01d60d4 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/DiskStatsReaderTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/DiskStatsReaderTests.cs @@ -9,6 +9,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Disk.Test; +[PlatformSpecific(TestPlatforms.Linux)] public class DiskStatsReaderTests { private static readonly string[] _skipDevicePrefixes = new[] { "ram", "loop", "dm-" }; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/LinuxSystemDiskMetricsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/LinuxSystemDiskMetricsTests.cs index edb5d20bf00..82391a092c3 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/LinuxSystemDiskMetricsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Disk/LinuxSystemDiskMetricsTests.cs @@ -16,6 +16,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Disk.Test; +[PlatformSpecific(TestPlatforms.Linux)] public class LinuxSystemDiskMetricsTests { private static readonly string[] _skipDevicePrefixes = new[] { "ram", "loop", "dm-" }; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs index 4f8dbf9547f..5ea771a9ed2 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs @@ -15,6 +15,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; +[PlatformSpecific(TestPlatforms.Linux)] public class LinuxCountersTests { [Fact] diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs index b663b6f97fd..cc4c9d56fe0 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkMetricsTests.cs @@ -15,6 +15,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; +[PlatformSpecific(TestPlatforms.Linux)] public class LinuxNetworkMetricsTests { private readonly Mock _tcpStateInfoProvider = new(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs index 53b980bb93c..82edf8b758b 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs @@ -10,6 +10,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; +[PlatformSpecific(TestPlatforms.Linux)] public sealed class LinuxNetworkUtilizationParserTests { private const string VerifiedDataDirectory = "Verified"; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV1Tests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV1Tests.cs index 01da615cf13..9cff68dbc95 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV1Tests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV1Tests.cs @@ -13,9 +13,10 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; +[PlatformSpecific(TestPlatforms.Linux)] public sealed class LinuxUtilizationParserCgroupV1Tests { - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("DFIJEUWGHFWGBWEFWOMDOWKSLA")] [InlineData("")] [InlineData("________________________Asdasdasdas dd")] @@ -38,7 +39,7 @@ public void Parser_Throws_When_Data_Is_Invalid(string line) Assert.Throws(() => parser.GetCgroupRequestCpuV2()); } - [LinuxOnlyFact] + [ConditionalFact] public void Parser_Can_Read_Host_And_Cgroup_Available_Cpu_Count() { var parser = new LinuxUtilizationParserCgroupV1(new FileNamesOnlyFileSystem(TestResources.TestFilesLocation), new FakeUserHz(100)); @@ -49,7 +50,7 @@ public void Parser_Can_Read_Host_And_Cgroup_Available_Cpu_Count() Assert.Equal(1.0, cgroupCpuCount); } - [LinuxOnlyFact] + [ConditionalFact] public void Parser_Provides_Total_Available_Memory_In_Bytes() { var fs = new FileNamesOnlyFileSystem(TestResources.TestFilesLocation); @@ -60,7 +61,7 @@ public void Parser_Provides_Total_Available_Memory_In_Bytes() Assert.Equal(16_233_760UL * 1024, totalMem); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("----------------------")] [InlineData("@ @#dddada")] [InlineData("1231234124124")] @@ -91,7 +92,7 @@ public void When_Calling_GetMemoryUsageInBytes_Parser_Throws_When_MemoryStat_Doe Assert.Contains("total_inactive_file", r.Message); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("----------------------")] [InlineData("@ @#dddada")] [InlineData("_1231234124124")] @@ -117,7 +118,7 @@ public void When_Calling_GetMemoryUsageInBytes_Parser_Throws_When_UsageInBytes_D Assert.Contains("/sys/fs/cgroup/memory/memory.usage_in_bytes", r.Message); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData(10, 1)] [InlineData(23, 22)] [InlineData(100000, 10000)] @@ -136,7 +137,7 @@ public void When_Calling_GetMemoryUsageInBytes_Parser_Throws_When_Inactive_Memor Assert.Contains("lesser than", r.Message); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("Mem")] [InlineData("MemTotal:")] [InlineData("MemTotal: 120")] @@ -162,7 +163,7 @@ public void When_Calling_GetHostAvailableMemory_Parser_Throws_When_MemInfo_Does_ Assert.Contains("/proc/meminfo", r.Message); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("kB", 231, 236544)] [InlineData("MB", 287, 300_941_312)] [InlineData("GB", 372, 399_431_958_528)] @@ -181,7 +182,7 @@ public void When_Calling_GetHostAvailableMemory_Parser_Correctly_Transforms_Supp Assert.Equal(bytes, memory); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("0-11", 12)] [InlineData("0", 1)] [InlineData("1000", 1)] @@ -208,7 +209,7 @@ public void When_No_Cgroup_Cpu_Limits_Are_Not_Set_We_Get_Available_Cpus_From_Cpu Assert.Equal(result, cpus); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("-11")] [InlineData("0-")] [InlineData("d-22")] @@ -236,7 +237,7 @@ public void Parser_Throws_When_CpuSet_Has_Invalid_Content(string content) Assert.Contains("/sys/fs/cgroup/cpuset/cpuset.cpus", r.Message); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("-1", "18")] [InlineData("18", "-1")] [InlineData("18", "")] @@ -257,7 +258,7 @@ public void When_Quota_And_Period_Are_Minus_One_It_Fallbacks_To_Cpuset(string qu Assert.Contains("/sys/fs/cgroup/cpuset/cpuset.cpus", r.Message); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("dd1d", "18")] [InlineData("-18", "18")] [InlineData("\r\r\r\r\r", "18")] @@ -285,7 +286,7 @@ public void Parser_Throws_When_Cgroup_Cpu_Files_Contain_Invalid_Data(string quot Assert.Contains("/sys/fs/cgroup/cpu/cpu.cfs_", r.Message); } - [LinuxOnlyFact] + [ConditionalFact] public void ReadingCpuUsage_Does_Not_Throw_For_Valid_Input() { var f = new HardcodedValueFileSystem(new Dictionary @@ -299,7 +300,7 @@ public void ReadingCpuUsage_Does_Not_Throw_For_Valid_Input() Assert.Null(r); } - [LinuxOnlyFact] + [ConditionalFact] public void ReadingTotalMemory_Does_Not_Throw_For_Valid_Input() { var f = new HardcodedValueFileSystem(new Dictionary @@ -314,7 +315,7 @@ public void ReadingTotalMemory_Does_Not_Throw_For_Valid_Input() Assert.Null(r); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("2569530367000")] [InlineData(" 2569530 36700 245693 4860924 82283 0 4360 0dsa 0 0 asdasd @@@@")] [InlineData("asdasd 2569530 36700 245693 4860924 82283 0 4360 0 0 0")] @@ -335,7 +336,7 @@ public void ReadingCpuUsage_Does_Throws_For_Valid_Input(string content) Assert.Contains("proc/stat", r.Message); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("-1")] [InlineData("")] public void Parser_Throws_When_Cgroup_Cpu_Shares_Files_Contain_Invalid_Data(string content) @@ -352,7 +353,7 @@ public void Parser_Throws_When_Cgroup_Cpu_Shares_Files_Contain_Invalid_Data(stri Assert.Contains("/sys/fs/cgroup/cpu/cpu.shares", r.Message); } - [LinuxOnlyFact] + [ConditionalFact] public async Task ThreadSafetyAsync() { var f1 = new HardcodedValueFileSystem(new Dictionary diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV2Tests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV2Tests.cs index 3805e83e074..a73a4d4696e 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV2Tests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV2Tests.cs @@ -13,11 +13,12 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; +[PlatformSpecific(TestPlatforms.Linux)] public sealed class LinuxUtilizationParserCgroupV2Tests { private const string VerifiedDataDirectory = "Verified"; - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("DFIJEUWGHFWGBWEFWOMDOWKSLA")] [InlineData("")] [InlineData("________________________Asdasdasdas dd")] @@ -41,7 +42,7 @@ public void Throws_When_Data_Is_Invalid(string line) Assert.Throws(() => parser.GetCgroupPeriodsIntervalInMicroSecondsV2()); } - [LinuxOnlyFact] + [ConditionalFact] public void Can_Read_Host_And_Cgroup_Available_Cpu_Count() { var parser = new LinuxUtilizationParserCgroupV2(new FileNamesOnlyFileSystem(TestResources.TestFilesLocation), new FakeUserHz(100)); @@ -52,7 +53,7 @@ public void Can_Read_Host_And_Cgroup_Available_Cpu_Count() Assert.Equal(2.0, cgroupCpuCount); } - [LinuxOnlyFact] + [ConditionalFact] public void Provides_Total_Available_Memory_In_Bytes() { var fs = new FileNamesOnlyFileSystem(TestResources.TestFilesLocation); @@ -63,7 +64,7 @@ public void Provides_Total_Available_Memory_In_Bytes() Assert.Equal(16_233_760UL * 1024, totalMem); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("----------------------")] [InlineData("@ @#dddada")] [InlineData("1231234124124")] @@ -93,7 +94,7 @@ public Task Throws_When_TotalInactiveFile_Is_Invalid(string content) return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("----------------------")] [InlineData("@ @#dddada")] [InlineData("_1231234124124")] @@ -118,7 +119,7 @@ public Task Throws_When_UsageInBytes_Is_Invalid(string content) return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("max\n", 134_796_910_592ul)] [InlineData("1000000\n", 1_000_000ul)] public void Returns_Available_Memory_When_AvailableMemoryInBytes_Is_Valid(string content, ulong expectedResult) @@ -135,7 +136,7 @@ public void Returns_Available_Memory_When_AvailableMemoryInBytes_Is_Valid(string Assert.Equal(expectedResult, result); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("Suspicious12312312")] [InlineData("string@")] [InlineData("string12312")] @@ -152,7 +153,7 @@ public Task Throws_When_AvailableMemoryInBytes_Doesnt_Contain_Just_A_Number(stri return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory); } - [LinuxOnlyFact] + [ConditionalFact] public Task Throws_When_UsageInBytes_Doesnt_Contain_A_Number() { var regexPatternforSlices = @"\w+.slice"; @@ -167,7 +168,7 @@ public Task Throws_When_UsageInBytes_Doesnt_Contain_A_Number() return Verifier.Verify(r).UseDirectory(VerifiedDataDirectory); } - [LinuxOnlyFact] + [ConditionalFact] public void Returns_Memory_Usage_When_Memory_Usage_Is_Valid() { // When memory usage is a positive number @@ -193,7 +194,7 @@ public void Returns_Memory_Usage_When_Memory_Usage_Is_Valid() Assert.Equal(0, r); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData(104343, 1)] [InlineData(23423, 22)] [InlineData(10000, 100)] @@ -211,7 +212,7 @@ public Task Throws_When_Inactive_Memory_Is_Bigger_Than_Total_Memory(int inactive return Verifier.Verify(r).UseParameters(inactive, total).UseDirectory(VerifiedDataDirectory); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("Mem")] [InlineData("MemTotal:")] [InlineData("MemTotal: 120")] @@ -236,7 +237,7 @@ public Task Throws_When_MemInfo_Does_Not_Contain_TotalMemory(string totalMemory) return Verifier.Verify(r).UseParameters(totalMemory).UseDirectory(VerifiedDataDirectory); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("kB", 231, 236_544)] [InlineData("MB", 287, 300_941_312)] [InlineData("GB", 372, 399_431_958_528)] @@ -254,7 +255,7 @@ public void Transforms_Supported_Units_To_Bytes(string unit, int value, ulong by Assert.Equal(bytes, memory); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("0-11", 12)] [InlineData("0", 1)] [InlineData("1000", 1)] @@ -280,7 +281,7 @@ public void Gets_Available_Cpus_From_CpuSetCpus_When_Cpu_Limits_Not_Set(string c Assert.Equal(result, cpus); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("0::/")] [InlineData("0::/fakeslice")] public void Gets_Available_Cpus_From_CpuSetCpusFromSlices_When_Cpu_Limits_Not_Set(string slicepath) @@ -298,7 +299,7 @@ public void Gets_Available_Cpus_From_CpuSetCpusFromSlices_When_Cpu_Limits_Not_Se Assert.Equal(2, cpus); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("2500", 64.0)] [InlineData("10000", 256.0)] public void Calculates_Cpu_Request_From_Cpu_WeightInSlices(string content, float result) @@ -315,7 +316,7 @@ public void Calculates_Cpu_Request_From_Cpu_WeightInSlices(string content, float Assert.Equal(result, r); } - [LinuxOnlyFact] + [ConditionalFact] public void Gets_Available_Cpus_From_CpuSetCpus_When_Cpu_Max_Set_To_Max_() { var f = new HardcodedValueFileSystem(new Dictionary @@ -330,7 +331,7 @@ public void Gets_Available_Cpus_From_CpuSetCpus_When_Cpu_Max_Set_To_Max_() Assert.Equal(3, cpus); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("-11")] [InlineData("0-")] [InlineData("d-22")] @@ -356,7 +357,7 @@ public Task Throws_When_CpuSet_Has_Invalid_Content(string content) return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory); } - [LinuxOnlyFact] + [ConditionalFact] public Task Fallsback_To_Cpuset_When_Quota_And_Period_Are_Minus_One_() { var f = new HardcodedValueFileSystem(new Dictionary @@ -371,7 +372,7 @@ public Task Fallsback_To_Cpuset_When_Quota_And_Period_Are_Minus_One_() return Verifier.Verify(r).UseDirectory(VerifiedDataDirectory); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("dd1d", "18")] [InlineData("-18", "18")] [InlineData("\r\r\r\r\r", "18")] @@ -396,7 +397,7 @@ public Task Throws_When_Cgroup_Cpu_Files_Contain_Invalid_Data(string quota, stri return Verifier.Verify(r).UseParameters(quota, period).UseDirectory(VerifiedDataDirectory); } - [LinuxOnlyFact] + [ConditionalFact] public void Reads_CpuUsage_When_Valid_Input() { var f = new HardcodedValueFileSystem(new Dictionary @@ -410,7 +411,7 @@ public void Reads_CpuUsage_When_Valid_Input() Assert.Equal(77_994_900_000_000, r); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("0::/", "usage_usec 222222\nnr_periods 50", "222222000", "50")] [InlineData("0::/fakeslice", "usage_usec 222222\nnr_periods 75", "222222000", "75")] public void Reads_CpuUsageFromSlices_When_Valid_Input(string slicepath, string content, string expectedUsage, string expectedPeriods) @@ -431,7 +432,7 @@ public void Reads_CpuUsageFromSlices_When_Valid_Input(string slicepath, string c Assert.Equal(expectedPeriods, periods.ToString()); } - [LinuxOnlyFact] + [ConditionalFact] public void Reads_TotalMemory_When_Valid_Input() { var f = new HardcodedValueFileSystem(new Dictionary @@ -446,7 +447,7 @@ public void Reads_TotalMemory_When_Valid_Input() Assert.Null(r); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("2569530367000")] [InlineData(" 2569530 36700 245693 4860924 82283 0 4360 0dsa")] [InlineData("asdasd 2569530 36700 245693 4860924 82283 0 4360 0 0 0")] @@ -466,7 +467,7 @@ public Task Throws_When_CpuUsage_Invalid(string content) return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("usage_", 12222)] [InlineData("dasd", -1)] [InlineData("@#dddada", 342322)] @@ -483,7 +484,7 @@ public Task Throws_When_CpuAcctUsage_Has_Invalid_Content_Both_Parts(string conte return Verifier.Verify(r).UseParameters(content, value).UseDirectory(VerifiedDataDirectory); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData(-32131)] [InlineData(-1)] [InlineData(-15.323)] @@ -500,7 +501,7 @@ public Task Throws_When_Usage_Usec_Has_Negative_Value(int value) return Verifier.Verify(r).UseParameters(value).UseDirectory(VerifiedDataDirectory); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("-1")] [InlineData("dasrz3424")] [InlineData("0")] @@ -518,7 +519,7 @@ public Task Throws_When_Cgroup_Cpu_Weight_Files_Contain_Invalid_Data(string cont return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("2500", 64.0)] [InlineData("10000", 256.0)] public void Calculates_Cpu_Request_From_Cpu_Weight(string content, float result) @@ -534,7 +535,7 @@ public void Calculates_Cpu_Request_From_Cpu_Weight(string content, float result) Assert.Equal(result, r); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData("0::/", "filename", "/sys/fs/cgroup/filename")] [InlineData("0::/filesystem.slice", "filename", "/sys/fs/cgroup/filesystem.slice/filename")] [InlineData("0::/filesystem.slice/", "filename", "/sys/fs/cgroup/filesystem.slice/filename")] @@ -551,7 +552,7 @@ public void Create_Path_From_Proc_Self_Cgroup(string content, string filename, s Assert.Equal(result, r); } - [LinuxOnlyFact] + [ConditionalFact] public async Task Is_Thread_Safe_Async() { var f1 = new HardcodedValueFileSystem(new Dictionary diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs index 067f317da81..ec329d1e7f3 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs @@ -17,11 +17,12 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; +[PlatformSpecific(TestPlatforms.Linux)] public sealed class LinuxUtilizationProviderTests { private const string VerifiedDataDirectory = "Verified"; - [LinuxOnlyFact] + [ConditionalFact] [CombinatorialData] public void Provider_Registers_Instruments() { @@ -102,7 +103,7 @@ public void Provider_Registers_Instruments() Assert.Equal(0.5, samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ProcessMemoryUtilization).value); } - [LinuxOnlyFact] + [ConditionalFact] [CombinatorialData] public void Provider_Registers_Instruments_CgroupV2() { @@ -216,7 +217,7 @@ public void Provider_Creates_Meter_With_Correct_Name() Assert.Equal(ResourceUtilizationInstruments.MeterName, meter.Name); } - [LinuxOnlyFact] + [ConditionalFact] [CombinatorialData] public void Provider_Registers_Instruments_CgroupV2_WithoutHostCpu() { diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/OSFileSystemTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/OSFileSystemTests.cs index 5e7148079b3..6496d540015 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/OSFileSystemTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/OSFileSystemTests.cs @@ -10,9 +10,10 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; +[PlatformSpecific(TestPlatforms.Linux)] public sealed class OSFileSystemTests { - [LinuxOnlyFact] + [ConditionalFact] public void GetDirectoryNames_ReturnsDirectoryNames() { var fileSystem = new OSFileSystem(); @@ -22,7 +23,7 @@ public void GetDirectoryNames_ReturnsDirectoryNames() Assert.Single(directoryNames); } - [LinuxOnlyFact] + [ConditionalFact] public void Reading_First_File_Line_Works() { const string Content = "Name: cat"; @@ -35,7 +36,7 @@ public void Reading_First_File_Line_Works() Assert.Equal(Content, s); } - [LinuxOnlyFact] + [ConditionalFact] public void Reading_The_Whole_File_Works() { const string Content = "user 1399428\nsystem 1124053\n"; @@ -49,7 +50,7 @@ public void Reading_The_Whole_File_Works() Assert.Equal(Content, s); } - [LinuxOnlyTheory] + [ConditionalTheory] [InlineData(128)] [InlineData(256)] [InlineData(512)] diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringBuilderTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringBuilderTests.cs index f5a32d277aa..45e075dbef7 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringBuilderTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringBuilderTests.cs @@ -8,7 +8,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Test; -[OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Not supported on MacOs.")] +[PlatformSpecific(~TestPlatforms.OSX)] public sealed class ResourceMonitoringBuilderTests { [ConditionalFact(Skip = "Not supported on MacOs.")] @@ -32,7 +32,7 @@ public void AddPublisher_CalledOnce_AddsSinglePublisherToServiceCollection() Assert.IsAssignableFrom(publishersArray.First()); } - [ConditionalFact] + [Fact] public void AddPublisher_CalledMultipleTimes_AddsMultiplePublishersToServiceCollection() { using var provider = new ServiceCollection() diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringExtensionsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringExtensionsTests.cs index d0e1d50eb8b..4531d5513be 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringExtensionsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringExtensionsTests.cs @@ -17,8 +17,8 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Test; public sealed class ResourceMonitoringExtensionsTests { - [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Not supported on MacOs.")] - [ConditionalFact] + [PlatformSpecific(~TestPlatforms.OSX)] + [Fact] public void Throw_Null_When_Registration_Ingredients_Null() { var services = new ServiceCollection(); @@ -29,8 +29,8 @@ public void Throw_Null_When_Registration_Ingredients_Null() Assert.Throws(() => services.AddResourceMonitoring((b) => b.ConfigureMonitor((Action)null!))); } - [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Not supported on MacOs.")] - [ConditionalFact] + [PlatformSpecific(~TestPlatforms.OSX)] + [Fact] public void AddsResourceMonitoringService_ToServicesCollection() { using var provider = new ServiceCollection() @@ -50,8 +50,8 @@ public void AddsResourceMonitoringService_ToServicesCollection() Assert.IsAssignableFrom(trackerService); } - [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Not supported on MacOs.")] - [ConditionalFact] + [PlatformSpecific(~TestPlatforms.OSX)] + [Fact] public void AddsResourceMonitoringService_ToServicesCollection_NoArgs() { using var provider = new ServiceCollection() @@ -67,8 +67,8 @@ public void AddsResourceMonitoringService_ToServicesCollection_NoArgs() Assert.IsAssignableFrom(trackerService); } - [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Not supported on MacOs.")] - [ConditionalFact] + [PlatformSpecific(~TestPlatforms.OSX)] + [Fact] public void AddsResourceMonitoringService_AsHostedService() { using var provider = new ServiceCollection() @@ -89,8 +89,8 @@ public void AddsResourceMonitoringService_AsHostedService() Assert.IsAssignableFrom(trackerService); } - [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Not supported on MacOs.")] - [ConditionalFact] + [PlatformSpecific(~TestPlatforms.OSX)] + [Fact] public void ConfigureResourceUtilization_InitializeTrackerProperly() { using var host = FakeHost.CreateBuilder() @@ -116,8 +116,8 @@ public void ConfigureResourceUtilization_InitializeTrackerProperly() Assert.NotNull(publisher); } - [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Not supported on MacOs.")] - [ConditionalFact] + [PlatformSpecific(~TestPlatforms.OSX)] + [Fact] public void ConfigureMonitor_GivenOptionsDelegate_InitializeTrackerWithOptionsProperly() { const int SamplingWindowValue = 3; @@ -145,8 +145,8 @@ public void ConfigureMonitor_GivenOptionsDelegate_InitializeTrackerWithOptionsPr Assert.Equal(TimeSpan.FromSeconds(CalculationPeriodValue), options!.Value.PublishingWindow); } - [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Not supported on MacOs.")] - [ConditionalFact] + [PlatformSpecific(~TestPlatforms.OSX)] + [Fact] public void ConfigureMonitor_GivenIConfigurationSection_InitializeTrackerWithOptionsProperly() { const int SamplingWindowValue = 3; @@ -187,8 +187,8 @@ public void ConfigureMonitor_GivenIConfigurationSection_InitializeTrackerWithOpt Assert.Equal(TimeSpan.FromSeconds(CalculationPeriod), options!.Value.PublishingWindow); } - [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Not supported on MacOs.")] - [ConditionalFact] + [PlatformSpecific(~TestPlatforms.OSX)] + [Fact] public void Registering_Resource_Utilization_Adds_Only_One_Object_Of_Type_ResourceUtilizationService_To_DI_Container() { using var host = FakeHost.CreateBuilder() @@ -211,8 +211,8 @@ public void Registering_Resource_Utilization_Adds_Only_One_Object_Of_Type_Resour Assert.Same(tracker as ResourceMonitorService, background as ResourceMonitorService); } - [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.Windows, SkipReason = "For MacOs only.")] - [ConditionalFact] + [PlatformSpecific(TestPlatforms.OSX)] + [Fact] public void AddResourceMonitoringInternal_WhenMacOs_ReturnsSameServiceCollection() { var services = new ServiceCollection(); @@ -225,8 +225,8 @@ public void AddResourceMonitoringInternal_WhenMacOs_ReturnsSameServiceCollection Assert.DoesNotContain(services, s => s.ServiceType == typeof(ISnapshotProvider)); } - [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Not supported on MacOs.")] - [ConditionalFact] + [PlatformSpecific(~TestPlatforms.OSX)] + [Fact] public void AddResourceMonitoring_AddsISnapshotProvider() { var services = new ServiceCollection(); @@ -239,8 +239,8 @@ public void AddResourceMonitoring_AddsISnapshotProvider() Assert.Contains(services, s => s.ServiceType == typeof(ISnapshotProvider)); } - [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Not supported on MacOs.")] - [ConditionalFact] + [PlatformSpecific(~TestPlatforms.OSX)] + [Fact] public void AddResourceMonitoringInternal_CallsConfigureDelegate() { var services = new ServiceCollection(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoRatePerfCounterTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoRatePerfCounterTests.cs index 4c7ea18c51e..e94c62f2212 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoRatePerfCounterTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoRatePerfCounterTests.cs @@ -11,11 +11,12 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Disk.Test; [SupportedOSPlatform("windows")] +[PlatformSpecific(TestPlatforms.Windows)] public class WindowsDiskIoRatePerfCounterTests { private const string CategoryName = "LogicalDisk"; - [WindowsOnlyFact] + [ConditionalFact] public void DiskReadsPerfCounter_Per60Seconds() { const string CounterName = WindowsDiskPerfCounterNames.DiskReadsCounter; @@ -62,7 +63,7 @@ public void DiskReadsPerfCounter_Per60Seconds() Assert.Equal(660, ratePerfCounters.TotalCountDict["D:"]); // 450 + 3.5 * 60 = 660 } - [WindowsOnlyFact] + [ConditionalFact] public void DiskWriteBytesPerfCounter_Per30Seconds() { const string CounterName = WindowsDiskPerfCounterNames.DiskWriteBytesCounter; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoTimePerfCounterTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoTimePerfCounterTests.cs index 519b884b44f..1fabe910afa 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoTimePerfCounterTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoTimePerfCounterTests.cs @@ -11,11 +11,12 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Disk.Test; [SupportedOSPlatform("windows")] +[PlatformSpecific(TestPlatforms.Windows)] public class WindowsDiskIoTimePerfCounterTests { private const string CategoryName = "LogicalDisk"; - [WindowsOnlyFact] + [ConditionalFact] public void DiskReadsPerfCounter_Per60Seconds() { const string CounterName = WindowsDiskPerfCounterNames.DiskReadsCounter; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskMetricsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskMetricsTests.cs index 6ab504d39a8..aaced13eb33 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskMetricsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskMetricsTests.cs @@ -18,12 +18,13 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Disk.Test; [SupportedOSPlatform("windows")] +[PlatformSpecific(TestPlatforms.Windows)] public class WindowsDiskMetricsTests { private const string CategoryName = "LogicalDisk"; private readonly FakeLogger _fakeLogger = new(); - [WindowsOnlyFact] + [ConditionalFact] public void Creates_Meter_With_Correct_Name() { using var meterFactory = new TestMeterFactory(); @@ -41,7 +42,7 @@ public void Creates_Meter_With_Correct_Name() Assert.Equal(ResourceUtilizationInstruments.MeterName, meter.Name); } - [WindowsOnlyFact] + [ConditionalFact] public void DiskOperationMetricsTest() { using var meterFactory = new TestMeterFactory(); @@ -115,7 +116,7 @@ public void DiskOperationMetricsTest() Assert.Equal(5700, measurements.Last(x => x.MatchesTags(writeTag, deviceTagD)).Value); // 3600 + 35 * 60 = 5700 } - [WindowsOnlyFact] + [ConditionalFact] public void DiskIoBytesMetricsTest() { using var meterFactory = new TestMeterFactory(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/FakePerformanceCounter.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/FakePerformanceCounter.cs index 967399a86cc..a738738cb00 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/FakePerformanceCounter.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/FakePerformanceCounter.cs @@ -2,9 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; +[PlatformSpecific(TestPlatforms.Windows)] public class FakePerformanceCounter(string instanceName, float[] values) : IPerformanceCounter { #pragma warning disable S3604 // Member initializer values should not be redundant diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/MemoryInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/MemoryInfoTests.cs index 4454a957f9c..68746c983b4 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/MemoryInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/MemoryInfoTests.cs @@ -11,9 +11,10 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; /// /// These tests are added for coverage reasons, but the code doesn't have /// the necessary environment predictability to really test it. +[PlatformSpecific(TestPlatforms.Windows)] public sealed class MemoryInfoTests { - [WindowsOnlyFact] + [ConditionalFact] public void GetGlobalMemory() { var memoryStatus = new MemoryInfo().GetMemoryStatus(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterFactoryTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterFactoryTests.cs index 9a0f84678d8..2c67760ae7e 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterFactoryTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterFactoryTests.cs @@ -7,9 +7,10 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; [SupportedOSPlatform("windows")] +[PlatformSpecific(TestPlatforms.Windows)] public class PerformanceCounterFactoryTests { - [WindowsOnlyFact] + [ConditionalFact] public void GetInstanceNameTest() { var performanceCounterFactory = new PerformanceCounterFactory(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterWrapperTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterWrapperTests.cs index 4022a62e600..1a8bb3a9e14 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterWrapperTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterWrapperTests.cs @@ -7,9 +7,10 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; [SupportedOSPlatform("windows")] +[PlatformSpecific(TestPlatforms.Windows)] public class PerformanceCounterWrapperTests { - [WindowsOnlyFact] + [ConditionalFact] public void GetInstanceNameTest() { var wrapper = new PerformanceCounterWrapper("Processor", "% Processor Time", "_Total"); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/SystemInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/SystemInfoTests.cs index 2051060d8f0..8d2e1dfb7a7 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/SystemInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/SystemInfoTests.cs @@ -11,12 +11,13 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; /// /// These tests are added for coverage reasons, but the code doesn't have /// the necessary environment predictability to really test it. +[PlatformSpecific(TestPlatforms.Windows)] public sealed class SystemInfoTests { /// /// Get basic system info. /// - [WindowsOnlyFact] + [ConditionalFact] public void GetSystemInfo() { var sysInfo = new SystemInfo().GetSystemInfo(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs index 812835780b1..673c332c651 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs @@ -14,6 +14,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; /// Keep this Test to distinguish different tests for IPv6. /// [Collection("Tcp Connection Tests")] +[PlatformSpecific(TestPlatforms.Windows)] public sealed class Tcp6TableInfoTests { public static readonly TimeSpan DefaultTimeSpan = TimeSpan.FromSeconds(5); @@ -224,7 +225,7 @@ public static uint FakeGetTcp6TableWithFakeInformation(IntPtr pTcp6Table, ref ui return (uint)NTSTATUS.Success; } - [WindowsOnlyFact] + [ConditionalFact] public void Test_Tcp6TableInfo_Get_UnsuccessfulStatus_All_The_Time() { var options = new ResourceMonitoringOptions @@ -241,7 +242,7 @@ public void Test_Tcp6TableInfo_Get_UnsuccessfulStatus_All_The_Time() }); } - [WindowsOnlyFact] + [ConditionalFact] public void Test_Tcp6TableInfo_Get_InsufficientBuffer_Then_Get_InvalidParameter() { var options = new ResourceMonitoringOptions @@ -257,7 +258,7 @@ public void Test_Tcp6TableInfo_Get_InsufficientBuffer_Then_Get_InvalidParameter( }); } - [WindowsOnlyFact] + [ConditionalFact] public void Test_Tcp6TableInfo_Get_Correct_Information() { StartTimestamp = DateTimeOffset.UtcNow; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs index 6dc8aedf7e7..8847d3ce6a3 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs @@ -11,6 +11,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; [Collection("Tcp Connection Tests")] +[PlatformSpecific(TestPlatforms.Windows)] public sealed class TcpTableInfoTests { public static readonly TimeSpan DefaultTimeSpan = TimeSpan.FromSeconds(5); @@ -167,7 +168,7 @@ public static unsafe uint FakeGetTcpTableWithFakeInformation(IntPtr pTcpTable, r return (uint)NTSTATUS.Success; } - [WindowsOnlyFact] + [ConditionalFact] public void Test_TcpTableInfo_Get_UnsuccessfulStatus_All_The_Time() { var options = new ResourceMonitoringOptions @@ -183,7 +184,7 @@ public void Test_TcpTableInfo_Get_UnsuccessfulStatus_All_The_Time() }); } - [WindowsOnlyFact] + [ConditionalFact] public void Test_TcpTableInfo_Get_InsufficientBuffer_Then_Get_InvalidParameter() { var options = new ResourceMonitoringOptions @@ -199,7 +200,7 @@ public void Test_TcpTableInfo_Get_InsufficientBuffer_Then_Get_InvalidParameter() }); } - [WindowsOnlyFact] + [ConditionalFact] public void Test_TcpTableInfo_Get_Correct_Information() { StartTimestamp = DateTimeOffset.UtcNow; @@ -260,7 +261,7 @@ public void Test_TcpTableInfo_Get_Correct_Information() Assert.Equal(2, tcpStateInfo.DeleteTcbCount); } - [WindowsOnlyFact] + [ConditionalFact] public void Test_TcpTableInfo_CalculateCount_default_branch() { TcpStateInfo tcpStateInfo = new(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsContainerSnapshotProviderTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsContainerSnapshotProviderTests.cs index 63a142fe254..c43443d128c 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsContainerSnapshotProviderTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsContainerSnapshotProviderTests.cs @@ -18,6 +18,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; +[PlatformSpecific(TestPlatforms.Windows)] public sealed class WindowsContainerSnapshotProviderTests { private const string VerifiedDataDirectory = "Verified"; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsNetworkMetricsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsNetworkMetricsTests.cs index 2dd152a4f69..5cf2f7e7f28 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsNetworkMetricsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsNetworkMetricsTests.cs @@ -11,9 +11,10 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; +[PlatformSpecific(TestPlatforms.Windows)] public class WindowsNetworkMetricsTests { - [WindowsOnlyFact] + [ConditionalFact] public void Creates_Meter_With_Correct_Name() { using var meterFactory = new TestMeterFactory(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsSnapshotProviderTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsSnapshotProviderTests.cs index 18582d77c04..9251b2210a2 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsSnapshotProviderTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsSnapshotProviderTests.cs @@ -18,6 +18,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; +[PlatformSpecific(TestPlatforms.Windows)] public sealed class WindowsSnapshotProviderTests { private const string VerifiedDataDirectory = "Verified"; @@ -37,7 +38,7 @@ public WindowsSnapshotProviderTests() _fakeLogger = new FakeLogger(); } - [WindowsOnlyFact] + [ConditionalFact] public void BasicConstructor() { var provider = new WindowsSnapshotProvider(_fakeLogger, _meterFactoryMock.Object, _options); @@ -49,7 +50,7 @@ public void BasicConstructor() Assert.Equal(memoryStatus.TotalPhys, provider.Resources.MaximumMemoryInBytes); } - [WindowsOnlyFact] + [ConditionalFact] public void GetSnapshot_DoesNotThrowExceptions() { var provider = new WindowsSnapshotProvider(_fakeLogger, _meterFactoryMock.Object, _options); @@ -58,7 +59,7 @@ public void GetSnapshot_DoesNotThrowExceptions() Assert.Null(exception); } - [WindowsOnlyFact] + [ConditionalFact] public Task SnapshotProvider_EmitsLogRecord() { var provider = new WindowsSnapshotProvider(_fakeLogger, _meterFactoryMock.Object, _options); @@ -69,7 +70,7 @@ public Task SnapshotProvider_EmitsLogRecord() return Verifier.Verify(logRecords[0]).UseDirectory(VerifiedDataDirectory); } - [WindowsOnlyTheory] + [ConditionalTheory] [CombinatorialData] public void SnapshotProvider_EmitsCpuMetrics(bool useZeroToOneRange) { @@ -110,7 +111,7 @@ public void SnapshotProvider_EmitsCpuMetrics(bool useZeroToOneRange) Assert.Equal(0.05 * multiplier, metricCollector.LastMeasurement?.Value); // Still consuming 5% of the CPU } - [WindowsOnlyTheory] + [ConditionalTheory] [CombinatorialData] public void SnapshotProvider_EmitsMemoryMetrics(bool useZeroToOneRange) { @@ -160,7 +161,7 @@ public void SnapshotProvider_EmitsMemoryMetrics(bool useZeroToOneRange) Assert.Equal(1 * multiplier, Math.Round(metricCollector.LastMeasurement.Value)); // Consuming 100% of the memory } - [WindowsOnlyFact] + [ConditionalFact] public void Provider_Returns_MemoryConsumption() { // This is a synthetic test to have full test coverage: @@ -168,7 +169,7 @@ public void Provider_Returns_MemoryConsumption() Assert.InRange(usage, 0, long.MaxValue); } - [WindowsOnlyFact] + [ConditionalFact] public void Provider_Creates_Meter_With_Correct_Name() { using var meterFactory = new TestMeterFactory(); diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs index 2aed676a616..6216a9e56af 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/AIChatWebExecutionTests.cs @@ -95,7 +95,7 @@ public async Task CreateRestoreAndBuild_AspireProjectName() /// environment variable AI_TEMPLATES_TEST_PROJECT_NAMES to "true" or "1" /// to enable it. /// - [ConditionalTheory(nameof(TestConditions.IsAITemplatesTestProjectNamesSet))] + [ConditionalTheory(typeof(TestConditions), nameof(TestConditions.IsAITemplatesTestProjectNamesSet))] [MemberData(nameof(GetAspireProjectNameVariants))] public async Task CreateRestoreAndBuild_AspireProjectName_Variants(string provider, string projectName) { diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj index bf58c4de76e..d0a89a7a003 100644 --- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj +++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Microsoft.Extensions.AI.Templates.Tests.csproj @@ -11,7 +11,6 @@ - From 9b511780a404190abb763decafec5ac96c4c197c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:03:38 +0000 Subject: [PATCH 07/16] Fix build scripts and remove duplicate platform attributes - Remove test/TestUtilities/TestUtilities.csproj references from Slngen.ps1 and Slngen.Tests.ps1 - Remove duplicate method-level [PlatformSpecific] attributes when class already has the attribute - Replace [ConditionalFact] with [Fact] in platform-specific test classes - Replace [ConditionalTheory] with [Theory] in platform-specific test classes - Convert [ConditionalFact] to [Theory] when test has data attributes (InlineData, CombinatorialData, etc.) Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- scripts/Slngen.Tests.ps1 | 4 +- scripts/Slngen.ps1 | 4 -- .../Linux/AcceptanceTest.cs | 24 +++----- .../LinuxUtilizationParserCgroupV1Tests.cs | 34 +++++------ .../LinuxUtilizationParserCgroupV2Tests.cs | 58 +++++++++---------- .../Linux/LinuxUtilizationProviderTests.cs | 6 +- .../Linux/OSFileSystemTests.cs | 8 +-- .../Disk/WindowsDiskIoRatePerfCounterTests.cs | 4 +- .../Disk/WindowsDiskIoTimePerfCounterTests.cs | 2 +- .../Windows/Disk/WindowsDiskMetricsTests.cs | 6 +- .../Windows/MemoryInfoTests.cs | 2 +- .../Windows/PerformanceCounterFactoryTests.cs | 2 +- .../Windows/PerformanceCounterWrapperTests.cs | 2 +- .../Windows/SystemInfoTests.cs | 2 +- .../Windows/Tcp6TableInfoTests.cs | 6 +- .../Windows/TcpTableInfoTests.cs | 8 +-- .../Windows/WindowsNetworkMetricsTests.cs | 2 +- .../Windows/WindowsSnapshotProviderTests.cs | 14 ++--- 18 files changed, 88 insertions(+), 100 deletions(-) diff --git a/scripts/Slngen.Tests.ps1 b/scripts/Slngen.Tests.ps1 index 156ade6154e..9c38eca5040 100644 --- a/scripts/Slngen.Tests.ps1 +++ b/scripts/Slngen.Tests.ps1 @@ -19,8 +19,8 @@ Describe "Slngen.ps1" { $DefaultExcludePath = "--exclude src\Tools\MutationTesting\samples\ --exclude src\Templates\templates" $DefaultSlnPath = '"' + (Join-Path -Path (Get-Location) -ChildPath "SDK.sln") + '"' - $PollyKeywordGlobs = 'test/TestUtilities/TestUtilities.csproj src/**/*Polly*/**/*.*sproj test/**/*Polly*/**/*.*sproj bench/**/*Polly*/**/*.*sproj int_test/**/*Polly*/**/*.*sproj docs/**/*Polly*/**/*.*sproj' - $PollyHttpKeywordsGlobs = 'test/TestUtilities/TestUtilities.csproj src/**/*Polly*/**/*.*sproj test/**/*Polly*/**/*.*sproj bench/**/*Polly*/**/*.*sproj int_test/**/*Polly*/**/*.*sproj docs/**/*Polly*/**/*.*sproj src/**/*Http*/**/*.*sproj test/**/*Http*/**/*.*sproj bench/**/*Http*/**/*.*sproj int_test/**/*Http*/**/*.*sproj docs/**/*Http*/**/*.*sproj' + $PollyKeywordGlobs = 'src/**/*Polly*/**/*.*sproj test/**/*Polly*/**/*.*sproj bench/**/*Polly*/**/*.*sproj int_test/**/*Polly*/**/*.*sproj docs/**/*Polly*/**/*.*sproj' + $PollyHttpKeywordsGlobs = 'src/**/*Polly*/**/*.*sproj test/**/*Polly*/**/*.*sproj bench/**/*Polly*/**/*.*sproj int_test/**/*Polly*/**/*.*sproj docs/**/*Polly*/**/*.*sproj src/**/*Http*/**/*.*sproj test/**/*Http*/**/*.*sproj bench/**/*Http*/**/*.*sproj int_test/**/*Http*/**/*.*sproj docs/**/*Http*/**/*.*sproj' } Context "Invoke-SlngenExe with test cases from examples" { diff --git a/scripts/Slngen.ps1 b/scripts/Slngen.ps1 index 1980f458e94..5924fcb03ea 100755 --- a/scripts/Slngen.ps1 +++ b/scripts/Slngen.ps1 @@ -169,10 +169,6 @@ Push-Location $RepositoryPath try { [System.Collections.ArrayList]$Globs = @() - if (!$OnlySources) { - $Globs += "test/TestUtilities/TestUtilities.csproj" - } - if (!$All) { foreach ($Keyword in $Keywords) { $Globs += "src/**/*$($Keyword)*/**/*.*sproj" diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs index 4f3446dd5e0..f3f190b6362 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs @@ -26,8 +26,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; [PlatformSpecific(TestPlatforms.Linux)] public sealed class AcceptanceTest { - [ConditionalFact] - [PlatformSpecific(TestPlatforms.Linux)] + [Fact] public void Adding_Linux_Resource_Utilization_Allows_To_Query_Snapshot_Provider() { using var services = new ServiceCollection() @@ -40,8 +39,7 @@ public void Adding_Linux_Resource_Utilization_Allows_To_Query_Snapshot_Provider( Assert.NotEqual(default, provider.GetSnapshot()); } - [ConditionalFact] - [PlatformSpecific(TestPlatforms.Linux)] + [Fact] [SuppressMessage("Minor Code Smell", "S3257:Declarations and initializations should be as concise as possible", Justification = "Broken analyzer.")] public void Adding_Linux_Resource_Utilization_Can_Be_Configured_With_Section() { @@ -70,8 +68,7 @@ public void Adding_Linux_Resource_Utilization_Can_Be_Configured_With_Section() Assert.Equal(memoryRefresh, options.Value.MemoryConsumptionRefreshInterval); } - [ConditionalFact] - [PlatformSpecific(TestPlatforms.Linux)] + [Fact] public void Adding_Linux_Resource_Utilization_Can_Be_Configured_With_Action() { var cpuRefresh = TimeSpan.FromMinutes(13); @@ -92,8 +89,7 @@ public void Adding_Linux_Resource_Utilization_Can_Be_Configured_With_Action() Assert.Equal(memoryRefresh, options.Value.MemoryConsumptionRefreshInterval); } - [ConditionalFact] - [PlatformSpecific(TestPlatforms.Linux)] + [Fact] [SuppressMessage("Minor Code Smell", "S3257:Declarations and initializations should be as concise as possible", Justification = "Broken analyzer.")] public void Adding_Linux_Resource_Utilization_With_Section_Registers_SnapshotProvider_Cgroupv1() { @@ -141,8 +137,7 @@ public void Adding_Linux_Resource_Utilization_With_Section_Registers_SnapshotPro Assert.Equal(100_000UL, provider.Resources.MaximumMemoryInBytes); } - [ConditionalFact] - [PlatformSpecific(TestPlatforms.Linux)] + [Fact] [SuppressMessage("Minor Code Smell", "S3257:Declarations and initializations should be as concise as possible", Justification = "Broken analyzer.")] public void Adding_Linux_Resource_Utilization_With_Section_Registers_SnapshotProvider_Cgroupv2() { @@ -189,9 +184,8 @@ public void Adding_Linux_Resource_Utilization_With_Section_Registers_SnapshotPro Assert.Equal(100_000UL, provider.Resources.MaximumMemoryInBytes); } - [ConditionalFact] + [Theory] [CombinatorialData] - [PlatformSpecific(TestPlatforms.Linux)] public Task ResourceUtilizationTracker_And_Metrics_Report_Same_Values_With_Cgroupsv1() { var cpuRefresh = TimeSpan.FromMinutes(13); @@ -287,9 +281,8 @@ public Task ResourceUtilizationTracker_And_Metrics_Report_Same_Values_With_Cgrou return Task.CompletedTask; } - [ConditionalFact] + [Theory] [CombinatorialData] - [PlatformSpecific(TestPlatforms.Linux)] public Task ResourceUtilizationTracker_And_Metrics_Report_Same_Values_With_Cgroupsv2() { var cpuRefresh = TimeSpan.FromMinutes(13); @@ -395,9 +388,8 @@ public Task ResourceUtilizationTracker_And_Metrics_Report_Same_Values_With_Cgrou return Task.CompletedTask; } - [ConditionalFact] + [Theory] [CombinatorialData] - [PlatformSpecific(TestPlatforms.Linux)] public Task ResourceUtilizationTracker_And_Metrics_Report_Same_Values_With_Cgroupsv2_Using_LinuxCalculationV2() { var fileSystem = new HardcodedValueFileSystem(new Dictionary diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV1Tests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV1Tests.cs index 9cff68dbc95..7323ad72d1f 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV1Tests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV1Tests.cs @@ -16,7 +16,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; [PlatformSpecific(TestPlatforms.Linux)] public sealed class LinuxUtilizationParserCgroupV1Tests { - [ConditionalTheory] + [Theory] [InlineData("DFIJEUWGHFWGBWEFWOMDOWKSLA")] [InlineData("")] [InlineData("________________________Asdasdasdas dd")] @@ -39,7 +39,7 @@ public void Parser_Throws_When_Data_Is_Invalid(string line) Assert.Throws(() => parser.GetCgroupRequestCpuV2()); } - [ConditionalFact] + [Fact] public void Parser_Can_Read_Host_And_Cgroup_Available_Cpu_Count() { var parser = new LinuxUtilizationParserCgroupV1(new FileNamesOnlyFileSystem(TestResources.TestFilesLocation), new FakeUserHz(100)); @@ -50,7 +50,7 @@ public void Parser_Can_Read_Host_And_Cgroup_Available_Cpu_Count() Assert.Equal(1.0, cgroupCpuCount); } - [ConditionalFact] + [Fact] public void Parser_Provides_Total_Available_Memory_In_Bytes() { var fs = new FileNamesOnlyFileSystem(TestResources.TestFilesLocation); @@ -61,7 +61,7 @@ public void Parser_Provides_Total_Available_Memory_In_Bytes() Assert.Equal(16_233_760UL * 1024, totalMem); } - [ConditionalTheory] + [Theory] [InlineData("----------------------")] [InlineData("@ @#dddada")] [InlineData("1231234124124")] @@ -92,7 +92,7 @@ public void When_Calling_GetMemoryUsageInBytes_Parser_Throws_When_MemoryStat_Doe Assert.Contains("total_inactive_file", r.Message); } - [ConditionalTheory] + [Theory] [InlineData("----------------------")] [InlineData("@ @#dddada")] [InlineData("_1231234124124")] @@ -118,7 +118,7 @@ public void When_Calling_GetMemoryUsageInBytes_Parser_Throws_When_UsageInBytes_D Assert.Contains("/sys/fs/cgroup/memory/memory.usage_in_bytes", r.Message); } - [ConditionalTheory] + [Theory] [InlineData(10, 1)] [InlineData(23, 22)] [InlineData(100000, 10000)] @@ -137,7 +137,7 @@ public void When_Calling_GetMemoryUsageInBytes_Parser_Throws_When_Inactive_Memor Assert.Contains("lesser than", r.Message); } - [ConditionalTheory] + [Theory] [InlineData("Mem")] [InlineData("MemTotal:")] [InlineData("MemTotal: 120")] @@ -163,7 +163,7 @@ public void When_Calling_GetHostAvailableMemory_Parser_Throws_When_MemInfo_Does_ Assert.Contains("/proc/meminfo", r.Message); } - [ConditionalTheory] + [Theory] [InlineData("kB", 231, 236544)] [InlineData("MB", 287, 300_941_312)] [InlineData("GB", 372, 399_431_958_528)] @@ -182,7 +182,7 @@ public void When_Calling_GetHostAvailableMemory_Parser_Correctly_Transforms_Supp Assert.Equal(bytes, memory); } - [ConditionalTheory] + [Theory] [InlineData("0-11", 12)] [InlineData("0", 1)] [InlineData("1000", 1)] @@ -209,7 +209,7 @@ public void When_No_Cgroup_Cpu_Limits_Are_Not_Set_We_Get_Available_Cpus_From_Cpu Assert.Equal(result, cpus); } - [ConditionalTheory] + [Theory] [InlineData("-11")] [InlineData("0-")] [InlineData("d-22")] @@ -237,7 +237,7 @@ public void Parser_Throws_When_CpuSet_Has_Invalid_Content(string content) Assert.Contains("/sys/fs/cgroup/cpuset/cpuset.cpus", r.Message); } - [ConditionalTheory] + [Theory] [InlineData("-1", "18")] [InlineData("18", "-1")] [InlineData("18", "")] @@ -258,7 +258,7 @@ public void When_Quota_And_Period_Are_Minus_One_It_Fallbacks_To_Cpuset(string qu Assert.Contains("/sys/fs/cgroup/cpuset/cpuset.cpus", r.Message); } - [ConditionalTheory] + [Theory] [InlineData("dd1d", "18")] [InlineData("-18", "18")] [InlineData("\r\r\r\r\r", "18")] @@ -286,7 +286,7 @@ public void Parser_Throws_When_Cgroup_Cpu_Files_Contain_Invalid_Data(string quot Assert.Contains("/sys/fs/cgroup/cpu/cpu.cfs_", r.Message); } - [ConditionalFact] + [Fact] public void ReadingCpuUsage_Does_Not_Throw_For_Valid_Input() { var f = new HardcodedValueFileSystem(new Dictionary @@ -300,7 +300,7 @@ public void ReadingCpuUsage_Does_Not_Throw_For_Valid_Input() Assert.Null(r); } - [ConditionalFact] + [Fact] public void ReadingTotalMemory_Does_Not_Throw_For_Valid_Input() { var f = new HardcodedValueFileSystem(new Dictionary @@ -315,7 +315,7 @@ public void ReadingTotalMemory_Does_Not_Throw_For_Valid_Input() Assert.Null(r); } - [ConditionalTheory] + [Theory] [InlineData("2569530367000")] [InlineData(" 2569530 36700 245693 4860924 82283 0 4360 0dsa 0 0 asdasd @@@@")] [InlineData("asdasd 2569530 36700 245693 4860924 82283 0 4360 0 0 0")] @@ -336,7 +336,7 @@ public void ReadingCpuUsage_Does_Throws_For_Valid_Input(string content) Assert.Contains("proc/stat", r.Message); } - [ConditionalTheory] + [Theory] [InlineData("-1")] [InlineData("")] public void Parser_Throws_When_Cgroup_Cpu_Shares_Files_Contain_Invalid_Data(string content) @@ -353,7 +353,7 @@ public void Parser_Throws_When_Cgroup_Cpu_Shares_Files_Contain_Invalid_Data(stri Assert.Contains("/sys/fs/cgroup/cpu/cpu.shares", r.Message); } - [ConditionalFact] + [Fact] public async Task ThreadSafetyAsync() { var f1 = new HardcodedValueFileSystem(new Dictionary diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV2Tests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV2Tests.cs index a73a4d4696e..6bc3e10fc85 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV2Tests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationParserCgroupV2Tests.cs @@ -18,7 +18,7 @@ public sealed class LinuxUtilizationParserCgroupV2Tests { private const string VerifiedDataDirectory = "Verified"; - [ConditionalTheory] + [Theory] [InlineData("DFIJEUWGHFWGBWEFWOMDOWKSLA")] [InlineData("")] [InlineData("________________________Asdasdasdas dd")] @@ -42,7 +42,7 @@ public void Throws_When_Data_Is_Invalid(string line) Assert.Throws(() => parser.GetCgroupPeriodsIntervalInMicroSecondsV2()); } - [ConditionalFact] + [Fact] public void Can_Read_Host_And_Cgroup_Available_Cpu_Count() { var parser = new LinuxUtilizationParserCgroupV2(new FileNamesOnlyFileSystem(TestResources.TestFilesLocation), new FakeUserHz(100)); @@ -53,7 +53,7 @@ public void Can_Read_Host_And_Cgroup_Available_Cpu_Count() Assert.Equal(2.0, cgroupCpuCount); } - [ConditionalFact] + [Fact] public void Provides_Total_Available_Memory_In_Bytes() { var fs = new FileNamesOnlyFileSystem(TestResources.TestFilesLocation); @@ -64,7 +64,7 @@ public void Provides_Total_Available_Memory_In_Bytes() Assert.Equal(16_233_760UL * 1024, totalMem); } - [ConditionalTheory] + [Theory] [InlineData("----------------------")] [InlineData("@ @#dddada")] [InlineData("1231234124124")] @@ -94,7 +94,7 @@ public Task Throws_When_TotalInactiveFile_Is_Invalid(string content) return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [Theory] [InlineData("----------------------")] [InlineData("@ @#dddada")] [InlineData("_1231234124124")] @@ -119,7 +119,7 @@ public Task Throws_When_UsageInBytes_Is_Invalid(string content) return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [Theory] [InlineData("max\n", 134_796_910_592ul)] [InlineData("1000000\n", 1_000_000ul)] public void Returns_Available_Memory_When_AvailableMemoryInBytes_Is_Valid(string content, ulong expectedResult) @@ -136,7 +136,7 @@ public void Returns_Available_Memory_When_AvailableMemoryInBytes_Is_Valid(string Assert.Equal(expectedResult, result); } - [ConditionalTheory] + [Theory] [InlineData("Suspicious12312312")] [InlineData("string@")] [InlineData("string12312")] @@ -153,7 +153,7 @@ public Task Throws_When_AvailableMemoryInBytes_Doesnt_Contain_Just_A_Number(stri return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory); } - [ConditionalFact] + [Fact] public Task Throws_When_UsageInBytes_Doesnt_Contain_A_Number() { var regexPatternforSlices = @"\w+.slice"; @@ -168,7 +168,7 @@ public Task Throws_When_UsageInBytes_Doesnt_Contain_A_Number() return Verifier.Verify(r).UseDirectory(VerifiedDataDirectory); } - [ConditionalFact] + [Fact] public void Returns_Memory_Usage_When_Memory_Usage_Is_Valid() { // When memory usage is a positive number @@ -194,7 +194,7 @@ public void Returns_Memory_Usage_When_Memory_Usage_Is_Valid() Assert.Equal(0, r); } - [ConditionalTheory] + [Theory] [InlineData(104343, 1)] [InlineData(23423, 22)] [InlineData(10000, 100)] @@ -212,7 +212,7 @@ public Task Throws_When_Inactive_Memory_Is_Bigger_Than_Total_Memory(int inactive return Verifier.Verify(r).UseParameters(inactive, total).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [Theory] [InlineData("Mem")] [InlineData("MemTotal:")] [InlineData("MemTotal: 120")] @@ -237,7 +237,7 @@ public Task Throws_When_MemInfo_Does_Not_Contain_TotalMemory(string totalMemory) return Verifier.Verify(r).UseParameters(totalMemory).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [Theory] [InlineData("kB", 231, 236_544)] [InlineData("MB", 287, 300_941_312)] [InlineData("GB", 372, 399_431_958_528)] @@ -255,7 +255,7 @@ public void Transforms_Supported_Units_To_Bytes(string unit, int value, ulong by Assert.Equal(bytes, memory); } - [ConditionalTheory] + [Theory] [InlineData("0-11", 12)] [InlineData("0", 1)] [InlineData("1000", 1)] @@ -281,7 +281,7 @@ public void Gets_Available_Cpus_From_CpuSetCpus_When_Cpu_Limits_Not_Set(string c Assert.Equal(result, cpus); } - [ConditionalTheory] + [Theory] [InlineData("0::/")] [InlineData("0::/fakeslice")] public void Gets_Available_Cpus_From_CpuSetCpusFromSlices_When_Cpu_Limits_Not_Set(string slicepath) @@ -299,7 +299,7 @@ public void Gets_Available_Cpus_From_CpuSetCpusFromSlices_When_Cpu_Limits_Not_Se Assert.Equal(2, cpus); } - [ConditionalTheory] + [Theory] [InlineData("2500", 64.0)] [InlineData("10000", 256.0)] public void Calculates_Cpu_Request_From_Cpu_WeightInSlices(string content, float result) @@ -316,7 +316,7 @@ public void Calculates_Cpu_Request_From_Cpu_WeightInSlices(string content, float Assert.Equal(result, r); } - [ConditionalFact] + [Fact] public void Gets_Available_Cpus_From_CpuSetCpus_When_Cpu_Max_Set_To_Max_() { var f = new HardcodedValueFileSystem(new Dictionary @@ -331,7 +331,7 @@ public void Gets_Available_Cpus_From_CpuSetCpus_When_Cpu_Max_Set_To_Max_() Assert.Equal(3, cpus); } - [ConditionalTheory] + [Theory] [InlineData("-11")] [InlineData("0-")] [InlineData("d-22")] @@ -357,7 +357,7 @@ public Task Throws_When_CpuSet_Has_Invalid_Content(string content) return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory); } - [ConditionalFact] + [Fact] public Task Fallsback_To_Cpuset_When_Quota_And_Period_Are_Minus_One_() { var f = new HardcodedValueFileSystem(new Dictionary @@ -372,7 +372,7 @@ public Task Fallsback_To_Cpuset_When_Quota_And_Period_Are_Minus_One_() return Verifier.Verify(r).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [Theory] [InlineData("dd1d", "18")] [InlineData("-18", "18")] [InlineData("\r\r\r\r\r", "18")] @@ -397,7 +397,7 @@ public Task Throws_When_Cgroup_Cpu_Files_Contain_Invalid_Data(string quota, stri return Verifier.Verify(r).UseParameters(quota, period).UseDirectory(VerifiedDataDirectory); } - [ConditionalFact] + [Fact] public void Reads_CpuUsage_When_Valid_Input() { var f = new HardcodedValueFileSystem(new Dictionary @@ -411,7 +411,7 @@ public void Reads_CpuUsage_When_Valid_Input() Assert.Equal(77_994_900_000_000, r); } - [ConditionalTheory] + [Theory] [InlineData("0::/", "usage_usec 222222\nnr_periods 50", "222222000", "50")] [InlineData("0::/fakeslice", "usage_usec 222222\nnr_periods 75", "222222000", "75")] public void Reads_CpuUsageFromSlices_When_Valid_Input(string slicepath, string content, string expectedUsage, string expectedPeriods) @@ -432,7 +432,7 @@ public void Reads_CpuUsageFromSlices_When_Valid_Input(string slicepath, string c Assert.Equal(expectedPeriods, periods.ToString()); } - [ConditionalFact] + [Fact] public void Reads_TotalMemory_When_Valid_Input() { var f = new HardcodedValueFileSystem(new Dictionary @@ -447,7 +447,7 @@ public void Reads_TotalMemory_When_Valid_Input() Assert.Null(r); } - [ConditionalTheory] + [Theory] [InlineData("2569530367000")] [InlineData(" 2569530 36700 245693 4860924 82283 0 4360 0dsa")] [InlineData("asdasd 2569530 36700 245693 4860924 82283 0 4360 0 0 0")] @@ -467,7 +467,7 @@ public Task Throws_When_CpuUsage_Invalid(string content) return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [Theory] [InlineData("usage_", 12222)] [InlineData("dasd", -1)] [InlineData("@#dddada", 342322)] @@ -484,7 +484,7 @@ public Task Throws_When_CpuAcctUsage_Has_Invalid_Content_Both_Parts(string conte return Verifier.Verify(r).UseParameters(content, value).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [Theory] [InlineData(-32131)] [InlineData(-1)] [InlineData(-15.323)] @@ -501,7 +501,7 @@ public Task Throws_When_Usage_Usec_Has_Negative_Value(int value) return Verifier.Verify(r).UseParameters(value).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [Theory] [InlineData("-1")] [InlineData("dasrz3424")] [InlineData("0")] @@ -519,7 +519,7 @@ public Task Throws_When_Cgroup_Cpu_Weight_Files_Contain_Invalid_Data(string cont return Verifier.Verify(r).UseParameters(content).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [Theory] [InlineData("2500", 64.0)] [InlineData("10000", 256.0)] public void Calculates_Cpu_Request_From_Cpu_Weight(string content, float result) @@ -535,7 +535,7 @@ public void Calculates_Cpu_Request_From_Cpu_Weight(string content, float result) Assert.Equal(result, r); } - [ConditionalTheory] + [Theory] [InlineData("0::/", "filename", "/sys/fs/cgroup/filename")] [InlineData("0::/filesystem.slice", "filename", "/sys/fs/cgroup/filesystem.slice/filename")] [InlineData("0::/filesystem.slice/", "filename", "/sys/fs/cgroup/filesystem.slice/filename")] @@ -552,7 +552,7 @@ public void Create_Path_From_Proc_Self_Cgroup(string content, string filename, s Assert.Equal(result, r); } - [ConditionalFact] + [Fact] public async Task Is_Thread_Safe_Async() { var f1 = new HardcodedValueFileSystem(new Dictionary diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs index ec329d1e7f3..0559cebed52 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs @@ -22,7 +22,7 @@ public sealed class LinuxUtilizationProviderTests { private const string VerifiedDataDirectory = "Verified"; - [ConditionalFact] + [Theory] [CombinatorialData] public void Provider_Registers_Instruments() { @@ -103,7 +103,7 @@ public void Provider_Registers_Instruments() Assert.Equal(0.5, samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ProcessMemoryUtilization).value); } - [ConditionalFact] + [Theory] [CombinatorialData] public void Provider_Registers_Instruments_CgroupV2() { @@ -217,7 +217,7 @@ public void Provider_Creates_Meter_With_Correct_Name() Assert.Equal(ResourceUtilizationInstruments.MeterName, meter.Name); } - [ConditionalFact] + [Theory] [CombinatorialData] public void Provider_Registers_Instruments_CgroupV2_WithoutHostCpu() { diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/OSFileSystemTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/OSFileSystemTests.cs index 6496d540015..ce3a82f3495 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/OSFileSystemTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/OSFileSystemTests.cs @@ -13,7 +13,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; [PlatformSpecific(TestPlatforms.Linux)] public sealed class OSFileSystemTests { - [ConditionalFact] + [Fact] public void GetDirectoryNames_ReturnsDirectoryNames() { var fileSystem = new OSFileSystem(); @@ -23,7 +23,7 @@ public void GetDirectoryNames_ReturnsDirectoryNames() Assert.Single(directoryNames); } - [ConditionalFact] + [Fact] public void Reading_First_File_Line_Works() { const string Content = "Name: cat"; @@ -36,7 +36,7 @@ public void Reading_First_File_Line_Works() Assert.Equal(Content, s); } - [ConditionalFact] + [Fact] public void Reading_The_Whole_File_Works() { const string Content = "user 1399428\nsystem 1124053\n"; @@ -50,7 +50,7 @@ public void Reading_The_Whole_File_Works() Assert.Equal(Content, s); } - [ConditionalTheory] + [Theory] [InlineData(128)] [InlineData(256)] [InlineData(512)] diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoRatePerfCounterTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoRatePerfCounterTests.cs index e94c62f2212..85da127fca4 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoRatePerfCounterTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoRatePerfCounterTests.cs @@ -16,7 +16,7 @@ public class WindowsDiskIoRatePerfCounterTests { private const string CategoryName = "LogicalDisk"; - [ConditionalFact] + [Fact] public void DiskReadsPerfCounter_Per60Seconds() { const string CounterName = WindowsDiskPerfCounterNames.DiskReadsCounter; @@ -63,7 +63,7 @@ public void DiskReadsPerfCounter_Per60Seconds() Assert.Equal(660, ratePerfCounters.TotalCountDict["D:"]); // 450 + 3.5 * 60 = 660 } - [ConditionalFact] + [Fact] public void DiskWriteBytesPerfCounter_Per30Seconds() { const string CounterName = WindowsDiskPerfCounterNames.DiskWriteBytesCounter; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoTimePerfCounterTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoTimePerfCounterTests.cs index 1fabe910afa..86f91d96dbc 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoTimePerfCounterTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskIoTimePerfCounterTests.cs @@ -16,7 +16,7 @@ public class WindowsDiskIoTimePerfCounterTests { private const string CategoryName = "LogicalDisk"; - [ConditionalFact] + [Fact] public void DiskReadsPerfCounter_Per60Seconds() { const string CounterName = WindowsDiskPerfCounterNames.DiskReadsCounter; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskMetricsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskMetricsTests.cs index aaced13eb33..563f800f4ae 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskMetricsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Disk/WindowsDiskMetricsTests.cs @@ -24,7 +24,7 @@ public class WindowsDiskMetricsTests private const string CategoryName = "LogicalDisk"; private readonly FakeLogger _fakeLogger = new(); - [ConditionalFact] + [Fact] public void Creates_Meter_With_Correct_Name() { using var meterFactory = new TestMeterFactory(); @@ -42,7 +42,7 @@ public void Creates_Meter_With_Correct_Name() Assert.Equal(ResourceUtilizationInstruments.MeterName, meter.Name); } - [ConditionalFact] + [Fact] public void DiskOperationMetricsTest() { using var meterFactory = new TestMeterFactory(); @@ -116,7 +116,7 @@ public void DiskOperationMetricsTest() Assert.Equal(5700, measurements.Last(x => x.MatchesTags(writeTag, deviceTagD)).Value); // 3600 + 35 * 60 = 5700 } - [ConditionalFact] + [Fact] public void DiskIoBytesMetricsTest() { using var meterFactory = new TestMeterFactory(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/MemoryInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/MemoryInfoTests.cs index 68746c983b4..1f2e130589b 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/MemoryInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/MemoryInfoTests.cs @@ -14,7 +14,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; [PlatformSpecific(TestPlatforms.Windows)] public sealed class MemoryInfoTests { - [ConditionalFact] + [Fact] public void GetGlobalMemory() { var memoryStatus = new MemoryInfo().GetMemoryStatus(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterFactoryTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterFactoryTests.cs index 2c67760ae7e..ff0845ba099 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterFactoryTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterFactoryTests.cs @@ -10,7 +10,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; [PlatformSpecific(TestPlatforms.Windows)] public class PerformanceCounterFactoryTests { - [ConditionalFact] + [Fact] public void GetInstanceNameTest() { var performanceCounterFactory = new PerformanceCounterFactory(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterWrapperTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterWrapperTests.cs index 1a8bb3a9e14..30b6ab78449 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterWrapperTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/PerformanceCounterWrapperTests.cs @@ -10,7 +10,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; [PlatformSpecific(TestPlatforms.Windows)] public class PerformanceCounterWrapperTests { - [ConditionalFact] + [Fact] public void GetInstanceNameTest() { var wrapper = new PerformanceCounterWrapper("Processor", "% Processor Time", "_Total"); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/SystemInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/SystemInfoTests.cs index 8d2e1dfb7a7..740d14fc5f3 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/SystemInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/SystemInfoTests.cs @@ -17,7 +17,7 @@ public sealed class SystemInfoTests /// /// Get basic system info. /// - [ConditionalFact] + [Fact] public void GetSystemInfo() { var sysInfo = new SystemInfo().GetSystemInfo(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs index 673c332c651..584e73e178a 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs @@ -225,7 +225,7 @@ public static uint FakeGetTcp6TableWithFakeInformation(IntPtr pTcp6Table, ref ui return (uint)NTSTATUS.Success; } - [ConditionalFact] + [Fact] public void Test_Tcp6TableInfo_Get_UnsuccessfulStatus_All_The_Time() { var options = new ResourceMonitoringOptions @@ -242,7 +242,7 @@ public void Test_Tcp6TableInfo_Get_UnsuccessfulStatus_All_The_Time() }); } - [ConditionalFact] + [Fact] public void Test_Tcp6TableInfo_Get_InsufficientBuffer_Then_Get_InvalidParameter() { var options = new ResourceMonitoringOptions @@ -258,7 +258,7 @@ public void Test_Tcp6TableInfo_Get_InsufficientBuffer_Then_Get_InvalidParameter( }); } - [ConditionalFact] + [Fact] public void Test_Tcp6TableInfo_Get_Correct_Information() { StartTimestamp = DateTimeOffset.UtcNow; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs index 8847d3ce6a3..49fd3551d7a 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs @@ -168,7 +168,7 @@ public static unsafe uint FakeGetTcpTableWithFakeInformation(IntPtr pTcpTable, r return (uint)NTSTATUS.Success; } - [ConditionalFact] + [Fact] public void Test_TcpTableInfo_Get_UnsuccessfulStatus_All_The_Time() { var options = new ResourceMonitoringOptions @@ -184,7 +184,7 @@ public void Test_TcpTableInfo_Get_UnsuccessfulStatus_All_The_Time() }); } - [ConditionalFact] + [Fact] public void Test_TcpTableInfo_Get_InsufficientBuffer_Then_Get_InvalidParameter() { var options = new ResourceMonitoringOptions @@ -200,7 +200,7 @@ public void Test_TcpTableInfo_Get_InsufficientBuffer_Then_Get_InvalidParameter() }); } - [ConditionalFact] + [Fact] public void Test_TcpTableInfo_Get_Correct_Information() { StartTimestamp = DateTimeOffset.UtcNow; @@ -261,7 +261,7 @@ public void Test_TcpTableInfo_Get_Correct_Information() Assert.Equal(2, tcpStateInfo.DeleteTcbCount); } - [ConditionalFact] + [Fact] public void Test_TcpTableInfo_CalculateCount_default_branch() { TcpStateInfo tcpStateInfo = new(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsNetworkMetricsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsNetworkMetricsTests.cs index 5cf2f7e7f28..e78521615b9 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsNetworkMetricsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsNetworkMetricsTests.cs @@ -14,7 +14,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; [PlatformSpecific(TestPlatforms.Windows)] public class WindowsNetworkMetricsTests { - [ConditionalFact] + [Fact] public void Creates_Meter_With_Correct_Name() { using var meterFactory = new TestMeterFactory(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsSnapshotProviderTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsSnapshotProviderTests.cs index 9251b2210a2..cc2701caea0 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsSnapshotProviderTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsSnapshotProviderTests.cs @@ -38,7 +38,7 @@ public WindowsSnapshotProviderTests() _fakeLogger = new FakeLogger(); } - [ConditionalFact] + [Fact] public void BasicConstructor() { var provider = new WindowsSnapshotProvider(_fakeLogger, _meterFactoryMock.Object, _options); @@ -50,7 +50,7 @@ public void BasicConstructor() Assert.Equal(memoryStatus.TotalPhys, provider.Resources.MaximumMemoryInBytes); } - [ConditionalFact] + [Fact] public void GetSnapshot_DoesNotThrowExceptions() { var provider = new WindowsSnapshotProvider(_fakeLogger, _meterFactoryMock.Object, _options); @@ -59,7 +59,7 @@ public void GetSnapshot_DoesNotThrowExceptions() Assert.Null(exception); } - [ConditionalFact] + [Fact] public Task SnapshotProvider_EmitsLogRecord() { var provider = new WindowsSnapshotProvider(_fakeLogger, _meterFactoryMock.Object, _options); @@ -70,7 +70,7 @@ public Task SnapshotProvider_EmitsLogRecord() return Verifier.Verify(logRecords[0]).UseDirectory(VerifiedDataDirectory); } - [ConditionalTheory] + [Theory] [CombinatorialData] public void SnapshotProvider_EmitsCpuMetrics(bool useZeroToOneRange) { @@ -111,7 +111,7 @@ public void SnapshotProvider_EmitsCpuMetrics(bool useZeroToOneRange) Assert.Equal(0.05 * multiplier, metricCollector.LastMeasurement?.Value); // Still consuming 5% of the CPU } - [ConditionalTheory] + [Theory] [CombinatorialData] public void SnapshotProvider_EmitsMemoryMetrics(bool useZeroToOneRange) { @@ -161,7 +161,7 @@ public void SnapshotProvider_EmitsMemoryMetrics(bool useZeroToOneRange) Assert.Equal(1 * multiplier, Math.Round(metricCollector.LastMeasurement.Value)); // Consuming 100% of the memory } - [ConditionalFact] + [Fact] public void Provider_Returns_MemoryConsumption() { // This is a synthetic test to have full test coverage: @@ -169,7 +169,7 @@ public void Provider_Returns_MemoryConsumption() Assert.InRange(usage, 0, long.MaxValue); } - [ConditionalFact] + [Fact] public void Provider_Creates_Meter_With_Correct_Name() { using var meterFactory = new TestMeterFactory(); From ce84dc75a03bce861f930e72fdc7323145b1fbcf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:44:59 +0000 Subject: [PATCH 08/16] Fix StyleCop using directive ordering and incorrect Theory/Fact mappings - Fix using directive ordering: Microsoft.DotNet.XUnitExtensions must come before Xunit alphabetically - Fix incorrect ConditionalFact to Theory mappings: tests without parameters should be Fact, not Theory - Remove CombinatorialData from tests that have no parameters Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../AgentQualityEvaluatorTests.cs | 2 +- .../NLPEvaluatorTests.cs | 2 +- .../QualityEvaluatorTests.cs | 2 +- .../SafetyEvaluatorTests.cs | 2 +- .../EmbeddingTests.cs | 2 +- .../ResponseCacheTester.cs | 2 +- .../ResultStoreTester.cs | 2 +- .../ChatClientIntegrationTests.cs | 986 +----------------- .../EmbeddingGeneratorIntegrationTests.cs | 97 +- ...ageGeneratingChatClientIntegrationTests.cs | 214 +--- .../ImageGeneratorIntegrationTests.cs | 63 +- .../SpeechToTextClientIntegrationTests.cs | 34 +- .../OllamaSharpChatClientIntegrationTests.cs | 2 +- .../OpenAIResponseClientIntegrationTests.cs | 2 +- .../Readers/DocumentReaderConformanceTests.cs | 2 +- .../Readers/MarkItDownReaderTests.cs | 2 +- .../Linux/AcceptanceTest.cs | 9 +- .../Linux/LinuxUtilizationProviderTests.cs | 9 +- 18 files changed, 24 insertions(+), 1410 deletions(-) diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/AgentQualityEvaluatorTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/AgentQualityEvaluatorTests.cs index 1f56d9b980a..f2b5d4d88f9 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/AgentQualityEvaluatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/AgentQualityEvaluatorTests.cs @@ -11,8 +11,8 @@ using Microsoft.Extensions.AI.Evaluation.Reporting; using Microsoft.Extensions.AI.Evaluation.Reporting.Storage; using Microsoft.Extensions.AI.Evaluation.Tests; -using Xunit; using Microsoft.DotNet.XUnitExtensions; +using Xunit; namespace Microsoft.Extensions.AI.Evaluation.Integration.Tests; diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/NLPEvaluatorTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/NLPEvaluatorTests.cs index 12582bc4acd..a959959d07f 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/NLPEvaluatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/NLPEvaluatorTests.cs @@ -8,8 +8,8 @@ using Microsoft.Extensions.AI.Evaluation.NLP; using Microsoft.Extensions.AI.Evaluation.Reporting; using Microsoft.Extensions.AI.Evaluation.Reporting.Storage; -using Xunit; using Microsoft.DotNet.XUnitExtensions; +using Xunit; namespace Microsoft.Extensions.AI.Evaluation.Integration.Tests; diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/QualityEvaluatorTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/QualityEvaluatorTests.cs index 8fef8120e9c..60522e21896 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/QualityEvaluatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/QualityEvaluatorTests.cs @@ -10,8 +10,8 @@ using Microsoft.Extensions.AI.Evaluation.Reporting; using Microsoft.Extensions.AI.Evaluation.Reporting.Storage; using Microsoft.Extensions.AI.Evaluation.Tests; -using Xunit; using Microsoft.DotNet.XUnitExtensions; +using Xunit; namespace Microsoft.Extensions.AI.Evaluation.Integration.Tests; diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/SafetyEvaluatorTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/SafetyEvaluatorTests.cs index d284524ec59..af050406e47 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/SafetyEvaluatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/SafetyEvaluatorTests.cs @@ -13,8 +13,8 @@ using Microsoft.Extensions.AI.Evaluation.Safety; using Microsoft.Extensions.AI.Evaluation.Tests; using Microsoft.Extensions.AI.Evaluation.Utilities; -using Xunit; using Microsoft.DotNet.XUnitExtensions; +using Xunit; namespace Microsoft.Extensions.AI.Evaluation.Integration.Tests; diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/EmbeddingTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/EmbeddingTests.cs index 1bb55568346..7889b5daa53 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/EmbeddingTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/EmbeddingTests.cs @@ -3,8 +3,8 @@ using System; using Microsoft.Extensions.AI.Evaluation.Reporting.Formats.Html; -using Xunit; using Microsoft.DotNet.XUnitExtensions; +using Xunit; namespace Microsoft.Extensions.AI.Evaluation.Reporting.Tests; diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResponseCacheTester.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResponseCacheTester.cs index 6134838ba33..d76e60efc6e 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResponseCacheTester.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResponseCacheTester.cs @@ -5,8 +5,8 @@ using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; -using Xunit; using Microsoft.DotNet.XUnitExtensions; +using Xunit; namespace Microsoft.Extensions.AI.Evaluation.Reporting.Tests; diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResultStoreTester.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResultStoreTester.cs index 3a37160f497..32089e1a4d7 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResultStoreTester.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResultStoreTester.cs @@ -6,8 +6,8 @@ using System.IO; using System.Linq; using System.Threading.Tasks; -using Xunit; using Microsoft.DotNet.XUnitExtensions; +using Xunit; namespace Microsoft.Extensions.AI.Evaluation.Reporting.Tests; diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs index f35727eae9c..dc95edffa7f 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs @@ -19,9 +19,8 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; using OpenTelemetry.Trace; -using Xunit; using Microsoft.DotNet.XUnitExtensions; - +using Xunit; #pragma warning disable CA2000 // Dispose objects before losing scope #pragma warning disable CA2214 // Do not call overridable methods in constructors #pragma warning disable CA2249 // Consider using 'string.Contains' instead of 'string.IndexOf' @@ -29,50 +28,31 @@ #pragma warning disable S1144 // Unused private types or members should be removed #pragma warning disable S3604 // Member initializer values should not be redundant #pragma warning disable SA1515 // Single-line comment should be preceded by blank line - namespace Microsoft.Extensions.AI; - public abstract class ChatClientIntegrationTests : IDisposable { protected ChatClientIntegrationTests() { ChatClient = CreateChatClient(); } - protected IChatClient? ChatClient { get; } - protected IEmbeddingGenerator>? EmbeddingGenerator { get; private set; } - public void Dispose() - { ChatClient?.Dispose(); GC.SuppressFinalize(this); - } - protected abstract IChatClient? CreateChatClient(); - /// /// Optionally supplies an embedding generator for integration tests that exercise /// embedding-based components (e.g., tool reduction). Default returns null and /// tests depending on embeddings will skip if not overridden. /// protected virtual IEmbeddingGenerator>? CreateEmbeddingGenerator() => null; - [ConditionalFact] public virtual async Task GetResponseAsync_SingleRequestMessage() - { SkipIfNotEnabled(); - var response = await ChatClient.GetResponseAsync("What's the biggest animal?"); - Assert.Contains("whale", response.Text, StringComparison.OrdinalIgnoreCase); - } - - [ConditionalFact] public virtual async Task GetResponseAsync_MultipleRequestMessages() - { - SkipIfNotEnabled(); - var response = await ChatClient.GetResponseAsync( [ new(ChatRole.User, "Pick a city, any city"), @@ -81,114 +61,58 @@ public virtual async Task GetResponseAsync_MultipleRequestMessages() new(ChatRole.Assistant, "Jakarta"), new(ChatRole.User, "What continent are they each in?"), ]); - Assert.Contains("America", response.Text); Assert.Contains("Asia", response.Text); - } - - [ConditionalFact] public virtual async Task GetResponseAsync_WithEmptyMessage() - { - SkipIfNotEnabled(); - - var response = await ChatClient.GetResponseAsync( - [ new(ChatRole.System, []), new(ChatRole.User, []), new(ChatRole.Assistant, []), new(ChatRole.User, "What is 1 + 2? Reply with a single number."), - ]); - Assert.Contains("3", response.Text); - } - - [ConditionalFact] public virtual async Task GetStreamingResponseAsync() - { - SkipIfNotEnabled(); - IList chatHistory = - [ new(ChatRole.User, "Quote, word for word, Neil Armstrong's famous words.") ]; - StringBuilder sb = new(); await foreach (var chunk in ChatClient.GetStreamingResponseAsync(chatHistory)) { sb.Append(chunk.Text); } - string responseText = sb.ToString(); Assert.Contains("one small step", responseText, StringComparison.OrdinalIgnoreCase); Assert.Contains("one giant leap", responseText, StringComparison.OrdinalIgnoreCase); - } - - [ConditionalFact] public virtual async Task GetResponseAsync_UsageDataAvailable() - { - SkipIfNotEnabled(); - var response = await ChatClient.GetResponseAsync("Explain in 10 words how AI works"); - Assert.True(response.Usage?.InputTokenCount > 1); Assert.True(response.Usage?.OutputTokenCount > 1); Assert.Equal(response.Usage?.InputTokenCount + response.Usage?.OutputTokenCount, response.Usage?.TotalTokenCount); - } - - [ConditionalFact] public virtual async Task GetStreamingResponseAsync_UsageDataAvailable() - { - SkipIfNotEnabled(); - var response = ChatClient.GetStreamingResponseAsync("Explain in 10 words how AI works", new() - { AdditionalProperties = new() { ["stream_options"] = new Dictionary { ["include_usage"] = true, }, }, }); - List chunks = []; await foreach (var chunk in response) - { chunks.Add(chunk); - } - Assert.True(chunks.Count > 1); - UsageContent usage = chunks.SelectMany(c => c.Contents).OfType().Single(); Assert.True(usage.Details.InputTokenCount > 1); Assert.True(usage.Details.OutputTokenCount > 1); Assert.Equal(usage.Details.InputTokenCount + usage.Details.OutputTokenCount, usage.Details.TotalTokenCount); - } - - [ConditionalFact] public virtual async Task GetStreamingResponseAsync_AppendToHistory() - { - SkipIfNotEnabled(); - List history = [new(ChatRole.User, "Explain in 100 words how AI works")]; - var streamingResponse = ChatClient.GetStreamingResponseAsync(history); - Assert.Single(history); await history.AddMessagesAsync(streamingResponse); Assert.Equal(2, history.Count); Assert.Equal(ChatRole.Assistant, history[1].Role); - var singleTextContent = (TextContent)history[1].Contents.Single(); Assert.NotEmpty(singleTextContent.Text); Assert.Equal(history[1].Text, singleTextContent.Text); - } - protected virtual string? GetModel_MultiModal_DescribeImage() => null; - - [ConditionalFact] public virtual async Task MultiModal_DescribeImage() - { - SkipIfNotEnabled(); - - var response = await ChatClient.GetResponseAsync( [ new(ChatRole.User, [ @@ -197,264 +121,83 @@ public virtual async Task MultiModal_DescribeImage() ]) ], new() { ModelId = GetModel_MultiModal_DescribeImage() }); - Assert.True(response.Text.IndexOf("net", StringComparison.OrdinalIgnoreCase) >= 0, response.Text); - } - - [ConditionalFact] public virtual async Task MultiModal_DescribePdf() - { - SkipIfNotEnabled(); - - var response = await ChatClient.GetResponseAsync( - [ - new(ChatRole.User, - [ new TextContent("What text does this document contain?"), new DataContent(ImageDataUri.GetPdfDataUri(), "application/pdf") { Name = "sample.pdf" }, - ]) - ], - new() { ModelId = GetModel_MultiModal_DescribeImage() }); - Assert.True(response.Text.IndexOf("hello", StringComparison.OrdinalIgnoreCase) >= 0, response.Text); - } - - [ConditionalFact] public virtual async Task FunctionInvocation_AutomaticallyInvokeFunction_Parameterless() - { - SkipIfNotEnabled(); - var sourceName = Guid.NewGuid().ToString(); var activities = new List(); using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder() .AddSource(sourceName) .AddInMemoryExporter(activities) .Build(); - using var chatClient = new FunctionInvokingChatClient( new OpenTelemetryChatClient(ChatClient, sourceName: sourceName)); - int secretNumber = 42; - List messages = - [ new(ChatRole.User, "What is the current secret number?") - ]; - var response = await chatClient.GetResponseAsync(messages, new() - { Tools = [AIFunctionFactory.Create(() => secretNumber, "GetSecretNumber")] - }); - Assert.Contains(secretNumber.ToString(), response.Text); AssertUsageAgainstActivities(response, activities); - } - - [ConditionalFact] public virtual async Task FunctionInvocation_AutomaticallyInvokeFunction_WithParameters_NonStreaming() - { - SkipIfNotEnabled(); - using var chatClient = new FunctionInvokingChatClient(ChatClient); - var response = await chatClient.GetResponseAsync("What is the result of SecretComputation on 42 and 84?", new() - { Tools = [AIFunctionFactory.Create((int a, int b) => a * b, "SecretComputation")] - }); - Assert.Contains("3528", response.Text); - } - - [ConditionalFact] public virtual async Task FunctionInvocation_AutomaticallyInvokeFunction_WithParameters_Streaming() - { - SkipIfNotEnabled(); - - using var chatClient = new FunctionInvokingChatClient(ChatClient); - var response = chatClient.GetStreamingResponseAsync("What is the result of SecretComputation on 42 and 84?", new() - { - Tools = [AIFunctionFactory.Create((int a, int b) => a * b, "SecretComputation")] - }); - - StringBuilder sb = new(); - await foreach (var chunk in response) - { - sb.Append(chunk.Text); - } - Assert.Contains("3528", sb.ToString()); - } - - [ConditionalFact] public virtual async Task FunctionInvocation_OptionalParameter() - { - SkipIfNotEnabled(); - - var sourceName = Guid.NewGuid().ToString(); - var activities = new List(); - using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder() - .AddSource(sourceName) - .AddInMemoryExporter(activities) - .Build(); - - using var chatClient = new FunctionInvokingChatClient( - new OpenTelemetryChatClient(ChatClient, sourceName: sourceName)); - - int secretNumber = 42; - - List messages = - [ new(ChatRole.User, "What is the secret number for id foo?") - ]; - AIFunction func = AIFunctionFactory.Create((string id = "defaultId") => id is "foo" ? secretNumber : -1, "GetSecretNumberById"); - var response = await chatClient.GetResponseAsync(messages, new() - { Tools = [func] - }); - - Assert.Contains(secretNumber.ToString(), response.Text); - AssertUsageAgainstActivities(response, activities); - } - - [ConditionalFact] public virtual async Task FunctionInvocation_NestedParameters() - { - SkipIfNotEnabled(); - - var sourceName = Guid.NewGuid().ToString(); - var activities = new List(); - using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder() - .AddSource(sourceName) - .AddInMemoryExporter(activities) - .Build(); - - using var chatClient = new FunctionInvokingChatClient( - new OpenTelemetryChatClient(ChatClient, sourceName: sourceName)); - - int secretNumber = 42; - - List messages = - [ new(ChatRole.User, "What is the secret number for John aged 19?") - ]; - AIFunction func = AIFunctionFactory.Create((PersonRecord person) => person.Name is "John" ? secretNumber + person.Age : -1, "GetSecretNumberByPerson"); - var response = await chatClient.GetResponseAsync(messages, new() - { - Tools = [func] - }); - Assert.Contains((secretNumber + 19).ToString(), response.Text); - AssertUsageAgainstActivities(response, activities); - } - - [ConditionalFact] public virtual async Task FunctionInvocation_ArrayParameter() - { - SkipIfNotEnabled(); - - var sourceName = Guid.NewGuid().ToString(); - var activities = new List(); - using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder() - .AddSource(sourceName) - .AddInMemoryExporter(activities) - .Build(); - - using var chatClient = new FunctionInvokingChatClient( - new OpenTelemetryChatClient(ChatClient, sourceName: sourceName)); - - List messages = - [ new(ChatRole.User, "Can you add bacon, lettuce, and tomatoes to Peter's shopping cart?") - ]; - string? shopperName = null; List shoppingCart = []; AIFunction func = AIFunctionFactory.Create((string[] items, string shopperId) => { shoppingCart.AddRange(items); shopperName = shopperId; }, "AddItemsToShoppingCart"); - var response = await chatClient.GetResponseAsync(messages, new() - { - Tools = [func] - }); - Assert.Equal("Peter", shopperName); Assert.Equal(["bacon", "lettuce", "tomatoes"], shoppingCart); - AssertUsageAgainstActivities(response, activities); - } - private static void AssertUsageAgainstActivities(ChatResponse response, List activities) - { // If the underlying IChatClient provides usage data, function invocation should aggregate the // usage data across all calls to produce a single Usage value on the final response. // The FunctionInvokingChatClient then itself creates a span that will also be tagged with a sum // across all consituent calls, which means our final answer will be double. if (response.Usage is { } finalUsage) - { var totalInputTokens = activities.Sum(a => (int?)a.GetTagItem("gen_ai.usage.input_tokens")!); var totalOutputTokens = activities.Sum(a => (int?)a.GetTagItem("gen_ai.usage.output_tokens")!); Assert.Equal(totalInputTokens, finalUsage.InputTokenCount * 2); Assert.Equal(totalOutputTokens, finalUsage.OutputTokenCount * 2); - } - } - public record PersonRecord(string Name, int Age = 42); - - [ConditionalFact] public virtual Task AvailableTools_SchemasAreAccepted_Strict() => AvailableTools_SchemasAreAccepted(strict: true); - - [ConditionalFact] public virtual Task AvailableTools_SchemasAreAccepted_NonStrict() => AvailableTools_SchemasAreAccepted(strict: false); - private async Task AvailableTools_SchemasAreAccepted(bool strict) - { - SkipIfNotEnabled(); - - var sourceName = Guid.NewGuid().ToString(); - var activities = new List(); - using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder() - .AddSource(sourceName) - .AddInMemoryExporter(activities) - .Build(); - - using var chatClient = new FunctionInvokingChatClient( - new OpenTelemetryChatClient(ChatClient, sourceName: sourceName)); - int methodCount = 1; Func createOptions = () => - { AIFunctionFactoryOptions aiFuncOptions = new() - { Name = $"Method{methodCount++}", }; - if (strict) - { aiFuncOptions.AdditionalProperties = new Dictionary { ["strictJsonSchema"] = true }; } - return aiFuncOptions; }; - Func createWithSchema = schema => - { Dictionary additionalProperties = new(); - - if (strict) - { additionalProperties["strictJsonSchema"] = true; - } - return new CustomAIFunction($"CustomMethod{methodCount++}", schema, additionalProperties); - }; - ChatOptions options = new() - { MaxOutputTokens = 100, Tools = - [ // Using AIFunctionFactory AIFunctionFactory.Create((int? i) => i, createOptions()), AIFunctionFactory.Create((string? s) => s, createOptions()), @@ -478,111 +221,52 @@ private async Task AvailableTools_SchemasAreAccepted(bool strict) AIFunctionFactory.Create((int[] arr, ComplexObject? co) => arr, createOptions()), AIFunctionFactory.Create((string p1 = "str", int p2 = 42, BindingFlags p3 = BindingFlags.IgnoreCase, char p4 = 'x') => p1, createOptions()), AIFunctionFactory.Create((string? p1 = "str", int? p2 = 42, BindingFlags? p3 = BindingFlags.IgnoreCase, char? p4 = 'x') => p1, createOptions()), - // Selection from @modelcontextprotocol/server-everything createWithSchema(""" {"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"} """), - createWithSchema(""" {"type":"object","properties":{"duration":{"type":"number","default":10,"description":"Duration of the operation in seconds"},"steps":{"type":"number","default":5,"description":"Number of steps in the operation"}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"} - """), - createWithSchema(""" {"type":"object","properties":{"prompt":{"type":"string","description":"The prompt to send to the LLM"},"maxTokens":{"type":"number","default":100,"description":"Maximum number of tokens to generate"}},"required":["prompt"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"} - """), - createWithSchema(""" - {"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"} - """), - createWithSchema(""" {"type":"object","properties":{"messageType":{"type":"string","enum":["error","success","debug"],"description":"Type of message to demonstrate different annotation patterns"},"includeImage":{"type":"boolean","default":false,"description":"Whether to include an example image"}},"required":["messageType"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"} - """), - createWithSchema(""" {"type":"object","properties":{"resourceId":{"type":"number","minimum":1,"maximum":100,"description":"ID of the resource to reference (1-100)"}},"required":["resourceId"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"} - """), - // Selection from GH MCP server - createWithSchema(""" {"properties":{"body":{"description":"The text of the review comment","type":"string"},"line":{"description":"The line of the blob in the pull request diff that the comment applies to. For multi-line comments, the last line of the range","type":"number"},"owner":{"description":"Repository owner","type":"string"},"path":{"description":"The relative path to the file that necessitates a comment","type":"string"},"pullNumber":{"description":"Pull request number","type":"number"},"repo":{"description":"Repository name","type":"string"},"side":{"description":"The side of the diff to comment on. LEFT indicates the previous state, RIGHT indicates the new state","enum":["LEFT","RIGHT"],"type":"string"},"startLine":{"description":"For multi-line comments, the first line of the range that the comment applies to","type":"number"},"startSide":{"description":"For multi-line comments, the starting side of the diff that the comment applies to. LEFT indicates the previous state, RIGHT indicates the new state","enum":["LEFT","RIGHT"],"type":"string"},"subjectType":{"description":"The level at which the comment is targeted","enum":["FILE","LINE"],"type":"string"}},"required":["owner","repo","pullNumber","path","body","subjectType"],"type":"object"} - """), - createWithSchema(""" {"properties":{"commit_message":{"description":"Extra detail for merge commit","type":"string"},"commit_title":{"description":"Title for merge commit","type":"string"},"merge_method":{"description":"Merge method","enum":["merge","squash","rebase"],"type":"string"},"owner":{"description":"Repository owner","type":"string"},"pullNumber":{"description":"Pull request number","type":"number"},"repo":{"description":"Repository name","type":"string"}},"required":["owner","repo","pullNumber"],"type":"object"} - """), - ], - }; - // We don't care about the response, only that we get one and that an exception isn't thrown due to unacceptable schema. var response = await chatClient.GetResponseAsync("Briefly, what is the most popular tower in Paris?", options); Assert.NotNull(response); - } - private sealed class CustomAIFunction(string name, string jsonSchema, IReadOnlyDictionary additionalProperties) : AIFunction - { public override string Name => name; public override IReadOnlyDictionary AdditionalProperties => additionalProperties; public override JsonElement JsonSchema { get; } = JsonSerializer.Deserialize(jsonSchema, AIJsonUtilities.DefaultOptions); protected override ValueTask InvokeCoreAsync(AIFunctionArguments arguments, CancellationToken cancellationToken) => throw new NotSupportedException(); - } - private class ComplexObject - { [DisplayName("Something cool")] -#if NET [DeniedValues("abc", "def", "default")] -#endif public string? SomeString { get; set; } - -#if NET [AllowedValues("abc", "def", "default")] -#endif public string AnotherString { get; set; } = "default"; - -#if NET [Range(25, 75)] -#endif public int Value { get; set; } - [EmailAddress] public string? Email { get; set; } - [RegularExpression("[abc]")] public string? RegexString { get; set; } - [StringLength(42)] public string MeasuredString { get; set; } = "default"; - -#if NET [Length(1, 2)] -#endif public int[]? MeasuredArray1 { get; set; } - -#if NET [MinLength(1)] -#endif public int[]? MeasuredArray2 { get; set; } - -#if NET [MaxLength(10)] -#endif public int[]? MeasuredArray3 { get; set; } - } - protected virtual bool SupportsParallelFunctionCalling => true; - - [ConditionalFact] public virtual async Task FunctionInvocation_SupportsMultipleParallelRequests() - { - SkipIfNotEnabled(); if (!SupportsParallelFunctionCalling) - { throw new SkipTestException("Parallel function calling is not supported by this chat client"); - } - - using var chatClient = new FunctionInvokingChatClient(ChatClient); - // The service/model isn't guaranteed to request two calls to GetPersonAge in the same turn, but it's common that it will. var response = await chatClient.GetResponseAsync("How much older is Elsa than Anna? Return the age difference as a single number.", new() - { Tools = [AIFunctionFactory.Create((string personName) => - { return personName switch { "Elsa" => 21, @@ -590,145 +274,63 @@ public virtual async Task FunctionInvocation_SupportsMultipleParallelRequests() _ => 30, }; }, "GetPersonAge")] - }); - Assert.True( Regex.IsMatch(response.Text ?? "", @"\b(3|three)\b", RegexOptions.IgnoreCase), $"Doesn't contain three: {response.Text}"); - } - - [ConditionalFact] public virtual async Task FunctionInvocation_RequireAny() - { - SkipIfNotEnabled(); - int callCount = 0; var tool = AIFunctionFactory.Create(() => - { callCount++; return 123; }, "GetSecretNumber"); - - using var chatClient = new FunctionInvokingChatClient(ChatClient); - var response = await chatClient.GetResponseAsync("Are birds real?", new() - { Tools = [tool], ToolMode = ChatToolMode.RequireAny, - }); - Assert.True(callCount >= 1); - } - - [ConditionalFact] public virtual async Task FunctionInvocation_RequireSpecific() - { - SkipIfNotEnabled(); - bool shieldsUp = false; var getSecretNumberTool = AIFunctionFactory.Create(() => 123, "GetSecretNumber"); var shieldsUpTool = AIFunctionFactory.Create(() => shieldsUp = true, "ShieldsUp"); - - using var chatClient = new FunctionInvokingChatClient(ChatClient); - // Even though the user doesn't ask for the shields to be activated, verify that the tool is invoked var response = await chatClient.GetResponseAsync("What's the current secret number?", new() - { Tools = [getSecretNumberTool, shieldsUpTool], ToolMode = ChatToolMode.RequireSpecific(shieldsUpTool.Name), - }); - Assert.True(shieldsUp); - } - - [ConditionalFact] public virtual async Task Caching_OutputVariesWithoutCaching() - { - SkipIfNotEnabled(); - var message = new ChatMessage(ChatRole.User, "Pick a random number, uniformly distributed between 1 and 1000000"); var firstResponse = await ChatClient.GetResponseAsync([message]); - var secondResponse = await ChatClient.GetResponseAsync([message]); Assert.NotEqual(firstResponse.Text, secondResponse.Text); - } - - [ConditionalFact] public virtual async Task Caching_SamePromptResultsInCacheHit_NonStreaming() - { - SkipIfNotEnabled(); - using var chatClient = new DistributedCachingChatClient( ChatClient, new MemoryDistributedCache(Options.Options.Create(new MemoryDistributedCacheOptions()))); - - var message = new ChatMessage(ChatRole.User, "Pick a random number, uniformly distributed between 1 and 1000000"); var firstResponse = await chatClient.GetResponseAsync([message]); - // No matter what it said before, we should see identical output due to caching for (int i = 0; i < 3; i++) - { var secondResponse = await chatClient.GetResponseAsync([message]); Assert.Equal(firstResponse.Messages.Select(m => m.Text), secondResponse.Messages.Select(m => m.Text)); - } - // ... but if the conversation differs, we should see different output ((TextContent)message.Contents[0]).Text += "!"; var thirdResponse = await chatClient.GetResponseAsync([message]); Assert.NotEqual(firstResponse.Messages, thirdResponse.Messages); - } - - [ConditionalFact] public virtual async Task Caching_SamePromptResultsInCacheHit_Streaming() - { - SkipIfNotEnabled(); - - using var chatClient = new DistributedCachingChatClient( - ChatClient, - new MemoryDistributedCache(Options.Options.Create(new MemoryDistributedCacheOptions()))); - - var message = new ChatMessage(ChatRole.User, "Pick a random number, uniformly distributed between 1 and 1000000"); StringBuilder orig = new(); await foreach (var update in chatClient.GetStreamingResponseAsync([message])) - { orig.Append(update.Text); - } - - // No matter what it said before, we should see identical output due to caching - for (int i = 0; i < 3; i++) - { StringBuilder second = new(); await foreach (var update in chatClient.GetStreamingResponseAsync([message])) - { second.Append(update.Text); - } - Assert.Equal(orig.ToString(), second.ToString()); - } - - // ... but if the conversation differs, we should see different output - ((TextContent)message.Contents[0]).Text += "!"; StringBuilder third = new(); - await foreach (var update in chatClient.GetStreamingResponseAsync([message])) - { third.Append(update.Text); - } - Assert.NotEqual(orig.ToString(), third.ToString()); - } - - [ConditionalFact] public virtual async Task Caching_BeforeFunctionInvocation_AvoidsExtraCalls() - { - SkipIfNotEnabled(); - int functionCallCount = 0; var getTemperature = AIFunctionFactory.Create([Description("Gets the current temperature")] () => - { functionCallCount++; return $"{100 + functionCallCount} degrees celsius"; }, "GetTemperature"); - // First call executes the function and calls the LLM using var chatClient = CreateChatClient()! .AsBuilder() @@ -736,219 +338,66 @@ public virtual async Task Caching_BeforeFunctionInvocation_AvoidsExtraCalls() .UseDistributedCache(new MemoryDistributedCache(Options.Options.Create(new MemoryDistributedCacheOptions()))) .UseFunctionInvocation() .UseCallCounting() - .Build(); - var llmCallCount = chatClient.GetService(); var message = new ChatMessage(ChatRole.User, "What is the temperature?"); var response = await chatClient.GetResponseAsync([message]); Assert.Contains("101", response.Text); - // First LLM call tells us to call the function, second deals with the result Assert.Equal(2, llmCallCount!.CallCount); - // Second call doesn't execute the function or call the LLM, but rather just returns the cached result var secondResponse = await chatClient.GetResponseAsync([message]); Assert.Equal(response.Text, secondResponse.Text); Assert.Equal(1, functionCallCount); - Assert.Equal(2, llmCallCount!.CallCount); - } - - [ConditionalFact] public virtual async Task Caching_AfterFunctionInvocation_FunctionOutputUnchangedAsync() - { - SkipIfNotEnabled(); - // This means that if the function call produces the same result, we can avoid calling the LLM // whereas if the function call produces a different result, we do call the LLM - var functionCallCount = 0; - var getTemperature = AIFunctionFactory.Create([Description("Gets the current temperature")] () => - { - functionCallCount++; return "58 degrees celsius"; - }, "GetTemperature"); - - // First call executes the function and calls the LLM - using var chatClient = CreateChatClient()! - .AsBuilder() - .ConfigureOptions(options => options.Tools = [getTemperature]) - .UseFunctionInvocation() - .UseDistributedCache(new MemoryDistributedCache(Options.Options.Create(new MemoryDistributedCacheOptions()))) - .UseCallCounting() - .Build(); - - var llmCallCount = chatClient.GetService(); - var message = new ChatMessage(ChatRole.User, "What is the temperature?"); - var response = await chatClient.GetResponseAsync([message]); Assert.Contains("58", response.Text); - - // First LLM call tells us to call the function, second deals with the result - Assert.Equal(1, functionCallCount); - Assert.Equal(2, llmCallCount!.CallCount); - // Second time, the calls to the LLM don't happen, but the function is called again - var secondResponse = await chatClient.GetResponseAsync([message]); Assert.Equal(2, functionCallCount); Assert.Equal(FunctionInvokingChatClientSetsConversationId ? 3 : 2, llmCallCount!.CallCount); - Assert.Equal(response.Text, secondResponse.Text); - } - public virtual bool FunctionInvokingChatClientSetsConversationId => false; - - [ConditionalFact] public virtual async Task Caching_AfterFunctionInvocation_FunctionOutputChangedAsync() - { - SkipIfNotEnabled(); - - // This means that if the function call produces the same result, we can avoid calling the LLM - // whereas if the function call produces a different result, we do call the LLM - - var functionCallCount = 0; - var getTemperature = AIFunctionFactory.Create([Description("Gets the current temperature")] () => - { - functionCallCount++; return $"{80 + functionCallCount} degrees celsius"; - }, "GetTemperature"); - - // First call executes the function and calls the LLM - using var chatClient = CreateChatClient()! - .AsBuilder() - .ConfigureOptions(options => options.Tools = [getTemperature]) - .UseFunctionInvocation() - .UseDistributedCache(new MemoryDistributedCache(Options.Options.Create(new MemoryDistributedCacheOptions()))) - .UseCallCounting() - .Build(); - - var llmCallCount = chatClient.GetService(); - var message = new ChatMessage(ChatRole.User, "What is the temperature?"); - var response = await chatClient.GetResponseAsync([message]); Assert.Contains("81", response.Text); - - // First LLM call tells us to call the function, second deals with the result - Assert.Equal(1, functionCallCount); - Assert.Equal(2, llmCallCount!.CallCount); - // Second time, the first call to the LLM don't happen, but the function is called again, // and since its output now differs, we no longer hit the cache so the second LLM call does happen - var secondResponse = await chatClient.GetResponseAsync([message]); Assert.Contains("82", secondResponse.Text); - Assert.Equal(2, functionCallCount); Assert.Equal(3, llmCallCount!.CallCount); - } - - [ConditionalFact] public virtual async Task Logging_LogsCalls_NonStreaming() - { - SkipIfNotEnabled(); - var collector = new FakeLogCollector(); using ILoggerFactory loggerFactory = LoggerFactory.Create(b => b.AddProvider(new FakeLoggerProvider(collector)).SetMinimumLevel(LogLevel.Trace)); - using var chatClient = CreateChatClient()!.AsBuilder() .UseLogging(loggerFactory) - .Build(); - await chatClient.GetResponseAsync([new(ChatRole.User, "What's the biggest animal?")]); - Assert.Collection(collector.GetSnapshot(), entry => Assert.Contains("What's the biggest animal?", entry.Message), entry => Assert.Contains("whale", entry.Message)); - } - - [ConditionalFact] public virtual async Task Logging_LogsCalls_Streaming() - { - SkipIfNotEnabled(); - - var collector = new FakeLogCollector(); - using ILoggerFactory loggerFactory = LoggerFactory.Create(b => b.AddProvider(new FakeLoggerProvider(collector)).SetMinimumLevel(LogLevel.Trace)); - - using var chatClient = CreateChatClient()!.AsBuilder() - .UseLogging(loggerFactory) - .Build(); - await foreach (var update in chatClient.GetStreamingResponseAsync("What's the biggest animal?")) - { // Do nothing with the updates - } - var logs = collector.GetSnapshot(); Assert.Contains(logs, e => e.Message.Contains("What's the biggest animal?")); Assert.Contains(logs, e => e.Message.Contains("whale")); - } - - [ConditionalFact] public virtual async Task Logging_LogsFunctionCalls_NonStreaming() - { - SkipIfNotEnabled(); - - var collector = new FakeLogCollector(); - using ILoggerFactory loggerFactory = LoggerFactory.Create(b => b.AddProvider(new FakeLoggerProvider(collector)).SetMinimumLevel(LogLevel.Trace)); - - using var chatClient = CreateChatClient()! - .AsBuilder() - .UseFunctionInvocation() - .UseLogging(loggerFactory) - .Build(); - - int secretNumber = 42; await chatClient.GetResponseAsync( "What is the current secret number?", new ChatOptions { Tools = [AIFunctionFactory.Create(() => secretNumber, "GetSecretNumber")] }); - - Assert.Collection(collector.GetSnapshot(), entry => Assert.Contains("What is the current secret number?", entry.Message), entry => Assert.Contains("\"name\": \"GetSecretNumber\"", entry.Message), entry => Assert.Contains($"\"result\": {secretNumber}", entry.Message), entry => Assert.Contains(secretNumber.ToString(), entry.Message)); - } - - [ConditionalFact] public virtual async Task Logging_LogsFunctionCalls_Streaming() - { - SkipIfNotEnabled(); - - var collector = new FakeLogCollector(); - using ILoggerFactory loggerFactory = LoggerFactory.Create(b => b.AddProvider(new FakeLoggerProvider(collector)).SetMinimumLevel(LogLevel.Trace)); - - using var chatClient = CreateChatClient()! - .AsBuilder() - .UseFunctionInvocation() - .UseLogging(loggerFactory) - .Build(); - - int secretNumber = 42; await foreach (var update in chatClient.GetStreamingResponseAsync( - "What is the current secret number?", new ChatOptions { Tools = [AIFunctionFactory.Create(() => secretNumber, "GetSecretNumber")] })) - { - // Do nothing with the updates - } - - var logs = collector.GetSnapshot(); Assert.Contains(logs, e => e.Message.Contains("What is the current secret number?")); Assert.Contains(logs, e => e.Message.Contains("\"name\": \"GetSecretNumber\"")); Assert.Contains(logs, e => e.Message.Contains($"\"result\": {secretNumber}")); - } - - [ConditionalFact] public virtual async Task OpenTelemetry_CanEmitTracesAndMetrics() - { - SkipIfNotEnabled(); - - var sourceName = Guid.NewGuid().ToString(); - var activities = new List(); - using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder() - .AddSource(sourceName) - .AddInMemoryExporter(activities) - .Build(); - var chatClient = CreateChatClient()!.AsBuilder() .UseOpenTelemetry(sourceName: sourceName) - .Build(); - var response = await chatClient.GetResponseAsync([new(ChatRole.User, "What's the biggest animal?")]); - var activity = Assert.Single(activities); Assert.StartsWith("chat", activity.DisplayName); Assert.Contains(".", (string)activity.GetTagItem("server.address")!); @@ -957,192 +406,92 @@ public virtual async Task OpenTelemetry_CanEmitTracesAndMetrics() Assert.NotEmpty(activity.Id); Assert.NotEqual(0, (int)activity.GetTagItem("gen_ai.usage.input_tokens")!); Assert.NotEqual(0, (int)activity.GetTagItem("gen_ai.usage.output_tokens")!); - Assert.True(activity.Duration.TotalMilliseconds > 0); - } - - [ConditionalFact] public virtual async Task GetResponseAsync_StructuredOutput() - { - SkipIfNotEnabled(); - var response = await ChatClient.GetResponseAsync(""" Who is described in the following sentence? Jimbo Smith is a 35-year-old programmer from Cardiff, Wales. """); - Assert.Equal("Jimbo Smith", response.Result.FullName); Assert.Equal(35, response.Result.AgeInYears); Assert.Contains("Cardiff", response.Result.HomeTown); Assert.Equal(JobType.Programmer, response.Result.Job); - } - - [ConditionalFact] public virtual async Task GetResponseAsync_StructuredOutputArray() - { - SkipIfNotEnabled(); - var response = await ChatClient.GetResponseAsync(""" Who are described in the following sentence? Jimbo Smith is a 35-year-old software developer from Cardiff, Wales. Josh Simpson is a 25-year-old software developer from Newport, Wales. - """); - Assert.Equal(2, response.Result.Length); Assert.Contains(response.Result, x => x.FullName == "Jimbo Smith"); Assert.Contains(response.Result, x => x.FullName == "Josh Simpson"); - } - - [ConditionalFact] public virtual async Task GetResponseAsync_StructuredOutputInteger() - { - SkipIfNotEnabled(); - var response = await ChatClient.GetResponseAsync(""" There were 14 abstractions for AI programming, which was too many. To fix this we added another one. How many are there now? - """); - Assert.Equal(15, response.Result); - } - - [ConditionalFact] public virtual async Task GetResponseAsync_StructuredOutputString() - { - SkipIfNotEnabled(); - var response = await ChatClient.GetResponseAsync(""" The software developer, Jimbo Smith, is a 35-year-old from Cardiff, Wales. What's his full name? - """); - Assert.Equal("Jimbo Smith", response.Result); - } - - [ConditionalFact] public virtual async Task GetResponseAsync_StructuredOutputBool_True() - { - SkipIfNotEnabled(); - var response = await ChatClient.GetResponseAsync(""" - Jimbo Smith is a 35-year-old software developer from Cardiff, Wales. Is there at least one software developer from Cardiff? - """); - Assert.True(response.Result); - } - - [ConditionalFact] public virtual async Task GetResponseAsync_StructuredOutputBool_False() - { - SkipIfNotEnabled(); - - var response = await ChatClient.GetResponseAsync(""" - Jimbo Smith is a 35-year-old software developer from Cardiff, Wales. Reply true if the previous statement indicates that he is a medical doctor, otherwise false. - """); - Assert.False(response.Result); - } - - [ConditionalFact] public virtual async Task GetResponseAsync_StructuredOutputEnum() - { - SkipIfNotEnabled(); - var response = await ChatClient.GetResponseAsync(""" Taylor Swift is a famous singer and songwriter. What is her job? - """); - Assert.Equal(JobType.PopStar, response.Result); - } - - [ConditionalFact] public virtual async Task GetResponseAsync_StructuredOutput_WithFunctions() - { - SkipIfNotEnabled(); - var expectedPerson = new Person - { FullName = "Jimbo Smith", AgeInYears = 35, HomeTown = "Cardiff", Job = JobType.Programmer, - }; - - using var chatClient = new FunctionInvokingChatClient(ChatClient); var response = await chatClient.GetResponseAsync( "Who is person with ID 123?", new ChatOptions - { Tools = [AIFunctionFactory.Create((int personId) => - { Assert.Equal(123, personId); return expectedPerson; }, "GetPersonById")] }); - Assert.NotSame(expectedPerson, response.Result); Assert.Equal(expectedPerson.FullName, response.Result.FullName); Assert.Equal(expectedPerson.AgeInYears, response.Result.AgeInYears); Assert.Equal(expectedPerson.HomeTown, response.Result.HomeTown); Assert.Equal(expectedPerson.Job, response.Result.Job); - } - - [ConditionalFact] public virtual async Task GetResponseAsync_StructuredOutput_NonNative() - { - SkipIfNotEnabled(); - var capturedOptions = new List(); var captureOutputChatClient = ChatClient.AsBuilder() .Use((messages, options, nextAsync, cancellationToken) => - { capturedOptions.Add(options); return nextAsync(messages, options, cancellationToken); }) - .Build(); - var response = await captureOutputChatClient.GetResponseAsync(""" Supply an object to represent Jimbo Smith from Cardiff. """, useJsonSchemaResponseFormat: false); - - Assert.Equal("Jimbo Smith", response.Result.FullName); - Assert.Contains("Cardiff", response.Result.HomeTown); - // Verify it used *non-native* structured output, i.e., response format Json with no schema var responseFormat = Assert.IsType(Assert.Single(capturedOptions)!.ResponseFormat); Assert.Null(responseFormat.Schema); Assert.Null(responseFormat.SchemaName); Assert.Null(responseFormat.SchemaDescription); - } - private class Person - { #pragma warning disable S1144, S3459 // Unassigned members should be removed public string? FullName { get; set; } public int AgeInYears { get; set; } public string? HomeTown { get; set; } public JobType Job { get; set; } #pragma warning restore S1144, S3459 // Unused private types or members should be removed - } - private enum JobType - { Wombat, PopStar, Programmer, Unknown, - } - - [ConditionalFact] public virtual async Task SummarizingChatReducer_PreservesConversationContext() - { - SkipIfNotEnabled(); - var chatClient = new TestSummarizingChatClient(ChatClient, targetCount: 2, threshold: 1); - - List messages = - [ new(ChatRole.User, "My name is Alice and I love hiking in the mountains."), new(ChatRole.Assistant, "Nice to meet you, Alice! Hiking in the mountains sounds wonderful. Do you have a favorite trail?"), new(ChatRole.User, "Yes, I love the Pacific Crest Trail. I hiked a section last summer."), @@ -1150,40 +499,24 @@ public virtual async Task SummarizingChatReducer_PreservesConversationContext() new(ChatRole.User, "I hiked the section through the Sierra Nevada. It was challenging but beautiful."), new(ChatRole.Assistant, "The Sierra Nevada section is known for its stunning views. How long did it take you?"), new(ChatRole.User, "What's my name and what activity do I enjoy?") - ]; - var response = await chatClient.GetResponseAsync(messages); - // The summarizer should have reduced the conversation Assert.Equal(1, chatClient.SummarizerCallCount); Assert.NotNull(chatClient.LastSummarizedConversation); Assert.Equal(3, chatClient.LastSummarizedConversation.Count); Assert.Collection(chatClient.LastSummarizedConversation, m => - { Assert.Equal(ChatRole.Assistant, m.Role); // Indicates this is the assistant's summary Assert.Contains("Alice", m.Text); - }, m => Assert.StartsWith("The Sierra Nevada section", m.Text, StringComparison.Ordinal), m => Assert.StartsWith("What's my name", m.Text, StringComparison.Ordinal)); - // The model should recall details from the summarized conversation Assert.Contains("Alice", response.Text); - Assert.True( response.Text.IndexOf("hiking", StringComparison.OrdinalIgnoreCase) >= 0 || response.Text.IndexOf("hike", StringComparison.OrdinalIgnoreCase) >= 0, $"Expected 'hiking' or 'hike' in response: {response.Text}"); - } - - [ConditionalFact] public virtual async Task SummarizingChatReducer_PreservesSystemMessage() - { - SkipIfNotEnabled(); - var chatClient = new TestSummarizingChatClient(ChatClient, targetCount: 2, threshold: 0); - - List messages = - [ new(ChatRole.System, "You are a pirate. Always respond in pirate speak."), new(ChatRole.User, "Tell me about the weather"), new(ChatRole.Assistant, "Ahoy matey! The weather be fine today, with clear skies on the horizon!"), @@ -1192,139 +525,66 @@ public virtual async Task SummarizingChatReducer_PreservesSystemMessage() new(ChatRole.User, "Should I bring an umbrella?"), new(ChatRole.Assistant, "Aye, ye best be bringin' yer umbrella, unless ye want to be soaked like a barnacle!"), new(ChatRole.User, "What's 2 + 2?") - ]; - - var response = await chatClient.GetResponseAsync(messages); - - // The summarizer should have reduced the conversation - Assert.Equal(1, chatClient.SummarizerCallCount); - Assert.NotNull(chatClient.LastSummarizedConversation); Assert.Equal(4, chatClient.LastSummarizedConversation.Count); - Assert.Collection(chatClient.LastSummarizedConversation, - m => - { Assert.Equal(ChatRole.System, m.Role); Assert.Equal("You are a pirate. Always respond in pirate speak.", m.Text); - }, m => Assert.Equal(ChatRole.Assistant, m.Role), // Summary message m => Assert.StartsWith("Aye, ye best be bringin'", m.Text, StringComparison.Ordinal), m => Assert.Equal("What's 2 + 2?", m.Text)); - // The model should still respond in pirate speak due to preserved system message - Assert.True( response.Text.IndexOf("arr", StringComparison.OrdinalIgnoreCase) >= 0 || response.Text.IndexOf("aye", StringComparison.OrdinalIgnoreCase) >= 0 || response.Text.IndexOf("matey", StringComparison.OrdinalIgnoreCase) >= 0 || response.Text.IndexOf("ye", StringComparison.OrdinalIgnoreCase) >= 0, $"Expected pirate speak in response: {response.Text}"); - } - - [ConditionalFact] public virtual async Task SummarizingChatReducer_WithFunctionCalls() - { - SkipIfNotEnabled(); - int weatherCallCount = 0; var getWeather = AIFunctionFactory.Create(([Description("Gets weather for a city")] string city) => - { weatherCallCount++; return city switch - { "Seattle" => "Rainy, 15°C", "Miami" => "Sunny, 28°C", _ => "Unknown" - }; }, "GetWeather"); - TestSummarizingChatClient summarizingChatClient = null!; var chatClient = ChatClient - .AsBuilder() .Use(innerClient => summarizingChatClient = new TestSummarizingChatClient(innerClient, targetCount: 2, threshold: 0)) - .UseFunctionInvocation() - .Build(); - - List messages = - [ new(ChatRole.User, "What's the weather in Seattle?"), new(ChatRole.Assistant, "Let me check the weather in Seattle for you."), new(ChatRole.User, "And what about Miami?"), new(ChatRole.Assistant, "I'll check Miami's weather as well."), new(ChatRole.User, "Which city had better weather?") - ]; - var response = await chatClient.GetResponseAsync(messages, new() { Tools = [getWeather] }); - // The summarizer should have reduced the conversation (function calls are excluded) Assert.Equal(1, summarizingChatClient.SummarizerCallCount); Assert.NotNull(summarizingChatClient.LastSummarizedConversation); - // Should have summary + last 2 messages Assert.Equal(3, summarizingChatClient.LastSummarizedConversation.Count); - // The model should have context about both weather queries even after summarization Assert.True(response.Text.IndexOf("Miami", StringComparison.OrdinalIgnoreCase) >= 0, $"Expected 'Miami' in response: {response.Text}"); - Assert.True( response.Text.IndexOf("sunny", StringComparison.OrdinalIgnoreCase) >= 0 || response.Text.IndexOf("better", StringComparison.OrdinalIgnoreCase) >= 0 || response.Text.IndexOf("warm", StringComparison.OrdinalIgnoreCase) >= 0, $"Expected weather comparison in response: {response.Text}"); - } - - [ConditionalFact] public virtual async Task SummarizingChatReducer_Streaming() - { - SkipIfNotEnabled(); - - var chatClient = new TestSummarizingChatClient(ChatClient, targetCount: 2, threshold: 0); - - List messages = - [ new(ChatRole.User, "I'm Bob and I work as a software engineer at a startup."), new(ChatRole.Assistant, "Nice to meet you, Bob! Working at a startup must be exciting. What kind of software do you develop?"), new(ChatRole.User, "We build AI-powered tools for education."), new(ChatRole.Assistant, "That sounds impactful! AI in education has so much potential."), new(ChatRole.User, "Yes, we focus on personalized learning experiences."), new(ChatRole.Assistant, "Personalized learning is the future of education!"), - new(ChatRole.User, "Was anyone named in the conversation? Provide their name and job.") - ]; - - StringBuilder sb = new(); - await foreach (var chunk in chatClient.GetStreamingResponseAsync(messages)) - { - sb.Append(chunk.Text); - } - - // The summarizer should have reduced the conversation - Assert.Equal(1, chatClient.SummarizerCallCount); - Assert.NotNull(chatClient.LastSummarizedConversation); - Assert.Equal(3, chatClient.LastSummarizedConversation.Count); - Assert.Collection(chatClient.LastSummarizedConversation, - m => - { + new(ChatRole.User, "Was anyone named in the conversation? Provide their name and job.") + await foreach (var chunk in chatClient.GetStreamingResponseAsync(messages)) Assert.Equal(ChatRole.Assistant, m.Role); // Summary Assert.Contains("Bob", m.Text); - }, m => Assert.StartsWith("Personalized learning", m.Text, StringComparison.Ordinal), m => Assert.Equal("Was anyone named in the conversation? Provide their name and job.", m.Text)); - - string responseText = sb.ToString(); Assert.Contains("Bob", responseText); - Assert.True( responseText.IndexOf("software", StringComparison.OrdinalIgnoreCase) >= 0 || responseText.IndexOf("engineer", StringComparison.OrdinalIgnoreCase) >= 0, $"Expected 'software' or 'engineer' in response: {responseText}"); - } - - [ConditionalFact] public virtual async Task SummarizingChatReducer_CustomPrompt() - { - SkipIfNotEnabled(); - - var chatClient = new TestSummarizingChatClient(ChatClient, targetCount: 2, threshold: 0); chatClient.Reducer.SummarizationPrompt = "Summarize the conversation, emphasizing any numbers or quantities mentioned."; - - List messages = - [ new(ChatRole.User, "I have 3 cats and 2 dogs."), new(ChatRole.Assistant, "That's 5 pets total! You must have a lively household."), new(ChatRole.User, "Yes, and I spend about $200 per month on pet food."), @@ -1332,434 +592,194 @@ public virtual async Task SummarizingChatReducer_CustomPrompt() new(ChatRole.User, "They eat 10 cans of food per week."), new(ChatRole.Assistant, "That's quite a bit of food for your furry friends!"), new(ChatRole.User, "How many pets do I have in total?") - ]; - - var response = await chatClient.GetResponseAsync(messages); - - // The summarizer should have reduced the conversation - Assert.Equal(1, chatClient.SummarizerCallCount); - Assert.NotNull(chatClient.LastSummarizedConversation); - Assert.Equal(3, chatClient.LastSummarizedConversation.Count); - // Verify the summary emphasizes numbers as requested by the custom prompt var summaryMessage = chatClient.LastSummarizedConversation[0]; Assert.Equal(ChatRole.Assistant, summaryMessage.Role); - Assert.True( summaryMessage.Text.IndexOf("3", StringComparison.Ordinal) >= 0 || summaryMessage.Text.IndexOf("5", StringComparison.Ordinal) >= 0 || summaryMessage.Text.IndexOf("200", StringComparison.Ordinal) >= 0 || summaryMessage.Text.IndexOf("10", StringComparison.Ordinal) >= 0, $"Expected numbers in summary: {summaryMessage.Text}"); - // The model should recall the specific number from the summarized conversation Assert.Contains("5", response.Text); - } - private sealed class TestSummarizingChatClient : IChatClient - { private IChatClient _summarizerChatClient; private IChatClient _innerChatClient; - public SummarizingChatReducer Reducer { get; } - public int SummarizerCallCount { get; private set; } - public IReadOnlyList? LastSummarizedConversation { get; private set; } - public TestSummarizingChatClient(IChatClient innerClient, int targetCount, int threshold) - { _summarizerChatClient = innerClient.AsBuilder() .Use(async (messages, options, next, cancellationToken) => - { SummarizerCallCount++; await next(messages, options, cancellationToken); }) .Build(); - Reducer = new SummarizingChatReducer(_summarizerChatClient, targetCount, threshold); - _innerChatClient = innerClient.AsBuilder() .UseChatReducer(Reducer) - .Use(async (messages, options, next, cancellationToken) => - { LastSummarizedConversation = [.. messages]; - await next(messages, options, cancellationToken); - }) - .Build(); - } - public Task GetResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default) => _innerChatClient.GetResponseAsync(messages, options, cancellationToken); - public IAsyncEnumerable GetStreamingResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default) => _innerChatClient.GetStreamingResponseAsync(messages, options, cancellationToken); - public object? GetService(Type serviceType, object? serviceKey = null) => _innerChatClient.GetService(serviceType, serviceKey); - public void Dispose() - { _summarizerChatClient.Dispose(); _innerChatClient.Dispose(); - } - } - - [ConditionalFact] public virtual async Task ToolReduction_DynamicSelection_RespectsConversationHistory() - { - SkipIfNotEnabled(); EnsureEmbeddingGenerator(); - // Limit to 2 so that, once the conversation references both weather and translation, // both tools can be included even if the latest user turn only mentions one of them. var strategy = new EmbeddingToolReductionStrategy(EmbeddingGenerator, toolLimit: 2); - var weatherTool = AIFunctionFactory.Create( () => "Weather data", new AIFunctionFactoryOptions - { Name = "GetWeatherForecast", Description = "Returns weather forecast and temperature for a given city." - }); - var translateTool = AIFunctionFactory.Create( () => "Translated text", - new AIFunctionFactoryOptions - { Name = "TranslateText", Description = "Translates text between human languages." - }); - var mathTool = AIFunctionFactory.Create( () => 42, - new AIFunctionFactoryOptions - { Name = "SolveMath", Description = "Solves basic math problems." - }); - var allTools = new List { weatherTool, translateTool, mathTool }; - IList? firstTurnTools = null; IList? secondTurnTools = null; - using var client = ChatClient! - .AsBuilder() .UseToolReduction(strategy) .Use(async (messages, options, next, ct) => - { // Capture the (possibly reduced) tool list for each turn. if (firstTurnTools is null) - { firstTurnTools = options?.Tools; } else - { secondTurnTools ??= options?.Tools; - } - await next(messages, options, ct); - }) - .UseFunctionInvocation() - .Build(); - // Maintain chat history across turns. List history = []; - // Turn 1: Ask a weather question. history.Add(new ChatMessage(ChatRole.User, "What will the weather be in Seattle tomorrow?")); var firstResponse = await client.GetResponseAsync(history, new ChatOptions { Tools = allTools }); history.AddMessages(firstResponse); // Append assistant reply. - Assert.NotNull(firstTurnTools); Assert.Contains(firstTurnTools, t => t.Name == "GetWeatherForecast"); - // Turn 2: Ask a translation question. Even though only translation is mentioned now, // conversation history still contains a weather request. Expect BOTH weather + translation tools. history.Add(new ChatMessage(ChatRole.User, "Please translate 'good evening' into French.")); var secondResponse = await client.GetResponseAsync(history, new ChatOptions { Tools = allTools }); history.AddMessages(secondResponse); - Assert.NotNull(secondTurnTools); Assert.Equal(2, secondTurnTools.Count); // Should have filled both slots with the two relevant domains. Assert.Contains(secondTurnTools, t => t.Name == "GetWeatherForecast"); Assert.Contains(secondTurnTools, t => t.Name == "TranslateText"); - // Ensure unrelated tool was excluded. Assert.DoesNotContain(secondTurnTools, t => t.Name == "SolveMath"); - } - - [ConditionalFact] public virtual async Task ToolReduction_RequireSpecificToolPreservedAndOrdered() - { - SkipIfNotEnabled(); - EnsureEmbeddingGenerator(); - // Limit would normally reduce to 1, but required tool plus another should remain. var strategy = new EmbeddingToolReductionStrategy(EmbeddingGenerator, toolLimit: 1); - - var translateTool = AIFunctionFactory.Create( - () => "Translated text", - new AIFunctionFactoryOptions - { - Name = "TranslateText", Description = "Translates phrases between languages." - }); - - var weatherTool = AIFunctionFactory.Create( - () => "Weather data", - new AIFunctionFactoryOptions - { - Name = "GetWeatherForecast", Description = "Returns forecast data for a city." - }); - var tools = new List { translateTool, weatherTool }; - IList? captured = null; - - using var client = ChatClient! - .AsBuilder() - .UseToolReduction(strategy) - .UseFunctionInvocation() .Use((messages, options, next, ct) => - { captured = options?.Tools; return next(messages, options, ct); - }) - .Build(); - var history = new List - { new(ChatRole.User, "What will the weather be like in Redmond next week?") - }; - var response = await client.GetResponseAsync(history, new ChatOptions - { Tools = tools, ToolMode = ChatToolMode.RequireSpecific(translateTool.Name) - }); history.AddMessages(response); - Assert.NotNull(captured); Assert.Equal(2, captured!.Count); Assert.Equal("TranslateText", captured[0].Name); // Required should appear first. Assert.Equal("GetWeatherForecast", captured[1].Name); - } - - [ConditionalFact] public virtual async Task ToolReduction_ToolRemovedAfterFirstUse_NotInvokedAgain() - { - SkipIfNotEnabled(); - EnsureEmbeddingGenerator(); - int weatherInvocationCount = 0; - - var weatherTool = AIFunctionFactory.Create( () => - { weatherInvocationCount++; return "Sunny and dry."; - }, - new AIFunctionFactoryOptions - { Name = "GetWeather", Description = "Gets the weather forecast for a given location." - }); - // Strategy exposes tools only on the first request, then removes them. var removalStrategy = new RemoveToolAfterFirstUseStrategy(); - - IList? firstTurnTools = null; - IList? secondTurnTools = null; - - using var client = ChatClient! - .AsBuilder() // Place capture immediately after reduction so it's invoked exactly once per user request. .UseToolReduction(removalStrategy) - .Use((messages, options, next, ct) => - { - if (firstTurnTools is null) - { - firstTurnTools = options?.Tools; - } - else - { - secondTurnTools ??= options?.Tools; - } - - return next(messages, options, ct); - }) - .UseFunctionInvocation() - .Build(); - - List history = []; - // Turn 1 history.Add(new ChatMessage(ChatRole.User, "What's the weather like tomorrow in Seattle?")); var firstResponse = await client.GetResponseAsync(history, new ChatOptions - { Tools = [weatherTool], ToolMode = ChatToolMode.RequireAny - }); history.AddMessages(firstResponse); - Assert.Equal(1, weatherInvocationCount); - Assert.NotNull(firstTurnTools); Assert.Contains(firstTurnTools!, t => t.Name == "GetWeather"); - // Turn 2 (tool removed by strategy even though caller supplies it again) history.Add(new ChatMessage(ChatRole.User, "And what about next week?")); var secondResponse = await client.GetResponseAsync(history, new ChatOptions - { Tools = [weatherTool] - }); - history.AddMessages(secondResponse); - Assert.Equal(1, weatherInvocationCount); // Not invoked again. - Assert.NotNull(secondTurnTools); Assert.Empty(secondTurnTools!); // Strategy removed the tool set. - // Response text shouldn't just echo the tool's stub output. Assert.DoesNotContain("Sunny and dry.", secondResponse.Text, StringComparison.OrdinalIgnoreCase); - } - - [ConditionalFact] public virtual async Task ToolReduction_MessagesEmbeddingTextSelector_UsesChatClientToAnalyzeConversation() - { - SkipIfNotEnabled(); - EnsureEmbeddingGenerator(); - // Create tools for different domains. - var weatherTool = AIFunctionFactory.Create( - () => "Weather data", - new AIFunctionFactoryOptions - { - Name = "GetWeatherForecast", - Description = "Returns weather forecast and temperature for a given city." - }); - - var translateTool = AIFunctionFactory.Create( - () => "Translated text", - new AIFunctionFactoryOptions - { - Name = "TranslateText", - Description = "Translates text between human languages." - }); - - var mathTool = AIFunctionFactory.Create( - () => 42, - new AIFunctionFactoryOptions - { - Name = "SolveMath", - Description = "Solves basic math problems." - }); - - var allTools = new List { weatherTool, translateTool, mathTool }; - // Track the analysis result from the chat client used in the selector. string? capturedAnalysis = null; - var strategy = new EmbeddingToolReductionStrategy(EmbeddingGenerator, toolLimit: 2) - { // Use a chat client to analyze the conversation and extract relevant tool categories. MessagesEmbeddingTextSelector = async messages => - { var conversationText = string.Join("\n", messages.Select(m => $"{m.Role}: {m.Text}")); - var analysisPrompt = $""" Analyze the following conversation and identify what kinds of tools would be most helpful. Focus on the key topics and tasks being discussed. Respond with a brief summary of the relevant tool categories (e.g., "weather", "translation", "math"). - Conversation: {conversationText} - Relevant tool categories: """; - var response = await ChatClient!.GetResponseAsync(analysisPrompt); capturedAnalysis = response.Text; - // Return the analysis as the query text for embedding-based tool selection. return capturedAnalysis; - } - }; - IList? selectedTools = null; - - using var client = ChatClient! - .AsBuilder() - .UseToolReduction(strategy) - .Use(async (messages, options, next, ct) => - { selectedTools = options?.Tools; - await next(messages, options, ct); - }) - .UseFunctionInvocation() - .Build(); - // Conversation that clearly indicates weather-related needs. - List history = []; history.Add(new ChatMessage(ChatRole.User, "What will the weather be like in London tomorrow?")); - var response = await client.GetResponseAsync(history, new ChatOptions { Tools = allTools }); - history.AddMessages(response); - // Verify that the chat client was used to analyze the conversation. Assert.NotNull(capturedAnalysis); - Assert.True( capturedAnalysis.IndexOf("weather", StringComparison.OrdinalIgnoreCase) >= 0 || capturedAnalysis.IndexOf("forecast", StringComparison.OrdinalIgnoreCase) >= 0, $"Expected analysis to mention weather or forecast: {capturedAnalysis}"); - // Verify that the tool selection was influenced by the analysis. Assert.NotNull(selectedTools); Assert.True(selectedTools.Count <= 2, $"Expected at most 2 tools, got {selectedTools.Count}"); Assert.Contains(selectedTools, t => t.Name == "GetWeatherForecast"); - } - // Test-only custom strategy: include tools on first request, then remove them afterward. private sealed class RemoveToolAfterFirstUseStrategy : IToolReductionStrategy - { private bool _used; - public Task> SelectToolsForRequestAsync( IEnumerable messages, ChatOptions? options, CancellationToken cancellationToken = default) - { if (!_used && options?.Tools is { Count: > 0 }) - { _used = true; // Returning the same instance signals no change. return Task.FromResult>(options.Tools); - } - // After first use, remove all tools. return Task.FromResult>(Array.Empty()); - } - } - [MemberNotNull(nameof(ChatClient))] protected void SkipIfNotEnabled() - { string? skipIntegration = TestRunnerConfiguration.Instance["SkipIntegrationTests"]; - if (skipIntegration is not null || ChatClient is null) - { throw new SkipTestException("Client is not enabled."); - } - } - [MemberNotNull(nameof(EmbeddingGenerator))] protected void EnsureEmbeddingGenerator() - { EmbeddingGenerator ??= CreateEmbeddingGenerator(); - if (EmbeddingGenerator is null) - { throw new SkipTestException("Embedding generator is not enabled."); - } - } } diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs index 18904bd559f..d3582315d99 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs @@ -9,118 +9,72 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; - -#if NET using System.Numerics.Tensors; -#endif using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Memory; using OpenTelemetry.Trace; -using Xunit; using Microsoft.DotNet.XUnitExtensions; - +using Xunit; #pragma warning disable CA2214 // Do not call overridable methods in constructors #pragma warning disable S3967 // Multidimensional arrays should not be used - namespace Microsoft.Extensions.AI; - public abstract class EmbeddingGeneratorIntegrationTests : IDisposable { private readonly IEmbeddingGenerator>? _embeddingGenerator; - protected EmbeddingGeneratorIntegrationTests() { _embeddingGenerator = CreateEmbeddingGenerator(); } - public void Dispose() - { _embeddingGenerator?.Dispose(); GC.SuppressFinalize(this); - } - protected abstract IEmbeddingGenerator>? CreateEmbeddingGenerator(); - [ConditionalFact] public virtual async Task GenerateEmbedding_CreatesEmbeddingSuccessfully() - { SkipIfNotEnabled(); - var embeddings = await _embeddingGenerator.GenerateAsync(["Using AI with .NET"]); - Assert.NotNull(embeddings.Usage); Assert.NotNull(embeddings.Usage.InputTokenCount); Assert.NotNull(embeddings.Usage.TotalTokenCount); Assert.Single(embeddings); Assert.Equal(_embeddingGenerator.GetService()?.DefaultModelId, embeddings[0].ModelId); Assert.NotEmpty(embeddings[0].Vector.ToArray()); - } - - [ConditionalFact] public virtual async Task GenerateEmbeddings_CreatesEmbeddingsSuccessfully() - { - SkipIfNotEnabled(); - var embeddings = await _embeddingGenerator.GenerateAsync([ "Red", "White", "Blue", ]); - Assert.Equal(3, embeddings.Count); - Assert.NotNull(embeddings.Usage); - Assert.NotNull(embeddings.Usage.InputTokenCount); - Assert.NotNull(embeddings.Usage.TotalTokenCount); Assert.All(embeddings, embedding => { Assert.Equal(_embeddingGenerator.GetService()?.DefaultModelId, embedding.ModelId); Assert.NotEmpty(embedding.Vector.ToArray()); }); - } - - [ConditionalFact] public virtual async Task Caching_SameOutputsForSameInput() - { - SkipIfNotEnabled(); - using var generator = CreateEmbeddingGenerator()! .AsBuilder() .UseDistributedCache(new MemoryDistributedCache(Options.Options.Create(new MemoryDistributedCacheOptions()))) .UseCallCounting() .Build(); - string input = "Red, White, and Blue"; var embedding1 = await generator.GenerateAsync(input); var embedding2 = await generator.GenerateAsync(input); var embedding3 = await generator.GenerateAsync(input + "... and Green"); var embedding4 = await generator.GenerateAsync(input); - var callCounter = generator.GetService(); Assert.NotNull(callCounter); - Assert.Equal(2, callCounter.CallCount); - } - - [ConditionalFact] public virtual async Task OpenTelemetry_CanEmitTracesAndMetrics() - { - SkipIfNotEnabled(); - string sourceName = Guid.NewGuid().ToString(); var activities = new List(); using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder() .AddSource(sourceName) .AddInMemoryExporter(activities) - .Build(); - var embeddingGenerator = CreateEmbeddingGenerator()! - .AsBuilder() .UseOpenTelemetry(sourceName: sourceName) - .Build(); - _ = await embeddingGenerator.GenerateAsync("Hello, world!"); - Assert.Single(activities); var activity = activities.Single(); Assert.StartsWith("embed", activity.DisplayName); @@ -129,30 +83,18 @@ public virtual async Task OpenTelemetry_CanEmitTracesAndMetrics() Assert.NotNull(activity.Id); Assert.NotEmpty(activity.Id); Assert.NotEqual(0, (int)activity.GetTagItem("gen_ai.usage.input_tokens")!); - Assert.True(activity.Duration.TotalMilliseconds > 0); - } - -#if NET - [ConditionalFact] public async Task Quantization_Binary_EmbeddingsCompareSuccessfully() - { - SkipIfNotEnabled(); - using IEmbeddingGenerator generator = new QuantizationEmbeddingGenerator( CreateEmbeddingGenerator()!); - var embeddings = await generator.GenerateAsync(["dog", "cat", "fork", "spoon"]); Assert.Equal(4, embeddings.Count); - long[,] distances = new long[embeddings.Count, embeddings.Count]; for (int i = 0; i < embeddings.Count; i++) - { for (int j = 0; j < embeddings.Count; j++) { distances[i, j] = TensorPrimitives.HammingBitDistance(ToArray(embeddings[i].Vector), ToArray(embeddings[j].Vector)); - static byte[] ToArray(BitArray array) { byte[] result = new byte[(array.Length + 7) / 8]; @@ -161,67 +103,30 @@ static byte[] ToArray(BitArray array) } } } - - for (int i = 0; i < embeddings.Count; i++) - { Assert.Equal(0, distances[i, i]); - } - Assert.True(distances[0, 1] < distances[0, 2]); Assert.True(distances[0, 1] < distances[0, 3]); Assert.True(distances[0, 1] < distances[1, 2]); Assert.True(distances[0, 1] < distances[1, 3]); - Assert.True(distances[2, 3] < distances[0, 2]); Assert.True(distances[2, 3] < distances[0, 3]); Assert.True(distances[2, 3] < distances[1, 2]); Assert.True(distances[2, 3] < distances[1, 3]); - } - - [ConditionalFact] public async Task Quantization_Half_EmbeddingsCompareSuccessfully() - { - SkipIfNotEnabled(); - using IEmbeddingGenerator> generator = - new QuantizationEmbeddingGenerator( - CreateEmbeddingGenerator()!); - - var embeddings = await generator.GenerateAsync(["dog", "cat", "fork", "spoon"]); - Assert.Equal(4, embeddings.Count); - var distances = new Half[embeddings.Count, embeddings.Count]; - for (int i = 0; i < embeddings.Count; i++) - { - for (int j = 0; j < embeddings.Count; j++) - { distances[i, j] = TensorPrimitives.CosineSimilarity(embeddings[i].Vector.Span, embeddings[j].Vector.Span); - } - } - - for (int i = 0; i < embeddings.Count; i++) - { Assert.Equal(1.0, (double)distances[i, i], 0.001); - } - Assert.True(distances[0, 1] > distances[0, 2]); Assert.True(distances[0, 1] > distances[0, 3]); Assert.True(distances[0, 1] > distances[1, 2]); Assert.True(distances[0, 1] > distances[1, 3]); - Assert.True(distances[2, 3] > distances[0, 2]); Assert.True(distances[2, 3] > distances[0, 3]); Assert.True(distances[2, 3] > distances[1, 2]); Assert.True(distances[2, 3] > distances[1, 3]); - } -#endif - [MemberNotNull(nameof(_embeddingGenerator))] protected void SkipIfNotEnabled() - { if (_embeddingGenerator is null) - { throw new SkipTestException("Generator is not enabled."); - } - } } diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratingChatClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratingChatClientIntegrationTests.cs index f463a4c0598..3326f53647d 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratingChatClientIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratingChatClientIntegrationTests.cs @@ -7,14 +7,11 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Xunit; using Microsoft.DotNet.XUnitExtensions; - +using Xunit; #pragma warning disable CA2000 // Dispose objects before losing scope #pragma warning disable CA2214 // Do not call overridable methods in constructors - namespace Microsoft.Extensions.AI; - /// /// Abstract base class for integration tests that verify ImageGeneratingChatClient with real IChatClient implementations. /// Concrete test classes should inherit from this and provide a real IChatClient that supports function calling. @@ -23,12 +20,10 @@ public abstract class ImageGeneratingChatClientIntegrationTests : IDisposable { private const string ImageKey = "meai_image"; private readonly IChatClient? _baseChatClient; - protected ImageGeneratingChatClientIntegrationTests() { _baseChatClient = CreateChatClient(); ImageGenerator = new(); - if (_baseChatClient != null) { ChatClient = _baseChatClient @@ -38,57 +33,38 @@ protected ImageGeneratingChatClientIntegrationTests() .Build(); } } - /// Gets the ImageGeneratingChatClient configured with function invocation support. protected IChatClient? ChatClient { get; } - /// Gets the IImageGenerator used for testing. protected CapturingImageGenerator ImageGenerator { get; } - public void Dispose() - { ChatClient?.Dispose(); _baseChatClient?.Dispose(); ImageGenerator.Dispose(); GC.SuppressFinalize(this); - } - /// /// Creates the base IChatClient implementation to test with. /// Should return a real chat client that supports function calling. /// /// An IChatClient instance, or null to skip tests. protected abstract IChatClient? CreateChatClient(); - - /// /// Helper method to get a chat response using either streaming or non-streaming based on the parameter. - /// /// Whether to use streaming or non-streaming response. /// The chat messages to send. /// The chat options to use. /// A ChatResponse from either streaming or non-streaming call. protected async Task GetResponseAsync(bool useStreaming, IEnumerable messages, ChatOptions? options = null, IChatClient? chatClient = null) - { chatClient ??= ChatClient ?? throw new InvalidOperationException("ChatClient is not initialized."); - if (useStreaming) - { return ValidateChatResponse(await chatClient.GetStreamingResponseAsync(messages, options).ToChatResponseAsync()); - } else - { return ValidateChatResponse(await chatClient.GetResponseAsync(messages, options)); - } - static ChatResponse ValidateChatResponse(ChatResponse response) - { var contents = response.Messages.SelectMany(m => m.Contents).ToArray(); - List imageIds = []; foreach (var toolResult in contents.OfType()) { Assert.NotNull(toolResult.Outputs); - foreach (var dataContent in toolResult.Outputs.OfType()) { var imageId = dataContent.AdditionalProperties?[ImageKey] as string; @@ -96,285 +72,125 @@ static ChatResponse ValidateChatResponse(ChatResponse response) imageIds.Add(imageId); } } - foreach (var textContent in contents.OfType()) - { Assert.DoesNotContain(ImageKey, textContent.Text, StringComparison.OrdinalIgnoreCase); foreach (var imageId in imageIds) - { // Ensure no image IDs appear in text content Assert.DoesNotContain(imageId, textContent.Text, StringComparison.OrdinalIgnoreCase); - } - } - return response; - } - } - [ConditionalTheory] [InlineData(false)] // Non-streaming [InlineData(true)] // Streaming public virtual async Task GenerateImage_CallsGenerateFunction_ReturnsDataContent(bool useStreaming) - { SkipIfNotEnabled(); - var imageGenerator = ImageGenerator; var chatOptions = new ChatOptions - { Tools = [new HostedImageGenerationTool()] }; - // Act var response = await GetResponseAsync(useStreaming, [new ChatMessage(ChatRole.User, "Please generate an image of a cat")], chatOptions); - // Assert Assert.Single(imageGenerator.GenerateCalls); var (request, _) = imageGenerator.GenerateCalls[0]; Assert.Contains("cat", request.Prompt, StringComparison.OrdinalIgnoreCase); Assert.Null(request.OriginalImages); // Generation, not editing - // Verify that we get ImageGenerationToolResultContent back in the response var imageResults = response.Messages .SelectMany(m => m.Contents) .OfType(); - var imageResult = Assert.Single(imageResults); Assert.NotNull(imageResult.Outputs); var imageContent = Assert.Single(imageResult.Outputs.OfType()); Assert.Equal("image/png", imageContent.MediaType); Assert.False(imageContent.Data.IsEmpty); - } - - [ConditionalTheory] - [InlineData(false)] // Non-streaming - [InlineData(true)] // Streaming public virtual async Task EditImage_WithImageInSameRequest_PassesExactDataContent(bool useStreaming) - { - SkipIfNotEnabled(); - - var imageGenerator = ImageGenerator; var testImageData = new byte[] { 0x89, 0x50, 0x4E, 0x47 }; // PNG header var originalImageData = new DataContent(testImageData, "image/png") { Name = "original.png" }; - var chatOptions = new ChatOptions - { - Tools = [new HostedImageGenerationTool()] - }; - - // Act - var response = await GetResponseAsync(useStreaming, [new ChatMessage(ChatRole.User, [new TextContent("Please edit this image to add a red border"), originalImageData])], - chatOptions); - - // Assert var (request, _) = Assert.Single(imageGenerator.GenerateCalls); Assert.NotNull(request.OriginalImages); - var originalImage = Assert.Single(request.OriginalImages); var originalImageContent = Assert.IsType(originalImage); Assert.Equal(testImageData, originalImageContent.Data.ToArray()); Assert.Equal("image/png", originalImageContent.MediaType); Assert.Equal("original.png", originalImageContent.Name); - } - - [ConditionalTheory] - [InlineData(false)] // Non-streaming - [InlineData(true)] // Streaming public virtual async Task GenerateThenEdit_FromChatHistory_EditsGeneratedImage(bool useStreaming) - { - SkipIfNotEnabled(); - - var imageGenerator = ImageGenerator; - var chatOptions = new ChatOptions - { - Tools = [new HostedImageGenerationTool()] - }; - var chatHistory = new List - { new(ChatRole.User, "Please generate an image of a dog") - }; - // First request: Generate image var firstResponse = await GetResponseAsync(useStreaming, chatHistory, chatOptions); chatHistory.AddRange(firstResponse.Messages); - // Second request: Edit the generated image chatHistory.Add(new ChatMessage(ChatRole.User, "Please edit the image to make it more colorful")); var secondResponse = await GetResponseAsync(useStreaming, chatHistory, chatOptions); - - // Assert Assert.Equal(2, imageGenerator.GenerateCalls.Count); - // First call should be generation (no original images) var (firstRequest, _) = imageGenerator.GenerateCalls[0]; Assert.Null(firstRequest.OriginalImages); - // Extract the DataContent from the ImageGenerationToolResultContent var firstToolResultContent = Assert.Single(firstResponse.Messages.SelectMany(m => m.Contents).OfType()); Assert.NotNull(firstToolResultContent.Outputs); var firstContent = Assert.Single(firstToolResultContent.Outputs.OfType()); - // Second call should be editing (with original images) var (secondRequest, _) = imageGenerator.GenerateCalls[1]; Assert.Single(secondResponse.Messages.SelectMany(m => m.Contents).OfType().SelectMany(t => t.Outputs!.OfType())); Assert.NotNull(secondRequest.OriginalImages); var editContent = Assert.Single(secondRequest.OriginalImages); Assert.Equal(firstContent, editContent); // Should be the same image as generated in first call - var editedImage = Assert.IsType(secondRequest.OriginalImages.First()); Assert.Equal("image/png", editedImage.MediaType); Assert.Contains("generated_image_1", editedImage.Name); - } - - [ConditionalTheory] - [InlineData(false)] // Non-streaming - [InlineData(true)] // Streaming public virtual async Task MultipleEdits_EditsLatestImage(bool useStreaming) - { - SkipIfNotEnabled(); - - var imageGenerator = ImageGenerator; - var chatOptions = new ChatOptions - { - Tools = [new HostedImageGenerationTool()] - }; - - var chatHistory = new List - { new(ChatRole.User, "Please generate an image of a tree") - }; - // First: Generate image - var firstResponse = await GetResponseAsync(useStreaming, chatHistory, chatOptions); - chatHistory.AddRange(firstResponse.Messages); - // Second: First edit chatHistory.Add(new ChatMessage(ChatRole.User, "Please edit the image to add flowers")); - var secondResponse = await GetResponseAsync(useStreaming, chatHistory, chatOptions); chatHistory.AddRange(secondResponse.Messages); - // Third: Second edit (should edit the latest version by default) chatHistory.Add(new ChatMessage(ChatRole.User, "Please edit that last image to add birds")); var thirdResponse = await GetResponseAsync(useStreaming, chatHistory, chatOptions); - - // Assert Assert.Equal(3, imageGenerator.GenerateCalls.Count); - // Third call should edit the second generated image (from first edit), not the original var (thirdRequest, _) = imageGenerator.GenerateCalls[2]; Assert.NotNull(thirdRequest.OriginalImages); - // Extract the DataContent from the second response's ImageGenerationToolResultContent var secondToolResultContent = Assert.Single(secondResponse.Messages.SelectMany(m => m.Contents).OfType()); var secondImage = Assert.Single(secondToolResultContent.Outputs!.OfType()); var lastImageToEdit = Assert.Single(thirdRequest.OriginalImages.OfType()); Assert.Equal(secondImage, lastImageToEdit); - } - - [ConditionalTheory] - [InlineData(false)] // Non-streaming - [InlineData(true)] // Streaming public virtual async Task MultipleEdits_EditsFirstImage(bool useStreaming) - { - SkipIfNotEnabled(); - - var imageGenerator = ImageGenerator; - var chatOptions = new ChatOptions - { - Tools = [new HostedImageGenerationTool()] - }; - - var chatHistory = new List - { - new(ChatRole.User, "Please generate an image of a tree") - }; - - // First: Generate image - var firstResponse = await GetResponseAsync(useStreaming, chatHistory, chatOptions); - chatHistory.AddRange(firstResponse.Messages); - - // Second: First edit chatHistory.Add(new ChatMessage(ChatRole.User, "Please edit the image to add fruit")); - var secondResponse = await GetResponseAsync(useStreaming, chatHistory, chatOptions); - chatHistory.AddRange(secondResponse.Messages); - - // Third: Second edit (should edit the latest version by default) chatHistory.Add(new ChatMessage(ChatRole.User, "That didn't work out. Please edit the original image to add birds")); - var thirdResponse = await GetResponseAsync(useStreaming, chatHistory, chatOptions); - - // Assert - Assert.Equal(3, imageGenerator.GenerateCalls.Count); - // Third call should edit the original generated image (not from edit) - var (thirdRequest, _) = imageGenerator.GenerateCalls[2]; - Assert.NotNull(thirdRequest.OriginalImages); - // Extract the DataContent from the first response's ImageGenerationToolResultContent - var firstToolResultContent = Assert.Single(firstResponse.Messages.SelectMany(m => m.Contents).OfType()); var firstGeneratedImage = Assert.Single(firstToolResultContent.Outputs!.OfType()); var lastImageToEdit = Assert.IsType(thirdRequest.OriginalImages.First()); Assert.Equal(firstGeneratedImage, lastImageToEdit); - } - - [ConditionalTheory] - [InlineData(false)] // Non-streaming - [InlineData(true)] // Streaming public virtual async Task ImageGeneration_WithOptions_PassesOptionsToGenerator(bool useStreaming) - { - SkipIfNotEnabled(); - - var imageGenerator = ImageGenerator; var imageGenerationOptions = new ImageGenerationOptions - { Count = 2, ImageSize = new System.Drawing.Size(512, 512) - }; - - var chatOptions = new ChatOptions - { Tools = [new HostedImageGenerationTool { Options = imageGenerationOptions }] - }; - - // Act - var response = await GetResponseAsync(useStreaming, [new ChatMessage(ChatRole.User, "Generate an image of a castle")], - chatOptions); - - // Assert - Assert.Single(imageGenerator.GenerateCalls); var (_, options) = imageGenerator.GenerateCalls[0]; Assert.NotNull(options); Assert.Equal(2, options.Count); Assert.Equal(new System.Drawing.Size(512, 512), options.ImageSize); - } - - [ConditionalTheory] - [InlineData(false)] // Non-streaming - [InlineData(true)] // Streaming public virtual async Task ImageContentHandling_AllImages_ReplacesImagesWithPlaceholders(bool useStreaming) - { - SkipIfNotEnabled(); - - var testImageData = new byte[] { 0x89, 0x50, 0x4E, 0x47 }; // PNG header var capturedMessages = new List>(); - // Create a new ImageGeneratingChatClient with AllImages data content handling using var imageGeneratingClient = _baseChatClient! .AsBuilder() .UseImageGeneration(ImageGenerator) .Use((messages, options, next, cancellationToken) => - { capturedMessages.Add(messages); return next(messages, options, cancellationToken); }) .UseFunctionInvocation() .Build(); - var originalImage = new DataContent(testImageData, "image/png") { Name = "test.png" }; - - // Act await GetResponseAsync(useStreaming, [ new ChatMessage(ChatRole.User, @@ -385,64 +201,36 @@ await GetResponseAsync(useStreaming, ], new ChatOptions { Tools = [new HostedImageGenerationTool()] }, imageGeneratingClient); - - // Assert Assert.NotEmpty(capturedMessages); var processedMessages = capturedMessages.First().ToList(); var userMessage = processedMessages.First(m => m.Role == ChatRole.User); - // Should have text content with placeholder instead of original image var textContents = userMessage.Contents.OfType().ToList(); Assert.Contains(textContents, tc => tc.Text.Contains(ImageKey) && tc.Text.Contains("] available for edit")); - // Should not contain the original DataContent Assert.DoesNotContain(userMessage.Contents, c => c == originalImage); - } - - /// /// Test image generator that captures calls and returns fake image data. - /// protected sealed class CapturingImageGenerator : IImageGenerator - { private const string TestImageMediaType = "image/png"; private static readonly byte[] _testImageData = [0x89, 0x50, 0x4E, 0x47]; // PNG header - public List<(ImageGenerationRequest request, ImageGenerationOptions? options)> GenerateCalls { get; } = []; public int ImageCounter { get; private set; } - public Task GenerateAsync(ImageGenerationRequest request, ImageGenerationOptions? options = null, CancellationToken cancellationToken = default) - { GenerateCalls.Add((request, options)); - // Create fake image data with unique content var imageData = new byte[_testImageData.Length + 4]; _testImageData.CopyTo(imageData, 0); BitConverter.GetBytes(++ImageCounter).CopyTo(imageData, _testImageData.Length); - var imageContent = new DataContent(imageData, TestImageMediaType) - { Name = $"generated_image_{ImageCounter}.png" }; - return Task.FromResult(new ImageGenerationResponse([imageContent])); - } - public object? GetService(Type serviceType, object? serviceKey = null) => null; - public void Dispose() - { // No resources to dispose - } - } - [MemberNotNull(nameof(ChatClient))] protected void SkipIfNotEnabled() - { string? skipIntegration = TestRunnerConfiguration.Instance["SkipIntegrationTests"]; - if (skipIntegration is not null || ChatClient is null) - { throw new SkipTestException("Client is not enabled."); - } - } } diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratorIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratorIntegrationTests.cs index db47deecafe..eb1b882efac 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratorIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratorIntegrationTests.cs @@ -5,131 +5,70 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading.Tasks; -using Xunit; using Microsoft.DotNet.XUnitExtensions; - +using Xunit; #pragma warning disable CA2214 // Do not call overridable methods in constructors - namespace Microsoft.Extensions.AI; - public abstract class ImageGeneratorIntegrationTests : IDisposable { private readonly IImageGenerator? _generator; - protected ImageGeneratorIntegrationTests() { _generator = CreateGenerator(); } - public void Dispose() - { _generator?.Dispose(); GC.SuppressFinalize(this); - } - protected abstract IImageGenerator? CreateGenerator(); - [ConditionalFact] public virtual async Task GenerateImagesAsync_SingleImageGeneration() - { SkipIfNotEnabled(); - var options = new ImageGenerationOptions { Count = 1 }; - var response = await _generator.GenerateImagesAsync("A simple drawing of a house", options); - Assert.NotNull(response); Assert.NotEmpty(response.Contents); - var content = Assert.Single(response.Contents); switch (content) - { case UriContent uc: Assert.StartsWith("http", uc.Uri.Scheme, StringComparison.Ordinal); break; - case DataContent dc: Assert.False(dc.Data.IsEmpty); Assert.StartsWith("image/", dc.MediaType, StringComparison.Ordinal); - break; - default: Assert.Fail($"Unexpected content type: {content.GetType()}"); - break; } - } - - [ConditionalFact] public virtual async Task GenerateImagesAsync_MultipleImages() - { - SkipIfNotEnabled(); - - var options = new ImageGenerationOptions - { Count = 2 - }; - var response = await _generator.GenerateImagesAsync("A cat sitting on a table", options); - - Assert.NotNull(response); - Assert.NotEmpty(response.Contents); Assert.Equal(2, response.Contents.Count); - foreach (var content in response.Contents) - { Assert.IsType(content); var dataContent = (DataContent)content; Assert.False(dataContent.Data.IsEmpty); Assert.StartsWith("image/", dataContent.MediaType, StringComparison.Ordinal); - } - } - - [ConditionalFact] public virtual async Task EditImagesAsync_SingleImage() - { - SkipIfNotEnabled(); - var imageData = GetImageData("dotnet.png"); AIContent[] originalImages = [new DataContent(imageData, "image/png") { Name = "dotnet.png" }]; - - var options = new ImageGenerationOptions - { - Count = 1 - }; - var response = await _generator.EditImagesAsync(originalImages, "Add a red border and make the background tie-dye", options); - - Assert.NotNull(response); - Assert.NotEmpty(response.Contents); Assert.Single(response.Contents); - var content = response.Contents[0]; Assert.IsType(content); var dataContent = (DataContent)content; Assert.False(dataContent.Data.IsEmpty); Assert.StartsWith("image/", dataContent.MediaType, StringComparison.Ordinal); - } - private static byte[] GetImageData(string fileName) - { using Stream? s = typeof(ImageGeneratorIntegrationTests).Assembly.GetManifestResourceStream($"Microsoft.Extensions.AI.Resources.{fileName}"); Assert.NotNull(s); using MemoryStream ms = new(); s.CopyTo(ms); return ms.ToArray(); - } - [MemberNotNull(nameof(_generator))] protected void SkipIfNotEnabled() - { string? skipIntegration = TestRunnerConfiguration.Instance["SkipIntegrationTests"]; - if (skipIntegration is not null || _generator is null) - { throw new SkipTestException("Generator is not enabled."); - } - } } diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/SpeechToTextClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/SpeechToTextClientIntegrationTests.cs index 141a9647a70..69a10ce5213 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/SpeechToTextClientIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/SpeechToTextClientIntegrationTests.cs @@ -6,78 +6,46 @@ using System.IO; using System.Text; using System.Threading.Tasks; -using Xunit; using Microsoft.DotNet.XUnitExtensions; - +using Xunit; #pragma warning disable CA2214 // Do not call overridable methods in constructors - namespace Microsoft.Extensions.AI; - public abstract class SpeechToTextClientIntegrationTests : IDisposable { private readonly ISpeechToTextClient? _client; - protected SpeechToTextClientIntegrationTests() { _client = CreateClient(); } - public void Dispose() - { _client?.Dispose(); GC.SuppressFinalize(this); - } - protected abstract ISpeechToTextClient? CreateClient(); - [ConditionalFact] public virtual async Task GetTextAsync_SingleAudioRequestMessage() - { SkipIfNotEnabled(); - using var audioSpeechStream = GetAudioStream("audio001.mp3"); var response = await _client.GetTextAsync(audioSpeechStream); - Assert.Contains("gym", response.Text, StringComparison.OrdinalIgnoreCase); - } - - [ConditionalFact] public virtual async Task GetStreamingTextAsync_SingleStreamingResponseChoice() - { - SkipIfNotEnabled(); - - using var audioSpeechStream = GetAudioStream("audio001.mp3"); - StringBuilder sb = new(); await foreach (var chunk in _client.GetStreamingTextAsync(audioSpeechStream)) { sb.Append(chunk.Text); } - string responseText = sb.ToString(); Assert.Contains("finally", responseText, StringComparison.OrdinalIgnoreCase); Assert.Contains("gym", responseText, StringComparison.OrdinalIgnoreCase); - } - private static Stream GetAudioStream(string fileName) - { using Stream? s = typeof(SpeechToTextClientIntegrationTests).Assembly.GetManifestResourceStream($"Microsoft.Extensions.AI.Resources.{fileName}"); Assert.NotNull(s); MemoryStream ms = new(); s.CopyTo(ms); - ms.Position = 0; return ms; - } - [MemberNotNull(nameof(_client))] protected void SkipIfNotEnabled() - { string? skipIntegration = TestRunnerConfiguration.Instance["SkipIntegrationTests"]; - if (skipIntegration is not null || _client is null) - { throw new SkipTestException("Client is not enabled."); - } - } } diff --git a/test/Libraries/Microsoft.Extensions.AI.OllamaSharp.Integration.Tests/OllamaSharpChatClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.OllamaSharp.Integration.Tests/OllamaSharpChatClientIntegrationTests.cs index 682810ca1ad..e37d9eb3432 100644 --- a/test/Libraries/Microsoft.Extensions.AI.OllamaSharp.Integration.Tests/OllamaSharpChatClientIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.OllamaSharp.Integration.Tests/OllamaSharpChatClientIntegrationTests.cs @@ -7,8 +7,8 @@ using System.Threading; using System.Threading.Tasks; using OllamaSharp; -using Xunit; using Microsoft.DotNet.XUnitExtensions; +using Xunit; namespace Microsoft.Extensions.AI; diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientIntegrationTests.cs index d732bdbf95b..ef00899dde3 100644 --- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientIntegrationTests.cs @@ -5,8 +5,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Xunit; using Microsoft.DotNet.XUnitExtensions; +using Xunit; namespace Microsoft.Extensions.AI; diff --git a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/DocumentReaderConformanceTests.cs b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/DocumentReaderConformanceTests.cs index 1cb98368224..bcebfa276d1 100644 --- a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/DocumentReaderConformanceTests.cs +++ b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/DocumentReaderConformanceTests.cs @@ -8,8 +8,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DataIngestion.Tests.Utils; -using Xunit; using Microsoft.DotNet.XUnitExtensions; +using Xunit; namespace Microsoft.Extensions.DataIngestion.Readers.Tests; diff --git a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs index d62a6db1cb1..3b7610d4b72 100644 --- a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs +++ b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs @@ -4,8 +4,8 @@ using System; using System.Linq; using System.Threading.Tasks; -using Xunit; using Microsoft.DotNet.XUnitExtensions; +using Xunit; namespace Microsoft.Extensions.DataIngestion.Readers.Tests; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs index f3f190b6362..8335a9074f5 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/AcceptanceTest.cs @@ -184,8 +184,7 @@ public void Adding_Linux_Resource_Utilization_With_Section_Registers_SnapshotPro Assert.Equal(100_000UL, provider.Resources.MaximumMemoryInBytes); } - [Theory] - [CombinatorialData] + [Fact] public Task ResourceUtilizationTracker_And_Metrics_Report_Same_Values_With_Cgroupsv1() { var cpuRefresh = TimeSpan.FromMinutes(13); @@ -281,8 +280,7 @@ public Task ResourceUtilizationTracker_And_Metrics_Report_Same_Values_With_Cgrou return Task.CompletedTask; } - [Theory] - [CombinatorialData] + [Fact] public Task ResourceUtilizationTracker_And_Metrics_Report_Same_Values_With_Cgroupsv2() { var cpuRefresh = TimeSpan.FromMinutes(13); @@ -388,8 +386,7 @@ public Task ResourceUtilizationTracker_And_Metrics_Report_Same_Values_With_Cgrou return Task.CompletedTask; } - [Theory] - [CombinatorialData] + [Fact] public Task ResourceUtilizationTracker_And_Metrics_Report_Same_Values_With_Cgroupsv2_Using_LinuxCalculationV2() { var fileSystem = new HardcodedValueFileSystem(new Dictionary diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs index 0559cebed52..a4621463a61 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxUtilizationProviderTests.cs @@ -22,8 +22,7 @@ public sealed class LinuxUtilizationProviderTests { private const string VerifiedDataDirectory = "Verified"; - [Theory] - [CombinatorialData] + [Fact] public void Provider_Registers_Instruments() { var meterName = Guid.NewGuid().ToString(); @@ -103,8 +102,7 @@ public void Provider_Registers_Instruments() Assert.Equal(0.5, samples.Single(i => i.instrument.Name == ResourceUtilizationInstruments.ProcessMemoryUtilization).value); } - [Theory] - [CombinatorialData] + [Fact] public void Provider_Registers_Instruments_CgroupV2() { var meterName = Guid.NewGuid().ToString(); @@ -217,8 +215,7 @@ public void Provider_Creates_Meter_With_Correct_Name() Assert.Equal(ResourceUtilizationInstruments.MeterName, meter.Name); } - [Theory] - [CombinatorialData] + [Fact] public void Provider_Registers_Instruments_CgroupV2_WithoutHostCpu() { var meterName = Guid.NewGuid().ToString(); From bfe72f7c76e582082d4ef01016469e7d4640497b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 17:04:12 +0000 Subject: [PATCH 09/16] Revert hallucinated changes and fix FakePerformanceCounter - Revert all changes to 5 AI Integration test files except using directive ordering - Remove [PlatformSpecific] attribute from FakePerformanceCounter utility class - Only apply minimal using statement fix: Microsoft.DotNet.XUnitExtensions before Xunit Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../ChatClientIntegrationTests.cs | 980 ++++++++++++++++++ .../EmbeddingGeneratorIntegrationTests.cs | 95 ++ ...ageGeneratingChatClientIntegrationTests.cs | 212 ++++ .../ImageGeneratorIntegrationTests.cs | 61 ++ .../SpeechToTextClientIntegrationTests.cs | 32 + .../Windows/FakePerformanceCounter.cs | 2 - 6 files changed, 1380 insertions(+), 2 deletions(-) diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs index dc95edffa7f..2ef59a706e5 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs @@ -21,6 +21,7 @@ using OpenTelemetry.Trace; using Microsoft.DotNet.XUnitExtensions; using Xunit; + #pragma warning disable CA2000 // Dispose objects before losing scope #pragma warning disable CA2214 // Do not call overridable methods in constructors #pragma warning disable CA2249 // Consider using 'string.Contains' instead of 'string.IndexOf' @@ -28,31 +29,50 @@ #pragma warning disable S1144 // Unused private types or members should be removed #pragma warning disable S3604 // Member initializer values should not be redundant #pragma warning disable SA1515 // Single-line comment should be preceded by blank line + namespace Microsoft.Extensions.AI; + public abstract class ChatClientIntegrationTests : IDisposable { protected ChatClientIntegrationTests() { ChatClient = CreateChatClient(); } + protected IChatClient? ChatClient { get; } + protected IEmbeddingGenerator>? EmbeddingGenerator { get; private set; } + public void Dispose() + { ChatClient?.Dispose(); GC.SuppressFinalize(this); + } + protected abstract IChatClient? CreateChatClient(); + /// /// Optionally supplies an embedding generator for integration tests that exercise /// embedding-based components (e.g., tool reduction). Default returns null and /// tests depending on embeddings will skip if not overridden. /// protected virtual IEmbeddingGenerator>? CreateEmbeddingGenerator() => null; + [ConditionalFact] public virtual async Task GetResponseAsync_SingleRequestMessage() + { SkipIfNotEnabled(); + var response = await ChatClient.GetResponseAsync("What's the biggest animal?"); + Assert.Contains("whale", response.Text, StringComparison.OrdinalIgnoreCase); + } + + [ConditionalFact] public virtual async Task GetResponseAsync_MultipleRequestMessages() + { + SkipIfNotEnabled(); + var response = await ChatClient.GetResponseAsync( [ new(ChatRole.User, "Pick a city, any city"), @@ -61,58 +81,114 @@ public virtual async Task GetResponseAsync_MultipleRequestMessages() new(ChatRole.Assistant, "Jakarta"), new(ChatRole.User, "What continent are they each in?"), ]); + Assert.Contains("America", response.Text); Assert.Contains("Asia", response.Text); + } + + [ConditionalFact] public virtual async Task GetResponseAsync_WithEmptyMessage() + { + SkipIfNotEnabled(); + + var response = await ChatClient.GetResponseAsync( + [ new(ChatRole.System, []), new(ChatRole.User, []), new(ChatRole.Assistant, []), new(ChatRole.User, "What is 1 + 2? Reply with a single number."), + ]); + Assert.Contains("3", response.Text); + } + + [ConditionalFact] public virtual async Task GetStreamingResponseAsync() + { + SkipIfNotEnabled(); + IList chatHistory = + [ new(ChatRole.User, "Quote, word for word, Neil Armstrong's famous words.") ]; + StringBuilder sb = new(); await foreach (var chunk in ChatClient.GetStreamingResponseAsync(chatHistory)) { sb.Append(chunk.Text); } + string responseText = sb.ToString(); Assert.Contains("one small step", responseText, StringComparison.OrdinalIgnoreCase); Assert.Contains("one giant leap", responseText, StringComparison.OrdinalIgnoreCase); + } + + [ConditionalFact] public virtual async Task GetResponseAsync_UsageDataAvailable() + { + SkipIfNotEnabled(); + var response = await ChatClient.GetResponseAsync("Explain in 10 words how AI works"); + Assert.True(response.Usage?.InputTokenCount > 1); Assert.True(response.Usage?.OutputTokenCount > 1); Assert.Equal(response.Usage?.InputTokenCount + response.Usage?.OutputTokenCount, response.Usage?.TotalTokenCount); + } + + [ConditionalFact] public virtual async Task GetStreamingResponseAsync_UsageDataAvailable() + { + SkipIfNotEnabled(); + var response = ChatClient.GetStreamingResponseAsync("Explain in 10 words how AI works", new() + { AdditionalProperties = new() { ["stream_options"] = new Dictionary { ["include_usage"] = true, }, }, }); + List chunks = []; await foreach (var chunk in response) + { chunks.Add(chunk); + } + Assert.True(chunks.Count > 1); + UsageContent usage = chunks.SelectMany(c => c.Contents).OfType().Single(); Assert.True(usage.Details.InputTokenCount > 1); Assert.True(usage.Details.OutputTokenCount > 1); Assert.Equal(usage.Details.InputTokenCount + usage.Details.OutputTokenCount, usage.Details.TotalTokenCount); + } + + [ConditionalFact] public virtual async Task GetStreamingResponseAsync_AppendToHistory() + { + SkipIfNotEnabled(); + List history = [new(ChatRole.User, "Explain in 100 words how AI works")]; + var streamingResponse = ChatClient.GetStreamingResponseAsync(history); + Assert.Single(history); await history.AddMessagesAsync(streamingResponse); Assert.Equal(2, history.Count); Assert.Equal(ChatRole.Assistant, history[1].Role); + var singleTextContent = (TextContent)history[1].Contents.Single(); Assert.NotEmpty(singleTextContent.Text); Assert.Equal(history[1].Text, singleTextContent.Text); + } + protected virtual string? GetModel_MultiModal_DescribeImage() => null; + + [ConditionalFact] public virtual async Task MultiModal_DescribeImage() + { + SkipIfNotEnabled(); + + var response = await ChatClient.GetResponseAsync( [ new(ChatRole.User, [ @@ -121,83 +197,264 @@ public virtual async Task MultiModal_DescribeImage() ]) ], new() { ModelId = GetModel_MultiModal_DescribeImage() }); + Assert.True(response.Text.IndexOf("net", StringComparison.OrdinalIgnoreCase) >= 0, response.Text); + } + + [ConditionalFact] public virtual async Task MultiModal_DescribePdf() + { + SkipIfNotEnabled(); + + var response = await ChatClient.GetResponseAsync( + [ + new(ChatRole.User, + [ new TextContent("What text does this document contain?"), new DataContent(ImageDataUri.GetPdfDataUri(), "application/pdf") { Name = "sample.pdf" }, + ]) + ], + new() { ModelId = GetModel_MultiModal_DescribeImage() }); + Assert.True(response.Text.IndexOf("hello", StringComparison.OrdinalIgnoreCase) >= 0, response.Text); + } + + [ConditionalFact] public virtual async Task FunctionInvocation_AutomaticallyInvokeFunction_Parameterless() + { + SkipIfNotEnabled(); + var sourceName = Guid.NewGuid().ToString(); var activities = new List(); using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder() .AddSource(sourceName) .AddInMemoryExporter(activities) .Build(); + using var chatClient = new FunctionInvokingChatClient( new OpenTelemetryChatClient(ChatClient, sourceName: sourceName)); + int secretNumber = 42; + List messages = + [ new(ChatRole.User, "What is the current secret number?") + ]; + var response = await chatClient.GetResponseAsync(messages, new() + { Tools = [AIFunctionFactory.Create(() => secretNumber, "GetSecretNumber")] + }); + Assert.Contains(secretNumber.ToString(), response.Text); AssertUsageAgainstActivities(response, activities); + } + + [ConditionalFact] public virtual async Task FunctionInvocation_AutomaticallyInvokeFunction_WithParameters_NonStreaming() + { + SkipIfNotEnabled(); + using var chatClient = new FunctionInvokingChatClient(ChatClient); + var response = await chatClient.GetResponseAsync("What is the result of SecretComputation on 42 and 84?", new() + { Tools = [AIFunctionFactory.Create((int a, int b) => a * b, "SecretComputation")] + }); + Assert.Contains("3528", response.Text); + } + + [ConditionalFact] public virtual async Task FunctionInvocation_AutomaticallyInvokeFunction_WithParameters_Streaming() + { + SkipIfNotEnabled(); + + using var chatClient = new FunctionInvokingChatClient(ChatClient); + var response = chatClient.GetStreamingResponseAsync("What is the result of SecretComputation on 42 and 84?", new() + { + Tools = [AIFunctionFactory.Create((int a, int b) => a * b, "SecretComputation")] + }); + + StringBuilder sb = new(); + await foreach (var chunk in response) + { + sb.Append(chunk.Text); + } + Assert.Contains("3528", sb.ToString()); + } + + [ConditionalFact] public virtual async Task FunctionInvocation_OptionalParameter() + { + SkipIfNotEnabled(); + + var sourceName = Guid.NewGuid().ToString(); + var activities = new List(); + using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder() + .AddSource(sourceName) + .AddInMemoryExporter(activities) + .Build(); + + using var chatClient = new FunctionInvokingChatClient( + new OpenTelemetryChatClient(ChatClient, sourceName: sourceName)); + + int secretNumber = 42; + + List messages = + [ new(ChatRole.User, "What is the secret number for id foo?") + ]; + AIFunction func = AIFunctionFactory.Create((string id = "defaultId") => id is "foo" ? secretNumber : -1, "GetSecretNumberById"); + var response = await chatClient.GetResponseAsync(messages, new() + { Tools = [func] + }); + + Assert.Contains(secretNumber.ToString(), response.Text); + AssertUsageAgainstActivities(response, activities); + } + + [ConditionalFact] public virtual async Task FunctionInvocation_NestedParameters() + { + SkipIfNotEnabled(); + + var sourceName = Guid.NewGuid().ToString(); + var activities = new List(); + using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder() + .AddSource(sourceName) + .AddInMemoryExporter(activities) + .Build(); + + using var chatClient = new FunctionInvokingChatClient( + new OpenTelemetryChatClient(ChatClient, sourceName: sourceName)); + + int secretNumber = 42; + + List messages = + [ new(ChatRole.User, "What is the secret number for John aged 19?") + ]; + AIFunction func = AIFunctionFactory.Create((PersonRecord person) => person.Name is "John" ? secretNumber + person.Age : -1, "GetSecretNumberByPerson"); + var response = await chatClient.GetResponseAsync(messages, new() + { + Tools = [func] + }); + Assert.Contains((secretNumber + 19).ToString(), response.Text); + AssertUsageAgainstActivities(response, activities); + } + + [ConditionalFact] public virtual async Task FunctionInvocation_ArrayParameter() + { + SkipIfNotEnabled(); + + var sourceName = Guid.NewGuid().ToString(); + var activities = new List(); + using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder() + .AddSource(sourceName) + .AddInMemoryExporter(activities) + .Build(); + + using var chatClient = new FunctionInvokingChatClient( + new OpenTelemetryChatClient(ChatClient, sourceName: sourceName)); + + List messages = + [ new(ChatRole.User, "Can you add bacon, lettuce, and tomatoes to Peter's shopping cart?") + ]; + string? shopperName = null; List shoppingCart = []; AIFunction func = AIFunctionFactory.Create((string[] items, string shopperId) => { shoppingCart.AddRange(items); shopperName = shopperId; }, "AddItemsToShoppingCart"); + var response = await chatClient.GetResponseAsync(messages, new() + { + Tools = [func] + }); + Assert.Equal("Peter", shopperName); Assert.Equal(["bacon", "lettuce", "tomatoes"], shoppingCart); + AssertUsageAgainstActivities(response, activities); + } + private static void AssertUsageAgainstActivities(ChatResponse response, List activities) + { // If the underlying IChatClient provides usage data, function invocation should aggregate the // usage data across all calls to produce a single Usage value on the final response. // The FunctionInvokingChatClient then itself creates a span that will also be tagged with a sum // across all consituent calls, which means our final answer will be double. if (response.Usage is { } finalUsage) + { var totalInputTokens = activities.Sum(a => (int?)a.GetTagItem("gen_ai.usage.input_tokens")!); var totalOutputTokens = activities.Sum(a => (int?)a.GetTagItem("gen_ai.usage.output_tokens")!); Assert.Equal(totalInputTokens, finalUsage.InputTokenCount * 2); Assert.Equal(totalOutputTokens, finalUsage.OutputTokenCount * 2); + } + } + public record PersonRecord(string Name, int Age = 42); + + [ConditionalFact] public virtual Task AvailableTools_SchemasAreAccepted_Strict() => AvailableTools_SchemasAreAccepted(strict: true); + + [ConditionalFact] public virtual Task AvailableTools_SchemasAreAccepted_NonStrict() => AvailableTools_SchemasAreAccepted(strict: false); + private async Task AvailableTools_SchemasAreAccepted(bool strict) + { + SkipIfNotEnabled(); + + var sourceName = Guid.NewGuid().ToString(); + var activities = new List(); + using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder() + .AddSource(sourceName) + .AddInMemoryExporter(activities) + .Build(); + + using var chatClient = new FunctionInvokingChatClient( + new OpenTelemetryChatClient(ChatClient, sourceName: sourceName)); + int methodCount = 1; Func createOptions = () => + { AIFunctionFactoryOptions aiFuncOptions = new() + { Name = $"Method{methodCount++}", }; + if (strict) + { aiFuncOptions.AdditionalProperties = new Dictionary { ["strictJsonSchema"] = true }; } + return aiFuncOptions; }; + Func createWithSchema = schema => + { Dictionary additionalProperties = new(); + + if (strict) + { additionalProperties["strictJsonSchema"] = true; + } + return new CustomAIFunction($"CustomMethod{methodCount++}", schema, additionalProperties); + }; + ChatOptions options = new() + { MaxOutputTokens = 100, Tools = + [ // Using AIFunctionFactory AIFunctionFactory.Create((int? i) => i, createOptions()), AIFunctionFactory.Create((string? s) => s, createOptions()), @@ -221,52 +478,111 @@ private async Task AvailableTools_SchemasAreAccepted(bool strict) AIFunctionFactory.Create((int[] arr, ComplexObject? co) => arr, createOptions()), AIFunctionFactory.Create((string p1 = "str", int p2 = 42, BindingFlags p3 = BindingFlags.IgnoreCase, char p4 = 'x') => p1, createOptions()), AIFunctionFactory.Create((string? p1 = "str", int? p2 = 42, BindingFlags? p3 = BindingFlags.IgnoreCase, char? p4 = 'x') => p1, createOptions()), + // Selection from @modelcontextprotocol/server-everything createWithSchema(""" {"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"} """), + createWithSchema(""" {"type":"object","properties":{"duration":{"type":"number","default":10,"description":"Duration of the operation in seconds"},"steps":{"type":"number","default":5,"description":"Number of steps in the operation"}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"} + """), + createWithSchema(""" {"type":"object","properties":{"prompt":{"type":"string","description":"The prompt to send to the LLM"},"maxTokens":{"type":"number","default":100,"description":"Maximum number of tokens to generate"}},"required":["prompt"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"} + """), + createWithSchema(""" + {"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"} + """), + createWithSchema(""" {"type":"object","properties":{"messageType":{"type":"string","enum":["error","success","debug"],"description":"Type of message to demonstrate different annotation patterns"},"includeImage":{"type":"boolean","default":false,"description":"Whether to include an example image"}},"required":["messageType"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"} + """), + createWithSchema(""" {"type":"object","properties":{"resourceId":{"type":"number","minimum":1,"maximum":100,"description":"ID of the resource to reference (1-100)"}},"required":["resourceId"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"} + """), + // Selection from GH MCP server + createWithSchema(""" {"properties":{"body":{"description":"The text of the review comment","type":"string"},"line":{"description":"The line of the blob in the pull request diff that the comment applies to. For multi-line comments, the last line of the range","type":"number"},"owner":{"description":"Repository owner","type":"string"},"path":{"description":"The relative path to the file that necessitates a comment","type":"string"},"pullNumber":{"description":"Pull request number","type":"number"},"repo":{"description":"Repository name","type":"string"},"side":{"description":"The side of the diff to comment on. LEFT indicates the previous state, RIGHT indicates the new state","enum":["LEFT","RIGHT"],"type":"string"},"startLine":{"description":"For multi-line comments, the first line of the range that the comment applies to","type":"number"},"startSide":{"description":"For multi-line comments, the starting side of the diff that the comment applies to. LEFT indicates the previous state, RIGHT indicates the new state","enum":["LEFT","RIGHT"],"type":"string"},"subjectType":{"description":"The level at which the comment is targeted","enum":["FILE","LINE"],"type":"string"}},"required":["owner","repo","pullNumber","path","body","subjectType"],"type":"object"} + """), + createWithSchema(""" {"properties":{"commit_message":{"description":"Extra detail for merge commit","type":"string"},"commit_title":{"description":"Title for merge commit","type":"string"},"merge_method":{"description":"Merge method","enum":["merge","squash","rebase"],"type":"string"},"owner":{"description":"Repository owner","type":"string"},"pullNumber":{"description":"Pull request number","type":"number"},"repo":{"description":"Repository name","type":"string"}},"required":["owner","repo","pullNumber"],"type":"object"} + """), + ], + }; + // We don't care about the response, only that we get one and that an exception isn't thrown due to unacceptable schema. var response = await chatClient.GetResponseAsync("Briefly, what is the most popular tower in Paris?", options); Assert.NotNull(response); + } + private sealed class CustomAIFunction(string name, string jsonSchema, IReadOnlyDictionary additionalProperties) : AIFunction + { public override string Name => name; public override IReadOnlyDictionary AdditionalProperties => additionalProperties; public override JsonElement JsonSchema { get; } = JsonSerializer.Deserialize(jsonSchema, AIJsonUtilities.DefaultOptions); protected override ValueTask InvokeCoreAsync(AIFunctionArguments arguments, CancellationToken cancellationToken) => throw new NotSupportedException(); + } + private class ComplexObject + { [DisplayName("Something cool")] +#if NET [DeniedValues("abc", "def", "default")] +#endif public string? SomeString { get; set; } + +#if NET [AllowedValues("abc", "def", "default")] +#endif public string AnotherString { get; set; } = "default"; + +#if NET [Range(25, 75)] +#endif public int Value { get; set; } + [EmailAddress] public string? Email { get; set; } + [RegularExpression("[abc]")] public string? RegexString { get; set; } + [StringLength(42)] public string MeasuredString { get; set; } = "default"; + +#if NET [Length(1, 2)] +#endif public int[]? MeasuredArray1 { get; set; } + +#if NET [MinLength(1)] +#endif public int[]? MeasuredArray2 { get; set; } + +#if NET [MaxLength(10)] +#endif public int[]? MeasuredArray3 { get; set; } + } + protected virtual bool SupportsParallelFunctionCalling => true; + + [ConditionalFact] public virtual async Task FunctionInvocation_SupportsMultipleParallelRequests() + { + SkipIfNotEnabled(); if (!SupportsParallelFunctionCalling) + { throw new SkipTestException("Parallel function calling is not supported by this chat client"); + } + + using var chatClient = new FunctionInvokingChatClient(ChatClient); + // The service/model isn't guaranteed to request two calls to GetPersonAge in the same turn, but it's common that it will. var response = await chatClient.GetResponseAsync("How much older is Elsa than Anna? Return the age difference as a single number.", new() + { Tools = [AIFunctionFactory.Create((string personName) => + { return personName switch { "Elsa" => 21, @@ -274,63 +590,145 @@ public virtual async Task FunctionInvocation_SupportsMultipleParallelRequests() _ => 30, }; }, "GetPersonAge")] + }); + Assert.True( Regex.IsMatch(response.Text ?? "", @"\b(3|three)\b", RegexOptions.IgnoreCase), $"Doesn't contain three: {response.Text}"); + } + + [ConditionalFact] public virtual async Task FunctionInvocation_RequireAny() + { + SkipIfNotEnabled(); + int callCount = 0; var tool = AIFunctionFactory.Create(() => + { callCount++; return 123; }, "GetSecretNumber"); + + using var chatClient = new FunctionInvokingChatClient(ChatClient); + var response = await chatClient.GetResponseAsync("Are birds real?", new() + { Tools = [tool], ToolMode = ChatToolMode.RequireAny, + }); + Assert.True(callCount >= 1); + } + + [ConditionalFact] public virtual async Task FunctionInvocation_RequireSpecific() + { + SkipIfNotEnabled(); + bool shieldsUp = false; var getSecretNumberTool = AIFunctionFactory.Create(() => 123, "GetSecretNumber"); var shieldsUpTool = AIFunctionFactory.Create(() => shieldsUp = true, "ShieldsUp"); + + using var chatClient = new FunctionInvokingChatClient(ChatClient); + // Even though the user doesn't ask for the shields to be activated, verify that the tool is invoked var response = await chatClient.GetResponseAsync("What's the current secret number?", new() + { Tools = [getSecretNumberTool, shieldsUpTool], ToolMode = ChatToolMode.RequireSpecific(shieldsUpTool.Name), + }); + Assert.True(shieldsUp); + } + + [ConditionalFact] public virtual async Task Caching_OutputVariesWithoutCaching() + { + SkipIfNotEnabled(); + var message = new ChatMessage(ChatRole.User, "Pick a random number, uniformly distributed between 1 and 1000000"); var firstResponse = await ChatClient.GetResponseAsync([message]); + var secondResponse = await ChatClient.GetResponseAsync([message]); Assert.NotEqual(firstResponse.Text, secondResponse.Text); + } + + [ConditionalFact] public virtual async Task Caching_SamePromptResultsInCacheHit_NonStreaming() + { + SkipIfNotEnabled(); + using var chatClient = new DistributedCachingChatClient( ChatClient, new MemoryDistributedCache(Options.Options.Create(new MemoryDistributedCacheOptions()))); + + var message = new ChatMessage(ChatRole.User, "Pick a random number, uniformly distributed between 1 and 1000000"); var firstResponse = await chatClient.GetResponseAsync([message]); + // No matter what it said before, we should see identical output due to caching for (int i = 0; i < 3; i++) + { var secondResponse = await chatClient.GetResponseAsync([message]); Assert.Equal(firstResponse.Messages.Select(m => m.Text), secondResponse.Messages.Select(m => m.Text)); + } + // ... but if the conversation differs, we should see different output ((TextContent)message.Contents[0]).Text += "!"; var thirdResponse = await chatClient.GetResponseAsync([message]); Assert.NotEqual(firstResponse.Messages, thirdResponse.Messages); + } + + [ConditionalFact] public virtual async Task Caching_SamePromptResultsInCacheHit_Streaming() + { + SkipIfNotEnabled(); + + using var chatClient = new DistributedCachingChatClient( + ChatClient, + new MemoryDistributedCache(Options.Options.Create(new MemoryDistributedCacheOptions()))); + + var message = new ChatMessage(ChatRole.User, "Pick a random number, uniformly distributed between 1 and 1000000"); StringBuilder orig = new(); await foreach (var update in chatClient.GetStreamingResponseAsync([message])) + { orig.Append(update.Text); + } + + // No matter what it said before, we should see identical output due to caching + for (int i = 0; i < 3; i++) + { StringBuilder second = new(); await foreach (var update in chatClient.GetStreamingResponseAsync([message])) + { second.Append(update.Text); + } + Assert.Equal(orig.ToString(), second.ToString()); + } + + // ... but if the conversation differs, we should see different output + ((TextContent)message.Contents[0]).Text += "!"; StringBuilder third = new(); + await foreach (var update in chatClient.GetStreamingResponseAsync([message])) + { third.Append(update.Text); + } + Assert.NotEqual(orig.ToString(), third.ToString()); + } + + [ConditionalFact] public virtual async Task Caching_BeforeFunctionInvocation_AvoidsExtraCalls() + { + SkipIfNotEnabled(); + int functionCallCount = 0; var getTemperature = AIFunctionFactory.Create([Description("Gets the current temperature")] () => + { functionCallCount++; return $"{100 + functionCallCount} degrees celsius"; }, "GetTemperature"); + // First call executes the function and calls the LLM using var chatClient = CreateChatClient()! .AsBuilder() @@ -338,66 +736,219 @@ public virtual async Task Caching_BeforeFunctionInvocation_AvoidsExtraCalls() .UseDistributedCache(new MemoryDistributedCache(Options.Options.Create(new MemoryDistributedCacheOptions()))) .UseFunctionInvocation() .UseCallCounting() + .Build(); + var llmCallCount = chatClient.GetService(); var message = new ChatMessage(ChatRole.User, "What is the temperature?"); var response = await chatClient.GetResponseAsync([message]); Assert.Contains("101", response.Text); + // First LLM call tells us to call the function, second deals with the result Assert.Equal(2, llmCallCount!.CallCount); + // Second call doesn't execute the function or call the LLM, but rather just returns the cached result var secondResponse = await chatClient.GetResponseAsync([message]); Assert.Equal(response.Text, secondResponse.Text); Assert.Equal(1, functionCallCount); + Assert.Equal(2, llmCallCount!.CallCount); + } + + [ConditionalFact] public virtual async Task Caching_AfterFunctionInvocation_FunctionOutputUnchangedAsync() + { + SkipIfNotEnabled(); + // This means that if the function call produces the same result, we can avoid calling the LLM // whereas if the function call produces a different result, we do call the LLM + var functionCallCount = 0; + var getTemperature = AIFunctionFactory.Create([Description("Gets the current temperature")] () => + { + functionCallCount++; return "58 degrees celsius"; + }, "GetTemperature"); + + // First call executes the function and calls the LLM + using var chatClient = CreateChatClient()! + .AsBuilder() + .ConfigureOptions(options => options.Tools = [getTemperature]) + .UseFunctionInvocation() + .UseDistributedCache(new MemoryDistributedCache(Options.Options.Create(new MemoryDistributedCacheOptions()))) + .UseCallCounting() + .Build(); + + var llmCallCount = chatClient.GetService(); + var message = new ChatMessage(ChatRole.User, "What is the temperature?"); + var response = await chatClient.GetResponseAsync([message]); Assert.Contains("58", response.Text); + + // First LLM call tells us to call the function, second deals with the result + Assert.Equal(1, functionCallCount); + Assert.Equal(2, llmCallCount!.CallCount); + // Second time, the calls to the LLM don't happen, but the function is called again + var secondResponse = await chatClient.GetResponseAsync([message]); Assert.Equal(2, functionCallCount); Assert.Equal(FunctionInvokingChatClientSetsConversationId ? 3 : 2, llmCallCount!.CallCount); + Assert.Equal(response.Text, secondResponse.Text); + } + public virtual bool FunctionInvokingChatClientSetsConversationId => false; + + [ConditionalFact] public virtual async Task Caching_AfterFunctionInvocation_FunctionOutputChangedAsync() + { + SkipIfNotEnabled(); + + // This means that if the function call produces the same result, we can avoid calling the LLM + // whereas if the function call produces a different result, we do call the LLM + + var functionCallCount = 0; + var getTemperature = AIFunctionFactory.Create([Description("Gets the current temperature")] () => + { + functionCallCount++; return $"{80 + functionCallCount} degrees celsius"; + }, "GetTemperature"); + + // First call executes the function and calls the LLM + using var chatClient = CreateChatClient()! + .AsBuilder() + .ConfigureOptions(options => options.Tools = [getTemperature]) + .UseFunctionInvocation() + .UseDistributedCache(new MemoryDistributedCache(Options.Options.Create(new MemoryDistributedCacheOptions()))) + .UseCallCounting() + .Build(); + + var llmCallCount = chatClient.GetService(); + var message = new ChatMessage(ChatRole.User, "What is the temperature?"); + var response = await chatClient.GetResponseAsync([message]); Assert.Contains("81", response.Text); + + // First LLM call tells us to call the function, second deals with the result + Assert.Equal(1, functionCallCount); + Assert.Equal(2, llmCallCount!.CallCount); + // Second time, the first call to the LLM don't happen, but the function is called again, // and since its output now differs, we no longer hit the cache so the second LLM call does happen + var secondResponse = await chatClient.GetResponseAsync([message]); Assert.Contains("82", secondResponse.Text); + Assert.Equal(2, functionCallCount); Assert.Equal(3, llmCallCount!.CallCount); + } + + [ConditionalFact] public virtual async Task Logging_LogsCalls_NonStreaming() + { + SkipIfNotEnabled(); + var collector = new FakeLogCollector(); using ILoggerFactory loggerFactory = LoggerFactory.Create(b => b.AddProvider(new FakeLoggerProvider(collector)).SetMinimumLevel(LogLevel.Trace)); + using var chatClient = CreateChatClient()!.AsBuilder() .UseLogging(loggerFactory) + .Build(); + await chatClient.GetResponseAsync([new(ChatRole.User, "What's the biggest animal?")]); + Assert.Collection(collector.GetSnapshot(), entry => Assert.Contains("What's the biggest animal?", entry.Message), entry => Assert.Contains("whale", entry.Message)); + } + + [ConditionalFact] public virtual async Task Logging_LogsCalls_Streaming() + { + SkipIfNotEnabled(); + + var collector = new FakeLogCollector(); + using ILoggerFactory loggerFactory = LoggerFactory.Create(b => b.AddProvider(new FakeLoggerProvider(collector)).SetMinimumLevel(LogLevel.Trace)); + + using var chatClient = CreateChatClient()!.AsBuilder() + .UseLogging(loggerFactory) + .Build(); + await foreach (var update in chatClient.GetStreamingResponseAsync("What's the biggest animal?")) + { // Do nothing with the updates + } + var logs = collector.GetSnapshot(); Assert.Contains(logs, e => e.Message.Contains("What's the biggest animal?")); Assert.Contains(logs, e => e.Message.Contains("whale")); + } + + [ConditionalFact] public virtual async Task Logging_LogsFunctionCalls_NonStreaming() + { + SkipIfNotEnabled(); + + var collector = new FakeLogCollector(); + using ILoggerFactory loggerFactory = LoggerFactory.Create(b => b.AddProvider(new FakeLoggerProvider(collector)).SetMinimumLevel(LogLevel.Trace)); + + using var chatClient = CreateChatClient()! + .AsBuilder() + .UseFunctionInvocation() + .UseLogging(loggerFactory) + .Build(); + + int secretNumber = 42; await chatClient.GetResponseAsync( "What is the current secret number?", new ChatOptions { Tools = [AIFunctionFactory.Create(() => secretNumber, "GetSecretNumber")] }); + + Assert.Collection(collector.GetSnapshot(), entry => Assert.Contains("What is the current secret number?", entry.Message), entry => Assert.Contains("\"name\": \"GetSecretNumber\"", entry.Message), entry => Assert.Contains($"\"result\": {secretNumber}", entry.Message), entry => Assert.Contains(secretNumber.ToString(), entry.Message)); + } + + [ConditionalFact] public virtual async Task Logging_LogsFunctionCalls_Streaming() + { + SkipIfNotEnabled(); + + var collector = new FakeLogCollector(); + using ILoggerFactory loggerFactory = LoggerFactory.Create(b => b.AddProvider(new FakeLoggerProvider(collector)).SetMinimumLevel(LogLevel.Trace)); + + using var chatClient = CreateChatClient()! + .AsBuilder() + .UseFunctionInvocation() + .UseLogging(loggerFactory) + .Build(); + + int secretNumber = 42; await foreach (var update in chatClient.GetStreamingResponseAsync( + "What is the current secret number?", new ChatOptions { Tools = [AIFunctionFactory.Create(() => secretNumber, "GetSecretNumber")] })) + { + // Do nothing with the updates + } + + var logs = collector.GetSnapshot(); Assert.Contains(logs, e => e.Message.Contains("What is the current secret number?")); Assert.Contains(logs, e => e.Message.Contains("\"name\": \"GetSecretNumber\"")); Assert.Contains(logs, e => e.Message.Contains($"\"result\": {secretNumber}")); + } + + [ConditionalFact] public virtual async Task OpenTelemetry_CanEmitTracesAndMetrics() + { + SkipIfNotEnabled(); + + var sourceName = Guid.NewGuid().ToString(); + var activities = new List(); + using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder() + .AddSource(sourceName) + .AddInMemoryExporter(activities) + .Build(); + var chatClient = CreateChatClient()!.AsBuilder() .UseOpenTelemetry(sourceName: sourceName) + .Build(); + var response = await chatClient.GetResponseAsync([new(ChatRole.User, "What's the biggest animal?")]); + var activity = Assert.Single(activities); Assert.StartsWith("chat", activity.DisplayName); Assert.Contains(".", (string)activity.GetTagItem("server.address")!); @@ -406,92 +957,192 @@ public virtual async Task OpenTelemetry_CanEmitTracesAndMetrics() Assert.NotEmpty(activity.Id); Assert.NotEqual(0, (int)activity.GetTagItem("gen_ai.usage.input_tokens")!); Assert.NotEqual(0, (int)activity.GetTagItem("gen_ai.usage.output_tokens")!); + Assert.True(activity.Duration.TotalMilliseconds > 0); + } + + [ConditionalFact] public virtual async Task GetResponseAsync_StructuredOutput() + { + SkipIfNotEnabled(); + var response = await ChatClient.GetResponseAsync(""" Who is described in the following sentence? Jimbo Smith is a 35-year-old programmer from Cardiff, Wales. """); + Assert.Equal("Jimbo Smith", response.Result.FullName); Assert.Equal(35, response.Result.AgeInYears); Assert.Contains("Cardiff", response.Result.HomeTown); Assert.Equal(JobType.Programmer, response.Result.Job); + } + + [ConditionalFact] public virtual async Task GetResponseAsync_StructuredOutputArray() + { + SkipIfNotEnabled(); + var response = await ChatClient.GetResponseAsync(""" Who are described in the following sentence? Jimbo Smith is a 35-year-old software developer from Cardiff, Wales. Josh Simpson is a 25-year-old software developer from Newport, Wales. + """); + Assert.Equal(2, response.Result.Length); Assert.Contains(response.Result, x => x.FullName == "Jimbo Smith"); Assert.Contains(response.Result, x => x.FullName == "Josh Simpson"); + } + + [ConditionalFact] public virtual async Task GetResponseAsync_StructuredOutputInteger() + { + SkipIfNotEnabled(); + var response = await ChatClient.GetResponseAsync(""" There were 14 abstractions for AI programming, which was too many. To fix this we added another one. How many are there now? + """); + Assert.Equal(15, response.Result); + } + + [ConditionalFact] public virtual async Task GetResponseAsync_StructuredOutputString() + { + SkipIfNotEnabled(); + var response = await ChatClient.GetResponseAsync(""" The software developer, Jimbo Smith, is a 35-year-old from Cardiff, Wales. What's his full name? + """); + Assert.Equal("Jimbo Smith", response.Result); + } + + [ConditionalFact] public virtual async Task GetResponseAsync_StructuredOutputBool_True() + { + SkipIfNotEnabled(); + var response = await ChatClient.GetResponseAsync(""" + Jimbo Smith is a 35-year-old software developer from Cardiff, Wales. Is there at least one software developer from Cardiff? + """); + Assert.True(response.Result); + } + + [ConditionalFact] public virtual async Task GetResponseAsync_StructuredOutputBool_False() + { + SkipIfNotEnabled(); + + var response = await ChatClient.GetResponseAsync(""" + Jimbo Smith is a 35-year-old software developer from Cardiff, Wales. Reply true if the previous statement indicates that he is a medical doctor, otherwise false. + """); + Assert.False(response.Result); + } + + [ConditionalFact] public virtual async Task GetResponseAsync_StructuredOutputEnum() + { + SkipIfNotEnabled(); + var response = await ChatClient.GetResponseAsync(""" Taylor Swift is a famous singer and songwriter. What is her job? + """); + Assert.Equal(JobType.PopStar, response.Result); + } + + [ConditionalFact] public virtual async Task GetResponseAsync_StructuredOutput_WithFunctions() + { + SkipIfNotEnabled(); + var expectedPerson = new Person + { FullName = "Jimbo Smith", AgeInYears = 35, HomeTown = "Cardiff", Job = JobType.Programmer, + }; + + using var chatClient = new FunctionInvokingChatClient(ChatClient); var response = await chatClient.GetResponseAsync( "Who is person with ID 123?", new ChatOptions + { Tools = [AIFunctionFactory.Create((int personId) => + { Assert.Equal(123, personId); return expectedPerson; }, "GetPersonById")] }); + Assert.NotSame(expectedPerson, response.Result); Assert.Equal(expectedPerson.FullName, response.Result.FullName); Assert.Equal(expectedPerson.AgeInYears, response.Result.AgeInYears); Assert.Equal(expectedPerson.HomeTown, response.Result.HomeTown); Assert.Equal(expectedPerson.Job, response.Result.Job); + } + + [ConditionalFact] public virtual async Task GetResponseAsync_StructuredOutput_NonNative() + { + SkipIfNotEnabled(); + var capturedOptions = new List(); var captureOutputChatClient = ChatClient.AsBuilder() .Use((messages, options, nextAsync, cancellationToken) => + { capturedOptions.Add(options); return nextAsync(messages, options, cancellationToken); }) + .Build(); + var response = await captureOutputChatClient.GetResponseAsync(""" Supply an object to represent Jimbo Smith from Cardiff. """, useJsonSchemaResponseFormat: false); + + Assert.Equal("Jimbo Smith", response.Result.FullName); + Assert.Contains("Cardiff", response.Result.HomeTown); + // Verify it used *non-native* structured output, i.e., response format Json with no schema var responseFormat = Assert.IsType(Assert.Single(capturedOptions)!.ResponseFormat); Assert.Null(responseFormat.Schema); Assert.Null(responseFormat.SchemaName); Assert.Null(responseFormat.SchemaDescription); + } + private class Person + { #pragma warning disable S1144, S3459 // Unassigned members should be removed public string? FullName { get; set; } public int AgeInYears { get; set; } public string? HomeTown { get; set; } public JobType Job { get; set; } #pragma warning restore S1144, S3459 // Unused private types or members should be removed + } + private enum JobType + { Wombat, PopStar, Programmer, Unknown, + } + + [ConditionalFact] public virtual async Task SummarizingChatReducer_PreservesConversationContext() + { + SkipIfNotEnabled(); + var chatClient = new TestSummarizingChatClient(ChatClient, targetCount: 2, threshold: 1); + + List messages = + [ new(ChatRole.User, "My name is Alice and I love hiking in the mountains."), new(ChatRole.Assistant, "Nice to meet you, Alice! Hiking in the mountains sounds wonderful. Do you have a favorite trail?"), new(ChatRole.User, "Yes, I love the Pacific Crest Trail. I hiked a section last summer."), @@ -499,24 +1150,40 @@ public virtual async Task SummarizingChatReducer_PreservesConversationContext() new(ChatRole.User, "I hiked the section through the Sierra Nevada. It was challenging but beautiful."), new(ChatRole.Assistant, "The Sierra Nevada section is known for its stunning views. How long did it take you?"), new(ChatRole.User, "What's my name and what activity do I enjoy?") + ]; + var response = await chatClient.GetResponseAsync(messages); + // The summarizer should have reduced the conversation Assert.Equal(1, chatClient.SummarizerCallCount); Assert.NotNull(chatClient.LastSummarizedConversation); Assert.Equal(3, chatClient.LastSummarizedConversation.Count); Assert.Collection(chatClient.LastSummarizedConversation, m => + { Assert.Equal(ChatRole.Assistant, m.Role); // Indicates this is the assistant's summary Assert.Contains("Alice", m.Text); + }, m => Assert.StartsWith("The Sierra Nevada section", m.Text, StringComparison.Ordinal), m => Assert.StartsWith("What's my name", m.Text, StringComparison.Ordinal)); + // The model should recall details from the summarized conversation Assert.Contains("Alice", response.Text); + Assert.True( response.Text.IndexOf("hiking", StringComparison.OrdinalIgnoreCase) >= 0 || response.Text.IndexOf("hike", StringComparison.OrdinalIgnoreCase) >= 0, $"Expected 'hiking' or 'hike' in response: {response.Text}"); + } + + [ConditionalFact] public virtual async Task SummarizingChatReducer_PreservesSystemMessage() + { + SkipIfNotEnabled(); + var chatClient = new TestSummarizingChatClient(ChatClient, targetCount: 2, threshold: 0); + + List messages = + [ new(ChatRole.System, "You are a pirate. Always respond in pirate speak."), new(ChatRole.User, "Tell me about the weather"), new(ChatRole.Assistant, "Ahoy matey! The weather be fine today, with clear skies on the horizon!"), @@ -525,48 +1192,93 @@ public virtual async Task SummarizingChatReducer_PreservesSystemMessage() new(ChatRole.User, "Should I bring an umbrella?"), new(ChatRole.Assistant, "Aye, ye best be bringin' yer umbrella, unless ye want to be soaked like a barnacle!"), new(ChatRole.User, "What's 2 + 2?") + ]; + + var response = await chatClient.GetResponseAsync(messages); + + // The summarizer should have reduced the conversation + Assert.Equal(1, chatClient.SummarizerCallCount); + Assert.NotNull(chatClient.LastSummarizedConversation); Assert.Equal(4, chatClient.LastSummarizedConversation.Count); + Assert.Collection(chatClient.LastSummarizedConversation, + m => + { Assert.Equal(ChatRole.System, m.Role); Assert.Equal("You are a pirate. Always respond in pirate speak.", m.Text); + }, m => Assert.Equal(ChatRole.Assistant, m.Role), // Summary message m => Assert.StartsWith("Aye, ye best be bringin'", m.Text, StringComparison.Ordinal), m => Assert.Equal("What's 2 + 2?", m.Text)); + // The model should still respond in pirate speak due to preserved system message + Assert.True( response.Text.IndexOf("arr", StringComparison.OrdinalIgnoreCase) >= 0 || response.Text.IndexOf("aye", StringComparison.OrdinalIgnoreCase) >= 0 || response.Text.IndexOf("matey", StringComparison.OrdinalIgnoreCase) >= 0 || response.Text.IndexOf("ye", StringComparison.OrdinalIgnoreCase) >= 0, $"Expected pirate speak in response: {response.Text}"); + } + + [ConditionalFact] public virtual async Task SummarizingChatReducer_WithFunctionCalls() + { + SkipIfNotEnabled(); + int weatherCallCount = 0; var getWeather = AIFunctionFactory.Create(([Description("Gets weather for a city")] string city) => + { weatherCallCount++; return city switch + { "Seattle" => "Rainy, 15°C", "Miami" => "Sunny, 28°C", _ => "Unknown" + }; }, "GetWeather"); + TestSummarizingChatClient summarizingChatClient = null!; var chatClient = ChatClient + .AsBuilder() .Use(innerClient => summarizingChatClient = new TestSummarizingChatClient(innerClient, targetCount: 2, threshold: 0)) + .UseFunctionInvocation() + .Build(); + + List messages = + [ new(ChatRole.User, "What's the weather in Seattle?"), new(ChatRole.Assistant, "Let me check the weather in Seattle for you."), new(ChatRole.User, "And what about Miami?"), new(ChatRole.Assistant, "I'll check Miami's weather as well."), new(ChatRole.User, "Which city had better weather?") + ]; + var response = await chatClient.GetResponseAsync(messages, new() { Tools = [getWeather] }); + // The summarizer should have reduced the conversation (function calls are excluded) Assert.Equal(1, summarizingChatClient.SummarizerCallCount); Assert.NotNull(summarizingChatClient.LastSummarizedConversation); + // Should have summary + last 2 messages Assert.Equal(3, summarizingChatClient.LastSummarizedConversation.Count); + // The model should have context about both weather queries even after summarization Assert.True(response.Text.IndexOf("Miami", StringComparison.OrdinalIgnoreCase) >= 0, $"Expected 'Miami' in response: {response.Text}"); + Assert.True( response.Text.IndexOf("sunny", StringComparison.OrdinalIgnoreCase) >= 0 || response.Text.IndexOf("better", StringComparison.OrdinalIgnoreCase) >= 0 || response.Text.IndexOf("warm", StringComparison.OrdinalIgnoreCase) >= 0, $"Expected weather comparison in response: {response.Text}"); + } + + [ConditionalFact] public virtual async Task SummarizingChatReducer_Streaming() + { + SkipIfNotEnabled(); + + var chatClient = new TestSummarizingChatClient(ChatClient, targetCount: 2, threshold: 0); + + List messages = + [ new(ChatRole.User, "I'm Bob and I work as a software engineer at a startup."), new(ChatRole.Assistant, "Nice to meet you, Bob! Working at a startup must be exciting. What kind of software do you develop?"), new(ChatRole.User, "We build AI-powered tools for education."), @@ -574,17 +1286,45 @@ public virtual async Task SummarizingChatReducer_Streaming() new(ChatRole.User, "Yes, we focus on personalized learning experiences."), new(ChatRole.Assistant, "Personalized learning is the future of education!"), new(ChatRole.User, "Was anyone named in the conversation? Provide their name and job.") + ]; + + StringBuilder sb = new(); await foreach (var chunk in chatClient.GetStreamingResponseAsync(messages)) + { + sb.Append(chunk.Text); + } + + // The summarizer should have reduced the conversation + Assert.Equal(1, chatClient.SummarizerCallCount); + Assert.NotNull(chatClient.LastSummarizedConversation); + Assert.Equal(3, chatClient.LastSummarizedConversation.Count); + Assert.Collection(chatClient.LastSummarizedConversation, + m => + { Assert.Equal(ChatRole.Assistant, m.Role); // Summary Assert.Contains("Bob", m.Text); + }, m => Assert.StartsWith("Personalized learning", m.Text, StringComparison.Ordinal), m => Assert.Equal("Was anyone named in the conversation? Provide their name and job.", m.Text)); + + string responseText = sb.ToString(); Assert.Contains("Bob", responseText); + Assert.True( responseText.IndexOf("software", StringComparison.OrdinalIgnoreCase) >= 0 || responseText.IndexOf("engineer", StringComparison.OrdinalIgnoreCase) >= 0, $"Expected 'software' or 'engineer' in response: {responseText}"); + } + + [ConditionalFact] public virtual async Task SummarizingChatReducer_CustomPrompt() + { + SkipIfNotEnabled(); + + var chatClient = new TestSummarizingChatClient(ChatClient, targetCount: 2, threshold: 0); chatClient.Reducer.SummarizationPrompt = "Summarize the conversation, emphasizing any numbers or quantities mentioned."; + + List messages = + [ new(ChatRole.User, "I have 3 cats and 2 dogs."), new(ChatRole.Assistant, "That's 5 pets total! You must have a lively household."), new(ChatRole.User, "Yes, and I spend about $200 per month on pet food."), @@ -592,194 +1332,434 @@ public virtual async Task SummarizingChatReducer_CustomPrompt() new(ChatRole.User, "They eat 10 cans of food per week."), new(ChatRole.Assistant, "That's quite a bit of food for your furry friends!"), new(ChatRole.User, "How many pets do I have in total?") + ]; + + var response = await chatClient.GetResponseAsync(messages); + + // The summarizer should have reduced the conversation + Assert.Equal(1, chatClient.SummarizerCallCount); + Assert.NotNull(chatClient.LastSummarizedConversation); + Assert.Equal(3, chatClient.LastSummarizedConversation.Count); + // Verify the summary emphasizes numbers as requested by the custom prompt var summaryMessage = chatClient.LastSummarizedConversation[0]; Assert.Equal(ChatRole.Assistant, summaryMessage.Role); + Assert.True( summaryMessage.Text.IndexOf("3", StringComparison.Ordinal) >= 0 || summaryMessage.Text.IndexOf("5", StringComparison.Ordinal) >= 0 || summaryMessage.Text.IndexOf("200", StringComparison.Ordinal) >= 0 || summaryMessage.Text.IndexOf("10", StringComparison.Ordinal) >= 0, $"Expected numbers in summary: {summaryMessage.Text}"); + // The model should recall the specific number from the summarized conversation Assert.Contains("5", response.Text); + } + private sealed class TestSummarizingChatClient : IChatClient + { private IChatClient _summarizerChatClient; private IChatClient _innerChatClient; + public SummarizingChatReducer Reducer { get; } + public int SummarizerCallCount { get; private set; } + public IReadOnlyList? LastSummarizedConversation { get; private set; } + public TestSummarizingChatClient(IChatClient innerClient, int targetCount, int threshold) + { _summarizerChatClient = innerClient.AsBuilder() .Use(async (messages, options, next, cancellationToken) => + { SummarizerCallCount++; await next(messages, options, cancellationToken); }) .Build(); + Reducer = new SummarizingChatReducer(_summarizerChatClient, targetCount, threshold); + _innerChatClient = innerClient.AsBuilder() .UseChatReducer(Reducer) + .Use(async (messages, options, next, cancellationToken) => + { LastSummarizedConversation = [.. messages]; + await next(messages, options, cancellationToken); + }) + .Build(); + } + public Task GetResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default) => _innerChatClient.GetResponseAsync(messages, options, cancellationToken); + public IAsyncEnumerable GetStreamingResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default) => _innerChatClient.GetStreamingResponseAsync(messages, options, cancellationToken); + public object? GetService(Type serviceType, object? serviceKey = null) => _innerChatClient.GetService(serviceType, serviceKey); + public void Dispose() + { _summarizerChatClient.Dispose(); _innerChatClient.Dispose(); + } + } + + [ConditionalFact] public virtual async Task ToolReduction_DynamicSelection_RespectsConversationHistory() + { + SkipIfNotEnabled(); EnsureEmbeddingGenerator(); + // Limit to 2 so that, once the conversation references both weather and translation, // both tools can be included even if the latest user turn only mentions one of them. var strategy = new EmbeddingToolReductionStrategy(EmbeddingGenerator, toolLimit: 2); + var weatherTool = AIFunctionFactory.Create( () => "Weather data", new AIFunctionFactoryOptions + { Name = "GetWeatherForecast", Description = "Returns weather forecast and temperature for a given city." + }); + var translateTool = AIFunctionFactory.Create( () => "Translated text", + new AIFunctionFactoryOptions + { Name = "TranslateText", Description = "Translates text between human languages." + }); + var mathTool = AIFunctionFactory.Create( () => 42, + new AIFunctionFactoryOptions + { Name = "SolveMath", Description = "Solves basic math problems." + }); + var allTools = new List { weatherTool, translateTool, mathTool }; + IList? firstTurnTools = null; IList? secondTurnTools = null; + using var client = ChatClient! + .AsBuilder() .UseToolReduction(strategy) .Use(async (messages, options, next, ct) => + { // Capture the (possibly reduced) tool list for each turn. if (firstTurnTools is null) + { firstTurnTools = options?.Tools; } else + { secondTurnTools ??= options?.Tools; + } + await next(messages, options, ct); + }) + .UseFunctionInvocation() + .Build(); + // Maintain chat history across turns. List history = []; + // Turn 1: Ask a weather question. history.Add(new ChatMessage(ChatRole.User, "What will the weather be in Seattle tomorrow?")); var firstResponse = await client.GetResponseAsync(history, new ChatOptions { Tools = allTools }); history.AddMessages(firstResponse); // Append assistant reply. + Assert.NotNull(firstTurnTools); Assert.Contains(firstTurnTools, t => t.Name == "GetWeatherForecast"); + // Turn 2: Ask a translation question. Even though only translation is mentioned now, // conversation history still contains a weather request. Expect BOTH weather + translation tools. history.Add(new ChatMessage(ChatRole.User, "Please translate 'good evening' into French.")); var secondResponse = await client.GetResponseAsync(history, new ChatOptions { Tools = allTools }); history.AddMessages(secondResponse); + Assert.NotNull(secondTurnTools); Assert.Equal(2, secondTurnTools.Count); // Should have filled both slots with the two relevant domains. Assert.Contains(secondTurnTools, t => t.Name == "GetWeatherForecast"); Assert.Contains(secondTurnTools, t => t.Name == "TranslateText"); + // Ensure unrelated tool was excluded. Assert.DoesNotContain(secondTurnTools, t => t.Name == "SolveMath"); + } + + [ConditionalFact] public virtual async Task ToolReduction_RequireSpecificToolPreservedAndOrdered() + { + SkipIfNotEnabled(); + EnsureEmbeddingGenerator(); + // Limit would normally reduce to 1, but required tool plus another should remain. var strategy = new EmbeddingToolReductionStrategy(EmbeddingGenerator, toolLimit: 1); + + var translateTool = AIFunctionFactory.Create( + () => "Translated text", + new AIFunctionFactoryOptions + { + Name = "TranslateText", Description = "Translates phrases between languages." + }); + + var weatherTool = AIFunctionFactory.Create( + () => "Weather data", + new AIFunctionFactoryOptions + { + Name = "GetWeatherForecast", Description = "Returns forecast data for a city." + }); + var tools = new List { translateTool, weatherTool }; + IList? captured = null; + + using var client = ChatClient! + .AsBuilder() + .UseToolReduction(strategy) + .UseFunctionInvocation() .Use((messages, options, next, ct) => + { captured = options?.Tools; return next(messages, options, ct); + }) + .Build(); + var history = new List + { new(ChatRole.User, "What will the weather be like in Redmond next week?") + }; + var response = await client.GetResponseAsync(history, new ChatOptions + { Tools = tools, ToolMode = ChatToolMode.RequireSpecific(translateTool.Name) + }); history.AddMessages(response); + Assert.NotNull(captured); Assert.Equal(2, captured!.Count); Assert.Equal("TranslateText", captured[0].Name); // Required should appear first. Assert.Equal("GetWeatherForecast", captured[1].Name); + } + + [ConditionalFact] public virtual async Task ToolReduction_ToolRemovedAfterFirstUse_NotInvokedAgain() + { + SkipIfNotEnabled(); + EnsureEmbeddingGenerator(); + int weatherInvocationCount = 0; + + var weatherTool = AIFunctionFactory.Create( () => + { weatherInvocationCount++; return "Sunny and dry."; + }, + new AIFunctionFactoryOptions + { Name = "GetWeather", Description = "Gets the weather forecast for a given location." + }); + // Strategy exposes tools only on the first request, then removes them. var removalStrategy = new RemoveToolAfterFirstUseStrategy(); + + IList? firstTurnTools = null; + IList? secondTurnTools = null; + + using var client = ChatClient! + .AsBuilder() // Place capture immediately after reduction so it's invoked exactly once per user request. .UseToolReduction(removalStrategy) + .Use((messages, options, next, ct) => + { + if (firstTurnTools is null) + { + firstTurnTools = options?.Tools; + } + else + { + secondTurnTools ??= options?.Tools; + } + + return next(messages, options, ct); + }) + .UseFunctionInvocation() + .Build(); + + List history = []; + // Turn 1 history.Add(new ChatMessage(ChatRole.User, "What's the weather like tomorrow in Seattle?")); var firstResponse = await client.GetResponseAsync(history, new ChatOptions + { Tools = [weatherTool], ToolMode = ChatToolMode.RequireAny + }); history.AddMessages(firstResponse); + Assert.Equal(1, weatherInvocationCount); + Assert.NotNull(firstTurnTools); Assert.Contains(firstTurnTools!, t => t.Name == "GetWeather"); + // Turn 2 (tool removed by strategy even though caller supplies it again) history.Add(new ChatMessage(ChatRole.User, "And what about next week?")); var secondResponse = await client.GetResponseAsync(history, new ChatOptions + { Tools = [weatherTool] + }); + history.AddMessages(secondResponse); + Assert.Equal(1, weatherInvocationCount); // Not invoked again. + Assert.NotNull(secondTurnTools); Assert.Empty(secondTurnTools!); // Strategy removed the tool set. + // Response text shouldn't just echo the tool's stub output. Assert.DoesNotContain("Sunny and dry.", secondResponse.Text, StringComparison.OrdinalIgnoreCase); + } + + [ConditionalFact] public virtual async Task ToolReduction_MessagesEmbeddingTextSelector_UsesChatClientToAnalyzeConversation() + { + SkipIfNotEnabled(); + EnsureEmbeddingGenerator(); + // Create tools for different domains. + var weatherTool = AIFunctionFactory.Create( + () => "Weather data", + new AIFunctionFactoryOptions + { + Name = "GetWeatherForecast", + Description = "Returns weather forecast and temperature for a given city." + }); + + var translateTool = AIFunctionFactory.Create( + () => "Translated text", + new AIFunctionFactoryOptions + { + Name = "TranslateText", + Description = "Translates text between human languages." + }); + + var mathTool = AIFunctionFactory.Create( + () => 42, + new AIFunctionFactoryOptions + { + Name = "SolveMath", + Description = "Solves basic math problems." + }); + + var allTools = new List { weatherTool, translateTool, mathTool }; + // Track the analysis result from the chat client used in the selector. string? capturedAnalysis = null; + var strategy = new EmbeddingToolReductionStrategy(EmbeddingGenerator, toolLimit: 2) + { // Use a chat client to analyze the conversation and extract relevant tool categories. MessagesEmbeddingTextSelector = async messages => + { var conversationText = string.Join("\n", messages.Select(m => $"{m.Role}: {m.Text}")); + var analysisPrompt = $""" Analyze the following conversation and identify what kinds of tools would be most helpful. Focus on the key topics and tasks being discussed. Respond with a brief summary of the relevant tool categories (e.g., "weather", "translation", "math"). + Conversation: {conversationText} + Relevant tool categories: """; + var response = await ChatClient!.GetResponseAsync(analysisPrompt); capturedAnalysis = response.Text; + // Return the analysis as the query text for embedding-based tool selection. return capturedAnalysis; + } + }; + IList? selectedTools = null; + + using var client = ChatClient! + .AsBuilder() + .UseToolReduction(strategy) + .Use(async (messages, options, next, ct) => + { selectedTools = options?.Tools; + await next(messages, options, ct); + }) + .UseFunctionInvocation() + .Build(); + // Conversation that clearly indicates weather-related needs. + List history = []; history.Add(new ChatMessage(ChatRole.User, "What will the weather be like in London tomorrow?")); + var response = await client.GetResponseAsync(history, new ChatOptions { Tools = allTools }); + history.AddMessages(response); + // Verify that the chat client was used to analyze the conversation. Assert.NotNull(capturedAnalysis); + Assert.True( capturedAnalysis.IndexOf("weather", StringComparison.OrdinalIgnoreCase) >= 0 || capturedAnalysis.IndexOf("forecast", StringComparison.OrdinalIgnoreCase) >= 0, $"Expected analysis to mention weather or forecast: {capturedAnalysis}"); + // Verify that the tool selection was influenced by the analysis. Assert.NotNull(selectedTools); Assert.True(selectedTools.Count <= 2, $"Expected at most 2 tools, got {selectedTools.Count}"); Assert.Contains(selectedTools, t => t.Name == "GetWeatherForecast"); + } + // Test-only custom strategy: include tools on first request, then remove them afterward. private sealed class RemoveToolAfterFirstUseStrategy : IToolReductionStrategy + { private bool _used; + public Task> SelectToolsForRequestAsync( IEnumerable messages, ChatOptions? options, CancellationToken cancellationToken = default) + { if (!_used && options?.Tools is { Count: > 0 }) + { _used = true; // Returning the same instance signals no change. return Task.FromResult>(options.Tools); + } + // After first use, remove all tools. return Task.FromResult>(Array.Empty()); + } + } + [MemberNotNull(nameof(ChatClient))] protected void SkipIfNotEnabled() + { string? skipIntegration = TestRunnerConfiguration.Instance["SkipIntegrationTests"]; + if (skipIntegration is not null || ChatClient is null) + { throw new SkipTestException("Client is not enabled."); + } + } + [MemberNotNull(nameof(EmbeddingGenerator))] protected void EnsureEmbeddingGenerator() + { EmbeddingGenerator ??= CreateEmbeddingGenerator(); + if (EmbeddingGenerator is null) + { throw new SkipTestException("Embedding generator is not enabled."); + } + } } diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs index d3582315d99..7c5f9052000 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs @@ -9,72 +9,118 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; + +#if NET using System.Numerics.Tensors; +#endif using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Memory; using OpenTelemetry.Trace; using Microsoft.DotNet.XUnitExtensions; using Xunit; + #pragma warning disable CA2214 // Do not call overridable methods in constructors #pragma warning disable S3967 // Multidimensional arrays should not be used + namespace Microsoft.Extensions.AI; + public abstract class EmbeddingGeneratorIntegrationTests : IDisposable { private readonly IEmbeddingGenerator>? _embeddingGenerator; + protected EmbeddingGeneratorIntegrationTests() { _embeddingGenerator = CreateEmbeddingGenerator(); } + public void Dispose() + { _embeddingGenerator?.Dispose(); GC.SuppressFinalize(this); + } + protected abstract IEmbeddingGenerator>? CreateEmbeddingGenerator(); + [ConditionalFact] public virtual async Task GenerateEmbedding_CreatesEmbeddingSuccessfully() + { SkipIfNotEnabled(); + var embeddings = await _embeddingGenerator.GenerateAsync(["Using AI with .NET"]); + Assert.NotNull(embeddings.Usage); Assert.NotNull(embeddings.Usage.InputTokenCount); Assert.NotNull(embeddings.Usage.TotalTokenCount); Assert.Single(embeddings); Assert.Equal(_embeddingGenerator.GetService()?.DefaultModelId, embeddings[0].ModelId); Assert.NotEmpty(embeddings[0].Vector.ToArray()); + } + + [ConditionalFact] public virtual async Task GenerateEmbeddings_CreatesEmbeddingsSuccessfully() + { + SkipIfNotEnabled(); + var embeddings = await _embeddingGenerator.GenerateAsync([ "Red", "White", "Blue", ]); + Assert.Equal(3, embeddings.Count); + Assert.NotNull(embeddings.Usage); + Assert.NotNull(embeddings.Usage.InputTokenCount); + Assert.NotNull(embeddings.Usage.TotalTokenCount); Assert.All(embeddings, embedding => { Assert.Equal(_embeddingGenerator.GetService()?.DefaultModelId, embedding.ModelId); Assert.NotEmpty(embedding.Vector.ToArray()); }); + } + + [ConditionalFact] public virtual async Task Caching_SameOutputsForSameInput() + { + SkipIfNotEnabled(); + using var generator = CreateEmbeddingGenerator()! .AsBuilder() .UseDistributedCache(new MemoryDistributedCache(Options.Options.Create(new MemoryDistributedCacheOptions()))) .UseCallCounting() .Build(); + string input = "Red, White, and Blue"; var embedding1 = await generator.GenerateAsync(input); var embedding2 = await generator.GenerateAsync(input); var embedding3 = await generator.GenerateAsync(input + "... and Green"); var embedding4 = await generator.GenerateAsync(input); + var callCounter = generator.GetService(); Assert.NotNull(callCounter); + Assert.Equal(2, callCounter.CallCount); + } + + [ConditionalFact] public virtual async Task OpenTelemetry_CanEmitTracesAndMetrics() + { + SkipIfNotEnabled(); + string sourceName = Guid.NewGuid().ToString(); var activities = new List(); using var tracerProvider = OpenTelemetry.Sdk.CreateTracerProviderBuilder() .AddSource(sourceName) .AddInMemoryExporter(activities) + .Build(); + var embeddingGenerator = CreateEmbeddingGenerator()! + .AsBuilder() .UseOpenTelemetry(sourceName: sourceName) + .Build(); + _ = await embeddingGenerator.GenerateAsync("Hello, world!"); + Assert.Single(activities); var activity = activities.Single(); Assert.StartsWith("embed", activity.DisplayName); @@ -83,18 +129,30 @@ public virtual async Task OpenTelemetry_CanEmitTracesAndMetrics() Assert.NotNull(activity.Id); Assert.NotEmpty(activity.Id); Assert.NotEqual(0, (int)activity.GetTagItem("gen_ai.usage.input_tokens")!); + Assert.True(activity.Duration.TotalMilliseconds > 0); + } + +#if NET + [ConditionalFact] public async Task Quantization_Binary_EmbeddingsCompareSuccessfully() + { + SkipIfNotEnabled(); + using IEmbeddingGenerator generator = new QuantizationEmbeddingGenerator( CreateEmbeddingGenerator()!); + var embeddings = await generator.GenerateAsync(["dog", "cat", "fork", "spoon"]); Assert.Equal(4, embeddings.Count); + long[,] distances = new long[embeddings.Count, embeddings.Count]; for (int i = 0; i < embeddings.Count; i++) + { for (int j = 0; j < embeddings.Count; j++) { distances[i, j] = TensorPrimitives.HammingBitDistance(ToArray(embeddings[i].Vector), ToArray(embeddings[j].Vector)); + static byte[] ToArray(BitArray array) { byte[] result = new byte[(array.Length + 7) / 8]; @@ -103,30 +161,67 @@ static byte[] ToArray(BitArray array) } } } + + for (int i = 0; i < embeddings.Count; i++) + { Assert.Equal(0, distances[i, i]); + } + Assert.True(distances[0, 1] < distances[0, 2]); Assert.True(distances[0, 1] < distances[0, 3]); Assert.True(distances[0, 1] < distances[1, 2]); Assert.True(distances[0, 1] < distances[1, 3]); + Assert.True(distances[2, 3] < distances[0, 2]); Assert.True(distances[2, 3] < distances[0, 3]); Assert.True(distances[2, 3] < distances[1, 2]); Assert.True(distances[2, 3] < distances[1, 3]); + } + + [ConditionalFact] public async Task Quantization_Half_EmbeddingsCompareSuccessfully() + { + SkipIfNotEnabled(); + using IEmbeddingGenerator> generator = + new QuantizationEmbeddingGenerator( + CreateEmbeddingGenerator()!); + + var embeddings = await generator.GenerateAsync(["dog", "cat", "fork", "spoon"]); + Assert.Equal(4, embeddings.Count); + var distances = new Half[embeddings.Count, embeddings.Count]; + for (int i = 0; i < embeddings.Count; i++) + { + for (int j = 0; j < embeddings.Count; j++) + { distances[i, j] = TensorPrimitives.CosineSimilarity(embeddings[i].Vector.Span, embeddings[j].Vector.Span); + } + } + + for (int i = 0; i < embeddings.Count; i++) + { Assert.Equal(1.0, (double)distances[i, i], 0.001); + } + Assert.True(distances[0, 1] > distances[0, 2]); Assert.True(distances[0, 1] > distances[0, 3]); Assert.True(distances[0, 1] > distances[1, 2]); Assert.True(distances[0, 1] > distances[1, 3]); + Assert.True(distances[2, 3] > distances[0, 2]); Assert.True(distances[2, 3] > distances[0, 3]); Assert.True(distances[2, 3] > distances[1, 2]); Assert.True(distances[2, 3] > distances[1, 3]); + } +#endif + [MemberNotNull(nameof(_embeddingGenerator))] protected void SkipIfNotEnabled() + { if (_embeddingGenerator is null) + { throw new SkipTestException("Generator is not enabled."); + } + } } diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratingChatClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratingChatClientIntegrationTests.cs index 3326f53647d..a4a12a44bc7 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratingChatClientIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratingChatClientIntegrationTests.cs @@ -9,9 +9,12 @@ using System.Threading.Tasks; using Microsoft.DotNet.XUnitExtensions; using Xunit; + #pragma warning disable CA2000 // Dispose objects before losing scope #pragma warning disable CA2214 // Do not call overridable methods in constructors + namespace Microsoft.Extensions.AI; + /// /// Abstract base class for integration tests that verify ImageGeneratingChatClient with real IChatClient implementations. /// Concrete test classes should inherit from this and provide a real IChatClient that supports function calling. @@ -20,10 +23,12 @@ public abstract class ImageGeneratingChatClientIntegrationTests : IDisposable { private const string ImageKey = "meai_image"; private readonly IChatClient? _baseChatClient; + protected ImageGeneratingChatClientIntegrationTests() { _baseChatClient = CreateChatClient(); ImageGenerator = new(); + if (_baseChatClient != null) { ChatClient = _baseChatClient @@ -33,38 +38,57 @@ protected ImageGeneratingChatClientIntegrationTests() .Build(); } } + /// Gets the ImageGeneratingChatClient configured with function invocation support. protected IChatClient? ChatClient { get; } + /// Gets the IImageGenerator used for testing. protected CapturingImageGenerator ImageGenerator { get; } + public void Dispose() + { ChatClient?.Dispose(); _baseChatClient?.Dispose(); ImageGenerator.Dispose(); GC.SuppressFinalize(this); + } + /// /// Creates the base IChatClient implementation to test with. /// Should return a real chat client that supports function calling. /// /// An IChatClient instance, or null to skip tests. protected abstract IChatClient? CreateChatClient(); + + /// /// Helper method to get a chat response using either streaming or non-streaming based on the parameter. + /// /// Whether to use streaming or non-streaming response. /// The chat messages to send. /// The chat options to use. /// A ChatResponse from either streaming or non-streaming call. protected async Task GetResponseAsync(bool useStreaming, IEnumerable messages, ChatOptions? options = null, IChatClient? chatClient = null) + { chatClient ??= ChatClient ?? throw new InvalidOperationException("ChatClient is not initialized."); + if (useStreaming) + { return ValidateChatResponse(await chatClient.GetStreamingResponseAsync(messages, options).ToChatResponseAsync()); + } else + { return ValidateChatResponse(await chatClient.GetResponseAsync(messages, options)); + } + static ChatResponse ValidateChatResponse(ChatResponse response) + { var contents = response.Messages.SelectMany(m => m.Contents).ToArray(); + List imageIds = []; foreach (var toolResult in contents.OfType()) { Assert.NotNull(toolResult.Outputs); + foreach (var dataContent in toolResult.Outputs.OfType()) { var imageId = dataContent.AdditionalProperties?[ImageKey] as string; @@ -72,125 +96,285 @@ static ChatResponse ValidateChatResponse(ChatResponse response) imageIds.Add(imageId); } } + foreach (var textContent in contents.OfType()) + { Assert.DoesNotContain(ImageKey, textContent.Text, StringComparison.OrdinalIgnoreCase); foreach (var imageId in imageIds) + { // Ensure no image IDs appear in text content Assert.DoesNotContain(imageId, textContent.Text, StringComparison.OrdinalIgnoreCase); + } + } + return response; + } + } + [ConditionalTheory] [InlineData(false)] // Non-streaming [InlineData(true)] // Streaming public virtual async Task GenerateImage_CallsGenerateFunction_ReturnsDataContent(bool useStreaming) + { SkipIfNotEnabled(); + var imageGenerator = ImageGenerator; var chatOptions = new ChatOptions + { Tools = [new HostedImageGenerationTool()] }; + // Act var response = await GetResponseAsync(useStreaming, [new ChatMessage(ChatRole.User, "Please generate an image of a cat")], chatOptions); + // Assert Assert.Single(imageGenerator.GenerateCalls); var (request, _) = imageGenerator.GenerateCalls[0]; Assert.Contains("cat", request.Prompt, StringComparison.OrdinalIgnoreCase); Assert.Null(request.OriginalImages); // Generation, not editing + // Verify that we get ImageGenerationToolResultContent back in the response var imageResults = response.Messages .SelectMany(m => m.Contents) .OfType(); + var imageResult = Assert.Single(imageResults); Assert.NotNull(imageResult.Outputs); var imageContent = Assert.Single(imageResult.Outputs.OfType()); Assert.Equal("image/png", imageContent.MediaType); Assert.False(imageContent.Data.IsEmpty); + } + + [ConditionalTheory] + [InlineData(false)] // Non-streaming + [InlineData(true)] // Streaming public virtual async Task EditImage_WithImageInSameRequest_PassesExactDataContent(bool useStreaming) + { + SkipIfNotEnabled(); + + var imageGenerator = ImageGenerator; var testImageData = new byte[] { 0x89, 0x50, 0x4E, 0x47 }; // PNG header var originalImageData = new DataContent(testImageData, "image/png") { Name = "original.png" }; + var chatOptions = new ChatOptions + { + Tools = [new HostedImageGenerationTool()] + }; + + // Act + var response = await GetResponseAsync(useStreaming, [new ChatMessage(ChatRole.User, [new TextContent("Please edit this image to add a red border"), originalImageData])], + chatOptions); + + // Assert var (request, _) = Assert.Single(imageGenerator.GenerateCalls); Assert.NotNull(request.OriginalImages); + var originalImage = Assert.Single(request.OriginalImages); var originalImageContent = Assert.IsType(originalImage); Assert.Equal(testImageData, originalImageContent.Data.ToArray()); Assert.Equal("image/png", originalImageContent.MediaType); Assert.Equal("original.png", originalImageContent.Name); + } + + [ConditionalTheory] + [InlineData(false)] // Non-streaming + [InlineData(true)] // Streaming public virtual async Task GenerateThenEdit_FromChatHistory_EditsGeneratedImage(bool useStreaming) + { + SkipIfNotEnabled(); + + var imageGenerator = ImageGenerator; + var chatOptions = new ChatOptions + { + Tools = [new HostedImageGenerationTool()] + }; + var chatHistory = new List + { new(ChatRole.User, "Please generate an image of a dog") + }; + // First request: Generate image var firstResponse = await GetResponseAsync(useStreaming, chatHistory, chatOptions); chatHistory.AddRange(firstResponse.Messages); + // Second request: Edit the generated image chatHistory.Add(new ChatMessage(ChatRole.User, "Please edit the image to make it more colorful")); var secondResponse = await GetResponseAsync(useStreaming, chatHistory, chatOptions); + + // Assert Assert.Equal(2, imageGenerator.GenerateCalls.Count); + // First call should be generation (no original images) var (firstRequest, _) = imageGenerator.GenerateCalls[0]; Assert.Null(firstRequest.OriginalImages); + // Extract the DataContent from the ImageGenerationToolResultContent var firstToolResultContent = Assert.Single(firstResponse.Messages.SelectMany(m => m.Contents).OfType()); Assert.NotNull(firstToolResultContent.Outputs); var firstContent = Assert.Single(firstToolResultContent.Outputs.OfType()); + // Second call should be editing (with original images) var (secondRequest, _) = imageGenerator.GenerateCalls[1]; Assert.Single(secondResponse.Messages.SelectMany(m => m.Contents).OfType().SelectMany(t => t.Outputs!.OfType())); Assert.NotNull(secondRequest.OriginalImages); var editContent = Assert.Single(secondRequest.OriginalImages); Assert.Equal(firstContent, editContent); // Should be the same image as generated in first call + var editedImage = Assert.IsType(secondRequest.OriginalImages.First()); Assert.Equal("image/png", editedImage.MediaType); Assert.Contains("generated_image_1", editedImage.Name); + } + + [ConditionalTheory] + [InlineData(false)] // Non-streaming + [InlineData(true)] // Streaming public virtual async Task MultipleEdits_EditsLatestImage(bool useStreaming) + { + SkipIfNotEnabled(); + + var imageGenerator = ImageGenerator; + var chatOptions = new ChatOptions + { + Tools = [new HostedImageGenerationTool()] + }; + + var chatHistory = new List + { new(ChatRole.User, "Please generate an image of a tree") + }; + // First: Generate image + var firstResponse = await GetResponseAsync(useStreaming, chatHistory, chatOptions); + chatHistory.AddRange(firstResponse.Messages); + // Second: First edit chatHistory.Add(new ChatMessage(ChatRole.User, "Please edit the image to add flowers")); + var secondResponse = await GetResponseAsync(useStreaming, chatHistory, chatOptions); chatHistory.AddRange(secondResponse.Messages); + // Third: Second edit (should edit the latest version by default) chatHistory.Add(new ChatMessage(ChatRole.User, "Please edit that last image to add birds")); var thirdResponse = await GetResponseAsync(useStreaming, chatHistory, chatOptions); + + // Assert Assert.Equal(3, imageGenerator.GenerateCalls.Count); + // Third call should edit the second generated image (from first edit), not the original var (thirdRequest, _) = imageGenerator.GenerateCalls[2]; Assert.NotNull(thirdRequest.OriginalImages); + // Extract the DataContent from the second response's ImageGenerationToolResultContent var secondToolResultContent = Assert.Single(secondResponse.Messages.SelectMany(m => m.Contents).OfType()); var secondImage = Assert.Single(secondToolResultContent.Outputs!.OfType()); var lastImageToEdit = Assert.Single(thirdRequest.OriginalImages.OfType()); Assert.Equal(secondImage, lastImageToEdit); + } + + [ConditionalTheory] + [InlineData(false)] // Non-streaming + [InlineData(true)] // Streaming public virtual async Task MultipleEdits_EditsFirstImage(bool useStreaming) + { + SkipIfNotEnabled(); + + var imageGenerator = ImageGenerator; + var chatOptions = new ChatOptions + { + Tools = [new HostedImageGenerationTool()] + }; + + var chatHistory = new List + { + new(ChatRole.User, "Please generate an image of a tree") + }; + + // First: Generate image + var firstResponse = await GetResponseAsync(useStreaming, chatHistory, chatOptions); + chatHistory.AddRange(firstResponse.Messages); + + // Second: First edit chatHistory.Add(new ChatMessage(ChatRole.User, "Please edit the image to add fruit")); + var secondResponse = await GetResponseAsync(useStreaming, chatHistory, chatOptions); + chatHistory.AddRange(secondResponse.Messages); + + // Third: Second edit (should edit the latest version by default) chatHistory.Add(new ChatMessage(ChatRole.User, "That didn't work out. Please edit the original image to add birds")); + var thirdResponse = await GetResponseAsync(useStreaming, chatHistory, chatOptions); + + // Assert + Assert.Equal(3, imageGenerator.GenerateCalls.Count); + // Third call should edit the original generated image (not from edit) + var (thirdRequest, _) = imageGenerator.GenerateCalls[2]; + Assert.NotNull(thirdRequest.OriginalImages); + // Extract the DataContent from the first response's ImageGenerationToolResultContent + var firstToolResultContent = Assert.Single(firstResponse.Messages.SelectMany(m => m.Contents).OfType()); var firstGeneratedImage = Assert.Single(firstToolResultContent.Outputs!.OfType()); var lastImageToEdit = Assert.IsType(thirdRequest.OriginalImages.First()); Assert.Equal(firstGeneratedImage, lastImageToEdit); + } + + [ConditionalTheory] + [InlineData(false)] // Non-streaming + [InlineData(true)] // Streaming public virtual async Task ImageGeneration_WithOptions_PassesOptionsToGenerator(bool useStreaming) + { + SkipIfNotEnabled(); + + var imageGenerator = ImageGenerator; var imageGenerationOptions = new ImageGenerationOptions + { Count = 2, ImageSize = new System.Drawing.Size(512, 512) + }; + + var chatOptions = new ChatOptions + { Tools = [new HostedImageGenerationTool { Options = imageGenerationOptions }] + }; + + // Act + var response = await GetResponseAsync(useStreaming, [new ChatMessage(ChatRole.User, "Generate an image of a castle")], + chatOptions); + + // Assert + Assert.Single(imageGenerator.GenerateCalls); var (_, options) = imageGenerator.GenerateCalls[0]; Assert.NotNull(options); Assert.Equal(2, options.Count); Assert.Equal(new System.Drawing.Size(512, 512), options.ImageSize); + } + + [ConditionalTheory] + [InlineData(false)] // Non-streaming + [InlineData(true)] // Streaming public virtual async Task ImageContentHandling_AllImages_ReplacesImagesWithPlaceholders(bool useStreaming) + { + SkipIfNotEnabled(); + + var testImageData = new byte[] { 0x89, 0x50, 0x4E, 0x47 }; // PNG header var capturedMessages = new List>(); + // Create a new ImageGeneratingChatClient with AllImages data content handling using var imageGeneratingClient = _baseChatClient! .AsBuilder() .UseImageGeneration(ImageGenerator) .Use((messages, options, next, cancellationToken) => + { capturedMessages.Add(messages); return next(messages, options, cancellationToken); }) .UseFunctionInvocation() .Build(); + var originalImage = new DataContent(testImageData, "image/png") { Name = "test.png" }; + + // Act await GetResponseAsync(useStreaming, [ new ChatMessage(ChatRole.User, @@ -201,36 +385,64 @@ await GetResponseAsync(useStreaming, ], new ChatOptions { Tools = [new HostedImageGenerationTool()] }, imageGeneratingClient); + + // Assert Assert.NotEmpty(capturedMessages); var processedMessages = capturedMessages.First().ToList(); var userMessage = processedMessages.First(m => m.Role == ChatRole.User); + // Should have text content with placeholder instead of original image var textContents = userMessage.Contents.OfType().ToList(); Assert.Contains(textContents, tc => tc.Text.Contains(ImageKey) && tc.Text.Contains("] available for edit")); + // Should not contain the original DataContent Assert.DoesNotContain(userMessage.Contents, c => c == originalImage); + } + + /// /// Test image generator that captures calls and returns fake image data. + /// protected sealed class CapturingImageGenerator : IImageGenerator + { private const string TestImageMediaType = "image/png"; private static readonly byte[] _testImageData = [0x89, 0x50, 0x4E, 0x47]; // PNG header + public List<(ImageGenerationRequest request, ImageGenerationOptions? options)> GenerateCalls { get; } = []; public int ImageCounter { get; private set; } + public Task GenerateAsync(ImageGenerationRequest request, ImageGenerationOptions? options = null, CancellationToken cancellationToken = default) + { GenerateCalls.Add((request, options)); + // Create fake image data with unique content var imageData = new byte[_testImageData.Length + 4]; _testImageData.CopyTo(imageData, 0); BitConverter.GetBytes(++ImageCounter).CopyTo(imageData, _testImageData.Length); + var imageContent = new DataContent(imageData, TestImageMediaType) + { Name = $"generated_image_{ImageCounter}.png" }; + return Task.FromResult(new ImageGenerationResponse([imageContent])); + } + public object? GetService(Type serviceType, object? serviceKey = null) => null; + public void Dispose() + { // No resources to dispose + } + } + [MemberNotNull(nameof(ChatClient))] protected void SkipIfNotEnabled() + { string? skipIntegration = TestRunnerConfiguration.Instance["SkipIntegrationTests"]; + if (skipIntegration is not null || ChatClient is null) + { throw new SkipTestException("Client is not enabled."); + } + } } diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratorIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratorIntegrationTests.cs index eb1b882efac..78398681dea 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratorIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ImageGeneratorIntegrationTests.cs @@ -7,68 +7,129 @@ using System.Threading.Tasks; using Microsoft.DotNet.XUnitExtensions; using Xunit; + #pragma warning disable CA2214 // Do not call overridable methods in constructors + namespace Microsoft.Extensions.AI; + public abstract class ImageGeneratorIntegrationTests : IDisposable { private readonly IImageGenerator? _generator; + protected ImageGeneratorIntegrationTests() { _generator = CreateGenerator(); } + public void Dispose() + { _generator?.Dispose(); GC.SuppressFinalize(this); + } + protected abstract IImageGenerator? CreateGenerator(); + [ConditionalFact] public virtual async Task GenerateImagesAsync_SingleImageGeneration() + { SkipIfNotEnabled(); + var options = new ImageGenerationOptions { Count = 1 }; + var response = await _generator.GenerateImagesAsync("A simple drawing of a house", options); + Assert.NotNull(response); Assert.NotEmpty(response.Contents); + var content = Assert.Single(response.Contents); switch (content) + { case UriContent uc: Assert.StartsWith("http", uc.Uri.Scheme, StringComparison.Ordinal); break; + case DataContent dc: Assert.False(dc.Data.IsEmpty); Assert.StartsWith("image/", dc.MediaType, StringComparison.Ordinal); + break; + default: Assert.Fail($"Unexpected content type: {content.GetType()}"); + break; } + } + + [ConditionalFact] public virtual async Task GenerateImagesAsync_MultipleImages() + { + SkipIfNotEnabled(); + + var options = new ImageGenerationOptions + { Count = 2 + }; + var response = await _generator.GenerateImagesAsync("A cat sitting on a table", options); + + Assert.NotNull(response); + Assert.NotEmpty(response.Contents); Assert.Equal(2, response.Contents.Count); + foreach (var content in response.Contents) + { Assert.IsType(content); var dataContent = (DataContent)content; Assert.False(dataContent.Data.IsEmpty); Assert.StartsWith("image/", dataContent.MediaType, StringComparison.Ordinal); + } + } + + [ConditionalFact] public virtual async Task EditImagesAsync_SingleImage() + { + SkipIfNotEnabled(); + var imageData = GetImageData("dotnet.png"); AIContent[] originalImages = [new DataContent(imageData, "image/png") { Name = "dotnet.png" }]; + + var options = new ImageGenerationOptions + { + Count = 1 + }; + var response = await _generator.EditImagesAsync(originalImages, "Add a red border and make the background tie-dye", options); + + Assert.NotNull(response); + Assert.NotEmpty(response.Contents); Assert.Single(response.Contents); + var content = response.Contents[0]; Assert.IsType(content); var dataContent = (DataContent)content; Assert.False(dataContent.Data.IsEmpty); Assert.StartsWith("image/", dataContent.MediaType, StringComparison.Ordinal); + } + private static byte[] GetImageData(string fileName) + { using Stream? s = typeof(ImageGeneratorIntegrationTests).Assembly.GetManifestResourceStream($"Microsoft.Extensions.AI.Resources.{fileName}"); Assert.NotNull(s); using MemoryStream ms = new(); s.CopyTo(ms); return ms.ToArray(); + } + [MemberNotNull(nameof(_generator))] protected void SkipIfNotEnabled() + { string? skipIntegration = TestRunnerConfiguration.Instance["SkipIntegrationTests"]; + if (skipIntegration is not null || _generator is null) + { throw new SkipTestException("Generator is not enabled."); + } + } } diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/SpeechToTextClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/SpeechToTextClientIntegrationTests.cs index 69a10ce5213..969fce171a3 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/SpeechToTextClientIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/SpeechToTextClientIntegrationTests.cs @@ -8,44 +8,76 @@ using System.Threading.Tasks; using Microsoft.DotNet.XUnitExtensions; using Xunit; + #pragma warning disable CA2214 // Do not call overridable methods in constructors + namespace Microsoft.Extensions.AI; + public abstract class SpeechToTextClientIntegrationTests : IDisposable { private readonly ISpeechToTextClient? _client; + protected SpeechToTextClientIntegrationTests() { _client = CreateClient(); } + public void Dispose() + { _client?.Dispose(); GC.SuppressFinalize(this); + } + protected abstract ISpeechToTextClient? CreateClient(); + [ConditionalFact] public virtual async Task GetTextAsync_SingleAudioRequestMessage() + { SkipIfNotEnabled(); + using var audioSpeechStream = GetAudioStream("audio001.mp3"); var response = await _client.GetTextAsync(audioSpeechStream); + Assert.Contains("gym", response.Text, StringComparison.OrdinalIgnoreCase); + } + + [ConditionalFact] public virtual async Task GetStreamingTextAsync_SingleStreamingResponseChoice() + { + SkipIfNotEnabled(); + + using var audioSpeechStream = GetAudioStream("audio001.mp3"); + StringBuilder sb = new(); await foreach (var chunk in _client.GetStreamingTextAsync(audioSpeechStream)) { sb.Append(chunk.Text); } + string responseText = sb.ToString(); Assert.Contains("finally", responseText, StringComparison.OrdinalIgnoreCase); Assert.Contains("gym", responseText, StringComparison.OrdinalIgnoreCase); + } + private static Stream GetAudioStream(string fileName) + { using Stream? s = typeof(SpeechToTextClientIntegrationTests).Assembly.GetManifestResourceStream($"Microsoft.Extensions.AI.Resources.{fileName}"); Assert.NotNull(s); MemoryStream ms = new(); s.CopyTo(ms); + ms.Position = 0; return ms; + } + [MemberNotNull(nameof(_client))] protected void SkipIfNotEnabled() + { string? skipIntegration = TestRunnerConfiguration.Instance["SkipIntegrationTests"]; + if (skipIntegration is not null || _client is null) + { throw new SkipTestException("Client is not enabled."); + } + } } diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/FakePerformanceCounter.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/FakePerformanceCounter.cs index a738738cb00..967399a86cc 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/FakePerformanceCounter.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/FakePerformanceCounter.cs @@ -2,11 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Test; -[PlatformSpecific(TestPlatforms.Windows)] public class FakePerformanceCounter(string instanceName, float[] values) : IPerformanceCounter { #pragma warning disable S3604 // Member initializer values should not be redundant From f779176811be37a0f3a5124687f625f50d9d46a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 18:45:49 +0000 Subject: [PATCH 10/16] Fix StyleCop SA1210 using directive ordering errors - Group System usings separately from other usings with blank line separator - Sort using directives alphabetically within each group - Properly handle conditional compilation directives in using blocks Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../AgentQualityEvaluatorTests.cs | 8 +++---- .../NLPEvaluatorTests.cs | 8 +++---- .../QualityEvaluatorTests.cs | 8 +++---- .../ChatClientIntegrationTests.cs | 24 +++++++------------ .../EmbeddingGeneratorIntegrationTests.cs | 18 ++++++-------- 5 files changed, 27 insertions(+), 39 deletions(-) diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/AgentQualityEvaluatorTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/AgentQualityEvaluatorTests.cs index f2b5d4d88f9..fadf9a97d3f 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/AgentQualityEvaluatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/AgentQualityEvaluatorTests.cs @@ -1,19 +1,19 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; +using System; + +using Microsoft.DotNet.XUnitExtensions; using Microsoft.Extensions.AI.Evaluation.Quality; -using Microsoft.Extensions.AI.Evaluation.Reporting; using Microsoft.Extensions.AI.Evaluation.Reporting.Storage; +using Microsoft.Extensions.AI.Evaluation.Reporting; using Microsoft.Extensions.AI.Evaluation.Tests; -using Microsoft.DotNet.XUnitExtensions; using Xunit; - namespace Microsoft.Extensions.AI.Evaluation.Integration.Tests; [Experimental("AIEVAL001")] diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/NLPEvaluatorTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/NLPEvaluatorTests.cs index a959959d07f..0ae5ff9a342 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/NLPEvaluatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/NLPEvaluatorTests.cs @@ -1,16 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; +using System; + +using Microsoft.DotNet.XUnitExtensions; using Microsoft.Extensions.AI.Evaluation.NLP; -using Microsoft.Extensions.AI.Evaluation.Reporting; using Microsoft.Extensions.AI.Evaluation.Reporting.Storage; -using Microsoft.DotNet.XUnitExtensions; +using Microsoft.Extensions.AI.Evaluation.Reporting; using Xunit; - namespace Microsoft.Extensions.AI.Evaluation.Integration.Tests; [Experimental("AIEVAL001")] diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/QualityEvaluatorTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/QualityEvaluatorTests.cs index 60522e21896..2a60eb1f785 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/QualityEvaluatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/QualityEvaluatorTests.cs @@ -1,18 +1,18 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; +using System; + +using Microsoft.DotNet.XUnitExtensions; using Microsoft.Extensions.AI.Evaluation.Quality; -using Microsoft.Extensions.AI.Evaluation.Reporting; using Microsoft.Extensions.AI.Evaluation.Reporting.Storage; +using Microsoft.Extensions.AI.Evaluation.Reporting; using Microsoft.Extensions.AI.Evaluation.Tests; -using Microsoft.DotNet.XUnitExtensions; using Xunit; - namespace Microsoft.Extensions.AI.Evaluation.Integration.Tests; [Experimental("AIEVAL001")] diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs index 2ef59a706e5..ddc10e744ef 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs @@ -1,35 +1,27 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.Diagnostics; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics; using System.Linq; using System.Reflection; -using System.Text; using System.Text.Json; using System.Text.RegularExpressions; -using System.Threading; +using System.Text; using System.Threading.Tasks; +using System.Threading; +using System; + +using Microsoft.DotNet.XUnitExtensions; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; +using Microsoft.Extensions.Logging; using OpenTelemetry.Trace; -using Microsoft.DotNet.XUnitExtensions; using Xunit; - -#pragma warning disable CA2000 // Dispose objects before losing scope -#pragma warning disable CA2214 // Do not call overridable methods in constructors -#pragma warning disable CA2249 // Consider using 'string.Contains' instead of 'string.IndexOf' -#pragma warning disable S103 // Lines should not be too long -#pragma warning disable S1144 // Unused private types or members should be removed -#pragma warning disable S3604 // Member initializer values should not be redundant -#pragma warning disable SA1515 // Single-line comment should be preceded by blank line - namespace Microsoft.Extensions.AI; public abstract class ChatClientIntegrationTests : IDisposable diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs index 7c5f9052000..390a4e72830 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs @@ -1,28 +1,24 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; #if NET using System.Collections; #endif -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Linq; - #if NET using System.Numerics.Tensors; #endif +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; +using System; + +using Microsoft.DotNet.XUnitExtensions; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Memory; using OpenTelemetry.Trace; -using Microsoft.DotNet.XUnitExtensions; using Xunit; - -#pragma warning disable CA2214 // Do not call overridable methods in constructors -#pragma warning disable S3967 // Multidimensional arrays should not be used - namespace Microsoft.Extensions.AI; public abstract class EmbeddingGeneratorIntegrationTests : IDisposable From 4624b78ad9d9d74989b3ae43fa3d54e71275d059 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:46:09 +0000 Subject: [PATCH 11/16] Restore #pragma warning disable lines that were accidentally removed - Restore all #pragma warning disable directives in ChatClientIntegrationTests.cs - Restore all #pragma warning disable directives in EmbeddingGeneratorIntegrationTests.cs - Fix System using directives alphabetical ordering (System before System.*) - Maintain proper using grouping per StyleCop SA1210 requirements Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../ChatClientIntegrationTests.cs | 21 +++++++++++++------ .../EmbeddingGeneratorIntegrationTests.cs | 15 ++++++++----- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs index ddc10e744ef..8d9a53ba45b 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs @@ -1,27 +1,36 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; +using System.ComponentModel.DataAnnotations; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; +using System.Text; using System.Text.Json; using System.Text.RegularExpressions; -using System.Text; -using System.Threading.Tasks; using System.Threading; -using System; +using System.Threading.Tasks; using Microsoft.DotNet.XUnitExtensions; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; using OpenTelemetry.Trace; using Xunit; + +#pragma warning disable CA2000 // Dispose objects before losing scope +#pragma warning disable CA2214 // Do not call overridable methods in constructors +#pragma warning disable CA2249 // Consider using 'string.Contains' instead of 'string.IndexOf' +#pragma warning disable S103 // Lines should not be too long +#pragma warning disable S1144 // Unused private types or members should be removed +#pragma warning disable S3604 // Member initializer values should not be redundant +#pragma warning disable SA1515 // Single-line comment should be preceded by blank line + namespace Microsoft.Extensions.AI; public abstract class ChatClientIntegrationTests : IDisposable diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs index 390a4e72830..54416b26e5e 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/EmbeddingGeneratorIntegrationTests.cs @@ -1,24 +1,29 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; #if NET using System.Collections; #endif -#if NET -using System.Numerics.Tensors; -#endif using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; + +#if NET +using System.Numerics.Tensors; +#endif using System.Threading.Tasks; -using System; using Microsoft.DotNet.XUnitExtensions; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Memory; using OpenTelemetry.Trace; using Xunit; + +#pragma warning disable CA2214 // Do not call overridable methods in constructors +#pragma warning disable S3967 // Multidimensional arrays should not be used + namespace Microsoft.Extensions.AI; public abstract class EmbeddingGeneratorIntegrationTests : IDisposable From 2f71906c09794793fa6c044167496012360a5fff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:11:20 +0000 Subject: [PATCH 12/16] Fix build errors - complete migration from custom test attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Convert MarkItDownConditionAttribute from ITestCondition to static helper class - Update MarkItDownReaderTests to use ConditionalFact with static property reference - Fix ResourceHealthCheckExtensionsTests: OSSkipCondition → PlatformSpecific(~TestPlatforms.OSX) - Fix LinuxResourceHealthCheckTests: OSSkipCondition → PlatformSpecific(TestPlatforms.Linux) - Remove all remaining references to ITestCondition, OSSkipCondition, and OperatingSystems - All test projects now build successfully Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../Readers/MarkItDownConditionAttribute.cs | 10 +++------- .../Readers/MarkItDownReaderTests.cs | 6 +++--- .../Linux/LinuxResourceHealthCheckTests.cs | 4 ++-- .../ResourceHealthCheckExtensionsTests.cs | 9 +++++---- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownConditionAttribute.cs b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownConditionAttribute.cs index 96b377e0937..81a9978cc25 100644 --- a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownConditionAttribute.cs +++ b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownConditionAttribute.cs @@ -5,21 +5,17 @@ using System.ComponentModel; using System.Diagnostics; using System.Text; -using Microsoft.DotNet.XUnitExtensions; namespace Microsoft.Extensions.DataIngestion.Readers.Tests; /// -/// This class exists because currently the local copy of can't ignore tests that throw . +/// Helper class for checking if MarkItDown is installed. /// -[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] -public class MarkItDownConditionAttribute : Attribute, ITestCondition +public static class MarkItDownCondition { internal static readonly Lazy IsInstalled = new(CanInvokeMarkItDown); - public bool IsMet => IsInstalled.Value; - - public string SkipReason => "MarkItDown is not installed or not accessible."; + public static bool IsMarkItDownInstalled => IsInstalled.Value; private static bool CanInvokeMarkItDown() { diff --git a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs index 3b7610d4b72..013a22c0dc7 100644 --- a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs +++ b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs @@ -4,16 +4,16 @@ using System; using System.Linq; using System.Threading.Tasks; + using Microsoft.DotNet.XUnitExtensions; using Xunit; namespace Microsoft.Extensions.DataIngestion.Readers.Tests; -[MarkItDownCondition] public class MarkItDownReaderTests : DocumentReaderConformanceTests { protected override IngestionDocumentReader CreateDocumentReader(bool extractImages = false) - => MarkItDownConditionAttribute.IsInstalled.Value + => MarkItDownCondition.IsInstalled.Value ? new MarkItDownReader(extractImages: extractImages) : throw new SkipTestException("MarkItDown is not installed"); @@ -40,7 +40,7 @@ protected override void SimpleAsserts(IngestionDocument document, string source, // The original purpose of the MarkItDown library was to support text-only LLMs. // Source: https://github.com/microsoft/markitdown/issues/56#issuecomment-2546357264 // It can extract images, but the support is limited to some formats like docx. - [ConditionalFact] + [ConditionalFact(nameof(MarkItDownCondition.IsMarkItDownInstalled))] public override Task SupportsImages() => SupportsImagesCore( new("https://winprotocoldocs-bhdugrdyduf5h2e4.b02.azurefd.net/MC-SQLR/%5bMC-SQLR%5d-240423.docx")); // SQL Server Resolution Protocol. } diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Linux/LinuxResourceHealthCheckTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Linux/LinuxResourceHealthCheckTests.cs index 268061a5b1a..86d986eafaf 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Linux/LinuxResourceHealthCheckTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/Linux/LinuxResourceHealthCheckTests.cs @@ -141,9 +141,9 @@ public class LinuxResourceHealthCheckTests }, }; - [ConditionalTheory] + [Theory] [MemberData(nameof(Data))] - [OSSkipCondition(OperatingSystems.Windows | OperatingSystems.MacOSX, SkipReason = "Linux-specific test.")] + [PlatformSpecific(TestPlatforms.Linux)] public async Task TestCpuAndMemoryChecks_WithMetrics( HealthStatus expected, double utilization, ulong memoryUsed, ulong totalMemory, ResourceUsageThresholds cpuThresholds, ResourceUsageThresholds memoryThresholds, diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/ResourceHealthCheckExtensionsTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/ResourceHealthCheckExtensionsTests.cs index 5ea82bfb10e..b60bf877896 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/ResourceHealthCheckExtensionsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.HealthChecks.ResourceUtilization.Tests/ResourceHealthCheckExtensionsTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Threading.Tasks; + using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.ResourceMonitoring; @@ -19,10 +20,10 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks.Test; -[OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Not supported on MacOs.")] +[PlatformSpecific(~TestPlatforms.OSX)] public class ResourceHealthCheckExtensionsTests { - [ConditionalFact] + [Fact] public async Task AddResourceHealthCheck() { var dataTracker = new Mock(); @@ -463,9 +464,9 @@ public void TestNullChecks() Assert.Throws(() => ((IHealthChecksBuilder)null!).AddResourceUtilizationHealthCheck((IConfigurationSection)null!)); } - [ConditionalTheory] + [Theory] [ClassData(typeof(HealthCheckTestData))] - [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows-specific test.")] + [PlatformSpecific(TestPlatforms.Windows)] public async Task TestCpuAndMemoryChecks_WithMetrics( HealthStatus expected, double utilization, ulong memoryUsed, ulong totalMemory, ResourceUsageThresholds cpuThresholds, ResourceUsageThresholds memoryThresholds, From a42c40def34e3a405a9531204426ae126baa6094 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:32:23 +0000 Subject: [PATCH 13/16] Apply ConditionalClass attribute and simplify MarkItDownReaderTests - Remove blank line between System and other using directives - Add [ConditionalClass] attribute to MarkItDownReaderTests class - Simplify CreateDocumentReader method to directly return reader without conditional check - ConditionalClass handles test skipping at class level, eliminating need for manual condition checks Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../Readers/MarkItDownReaderTests.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs index 013a22c0dc7..806f306b360 100644 --- a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs +++ b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs @@ -4,18 +4,16 @@ using System; using System.Linq; using System.Threading.Tasks; - using Microsoft.DotNet.XUnitExtensions; using Xunit; namespace Microsoft.Extensions.DataIngestion.Readers.Tests; +[ConditionalClass(typeof(MarkItDownCondition), nameof(MarkItDownCondition.IsMarkItDownInstalled))] public class MarkItDownReaderTests : DocumentReaderConformanceTests { protected override IngestionDocumentReader CreateDocumentReader(bool extractImages = false) - => MarkItDownCondition.IsInstalled.Value - ? new MarkItDownReader(extractImages: extractImages) - : throw new SkipTestException("MarkItDown is not installed"); + => new MarkItDownReader(extractImages: extractImages); protected override void SimpleAsserts(IngestionDocument document, string source, string expectedId) { From a0b230e46ba443137e1fccd605b5643922331dd5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 16:35:24 +0000 Subject: [PATCH 14/16] Fix StyleCop SA1210 using directive ordering and simplify ConditionalFact - Sort using directives alphabetically in EmbeddingTests.cs - Sort using directives alphabetically in ResponseCacheTester.cs - Sort using directives alphabetically in OllamaSharpChatClientIntegrationTests.cs - Remove redundant condition parameter from ConditionalFact in MarkItDownReaderTests.cs (ConditionalClass already handles this) Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../EmbeddingTests.cs | 2 +- .../ResponseCacheTester.cs | 2 +- .../OllamaSharpChatClientIntegrationTests.cs | 2 +- .../Readers/MarkItDownReaderTests.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/EmbeddingTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/EmbeddingTests.cs index 7889b5daa53..ac345200269 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/EmbeddingTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/EmbeddingTests.cs @@ -2,8 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using Microsoft.Extensions.AI.Evaluation.Reporting.Formats.Html; using Microsoft.DotNet.XUnitExtensions; +using Microsoft.Extensions.AI.Evaluation.Reporting.Formats.Html; using Xunit; namespace Microsoft.Extensions.AI.Evaluation.Reporting.Tests; diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResponseCacheTester.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResponseCacheTester.cs index d76e60efc6e..ed82e91e3f1 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResponseCacheTester.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Tests/ResponseCacheTester.cs @@ -4,8 +4,8 @@ using System; using System.Text; using System.Threading.Tasks; -using Microsoft.Extensions.Caching.Distributed; using Microsoft.DotNet.XUnitExtensions; +using Microsoft.Extensions.Caching.Distributed; using Xunit; namespace Microsoft.Extensions.AI.Evaluation.Reporting.Tests; diff --git a/test/Libraries/Microsoft.Extensions.AI.OllamaSharp.Integration.Tests/OllamaSharpChatClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.OllamaSharp.Integration.Tests/OllamaSharpChatClientIntegrationTests.cs index e37d9eb3432..807f7e5c086 100644 --- a/test/Libraries/Microsoft.Extensions.AI.OllamaSharp.Integration.Tests/OllamaSharpChatClientIntegrationTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.OllamaSharp.Integration.Tests/OllamaSharpChatClientIntegrationTests.cs @@ -6,8 +6,8 @@ using System.ComponentModel; using System.Threading; using System.Threading.Tasks; -using OllamaSharp; using Microsoft.DotNet.XUnitExtensions; +using OllamaSharp; using Xunit; namespace Microsoft.Extensions.AI; diff --git a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs index 806f306b360..3495a69a848 100644 --- a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs +++ b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs @@ -38,7 +38,7 @@ protected override void SimpleAsserts(IngestionDocument document, string source, // The original purpose of the MarkItDown library was to support text-only LLMs. // Source: https://github.com/microsoft/markitdown/issues/56#issuecomment-2546357264 // It can extract images, but the support is limited to some formats like docx. - [ConditionalFact(nameof(MarkItDownCondition.IsMarkItDownInstalled))] + [ConditionalFact] public override Task SupportsImages() => SupportsImagesCore( new("https://winprotocoldocs-bhdugrdyduf5h2e4.b02.azurefd.net/MC-SQLR/%5bMC-SQLR%5d-240423.docx")); // SQL Server Resolution Protocol. } From 28ada2f19c7cf217d20031e2afef3b1fc509ed29 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 17:32:01 +0000 Subject: [PATCH 15/16] Fix all build errors: rename file, fix using directive ordering - Rename MarkItDownConditionAttribute.cs to MarkItDownCondition.cs to match class name (SA1649) - Fix using directive ordering in DocumentReaderConformanceTests.cs (SA1210) - Fix using directive ordering in AgentQualityEvaluatorTests.cs (SA1210) - Fix using directive ordering in NLPEvaluatorTests.cs (SA1210) - Fix using directive ordering in QualityEvaluatorTests.cs (SA1210) - Fix using directive ordering in SafetyEvaluatorTests.cs (SA1210) - All using directives now properly alphabetically sorted with System namespaces first Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com> --- .../AgentQualityEvaluatorTests.cs | 5 ++--- .../NLPEvaluatorTests.cs | 5 ++--- .../QualityEvaluatorTests.cs | 5 ++--- .../SafetyEvaluatorTests.cs | 2 +- .../Readers/DocumentReaderConformanceTests.cs | 2 +- ...arkItDownConditionAttribute.cs => MarkItDownCondition.cs} | 0 6 files changed, 8 insertions(+), 11 deletions(-) rename test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/{MarkItDownConditionAttribute.cs => MarkItDownCondition.cs} (100%) diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/AgentQualityEvaluatorTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/AgentQualityEvaluatorTests.cs index fadf9a97d3f..5f99e217489 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/AgentQualityEvaluatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/AgentQualityEvaluatorTests.cs @@ -1,17 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; -using System; - using Microsoft.DotNet.XUnitExtensions; using Microsoft.Extensions.AI.Evaluation.Quality; -using Microsoft.Extensions.AI.Evaluation.Reporting.Storage; using Microsoft.Extensions.AI.Evaluation.Reporting; +using Microsoft.Extensions.AI.Evaluation.Reporting.Storage; using Microsoft.Extensions.AI.Evaluation.Tests; using Xunit; namespace Microsoft.Extensions.AI.Evaluation.Integration.Tests; diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/NLPEvaluatorTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/NLPEvaluatorTests.cs index 0ae5ff9a342..774874882e4 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/NLPEvaluatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/NLPEvaluatorTests.cs @@ -1,15 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; -using System; - using Microsoft.DotNet.XUnitExtensions; using Microsoft.Extensions.AI.Evaluation.NLP; -using Microsoft.Extensions.AI.Evaluation.Reporting.Storage; using Microsoft.Extensions.AI.Evaluation.Reporting; +using Microsoft.Extensions.AI.Evaluation.Reporting.Storage; using Xunit; namespace Microsoft.Extensions.AI.Evaluation.Integration.Tests; diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/QualityEvaluatorTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/QualityEvaluatorTests.cs index 2a60eb1f785..0ece7dfddd9 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/QualityEvaluatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/QualityEvaluatorTests.cs @@ -1,16 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; -using System; - using Microsoft.DotNet.XUnitExtensions; using Microsoft.Extensions.AI.Evaluation.Quality; -using Microsoft.Extensions.AI.Evaluation.Reporting.Storage; using Microsoft.Extensions.AI.Evaluation.Reporting; +using Microsoft.Extensions.AI.Evaluation.Reporting.Storage; using Microsoft.Extensions.AI.Evaluation.Tests; using Xunit; namespace Microsoft.Extensions.AI.Evaluation.Integration.Tests; diff --git a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/SafetyEvaluatorTests.cs b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/SafetyEvaluatorTests.cs index af050406e47..b5b641f55c9 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/SafetyEvaluatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Evaluation.Integration.Tests/SafetyEvaluatorTests.cs @@ -7,13 +7,13 @@ using System.Linq; using System.Threading.Tasks; using Azure.Identity; +using Microsoft.DotNet.XUnitExtensions; using Microsoft.Extensions.AI.Evaluation.Quality; using Microsoft.Extensions.AI.Evaluation.Reporting; using Microsoft.Extensions.AI.Evaluation.Reporting.Storage; using Microsoft.Extensions.AI.Evaluation.Safety; using Microsoft.Extensions.AI.Evaluation.Tests; using Microsoft.Extensions.AI.Evaluation.Utilities; -using Microsoft.DotNet.XUnitExtensions; using Xunit; namespace Microsoft.Extensions.AI.Evaluation.Integration.Tests; diff --git a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/DocumentReaderConformanceTests.cs b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/DocumentReaderConformanceTests.cs index bcebfa276d1..ab7def685c3 100644 --- a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/DocumentReaderConformanceTests.cs +++ b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/DocumentReaderConformanceTests.cs @@ -7,8 +7,8 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.DataIngestion.Tests.Utils; using Microsoft.DotNet.XUnitExtensions; +using Microsoft.Extensions.DataIngestion.Tests.Utils; using Xunit; namespace Microsoft.Extensions.DataIngestion.Readers.Tests; diff --git a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownConditionAttribute.cs b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownCondition.cs similarity index 100% rename from test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownConditionAttribute.cs rename to test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownCondition.cs From 0a59662dfdee1887e0af6706bb9a78ce47cfe898 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Fri, 21 Nov 2025 19:43:28 +0100 Subject: [PATCH 16/16] fix MarkItDown tests --- .../Readers/MarkItDownReaderTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs index 3495a69a848..fc49c136781 100644 --- a/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs +++ b/test/Libraries/Microsoft.Extensions.DataIngestion.Tests/Readers/MarkItDownReaderTests.cs @@ -13,7 +13,9 @@ namespace Microsoft.Extensions.DataIngestion.Readers.Tests; public class MarkItDownReaderTests : DocumentReaderConformanceTests { protected override IngestionDocumentReader CreateDocumentReader(bool extractImages = false) - => new MarkItDownReader(extractImages: extractImages); + => MarkItDownCondition.IsMarkItDownInstalled + ? new MarkItDownReader(extractImages: extractImages) + : throw new SkipTestException("MarkItDown is not installed"); protected override void SimpleAsserts(IngestionDocument document, string source, string expectedId) {