Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
d864664
Start the #5633 branch: MCP tool audit fixes
erikdarlingdata Oct 12, 2026
0a78fb7
Name spilled pages, robust-frame baseline median and the sigma cap in…
erikdarlingdata Oct 12, 2026
79ca0c7
MCP audit items 17, 18, 21: panel server names resolve through the re…
erikdarlingdata Oct 12, 2026
02b73a5
Merge origin/dev into fix/5633-L2
erikdarlingdata Oct 12, 2026
eb656f3
MCP tool audit: percent growth in get_database_sizes, NULLS LAST and …
erikdarlingdata Oct 12, 2026
62a2e5b
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 12, 2026
08f4218
Merge remote-tracking branch 'origin/dev' into fix/5633-L4
erikdarlingdata Oct 12, 2026
1a0bf37
FinOps: one CPU right-sizing verdict per server, null cost in the inv…
erikdarlingdata Oct 12, 2026
6cfa952
Leave dropped databases out of FinOps, clutter and top-procedure read…
erikdarlingdata Oct 12, 2026
894159d
Fix audit_config edition, compare_analysis overlapping windows, get_r…
erikdarlingdata Oct 12, 2026
ef63a59
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 12, 2026
06b114a
FinOps golden: compression names its database (#5633 item 4)
erikdarlingdata Oct 12, 2026
d3a29dc
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 12, 2026
2fe471d
Lite twin test for the waiting-tasks rollup (#5633 item 18)
erikdarlingdata Oct 12, 2026
bd21e4e
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 12, 2026
336a5ac
Merge remote-tracking branch 'origin/dev' into fix/5633-L3
erikdarlingdata Oct 12, 2026
9d195d6
MCP: truthful empty reasons for top procedures and trace flags, min s…
erikdarlingdata Oct 12, 2026
1e90222
Merge shared branch into L2
erikdarlingdata Oct 12, 2026
f8bfea7
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 12, 2026
db39584
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 12, 2026
bd2caae
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 12, 2026
9dfde66
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 12, 2026
aa7415e
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 12, 2026
48116b6
Merge-fix #5633: Lite FinOps tests follow the logical-CPU rule, delet…
erikdarlingdata Oct 12, 2026
7abb9bc
Lite: pin the Parallelism wait category (parity with Darling, #5633 i…
erikdarlingdata Oct 12, 2026
3b779b1
Compose scope resolves exact names only; current-database set honours…
erikdarlingdata Oct 12, 2026
787fed2
Merge origin/fix/5633-mcp-tool-audit
erikdarlingdata Oct 12, 2026
3a2729e
WaitingTasksRollup5633Tests joins the live-postgres collection (hygie…
erikdarlingdata Oct 12, 2026
47eab65
waiting tasks: resource, blocker and duration from the longest-waitin…
erikdarlingdata Oct 12, 2026
b043bec
CurrentDatabaseScope: cancellation token last (CA1068)
erikdarlingdata Oct 12, 2026
0e78bcf
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 12, 2026
5f794e5
Trace-flag change diff reads successful empty trace_flags runs as emp…
erikdarlingdata Oct 12, 2026
77389ad
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 12, 2026
33b0375
spilled-pages wording on the WPF grids and tool guides; spilling_quer…
erikdarlingdata Oct 12, 2026
2e18105
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 12, 2026
a8fd813
Merge remote-tracking branch 'origin/dev' into fix/5633-F4
erikdarlingdata Oct 12, 2026
77c2935
Merge remote-tracking branch 'origin/dev' into fix/5633-F3
erikdarlingdata Oct 12, 2026
168d822
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 12, 2026
49760c7
Trace-flag change diff: bound the empty-run baseline at the retention…
erikdarlingdata Oct 12, 2026
d406582
Merge remote-tracking branch 'origin/dev' into fix/5633-F5
erikdarlingdata Oct 12, 2026
2a688bf
Grant the MCP role the collection-caveat writes analyze_server needs,…
erikdarlingdata Oct 12, 2026
d469d6b
Merge origin/dev into fix/5633-W2A
erikdarlingdata Oct 12, 2026
989495f
Fix review round 2: serialize live McpToolAudit5633L3 tests, count di…
erikdarlingdata Oct 12, 2026
2bb8c8a
Merge remote-tracking branch 'origin/dev' into fix/5633-F7
erikdarlingdata Oct 12, 2026
3e3ed21
Guard trait on the trace_flags failed-run pins (#5633 item 19 review)
erikdarlingdata Oct 12, 2026
59feb9b
Clutter as_of keeps databases dropped after the window; ranking exclu…
erikdarlingdata Oct 12, 2026
a9aa61c
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 12, 2026
12bfdb3
Merge origin/fix/5633-mcp-tool-audit into fix/5633-W2A
erikdarlingdata Oct 12, 2026
c160aaf
Size the managed store's background workers for every TimescaleDB pol…
erikdarlingdata Oct 12, 2026
db58370
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 12, 2026
667fd93
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 12, 2026
44e3869
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 12, 2026
d58eb71
Mark the compose scope live test as own-store (#5633)
erikdarlingdata Oct 12, 2026
0591468
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 12, 2026
6c44282
Withdraw the MCP caveat-table grants: only the scheduled analysis pas…
erikdarlingdata Oct 12, 2026
3477efd
Merge remote-tracking branch 'origin/dev' into fix/5633-F8
erikdarlingdata Oct 12, 2026
c56a30c
Size the managed store's background workers by host RAM, between the …
erikdarlingdata Oct 12, 2026
9ff6fd9
Merge remote-tracking branch 'origin/dev' into fix/5633-F9
erikdarlingdata Oct 12, 2026
7932ef4
Update the analysis-service source and reflection pins for the persis…
erikdarlingdata Oct 12, 2026
4535bb3
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 12, 2026
35ca27c
Update the Darling pins that read source the #5633 lanes changed: aud…
erikdarlingdata Oct 13, 2026
794b3ac
Empty-reason run facts honour the as_of anchor; current-database read…
erikdarlingdata Oct 13, 2026
ba446fa
Merge remote-tracking branch 'origin/dev' into fix/5633-F11
erikdarlingdata Oct 13, 2026
47b64fe
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 13, 2026
71365f5
#5633 review: waiting-tasks rollup groups on rtrim(wait_type), one em…
erikdarlingdata Oct 13, 2026
0095611
Merge remote-tracking branch 'origin/dev' into fix/5633-F13
erikdarlingdata Oct 13, 2026
e401d9b
Top procedures, Query Store clutter and index analysis say which drop…
erikdarlingdata Oct 13, 2026
da6c957
Merge remote-tracking branch 'origin/fix/5633-mcp-tool-audit' into fi…
erikdarlingdata Oct 13, 2026
d44e441
FinOps index analysis and recommendations pass the host logger to the…
erikdarlingdata Oct 13, 2026
f491226
Merge remote-tracking branch 'origin/dev' into fix/5633-F14
erikdarlingdata Oct 13, 2026
8889110
Merge origin/dev into fix/5633-mcp-tool-audit
erikdarlingdata Oct 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ namespace Darling.Tests;
[Trait("Reads", "Lite")]
public sealed class AnalysisWindowEmptyRuleParityTests
{
private const string PassEntry = "public async Task<List<AnalysisFinding>> AnalyzeAsync(AnalysisContext context)";
private const string PassEntry = "public async Task<List<AnalysisFinding>> AnalyzeAsync(AnalysisContext context";
private const string UnobservedBranch = "if (context.ObservedDurationMs <= 0)";
private const string ObservedZeroBranch = "if (facts.Count == 0)";
private const string PartialCaveat = "if (context.Coverage is { IsPartial: true } partial)";
Expand Down
2 changes: 1 addition & 1 deletion Darling/Darling.Tests/AuditConfigEditionNameTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public void AuditConfig_AsksTheEngineQuestion_BeforeItSaysTheConfigCollectorMayN
Assert.True(gate < body.LastIndexOf("\"no_config_data\"", StringComparison.Ordinal));

/* The private edition switch is gone: the tool names an edition through the shared table only. */
Assert.Contains("AuditEditionName(edition)", body, StringComparison.Ordinal);
Assert.Contains("AuditEditionLabel(propertiesRow?.Edition, edition)", body, StringComparison.Ordinal);
Assert.DoesNotContain("(HADR)", body, StringComparison.Ordinal);
Assert.DoesNotContain("Azure Synapse serverless\"", body, StringComparison.Ordinal);
}
Expand Down
5 changes: 4 additions & 1 deletion Darling/Darling.Tests/AzureSiblingSizePayloadTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ public void TheLatestSnapshotSql_SelectsTheFileId_AsTheColumnTheReaderMapsLast()
{
var sql = DarlingObjectStatsReader.DatabaseSizeLatestSql;

Assert.Matches(new Regex(@"volume_free_mb,\s*file_id\s*FROM v_database_size_stats"), sql);
/* #5633 item 6 appended the percent-growth pair AFTER file_id, so file_id is no longer the last column. The
reader maps by ordinal (file_id at 11, then is_percent_growth 12, growth_pct 13); this pins that exact
order, so a column slipped in before file_id would shift the mapping and fail here. */
Assert.Matches(new Regex(@"volume_free_mb,\s*file_id,\s*is_percent_growth,\s*CAST\(growth_pct AS double precision\) AS growth_pct\s*FROM v_database_size_stats"), sql);
}

[Fact]
Expand Down
8 changes: 4 additions & 4 deletions Darling/Darling.Tests/AzureSqlDatabaseMemoryScopeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -410,14 +410,14 @@ public void CpuRightSizingRecommendation_OnAzureSqlDatabase_NamesTheVcores_InThe
[InlineData(3)]
[InlineData(8)]
[InlineData(0)]
public void CpuRightSizingRecommendation_OffAzureSqlDatabase_KeepsTheWordCores(int engineEdition)
public void CpuRightSizingRecommendation_OffAzureSqlDatabase_SaysLogicalCpus(int engineEdition)
{
var rec = ViewerDataService.BuildCpuRightSizingRecommendation(CpuRow(engineEdition), monthlyCost: 1_000m);

Assert.NotNull(rec);
Assert.StartsWith("CPU over-provisioned (32 cores, P95 = ", rec!.Finding, StringComparison.Ordinal);
Assert.Contains("across 32 cores. Consider reducing to ~", rec.Detail, StringComparison.Ordinal);
Assert.EndsWith(" cores.", rec.Detail, StringComparison.Ordinal);
Assert.StartsWith("CPU over-provisioned (32 logical CPUs, P95 = ", rec!.Finding, StringComparison.Ordinal);
Assert.Contains("across 32 logical CPUs. Consider reducing to ~", rec.Detail, StringComparison.Ordinal);
Assert.EndsWith(" logical CPUs.", rec.Detail, StringComparison.Ordinal);
Assert.DoesNotContain("vCores", rec.Finding + rec.Detail, StringComparison.Ordinal);
Assert.True(rec.EstMonthlySavings > 0, "a server with a budget still gets its savings estimate");
}
Expand Down
4 changes: 2 additions & 2 deletions Darling/Darling.Tests/AzureSqlDatabaseOwnFiguresTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -422,13 +422,13 @@ public void CpuCountUnit_IsVcoresOnAzureSqlDatabase_AndCpusEverywhereElse()
}

[Fact]
public void CpuCoreNoun_IsVcoresOnAzureSqlDatabase_AndCoresEverywhereElse()
public void CpuCoreNoun_IsVcoresOnAzureSqlDatabase_AndLogicalCpusEverywhereElse()
{
/* The FinOps CPU right-sizing text prints the utilization read's count: the vCores on an Azure SQL Database, which it
names the way the utilization card does, and a CPU count everywhere else, in the word it has always used. */
Assert.Equal("vCores", ServerHardwareScope.CpuCoreNoun(5));
foreach (var engineEdition in new int?[] { 1, 2, 3, 4, 8, null })
Assert.Equal("cores", ServerHardwareScope.CpuCoreNoun(engineEdition));
Assert.Equal("logical CPUs", ServerHardwareScope.CpuCoreNoun(engineEdition));
}

[Fact]
Expand Down
2 changes: 2 additions & 0 deletions Darling/Darling.Tests/CiComposeWorkerSizingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public sealed class CiComposeWorkerSizingTests
/* The product's formula, restated ONCE — same as CiClusterWorkerSizingTests. Both this test and the
compose file are re-derived from the live catalog count on every run, so neither can be right about
a stale hypertable count. */
/* The compose file keeps the FLOOR (hypertables + 2): a container has no host-RAM figure to size from, and
a 2 GB container is at the floor of the managed RAM rule anyway (#5633 item 14). */
private static int ExpectedBackgroundWorkers => TimescaleSupport.HypertableCount + 2;

private static int ExpectedWorkerProcesses => 3 + ExpectedBackgroundWorkers + 8;
Expand Down
2 changes: 1 addition & 1 deletion Darling/Darling.Tests/CollectionCaveatsParityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public void BothPasses_CarryTheFailuresAndTheFactCountsOut_AndBothReadsReturnThe
{
foreach (var (sku, source) in Services())
{
var passStart = source.IndexOf("public async Task<List<AnalysisFinding>> AnalyzeAsync(AnalysisContext context)", StringComparison.Ordinal);
var passStart = source.IndexOf("public async Task<List<AnalysisFinding>> AnalyzeAsync(AnalysisContext context", StringComparison.Ordinal);
const string readEntry = "public async Task<(List<Fact> Facts, WindowCoverage? Coverage, CollectionCaveatState Caveats)> CollectAndScoreFactsAsync(";
var readStart = source.IndexOf(readEntry, passStart, StringComparison.Ordinal);
var readEnd = source.IndexOf("ComparePeriodsAsync(", readStart, StringComparison.Ordinal);
Expand Down
193 changes: 193 additions & 0 deletions Darling/Darling.Tests/CollectorRunEmptyReasonTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*
* Copyright (c) 2026 Erik Darling, Darling Data LLC
*
* This file is part of the SQL Server Performance Monitor.
*
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
*/

using System;
using System.Globalization;
using System.Linq;
using System.Text.Json;
using PerformanceMonitor.Common;
using PerformanceMonitor.Darling.Service.Mcp;
using PerformanceMonitor.Darling.Viewer;
using Xunit;

namespace Darling.Tests;

/// <summary>
/// #5633 items 3, 19 and 9. An empty read says WHY from the collector's own run history, and the one shared rule
/// (<see cref="CollectorRunFacts"/>, <see cref="ConfigValuesMatch"/>) is what every surface calls. Each assertion fails
/// on the old shape: the old procedure text was returned for every empty page, the old trace-flag text claimed fewer
/// than two snapshots whatever the log said, and the old match flag was plain equality.
/// </summary>
public sealed class CollectorRunEmptyReasonTests
{
private static readonly DateTime Last = new(2026, 10, 12, 8, 30, 0, DateTimeKind.Utc);

private static string Field(string json, string name) =>
JsonDocument.Parse(json).RootElement.GetProperty(name).GetString()!;

[Theory]
[InlineData(0)]
[InlineData(1)]
public void ProcedureStats_BeforeTwoSuccessfulRuns_KeepsTheTwoCyclesReason(int runs)
{
var json = DarlingMcpDataTools.EmptyProcedureStatsStatus(new CollectorRunFacts(runs, runs, Last, 0), 24);

Assert.Equal("unavailable", Field(json, "status"));
Assert.Contains("two collection cycles", Field(json, "message"), StringComparison.Ordinal);
}

/// <summary>Review L-b: the UTC stamp in these sentences is a custom format, whose ':' is the CURRENT culture's time separator.
/// On a host whose culture uses another one the stamp was mangled; the sentences format with the invariant culture.</summary>
[Fact]
public void TheUtcStamps_UseTheInvariantTimeSeparator_WhateverTheHostCulture()
{
var odd = (CultureInfo)CultureInfo.InvariantCulture.Clone();
odd.DateTimeFormat.TimeSeparator = ".";
var saved = CultureInfo.CurrentCulture;
try
{
CultureInfo.CurrentCulture = odd;
var gap = new CollectorRunFacts(2, 0, Last, 0).ProcedureStatsEmpty(6).Message;
var traceFlags = new CollectorRunFacts(1, 0, Last, 0).NoTraceFlagsEnabled();

Assert.Contains("2026-10-12 08:30:00 UTC", gap, StringComparison.Ordinal);
Assert.Contains("2026-10-12 08:30:00 UTC", traceFlags, StringComparison.Ordinal);
}
finally
{
CultureInfo.CurrentCulture = saved;
}
}

[Fact]
public void ProcedureStats_AfterTheCollectorHasRun_SaysNoProcedureExecuted()
{
var json = DarlingMcpDataTools.EmptyProcedureStatsStatus(new CollectorRunFacts(2, 30, Last, 0), 24);

Assert.Equal("empty", Field(json, "status"));
var message = Field(json, "message");
Assert.Contains("No procedure executed in the last 24h", message, StringComparison.Ordinal);
Assert.DoesNotContain("two collection cycles", message, StringComparison.Ordinal);
}

/// <summary>#5633 review (finding D): a read that left databases out claims only the ones the server still has, and never says the
/// collector "recorded nothing for it". The gap and new-collector branches are unchanged by the count.</summary>
[Fact]
public void ProcedureStats_AfterALeftOutDatabase_ClaimsOnlyTheDatabasesTheServerStillHas()
{
var json = DarlingMcpDataTools.EmptyProcedureStatsStatus(new CollectorRunFacts(2, 30, Last, 0), 24, databasesLeftOut: 3);

Assert.Equal("empty", Field(json, "status"));
var message = Field(json, "message");
Assert.DoesNotContain("recorded nothing for it", message, StringComparison.Ordinal);
Assert.DoesNotContain("matches this request", message, StringComparison.Ordinal);
Assert.Contains("No procedure ran in the last 24h in a database this server still has", message, StringComparison.Ordinal);
Assert.Contains("3 database(s) the server no longer lists", message, StringComparison.Ordinal);
Assert.Equal(new CollectorRunFacts(2, 0, Last, 0).ProcedureStatsEmpty(24).Message, new CollectorRunFacts(2, 0, Last, 0).ProcedureStatsEmpty(24, 3).Message);
Assert.Equal(new CollectorRunFacts(1, 1, Last, 0).ProcedureStatsEmpty(24).Message, new CollectorRunFacts(1, 1, Last, 0).ProcedureStatsEmpty(24, 3).Message);
}

[Fact]
public void DatabasesLeftOut_ListsTheFirstTwentyNames_AndTheNoteCarriesTheFullCount()
{
var all = DatabasesLeftOut.Distinct(Enumerable.Range(0, 25).Select(i => $"Db{i:D2}").Concat(new[] { "Db00", " " }));

Assert.Equal(25, all!.Count);
Assert.Equal(20, DatabasesLeftOut.Named(all)!.Count);
Assert.Contains("25 in all", DatabasesLeftOut.Note(all), StringComparison.Ordinal);
Assert.Null(DatabasesLeftOut.Distinct(new string?[] { null, "" }));
Assert.Null(DatabasesLeftOut.Note(null));

var wrapped = McpHelpers.WithDatabasesLeftOut(McpHelpers.Status("empty", "m"), all)!;
using var doc = JsonDocument.Parse(wrapped);
Assert.Equal(20, doc.RootElement.GetProperty("databases_left_out").GetArrayLength());
Assert.Equal(McpHelpers.Status("empty", "m"), McpHelpers.WithDatabasesLeftOut(McpHelpers.Status("empty", "m"), null));
}

[Fact]
public void ProcedureStats_WithNoRunInTheWindow_IsAGapNotAnAllClear()
{
var json = DarlingMcpDataTools.EmptyProcedureStatsStatus(new CollectorRunFacts(2, 0, Last, 0), 6);

Assert.Equal("unavailable", Field(json, "status"));
Assert.Contains("no successful run inside the last 6h", Field(json, "message"), StringComparison.Ordinal);
}

[Fact]
public void TraceFlagChanges_AfterARunThatStoredNothing_SaysNoFlagIsEnabled()
{
var facts = new CollectorRunFacts(1, null, Last, 0);

var json = DarlingMcpConfigHistoryTools.NoTraceFlagChanges("srv", 168, 0, McpWindowNotice.Unavailable, facts);

Assert.Equal("empty", Field(json, "status"));
var message = Field(json, "message");
Assert.Contains("No trace flags are enabled as of", message, StringComparison.Ordinal);
Assert.DoesNotContain("fewer than two", message, StringComparison.Ordinal);
Assert.Equal(0, JsonDocument.Parse(json).RootElement.GetProperty("hints").GetProperty("snapshot_count").GetInt32());
}

[Fact]
public void TraceFlagChanges_AfterFlagsWereCapturedThenNone_SaysWhyTheRemovalIsNotListed()
{
var facts = new CollectorRunFacts(2, null, Last, 0);

var message = Field(DarlingMcpConfigHistoryTools.NoTraceFlagChanges("srv", 168, 1, McpWindowNotice.Unavailable, facts), "message");

Assert.Contains("No trace flags are enabled as of", message, StringComparison.Ordinal);
Assert.Contains("counts as an empty capture", message, StringComparison.Ordinal);
}

[Theory]
[InlineData(0, null, 0)]
[InlineData(1, 4L, 1)]
public void TraceFlagChanges_WithoutAnEmptyRun_KeepTheFewerThanTwoSnapshotsText(long runs, long? rows, int snapshots)
{
var facts = new CollectorRunFacts(runs, null, runs == 0 ? null : Last, rows);

var message = Field(DarlingMcpConfigHistoryTools.NoTraceFlagChanges("srv", 168, snapshots, McpWindowNotice.Unavailable, facts), "message");

Assert.Contains("fewer than two config snapshots", message, StringComparison.Ordinal);
}

[Fact]
public void TraceFlags_EmptyReadAfterARunThatStoredNothing_SaysNoFlagIsEnabled()
{
var after = Field(DarlingMcpConfigTools.EmptyTraceFlagsStatus(new CollectorRunFacts(1, null, Last, 0)), "message");
var never = Field(DarlingMcpConfigTools.EmptyTraceFlagsStatus(CollectorRunFacts.None), "message");

Assert.Contains("No trace flags are enabled as of", after, StringComparison.Ordinal);
Assert.Contains("config collector has not run yet", never, StringComparison.Ordinal);
}

[Fact]
public void RunFactsReaderSql_CountsOnlySuccessfulRuns()
{
Assert.Contains("status = 'SUCCESS'", DarlingCollectorRunFactsReader.LatestSuccessSql, StringComparison.Ordinal);
Assert.Contains("status = 'SUCCESS'", DarlingCollectorRunFactsReader.RunsInWindowSql, StringComparison.Ordinal);
Assert.Contains("LIMIT $3", DarlingCollectorRunFactsReader.LatestSuccessSql, StringComparison.Ordinal);
/* #5633 review: an as_of read bounds the newest run at the anchor, the same text otherwise. */
Assert.Contains("collection_time <= $4", DarlingCollectorRunFactsReader.LatestSuccessAtOrBeforeSql, StringComparison.Ordinal);
Assert.Contains("status = 'SUCCESS'", DarlingCollectorRunFactsReader.LatestSuccessAtOrBeforeSql, StringComparison.Ordinal);
Assert.Contains("LIMIT $3", DarlingCollectorRunFactsReader.LatestSuccessAtOrBeforeSql, StringComparison.Ordinal);
}

[Theory]
[InlineData("min server memory (MB)", 0, 16, true)]
[InlineData("min server memory (MB)", 0, 0, true)]
[InlineData("min server memory (MB)", 0, 32, false)]
[InlineData("min server memory (MB)", 16, 0, false)]
[InlineData("max server memory (MB)", 0, 16, false)]
[InlineData("max degree of parallelism", 8, 4, false)]
public void MinServerMemoryZeroRunsAsSixteen_OnlyForThatSetting_OnEverySurface(string name, long configured, long inUse, bool expected)
{
Assert.Equal(expected, ConfigValuesMatch.Matches(name, configured, inUse));
Assert.Equal(expected, new DarlingCurrentConfigReader.ServerConfigReadRow(name, configured, inUse, true, true).ValuesMatch);
Assert.Equal(expected, new ServerConfigRow { ConfigurationName = name, ValueConfigured = configured, ValueInUse = inUse }.ValuesMatch);
}
}
Loading
Loading