mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-02-14 12:15:01 +00:00
test: change methodology to have a mock assembly
This commit is contained in:
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
using PepperDash.Essentials.Core.Abstractions;
|
using PepperDash.Essentials.Core.Abstractions;
|
||||||
using PepperDash.Essentials.Core.CrestronIO;
|
using PepperDash.Essentials.Core.CrestronIO;
|
||||||
|
using PepperDash.Essentials.Core.Factory;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Devices
|
namespace PepperDash.Essentials.Core.Devices
|
||||||
@@ -16,11 +17,12 @@ namespace PepperDash.Essentials.Core.Devices
|
|||||||
|
|
||||||
public ICrestronControlSystem Processor { get; private set; }
|
public ICrestronControlSystem Processor { get; private set; }
|
||||||
|
|
||||||
public CrestronProcessorTestable(string key, ICrestronControlSystem processor)
|
public CrestronProcessorTestable(string key, ICrestronControlSystem processor = null)
|
||||||
: base(key)
|
: base(key)
|
||||||
{
|
{
|
||||||
SwitchedOutputs = new Dictionary<uint, ISwitchedOutput>();
|
SwitchedOutputs = new Dictionary<uint, ISwitchedOutput>();
|
||||||
Processor = processor ?? throw new ArgumentNullException(nameof(processor));
|
// Use factory if processor not provided
|
||||||
|
Processor = processor ?? CrestronEnvironmentFactory.GetControlSystem();
|
||||||
GetRelays();
|
GetRelays();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
using System;
|
||||||
|
using PepperDash.Essentials.Core.Abstractions;
|
||||||
|
|
||||||
|
namespace PepperDash.Essentials.Core.Factory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Factory for creating Crestron environment dependencies
|
||||||
|
/// Allows switching between real Crestron libraries and mock implementations
|
||||||
|
/// </summary>
|
||||||
|
public static class CrestronEnvironmentFactory
|
||||||
|
{
|
||||||
|
private static ICrestronEnvironmentProvider _provider;
|
||||||
|
private static bool _isTestMode = false;
|
||||||
|
|
||||||
|
static CrestronEnvironmentFactory()
|
||||||
|
{
|
||||||
|
// Default to runtime provider
|
||||||
|
_provider = new CrestronRuntimeProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables test mode with mock implementations
|
||||||
|
/// </summary>
|
||||||
|
public static void EnableTestMode()
|
||||||
|
{
|
||||||
|
_isTestMode = true;
|
||||||
|
_provider = new CrestronMockProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disables test mode and returns to runtime implementations
|
||||||
|
/// </summary>
|
||||||
|
public static void DisableTestMode()
|
||||||
|
{
|
||||||
|
_isTestMode = false;
|
||||||
|
_provider = new CrestronRuntimeProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets a custom provider for Crestron environment
|
||||||
|
/// </summary>
|
||||||
|
public static void SetProvider(ICrestronEnvironmentProvider provider)
|
||||||
|
{
|
||||||
|
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the factory is in test mode
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsTestMode => _isTestMode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current control system instance
|
||||||
|
/// </summary>
|
||||||
|
public static ICrestronControlSystem GetControlSystem()
|
||||||
|
{
|
||||||
|
return _provider.GetControlSystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a relay port
|
||||||
|
/// </summary>
|
||||||
|
public static IRelayPort CreateRelayPort(uint portNumber)
|
||||||
|
{
|
||||||
|
return _provider.CreateRelayPort(portNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a digital input
|
||||||
|
/// </summary>
|
||||||
|
public static IDigitalInput CreateDigitalInput(uint portNumber)
|
||||||
|
{
|
||||||
|
return _provider.CreateDigitalInput(portNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a versiport
|
||||||
|
/// </summary>
|
||||||
|
public static IVersiPort CreateVersiPort(uint portNumber)
|
||||||
|
{
|
||||||
|
return _provider.CreateVersiPort(portNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets console manager for debugging
|
||||||
|
/// </summary>
|
||||||
|
public static IConsoleManager GetConsoleManager()
|
||||||
|
{
|
||||||
|
return _provider.GetConsoleManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets system information
|
||||||
|
/// </summary>
|
||||||
|
public static ISystemInfo GetSystemInfo()
|
||||||
|
{
|
||||||
|
return _provider.GetSystemInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provider interface for Crestron environment dependencies
|
||||||
|
/// </summary>
|
||||||
|
public interface ICrestronEnvironmentProvider
|
||||||
|
{
|
||||||
|
ICrestronControlSystem GetControlSystem();
|
||||||
|
IRelayPort CreateRelayPort(uint portNumber);
|
||||||
|
IDigitalInput CreateDigitalInput(uint portNumber);
|
||||||
|
IVersiPort CreateVersiPort(uint portNumber);
|
||||||
|
IConsoleManager GetConsoleManager();
|
||||||
|
ISystemInfo GetSystemInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Console manager abstraction
|
||||||
|
/// </summary>
|
||||||
|
public interface IConsoleManager
|
||||||
|
{
|
||||||
|
void Print(string message);
|
||||||
|
void PrintLine(string message);
|
||||||
|
void RegisterCommand(string command, Action<string> handler, string help);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// System information abstraction
|
||||||
|
/// </summary>
|
||||||
|
public interface ISystemInfo
|
||||||
|
{
|
||||||
|
string ProgramName { get; }
|
||||||
|
string SerialNumber { get; }
|
||||||
|
string MacAddress { get; }
|
||||||
|
string IpAddress { get; }
|
||||||
|
string FirmwareVersion { get; }
|
||||||
|
DateTime SystemUpTime { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
265
src/PepperDash.Essentials.Core/Factory/CrestronMockProvider.cs
Normal file
265
src/PepperDash.Essentials.Core/Factory/CrestronMockProvider.cs
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using PepperDash.Essentials.Core.Abstractions;
|
||||||
|
|
||||||
|
namespace PepperDash.Essentials.Core.Factory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Mock provider for unit testing without Crestron hardware
|
||||||
|
/// </summary>
|
||||||
|
public class CrestronMockProvider : ICrestronEnvironmentProvider
|
||||||
|
{
|
||||||
|
private MockControlSystem _controlSystem;
|
||||||
|
|
||||||
|
public CrestronMockProvider()
|
||||||
|
{
|
||||||
|
_controlSystem = new MockControlSystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICrestronControlSystem GetControlSystem()
|
||||||
|
{
|
||||||
|
return _controlSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IRelayPort CreateRelayPort(uint portNumber)
|
||||||
|
{
|
||||||
|
if (!_controlSystem.RelayPorts.ContainsKey(portNumber))
|
||||||
|
{
|
||||||
|
_controlSystem.RelayPorts[portNumber] = new MockRelayPort();
|
||||||
|
}
|
||||||
|
return _controlSystem.RelayPorts[portNumber];
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDigitalInput CreateDigitalInput(uint portNumber)
|
||||||
|
{
|
||||||
|
if (!_controlSystem.DigitalInputs.ContainsKey(portNumber))
|
||||||
|
{
|
||||||
|
_controlSystem.DigitalInputs[portNumber] = new MockDigitalInput();
|
||||||
|
}
|
||||||
|
return _controlSystem.DigitalInputs[portNumber];
|
||||||
|
}
|
||||||
|
|
||||||
|
public IVersiPort CreateVersiPort(uint portNumber)
|
||||||
|
{
|
||||||
|
if (!_controlSystem.VersiPorts.ContainsKey(portNumber))
|
||||||
|
{
|
||||||
|
_controlSystem.VersiPorts[portNumber] = new MockVersiPort();
|
||||||
|
}
|
||||||
|
return _controlSystem.VersiPorts[portNumber];
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConsoleManager GetConsoleManager()
|
||||||
|
{
|
||||||
|
return new MockConsoleManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISystemInfo GetSystemInfo()
|
||||||
|
{
|
||||||
|
return new MockSystemInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures the mock control system for testing
|
||||||
|
/// </summary>
|
||||||
|
public void ConfigureMockSystem(Action<MockControlSystem> configure)
|
||||||
|
{
|
||||||
|
configure(_controlSystem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mock implementation of control system for testing
|
||||||
|
/// </summary>
|
||||||
|
public class MockControlSystem : ICrestronControlSystem
|
||||||
|
{
|
||||||
|
public bool SupportsRelay { get; set; } = true;
|
||||||
|
public uint NumberOfRelayPorts { get; set; } = 8;
|
||||||
|
public Dictionary<uint, IRelayPort> RelayPorts { get; set; } = new Dictionary<uint, IRelayPort>();
|
||||||
|
public Dictionary<uint, IDigitalInput> DigitalInputs { get; set; } = new Dictionary<uint, IDigitalInput>();
|
||||||
|
public Dictionary<uint, IVersiPort> VersiPorts { get; set; } = new Dictionary<uint, IVersiPort>();
|
||||||
|
public string ProgramIdTag { get; set; } = "TEST_PROGRAM";
|
||||||
|
public string ControllerPrompt { get; set; } = "TEST>";
|
||||||
|
public bool SupportsEthernet { get; set; } = true;
|
||||||
|
public bool SupportsDigitalInput { get; set; } = true;
|
||||||
|
public uint NumberOfDigitalInputPorts { get; set; } = 8;
|
||||||
|
public bool SupportsVersiPort { get; set; } = true;
|
||||||
|
public uint NumberOfVersiPorts { get; set; } = 8;
|
||||||
|
|
||||||
|
public MockControlSystem()
|
||||||
|
{
|
||||||
|
// Initialize with default relay ports
|
||||||
|
for (uint i = 1; i <= NumberOfRelayPorts; i++)
|
||||||
|
{
|
||||||
|
RelayPorts[i] = new MockRelayPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize with default digital inputs
|
||||||
|
for (uint i = 1; i <= NumberOfDigitalInputPorts; i++)
|
||||||
|
{
|
||||||
|
DigitalInputs[i] = new MockDigitalInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize with default versiports
|
||||||
|
for (uint i = 1; i <= NumberOfVersiPorts; i++)
|
||||||
|
{
|
||||||
|
VersiPorts[i] = new MockVersiPort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mock implementation of relay port for testing
|
||||||
|
/// </summary>
|
||||||
|
public class MockRelayPort : IRelayPort
|
||||||
|
{
|
||||||
|
private bool _state;
|
||||||
|
|
||||||
|
public bool State => _state;
|
||||||
|
|
||||||
|
public event EventHandler<bool> StateChanged;
|
||||||
|
|
||||||
|
public void Open()
|
||||||
|
{
|
||||||
|
_state = false;
|
||||||
|
StateChanged?.Invoke(this, _state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
_state = true;
|
||||||
|
StateChanged?.Invoke(this, _state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Pulse(int delayMs)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
System.Threading.Tasks.Task.Delay(delayMs).ContinueWith(_ => Open());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test helper to set state directly
|
||||||
|
/// </summary>
|
||||||
|
public void SetState(bool state)
|
||||||
|
{
|
||||||
|
_state = state;
|
||||||
|
StateChanged?.Invoke(this, _state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mock implementation of digital input for testing
|
||||||
|
/// </summary>
|
||||||
|
public class MockDigitalInput : IDigitalInput
|
||||||
|
{
|
||||||
|
private bool _state;
|
||||||
|
|
||||||
|
public bool State => _state;
|
||||||
|
|
||||||
|
public event EventHandler<Abstractions.DigitalInputEventArgs> StateChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test helper to simulate input change
|
||||||
|
/// </summary>
|
||||||
|
public void SimulateStateChange(bool newState)
|
||||||
|
{
|
||||||
|
_state = newState;
|
||||||
|
StateChange?.Invoke(this, new Abstractions.DigitalInputEventArgs(newState));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mock implementation of versiport for testing
|
||||||
|
/// </summary>
|
||||||
|
public class MockVersiPort : IVersiPort
|
||||||
|
{
|
||||||
|
private bool _digitalIn;
|
||||||
|
private bool _digitalOut;
|
||||||
|
private ushort _analogIn;
|
||||||
|
|
||||||
|
public bool DigitalIn => _digitalIn;
|
||||||
|
public ushort AnalogIn => _analogIn;
|
||||||
|
|
||||||
|
public event EventHandler<Abstractions.VersiPortEventArgs> VersiportChange;
|
||||||
|
|
||||||
|
public void SetDigitalOut(bool value)
|
||||||
|
{
|
||||||
|
_digitalOut = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test helper to simulate digital input change
|
||||||
|
/// </summary>
|
||||||
|
public void SimulateDigitalInChange(bool value)
|
||||||
|
{
|
||||||
|
_digitalIn = value;
|
||||||
|
VersiportChange?.Invoke(this, new Abstractions.VersiPortEventArgs
|
||||||
|
{
|
||||||
|
EventType = Abstractions.VersiPortEventType.DigitalInChange,
|
||||||
|
Value = value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test helper to simulate analog input change
|
||||||
|
/// </summary>
|
||||||
|
public void SimulateAnalogInChange(ushort value)
|
||||||
|
{
|
||||||
|
_analogIn = value;
|
||||||
|
VersiportChange?.Invoke(this, new Abstractions.VersiPortEventArgs
|
||||||
|
{
|
||||||
|
EventType = Abstractions.VersiPortEventType.AnalogInChange,
|
||||||
|
Value = value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mock implementation of console manager for testing
|
||||||
|
/// </summary>
|
||||||
|
public class MockConsoleManager : IConsoleManager
|
||||||
|
{
|
||||||
|
public List<string> OutputLines { get; } = new List<string>();
|
||||||
|
public Dictionary<string, Action<string>> Commands { get; } = new Dictionary<string, Action<string>>();
|
||||||
|
|
||||||
|
public void Print(string message)
|
||||||
|
{
|
||||||
|
OutputLines.Add(message);
|
||||||
|
Console.Write(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PrintLine(string message)
|
||||||
|
{
|
||||||
|
OutputLines.Add(message);
|
||||||
|
Console.WriteLine(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterCommand(string command, Action<string> handler, string help)
|
||||||
|
{
|
||||||
|
Commands[command] = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test helper to execute a registered command
|
||||||
|
/// </summary>
|
||||||
|
public void ExecuteCommand(string command, string args)
|
||||||
|
{
|
||||||
|
if (Commands.TryGetValue(command, out var handler))
|
||||||
|
{
|
||||||
|
handler(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mock implementation of system info for testing
|
||||||
|
/// </summary>
|
||||||
|
public class MockSystemInfo : ISystemInfo
|
||||||
|
{
|
||||||
|
public string ProgramName { get; set; } = "TestProgram";
|
||||||
|
public string SerialNumber { get; set; } = "TEST123456";
|
||||||
|
public string MacAddress { get; set; } = "00:11:22:33:44:55";
|
||||||
|
public string IpAddress { get; set; } = "192.168.1.100";
|
||||||
|
public string FirmwareVersion { get; set; } = "1.0.0.0";
|
||||||
|
public DateTime SystemUpTime { get; set; } = DateTime.Now.AddHours(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using PepperDash.Essentials.Core.Abstractions;
|
||||||
|
|
||||||
|
#if !TEST_BUILD
|
||||||
|
using Crestron.SimplSharp;
|
||||||
|
using Crestron.SimplSharpPro;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace PepperDash.Essentials.Core.Factory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Runtime provider that uses actual Crestron libraries
|
||||||
|
/// </summary>
|
||||||
|
public class CrestronRuntimeProvider : ICrestronEnvironmentProvider
|
||||||
|
{
|
||||||
|
private ICrestronControlSystem _controlSystem;
|
||||||
|
|
||||||
|
public ICrestronControlSystem GetControlSystem()
|
||||||
|
{
|
||||||
|
if (_controlSystem == null)
|
||||||
|
{
|
||||||
|
#if !TEST_BUILD
|
||||||
|
// In runtime, wrap the actual Crestron control system
|
||||||
|
// Note: This would need to be adapted based on the actual Crestron API
|
||||||
|
// For now, return a null implementation
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
// Return a null object pattern implementation for non-Crestron environments
|
||||||
|
_controlSystem = new NullControlSystem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _controlSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IRelayPort CreateRelayPort(uint portNumber)
|
||||||
|
{
|
||||||
|
#if !TEST_BUILD
|
||||||
|
var controlSystem = GetControlSystem();
|
||||||
|
if (controlSystem.RelayPorts.TryGetValue(portNumber, out var port))
|
||||||
|
{
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return new NullRelayPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDigitalInput CreateDigitalInput(uint portNumber)
|
||||||
|
{
|
||||||
|
#if !TEST_BUILD
|
||||||
|
// Implementation would wrap actual Crestron digital input
|
||||||
|
// This is a simplified version
|
||||||
|
#endif
|
||||||
|
return new NullDigitalInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IVersiPort CreateVersiPort(uint portNumber)
|
||||||
|
{
|
||||||
|
#if !TEST_BUILD
|
||||||
|
// Implementation would wrap actual Crestron versiport
|
||||||
|
// This is a simplified version
|
||||||
|
#endif
|
||||||
|
return new NullVersiPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConsoleManager GetConsoleManager()
|
||||||
|
{
|
||||||
|
#if !TEST_BUILD
|
||||||
|
return new CrestronConsoleManager();
|
||||||
|
#else
|
||||||
|
return new NullConsoleManager();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISystemInfo GetSystemInfo()
|
||||||
|
{
|
||||||
|
#if !TEST_BUILD
|
||||||
|
return new CrestronSystemInfo();
|
||||||
|
#else
|
||||||
|
return new NullSystemInfo();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Null Object Pattern Implementations
|
||||||
|
|
||||||
|
private class NullControlSystem : ICrestronControlSystem
|
||||||
|
{
|
||||||
|
public bool SupportsRelay => false;
|
||||||
|
public uint NumberOfRelayPorts => 0;
|
||||||
|
public Dictionary<uint, IRelayPort> RelayPorts => new Dictionary<uint, IRelayPort>();
|
||||||
|
public string ProgramIdTag => "NULL";
|
||||||
|
public string ControllerPrompt => "NULL>";
|
||||||
|
public bool SupportsEthernet => false;
|
||||||
|
public bool SupportsDigitalInput => false;
|
||||||
|
public uint NumberOfDigitalInputPorts => 0;
|
||||||
|
public bool SupportsVersiPort => false;
|
||||||
|
public uint NumberOfVersiPorts => 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NullRelayPort : IRelayPort
|
||||||
|
{
|
||||||
|
public bool State => false;
|
||||||
|
public void Open() { }
|
||||||
|
public void Close() { }
|
||||||
|
public void Pulse(int delayMs) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NullDigitalInput : IDigitalInput
|
||||||
|
{
|
||||||
|
public bool State => false;
|
||||||
|
public event EventHandler<Abstractions.DigitalInputEventArgs> StateChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NullVersiPort : IVersiPort
|
||||||
|
{
|
||||||
|
public bool DigitalIn => false;
|
||||||
|
public ushort AnalogIn => 0;
|
||||||
|
public void SetDigitalOut(bool value) { }
|
||||||
|
public event EventHandler<Abstractions.VersiPortEventArgs> VersiportChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NullConsoleManager : IConsoleManager
|
||||||
|
{
|
||||||
|
public void Print(string message) { }
|
||||||
|
public void PrintLine(string message) { }
|
||||||
|
public void RegisterCommand(string command, Action<string> handler, string help) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NullSystemInfo : ISystemInfo
|
||||||
|
{
|
||||||
|
public string ProgramName => "NULL";
|
||||||
|
public string SerialNumber => "000000";
|
||||||
|
public string MacAddress => "00:00:00:00:00:00";
|
||||||
|
public string IpAddress => "0.0.0.0";
|
||||||
|
public string FirmwareVersion => "0.0.0";
|
||||||
|
public DateTime SystemUpTime => DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#if !TEST_BUILD
|
||||||
|
private class CrestronConsoleManager : IConsoleManager
|
||||||
|
{
|
||||||
|
public void Print(string message)
|
||||||
|
{
|
||||||
|
CrestronConsole.Print(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PrintLine(string message)
|
||||||
|
{
|
||||||
|
CrestronConsole.PrintLine(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterCommand(string command, Action<string> handler, string help)
|
||||||
|
{
|
||||||
|
CrestronConsole.AddNewConsoleCommand((s) => handler(s), command, help, ConsoleAccessLevelEnum.AccessOperator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CrestronSystemInfo : ISystemInfo
|
||||||
|
{
|
||||||
|
public string ProgramName => "CrestronProgram";
|
||||||
|
public string SerialNumber => "000000";
|
||||||
|
public string MacAddress => "00:00:00:00:00:00";
|
||||||
|
public string IpAddress => "0.0.0.0";
|
||||||
|
public string FirmwareVersion => "1.0.0";
|
||||||
|
public DateTime SystemUpTime => DateTime.Now;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,384 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Xunit;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
|
using PepperDash.Essentials.Core.Devices;
|
||||||
|
using PepperDash.Essentials.Core.Abstractions;
|
||||||
|
using PepperDash.Essentials.Core.Factory;
|
||||||
|
using PepperDash.Essentials.Core.CrestronIO;
|
||||||
|
|
||||||
|
namespace PepperDash.Essentials.Core.Tests.Devices
|
||||||
|
{
|
||||||
|
public class CrestronProcessorTestableTests : IDisposable
|
||||||
|
{
|
||||||
|
public CrestronProcessorTestableTests()
|
||||||
|
{
|
||||||
|
// Enable test mode for all tests in this class
|
||||||
|
CrestronEnvironmentFactory.EnableTestMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Restore runtime mode after tests
|
||||||
|
CrestronEnvironmentFactory.DisableTestMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WithNullProcessor_UsesFactoryToGetControlSystem()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var processor = new CrestronProcessorTestable("test-processor");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
processor.Should().NotBeNull();
|
||||||
|
processor.Processor.Should().NotBeNull();
|
||||||
|
processor.Key.Should().Be("test-processor");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WithProvidedProcessor_UsesProvidedProcessor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockProcessor = new Mock<ICrestronControlSystem>();
|
||||||
|
mockProcessor.Setup(p => p.SupportsRelay).Returns(false);
|
||||||
|
mockProcessor.Setup(p => p.RelayPorts).Returns(new Dictionary<uint, IRelayPort>());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var processor = new CrestronProcessorTestable("test-processor", mockProcessor.Object);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
processor.Processor.Should().BeSameAs(mockProcessor.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetRelays_WhenProcessorSupportsRelays_CreatesRelayDevices()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockProvider = new CrestronMockProvider();
|
||||||
|
mockProvider.ConfigureMockSystem(system =>
|
||||||
|
{
|
||||||
|
system.SupportsRelay = true;
|
||||||
|
system.NumberOfRelayPorts = 4;
|
||||||
|
// Ensure relay ports are initialized
|
||||||
|
for (uint i = 1; i <= 4; i++)
|
||||||
|
{
|
||||||
|
system.RelayPorts[i] = new MockRelayPort();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
CrestronEnvironmentFactory.SetProvider(mockProvider);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var processor = new CrestronProcessorTestable("test-processor");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
processor.SwitchedOutputs.Should().HaveCount(4);
|
||||||
|
processor.SwitchedOutputs.Should().ContainKeys(1, 2, 3, 4);
|
||||||
|
|
||||||
|
foreach (var kvp in processor.SwitchedOutputs)
|
||||||
|
{
|
||||||
|
kvp.Value.Should().BeOfType<GenericRelayDeviceTestable>();
|
||||||
|
var relayDevice = kvp.Value as GenericRelayDeviceTestable;
|
||||||
|
relayDevice.Key.Should().Be($"test-processor-relay-{kvp.Key}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetRelays_WhenProcessorDoesNotSupportRelays_CreatesNoDevices()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockProcessor = new Mock<ICrestronControlSystem>();
|
||||||
|
mockProcessor.Setup(p => p.SupportsRelay).Returns(false);
|
||||||
|
mockProcessor.Setup(p => p.RelayPorts).Returns(new Dictionary<uint, IRelayPort>());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var processor = new CrestronProcessorTestable("test-processor", mockProcessor.Object);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
processor.SwitchedOutputs.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetRelays_HandlesExceptionGracefully()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockProcessor = new Mock<ICrestronControlSystem>();
|
||||||
|
mockProcessor.Setup(p => p.SupportsRelay).Returns(true);
|
||||||
|
mockProcessor.Setup(p => p.NumberOfRelayPorts).Throws(new Exception("Test exception"));
|
||||||
|
mockProcessor.Setup(p => p.RelayPorts).Returns(new Dictionary<uint, IRelayPort>());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
Action act = () => new CrestronProcessorTestable("test-processor", mockProcessor.Object);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
act.Should().NotThrow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GenericRelayDeviceTestableTests : IDisposable
|
||||||
|
{
|
||||||
|
public GenericRelayDeviceTestableTests()
|
||||||
|
{
|
||||||
|
CrestronEnvironmentFactory.EnableTestMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
CrestronEnvironmentFactory.DisableTestMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WithNullRelayPort_ThrowsArgumentNullException()
|
||||||
|
{
|
||||||
|
// Act & Assert
|
||||||
|
Action act = () => new GenericRelayDeviceTestable("test-relay", null);
|
||||||
|
act.Should().Throw<ArgumentNullException>()
|
||||||
|
.WithParameterName("relayPort");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WithValidRelayPort_InitializesCorrectly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockRelayPort = new MockRelayPort();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var device = new GenericRelayDeviceTestable("test-relay", mockRelayPort);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
device.Should().NotBeNull();
|
||||||
|
device.Key.Should().Be("test-relay");
|
||||||
|
device.OutputIsOnFeedback.Should().NotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OpenRelay_OpensRelayAndUpdatesFeedback()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockRelayPort = new MockRelayPort();
|
||||||
|
mockRelayPort.SetState(true); // Start with closed relay
|
||||||
|
var device = new GenericRelayDeviceTestable("test-relay", mockRelayPort);
|
||||||
|
|
||||||
|
bool feedbackFired = false;
|
||||||
|
device.OutputIsOnFeedback.OutputChange += (sender, args) => feedbackFired = true;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
device.OpenRelay();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
mockRelayPort.State.Should().BeFalse();
|
||||||
|
device.IsOn.Should().BeFalse();
|
||||||
|
feedbackFired.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CloseRelay_ClosesRelayAndUpdatesFeedback()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockRelayPort = new MockRelayPort();
|
||||||
|
mockRelayPort.SetState(false); // Start with open relay
|
||||||
|
var device = new GenericRelayDeviceTestable("test-relay", mockRelayPort);
|
||||||
|
|
||||||
|
bool feedbackFired = false;
|
||||||
|
device.OutputIsOnFeedback.OutputChange += (sender, args) => feedbackFired = true;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
device.CloseRelay();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
mockRelayPort.State.Should().BeTrue();
|
||||||
|
device.IsOn.Should().BeTrue();
|
||||||
|
feedbackFired.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PulseRelay_CallsPulseOnRelayPort()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockRelayPort = new Mock<IRelayPort>();
|
||||||
|
var device = new GenericRelayDeviceTestable("test-relay", mockRelayPort.Object);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
device.PulseRelay(500);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
mockRelayPort.Verify(r => r.Pulse(500), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void On_ClosesRelay()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockRelayPort = new MockRelayPort();
|
||||||
|
var device = new GenericRelayDeviceTestable("test-relay", mockRelayPort);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
device.On();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
mockRelayPort.State.Should().BeTrue();
|
||||||
|
device.IsOn.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Off_OpensRelay()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockRelayPort = new MockRelayPort();
|
||||||
|
mockRelayPort.SetState(true); // Start with closed relay
|
||||||
|
var device = new GenericRelayDeviceTestable("test-relay", mockRelayPort);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
device.Off();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
mockRelayPort.State.Should().BeFalse();
|
||||||
|
device.IsOn.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PowerToggle_TogglesRelayState()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockRelayPort = new MockRelayPort();
|
||||||
|
var device = new GenericRelayDeviceTestable("test-relay", mockRelayPort);
|
||||||
|
|
||||||
|
// Act & Assert - First toggle (off to on)
|
||||||
|
device.PowerToggle();
|
||||||
|
mockRelayPort.State.Should().BeTrue();
|
||||||
|
device.IsOn.Should().BeTrue();
|
||||||
|
|
||||||
|
// Act & Assert - Second toggle (on to off)
|
||||||
|
device.PowerToggle();
|
||||||
|
mockRelayPort.State.Should().BeFalse();
|
||||||
|
device.IsOn.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsOn_ReflectsRelayPortState()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockRelayPort = new MockRelayPort();
|
||||||
|
var device = new GenericRelayDeviceTestable("test-relay", mockRelayPort);
|
||||||
|
|
||||||
|
// Act & Assert - Initially off
|
||||||
|
device.IsOn.Should().BeFalse();
|
||||||
|
|
||||||
|
// Act & Assert - After closing
|
||||||
|
mockRelayPort.Close();
|
||||||
|
device.IsOn.Should().BeTrue();
|
||||||
|
|
||||||
|
// Act & Assert - After opening
|
||||||
|
mockRelayPort.Open();
|
||||||
|
device.IsOn.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CustomActivate_ReinitializesFeedback()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockRelayPort = new MockRelayPort();
|
||||||
|
var device = new GenericRelayDeviceTestable("test-relay", mockRelayPort);
|
||||||
|
var originalFeedback = device.OutputIsOnFeedback;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = device.CustomActivate();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().BeTrue();
|
||||||
|
device.OutputIsOnFeedback.Should().NotBeNull();
|
||||||
|
device.OutputIsOnFeedback.Should().NotBeSameAs(originalFeedback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IntegrationTests : IDisposable
|
||||||
|
{
|
||||||
|
public IntegrationTests()
|
||||||
|
{
|
||||||
|
CrestronEnvironmentFactory.EnableTestMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
CrestronEnvironmentFactory.DisableTestMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FullSystemIntegration_CreatesAndControlsRelays()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockProvider = new CrestronMockProvider();
|
||||||
|
mockProvider.ConfigureMockSystem(system =>
|
||||||
|
{
|
||||||
|
system.SupportsRelay = true;
|
||||||
|
system.NumberOfRelayPorts = 2;
|
||||||
|
system.ProgramIdTag = "INTEGRATION_TEST";
|
||||||
|
});
|
||||||
|
|
||||||
|
CrestronEnvironmentFactory.SetProvider(mockProvider);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var processor = new CrestronProcessorTestable("integration-processor");
|
||||||
|
|
||||||
|
// Assert processor creation
|
||||||
|
processor.Processor.ProgramIdTag.Should().Be("INTEGRATION_TEST");
|
||||||
|
processor.SwitchedOutputs.Should().HaveCount(2);
|
||||||
|
|
||||||
|
// Test relay control
|
||||||
|
var relay1 = processor.SwitchedOutputs[1] as GenericRelayDeviceTestable;
|
||||||
|
relay1.Should().NotBeNull();
|
||||||
|
|
||||||
|
// Test On/Off operations
|
||||||
|
relay1.On();
|
||||||
|
relay1.IsOn.Should().BeTrue();
|
||||||
|
|
||||||
|
relay1.Off();
|
||||||
|
relay1.IsOn.Should().BeFalse();
|
||||||
|
|
||||||
|
// Test toggle
|
||||||
|
relay1.PowerToggle();
|
||||||
|
relay1.IsOn.Should().BeTrue();
|
||||||
|
|
||||||
|
relay1.PowerToggle();
|
||||||
|
relay1.IsOn.Should().BeFalse();
|
||||||
|
|
||||||
|
// Test feedback
|
||||||
|
int feedbackCount = 0;
|
||||||
|
relay1.OutputIsOnFeedback.OutputChange += (sender, args) => feedbackCount++;
|
||||||
|
|
||||||
|
relay1.On();
|
||||||
|
feedbackCount.Should().Be(1);
|
||||||
|
|
||||||
|
relay1.Off();
|
||||||
|
feedbackCount.Should().Be(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FactoryPattern_AllowsSwitchingBetweenProviders()
|
||||||
|
{
|
||||||
|
// Arrange - Start with test mode
|
||||||
|
CrestronEnvironmentFactory.EnableTestMode();
|
||||||
|
CrestronEnvironmentFactory.IsTestMode.Should().BeTrue();
|
||||||
|
|
||||||
|
var testProcessor = new CrestronProcessorTestable("test-mode");
|
||||||
|
testProcessor.Processor.Should().BeOfType<MockControlSystem>();
|
||||||
|
|
||||||
|
// Act - Switch to runtime mode
|
||||||
|
CrestronEnvironmentFactory.DisableTestMode();
|
||||||
|
CrestronEnvironmentFactory.IsTestMode.Should().BeFalse();
|
||||||
|
|
||||||
|
// Note: In runtime mode without actual Crestron hardware,
|
||||||
|
// it will use the NullControlSystem implementation
|
||||||
|
var runtimeProcessor = new CrestronProcessorTestable("runtime-mode");
|
||||||
|
runtimeProcessor.Processor.Should().NotBeNull();
|
||||||
|
|
||||||
|
// Act - Switch back to test mode
|
||||||
|
CrestronEnvironmentFactory.EnableTestMode();
|
||||||
|
CrestronEnvironmentFactory.IsTestMode.Should().BeTrue();
|
||||||
|
|
||||||
|
var testProcessor2 = new CrestronProcessorTestable("test-mode-2");
|
||||||
|
testProcessor2.Processor.Should().BeOfType<MockControlSystem>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
266
tools/CrestronMockGenerator/AssemblyAnalyzer.cs
Normal file
266
tools/CrestronMockGenerator/AssemblyAnalyzer.cs
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Loader;
|
||||||
|
using System.Text;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace CrestronMockGenerator
|
||||||
|
{
|
||||||
|
public class AssemblyAnalyzer
|
||||||
|
{
|
||||||
|
private readonly string _assemblyPath;
|
||||||
|
private readonly string _xmlDocPath;
|
||||||
|
private Dictionary<string, string> _xmlDocumentation = new();
|
||||||
|
|
||||||
|
public AssemblyAnalyzer(string assemblyPath)
|
||||||
|
{
|
||||||
|
_assemblyPath = assemblyPath;
|
||||||
|
_xmlDocPath = Path.ChangeExtension(assemblyPath, ".xml");
|
||||||
|
|
||||||
|
if (File.Exists(_xmlDocPath))
|
||||||
|
{
|
||||||
|
LoadXmlDocumentation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadXmlDocumentation()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var doc = XDocument.Load(_xmlDocPath);
|
||||||
|
var members = doc.Descendants("member");
|
||||||
|
|
||||||
|
foreach (var member in members)
|
||||||
|
{
|
||||||
|
var name = member.Attribute("name")?.Value;
|
||||||
|
var summary = member.Element("summary")?.Value?.Trim();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(summary))
|
||||||
|
{
|
||||||
|
_xmlDocumentation[name] = summary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error loading XML documentation: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssemblyInfo AnalyzeAssembly()
|
||||||
|
{
|
||||||
|
var assemblyInfo = new AssemblyInfo
|
||||||
|
{
|
||||||
|
Name = Path.GetFileNameWithoutExtension(_assemblyPath),
|
||||||
|
Types = new List<TypeInfo>()
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Use MetadataLoadContext to load assembly without dependencies
|
||||||
|
var resolver = new PathAssemblyResolver(new[] { _assemblyPath });
|
||||||
|
var mlc = new MetadataLoadContext(resolver);
|
||||||
|
|
||||||
|
var assembly = mlc.LoadFromAssemblyPath(_assemblyPath);
|
||||||
|
|
||||||
|
foreach (var type in assembly.GetExportedTypes())
|
||||||
|
{
|
||||||
|
if (type.IsSpecialName || type.Name.Contains("<>"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var typeInfo = AnalyzeType(type);
|
||||||
|
if (typeInfo != null)
|
||||||
|
{
|
||||||
|
assemblyInfo.Types.Add(typeInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error analyzing assembly: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return assemblyInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeInfo? AnalyzeType(Type type)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var typeInfo = new TypeInfo
|
||||||
|
{
|
||||||
|
Name = type.Name,
|
||||||
|
Namespace = type.Namespace ?? "",
|
||||||
|
FullName = type.FullName ?? "",
|
||||||
|
IsInterface = type.IsInterface,
|
||||||
|
IsAbstract = type.IsAbstract,
|
||||||
|
IsSealed = type.IsSealed,
|
||||||
|
IsEnum = type.IsEnum,
|
||||||
|
IsClass = type.IsClass,
|
||||||
|
BaseType = type.BaseType?.FullName,
|
||||||
|
Documentation = GetDocumentation($"T:{type.FullName}"),
|
||||||
|
Properties = new List<PropertyInfo>(),
|
||||||
|
Methods = new List<MethodInfo>(),
|
||||||
|
Events = new List<EventInfo>(),
|
||||||
|
Fields = new List<FieldInfo>(),
|
||||||
|
Interfaces = type.GetInterfaces().Select(i => i.FullName).Where(n => n != null).ToList()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Analyze properties
|
||||||
|
foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static))
|
||||||
|
{
|
||||||
|
typeInfo.Properties.Add(new PropertyInfo
|
||||||
|
{
|
||||||
|
Name = prop.Name,
|
||||||
|
Type = prop.PropertyType.FullName ?? "",
|
||||||
|
CanRead = prop.CanRead,
|
||||||
|
CanWrite = prop.CanWrite,
|
||||||
|
IsStatic = prop.GetMethod?.IsStatic ?? prop.SetMethod?.IsStatic ?? false,
|
||||||
|
Documentation = GetDocumentation($"P:{type.FullName}.{prop.Name}")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analyze methods
|
||||||
|
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
|
||||||
|
.Where(m => !m.IsSpecialName))
|
||||||
|
{
|
||||||
|
var parameters = method.GetParameters().Select(p => new ParameterInfo
|
||||||
|
{
|
||||||
|
Name = p.Name ?? "",
|
||||||
|
Type = p.ParameterType.FullName ?? "",
|
||||||
|
IsOut = p.IsOut,
|
||||||
|
IsRef = p.ParameterType.IsByRef && !p.IsOut,
|
||||||
|
HasDefaultValue = p.HasDefaultValue,
|
||||||
|
DefaultValue = p.HasDefaultValue ? p.DefaultValue?.ToString() : null
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
typeInfo.Methods.Add(new MethodInfo
|
||||||
|
{
|
||||||
|
Name = method.Name,
|
||||||
|
ReturnType = method.ReturnType.FullName ?? "",
|
||||||
|
IsStatic = method.IsStatic,
|
||||||
|
IsVirtual = method.IsVirtual,
|
||||||
|
IsAbstract = method.IsAbstract,
|
||||||
|
Parameters = parameters,
|
||||||
|
Documentation = GetDocumentation($"M:{type.FullName}.{method.Name}")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analyze events
|
||||||
|
foreach (var evt in type.GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static))
|
||||||
|
{
|
||||||
|
typeInfo.Events.Add(new EventInfo
|
||||||
|
{
|
||||||
|
Name = evt.Name,
|
||||||
|
EventHandlerType = evt.EventHandlerType?.FullName ?? "",
|
||||||
|
Documentation = GetDocumentation($"E:{type.FullName}.{evt.Name}")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analyze fields (for enums)
|
||||||
|
if (type.IsEnum)
|
||||||
|
{
|
||||||
|
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Static))
|
||||||
|
{
|
||||||
|
if (field.Name == "value__") continue;
|
||||||
|
|
||||||
|
typeInfo.Fields.Add(new FieldInfo
|
||||||
|
{
|
||||||
|
Name = field.Name,
|
||||||
|
Type = field.FieldType.FullName ?? "",
|
||||||
|
Value = field.GetRawConstantValue()?.ToString(),
|
||||||
|
Documentation = GetDocumentation($"F:{type.FullName}.{field.Name}")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeInfo;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error analyzing type {type.Name}: {ex.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetDocumentation(string memberName)
|
||||||
|
{
|
||||||
|
return _xmlDocumentation.TryGetValue(memberName, out var doc) ? doc : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AssemblyInfo
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
public List<TypeInfo> Types { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TypeInfo
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
public string Namespace { get; set; } = "";
|
||||||
|
public string FullName { get; set; } = "";
|
||||||
|
public bool IsInterface { get; set; }
|
||||||
|
public bool IsAbstract { get; set; }
|
||||||
|
public bool IsSealed { get; set; }
|
||||||
|
public bool IsEnum { get; set; }
|
||||||
|
public bool IsClass { get; set; }
|
||||||
|
public string? BaseType { get; set; }
|
||||||
|
public List<string> Interfaces { get; set; } = new();
|
||||||
|
public string Documentation { get; set; } = "";
|
||||||
|
public List<PropertyInfo> Properties { get; set; } = new();
|
||||||
|
public List<MethodInfo> Methods { get; set; } = new();
|
||||||
|
public List<EventInfo> Events { get; set; } = new();
|
||||||
|
public List<FieldInfo> Fields { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropertyInfo
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
public string Type { get; set; } = "";
|
||||||
|
public bool CanRead { get; set; }
|
||||||
|
public bool CanWrite { get; set; }
|
||||||
|
public bool IsStatic { get; set; }
|
||||||
|
public string Documentation { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MethodInfo
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
public string ReturnType { get; set; } = "";
|
||||||
|
public bool IsStatic { get; set; }
|
||||||
|
public bool IsVirtual { get; set; }
|
||||||
|
public bool IsAbstract { get; set; }
|
||||||
|
public List<ParameterInfo> Parameters { get; set; } = new();
|
||||||
|
public string Documentation { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ParameterInfo
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
public string Type { get; set; } = "";
|
||||||
|
public bool IsOut { get; set; }
|
||||||
|
public bool IsRef { get; set; }
|
||||||
|
public bool HasDefaultValue { get; set; }
|
||||||
|
public string? DefaultValue { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EventInfo
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
public string EventHandlerType { get; set; } = "";
|
||||||
|
public string Documentation { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FieldInfo
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
public string Type { get; set; } = "";
|
||||||
|
public string? Value { get; set; }
|
||||||
|
public string Documentation { get; set; } = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
15
tools/CrestronMockGenerator/CrestronMockGenerator.csproj
Normal file
15
tools/CrestronMockGenerator/CrestronMockGenerator.csproj
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
468
tools/CrestronMockGenerator/MockGenerator.cs
Normal file
468
tools/CrestronMockGenerator/MockGenerator.cs
Normal file
@@ -0,0 +1,468 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
|
||||||
|
|
||||||
|
namespace CrestronMockGenerator
|
||||||
|
{
|
||||||
|
public class MockGenerator
|
||||||
|
{
|
||||||
|
public string GenerateMockClass(TypeInfo typeInfo)
|
||||||
|
{
|
||||||
|
var compilationUnit = CompilationUnit();
|
||||||
|
|
||||||
|
// Add usings
|
||||||
|
compilationUnit = compilationUnit.AddUsings(
|
||||||
|
UsingDirective(ParseName("System")),
|
||||||
|
UsingDirective(ParseName("System.Collections.Generic")),
|
||||||
|
UsingDirective(ParseName("System.Linq"))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create namespace
|
||||||
|
var namespaceDeclaration = NamespaceDeclaration(ParseName(typeInfo.Namespace));
|
||||||
|
|
||||||
|
// Add XML documentation if available
|
||||||
|
var trivia = new List<SyntaxTrivia>();
|
||||||
|
if (!string.IsNullOrEmpty(typeInfo.Documentation))
|
||||||
|
{
|
||||||
|
trivia.Add(Comment($"/// <summary>"));
|
||||||
|
trivia.Add(Comment($"/// {typeInfo.Documentation}"));
|
||||||
|
trivia.Add(Comment($"/// </summary>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create type declaration
|
||||||
|
MemberDeclarationSyntax typeDeclaration;
|
||||||
|
|
||||||
|
if (typeInfo.IsEnum)
|
||||||
|
{
|
||||||
|
typeDeclaration = GenerateEnum(typeInfo);
|
||||||
|
}
|
||||||
|
else if (typeInfo.IsInterface)
|
||||||
|
{
|
||||||
|
typeDeclaration = GenerateInterface(typeInfo);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
typeDeclaration = GenerateClass(typeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trivia.Any())
|
||||||
|
{
|
||||||
|
typeDeclaration = typeDeclaration.WithLeadingTrivia(trivia);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespaceDeclaration = namespaceDeclaration.AddMembers(typeDeclaration);
|
||||||
|
compilationUnit = compilationUnit.AddMembers(namespaceDeclaration);
|
||||||
|
|
||||||
|
// Format the code
|
||||||
|
var workspace = new AdhocWorkspace();
|
||||||
|
var formattedNode = Microsoft.CodeAnalysis.Formatting.Formatter.Format(
|
||||||
|
compilationUnit,
|
||||||
|
workspace);
|
||||||
|
|
||||||
|
return formattedNode.ToFullString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private EnumDeclarationSyntax GenerateEnum(TypeInfo typeInfo)
|
||||||
|
{
|
||||||
|
var enumDeclaration = EnumDeclaration(typeInfo.Name)
|
||||||
|
.AddModifiers(Token(SyntaxKind.PublicKeyword));
|
||||||
|
|
||||||
|
foreach (var field in typeInfo.Fields)
|
||||||
|
{
|
||||||
|
var member = EnumMemberDeclaration(field.Name);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(field.Value))
|
||||||
|
{
|
||||||
|
member = member.WithEqualsValue(
|
||||||
|
EqualsValueClause(ParseExpression(field.Value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(field.Documentation))
|
||||||
|
{
|
||||||
|
member = member.WithLeadingTrivia(
|
||||||
|
Comment($"/// <summary>"),
|
||||||
|
Comment($"/// {field.Documentation}"),
|
||||||
|
Comment($"/// </summary>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
enumDeclaration = enumDeclaration.AddMembers(member);
|
||||||
|
}
|
||||||
|
|
||||||
|
return enumDeclaration;
|
||||||
|
}
|
||||||
|
|
||||||
|
private InterfaceDeclarationSyntax GenerateInterface(TypeInfo typeInfo)
|
||||||
|
{
|
||||||
|
var interfaceDeclaration = InterfaceDeclaration(typeInfo.Name)
|
||||||
|
.AddModifiers(Token(SyntaxKind.PublicKeyword));
|
||||||
|
|
||||||
|
// Add base interfaces
|
||||||
|
if (typeInfo.Interfaces.Any())
|
||||||
|
{
|
||||||
|
var baseList = BaseList();
|
||||||
|
foreach (var baseInterface in typeInfo.Interfaces)
|
||||||
|
{
|
||||||
|
var typeName = GetSimpleTypeName(baseInterface);
|
||||||
|
baseList = baseList.AddTypes(SimpleBaseType(ParseTypeName(typeName)));
|
||||||
|
}
|
||||||
|
interfaceDeclaration = interfaceDeclaration.WithBaseList(baseList);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add properties
|
||||||
|
foreach (var property in typeInfo.Properties)
|
||||||
|
{
|
||||||
|
var propertyDeclaration = GenerateInterfaceProperty(property);
|
||||||
|
interfaceDeclaration = interfaceDeclaration.AddMembers(propertyDeclaration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add methods
|
||||||
|
foreach (var method in typeInfo.Methods)
|
||||||
|
{
|
||||||
|
var methodDeclaration = GenerateInterfaceMethod(method);
|
||||||
|
interfaceDeclaration = interfaceDeclaration.AddMembers(methodDeclaration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add events
|
||||||
|
foreach (var evt in typeInfo.Events)
|
||||||
|
{
|
||||||
|
var eventDeclaration = GenerateInterfaceEvent(evt);
|
||||||
|
interfaceDeclaration = interfaceDeclaration.AddMembers(eventDeclaration);
|
||||||
|
}
|
||||||
|
|
||||||
|
return interfaceDeclaration;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClassDeclarationSyntax GenerateClass(TypeInfo typeInfo)
|
||||||
|
{
|
||||||
|
var classDeclaration = ClassDeclaration(typeInfo.Name)
|
||||||
|
.AddModifiers(Token(SyntaxKind.PublicKeyword));
|
||||||
|
|
||||||
|
if (typeInfo.IsAbstract)
|
||||||
|
{
|
||||||
|
classDeclaration = classDeclaration.AddModifiers(Token(SyntaxKind.AbstractKeyword));
|
||||||
|
}
|
||||||
|
else if (typeInfo.IsSealed)
|
||||||
|
{
|
||||||
|
classDeclaration = classDeclaration.AddModifiers(Token(SyntaxKind.SealedKeyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add base class and interfaces
|
||||||
|
var baseTypes = new List<string>();
|
||||||
|
if (!string.IsNullOrEmpty(typeInfo.BaseType) && typeInfo.BaseType != "System.Object")
|
||||||
|
{
|
||||||
|
baseTypes.Add(typeInfo.BaseType);
|
||||||
|
}
|
||||||
|
baseTypes.AddRange(typeInfo.Interfaces);
|
||||||
|
|
||||||
|
if (baseTypes.Any())
|
||||||
|
{
|
||||||
|
var baseList = BaseList();
|
||||||
|
foreach (var baseType in baseTypes)
|
||||||
|
{
|
||||||
|
var typeName = GetSimpleTypeName(baseType);
|
||||||
|
baseList = baseList.AddTypes(SimpleBaseType(ParseTypeName(typeName)));
|
||||||
|
}
|
||||||
|
classDeclaration = classDeclaration.WithBaseList(baseList);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add properties
|
||||||
|
foreach (var property in typeInfo.Properties)
|
||||||
|
{
|
||||||
|
var propertyDeclaration = GenerateProperty(property);
|
||||||
|
classDeclaration = classDeclaration.AddMembers(propertyDeclaration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add methods
|
||||||
|
foreach (var method in typeInfo.Methods)
|
||||||
|
{
|
||||||
|
var methodDeclaration = GenerateMethod(method, typeInfo.IsAbstract);
|
||||||
|
classDeclaration = classDeclaration.AddMembers(methodDeclaration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add events
|
||||||
|
foreach (var evt in typeInfo.Events)
|
||||||
|
{
|
||||||
|
var eventDeclaration = GenerateEvent(evt);
|
||||||
|
classDeclaration = classDeclaration.AddMembers(eventDeclaration);
|
||||||
|
}
|
||||||
|
|
||||||
|
return classDeclaration;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PropertyDeclarationSyntax GenerateProperty(PropertyInfo property)
|
||||||
|
{
|
||||||
|
var typeName = GetSimpleTypeName(property.Type);
|
||||||
|
var propertyDeclaration = PropertyDeclaration(ParseTypeName(typeName), property.Name)
|
||||||
|
.AddModifiers(Token(SyntaxKind.PublicKeyword));
|
||||||
|
|
||||||
|
if (property.IsStatic)
|
||||||
|
{
|
||||||
|
propertyDeclaration = propertyDeclaration.AddModifiers(Token(SyntaxKind.StaticKeyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessors = new List<AccessorDeclarationSyntax>();
|
||||||
|
|
||||||
|
if (property.CanRead)
|
||||||
|
{
|
||||||
|
accessors.Add(AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
|
||||||
|
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property.CanWrite)
|
||||||
|
{
|
||||||
|
accessors.Add(AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
|
||||||
|
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken)));
|
||||||
|
}
|
||||||
|
|
||||||
|
propertyDeclaration = propertyDeclaration.WithAccessorList(
|
||||||
|
AccessorList(List(accessors)));
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(property.Documentation))
|
||||||
|
{
|
||||||
|
propertyDeclaration = propertyDeclaration.WithLeadingTrivia(
|
||||||
|
Comment($"/// <summary>"),
|
||||||
|
Comment($"/// {property.Documentation}"),
|
||||||
|
Comment($"/// </summary>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return propertyDeclaration;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PropertyDeclarationSyntax GenerateInterfaceProperty(PropertyInfo property)
|
||||||
|
{
|
||||||
|
var typeName = GetSimpleTypeName(property.Type);
|
||||||
|
var propertyDeclaration = PropertyDeclaration(ParseTypeName(typeName), property.Name);
|
||||||
|
|
||||||
|
var accessors = new List<AccessorDeclarationSyntax>();
|
||||||
|
|
||||||
|
if (property.CanRead)
|
||||||
|
{
|
||||||
|
accessors.Add(AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
|
||||||
|
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property.CanWrite)
|
||||||
|
{
|
||||||
|
accessors.Add(AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
|
||||||
|
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken)));
|
||||||
|
}
|
||||||
|
|
||||||
|
propertyDeclaration = propertyDeclaration.WithAccessorList(
|
||||||
|
AccessorList(List(accessors)));
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(property.Documentation))
|
||||||
|
{
|
||||||
|
propertyDeclaration = propertyDeclaration.WithLeadingTrivia(
|
||||||
|
Comment($"/// <summary>"),
|
||||||
|
Comment($"/// {property.Documentation}"),
|
||||||
|
Comment($"/// </summary>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return propertyDeclaration;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodDeclarationSyntax GenerateMethod(MethodInfo method, bool isAbstractClass)
|
||||||
|
{
|
||||||
|
var returnTypeName = GetSimpleTypeName(method.ReturnType);
|
||||||
|
var methodDeclaration = MethodDeclaration(ParseTypeName(returnTypeName), method.Name)
|
||||||
|
.AddModifiers(Token(SyntaxKind.PublicKeyword));
|
||||||
|
|
||||||
|
if (method.IsStatic)
|
||||||
|
{
|
||||||
|
methodDeclaration = methodDeclaration.AddModifiers(Token(SyntaxKind.StaticKeyword));
|
||||||
|
}
|
||||||
|
else if (method.IsAbstract && isAbstractClass)
|
||||||
|
{
|
||||||
|
methodDeclaration = methodDeclaration.AddModifiers(Token(SyntaxKind.AbstractKeyword));
|
||||||
|
}
|
||||||
|
else if (method.IsVirtual)
|
||||||
|
{
|
||||||
|
methodDeclaration = methodDeclaration.AddModifiers(Token(SyntaxKind.VirtualKeyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add parameters
|
||||||
|
var parameters = new List<ParameterSyntax>();
|
||||||
|
foreach (var param in method.Parameters)
|
||||||
|
{
|
||||||
|
var paramTypeName = GetSimpleTypeName(param.Type);
|
||||||
|
var parameter = Parameter(Identifier(param.Name))
|
||||||
|
.WithType(ParseTypeName(paramTypeName));
|
||||||
|
|
||||||
|
if (param.IsOut)
|
||||||
|
{
|
||||||
|
parameter = parameter.AddModifiers(Token(SyntaxKind.OutKeyword));
|
||||||
|
}
|
||||||
|
else if (param.IsRef)
|
||||||
|
{
|
||||||
|
parameter = parameter.AddModifiers(Token(SyntaxKind.RefKeyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param.HasDefaultValue && !string.IsNullOrEmpty(param.DefaultValue))
|
||||||
|
{
|
||||||
|
parameter = parameter.WithDefault(
|
||||||
|
EqualsValueClause(ParseExpression(param.DefaultValue)));
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters.Add(parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
methodDeclaration = methodDeclaration.WithParameterList(
|
||||||
|
ParameterList(SeparatedList(parameters)));
|
||||||
|
|
||||||
|
// Add body or semicolon
|
||||||
|
if (method.IsAbstract && isAbstractClass)
|
||||||
|
{
|
||||||
|
methodDeclaration = methodDeclaration.WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var statements = new List<StatementSyntax>();
|
||||||
|
|
||||||
|
// Add default implementation
|
||||||
|
if (returnTypeName != "void")
|
||||||
|
{
|
||||||
|
statements.Add(ParseStatement($"throw new NotImplementedException();"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
statements.Add(ParseStatement($"// Mock implementation"));
|
||||||
|
}
|
||||||
|
|
||||||
|
methodDeclaration = methodDeclaration.WithBody(Block(statements));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(method.Documentation))
|
||||||
|
{
|
||||||
|
methodDeclaration = methodDeclaration.WithLeadingTrivia(
|
||||||
|
Comment($"/// <summary>"),
|
||||||
|
Comment($"/// {method.Documentation}"),
|
||||||
|
Comment($"/// </summary>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return methodDeclaration;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodDeclarationSyntax GenerateInterfaceMethod(MethodInfo method)
|
||||||
|
{
|
||||||
|
var returnTypeName = GetSimpleTypeName(method.ReturnType);
|
||||||
|
var methodDeclaration = MethodDeclaration(ParseTypeName(returnTypeName), method.Name);
|
||||||
|
|
||||||
|
// Add parameters
|
||||||
|
var parameters = new List<ParameterSyntax>();
|
||||||
|
foreach (var param in method.Parameters)
|
||||||
|
{
|
||||||
|
var paramTypeName = GetSimpleTypeName(param.Type);
|
||||||
|
var parameter = Parameter(Identifier(param.Name))
|
||||||
|
.WithType(ParseTypeName(paramTypeName));
|
||||||
|
|
||||||
|
if (param.IsOut)
|
||||||
|
{
|
||||||
|
parameter = parameter.AddModifiers(Token(SyntaxKind.OutKeyword));
|
||||||
|
}
|
||||||
|
else if (param.IsRef)
|
||||||
|
{
|
||||||
|
parameter = parameter.AddModifiers(Token(SyntaxKind.RefKeyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters.Add(parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
methodDeclaration = methodDeclaration
|
||||||
|
.WithParameterList(ParameterList(SeparatedList(parameters)))
|
||||||
|
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(method.Documentation))
|
||||||
|
{
|
||||||
|
methodDeclaration = methodDeclaration.WithLeadingTrivia(
|
||||||
|
Comment($"/// <summary>"),
|
||||||
|
Comment($"/// {method.Documentation}"),
|
||||||
|
Comment($"/// </summary>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return methodDeclaration;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventFieldDeclarationSyntax GenerateEvent(EventInfo evt)
|
||||||
|
{
|
||||||
|
var typeName = GetSimpleTypeName(evt.EventHandlerType);
|
||||||
|
var eventDeclaration = EventFieldDeclaration(
|
||||||
|
VariableDeclaration(ParseTypeName(typeName))
|
||||||
|
.AddVariables(VariableDeclarator(evt.Name)))
|
||||||
|
.AddModifiers(Token(SyntaxKind.PublicKeyword));
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(evt.Documentation))
|
||||||
|
{
|
||||||
|
eventDeclaration = eventDeclaration.WithLeadingTrivia(
|
||||||
|
Comment($"/// <summary>"),
|
||||||
|
Comment($"/// {evt.Documentation}"),
|
||||||
|
Comment($"/// </summary>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return eventDeclaration;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventFieldDeclarationSyntax GenerateInterfaceEvent(EventInfo evt)
|
||||||
|
{
|
||||||
|
var typeName = GetSimpleTypeName(evt.EventHandlerType);
|
||||||
|
var eventDeclaration = EventFieldDeclaration(
|
||||||
|
VariableDeclaration(ParseTypeName(typeName))
|
||||||
|
.AddVariables(VariableDeclarator(evt.Name)));
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(evt.Documentation))
|
||||||
|
{
|
||||||
|
eventDeclaration = eventDeclaration.WithLeadingTrivia(
|
||||||
|
Comment($"/// <summary>"),
|
||||||
|
Comment($"/// {evt.Documentation}"),
|
||||||
|
Comment($"/// </summary>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return eventDeclaration;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetSimpleTypeName(string fullTypeName)
|
||||||
|
{
|
||||||
|
// Convert full type names to simple names
|
||||||
|
var typeMappings = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["System.Void"] = "void",
|
||||||
|
["System.String"] = "string",
|
||||||
|
["System.Int32"] = "int",
|
||||||
|
["System.Int64"] = "long",
|
||||||
|
["System.Int16"] = "short",
|
||||||
|
["System.UInt32"] = "uint",
|
||||||
|
["System.UInt64"] = "ulong",
|
||||||
|
["System.UInt16"] = "ushort",
|
||||||
|
["System.Byte"] = "byte",
|
||||||
|
["System.SByte"] = "sbyte",
|
||||||
|
["System.Boolean"] = "bool",
|
||||||
|
["System.Single"] = "float",
|
||||||
|
["System.Double"] = "double",
|
||||||
|
["System.Decimal"] = "decimal",
|
||||||
|
["System.Object"] = "object",
|
||||||
|
["System.Char"] = "char"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeMappings.TryGetValue(fullTypeName, out var simpleName))
|
||||||
|
{
|
||||||
|
return simpleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle generic types
|
||||||
|
if (fullTypeName.Contains('`'))
|
||||||
|
{
|
||||||
|
// Simplified generic handling
|
||||||
|
return "object";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return last part of the type name
|
||||||
|
var lastDot = fullTypeName.LastIndexOf('.');
|
||||||
|
return lastDot >= 0 ? fullTypeName.Substring(lastDot + 1) : fullTypeName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
117
tools/CrestronMockGenerator/Program.cs
Normal file
117
tools/CrestronMockGenerator/Program.cs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace CrestronMockGenerator
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Crestron Mock Library Generator");
|
||||||
|
Console.WriteLine("================================");
|
||||||
|
|
||||||
|
if (args.Length < 2)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Usage: CrestronMockGenerator <input-assembly-path> <output-directory>");
|
||||||
|
Console.WriteLine("Example: CrestronMockGenerator /path/to/SimplSharp.dll /output/mocks");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var inputPath = args[0];
|
||||||
|
var outputDir = args[1];
|
||||||
|
|
||||||
|
if (!File.Exists(inputPath))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error: Assembly file not found: {inputPath}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory.CreateDirectory(outputDir);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Analyze the assembly
|
||||||
|
Console.WriteLine($"Analyzing assembly: {inputPath}");
|
||||||
|
var analyzer = new AssemblyAnalyzer(inputPath);
|
||||||
|
var assemblyInfo = analyzer.AnalyzeAssembly();
|
||||||
|
|
||||||
|
Console.WriteLine($"Found {assemblyInfo.Types.Count} types");
|
||||||
|
|
||||||
|
// Save assembly metadata as JSON
|
||||||
|
var metadataPath = Path.Combine(outputDir, $"{assemblyInfo.Name}.metadata.json");
|
||||||
|
var json = JsonConvert.SerializeObject(assemblyInfo, Formatting.Indented);
|
||||||
|
File.WriteAllText(metadataPath, json);
|
||||||
|
Console.WriteLine($"Saved metadata to: {metadataPath}");
|
||||||
|
|
||||||
|
// Generate mock classes
|
||||||
|
var generator = new MockGenerator();
|
||||||
|
var generatedFiles = new List<string>();
|
||||||
|
|
||||||
|
// Group types by namespace
|
||||||
|
var namespaceGroups = assemblyInfo.Types.GroupBy(t => t.Namespace);
|
||||||
|
|
||||||
|
foreach (var namespaceGroup in namespaceGroups)
|
||||||
|
{
|
||||||
|
var namespacePath = Path.Combine(outputDir, namespaceGroup.Key.Replace('.', Path.DirectorySeparatorChar));
|
||||||
|
Directory.CreateDirectory(namespacePath);
|
||||||
|
|
||||||
|
foreach (var typeInfo in namespaceGroup)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Generating mock for: {typeInfo.FullName}");
|
||||||
|
var mockCode = generator.GenerateMockClass(typeInfo);
|
||||||
|
|
||||||
|
var fileName = $"{typeInfo.Name}.cs";
|
||||||
|
var filePath = Path.Combine(namespacePath, fileName);
|
||||||
|
|
||||||
|
File.WriteAllText(filePath, mockCode);
|
||||||
|
generatedFiles.Add(filePath);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error generating mock for {typeInfo.Name}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate project file
|
||||||
|
GenerateProjectFile(outputDir, assemblyInfo.Name);
|
||||||
|
|
||||||
|
Console.WriteLine($"\nGeneration complete!");
|
||||||
|
Console.WriteLine($"Generated {generatedFiles.Count} mock files");
|
||||||
|
Console.WriteLine($"Output directory: {outputDir}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error: {ex.Message}");
|
||||||
|
Console.WriteLine(ex.StackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GenerateProjectFile(string outputDir, string assemblyName)
|
||||||
|
{
|
||||||
|
var projectContent = @"<Project Sdk=""Microsoft.NET.Sdk"">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<AssemblyName>" + assemblyName + @".Mock</AssemblyName>
|
||||||
|
<RootNamespace>" + assemblyName + @"</RootNamespace>
|
||||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
|
<PackageId>" + assemblyName + @".Mock</PackageId>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
<Description>Mock implementation of " + assemblyName + @" for testing</Description>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>";
|
||||||
|
|
||||||
|
var projectPath = Path.Combine(outputDir, $"{assemblyName}.Mock.csproj");
|
||||||
|
File.WriteAllText(projectPath, projectContent);
|
||||||
|
Console.WriteLine($"Generated project file: {projectPath}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user