mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-04-12 03:57:27 +00:00
feat: Add unit tests and fakes for Crestron environment and data store
- Introduced `FakeCrestronEnvironment` and `FakeEthernetHelper` for testing purposes. - Implemented `InMemoryCrestronDataStore` to facilitate unit tests for data storage. - Created `DebugServiceTests` to validate the behavior of debug-related services. - Added project files for unit tests targeting .NET 9.0 with necessary dependencies. - Developed production adapters (`CrestronConsoleAdapter`, `CrestronDataStoreAdapter`, `CrestronEnvironmentAdapter`, `CrestronEthernetAdapter`) to interface with the Crestron SDK. - Updated `Debug` class to utilize injected service abstractions instead of direct SDK calls. - Enhanced `ControlSystem` initialization to register service adapters before usage.
This commit is contained in:
parent
bfb9838743
commit
24df4e7a03
21 changed files with 1020 additions and 93 deletions
36
src/PepperDash.Core.Abstractions/DebugServiceRegistration.cs
Normal file
36
src/PepperDash.Core.Abstractions/DebugServiceRegistration.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
namespace PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows pre-registration of Crestron service implementations before the <c>Debug</c>
|
||||||
|
/// static class initialises. Call <see cref="Register"/> from the composition root
|
||||||
|
/// (e.g. ControlSystem constructor) <em>before</em> any code touches <c>Debug.*</c>.
|
||||||
|
/// Test projects should call it with no-op / in-memory implementations so that the
|
||||||
|
/// <c>Debug</c> static constructor never tries to reach the real Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
public static class DebugServiceRegistration
|
||||||
|
{
|
||||||
|
/// <summary>Gets the registered environment abstraction, or <c>null</c> if not registered.</summary>
|
||||||
|
public static ICrestronEnvironment? Environment { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Gets the registered console abstraction, or <c>null</c> if not registered.</summary>
|
||||||
|
public static ICrestronConsole? Console { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Gets the registered data-store abstraction, or <c>null</c> if not registered.</summary>
|
||||||
|
public static ICrestronDataStore? DataStore { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers the service implementations that <c>Debug</c> will use when its
|
||||||
|
/// static constructor runs. Any parameter may be <c>null</c> to leave the
|
||||||
|
/// corresponding service unregistered (the <c>Debug</c> class will skip that
|
||||||
|
/// capability gracefully).
|
||||||
|
/// </summary>
|
||||||
|
public static void Register(
|
||||||
|
ICrestronEnvironment? environment,
|
||||||
|
ICrestronConsole? console,
|
||||||
|
ICrestronDataStore? dataStore)
|
||||||
|
{
|
||||||
|
Environment = environment;
|
||||||
|
Console = console;
|
||||||
|
DataStore = dataStore;
|
||||||
|
}
|
||||||
|
}
|
||||||
100
src/PepperDash.Core.Abstractions/Enums.cs
Normal file
100
src/PepperDash.Core.Abstractions/Enums.cs
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
namespace PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors Crestron's <c>eDevicePlatform</c> without requiring the Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
public enum DevicePlatform
|
||||||
|
{
|
||||||
|
/// <summary>Hardware appliance (e.g. CP4, MC4).</summary>
|
||||||
|
Appliance,
|
||||||
|
/// <summary>Crestron Virtual Control / server runtime.</summary>
|
||||||
|
Server,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors Crestron's <c>eRuntimeEnvironment</c>.
|
||||||
|
/// </summary>
|
||||||
|
public enum RuntimeEnvironment
|
||||||
|
{
|
||||||
|
/// <summary>SimplSharpPro program slot (hardware 4-series).</summary>
|
||||||
|
SimplSharpPro,
|
||||||
|
/// <summary>SimplSharp (older 3-series or server environments).</summary>
|
||||||
|
SimplSharp,
|
||||||
|
/// <summary>Any other environment — check for completeness.</summary>
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors Crestron's <c>ConsoleAccessLevelEnum</c>.
|
||||||
|
/// </summary>
|
||||||
|
public enum ConsoleAccessLevel
|
||||||
|
{
|
||||||
|
AccessAdministrator = 0,
|
||||||
|
AccessOperator = 1,
|
||||||
|
AccessProgrammer = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors Crestron's <c>eProgramStatusEventType</c>.
|
||||||
|
/// </summary>
|
||||||
|
public enum ProgramStatusEventType
|
||||||
|
{
|
||||||
|
Starting,
|
||||||
|
Stopping,
|
||||||
|
Paused,
|
||||||
|
Resumed,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors the event type used by Crestron's <c>EthernetEventArgs</c>.
|
||||||
|
/// </summary>
|
||||||
|
public enum EthernetEventType
|
||||||
|
{
|
||||||
|
LinkDown = 0,
|
||||||
|
LinkUp = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event args for Crestron ethernet link events.
|
||||||
|
/// </summary>
|
||||||
|
public class PepperDashEthernetEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public EthernetEventType EthernetEventType { get; }
|
||||||
|
public short EthernetAdapter { get; }
|
||||||
|
|
||||||
|
public PepperDashEthernetEventArgs(EthernetEventType eventType, short adapter)
|
||||||
|
{
|
||||||
|
EthernetEventType = eventType;
|
||||||
|
EthernetAdapter = adapter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors the set of <c>CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET</c> values
|
||||||
|
/// used across this codebase — does not aim to be exhaustive.
|
||||||
|
/// </summary>
|
||||||
|
public enum EthernetParameterType
|
||||||
|
{
|
||||||
|
GetCurrentIpAddress,
|
||||||
|
GetHostname,
|
||||||
|
GetDomainName,
|
||||||
|
GetLinkStatus,
|
||||||
|
GetCurrentDhcpState,
|
||||||
|
GetCurrentIpMask,
|
||||||
|
GetCurrentRouter,
|
||||||
|
GetMacAddress,
|
||||||
|
GetDnsServer,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors Crestron's <c>SocketStatus</c> without requiring the Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
public enum PepperDashSocketStatus
|
||||||
|
{
|
||||||
|
SocketNotConnected = 0,
|
||||||
|
SocketConnected = 2,
|
||||||
|
SocketConnectionInProgress = 6,
|
||||||
|
SocketConnectFailed = 11,
|
||||||
|
SocketDisconnecting = 12,
|
||||||
|
SocketBrokenRemotely = 7,
|
||||||
|
}
|
||||||
32
src/PepperDash.Core.Abstractions/ICrestronConsole.cs
Normal file
32
src/PepperDash.Core.Abstractions/ICrestronConsole.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
namespace PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Abstracts <c>Crestron.SimplSharp.CrestronConsole</c> to allow unit testing
|
||||||
|
/// without the Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICrestronConsole
|
||||||
|
{
|
||||||
|
/// <summary>Prints a line to the Crestron console/telnet output.</summary>
|
||||||
|
void PrintLine(string message);
|
||||||
|
|
||||||
|
/// <summary>Prints text (without newline) to the Crestron console/telnet output.</summary>
|
||||||
|
void Print(string message);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a response string to the console for the currently-executing console command.
|
||||||
|
/// </summary>
|
||||||
|
void ConsoleCommandResponse(string message);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a new command with the Crestron console.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="callback">Handler invoked when the command is typed.</param>
|
||||||
|
/// <param name="command">Command name (no spaces).</param>
|
||||||
|
/// <param name="helpText">Help text shown by the Crestron console.</param>
|
||||||
|
/// <param name="accessLevel">Minimum access level required to run the command.</param>
|
||||||
|
void AddNewConsoleCommand(
|
||||||
|
Action<string> callback,
|
||||||
|
string command,
|
||||||
|
string helpText,
|
||||||
|
ConsoleAccessLevel accessLevel);
|
||||||
|
}
|
||||||
31
src/PepperDash.Core.Abstractions/ICrestronDataStore.cs
Normal file
31
src/PepperDash.Core.Abstractions/ICrestronDataStore.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
namespace PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Abstracts <c>Crestron.SimplSharp.CrestronDataStore.CrestronDataStoreStatic</c>
|
||||||
|
/// to allow unit testing without the Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICrestronDataStore
|
||||||
|
{
|
||||||
|
/// <summary>Initialises the data store. Must be called once before any other operation.</summary>
|
||||||
|
void InitStore();
|
||||||
|
|
||||||
|
/// <summary>Reads an integer value from the local (program-slot) store.</summary>
|
||||||
|
/// <returns><c>true</c> if the value was found and read successfully.</returns>
|
||||||
|
bool TryGetLocalInt(string key, out int value);
|
||||||
|
|
||||||
|
/// <summary>Writes an integer value to the local (program-slot) store.</summary>
|
||||||
|
/// <returns><c>true</c> on success.</returns>
|
||||||
|
bool SetLocalInt(string key, int value);
|
||||||
|
|
||||||
|
/// <summary>Writes an unsigned integer value to the local (program-slot) store.</summary>
|
||||||
|
/// <returns><c>true</c> on success.</returns>
|
||||||
|
bool SetLocalUint(string key, uint value);
|
||||||
|
|
||||||
|
/// <summary>Reads a boolean value from the local (program-slot) store.</summary>
|
||||||
|
/// <returns><c>true</c> if the value was found and read successfully.</returns>
|
||||||
|
bool TryGetLocalBool(string key, out bool value);
|
||||||
|
|
||||||
|
/// <summary>Writes a boolean value to the local (program-slot) store.</summary>
|
||||||
|
/// <returns><c>true</c> on success.</returns>
|
||||||
|
bool SetLocalBool(string key, bool value);
|
||||||
|
}
|
||||||
45
src/PepperDash.Core.Abstractions/ICrestronEnvironment.cs
Normal file
45
src/PepperDash.Core.Abstractions/ICrestronEnvironment.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
namespace PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Abstracts <c>Crestron.SimplSharp.CrestronEnvironment</c> to allow unit testing
|
||||||
|
/// without the Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICrestronEnvironment
|
||||||
|
{
|
||||||
|
/// <summary>Gets the platform the program is executing on.</summary>
|
||||||
|
DevicePlatform DevicePlatform { get; }
|
||||||
|
|
||||||
|
/// <summary>Gets the current runtime environment.</summary>
|
||||||
|
RuntimeEnvironment RuntimeEnvironment { get; }
|
||||||
|
|
||||||
|
/// <summary>Gets the platform-appropriate newline string.</summary>
|
||||||
|
string NewLine { get; }
|
||||||
|
|
||||||
|
/// <summary>Gets the application number (program slot).</summary>
|
||||||
|
uint ApplicationNumber { get; }
|
||||||
|
|
||||||
|
/// <summary>Gets the room ID (used in Crestron Virtual Control / server environments).</summary>
|
||||||
|
uint RoomId { get; }
|
||||||
|
|
||||||
|
/// <summary>Raised when program status changes (starting, stopping, etc.).</summary>
|
||||||
|
event EventHandler<ProgramStatusEventArgs> ProgramStatusChanged;
|
||||||
|
|
||||||
|
/// <summary>Raised when the ethernet link changes state.</summary>
|
||||||
|
event EventHandler<PepperDashEthernetEventArgs> EthernetEventReceived;
|
||||||
|
|
||||||
|
/// <summary>Gets the application root directory path.</summary>
|
||||||
|
string GetApplicationRootDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event args for <see cref="ICrestronEnvironment.ProgramStatusChanged"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class ProgramStatusEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public ProgramStatusEventType EventType { get; }
|
||||||
|
|
||||||
|
public ProgramStatusEventArgs(ProgramStatusEventType eventType)
|
||||||
|
{
|
||||||
|
EventType = eventType;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/PepperDash.Core.Abstractions/IEthernetHelper.cs
Normal file
16
src/PepperDash.Core.Abstractions/IEthernetHelper.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
namespace PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Abstracts <c>Crestron.SimplSharp.CrestronEthernetHelper</c> to allow unit testing
|
||||||
|
/// without the Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
public interface IEthernetHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a network parameter string for the specified adapter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parameter">The parameter to retrieve.</param>
|
||||||
|
/// <param name="ethernetAdapterId">Ethernet adapter index (0 = LAN A).</param>
|
||||||
|
/// <returns>String value of the requested parameter.</returns>
|
||||||
|
string GetEthernetParameter(EthernetParameterType parameter, short ethernetAdapterId);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<RootNamespace>PepperDash.Core.Abstractions</RootNamespace>
|
||||||
|
<AssemblyName>PepperDash.Core.Abstractions</AssemblyName>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
<NeutralLanguage>en</NeutralLanguage>
|
||||||
|
<Title>PepperDash Core Abstractions</Title>
|
||||||
|
<Company>PepperDash Technologies</Company>
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<RepositoryUrl>https://github.com/PepperDash/PepperDashCore</RepositoryUrl>
|
||||||
|
<NullableContextOptions>enable</NullableContextOptions>
|
||||||
|
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||||
|
<InformationalVersion>$(Version)</InformationalVersion>
|
||||||
|
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||||
|
<!-- No Crestron SDK reference — this project must remain hardware-agnostic so test projects can reference it -->
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
38
src/PepperDash.Core.Tests/Fakes/CrestronConsoleFakes.cs
Normal file
38
src/PepperDash.Core.Tests/Fakes/CrestronConsoleFakes.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
using PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Tests.Fakes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// No-op ICrestronConsole that captures output for test assertions.
|
||||||
|
/// </summary>
|
||||||
|
public class CapturingCrestronConsole : ICrestronConsole
|
||||||
|
{
|
||||||
|
public List<string> Lines { get; } = new();
|
||||||
|
public List<string> CommandResponses { get; } = new();
|
||||||
|
public List<(string Command, string HelpText)> RegisteredCommands { get; } = new();
|
||||||
|
|
||||||
|
public void PrintLine(string message) => Lines.Add(message);
|
||||||
|
public void Print(string message) => Lines.Add(message);
|
||||||
|
public void ConsoleCommandResponse(string message) => CommandResponses.Add(message);
|
||||||
|
|
||||||
|
public void AddNewConsoleCommand(
|
||||||
|
Action<string> callback,
|
||||||
|
string command,
|
||||||
|
string helpText,
|
||||||
|
ConsoleAccessLevel accessLevel)
|
||||||
|
{
|
||||||
|
RegisteredCommands.Add((command, helpText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimal no-op ICrestronConsole that discards all output. Useful when you only
|
||||||
|
/// care about the system under test and not what it logs.
|
||||||
|
/// </summary>
|
||||||
|
public class NoOpCrestronConsole : ICrestronConsole
|
||||||
|
{
|
||||||
|
public void PrintLine(string message) { }
|
||||||
|
public void Print(string message) { }
|
||||||
|
public void ConsoleCommandResponse(string message) { }
|
||||||
|
public void AddNewConsoleCommand(Action<string> _, string __, string ___, ConsoleAccessLevel ____) { }
|
||||||
|
}
|
||||||
46
src/PepperDash.Core.Tests/Fakes/CrestronEnvironmentFakes.cs
Normal file
46
src/PepperDash.Core.Tests/Fakes/CrestronEnvironmentFakes.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
using PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Tests.Fakes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configurable ICrestronEnvironment for unit tests.
|
||||||
|
/// Defaults: Appliance / SimplSharpPro / ApplicationNumber=1.
|
||||||
|
/// </summary>
|
||||||
|
public class FakeCrestronEnvironment : ICrestronEnvironment
|
||||||
|
{
|
||||||
|
public DevicePlatform DevicePlatform { get; set; } = DevicePlatform.Appliance;
|
||||||
|
public RuntimeEnvironment RuntimeEnvironment { get; set; } = RuntimeEnvironment.SimplSharpPro;
|
||||||
|
public string NewLine { get; set; } = "\r\n";
|
||||||
|
public uint ApplicationNumber { get; set; } = 1;
|
||||||
|
public uint RoomId { get; set; } = 0;
|
||||||
|
|
||||||
|
public event EventHandler<ProgramStatusEventArgs>? ProgramStatusChanged;
|
||||||
|
public event EventHandler<PepperDashEthernetEventArgs>? EthernetEventReceived;
|
||||||
|
|
||||||
|
public string GetApplicationRootDirectory() => System.IO.Path.GetTempPath();
|
||||||
|
|
||||||
|
/// <summary>Simulates a program status event for tests.</summary>
|
||||||
|
public void RaiseProgramStatus(ProgramStatusEventType type) =>
|
||||||
|
ProgramStatusChanged?.Invoke(this, new ProgramStatusEventArgs(type));
|
||||||
|
|
||||||
|
/// <summary>Simulates an ethernet event for tests.</summary>
|
||||||
|
public void RaiseEthernetEvent(EthernetEventType type, short adapter = 0) =>
|
||||||
|
EthernetEventReceived?.Invoke(this, new PepperDashEthernetEventArgs(type, adapter));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// No-op IEthernetHelper that returns configurable values.
|
||||||
|
/// </summary>
|
||||||
|
public class FakeEthernetHelper : IEthernetHelper
|
||||||
|
{
|
||||||
|
private readonly Dictionary<EthernetParameterType, string> _values = new();
|
||||||
|
|
||||||
|
public FakeEthernetHelper Seed(EthernetParameterType param, string value)
|
||||||
|
{
|
||||||
|
_values[param] = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetEthernetParameter(EthernetParameterType parameter, short ethernetAdapterId) =>
|
||||||
|
_values.TryGetValue(parameter, out var v) ? v : string.Empty;
|
||||||
|
}
|
||||||
59
src/PepperDash.Core.Tests/Fakes/InMemoryCrestronDataStore.cs
Normal file
59
src/PepperDash.Core.Tests/Fakes/InMemoryCrestronDataStore.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
using PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Tests.Fakes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// In-memory ICrestronDataStore backed by a dictionary.
|
||||||
|
/// Use in unit tests to verify that keys are read from and written to the store correctly.
|
||||||
|
/// </summary>
|
||||||
|
public class InMemoryCrestronDataStore : ICrestronDataStore
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, object> _store = new();
|
||||||
|
|
||||||
|
public bool Initialized { get; private set; }
|
||||||
|
|
||||||
|
public void InitStore() => Initialized = true;
|
||||||
|
|
||||||
|
public bool TryGetLocalInt(string key, out int value)
|
||||||
|
{
|
||||||
|
if (_store.TryGetValue(key, out var raw) && raw is int i)
|
||||||
|
{
|
||||||
|
value = i;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
value = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetLocalInt(string key, int value)
|
||||||
|
{
|
||||||
|
_store[key] = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetLocalUint(string key, uint value)
|
||||||
|
{
|
||||||
|
_store[key] = (int)value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetLocalBool(string key, out bool value)
|
||||||
|
{
|
||||||
|
if (_store.TryGetValue(key, out var raw) && raw is bool b)
|
||||||
|
{
|
||||||
|
value = b;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
value = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetLocalBool(string key, bool value)
|
||||||
|
{
|
||||||
|
_store[key] = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Seeds a key for testing read paths.</summary>
|
||||||
|
public void Seed(string key, object value) => _store[key] = value;
|
||||||
|
}
|
||||||
171
src/PepperDash.Core.Tests/Logging/DebugServiceTests.cs
Normal file
171
src/PepperDash.Core.Tests/Logging/DebugServiceTests.cs
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
using FluentAssertions;
|
||||||
|
using PepperDash.Core.Abstractions;
|
||||||
|
using PepperDash.Core.Tests.Fakes;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Tests.Logging;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests for Debug-related service interfaces and implementations.
|
||||||
|
/// These tests verify the behaviour of the abstractions in isolation (no Crestron SDK required).
|
||||||
|
/// </summary>
|
||||||
|
public class DebugServiceTests
|
||||||
|
{
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// ICrestronDataStore — InMemoryCrestronDataStore
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DataStore_InitStore_SetsInitializedFlag()
|
||||||
|
{
|
||||||
|
var store = new InMemoryCrestronDataStore();
|
||||||
|
store.Initialized.Should().BeFalse("not yet initialized");
|
||||||
|
|
||||||
|
store.InitStore();
|
||||||
|
|
||||||
|
store.Initialized.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DataStore_SetAndGetLocalInt_RoundTrips()
|
||||||
|
{
|
||||||
|
var store = new InMemoryCrestronDataStore();
|
||||||
|
|
||||||
|
store.SetLocalInt("MyKey", 42).Should().BeTrue();
|
||||||
|
store.TryGetLocalInt("MyKey", out var value).Should().BeTrue();
|
||||||
|
value.Should().Be(42);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DataStore_TryGetLocalInt_ReturnsFalse_WhenKeyAbsent()
|
||||||
|
{
|
||||||
|
var store = new InMemoryCrestronDataStore();
|
||||||
|
|
||||||
|
store.TryGetLocalInt("Missing", out var value).Should().BeFalse();
|
||||||
|
value.Should().Be(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DataStore_SetAndGetLocalBool_RoundTrips()
|
||||||
|
{
|
||||||
|
var store = new InMemoryCrestronDataStore();
|
||||||
|
|
||||||
|
store.SetLocalBool("FlagKey", true).Should().BeTrue();
|
||||||
|
store.TryGetLocalBool("FlagKey", out var value).Should().BeTrue();
|
||||||
|
value.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DataStore_TryGetLocalBool_ReturnsFalse_WhenKeyAbsent()
|
||||||
|
{
|
||||||
|
var store = new InMemoryCrestronDataStore();
|
||||||
|
|
||||||
|
store.TryGetLocalBool("Missing", out var value).Should().BeFalse();
|
||||||
|
value.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DataStore_SetLocalUint_CanBeReadBackAsInt()
|
||||||
|
{
|
||||||
|
var store = new InMemoryCrestronDataStore();
|
||||||
|
|
||||||
|
store.SetLocalUint("UintKey", 3u).Should().BeTrue();
|
||||||
|
store.TryGetLocalInt("UintKey", out var value).Should().BeTrue();
|
||||||
|
value.Should().Be(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DataStore_Seed_AllowsTestSetupOfReadPaths()
|
||||||
|
{
|
||||||
|
var store = new InMemoryCrestronDataStore();
|
||||||
|
store.Seed("MyLevel", 2);
|
||||||
|
|
||||||
|
store.TryGetLocalInt("MyLevel", out var level).Should().BeTrue();
|
||||||
|
level.Should().Be(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// DebugServiceRegistration
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ServiceRegistration_Register_StoresAllThreeServices()
|
||||||
|
{
|
||||||
|
var env = new FakeCrestronEnvironment();
|
||||||
|
var console = new NoOpCrestronConsole();
|
||||||
|
var store = new InMemoryCrestronDataStore();
|
||||||
|
|
||||||
|
DebugServiceRegistration.Register(env, console, store);
|
||||||
|
|
||||||
|
DebugServiceRegistration.Environment.Should().BeSameAs(env);
|
||||||
|
DebugServiceRegistration.Console.Should().BeSameAs(console);
|
||||||
|
DebugServiceRegistration.DataStore.Should().BeSameAs(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ServiceRegistration_Register_AcceptsNullsWithoutThrowing()
|
||||||
|
{
|
||||||
|
var act = () => DebugServiceRegistration.Register(null, null, null);
|
||||||
|
act.Should().NotThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// ICrestronEnvironment — FakeCrestronEnvironment
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FakeEnvironment_DefaultsToAppliance()
|
||||||
|
{
|
||||||
|
var env = new FakeCrestronEnvironment();
|
||||||
|
env.DevicePlatform.Should().Be(DevicePlatform.Appliance);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FakeEnvironment_RaiseProgramStatus_FiresEvent()
|
||||||
|
{
|
||||||
|
var env = new FakeCrestronEnvironment();
|
||||||
|
ProgramStatusEventType? received = null;
|
||||||
|
env.ProgramStatusChanged += (_, e) => received = e.EventType;
|
||||||
|
|
||||||
|
env.RaiseProgramStatus(ProgramStatusEventType.Stopping);
|
||||||
|
|
||||||
|
received.Should().Be(ProgramStatusEventType.Stopping);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FakeEnvironment_RaiseEthernetEvent_FiresEvent()
|
||||||
|
{
|
||||||
|
var env = new FakeCrestronEnvironment();
|
||||||
|
EthernetEventType? received = null;
|
||||||
|
env.EthernetEventReceived += (_, e) => received = e.EthernetEventType;
|
||||||
|
|
||||||
|
env.RaiseEthernetEvent(EthernetEventType.LinkUp, adapter: 0);
|
||||||
|
|
||||||
|
received.Should().Be(EthernetEventType.LinkUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// ICrestronConsole — CapturingCrestronConsole
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CapturingConsole_PrintLine_CapturesMessage()
|
||||||
|
{
|
||||||
|
var console = new CapturingCrestronConsole();
|
||||||
|
|
||||||
|
console.PrintLine("hello world");
|
||||||
|
|
||||||
|
console.Lines.Should().ContainSingle().Which.Should().Be("hello world");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CapturingConsole_AddNewConsoleCommand_RecordsCommandName()
|
||||||
|
{
|
||||||
|
var console = new CapturingCrestronConsole();
|
||||||
|
|
||||||
|
console.AddNewConsoleCommand(_ => { }, "appdebug", "Sets debug level", ConsoleAccessLevel.AccessOperator);
|
||||||
|
|
||||||
|
console.RegisteredCommands.Should().ContainSingle()
|
||||||
|
.Which.Command.Should().Be("appdebug");
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/PepperDash.Core.Tests/PepperDash.Core.Tests.csproj
Normal file
25
src/PepperDash.Core.Tests/PepperDash.Core.Tests.csproj
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||||
|
<PackageReference Include="FluentAssertions" Version="7.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Reference the Abstractions project only — no Crestron SDK dependency -->
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\PepperDash.Core.Abstractions\PepperDash.Core.Abstractions.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
35
src/PepperDash.Core/Adapters/CrestronConsoleAdapter.cs
Normal file
35
src/PepperDash.Core/Adapters/CrestronConsoleAdapter.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
using System;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
|
using PdCore = PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Adapters;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Production adapter — delegates ICrestronConsole calls to the real Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CrestronConsoleAdapter : PdCore.ICrestronConsole
|
||||||
|
{
|
||||||
|
public void PrintLine(string message) => CrestronConsole.PrintLine(message);
|
||||||
|
|
||||||
|
public void Print(string message) => CrestronConsole.Print(message);
|
||||||
|
|
||||||
|
public void ConsoleCommandResponse(string message) =>
|
||||||
|
CrestronConsole.ConsoleCommandResponse(message);
|
||||||
|
|
||||||
|
public void AddNewConsoleCommand(
|
||||||
|
Action<string> callback,
|
||||||
|
string command,
|
||||||
|
string helpText,
|
||||||
|
PdCore.ConsoleAccessLevel accessLevel)
|
||||||
|
{
|
||||||
|
var crestronLevel = accessLevel switch
|
||||||
|
{
|
||||||
|
PdCore.ConsoleAccessLevel.AccessAdministrator => ConsoleAccessLevelEnum.AccessAdministrator,
|
||||||
|
PdCore.ConsoleAccessLevel.AccessProgrammer => ConsoleAccessLevelEnum.AccessProgrammer,
|
||||||
|
_ => ConsoleAccessLevelEnum.AccessOperator,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wrap Action<string> in a lambda — Crestron's delegate is not a standard Action<string>.
|
||||||
|
CrestronConsole.AddNewConsoleCommand(s => callback(s), command, helpText, crestronLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/PepperDash.Core/Adapters/CrestronDataStoreAdapter.cs
Normal file
42
src/PepperDash.Core/Adapters/CrestronDataStoreAdapter.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
using Crestron.SimplSharp.CrestronDataStore;
|
||||||
|
using PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Adapters;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Production adapter — delegates ICrestronDataStore calls to the real Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CrestronDataStoreAdapter : ICrestronDataStore
|
||||||
|
{
|
||||||
|
public void InitStore() => CrestronDataStoreStatic.InitCrestronDataStore();
|
||||||
|
|
||||||
|
public bool TryGetLocalInt(string key, out int value)
|
||||||
|
{
|
||||||
|
var err = CrestronDataStoreStatic.GetLocalIntValue(key, out value);
|
||||||
|
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetLocalInt(string key, int value)
|
||||||
|
{
|
||||||
|
var err = CrestronDataStoreStatic.SetLocalIntValue(key, value);
|
||||||
|
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetLocalUint(string key, uint value)
|
||||||
|
{
|
||||||
|
var err = CrestronDataStoreStatic.SetLocalUintValue(key, value);
|
||||||
|
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetLocalBool(string key, out bool value)
|
||||||
|
{
|
||||||
|
var err = CrestronDataStoreStatic.GetLocalBoolValue(key, out value);
|
||||||
|
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetLocalBool(string key, bool value)
|
||||||
|
{
|
||||||
|
var err = CrestronDataStoreStatic.SetLocalBoolValue(key, value);
|
||||||
|
return err == CrestronDataStore.CDS_ERROR.CDS_SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/PepperDash.Core/Adapters/CrestronEnvironmentAdapter.cs
Normal file
68
src/PepperDash.Core/Adapters/CrestronEnvironmentAdapter.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
using System;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
|
using PdCore = PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Adapters;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Production adapter — delegates ICrestronEnvironment calls to the real Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CrestronEnvironmentAdapter : PdCore.ICrestronEnvironment
|
||||||
|
{
|
||||||
|
// Subscribe once in constructor and re-raise as our event types.
|
||||||
|
private event EventHandler<PdCore.ProgramStatusEventArgs>? _programStatusChanged;
|
||||||
|
private event EventHandler<PdCore.PepperDashEthernetEventArgs>? _ethernetEventReceived;
|
||||||
|
|
||||||
|
public CrestronEnvironmentAdapter()
|
||||||
|
{
|
||||||
|
CrestronEnvironment.ProgramStatusEventHandler += type =>
|
||||||
|
_programStatusChanged?.Invoke(this, new PdCore.ProgramStatusEventArgs(MapProgramStatus(type)));
|
||||||
|
|
||||||
|
CrestronEnvironment.EthernetEventHandler += args =>
|
||||||
|
_ethernetEventReceived?.Invoke(this, new PdCore.PepperDashEthernetEventArgs(
|
||||||
|
args.EthernetEventType == eEthernetEventType.LinkDown
|
||||||
|
? PdCore.EthernetEventType.LinkDown
|
||||||
|
: PdCore.EthernetEventType.LinkUp,
|
||||||
|
(short)args.EthernetAdapter));
|
||||||
|
}
|
||||||
|
|
||||||
|
public PdCore.DevicePlatform DevicePlatform =>
|
||||||
|
CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance
|
||||||
|
? PdCore.DevicePlatform.Appliance
|
||||||
|
: PdCore.DevicePlatform.Server;
|
||||||
|
|
||||||
|
public PdCore.RuntimeEnvironment RuntimeEnvironment =>
|
||||||
|
CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro
|
||||||
|
? PdCore.RuntimeEnvironment.SimplSharpPro
|
||||||
|
: PdCore.RuntimeEnvironment.Other;
|
||||||
|
|
||||||
|
public string NewLine => CrestronEnvironment.NewLine;
|
||||||
|
|
||||||
|
public uint ApplicationNumber => InitialParametersClass.ApplicationNumber;
|
||||||
|
|
||||||
|
public uint RoomId => uint.TryParse(InitialParametersClass.RoomId, out var r) ? r : 0;
|
||||||
|
|
||||||
|
public event EventHandler<PdCore.ProgramStatusEventArgs> ProgramStatusChanged
|
||||||
|
{
|
||||||
|
add => _programStatusChanged += value;
|
||||||
|
remove => _programStatusChanged -= value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<PdCore.PepperDashEthernetEventArgs> EthernetEventReceived
|
||||||
|
{
|
||||||
|
add => _ethernetEventReceived += value;
|
||||||
|
remove => _ethernetEventReceived -= value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetApplicationRootDirectory() =>
|
||||||
|
Crestron.SimplSharp.CrestronIO.Directory.GetApplicationRootDirectory();
|
||||||
|
|
||||||
|
private static PdCore.ProgramStatusEventType MapProgramStatus(eProgramStatusEventType type) =>
|
||||||
|
type switch
|
||||||
|
{
|
||||||
|
eProgramStatusEventType.Stopping => PdCore.ProgramStatusEventType.Stopping,
|
||||||
|
eProgramStatusEventType.Paused => PdCore.ProgramStatusEventType.Paused,
|
||||||
|
eProgramStatusEventType.Resumed => PdCore.ProgramStatusEventType.Resumed,
|
||||||
|
_ => PdCore.ProgramStatusEventType.Starting,
|
||||||
|
};
|
||||||
|
}
|
||||||
39
src/PepperDash.Core/Adapters/CrestronEthernetAdapter.cs
Normal file
39
src/PepperDash.Core/Adapters/CrestronEthernetAdapter.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
using System;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
|
using PepperDash.Core.Abstractions;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Adapters;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Production adapter — delegates IEthernetHelper calls to the real Crestron SDK.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CrestronEthernetAdapter : IEthernetHelper
|
||||||
|
{
|
||||||
|
public string GetEthernetParameter(EthernetParameterType parameter, short ethernetAdapterId)
|
||||||
|
{
|
||||||
|
var crestronParam = parameter switch
|
||||||
|
{
|
||||||
|
EthernetParameterType.GetCurrentIpAddress =>
|
||||||
|
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS,
|
||||||
|
EthernetParameterType.GetHostname =>
|
||||||
|
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME,
|
||||||
|
EthernetParameterType.GetDomainName =>
|
||||||
|
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME,
|
||||||
|
EthernetParameterType.GetLinkStatus =>
|
||||||
|
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_LINK_STATUS,
|
||||||
|
EthernetParameterType.GetCurrentDhcpState =>
|
||||||
|
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_DHCP_STATE,
|
||||||
|
EthernetParameterType.GetCurrentIpMask =>
|
||||||
|
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK,
|
||||||
|
EthernetParameterType.GetCurrentRouter =>
|
||||||
|
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER,
|
||||||
|
EthernetParameterType.GetMacAddress =>
|
||||||
|
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_MAC_ADDRESS,
|
||||||
|
EthernetParameterType.GetDnsServer =>
|
||||||
|
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DNS_SERVER,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(parameter), parameter, null),
|
||||||
|
};
|
||||||
|
|
||||||
|
return CrestronEthernetHelper.GetEthernetParameter(crestronParam, ethernetAdapterId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,7 @@ using Crestron.SimplSharp.CrestronIO;
|
||||||
using Crestron.SimplSharp.CrestronLogger;
|
using Crestron.SimplSharp.CrestronLogger;
|
||||||
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
|
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
|
||||||
using JsonConvert = NewtonsoftJson::Newtonsoft.Json.JsonConvert;
|
using JsonConvert = NewtonsoftJson::Newtonsoft.Json.JsonConvert;
|
||||||
|
using PdCore = PepperDash.Core.Abstractions;
|
||||||
using PepperDash.Core.Logging;
|
using PepperDash.Core.Logging;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Context;
|
using Serilog.Context;
|
||||||
|
|
@ -45,6 +46,13 @@ public static class Debug
|
||||||
|
|
||||||
private static ILogger _logger;
|
private static ILogger _logger;
|
||||||
|
|
||||||
|
// Injected service abstractions. Populated by DebugServiceRegistration.Register()
|
||||||
|
// before the Debug static constructor runs. Null when running on hardware without
|
||||||
|
// pre-registration (the static ctor falls back to the Crestron SDK directly).
|
||||||
|
private static PdCore.ICrestronEnvironment _environment;
|
||||||
|
private static PdCore.ICrestronConsole _console;
|
||||||
|
private static PdCore.ICrestronDataStore _dataStore;
|
||||||
|
|
||||||
private static readonly LoggingLevelSwitch consoleLoggingLevelSwitch;
|
private static readonly LoggingLevelSwitch consoleLoggingLevelSwitch;
|
||||||
|
|
||||||
private static readonly LoggingLevelSwitch websocketLoggingLevelSwitch;
|
private static readonly LoggingLevelSwitch websocketLoggingLevelSwitch;
|
||||||
|
|
@ -107,7 +115,7 @@ public static class Debug
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates whether the code is running on an appliance or not. Used to determine file paths and other appliance vs server differences
|
/// Indicates whether the code is running on an appliance or not. Used to determine file paths and other appliance vs server differences
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsRunningOnAppliance = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance;
|
public static bool IsRunningOnAppliance; // set in static constructor
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Version for the currently loaded PepperDashCore dll
|
/// Version for the currently loaded PepperDashCore dll
|
||||||
|
|
@ -145,102 +153,114 @@ public static class Debug
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
CrestronDataStoreStatic.InitCrestronDataStore();
|
// Pick up services pre-registered by the composition root (or test setup).
|
||||||
|
// If null, fall back to direct Crestron SDK calls for production hardware.
|
||||||
|
_environment = PdCore.DebugServiceRegistration.Environment;
|
||||||
|
_console = PdCore.DebugServiceRegistration.Console;
|
||||||
|
_dataStore = PdCore.DebugServiceRegistration.DataStore;
|
||||||
|
|
||||||
|
IsRunningOnAppliance = _environment?.DevicePlatform == PdCore.DevicePlatform.Appliance;
|
||||||
|
|
||||||
|
_dataStore?.InitStore();
|
||||||
|
|
||||||
consoleDebugTimer = new Timer(defaultConsoleDebugTimeoutMin * 60000) { AutoReset = false };
|
consoleDebugTimer = new Timer(defaultConsoleDebugTimeoutMin * 60000) { AutoReset = false };
|
||||||
consoleDebugTimer.Elapsed += (s, e) =>
|
consoleDebugTimer.Elapsed += (s, e) =>
|
||||||
{
|
{
|
||||||
SetDebugLevel(LogEventLevel.Information);
|
SetDebugLevel(LogEventLevel.Information);
|
||||||
CrestronConsole.ConsoleCommandResponse($"Console debug level reset to {LogEventLevel.Information} after timeout of {defaultConsoleDebugTimeoutMin} minutes");
|
_console?.ConsoleCommandResponse($"Console debug level reset to {LogEventLevel.Information} after timeout of {defaultConsoleDebugTimeoutMin} minutes");
|
||||||
};
|
};
|
||||||
|
|
||||||
var defaultConsoleLevel = GetStoredLogEventLevel(LevelStoreKey);
|
var defaultConsoleLevel = GetStoredLogEventLevel(LevelStoreKey);
|
||||||
|
|
||||||
var defaultWebsocketLevel = GetStoredLogEventLevel(WebSocketLevelStoreKey);
|
var defaultWebsocketLevel = GetStoredLogEventLevel(WebSocketLevelStoreKey);
|
||||||
|
|
||||||
var defaultErrorLogLevel = GetStoredLogEventLevel(ErrorLogLevelStoreKey);
|
var defaultErrorLogLevel = GetStoredLogEventLevel(ErrorLogLevelStoreKey);
|
||||||
|
|
||||||
var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey);
|
var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey);
|
||||||
|
|
||||||
consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel);
|
consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel);
|
||||||
|
|
||||||
websocketLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel);
|
websocketLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel);
|
||||||
|
|
||||||
errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel);
|
errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel);
|
||||||
|
|
||||||
fileLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel);
|
fileLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel);
|
||||||
|
|
||||||
websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true));
|
websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true));
|
||||||
|
|
||||||
var logFilePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ?
|
var appRoot = _environment?.GetApplicationRootDirectory()
|
||||||
$@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}app{InitialParametersClass.ApplicationNumber}{Path.DirectorySeparatorChar}global-log.log" :
|
?? System.IO.Path.GetTempPath();
|
||||||
$@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}room{InitialParametersClass.RoomId}{Path.DirectorySeparatorChar}global-log.log";
|
var sep = System.IO.Path.DirectorySeparatorChar;
|
||||||
|
var appNum = _environment?.ApplicationNumber ?? 0;
|
||||||
|
var roomId = _environment?.RoomId ?? 0;
|
||||||
|
|
||||||
CrestronConsole.PrintLine($"Saving log files to {logFilePath}");
|
var logFilePath = IsRunningOnAppliance
|
||||||
|
? $"{appRoot}{sep}user{sep}debug{sep}app{appNum}{sep}global-log.log"
|
||||||
|
: $"{appRoot}{sep}user{sep}debug{sep}room{roomId}{sep}global-log.log";
|
||||||
|
|
||||||
var errorLogTemplate = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance
|
_console?.PrintLine($"Saving log files to {logFilePath}");
|
||||||
? "{@t:fff}ms [{@l:u4}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}"
|
|
||||||
: "[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}";
|
|
||||||
|
|
||||||
|
// Build the base Serilog pipeline — sinks that require the Crestron SDK are
|
||||||
|
// added conditionally so the logger remains usable in test environments.
|
||||||
_defaultLoggerConfiguration = new LoggerConfiguration()
|
_defaultLoggerConfiguration = new LoggerConfiguration()
|
||||||
.MinimumLevel.Verbose()
|
.MinimumLevel.Verbose()
|
||||||
.Enrich.FromLogContext()
|
.Enrich.FromLogContext()
|
||||||
.Enrich.With(new CrestronEnricher())
|
|
||||||
.WriteTo.Sink(new DebugConsoleSink(new ExpressionTemplate("[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}")), levelSwitch: consoleLoggingLevelSwitch)
|
.WriteTo.Sink(new DebugConsoleSink(new ExpressionTemplate("[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}")), levelSwitch: consoleLoggingLevelSwitch)
|
||||||
.WriteTo.Sink(websocketSink, levelSwitch: websocketLoggingLevelSwitch)
|
.WriteTo.Sink(websocketSink, levelSwitch: websocketLoggingLevelSwitch)
|
||||||
.WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: errorLogLevelSwitch)
|
|
||||||
.WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath,
|
.WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath,
|
||||||
rollingInterval: RollingInterval.Day,
|
rollingInterval: RollingInterval.Day,
|
||||||
restrictedToMinimumLevel: LogEventLevel.Debug,
|
restrictedToMinimumLevel: LogEventLevel.Debug,
|
||||||
retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 30 : 60,
|
retainedFileCountLimit: IsRunningOnAppliance ? 30 : 60,
|
||||||
levelSwitch: fileLoggingLevelSwitch
|
levelSwitch: fileLoggingLevelSwitch
|
||||||
);
|
);
|
||||||
|
|
||||||
// Instantiate the root logger
|
// Add Crestron-specific enricher and error-log sink only when running on hardware.
|
||||||
_loggerConfiguration = _defaultLoggerConfiguration;
|
if (_environment != null)
|
||||||
|
|
||||||
_logger = _loggerConfiguration.CreateLogger();
|
|
||||||
// Get the assembly version and print it to console and the log
|
|
||||||
GetVersion();
|
|
||||||
|
|
||||||
string msg = $"[App {InitialParametersClass.ApplicationNumber}] Using PepperDash_Core v{PepperDashCoreVersion}";
|
|
||||||
|
|
||||||
if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server)
|
|
||||||
{
|
{
|
||||||
msg = $"[Room {InitialParametersClass.RoomId}] Using PepperDash_Core v{PepperDashCoreVersion}";
|
var errorLogTemplate = IsRunningOnAppliance
|
||||||
|
? "{@t:fff}ms [{@l:u4}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}"
|
||||||
|
: "[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}";
|
||||||
|
|
||||||
|
_defaultLoggerConfiguration
|
||||||
|
.Enrich.With(new CrestronEnricher())
|
||||||
|
.WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: errorLogLevelSwitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
CrestronConsole.PrintLine(msg);
|
_loggerConfiguration = _defaultLoggerConfiguration;
|
||||||
|
_logger = _loggerConfiguration.CreateLogger();
|
||||||
|
|
||||||
|
GetVersion();
|
||||||
|
|
||||||
|
string msg = IsRunningOnAppliance
|
||||||
|
? $"[App {appNum}] Using PepperDash_Core v{PepperDashCoreVersion}"
|
||||||
|
: $"[Room {roomId}] Using PepperDash_Core v{PepperDashCoreVersion}";
|
||||||
|
|
||||||
|
_console?.PrintLine(msg);
|
||||||
LogMessage(LogEventLevel.Information, msg);
|
LogMessage(LogEventLevel.Information, msg);
|
||||||
|
|
||||||
IncludedExcludedKeys = new Dictionary<string, object>();
|
IncludedExcludedKeys = new Dictionary<string, object>();
|
||||||
|
|
||||||
if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
|
if (_environment?.RuntimeEnvironment == PdCore.RuntimeEnvironment.SimplSharpPro)
|
||||||
{
|
{
|
||||||
// Add command to console
|
_console?.AddNewConsoleCommand(SetDoNotLoadOnNextBootFromConsole, "donotloadonnextboot",
|
||||||
CrestronConsole.AddNewConsoleCommand(SetDoNotLoadOnNextBootFromConsole, "donotloadonnextboot",
|
"donotloadonnextboot:P [true/false]: Should the application load on next boot",
|
||||||
"donotloadonnextboot:P [true/false]: Should the application load on next boot", ConsoleAccessLevelEnum.AccessOperator);
|
PdCore.ConsoleAccessLevel.AccessOperator);
|
||||||
|
|
||||||
CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
|
_console?.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
|
||||||
"appdebug:P [0-5]: Sets the application's console debug message level",
|
"appdebug:P [0-5]: Sets the application's console debug message level",
|
||||||
ConsoleAccessLevelEnum.AccessOperator);
|
PdCore.ConsoleAccessLevel.AccessOperator);
|
||||||
CrestronConsole.AddNewConsoleCommand(ShowDebugLog, "appdebuglog",
|
|
||||||
"appdebuglog:P [all] Use \"all\" for full log.",
|
|
||||||
ConsoleAccessLevelEnum.AccessOperator);
|
|
||||||
CrestronConsole.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear",
|
|
||||||
"appdebugclear:P Clears the current custom log",
|
|
||||||
ConsoleAccessLevelEnum.AccessOperator);
|
|
||||||
CrestronConsole.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter",
|
|
||||||
"appdebugfilter [params]", ConsoleAccessLevelEnum.AccessOperator);
|
|
||||||
}
|
|
||||||
|
|
||||||
// CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
|
_console?.AddNewConsoleCommand(ShowDebugLog, "appdebuglog",
|
||||||
|
"appdebuglog:P [all] Use \"all\" for full log.",
|
||||||
|
PdCore.ConsoleAccessLevel.AccessOperator);
|
||||||
|
|
||||||
|
_console?.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear",
|
||||||
|
"appdebugclear:P Clears the current custom log",
|
||||||
|
PdCore.ConsoleAccessLevel.AccessOperator);
|
||||||
|
|
||||||
|
_console?.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter",
|
||||||
|
"appdebugfilter [params]",
|
||||||
|
PdCore.ConsoleAccessLevel.AccessOperator);
|
||||||
|
}
|
||||||
|
|
||||||
DoNotLoadConfigOnNextBoot = GetDoNotLoadOnNextBoot();
|
DoNotLoadConfigOnNextBoot = GetDoNotLoadOnNextBoot();
|
||||||
|
|
||||||
if (DoNotLoadConfigOnNextBoot)
|
if (DoNotLoadConfigOnNextBoot)
|
||||||
CrestronConsole.PrintLine(string.Format("Program {0} will not load config after next boot. Use console command go:{0} to load the config manually", InitialParametersClass.ApplicationNumber));
|
_console?.PrintLine($"Program {appNum} will not load config after next boot. Use console command go:{appNum} to load the config manually");
|
||||||
|
|
||||||
consoleLoggingLevelSwitch.MinimumLevelChanged += (sender, args) =>
|
consoleLoggingLevelSwitch.MinimumLevelChanged += (sender, args) =>
|
||||||
{
|
{
|
||||||
|
|
@ -250,18 +270,20 @@ public static class Debug
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// _logger may not have been initialized yet — do not call LogError here.
|
// _logger may not have been initialized yet — do not call LogError here.
|
||||||
CrestronConsole.PrintLine($"Exception in Debug static constructor: {ex.Message}\r\n{ex.StackTrace}");
|
// _console may also be null; fall back to CrestronConsole as last resort.
|
||||||
|
try { _console?.PrintLine($"Exception in Debug static constructor: {ex.Message}\r\n{ex.StackTrace}"); }
|
||||||
|
catch { CrestronConsole.PrintLine($"Exception in Debug static constructor: {ex.Message}\r\n{ex.StackTrace}"); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool GetDoNotLoadOnNextBoot()
|
private static bool GetDoNotLoadOnNextBoot()
|
||||||
{
|
{
|
||||||
var err = CrestronDataStoreStatic.GetLocalBoolValue(DoNotLoadOnNextBootKey, out var doNotLoad);
|
if (_dataStore == null) return false;
|
||||||
|
|
||||||
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
if (!_dataStore.TryGetLocalBool(DoNotLoadOnNextBootKey, out var doNotLoad))
|
||||||
{
|
{
|
||||||
LogError("Error retrieving DoNotLoadOnNextBoot value: {err}", err);
|
LogError("Error retrieving DoNotLoadOnNextBoot value");
|
||||||
doNotLoad = false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return doNotLoad;
|
return doNotLoad;
|
||||||
|
|
@ -294,17 +316,17 @@ public static class Debug
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = CrestronDataStoreStatic.GetLocalIntValue(levelStoreKey, out int logLevel);
|
if (_dataStore == null) return LogEventLevel.Information;
|
||||||
|
|
||||||
if (result != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
if (!_dataStore.TryGetLocalInt(levelStoreKey, out int logLevel))
|
||||||
{
|
{
|
||||||
CrestronConsole.Print($"Unable to retrieve stored log level for {levelStoreKey}.\r\nError: {result}.\r\nSetting level to {LogEventLevel.Information}\r\n");
|
_console?.Print($"Unable to retrieve stored log level for {levelStoreKey}. Setting level to {LogEventLevel.Information}\r\n");
|
||||||
return LogEventLevel.Information;
|
return LogEventLevel.Information;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logLevel < 0 || logLevel > 5)
|
if (logLevel < 0 || logLevel > 5)
|
||||||
{
|
{
|
||||||
CrestronConsole.PrintLine($"Stored Log level not valid for {levelStoreKey}: {logLevel}. Setting level to {LogEventLevel.Information}");
|
_console?.PrintLine($"Stored Log level not valid for {levelStoreKey}: {logLevel}. Setting level to {LogEventLevel.Information}");
|
||||||
return LogEventLevel.Information;
|
return LogEventLevel.Information;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -312,7 +334,7 @@ public static class Debug
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
CrestronConsole.PrintLine($"Exception retrieving log level for {levelStoreKey}: {ex.Message}");
|
_console?.PrintLine($"Exception retrieving log level for {levelStoreKey}: {ex.Message}");
|
||||||
return LogEventLevel.Information;
|
return LogEventLevel.Information;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -369,7 +391,7 @@ public static class Debug
|
||||||
{
|
{
|
||||||
if (levelString.Trim() == "?")
|
if (levelString.Trim() == "?")
|
||||||
{
|
{
|
||||||
CrestronConsole.ConsoleCommandResponse(
|
_console?.ConsoleCommandResponse(
|
||||||
|
|
||||||
"Used to set the minimum level of debug messages to be printed to the console:\r\n" +
|
"Used to set the minimum level of debug messages to be printed to the console:\r\n" +
|
||||||
"[LogLevel] [TimeoutInMinutes]\r\n" +
|
"[LogLevel] [TimeoutInMinutes]\r\n" +
|
||||||
|
|
@ -386,7 +408,7 @@ public static class Debug
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(levelString.Trim()))
|
if (string.IsNullOrEmpty(levelString.Trim()))
|
||||||
{
|
{
|
||||||
CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", consoleLoggingLevelSwitch.MinimumLevel);
|
_console?.ConsoleCommandResponse($"AppDebug level = {consoleLoggingLevelSwitch.MinimumLevel}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -406,7 +428,7 @@ public static class Debug
|
||||||
{
|
{
|
||||||
if (levelInt < 0 || levelInt > 5)
|
if (levelInt < 0 || levelInt > 5)
|
||||||
{
|
{
|
||||||
CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level. If using a number, value must be between 0-5");
|
_console?.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level. If using a number, value must be between 0-5");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SetDebugLevel((uint)levelInt);
|
SetDebugLevel((uint)levelInt);
|
||||||
|
|
@ -420,11 +442,11 @@ public static class Debug
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level");
|
_console?.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level");
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [0-5]");
|
_console?.ConsoleCommandResponse("Usage: appdebug:P [0-5]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -439,7 +461,7 @@ public static class Debug
|
||||||
{
|
{
|
||||||
logLevel = LogEventLevel.Information;
|
logLevel = LogEventLevel.Information;
|
||||||
|
|
||||||
CrestronConsole.ConsoleCommandResponse($"{level} not valid. Setting level to {logLevel}");
|
_console?.ConsoleCommandResponse($"{level} not valid. Setting level to {logLevel}");
|
||||||
|
|
||||||
SetDebugLevel(logLevel, timeout);
|
SetDebugLevel(logLevel, timeout);
|
||||||
}
|
}
|
||||||
|
|
@ -460,17 +482,14 @@ public static class Debug
|
||||||
|
|
||||||
consoleLoggingLevelSwitch.MinimumLevel = level;
|
consoleLoggingLevelSwitch.MinimumLevel = level;
|
||||||
|
|
||||||
CrestronConsole.ConsoleCommandResponse("[Application {0}], Debug level set to {1}\r\n",
|
var appNum = _environment?.ApplicationNumber ?? 0;
|
||||||
InitialParametersClass.ApplicationNumber, consoleLoggingLevelSwitch.MinimumLevel);
|
_console?.ConsoleCommandResponse($"[Application {appNum}], Debug level set to {consoleLoggingLevelSwitch.MinimumLevel}\r\n");
|
||||||
|
_console?.ConsoleCommandResponse($"Storing level {level}:{(int)level}");
|
||||||
|
|
||||||
CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int)level}");
|
if (_dataStore != null && !_dataStore.SetLocalInt(LevelStoreKey, (int)level))
|
||||||
|
_console?.PrintLine($"Error saving console debug level setting");
|
||||||
var err = CrestronDataStoreStatic.SetLocalIntValue(LevelStoreKey, (int)level);
|
else
|
||||||
|
_console?.ConsoleCommandResponse($"Store result: {(int)level}");
|
||||||
CrestronConsole.ConsoleCommandResponse($"Store result: {err}:{(int)level}");
|
|
||||||
|
|
||||||
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
|
||||||
CrestronConsole.PrintLine($"Error saving console debug level setting: {err}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -481,10 +500,8 @@ public static class Debug
|
||||||
{
|
{
|
||||||
websocketLoggingLevelSwitch.MinimumLevel = level;
|
websocketLoggingLevelSwitch.MinimumLevel = level;
|
||||||
|
|
||||||
var err = CrestronDataStoreStatic.SetLocalUintValue(WebSocketLevelStoreKey, (uint)level);
|
if (_dataStore != null && !_dataStore.SetLocalUint(WebSocketLevelStoreKey, (uint)level))
|
||||||
|
LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting");
|
||||||
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
|
||||||
LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting: {error}", err);
|
|
||||||
|
|
||||||
LogMessage(LogEventLevel.Information, "Websocket debug level set to {0}", websocketLoggingLevelSwitch.MinimumLevel);
|
LogMessage(LogEventLevel.Information, "Websocket debug level set to {0}", websocketLoggingLevelSwitch.MinimumLevel);
|
||||||
}
|
}
|
||||||
|
|
@ -498,10 +515,8 @@ public static class Debug
|
||||||
{
|
{
|
||||||
errorLogLevelSwitch.MinimumLevel = level;
|
errorLogLevelSwitch.MinimumLevel = level;
|
||||||
|
|
||||||
var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level);
|
if (_dataStore != null && !_dataStore.SetLocalUint(ErrorLogLevelStoreKey, (uint)level))
|
||||||
|
LogMessage(LogEventLevel.Information, "Error saving Error Log debug level setting");
|
||||||
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
|
||||||
LogMessage(LogEventLevel.Information, "Error saving Error Log debug level setting: {error}", err);
|
|
||||||
|
|
||||||
LogMessage(LogEventLevel.Information, "Error log debug level set to {0}", errorLogLevelSwitch.MinimumLevel);
|
LogMessage(LogEventLevel.Information, "Error log debug level set to {0}", errorLogLevelSwitch.MinimumLevel);
|
||||||
}
|
}
|
||||||
|
|
@ -513,10 +528,8 @@ public static class Debug
|
||||||
{
|
{
|
||||||
fileLoggingLevelSwitch.MinimumLevel = level;
|
fileLoggingLevelSwitch.MinimumLevel = level;
|
||||||
|
|
||||||
var err = CrestronDataStoreStatic.SetLocalUintValue(FileLevelStoreKey, (uint)level);
|
if (_dataStore != null && !_dataStore.SetLocalUint(FileLevelStoreKey, (uint)level))
|
||||||
|
LogMessage(LogEventLevel.Information, "Error saving File debug level setting");
|
||||||
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
|
||||||
LogMessage(LogEventLevel.Information, "Error saving File debug level setting: {error}", err);
|
|
||||||
|
|
||||||
LogMessage(LogEventLevel.Information, "File debug level set to {0}", fileLoggingLevelSwitch.MinimumLevel);
|
LogMessage(LogEventLevel.Information, "File debug level set to {0}", fileLoggingLevelSwitch.MinimumLevel);
|
||||||
}
|
}
|
||||||
|
|
@ -531,7 +544,7 @@ public static class Debug
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(stateString.Trim()))
|
if (string.IsNullOrEmpty(stateString.Trim()))
|
||||||
{
|
{
|
||||||
CrestronConsole.ConsoleCommandResponse("DoNotLoadOnNextBoot = {0}", DoNotLoadConfigOnNextBoot);
|
_console?.ConsoleCommandResponse($"DoNotLoadOnNextBoot = {DoNotLoadConfigOnNextBoot}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -539,7 +552,7 @@ public static class Debug
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
CrestronConsole.ConsoleCommandResponse("Usage: donotloadonnextboot:P [true/false]");
|
_console?.ConsoleCommandResponse("Usage: donotloadonnextboot:P [true/false]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -655,10 +668,8 @@ public static class Debug
|
||||||
{
|
{
|
||||||
DoNotLoadConfigOnNextBoot = state;
|
DoNotLoadConfigOnNextBoot = state;
|
||||||
|
|
||||||
var err = CrestronDataStoreStatic.SetLocalBoolValue(DoNotLoadOnNextBootKey, state);
|
if (_dataStore != null && !_dataStore.SetLocalBool(DoNotLoadOnNextBootKey, state))
|
||||||
|
LogError("Error saving DoNotLoadConfigOnNextBoot setting");
|
||||||
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
|
||||||
LogError("Error saving console debug level setting: {err}", err);
|
|
||||||
|
|
||||||
LogInformation("Do Not Load Config on Next Boot set to {state}", DoNotLoadConfigOnNextBoot);
|
LogInformation("Do Not Load Config on Next Boot set to {state}", DoNotLoadConfigOnNextBoot);
|
||||||
}
|
}
|
||||||
|
|
@ -668,9 +679,10 @@ public static class Debug
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void ShowDebugLog(string s)
|
public static void ShowDebugLog(string s)
|
||||||
{
|
{
|
||||||
|
if (_environment == null) return; // CrestronLogger not available in test environments
|
||||||
var loglist = CrestronLogger.PrintTheLog(s.ToLower() == "all");
|
var loglist = CrestronLogger.PrintTheLog(s.ToLower() == "all");
|
||||||
foreach (var l in loglist)
|
foreach (var l in loglist)
|
||||||
CrestronConsole.ConsoleCommandResponse(l + CrestronEnvironment.NewLine);
|
_console?.ConsoleCommandResponse(l + CrestronEnvironment.NewLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,9 @@
|
||||||
<PackageReference Include="SSH.NET" Version="2025.0.0" />
|
<PackageReference Include="SSH.NET" Version="2025.0.0" />
|
||||||
<PackageReference Include="WebSocketSharp-netstandard" Version="1.0.1" />
|
<PackageReference Include="WebSocketSharp-netstandard" Version="1.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\PepperDash.Core.Abstractions\PepperDash.Core.Abstractions.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="Comm\._GenericSshClient.cs" />
|
<Compile Remove="Comm\._GenericSshClient.cs" />
|
||||||
<Compile Remove="Comm\._GenericTcpIpClient.cs" />
|
<Compile Remove="Comm\._GenericTcpIpClient.cs" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
using FluentAssertions;
|
||||||
|
using PepperDash.Core.Abstractions;
|
||||||
|
using PepperDash.Core.Tests.Fakes;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace PepperDash.Essentials.Core.Tests.Config;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests for the configuration loading abstractions.
|
||||||
|
/// These verify behaviour of the test fakes and interfaces independently of
|
||||||
|
/// any Crestron SDK types (ConfigReader itself will be tested here once it
|
||||||
|
/// is migrated from Crestron.SimplSharp.CrestronIO to System.IO — see plan Phase 4).
|
||||||
|
/// </summary>
|
||||||
|
public class ConfigServiceFakesTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void DataStore_MultipleKeys_AreStoredIndependently()
|
||||||
|
{
|
||||||
|
var store = new InMemoryCrestronDataStore();
|
||||||
|
store.InitStore();
|
||||||
|
|
||||||
|
store.SetLocalInt("KeyA", 1);
|
||||||
|
store.SetLocalInt("KeyB", 2);
|
||||||
|
|
||||||
|
store.TryGetLocalInt("KeyA", out var a);
|
||||||
|
store.TryGetLocalInt("KeyB", out var b);
|
||||||
|
|
||||||
|
a.Should().Be(1);
|
||||||
|
b.Should().Be(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DataStore_OverwriteKey_ReturnsNewValue()
|
||||||
|
{
|
||||||
|
var store = new InMemoryCrestronDataStore();
|
||||||
|
store.SetLocalInt("Level", 1);
|
||||||
|
store.SetLocalInt("Level", 5);
|
||||||
|
|
||||||
|
store.TryGetLocalInt("Level", out var level);
|
||||||
|
level.Should().Be(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FakeEnvironment_CanBeConfiguredForServer()
|
||||||
|
{
|
||||||
|
var env = new FakeCrestronEnvironment
|
||||||
|
{
|
||||||
|
DevicePlatform = DevicePlatform.Server,
|
||||||
|
ApplicationNumber = 1,
|
||||||
|
RoomId = 42,
|
||||||
|
};
|
||||||
|
|
||||||
|
env.DevicePlatform.Should().Be(DevicePlatform.Server);
|
||||||
|
env.RoomId.Should().Be(42u);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FakeEthernetHelper_SeedAndRetrieve()
|
||||||
|
{
|
||||||
|
var eth = new FakeEthernetHelper()
|
||||||
|
.Seed(EthernetParameterType.GetCurrentIpAddress, "192.168.1.100")
|
||||||
|
.Seed(EthernetParameterType.GetHostname, "MC4-TEST");
|
||||||
|
|
||||||
|
eth.GetEthernetParameter(EthernetParameterType.GetCurrentIpAddress, 0)
|
||||||
|
.Should().Be("192.168.1.100");
|
||||||
|
|
||||||
|
eth.GetEthernetParameter(EthernetParameterType.GetHostname, 0)
|
||||||
|
.Should().Be("MC4-TEST");
|
||||||
|
|
||||||
|
eth.GetEthernetParameter(EthernetParameterType.GetDomainName, 0)
|
||||||
|
.Should().BeEmpty("unseeded parameter should return empty string");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||||
|
<PackageReference Include="FluentAssertions" Version="7.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Reference the Abstractions project only — no Crestron SDK dependency -->
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\PepperDash.Core.Abstractions\PepperDash.Core.Abstractions.csproj" />
|
||||||
|
<!-- Reference Core.Tests to share the Fakes folder -->
|
||||||
|
<ProjectReference Include="..\PepperDash.Core.Tests\PepperDash.Core.Tests.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
|
|
@ -7,6 +7,8 @@ using Crestron.SimplSharp.CrestronIO;
|
||||||
using Crestron.SimplSharpPro;
|
using Crestron.SimplSharpPro;
|
||||||
using Crestron.SimplSharpPro.Diagnostics;
|
using Crestron.SimplSharpPro.Diagnostics;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
|
using PepperDash.Core.Abstractions;
|
||||||
|
using PepperDash.Core.Adapters;
|
||||||
using PepperDash.Essentials.Core;
|
using PepperDash.Essentials.Core;
|
||||||
using PepperDash.Essentials.Core.Bridges;
|
using PepperDash.Essentials.Core.Bridges;
|
||||||
using PepperDash.Essentials.Core.Config;
|
using PepperDash.Essentials.Core.Config;
|
||||||
|
|
@ -46,6 +48,14 @@ public class ControlSystem : CrestronControlSystem, ILoadConfig
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Register Crestron service adapters BEFORE the first reference to Debug,
|
||||||
|
// so that Debug's static constructor uses these implementations instead of
|
||||||
|
// calling the Crestron SDK statics directly.
|
||||||
|
DebugServiceRegistration.Register(
|
||||||
|
new CrestronEnvironmentAdapter(),
|
||||||
|
new CrestronConsoleAdapter(),
|
||||||
|
new CrestronDataStoreAdapter());
|
||||||
|
|
||||||
Crestron.SimplSharpPro.CrestronThread.Thread.MaxNumberOfUserThreads = 400;
|
Crestron.SimplSharpPro.CrestronThread.Thread.MaxNumberOfUserThreads = 400;
|
||||||
|
|
||||||
Global.ControlSystem = this;
|
Global.ControlSystem = this;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue