Skip to content

chore: 🔧 move display list logging into service, refactor #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 4 additions & 11 deletions DisplayDetective.CommandLineApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,11 @@
var services = context.GetHost().Services;
var logger = services.GetRequiredService<ILogger<Program>>();
var service = services.GetRequiredService<IDisplayListService>();
logger.LogInformation("🔍 Scanning displays");
var displays = service.GetDisplays();
logger.LogInformation("✨ Found {count} display(s)", displays.Count);
for (int i = 0; i < displays.Count; i++)
{
var display = displays[i];
logger.LogInformation("\n📺 Display {i}\n{display}", i + 1, display.ToMultilineString());
}
service.ListDisplays();
});

var monitorCmd = new Command("monitor");
monitorCmd.SetHandler(async context =>
monitorCmd.SetHandler(static async context =>
{
var token = context.GetCancellationToken();
var services = context.GetHost().Services;
Expand Down Expand Up @@ -59,9 +52,9 @@
{
services.Configure<EventLogSettings>(host.Configuration.GetSection("Logging:EventLog"));
}
services.AddTransient((_) =>
services.AddTransient((provider) =>
{
var factory = new DisplayListServiceFactory(OperatingSystem.IsWindows());
var factory = new DisplayListServiceFactory(provider, OperatingSystem.IsWindows());
return factory.Create();
});
services.AddTransient((_) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Diagnostics;
using System.Reflection.Emit;

using DisplayDetective.Library.Common;

Expand All @@ -26,16 +25,6 @@ private static IEnumerable<TheoryDataRow<string>> GetConfigFiles(string subDirec

private static IConfiguration GoodConfiguration => GetGoodConfig("both-commands-with-args");

private static void VerifyLog<T>(Mock<ILogger<T>> loggerMock, LogLevel level, Times times) where T : class
{
loggerMock.Verify(m => m.Log(
level,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()), times);
}

private const string DeviceID = "TestDeviceID";
private static readonly IDisplay TestDisplay = Display.Create(DeviceID, "TestName", "TestManufacturer", "TestDescription");

Expand Down Expand Up @@ -102,8 +91,8 @@ public void RunAsync_Works_BothCommandAndArgs()
runnerMock.Verify(m => m.Run("test2.exe", new string[] { "argZ" }, TestContext.Current.CancellationToken), Times.Once());
runnerMock.VerifyNoOtherCalls();

VerifyLog(loggerMock, LogLevel.Error, Times.Never());
VerifyLog(loggerMock, LogLevel.Warning, Times.Never());
loggerMock.VerifyLog(LogLevel.Error, Times.Never());
loggerMock.VerifyLog(LogLevel.Warning, Times.Never());
}

[Fact]
Expand All @@ -130,8 +119,8 @@ public void RunAsync_Works_OnlyCreateCommandWithArgs()
runnerMock.Verify(m => m.Run("test1.exe", new string[] { "argX", "argY" }, TestContext.Current.CancellationToken), Times.Once());
runnerMock.VerifyNoOtherCalls();

VerifyLog(loggerMock, LogLevel.Error, Times.Never());
VerifyLog(loggerMock, LogLevel.Warning, Times.Never());
loggerMock.VerifyLog(LogLevel.Error, Times.Never());
loggerMock.VerifyLog(LogLevel.Warning, Times.Never());
}

[Fact]
Expand Down Expand Up @@ -162,8 +151,8 @@ public void RunAsync_AfterCancelling_NoMoreEventsFire()

runnerMock.VerifyNoOtherCalls();

VerifyLog(loggerMock, LogLevel.Error, Times.Never());
VerifyLog(loggerMock, LogLevel.Warning, Times.Never());
loggerMock.VerifyLog(LogLevel.Error, Times.Never());
loggerMock.VerifyLog(LogLevel.Warning, Times.Never());
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using DisplayDetective.Library.Common;
using DisplayDetective.Library.Windows;

using Microsoft.Extensions.Logging;

namespace DisplayDetective.Library.Tests.Windows;

[Trait("Category", "Unit")]
Expand All @@ -12,19 +14,30 @@ public class DisplayListServiceFactoryTests
[Fact]
public void Create_OnWindows_Works()
{
var provider = new Mock<IServiceProvider>();
provider.Setup(m => m.GetService(typeof(ILogger<WindowsDisplayListService>)))
.Returns(Mock.Of<ILogger<WindowsDisplayListService>>());

bool isWindows = true;
var factory = new DisplayListServiceFactory(isWindows);

var factory = new DisplayListServiceFactory(provider.Object, isWindows);
var service = factory.Create();

Assert.NotNull(service);
Assert.IsType<WindowsDisplayListService>(service);

provider.Verify(m => m.GetService(typeof(ILogger<WindowsDisplayListService>)), Times.Once());
provider.VerifyNoOtherCalls();
}

[UnsupportedOSPlatform("windows")]
[Fact]
public void Create_NotOnWindows_DoesNotWork()
{
var provider = new Mock<IServiceProvider>();
bool isWindows = false;
var factory = new DisplayListServiceFactory(isWindows);
Assert.Throws<PlatformNotSupportedException>(() => factory.Create());
var factory = new DisplayListServiceFactory(provider.Object, isWindows);
Assert.Throws<PlatformNotSupportedException>(factory.Create);
provider.VerifyNoOtherCalls();
}
}
30 changes: 30 additions & 0 deletions DisplayDetective.Library.Tests/MockLoggerExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Text.RegularExpressions;

using Microsoft.Extensions.Logging;

public static class MockLoggerExtensions
{
public static void VerifyLog<T>(this Mock<ILogger<T>> loggerMock, LogLevel level, Times times) where T : class
{
loggerMock.Verify(m => m.Log(
level,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()), times);
}

public static Mock<ILogger<T>> VerifyLogMatch<T>(this Mock<ILogger<T>> logger, LogLevel level, Times times, string pattern)
{
Func<object, Type, bool> test = (value, _) => value != null && Regex.IsMatch(value.ToString() ?? "", pattern);

logger.Verify(m => m.Log(
level,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((v, t) => test(v, t)),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()), times);

return logger;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

using DisplayDetective.Library.Windows;

using Microsoft.Extensions.Logging;

namespace DisplayDetective.Library.Tests.Windows;

[Trait("Category", "Acceptance")]
Expand All @@ -11,16 +13,13 @@ public class WindowsDisplayListServiceTests
[Fact]
public void GetDisplays_ReturnsNonEmptyList()
{
var service = new WindowsDisplayListService();
var displays = service.GetDisplays();

Assert.NotNull(displays);
Assert.NotEmpty(displays);
var loggerMock = new Mock<ILogger<WindowsDisplayListService>>();
var service = new WindowsDisplayListService(loggerMock.Object);
service.ListDisplays();

var display = displays.First();
Assert.NotEmpty(display.DeviceID);
Assert.NotEmpty(display.Name);
Assert.NotEmpty(display.Manufacturer);
Assert.NotEmpty(display.Description);
loggerMock.VerifyLogMatch(LogLevel.Information, Times.Once(), @"(?i).+scanning");
loggerMock.VerifyLogMatch(LogLevel.Information, Times.Once(), @"(?i).+found \d+ display");
loggerMock.VerifyLogMatch(LogLevel.Information, Times.Once(), @"(?i).+display 1");
loggerMock.VerifyLog(LogLevel.Error, Times.Never());
}
}
10 changes: 8 additions & 2 deletions DisplayDetective.Library/Common/DisplayListServiceFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,29 @@

using DisplayDetective.Library.Windows;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace DisplayDetective.Library.Common;

public sealed class DisplayListServiceFactory
{
private readonly IServiceProvider _provider;

[SupportedOSPlatformGuard("windows")]
private readonly bool _isWindows;

public DisplayListServiceFactory(bool isWindows)
public DisplayListServiceFactory(IServiceProvider provider, bool isWindows)
{
_provider = provider;
_isWindows = isWindows;
}

public IDisplayListService Create()
{
if (_isWindows)
{
return new WindowsDisplayListService();
return new WindowsDisplayListService(_provider.GetRequiredService<ILogger<WindowsDisplayListService>>());
}
throw new PlatformNotSupportedException();
}
Expand Down
2 changes: 1 addition & 1 deletion DisplayDetective.Library/Common/IDisplayListService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ namespace DisplayDetective.Library.Common;

public interface IDisplayListService
{
IList<IDisplay> GetDisplays();
void ListDisplays();
}
22 changes: 19 additions & 3 deletions DisplayDetective.Library/Windows/WindowsDisplayListService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,34 @@

using DisplayDetective.Library.Common;

using Microsoft.Extensions.Logging;

namespace DisplayDetective.Library.Windows;

[SupportedOSPlatform("windows")]
internal sealed class WindowsDisplayListService : IDisplayListService
public sealed class WindowsDisplayListService : IDisplayListService
{
private static readonly ManagementScope Scope = new(ManagementPath.DefaultPath);

private static readonly ObjectQuery Query = new("SELECT * FROM Win32_PnPEntity WHERE PNPClass = 'Monitor'");

public IList<IDisplay> GetDisplays()
private readonly ILogger<WindowsDisplayListService> _logger;

internal WindowsDisplayListService(ILogger<WindowsDisplayListService> logger)
{
_logger = logger;
}

public void ListDisplays()
{
_logger.LogInformation("🔍 Scanning displays");
using var searcher = new ManagementObjectSearcher(Scope, Query);
return [.. searcher.Get().Cast<ManagementObject>().Select(Display.Create)];
IList<IDisplay> displays = [.. searcher.Get().Cast<ManagementObject>().Select(Display.Create)];
_logger.LogInformation("✨ Found {count} display(s)", displays.Count);
for (int i = 0; i < displays.Count; i++)
{
var display = displays[i];
_logger.LogInformation("\n📺 Display {i}\n{display}", i + 1, display.ToMultilineString());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
namespace DisplayDetective.Library.Windows;

[SupportedOSPlatform("windows")]
internal sealed class WindowsDisplayMonitorService : IDisplayMonitorService
public sealed class WindowsDisplayMonitorService : IDisplayMonitorService
{
public event EventHandler<IDisplay> OnDisplayCreated = delegate { };

Expand Down