using System.Diagnostics; using System.Runtime.Versioning; using SQLInsight.Core.Servers; using SQLInsight.Servers; namespace SQLInsight.Servers.Tests; /// The local CIM tests run alone: WMI on a busy test machine (SQL Server containers, every test assembly at once) is slow enough already. [CollectionDefinition(Name, DisableParallelization = true)] public sealed class LocalCimGroup { public const string Name = "Local CIM"; } /// /// One local session for the class, with WMI warmed up once: the first query of a class can take long on a cold or busy /// machine (the provider loads), which is not what the tests are about. /// [SupportedOSPlatform("windows")] public sealed class LocalCimFixture : IAsyncLifetime { /// Generous: the whole test suite (with SQL Server containers) may run at the same time. public static readonly ServerConnectionOptions Options = new() { OperationTimeout = TimeSpan.FromMinutes(2) }; public CimWindowsServerSource? Source { get; private set; } public IWindowsServerSession? Session { get; private set; } /// How long the warm-up took, for the assertion messages. public string WarmUp { get; private set; } = "no warm-up"; public async Task InitializeAsync() { if (!OperatingSystem.IsWindows()) { return; } using var stop = new CancellationTokenSource(TimeSpan.FromMinutes(5)); var watch = Stopwatch.StartNew(); var source = new CimWindowsServerSource(Options); var session = await Once(() => source.ConnectAsync(Environment.MachineName, stop.Token)); Source = source; Session = session; // One read of every class the tests use; a timeout is retried once (a cold provider), anything else fails the tests. await Once(() => session.GetOperatingSystemAsync(stop.Token)); await Once(() => session.GetPerformanceAsync(DateTimeOffset.UtcNow, stop.Token)); await Once(() => session.GetDisksAsync(stop.Token)); await Once(() => session.GetServiceAsync("EventLog", stop.Token)); await Once(() => session.GetEventsAsync(DateTimeOffset.UtcNow.AddMinutes(-5), 1, stop.Token)); await Once(() => session.GetTasksAsync(_ => false, 1, stop.Token)); WarmUp = $"warm-up {watch.Elapsed.TotalSeconds:0.0} s"; static async Task Once(Func> read) { try { return await read(); } catch (ServerAccessException ex) when (ex.Problem == ServerAccessProblem.Timeout) { return await read(); } } } public Task DisposeAsync() { Session?.Dispose(); return Task.CompletedTask; } } /// /// The CIM implementation against this computer (a local CIM session, as for the agent's own server). Remote access over /// WS-Man/DCOM with least privilege cannot be tested here; it needs a domain with a second server. /// [Trait("Category", "Windows")] [Collection(LocalCimGroup.Name)] [SupportedOSPlatform("windows")] public sealed class LocalCimTests(LocalCimFixture cim) : IClassFixture { /// Every call in a test shares this: a hang is a failure within minutes, never a stuck test run. private static readonly TimeSpan TestDeadline = TimeSpan.FromMinutes(5); private readonly List _timings = []; private string Timings => cim.WarmUp + "; " + string.Join(", ", _timings); private async Task Timed(string what, Func> read) { var watch = Stopwatch.StartNew(); try { return await read(); } finally { _timings.Add($"{what} {watch.Elapsed.TotalSeconds:0.0} s"); } } [Fact] public async Task Reads_os_performance_disks_services_events_and_tasks() { if (cim.Session is not { } session) { return; } using var stop = new CancellationTokenSource(TestDeadline); var os = await Timed("os", () => session.GetOperatingSystemAsync(stop.Token)); var first = await Timed("performance", () => session.GetPerformanceAsync(DateTimeOffset.UtcNow, stop.Token)); await Task.Delay(1500, stop.Token); var second = await Timed("performance", () => session.GetPerformanceAsync(DateTimeOffset.UtcNow, stop.Token)); var sample = ServerPerformanceMath.Sample(first, second, os); var disks = await Timed("disks", () => session.GetDisksAsync(stop.Token)); var services = await Timed("services", () => session.GetServicesAsync(stop.Token)); var service = await Timed("service", () => session.GetServiceAsync(services[0].Name, stop.Token)); var missing = await Timed("missing service", () => session.GetServiceAsync("SQLInsight-findes-ikke", stop.Token)); var events = await Timed("events", () => session.GetEventsAsync(DateTimeOffset.UtcNow.AddHours(-1), 50, stop.Token)); var tasks = await Timed("tasks", () => session.GetTasksAsync(name => !TaskResults.IsMicrosoftTask(name), 1, stop.Token)); Assert.True(session.Protocol == "lokal", Timings); Assert.Contains("Windows", os.Caption, StringComparison.OrdinalIgnoreCase); Assert.True(os.LastBootUpTime < DateTimeOffset.UtcNow, Timings); Assert.True(os.TotalMemoryMb > 0, Timings); Assert.True(os.LogicalProcessors > 0, Timings); Assert.InRange(sample.CpuPercent!.Value, 0, 100); Assert.True(sample.AvailableMb > 0, Timings); Assert.InRange(sample.CommitPercent!.Value, 0, 100); Assert.True(disks.Count > 0, Timings); Assert.All(disks, d => Assert.False(d.Volume.StartsWith(@"\\?\", StringComparison.Ordinal))); Assert.All(disks, d => Assert.InRange(d.FreeGb, 0, d.SizeGb)); Assert.True(services.Any(s => s.IsRunning && s.IsAutomatic), Timings); Assert.True(services[0].Name == service?.Name, Timings); Assert.True(missing is null, Timings); Assert.True(events.Method == "Event Log (RPC)", $"{events.Method} {events.Note}; {Timings}"); Assert.True(events.Entries.Count <= 2 * 50, Timings); Assert.All(events.Entries, e => Assert.InRange(e.Level, 1, 2)); Assert.True(tasks.Visible > 0, Timings); Assert.True(tasks.Watched.Count <= 1, Timings); Assert.All(tasks.Watched, t => Assert.False(TaskResults.IsMicrosoftTask(t.FullName))); } [Fact] public async Task The_test_reports_every_area() { if (cim.Source is not { } source) { return; } using var stop = new CancellationTokenSource(TestDeadline); var result = await Timed("test", () => WindowsServerTester.TestAsync(source, "localhost", "test", DateTimeOffset.UtcNow, TestDeadline, stop.Token)); Assert.True(result.Reachable, $"{result.Error}; {Timings}"); Assert.Equal(ServerArea.All, result.Areas.Select(a => a.Area)); Assert.All(result.Areas, a => Assert.True(a.Ok, $"{a.Area}: {a.Detail}; {Timings}")); } [Fact] public async Task An_unknown_host_is_unreachable_with_a_reason() { if (!OperatingSystem.IsWindows()) { return; } using var stop = new CancellationTokenSource(TestDeadline); var source = new CimWindowsServerSource(new ServerConnectionOptions { OperationTimeout = TimeSpan.FromSeconds(5) }); var error = await Assert.ThrowsAsync(() => Timed("connect", () => source.ConnectAsync("sqlinsight-findes-ikke.invalid", stop.Token))); Assert.True(error.Problem is ServerAccessProblem.Unreachable or ServerAccessProblem.Timeout, $"{error.Message}; {Timings}"); Assert.Contains("sqlinsight-findes-ikke.invalid", error.Message, StringComparison.Ordinal); } }