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);
}
}