|
| 1 | +using AiDotNet.Configuration; |
| 2 | +using AiDotNet.Deployment.Configuration; |
| 3 | +using AiDotNet.Enums; |
| 4 | +using Xunit; |
| 5 | +using Xunit.Abstractions; |
| 6 | + |
| 7 | +namespace AiDotNet.Tests.IntegrationTests.ConfigureMethodCoverage; |
| 8 | + |
| 9 | +/// <summary> |
| 10 | +/// Bucket 4 — Configure* methods whose ONLY observable effect is that the |
| 11 | +/// configured value lands on <see cref="AiModelResult{T,TInput,TOutput}.DeploymentConfiguration"/> |
| 12 | +/// (or on a process-wide config flag). |
| 13 | +/// </summary> |
| 14 | +/// <remarks> |
| 15 | +/// <para> |
| 16 | +/// Each test sets a NON-DEFAULT value on its config (e.g. |
| 17 | +/// <c>MaxCacheSize = 99</c>) and asserts that the SAME non-default value |
| 18 | +/// is observable on the post-build result. This screens for the systemic |
| 19 | +/// "stored on the builder but never consumed" pattern that PR #1357 / #1361 |
| 20 | +/// found across the Configure* surface. |
| 21 | +/// </para> |
| 22 | +/// <para> |
| 23 | +/// Methods covered (5 of the methods not touched by the other open PRs |
| 24 | +/// in flight — #1361 covers AdversarialRobustness, #1362 covers |
| 25 | +/// MixedPrecision, #1367 covers ModelRegistry, #1351 covers Adam, |
| 26 | +/// #1349/#1363 cover INT8): |
| 27 | +/// </para> |
| 28 | +/// <list type="bullet"> |
| 29 | +/// <item>ConfigureCaching</item> |
| 30 | +/// <item>ConfigureVersioning</item> |
| 31 | +/// <item>ConfigureABTesting</item> |
| 32 | +/// <item>ConfigureExport</item> |
| 33 | +/// <item>ConfigureGpuDiagnostics</item> |
| 34 | +/// </list> |
| 35 | +/// </remarks> |
| 36 | +[Collection("ConfigureMethodCoverage")] |
| 37 | +public class Bucket4_DeploymentMetadataTests : ConfigureMethodTestBase |
| 38 | +{ |
| 39 | + private readonly ITestOutputHelper _output; |
| 40 | + public Bucket4_DeploymentMetadataTests(ITestOutputHelper output) { _output = output; } |
| 41 | + |
| 42 | + /// <summary> |
| 43 | + /// ConfigureCaching — a non-default <c>MaxCacheSize</c> set via |
| 44 | + /// <c>ConfigureCaching</c> MUST land on <c>result.DeploymentConfiguration.Caching.MaxCacheSize</c> |
| 45 | + /// after BuildAsync. Verifies the field flows through |
| 46 | + /// <c>DeploymentConfiguration.Create</c> into the result instead of being |
| 47 | + /// silently dropped. |
| 48 | + /// </summary> |
| 49 | + [Fact] |
| 50 | + [Trait("category", "integration-configure-method")] |
| 51 | + public async Task ConfigureCaching_NonDefaultValue_LandsOnDeploymentConfiguration() |
| 52 | + { |
| 53 | + const int sentinel = 99; |
| 54 | + var (features, labels) = MakeMemorizationSet(); |
| 55 | + var loader = MakeCanaryLoader(features, labels); |
| 56 | + var model = MakeCanaryModel(); |
| 57 | + |
| 58 | + var cacheCfg = new CacheConfig { MaxCacheSize = sentinel }; |
| 59 | + var result = await new AiModelBuilder<float, Tensor<float>, Tensor<float>>() |
| 60 | + .ConfigureModel(model) |
| 61 | + .ConfigureDataLoader(loader) |
| 62 | + .ConfigureCaching(cacheCfg) |
| 63 | + .BuildAsync(); |
| 64 | + |
| 65 | + Assert.NotNull(result.DeploymentConfiguration); |
| 66 | + Assert.NotNull(result.DeploymentConfiguration!.Caching); |
| 67 | + Assert.Equal(sentinel, result.DeploymentConfiguration.Caching!.MaxCacheSize); |
| 68 | + } |
| 69 | + |
| 70 | + /// <summary> |
| 71 | + /// ConfigureVersioning — a non-default <c>DefaultVersion</c> set via |
| 72 | + /// <c>ConfigureVersioning</c> MUST land on |
| 73 | + /// <c>result.DeploymentConfiguration.Versioning.DefaultVersion</c>. |
| 74 | + /// </summary> |
| 75 | + [Fact] |
| 76 | + [Trait("category", "integration-configure-method")] |
| 77 | + public async Task ConfigureVersioning_NonDefaultValue_LandsOnDeploymentConfiguration() |
| 78 | + { |
| 79 | + const string sentinel = "v999-integration-test"; |
| 80 | + var (features, labels) = MakeMemorizationSet(); |
| 81 | + var loader = MakeCanaryLoader(features, labels); |
| 82 | + var model = MakeCanaryModel(); |
| 83 | + |
| 84 | + var versCfg = new VersioningConfig { DefaultVersion = sentinel }; |
| 85 | + var result = await new AiModelBuilder<float, Tensor<float>, Tensor<float>>() |
| 86 | + .ConfigureModel(model) |
| 87 | + .ConfigureDataLoader(loader) |
| 88 | + .ConfigureVersioning(versCfg) |
| 89 | + .BuildAsync(); |
| 90 | + |
| 91 | + Assert.NotNull(result.DeploymentConfiguration); |
| 92 | + Assert.NotNull(result.DeploymentConfiguration!.Versioning); |
| 93 | + Assert.Equal(sentinel, result.DeploymentConfiguration.Versioning!.DefaultVersion); |
| 94 | + } |
| 95 | + |
| 96 | + /// <summary> |
| 97 | + /// ConfigureABTesting — a non-default <c>DefaultTrafficSplit</c> set via |
| 98 | + /// <c>ConfigureABTesting</c> MUST land on |
| 99 | + /// <c>result.DeploymentConfiguration.ABTesting.DefaultTrafficSplit</c>. |
| 100 | + /// </summary> |
| 101 | + [Fact] |
| 102 | + [Trait("category", "integration-configure-method")] |
| 103 | + public async Task ConfigureABTesting_NonDefaultValue_LandsOnDeploymentConfiguration() |
| 104 | + { |
| 105 | + const double sentinel = 0.123; |
| 106 | + var (features, labels) = MakeMemorizationSet(); |
| 107 | + var loader = MakeCanaryLoader(features, labels); |
| 108 | + var model = MakeCanaryModel(); |
| 109 | + |
| 110 | + var abCfg = new ABTestingConfig { Enabled = true, DefaultTrafficSplit = sentinel }; |
| 111 | + var result = await new AiModelBuilder<float, Tensor<float>, Tensor<float>>() |
| 112 | + .ConfigureModel(model) |
| 113 | + .ConfigureDataLoader(loader) |
| 114 | + .ConfigureABTesting(abCfg) |
| 115 | + .BuildAsync(); |
| 116 | + |
| 117 | + Assert.NotNull(result.DeploymentConfiguration); |
| 118 | + Assert.NotNull(result.DeploymentConfiguration!.ABTesting); |
| 119 | + Assert.Equal(sentinel, result.DeploymentConfiguration.ABTesting!.DefaultTrafficSplit); |
| 120 | + } |
| 121 | + |
| 122 | + /// <summary> |
| 123 | + /// ConfigureExport — a non-default <c>TargetPlatform</c> set via |
| 124 | + /// <c>ConfigureExport</c> MUST land on |
| 125 | + /// <c>result.DeploymentConfiguration.Export.TargetPlatform</c>. Verifies |
| 126 | + /// the export config flows into the result so downstream |
| 127 | + /// <c>ExportToOnnx</c> / <c>ExportToCoreML</c> / etc. methods see it. |
| 128 | + /// </summary> |
| 129 | + [Fact] |
| 130 | + [Trait("category", "integration-configure-method")] |
| 131 | + public async Task ConfigureExport_NonDefaultTarget_LandsOnDeploymentConfiguration() |
| 132 | + { |
| 133 | + const TargetPlatform sentinel = TargetPlatform.TFLite; |
| 134 | + var (features, labels) = MakeMemorizationSet(); |
| 135 | + var loader = MakeCanaryLoader(features, labels); |
| 136 | + var model = MakeCanaryModel(); |
| 137 | + |
| 138 | + var expCfg = new ExportConfig { TargetPlatform = sentinel }; |
| 139 | + var result = await new AiModelBuilder<float, Tensor<float>, Tensor<float>>() |
| 140 | + .ConfigureModel(model) |
| 141 | + .ConfigureDataLoader(loader) |
| 142 | + .ConfigureExport(expCfg) |
| 143 | + .BuildAsync(); |
| 144 | + |
| 145 | + Assert.NotNull(result.DeploymentConfiguration); |
| 146 | + Assert.NotNull(result.DeploymentConfiguration!.Export); |
| 147 | + Assert.Equal(sentinel, result.DeploymentConfiguration.Export!.TargetPlatform); |
| 148 | + } |
| 149 | + |
| 150 | + /// <summary> |
| 151 | + /// ConfigureGpuDiagnostics — sets the process-wide |
| 152 | + /// <c>GpuDiagnosticsConfig.Level</c>. Picks <c>Verbose</c> as the |
| 153 | + /// sentinel because the test fixture's CPU-mode default leaves |
| 154 | + /// <c>Level</c> at <c>Silent</c>; asserts the explicit Configure call |
| 155 | + /// flipped the global to the requested value. |
| 156 | + /// </summary> |
| 157 | + [Fact] |
| 158 | + [Trait("category", "integration-configure-method")] |
| 159 | + public async Task ConfigureGpuDiagnostics_LevelOverride_AppliesToGlobalConfig() |
| 160 | + { |
| 161 | + const GpuDiagnosticLevel sentinel = GpuDiagnosticLevel.Verbose; |
| 162 | + var (features, labels) = MakeMemorizationSet(); |
| 163 | + var loader = MakeCanaryLoader(features, labels); |
| 164 | + var model = MakeCanaryModel(); |
| 165 | + |
| 166 | + var previous = GpuDiagnosticsConfig.Level; |
| 167 | + try |
| 168 | + { |
| 169 | + var diag = new GpuDiagnosticsOptions { Level = sentinel }; |
| 170 | + var result = await new AiModelBuilder<float, Tensor<float>, Tensor<float>>() |
| 171 | + .ConfigureModel(model) |
| 172 | + .ConfigureDataLoader(loader) |
| 173 | + .ConfigureGpuDiagnostics(diag) |
| 174 | + .BuildAsync(); |
| 175 | + |
| 176 | + Assert.Equal(sentinel, GpuDiagnosticsConfig.Level); |
| 177 | + } |
| 178 | + finally |
| 179 | + { |
| 180 | + // Restore the previous global so we don't bleed state into |
| 181 | + // other tests that read GpuDiagnosticsConfig.Level. |
| 182 | + GpuDiagnosticsConfig.Level = previous; |
| 183 | + } |
| 184 | + } |
| 185 | +} |
0 commit comments