From b59c9724a1059bdba1229c57793881bb7c220c4a Mon Sep 17 00:00:00 2001 From: helto4real Date: Sat, 26 Dec 2020 00:05:53 +0100 Subject: [PATCH 01/10] First series of changes --- .../NetDaemon.App/Common/AppRuntimeInfo.cs | 4 - src/App/NetDaemon.App/Common/Attributes.cs | 28 ++--- .../Configuration/HomeAssistantSettings.cs | 2 +- .../Common/Configuration/NetDaemonSettings.cs | 2 +- src/App/NetDaemon.App/Common/DelayResult.cs | 3 + .../NetDaemon.App/Common/ExtensionMethods.cs | 2 +- .../NetDaemon.App/Common/ExternalEvents.cs | 8 +- .../NetDaemon.App/Common/Fluent/Context.cs | 5 +- .../NetDaemon.App/Common/Fluent/EntityBase.cs | 4 +- .../Common/Fluent/EntityManager.cs | 49 ++++++--- .../Common/Fluent/FluentEvent.cs | 13 ++- .../Common/Fluent/FluentExpandoObject.cs | 16 +-- .../Common/Fluent/FluentInputSelect.cs | 4 +- .../Common/Fluent/FluentMediaPlayer.cs | 6 +- src/App/NetDaemon.App/Common/INetDaemon.cs | 17 +-- src/App/NetDaemon.App/Common/IScheduler.cs | 2 +- src/App/NetDaemon.App/Common/Messages.cs | 3 +- src/App/NetDaemon.App/Common/NetDaemonApp.cs | 42 ++++---- .../NetDaemon.App/Common/NetDaemonAppBase.cs | 61 ++++------- .../Common/Reactive/AppDaemonRxApp.cs | 28 +++-- .../Common/Reactive/DisposableTimerResult.cs | 1 - .../Common/Reactive/ObservableBase.cs | 2 +- .../Reactive/ObservableExtensionMethods.cs | 1 - .../NetDaemon.App/Common/Reactive/RxEntity.cs | 7 +- .../NetDaemon.App/Common/Reactive/RxEvent.cs | 10 +- .../NetDaemon.Daemon/Daemon/CodeManager.cs | 4 +- .../Daemon/Config/ConfigExtensions.cs | 3 +- .../Daemon/Config/YamlAppConfig.cs | 26 ++--- .../Daemon/Config/YamlExtensions.cs | 9 +- .../Daemon/DaemonAppExtensions.cs | 17 +-- .../NetDaemon.Daemon/Daemon/HttpHandler.cs | 18 ++-- .../NetDaemon.Daemon/Daemon/INetDaemonHost.cs | 1 - .../NetDaemon.Daemon/Daemon/NetDaemonHost.cs | 102 ++++++++---------- .../DaemonRunner/Service/RunnerService.cs | 14 ++- .../NetDaemon.DevelopmentApps.csproj | 1 + .../NetDaemon.Fakes/DaemonHostTestBase.cs | 76 +++++++------ .../Daemon/NetDaemonHostTests.cs | 10 +- .../NetDaemon.Daemon.Tests/TimeManagerMock.cs | 2 +- 38 files changed, 273 insertions(+), 330 deletions(-) diff --git a/src/App/NetDaemon.App/Common/AppRuntimeInfo.cs b/src/App/NetDaemon.App/Common/AppRuntimeInfo.cs index d6fb11f5d..a558f0645 100644 --- a/src/App/NetDaemon.App/Common/AppRuntimeInfo.cs +++ b/src/App/NetDaemon.App/Common/AppRuntimeInfo.cs @@ -34,11 +34,7 @@ public sealed class AppRuntimeInfo /// [JsonPropertyName("app_attributes")] public Dictionary AppAttributes { get; set; } = new(); - - } - - } diff --git a/src/App/NetDaemon.App/Common/Attributes.cs b/src/App/NetDaemon.App/Common/Attributes.cs index 1fa7f4f4e..ba33e68e0 100644 --- a/src/App/NetDaemon.App/Common/Attributes.cs +++ b/src/App/NetDaemon.App/Common/Attributes.cs @@ -21,7 +21,7 @@ public sealed class DisableLogAttribute : System.Attribute { // See the attribute guidelines at // http://go.microsoft.com/fwlink/?LinkId=85236 - private SupressLogType[] _logTypesToSupress; + private readonly SupressLogType[] _logTypesToSupress; /// /// Default constructor @@ -50,16 +50,6 @@ public sealed class HomeAssistantServiceCallAttribute : System.Attribute { } [System.AttributeUsage(System.AttributeTargets.Method, Inherited = false, AllowMultiple = true)] public sealed class HomeAssistantStateChangedAttribute : System.Attribute { - private readonly bool _allChanges; - - private readonly object? _from; - - private readonly object? _to; - - // See the attribute guidelines at - // http://go.microsoft.com/fwlink/?LinkId=85236 - private string _entityId; - /// /// Default constructor /// @@ -69,30 +59,30 @@ public sealed class HomeAssistantStateChangedAttribute : System.Attribute /// Get all changes, ie also attribute changes public HomeAssistantStateChangedAttribute(string entityId, object? to = null, object? from = null, bool allChanges = false) { - _entityId = entityId; - _to = to; - _from = from; - _allChanges = allChanges; + EntityId = entityId; + To = to; + From = from; + AllChanges = allChanges; } /// /// Get all changes, even if only attribute changes /// - public bool AllChanges => _allChanges; + public bool AllChanges { get; } /// /// Unique id of the entity /// - public string EntityId => _entityId; + public string EntityId { get; } /// /// From state filter /// - public object? From => _from; + public object? From { get; } /// /// To state filter /// - public object? To => _to; + public object? To { get; } } } \ No newline at end of file diff --git a/src/App/NetDaemon.App/Common/Configuration/HomeAssistantSettings.cs b/src/App/NetDaemon.App/Common/Configuration/HomeAssistantSettings.cs index 918ee304d..77d292629 100644 --- a/src/App/NetDaemon.App/Common/Configuration/HomeAssistantSettings.cs +++ b/src/App/NetDaemon.App/Common/Configuration/HomeAssistantSettings.cs @@ -18,7 +18,7 @@ public class HomeAssistantSettings /// public bool Ssl { get; set; } = false; /// - /// Token to authorize + /// Token to authorize /// public string Token { get; set; } = string.Empty; } diff --git a/src/App/NetDaemon.App/Common/Configuration/NetDaemonSettings.cs b/src/App/NetDaemon.App/Common/Configuration/NetDaemonSettings.cs index aa558a8b4..a48847469 100644 --- a/src/App/NetDaemon.App/Common/Configuration/NetDaemonSettings.cs +++ b/src/App/NetDaemon.App/Common/Configuration/NetDaemonSettings.cs @@ -30,7 +30,7 @@ public class NetDaemonSettings public string? AppSource { get; set; } = null; /// - /// Returns the directory path of AppSource + /// Returns the directory path of AppSource /// public string GetAppSourceDirectory() { diff --git a/src/App/NetDaemon.App/Common/DelayResult.cs b/src/App/NetDaemon.App/Common/DelayResult.cs index fa92f28c6..19f822854 100644 --- a/src/App/NetDaemon.App/Common/DelayResult.cs +++ b/src/App/NetDaemon.App/Common/DelayResult.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Concurrent; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -60,6 +61,8 @@ public void Dispose() { // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(true); + // Suppress finalization. + GC.SuppressFinalize(this); } /// diff --git a/src/App/NetDaemon.App/Common/ExtensionMethods.cs b/src/App/NetDaemon.App/Common/ExtensionMethods.cs index 626bc432a..1c6529e94 100644 --- a/src/App/NetDaemon.App/Common/ExtensionMethods.cs +++ b/src/App/NetDaemon.App/Common/ExtensionMethods.cs @@ -56,7 +56,7 @@ public static dynamic ToDynamic(this (string name, object val)[] attributeNameVa public static string ToSafeHomeAssistantEntityId(this string str) { string normalizedString = str.Normalize(NormalizationForm.FormD); - StringBuilder stringBuilder = new StringBuilder(str.Length); + StringBuilder stringBuilder = new(str.Length); foreach (char c in normalizedString) { diff --git a/src/App/NetDaemon.App/Common/ExternalEvents.cs b/src/App/NetDaemon.App/Common/ExternalEvents.cs index b7b34819c..73efea5d5 100644 --- a/src/App/NetDaemon.App/Common/ExternalEvents.cs +++ b/src/App/NetDaemon.App/Common/ExternalEvents.cs @@ -5,13 +5,11 @@ namespace NetDaemon.Common { - /// - /// Base class for all external events + /// Base class for all external events /// public class ExternalEventBase { - } /// @@ -19,10 +17,8 @@ public class ExternalEventBase /// public class AppsInformationEvent : ExternalEventBase { - } - /// /// Information about the application /// @@ -56,7 +52,6 @@ public class ApplicationInfo /// Last known error message /// public string? LastErrorMessage { get; set; } - } /// @@ -72,6 +67,5 @@ public class ConfigInfo /// Settings Home Assistant related /// public HomeAssistantSettings? HomeAssistantSettings { get; set; } - } } \ No newline at end of file diff --git a/src/App/NetDaemon.App/Common/Fluent/Context.cs b/src/App/NetDaemon.App/Common/Fluent/Context.cs index df0d27a99..2d770ded7 100644 --- a/src/App/NetDaemon.App/Common/Fluent/Context.cs +++ b/src/App/NetDaemon.App/Common/Fluent/Context.cs @@ -1,7 +1,7 @@ namespace NetDaemon.Common.Fluent { /// - /// + /// Context /// public class Context { @@ -10,13 +10,12 @@ public class Context /// public string Id { get; set; } = ""; /// - /// + /// ParentId /// public string? ParentId { get; set; } /// /// The id of the user who is responsible for the connected item. /// public string? UserId { get; set; } - } } \ No newline at end of file diff --git a/src/App/NetDaemon.App/Common/Fluent/EntityBase.cs b/src/App/NetDaemon.App/Common/Fluent/EntityBase.cs index 784e2183b..9e7926dad 100644 --- a/src/App/NetDaemon.App/Common/Fluent/EntityBase.cs +++ b/src/App/NetDaemon.App/Common/Fluent/EntityBase.cs @@ -11,11 +11,11 @@ namespace NetDaemon.Common.Fluent public class EntityBase //: EntityState { internal readonly ConcurrentQueue _actions = - new ConcurrentQueue(); + new(); internal FluentAction? _currentAction; - internal StateChangedInfo _currentState = new StateChangedInfo(); + internal StateChangedInfo _currentState = new(); /// /// The daemon used in the API diff --git a/src/App/NetDaemon.App/Common/Fluent/EntityManager.cs b/src/App/NetDaemon.App/Common/Fluent/EntityManager.cs index 71eefacf0..07ea11dd4 100644 --- a/src/App/NetDaemon.App/Common/Fluent/EntityManager.cs +++ b/src/App/NetDaemon.App/Common/Fluent/EntityManager.cs @@ -52,6 +52,7 @@ IDelayResult IDelayStateChange.DelayUntilStateChange(Func { try @@ -74,13 +75,13 @@ public void Execute() } else { - if (_currentState.To != null) - if (_currentState.To != newState?.State) - return; + if (_currentState.To != null && _currentState.To != newState?.State) + { + return; + } - if (_currentState.From != null) - if (_currentState.From != oldState?.State) - return; + if (_currentState.From != null && _currentState.From != oldState?.State) + return; // If we don´t accept all changes in the state change // and we do not have a state change so return @@ -104,7 +105,9 @@ public void Execute() "State same {newState} during period of {time}, executing action!", $"{newState?.State}", _currentState.ForTimeSpan); // The state has not changed during the time we waited if (_currentState.FuncToCall == null) + { await entityManager.ExecuteAsync(true).ConfigureAwait(false); + } else { try @@ -121,11 +124,10 @@ public void Execute() } else { - App.LogDebug( - "State same {newState} but different state changed: {currentLastChanged}, expected {newLastChanged}", - $"{newState?.State}", - currentState?.LastChanged!, - newState?.LastChanged!); + App.LogDebug("State same {newState} but different state changed: {currentLastChanged}, expected {newLastChanged}", + $"{newState?.State}", + currentState?.LastChanged ?? DateTime.MinValue, + newState?.LastChanged ?? DateTime.MinValue); } } else @@ -152,15 +154,23 @@ public void Execute() } catch (Exception e) { - App.LogError(e, - "Call function error in App {appId}, EntityId: {entityId}, From: {newState} To: {oldState}", - App.Id!, entityIdInn, $"{newState?.State}", $"{oldState?.State}"); + App.LogError( + e, + "Call function error in App {appId}, EntityId: {entityId}, From: {newState} To: {oldState}", + App.Id!, + entityIdInn, + $"{newState?.State}", + $"{oldState?.State}"); } } else if (_currentState.ScriptToCall != null) + { await Daemon.RunScript(App, _currentState.ScriptToCall).ExecuteAsync().ConfigureAwait(false); + } else + { await entityManager.ExecuteAsync(true).ConfigureAwait(false); + } } } catch (OperationCanceledException) @@ -172,6 +182,7 @@ public void Execute() App.LogWarning(e, "Unhandled error in ListenState in App {appId}", App.Id!); } }); + } //} } @@ -213,11 +224,15 @@ async Task IScript.ExecuteAsync() public async Task ExecuteAsync(bool keepItems) { if (keepItems) + { foreach (var action in _actions) await HandleAction(action).ConfigureAwait(false); + } else + { while (_actions.TryDequeue(out var fluentAction)) await HandleAction(fluentAction).ConfigureAwait(false); + } } /// @@ -230,8 +245,10 @@ public IExecute RunScript(params string[] entityIds) /// IAction ISetState.SetState(dynamic state) { - _currentAction = new FluentAction(FluentActionType.SetState); - _currentAction.State = state; + _currentAction = new FluentAction(FluentActionType.SetState) + { + State = state + }; _actions.Enqueue(_currentAction); return this; } diff --git a/src/App/NetDaemon.App/Common/Fluent/FluentEvent.cs b/src/App/NetDaemon.App/Common/Fluent/FluentEvent.cs index 78072a2cc..5b69d341c 100644 --- a/src/App/NetDaemon.App/Common/Fluent/FluentEvent.cs +++ b/src/App/NetDaemon.App/Common/Fluent/FluentEvent.cs @@ -21,10 +21,10 @@ public interface IFluentEvent /// public class FluentEventManager : IFluentEvent, IExecute { - private IEnumerable? _events; - private INetDaemonApp _daemon; + private readonly IEnumerable? _events; + private readonly INetDaemonApp _daemon; private Func? _functionToCall; - private Func? _funcSelector; + private readonly Func? _funcSelector; /// /// Constructor @@ -65,10 +65,14 @@ public void Execute() throw new NullReferenceException($"Both {nameof(_events)} or {nameof(_events)} cant be null"); if (_events != null) + { foreach (var ev in _events) _daemon.ListenEvent(ev, _functionToCall!); + } else + { _daemon.ListenEvent(_funcSelector!, _functionToCall!); + } } } @@ -83,9 +87,8 @@ public class FluentEventProperty public string EventId { get; set; } = ""; /// - /// + /// Data of the event /// - /// public dynamic? Data { get; set; } = null; } } \ No newline at end of file diff --git a/src/App/NetDaemon.App/Common/Fluent/FluentExpandoObject.cs b/src/App/NetDaemon.App/Common/Fluent/FluentExpandoObject.cs index a5a79d148..2a8d6b08b 100644 --- a/src/App/NetDaemon.App/Common/Fluent/FluentExpandoObject.cs +++ b/src/App/NetDaemon.App/Common/Fluent/FluentExpandoObject.cs @@ -72,7 +72,6 @@ public bool Contains(KeyValuePair item) /// public void CopyTo(KeyValuePair[] array, int arrayIndex) { - throw new NotImplementedException(); } /// @@ -130,11 +129,9 @@ public object this[string key] public override bool TrySetMember(SetMemberBinder binder, object? value) { UpdateDictionary(binder.Name, value); - if (_daemonApp != null) - { - // It is supposed to persist, this is the only reason _daemon is present - _daemonApp.SaveAppState(); - } + // It is supposed to persist, this is the only reason _daemon is present + _daemonApp?.SaveAppState(); + return true; } @@ -250,12 +247,9 @@ public override string ToString() private void UpdateDictionary(string name, object? value) { - _ = value ?? throw new ArgumentNullException("value", "value cannot be null"); + _ = value ?? throw new ArgumentNullException(nameof(value), "value cannot be null"); var key = NormalizePropertyName(name); - if (_dict.ContainsKey(key)) - _dict[key] = value; - else - _dict.Add(key, value); + _dict[key] = value; } private string NormalizePropertyName(string propertyName) diff --git a/src/App/NetDaemon.App/Common/Fluent/FluentInputSelect.cs b/src/App/NetDaemon.App/Common/Fluent/FluentInputSelect.cs index a9cb745ce..7c9f5b3e8 100644 --- a/src/App/NetDaemon.App/Common/Fluent/FluentInputSelect.cs +++ b/src/App/NetDaemon.App/Common/Fluent/FluentInputSelect.cs @@ -41,8 +41,8 @@ public interface IFluentSetOption public class InputSelectManager : IFluentInputSelect, IFluentExecuteAsync { private readonly IEnumerable _entityIds; - private INetDaemon _daemon; - private INetDaemonApp _app; + private readonly INetDaemon _daemon; + private readonly INetDaemonApp _app; private string? _option; /// diff --git a/src/App/NetDaemon.App/Common/Fluent/FluentMediaPlayer.cs b/src/App/NetDaemon.App/Common/Fluent/FluentMediaPlayer.cs index 9bcb1251b..1ad93283d 100644 --- a/src/App/NetDaemon.App/Common/Fluent/FluentMediaPlayer.cs +++ b/src/App/NetDaemon.App/Common/Fluent/FluentMediaPlayer.cs @@ -63,8 +63,10 @@ public IMediaPlayerExecuteAsync PlayPause() /// public IMediaPlayerExecuteAsync Speak(string message) { - _currentAction = new FluentAction(FluentActionType.Speak); - _currentAction.MessageToSpeak = message; + _currentAction = new FluentAction(FluentActionType.Speak) + { + MessageToSpeak = message + }; return this; } diff --git a/src/App/NetDaemon.App/Common/INetDaemon.cs b/src/App/NetDaemon.App/Common/INetDaemon.cs index 34e7550cb..d4a1d1375 100644 --- a/src/App/NetDaemon.App/Common/INetDaemon.cs +++ b/src/App/NetDaemon.App/Common/INetDaemon.cs @@ -103,8 +103,8 @@ public interface INetDaemon : INetDaemonCommon /// /// Selects the input selects to do actions on using lambda /// - /// The lambda expression selecting input select /// The Daemon App calling fluent API + /// The lambda expression selecting input select IFluentInputSelect InputSelects(INetDaemonApp app, Func func); /// @@ -361,7 +361,7 @@ public interface INetDaemonAppBase : /// /// Gets or sets a flag indicating whether this app is enabled. - /// This property property can be controlled from Home Assistant. + /// This property can be controlled from Home Assistant. /// /// /// A disabled app will not be initialized during the discovery. @@ -405,7 +405,6 @@ public interface INetDaemonAppBase : void ListenServiceCall(string domain, string service, Func action); - /// /// Returns different runtime information about an app /// @@ -602,7 +601,7 @@ public interface INetDaemonCommon /// /// Unique Id of the data /// The data persistent or null if not exists - ValueTask GetDataAsync(string id) where T : class; + Task GetDataAsync(string id) where T : class; /// /// Gets current state for the entity @@ -661,10 +660,11 @@ public interface INetDaemonInitialableApp /// /// /// + /// /// Restores the state of the storage object.!-- /// Todo: in the future also the state of tagged properties - /// - /// It is implemented async so state will be lazy saved + /// + /// It is implemented async so state will be lazy saved /// Task RestoreAppStateAsync(); @@ -672,10 +672,11 @@ public interface INetDaemonInitialableApp /// Saves the app state /// /// + /// /// Saves the state of the storage object.!-- /// Todo: in the future also the state of tagged properties - /// - /// It is implemented async so state will be lazy saved + /// + /// It is implemented async so state will be lazy saved /// void SaveAppState(); diff --git a/src/App/NetDaemon.App/Common/IScheduler.cs b/src/App/NetDaemon.App/Common/IScheduler.cs index 6ecdb4b83..3b1160b1b 100644 --- a/src/App/NetDaemon.App/Common/IScheduler.cs +++ b/src/App/NetDaemon.App/Common/IScheduler.cs @@ -22,8 +22,8 @@ public interface IScheduler : IAsyncDisposable /// Run daily tasks /// /// The time in the format HH:mm:ss - /// The action to run /// A list of days the scheduler will run on + /// The action to run /// ISchedulerResult RunDaily(string time, IEnumerable? runOnDays, Func func); diff --git a/src/App/NetDaemon.App/Common/Messages.cs b/src/App/NetDaemon.App/Common/Messages.cs index c9c9ad0e1..3e032d607 100644 --- a/src/App/NetDaemon.App/Common/Messages.cs +++ b/src/App/NetDaemon.App/Common/Messages.cs @@ -115,7 +115,7 @@ public override string ToString() { if (key is not null) { - attributes = attributes + string.Format(" {0}:{1}", key, attr[key]); + attributes += string.Format(" {0}:{1}", key, attr[key]); } } } @@ -131,7 +131,6 @@ public override string ToString() } } - /// /// Unit system parameters for Home Assistant /// diff --git a/src/App/NetDaemon.App/Common/NetDaemonApp.cs b/src/App/NetDaemon.App/Common/NetDaemonApp.cs index d1c6fcd9e..586e760e4 100644 --- a/src/App/NetDaemon.App/Common/NetDaemonApp.cs +++ b/src/App/NetDaemon.App/Common/NetDaemonApp.cs @@ -15,23 +15,16 @@ namespace NetDaemon.Common /// public abstract class NetDaemonApp : NetDaemonAppBase, INetDaemonApp, INetDaemonCommon { - private readonly IList<(string pattern, Func action)> _eventCallbacks = - new List<(string pattern, Func action)>(); - - private readonly List<(Func, Func)> _eventFunctionSelectorCallbacks = - new List<(Func, Func)>(); - private readonly ConcurrentDictionary action)> _stateCallbacks = - new ConcurrentDictionary action)>(); - /// /// All actions being performed for named events /// - public IList<(string pattern, Func action)> EventCallbacks => _eventCallbacks; + public IList<(string pattern, Func action)> EventCallbacks { get; } = + new List<(string pattern, Func action)>(); /// /// All actions being performed for lambda selected events /// - public List<(Func, Func)> EventFunctionCallbacks => _eventFunctionSelectorCallbacks; + public List<(Func, Func)> EventFunctionCallbacks { get; } = new(); /// public IScheduler Scheduler => _daemon?.Scheduler ?? @@ -42,10 +35,11 @@ public abstract class NetDaemonApp : NetDaemonAppBase, INetDaemonApp, INetDaemon /// public ConcurrentDictionary action)> - StateCallbacks => _stateCallbacks; + StateCallbacks + { get; } = new(); // Used for testing - internal ConcurrentDictionary action)> InternalStateActions => _stateCallbacks; + internal ConcurrentDictionary action)> InternalStateActions => StateCallbacks; /// public Task CallService(string domain, string service, dynamic? data = null, bool waitForResponse = false) @@ -79,7 +73,7 @@ public ICamera Cameras(Func func) public void CancelListenState(string id) { // Remove and ignore if not exist - _stateCallbacks.Remove(id, out _); + StateCallbacks.Remove(id, out _); } /// @@ -95,15 +89,19 @@ public IDelayResult DelayUntilStateChange(IEnumerable entityIds, object? foreach (var entityId in entityIds) { - result.StateSubscriptions.Add(ListenState(entityId, (entityIdInn, newState, oldState) => + result.StateSubscriptions.Add(ListenState(entityId, (_, newState, oldState) => { if (to != null) + { if ((dynamic)to != newState?.State) return Task.CompletedTask; + } if (from != null) + { if ((dynamic)from != oldState?.State) return Task.CompletedTask; + } // If we don´t accept all changes in the state change // and we do not have a state change so return @@ -131,7 +129,7 @@ public IDelayResult DelayUntilStateChange(IEnumerable entityIds, Func + result.StateSubscriptions.Add(ListenState(entityId, (_, newState, oldState) => { try { @@ -161,9 +159,9 @@ public IDelayResult DelayUntilStateChange(IEnumerable entityIds, Func public async override ValueTask DisposeAsync() { - _stateCallbacks.Clear(); - _eventCallbacks.Clear(); - _eventFunctionSelectorCallbacks.Clear(); + StateCallbacks.Clear(); + EventCallbacks.Clear(); + EventFunctionCallbacks.Clear(); await base.DisposeAsync().ConfigureAwait(false); } @@ -198,7 +196,7 @@ public IEntity Entity(params string[] entityId) public IFluentEvent Events(IEnumerable eventParams) => new FluentEventManager(eventParams, this); /// - public async ValueTask GetDataAsync(string id) where T : class + public async Task GetDataAsync(string id) where T : class { _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); return await _daemon!.GetDataAsync(id).ConfigureAwait(false); @@ -229,10 +227,10 @@ public IFluentInputSelect InputSelects(Func func) } /// - public void ListenEvent(string ev, Func action) => _eventCallbacks.Add((ev, action)); + public void ListenEvent(string ev, Func action) => EventCallbacks.Add((ev, action)); /// - public void ListenEvent(Func funcSelector, Func func) => _eventFunctionSelectorCallbacks.Add((funcSelector, func)); + public void ListenEvent(Func funcSelector, Func func) => EventFunctionCallbacks.Add((funcSelector, func)); /// public string? ListenState(string pattern, @@ -241,7 +239,7 @@ public IFluentInputSelect InputSelects(Func func) // Use guid as unique id but will externally use string so // The design can change in-case guid won't cut it. var uniqueId = Guid.NewGuid().ToString(); - _stateCallbacks[uniqueId] = (pattern, action); + StateCallbacks[uniqueId] = (pattern, action); return uniqueId; } diff --git a/src/App/NetDaemon.App/Common/NetDaemonAppBase.cs b/src/App/NetDaemon.App/Common/NetDaemonAppBase.cs index c546776ca..870d5eea5 100644 --- a/src/App/NetDaemon.App/Common/NetDaemonAppBase.cs +++ b/src/App/NetDaemon.App/Common/NetDaemonAppBase.cs @@ -27,26 +27,13 @@ public abstract class NetDaemonAppBase : INetDaemonAppBase protected INetDaemon? _daemon; private Task? _manageRuntimeInformationUpdatesTask; - /// - /// Registered callbacks for service calls - /// - private readonly List<(string, string, Func)> _daemonCallBacksForServiceCalls - = new List<(string, string, Func)>(); - /// /// All actions being performed for service call events /// - public List<(string, string, Func)> DaemonCallBacksForServiceCalls => _daemonCallBacksForServiceCalls; - + public List<(string, string, Func)> DaemonCallBacksForServiceCalls { get; } = new(); // This is declared as static since it will contain state shared globally - private static ConcurrentDictionary _global = new(); - - private readonly ConcurrentDictionary _attributes = new(); - - // To handle state saves max once at a time, internal due to tests - private readonly Channel _lazyStoreStateQueue = - Channel.CreateBounded(1); + private static readonly ConcurrentDictionary _global = new(); private readonly Channel _updateRuntimeInfoChannel = Channel.CreateBounded(5); @@ -55,9 +42,8 @@ public abstract class NetDaemonAppBase : INetDaemonAppBase // /// The last error message logged och catched // /// // public string? RuntimeInfo.LastErrorMessage { get; set; } = null; - private CancellationTokenSource _cancelSource = new CancellationTokenSource(); + private readonly CancellationTokenSource _cancelSource = new(); private Task? _lazyStoreStateTask; - private FluentExpandoObject? _storageObject; /// /// Dependencies on other applications that will be initialized before this app @@ -104,15 +90,15 @@ public string Description } } - /// public ILogger? Logger { get; set; } /// - public dynamic Storage => _storageObject ?? throw new NullReferenceException($"{nameof(_storageObject)} cant be null"); + public dynamic Storage => InternalStorageObject ?? throw new NullReferenceException($"{nameof(InternalStorageObject)} cant be null"); - internal Channel InternalLazyStoreStateQueue => _lazyStoreStateQueue; - internal FluentExpandoObject? InternalStorageObject { get { return _storageObject; } set { _storageObject = value; } } + internal Channel InternalLazyStoreStateQueue { get; } = + Channel.CreateBounded(1); + internal FluentExpandoObject? InternalStorageObject { get; set; } /// /// Implements the IEqualit.Equals method @@ -120,10 +106,7 @@ public string Description /// The instance to compare public bool Equals([AllowNull] INetDaemonAppBase other) { - if (other is not null && other.Id is not null && this.Id is not null && this.Id == other.Id) - return true; - - return false; + return other is not null && other.Id is not null && Id is not null && Id == other.Id; } /// @@ -189,10 +172,8 @@ public async Task RestoreAppStateAsync() /// public string EntityId => $"switch.netdaemon_{Id?.ToSafeHomeAssistantEntityId()}"; - AppRuntimeInfo _runtimeInfo = new AppRuntimeInfo { HasError = false }; - /// - public AppRuntimeInfo RuntimeInfo => _runtimeInfo; + public AppRuntimeInfo RuntimeInfo { get; } = new AppRuntimeInfo { HasError = false }; /// public IEnumerable EntityIds => _daemon?.State.Select(n => n.EntityId) ?? @@ -204,7 +185,7 @@ public void SaveAppState() // Intentionally ignores full queue since we know // a state change already is in progress wich means // this state will be saved - var x = _lazyStoreStateQueue.Writer.TryWrite(true); + _ = InternalLazyStoreStateQueue.Writer.TryWrite(true); } /// @@ -220,14 +201,13 @@ public virtual Task StartUpAsync(INetDaemon daemon) _daemon = daemon; _manageRuntimeInformationUpdatesTask = ManageRuntimeInformationUpdates(); _lazyStoreStateTask = Task.Run(async () => await HandleLazyStorage().ConfigureAwait(false)); - _storageObject = new FluentExpandoObject(false, true, daemon: this); + InternalStorageObject = new FluentExpandoObject(false, true, daemon: this); Logger = daemon.Logger; Logger.LogDebug("Startup: {app}", GetUniqueIdForStorage()); var appInfo = _daemon!.State.FirstOrDefault(s => s.EntityId == EntityId); - var appState = appInfo?.State as string; - if (appState == null || (appState != "on" && appState != "off")) + if (appInfo?.State is not string appState || (appState != "on" && appState != "off")) { IsEnabled = true; } @@ -246,8 +226,8 @@ public virtual Task StartUpAsync(INetDaemon daemon) private async Task HandleLazyStorage() { - _ = _storageObject ?? - throw new NullReferenceException($"{nameof(_storageObject)} cant be null!"); + _ = InternalStorageObject ?? + throw new NullReferenceException($"{nameof(InternalStorageObject)} cant be null!"); _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); while (!_cancelSource.IsCancellationRequested) @@ -255,7 +235,7 @@ private async Task HandleLazyStorage() try { // Dont care about the result, just that it is time to store state - _ = await _lazyStoreStateQueue.Reader.ReadAsync(_cancelSource.Token); + _ = await InternalLazyStoreStateQueue.Reader.ReadAsync(_cancelSource.Token).ConfigureAwait(false); await _daemon!.SaveDataAsync>(GetUniqueIdForStorage(), (IDictionary)Storage) .ConfigureAwait(false); @@ -282,13 +262,12 @@ public async virtual ValueTask DisposeAsync() if (_manageRuntimeInformationUpdatesTask is not null) await _manageRuntimeInformationUpdatesTask.ConfigureAwait(false); - _daemonCallBacksForServiceCalls.Clear(); + DaemonCallBacksForServiceCalls.Clear(); this.IsEnabled = false; _lazyStoreStateTask = null; - _storageObject = null; + InternalStorageObject = null; _daemon = null; - } /// @@ -300,7 +279,7 @@ public async virtual ValueTask DisposeAsync() /// public void ListenServiceCall(string domain, string service, Func action) - => _daemonCallBacksForServiceCalls.Add((domain.ToLowerInvariant(), service.ToLowerInvariant(), action)); + => DaemonCallBacksForServiceCalls.Add((domain.ToLowerInvariant(), service.ToLowerInvariant(), action)); /// public void Log(string message) => Log(LogLevel.Information, message); @@ -462,7 +441,7 @@ private async Task ManageRuntimeInformationUpdates() { while (_updateRuntimeInfoChannel.Reader.TryRead(out _)) ; - _ = await _updateRuntimeInfoChannel.Reader.ReadAsync(_cancelSource.Token); + _ = await _updateRuntimeInfoChannel.Reader.ReadAsync(_cancelSource.Token).ConfigureAwait(false); // do the deed await HandleUpdateRuntimeInformation().ConfigureAwait(false); // make sure we never push more messages that 10 per second @@ -487,7 +466,5 @@ private async Task HandleUpdateRuntimeInformation() await _daemon!.SetStateAsync(EntityId, IsEnabled ? "on" : "off", ("runtime_info", RuntimeInfo)).ConfigureAwait(false); } - - } } \ No newline at end of file diff --git a/src/App/NetDaemon.App/Common/Reactive/AppDaemonRxApp.cs b/src/App/NetDaemon.App/Common/Reactive/AppDaemonRxApp.cs index 24537d81b..596f0c532 100644 --- a/src/App/NetDaemon.App/Common/Reactive/AppDaemonRxApp.cs +++ b/src/App/NetDaemon.App/Common/Reactive/AppDaemonRxApp.cs @@ -20,7 +20,7 @@ namespace NetDaemon.Common.Reactive /// public abstract class NetDaemonRxApp : NetDaemonAppBase, INetDaemonReactive { - private CancellationTokenSource _cancelTimers = new CancellationTokenSource(); + private readonly CancellationTokenSource _cancelTimers = new(); private EventObservable? _eventObservables; private ReactiveEvent? _reactiveEvent = null; private ReactiveState? _reactiveState = null; @@ -127,9 +127,7 @@ public RxEntity Entity(string entityId) /// public IDisposable RunDaily(string time, Action action) { - DateTime parsedTime; - - if (!DateTime.TryParseExact(time, "HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedTime)) + if (!DateTime.TryParseExact(time, "HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime parsedTime)) { throw new FormatException($"{time} is not a valid time for the current locale"); } @@ -144,8 +142,10 @@ public IDisposable RunDaily(string time, Action action) ); if (now > timeOfDayToTrigger) + { // It is not due until tomorrow timeOfDayToTrigger = timeOfDayToTrigger.AddDays(1); + } return CreateObservableTimer(timeOfDayToTrigger, TimeSpan.FromDays(1), action); } @@ -159,10 +159,9 @@ public IDisposable RunEvery(TimeSpan timespan, Action action) /// public IDisposable RunEveryHour(string time, Action action) { - DateTime parsedTime; time = $"{DateTime.Now.Hour:D2}:{time}"; - if (!DateTime.TryParseExact(time, "HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedTime)) + if (!DateTime.TryParseExact(time, "HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime parsedTime)) { throw new FormatException($"{time} is not a valid time for the current locale"); } @@ -178,8 +177,10 @@ public IDisposable RunEveryHour(string time, Action action) ); if (now > timeOfDayToTrigger) + { // It is not due until tomorrow timeOfDayToTrigger = timeOfDayToTrigger.AddHours(1); + } return CreateObservableTimer(timeOfDayToTrigger, TimeSpan.FromHours(1), action); } @@ -199,7 +200,7 @@ public IDisposable RunIn(TimeSpan timespan, Action action) var result = new DisposableTimerResult(_cancelTimers.Token); Observable.Timer(timespan, TaskPoolScheduler.Default) .Subscribe( - s => + _ => { try { @@ -252,7 +253,7 @@ public void SetState(string entityId, dynamic state, dynamic? attributes = null) /// public async override Task StartUpAsync(INetDaemon daemon) { - await base.StartUpAsync(daemon); + await base.StartUpAsync(daemon).ConfigureAwait(false); _ = _daemon as INetDaemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); _ = Logger ?? throw new NullReferenceException("Logger can not be null!"); @@ -278,7 +279,7 @@ internal virtual IDisposable CreateObservableIntervall(TimeSpan timespan, Action Observable.Interval(timespan, TaskPoolScheduler.Default) .Subscribe( - s => + _ => { try { @@ -298,10 +299,7 @@ internal virtual IDisposable CreateObservableIntervall(TimeSpan timespan, Action LogError(e, "Error, ObservableIntervall APP: {app}", Id ?? "unknown"); } }, - ex => - { - LogTrace("Exiting ObservableIntervall for app {app}, {trigger}:{span}", Id!, timespan); - } + ex => LogTrace(ex, "Exiting ObservableIntervall for app {app}, {trigger}:{span}", Id!, timespan) , result.Token); return result; @@ -326,7 +324,7 @@ internal virtual IDisposable CreateObservableTimer(DateTime timeOfDayToTrigger, interval, TaskPoolScheduler.Default) .Subscribe( - s => + _ => { try { @@ -349,7 +347,7 @@ internal virtual IDisposable CreateObservableTimer(DateTime timeOfDayToTrigger, () => Log("Exiting timer for app {app}, {trigger}:{span}", Id!, timeOfDayToTrigger, interval), result.Token - ); + ); return result; } diff --git a/src/App/NetDaemon.App/Common/Reactive/DisposableTimerResult.cs b/src/App/NetDaemon.App/Common/Reactive/DisposableTimerResult.cs index faa65eae9..3370d6302 100644 --- a/src/App/NetDaemon.App/Common/Reactive/DisposableTimerResult.cs +++ b/src/App/NetDaemon.App/Common/Reactive/DisposableTimerResult.cs @@ -32,5 +32,4 @@ public void Dispose() _internalToken.Cancel(); } } - } \ No newline at end of file diff --git a/src/App/NetDaemon.App/Common/Reactive/ObservableBase.cs b/src/App/NetDaemon.App/Common/Reactive/ObservableBase.cs index fec144b97..84a5d83f9 100644 --- a/src/App/NetDaemon.App/Common/Reactive/ObservableBase.cs +++ b/src/App/NetDaemon.App/Common/Reactive/ObservableBase.cs @@ -17,7 +17,7 @@ public class ObservableBase : IObservable private readonly ILogger _logger; private readonly ConcurrentDictionary, IObserver> - _observersTuples = new ConcurrentDictionary, IObserver>(); + _observersTuples = new(); /// /// Constructor /// diff --git a/src/App/NetDaemon.App/Common/Reactive/ObservableExtensionMethods.cs b/src/App/NetDaemon.App/Common/Reactive/ObservableExtensionMethods.cs index 938726c2b..c09651dd9 100644 --- a/src/App/NetDaemon.App/Common/Reactive/ObservableExtensionMethods.cs +++ b/src/App/NetDaemon.App/Common/Reactive/ObservableExtensionMethods.cs @@ -37,6 +37,5 @@ public static class ObservableExtensionMethods { return observable.Timeout(TimeSpan.FromSeconds(5), Observable.Return((new NetDaemon.Common.EntityState() { State = "TimeOut" }, new NetDaemon.Common.EntityState() { State = "TimeOut" }))).Take(1); } - } } \ No newline at end of file diff --git a/src/App/NetDaemon.App/Common/Reactive/RxEntity.cs b/src/App/NetDaemon.App/Common/Reactive/RxEntity.cs index 10118100b..c3b9030d1 100644 --- a/src/App/NetDaemon.App/Common/Reactive/RxEntity.cs +++ b/src/App/NetDaemon.App/Common/Reactive/RxEntity.cs @@ -108,7 +108,6 @@ public void SetState(dynamic state, dynamic? attributes = null) { foreach (var entityId in EntityIds) { - var domain = GetDomainFromEntity(entityId); DaemonRxApp.SetState(entityId, state, attributes); } } @@ -138,7 +137,7 @@ internal static string GetDomainFromEntity(string entity) /// Data to provide public void CallService(string service, dynamic? data = null) { - if (EntityIds is null || EntityIds is not null && EntityIds.Count() == 0) + if (EntityIds is null || (EntityIds is not null && !EntityIds.Any())) return; foreach (var entityId in EntityIds!) @@ -170,14 +169,14 @@ public void CallService(string service, dynamic? data = null) private void CallServiceOnEntity(string service, dynamic? attributes = null) { - if (EntityIds is null || EntityIds is not null && EntityIds.Count() == 0) + if (EntityIds is null || (EntityIds is not null && !EntityIds.Any())) return; dynamic? data = null; if (attributes is not null) { - if (attributes is IDictionary == false) + if (attributes is not IDictionary) data = ((object)attributes).ToExpandoObject(); else data = attributes; diff --git a/src/App/NetDaemon.App/Common/Reactive/RxEvent.cs b/src/App/NetDaemon.App/Common/Reactive/RxEvent.cs index 327a74b9c..57a6d39ad 100644 --- a/src/App/NetDaemon.App/Common/Reactive/RxEvent.cs +++ b/src/App/NetDaemon.App/Common/Reactive/RxEvent.cs @@ -5,9 +5,7 @@ /// public struct RxEvent { - private readonly dynamic? _data; private readonly string? _domain; - private readonly string _eventName; /// /// Constructor @@ -17,15 +15,15 @@ public struct RxEvent /// Data public RxEvent(string eventName, string? domain, dynamic? data) { - _eventName = eventName; + Event = eventName; _domain = domain; - _data = data; + Data = data; } /// /// Data from event /// - public dynamic? Data => _data; + public dynamic? Data { get; } /// /// Domain (call service event) @@ -35,6 +33,6 @@ public RxEvent(string eventName, string? domain, dynamic? data) /// /// The event being sent /// - public string Event => _eventName; + public string Event { get; } } } \ No newline at end of file diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs b/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs index c6079cfa9..15bc89595 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs @@ -15,7 +15,7 @@ public sealed class CodeManager : IInstanceDaemonApp { private readonly ILogger _logger; private readonly IYamlConfig _yamlConfig; - private IEnumerable? _loadedDaemonApps; + private readonly IEnumerable? _loadedDaemonApps; /// /// Constructor @@ -40,7 +40,7 @@ public IEnumerable InstanceDaemonApps() var result = new List(50); // No loaded, just return an empty list - if (_loadedDaemonApps is null || _loadedDaemonApps.Count() == 0) + if (_loadedDaemonApps?.Any() != true) return result; // Get all yaml config file paths diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Config/ConfigExtensions.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Config/ConfigExtensions.cs index 532e45605..9f668ef94 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Config/ConfigExtensions.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Config/ConfigExtensions.cs @@ -30,7 +30,7 @@ public static string ToPythonStyle(this string str) foreach (char c in str) { if (char.IsUpper(c) && !isStart) - build.Append("_"); + build.Append('_'); else isStart = false; build.Append(char.ToLower(c)); @@ -55,7 +55,6 @@ public static string ToCamelCase(this string str) nextIsUpper = false; isFirstCharacter = false; } - var returnString = build.ToString(); return build.ToString(); } diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs index 1b72b1257..8d7f003af 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs @@ -46,12 +46,11 @@ public IEnumerable Instances // Get the class string? appClass = GetTypeNameFromClassConfig((YamlMappingNode)app.Value); - Type? appType = _types.Where(n => n.FullName?.ToLowerInvariant() == appClass) - .FirstOrDefault(); + Type? appType = _types.FirstOrDefault(n => n.FullName?.ToLowerInvariant() == appClass); if (appType != null) { - var instance = InstanceAndSetPropertyConfig(appType, ((YamlMappingNode)app.Value), appId); + var instance = InstanceAndSetPropertyConfig(appType, (YamlMappingNode)app.Value, appId); if (instance != null) { instance.Id = appId; @@ -69,7 +68,10 @@ public IEnumerable Instances } } - public INetDaemonAppBase? InstanceAndSetPropertyConfig(Type netDaemonAppType, YamlMappingNode appNode, string? appId) + public INetDaemonAppBase? InstanceAndSetPropertyConfig( + Type netDaemonAppType, + YamlMappingNode appNode, + string? appId) { var netDaemonApp = (INetDaemonAppBase?)Activator.CreateInstance(netDaemonAppType); @@ -96,7 +98,7 @@ public IEnumerable Instances } catch (Exception e) { - throw new ApplicationException($"Failed to set value {scalarPropertyName}", e); + throw new ApplicationException($"Failed to set value {scalarPropertyName} for app {appId}", e); } } @@ -105,7 +107,6 @@ public IEnumerable Instances private object? InstanceProperty(Object? parent, Type instanceType, YamlNode node) { - if (node.NodeType == YamlNodeType.Scalar) { var scalarNode = (YamlScalarNode)node; @@ -120,13 +121,12 @@ public IEnumerable Instances throw new NullReferenceException($"The property {instanceType?.Name} of Class {parent?.GetType().Name} is not compatible with configuration"); IList list = listType.CreateListOfPropertyType() ?? - throw new NullReferenceException("Failed to create listtype, plese check {prop.Name} of Class {app.GetType().Name}"); + throw new NullReferenceException("Failed to create listtype, plese check {prop.Name} of Class {app.GetType().Name}"); foreach (YamlNode item in ((YamlSequenceNode)node).Children) { - var instance = InstanceProperty(null, listType, item) ?? - throw new NotSupportedException($"The class {parent?.GetType().Name} has wrong type in items"); + throw new NotSupportedException($"The class {parent?.GetType().Name} has wrong type in items"); list.Add(instance); } @@ -162,7 +162,7 @@ public IEnumerable Instances break; case YamlNodeType.Mapping: - var map = (YamlMappingNode)entry.Value; + // Maps are not currently supported (var map = (YamlMappingNode)entry.Value;) break; } childProp.SetValue(instance, result); @@ -182,10 +182,10 @@ private void ReplaceSecretIfExists(YamlScalarNode scalarNode) scalarNode.Value = secretReplacement ?? throw new ApplicationException($"{scalarNode.Value!} not found in secrets.yaml"); } - private string? GetTypeNameFromClassConfig(YamlMappingNode appNode) + private static string? GetTypeNameFromClassConfig(YamlMappingNode appNode) { - KeyValuePair classChild = appNode.Children.Where(n => - ((YamlScalarNode)n.Key)?.Value?.ToLowerInvariant() == "class").FirstOrDefault(); + KeyValuePair classChild = appNode.Children.FirstOrDefault(n => + ((YamlScalarNode)n.Key)?.Value?.ToLowerInvariant() == "class"); if (classChild.Key == null || classChild.Value == null) { diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlExtensions.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlExtensions.cs index 09b49f17a..9d121c82e 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlExtensions.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlExtensions.cs @@ -25,13 +25,8 @@ public static class YamlExtensions { public static PropertyInfo? GetYamlProperty(this Type type, string propertyName) { - var prop = type.GetProperty(propertyName); - - if (prop == null) - { - // Lets try convert from python style to CamelCase - prop = type.GetProperty(propertyName.ToCamelCase()); - } + // Lets try convert from python style to CamelCase + var prop = type.GetProperty(propertyName) ?? type.GetProperty(propertyName.ToCamelCase()); return prop; } diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/DaemonAppExtensions.cs b/src/Daemon/NetDaemon.Daemon/Daemon/DaemonAppExtensions.cs index bde6b0c4d..985c9f2e3 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/DaemonAppExtensions.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/DaemonAppExtensions.cs @@ -27,7 +27,7 @@ public static async Task HandleAttributeInitialization(this INetDaemonAppBase ne { switch (attr) { - case HomeAssistantServiceCallAttribute hasstServiceCallAttribute: + case HomeAssistantServiceCallAttribute: await HandleServiceCallAttribute(_daemon, daemonApp, method, true).ConfigureAwait(false); break; @@ -40,7 +40,7 @@ public static async Task HandleAttributeInitialization(this INetDaemonAppBase ne { switch (attr) { - case HomeAssistantServiceCallAttribute hasstServiceCallAttribute: + case HomeAssistantServiceCallAttribute: await HandleServiceCallAttribute(_daemon, daemonRxApp, method, false).ConfigureAwait(false); break; } @@ -56,13 +56,15 @@ private static (bool, string) CheckIfServiceCallSignatureIsOk(MethodInfo method, var parameters = method.GetParameters(); - if (parameters == null || (parameters != null && parameters.Length != 1)) + if (parameters == null || parameters.Length != 1) return (false, $"{method.Name} has not correct number of parameters"); var dynParam = parameters![0]; if (dynParam.CustomAttributes.Count() == 1 && dynParam.CustomAttributes.First().AttributeType == typeof(DynamicAttribute)) + { return (true, string.Empty); + } return (false, $"{method.Name} is not correct signature"); } @@ -74,7 +76,7 @@ private static (bool, string) CheckIfStateChangedSignatureIsOk(MethodInfo method var parameters = method.GetParameters(); - if (parameters == null || (parameters != null && parameters.Length != 3)) + if (parameters == null || parameters.Length != 3) return (false, $"{method.Name} has not correct number of parameters"); if (parameters![0].ParameterType != typeof(string)) @@ -89,7 +91,7 @@ private static (bool, string) CheckIfStateChangedSignatureIsOk(MethodInfo method return (true, string.Empty); } - private static async Task HandleServiceCallAttribute(INetDaemon _daemon, NetDaemonAppBase netDaemonApp, MethodInfo method, bool async=true) + private static async Task HandleServiceCallAttribute(INetDaemon _daemon, NetDaemonAppBase netDaemonApp, MethodInfo method, bool async = true) { var (signatureOk, err) = CheckIfServiceCallSignatureIsOk(method, async); if (!signatureOk) @@ -118,7 +120,6 @@ private static async Task HandleServiceCallAttribute(INetDaemon _daemon, NetDaem } }); } - private static void HandleStateChangedAttribute( INetDaemon _daemon, @@ -141,12 +142,16 @@ MethodInfo method try { if (hassStateChangedAttribute.To != null) + { if ((dynamic)hassStateChangedAttribute.To != to?.State) return; + } if (hassStateChangedAttribute.From != null) + { if ((dynamic)hassStateChangedAttribute.From != from?.State) return; + } // If we don´t accept all changes in the state change // and we do not have a state change so return diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs b/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs index 98a6a8a7c..5c80b5f9c 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs @@ -33,7 +33,7 @@ public HttpClient CreateHttpClient(string? name = null) var streamTask = httpClient.GetStreamAsync(url) ?? throw new ApplicationException($"Unexpected, nothing returned from {url}"); - return await JsonSerializer.DeserializeAsync(await streamTask.ConfigureAwait(false), options); + return await JsonSerializer.DeserializeAsync(await streamTask.ConfigureAwait(false), options).ConfigureAwait(false); } public async Task PostJson(string url, object request, JsonSerializerOptions? options = null, params (string, object)[] headers) @@ -46,12 +46,12 @@ public HttpClient CreateHttpClient(string? name = null) var bytesToPost = JsonSerializer.SerializeToUtf8Bytes(request, request.GetType(), options); - var response = await httpClient.PostAsync(url, new ByteArrayContent(bytesToPost)); + var response = await httpClient.PostAsync(url, new ByteArrayContent(bytesToPost)).ConfigureAwait(false); response.EnsureSuccessStatusCode(); var streamTask = response.Content.ReadAsStreamAsync(); - return await JsonSerializer.DeserializeAsync(await streamTask.ConfigureAwait(false)); + return await JsonSerializer.DeserializeAsync(await streamTask.ConfigureAwait(false)).ConfigureAwait(false); } public async Task PostJson(string url, object request, JsonSerializerOptions? options = null, params (string, object)[] headers) @@ -64,22 +64,22 @@ public async Task PostJson(string url, object request, JsonSerializerOptions? op var bytesToPost = JsonSerializer.SerializeToUtf8Bytes(request, request.GetType(), options); - var response = await httpClient.PostAsync(url, new ByteArrayContent(bytesToPost)); + var response = await httpClient.PostAsync(url, new ByteArrayContent(bytesToPost)).ConfigureAwait(false); response.EnsureSuccessStatusCode(); } - private void AddHeaders(HttpClient httpClient, (string, object)[] headers) + private static void AddHeaders(HttpClient httpClient, (string, object)[] headers) { if (headers is not null && headers.Length > 0) { httpClient.DefaultRequestHeaders.Clear(); foreach (var (name, header) in headers) { - if (header is string) - httpClient.DefaultRequestHeaders.Add(name, (string)header); - else if (header is IEnumerable) - httpClient.DefaultRequestHeaders.Add(name, (IEnumerable)header); + if (header is string headerStr) + httpClient.DefaultRequestHeaders.Add(name, headerStr); + else if (header is IEnumerable headerStrings) + httpClient.DefaultRequestHeaders.Add(name, headerStrings); else throw new ApplicationException($"Unsupported header, expected string or IEnumerable for {name}"); } diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/INetDaemonHost.cs b/src/Daemon/NetDaemon.Daemon/Daemon/INetDaemonHost.cs index 09b8cf9aa..52a61f27f 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/INetDaemonHost.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/INetDaemonHost.cs @@ -8,7 +8,6 @@ namespace NetDaemon.Daemon { - /// /// The interface that interacts with the daemon host main logic /// diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs b/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs index e23f0b119..05b06b3aa 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs @@ -24,17 +24,18 @@ namespace NetDaemon.Daemon { public class NetDaemonHost : INetDaemonHost, IAsyncDisposable { + private static readonly ConcurrentBag> concurrentBag = new(); internal readonly ConcurrentBag> _externalEventCallSubscribers = - new ConcurrentBag>(); + concurrentBag; internal readonly ConcurrentDictionary _hassAreas = - new ConcurrentDictionary(); + new(); // Internal for test internal readonly ConcurrentDictionary _hassDevices = - new ConcurrentDictionary(); + new(); internal readonly ConcurrentDictionary _hassEntities = - new ConcurrentDictionary(); + new(); internal readonly Channel<(string, string, dynamic?)> _serviceCallMessageChannel = Channel.CreateBounded<(string, string, dynamic?)>(200); @@ -54,10 +55,10 @@ public class NetDaemonHost : INetDaemonHost, IAsyncDisposable private IInstanceDaemonApp? _appInstanceManager; // Internal token source for just cancel this objects activities - private readonly CancellationTokenSource _cancelDaemon = new CancellationTokenSource(); + private readonly CancellationTokenSource _cancelDaemon = new(); private readonly ConcurrentBag<(string, string, Func)> _daemonServiceCallFunctions - = new ConcurrentBag<(string, string, Func)>(); + = new(); /// /// Currently running tasks for handling new events from HomeAssistant @@ -70,36 +71,17 @@ public class NetDaemonHost : INetDaemonHost, IAsyncDisposable private readonly IDataRepository? _repository; - private readonly ConcurrentDictionary _runningAppInstances = - new ConcurrentDictionary(); - private readonly ConcurrentDictionary _allAppInstances = - new ConcurrentDictionary(); - /// /// Used for testing /// - internal ConcurrentDictionary InternalAllAppInstances => _allAppInstances; + internal ConcurrentDictionary InternalAllAppInstances { get; } = new(); private readonly Scheduler _scheduler; - private readonly List _supportedDomainsForTurnOnOff = new() - { - "light", - "switch", - "input_boolean", - "automation", - "input_boolean", - "camera", - "scene", - "script", - }; - // Following token source and token are set at RUN private CancellationToken _cancelToken; private CancellationTokenSource? _cancelTokenSource; - private IDictionary _dataCache = new Dictionary(); - /// /// Constructor @@ -119,7 +101,8 @@ public NetDaemonHost( loggerFactory ??= DefaultLoggerFactory; _httpHandler = httpHandler; Logger = loggerFactory.CreateLogger(); - _hassClient = hassClient ?? throw new ArgumentNullException("HassClient can't be null!"); + _hassClient = hassClient + ?? throw new ArgumentNullException(nameof(hassClient)); _scheduler = new Scheduler(loggerFactory: loggerFactory); _repository = repository; Logger.LogTrace("Instance NetDaemonHost"); @@ -138,16 +121,16 @@ public IHttpHandler Http public ILogger Logger { get; } - public IEnumerable RunningAppInstances => _runningAppInstances.Values; + public IEnumerable RunningAppInstances => InternalRunningAppInstances.Values; - public IEnumerable AllAppInstances => _allAppInstances.Values; + public IEnumerable AllAppInstances => InternalAllAppInstances.Values; public IScheduler Scheduler => _scheduler; public IEnumerable State => InternalState.Select(n => n.Value); // For testing - internal ConcurrentDictionary InternalRunningAppInstances => _runningAppInstances; + internal ConcurrentDictionary InternalRunningAppInstances { get; } = new(); private static ILoggerFactory DefaultLoggerFactory => LoggerFactory.Create(builder => { @@ -156,19 +139,20 @@ public IHttpHandler Http .AddConsole(); }); + public IDictionary DataCache { get; } = new Dictionary(); public async Task> GetAllServices() { this._cancelToken.ThrowIfCancellationRequested(); - return await _hassClient.GetServices(); + return await _hassClient.GetServices().ConfigureAwait(false); } public void CallService(string domain, string service, dynamic? data = null) { this._cancelToken.ThrowIfCancellationRequested(); - if (_serviceCallMessageChannel.Writer.TryWrite((domain, service, data)) == false) + if (!_serviceCallMessageChannel.Writer.TryWrite((domain, service, data))) throw new ApplicationException("Servicecall queue full!"); } @@ -224,10 +208,10 @@ public async ValueTask DisposeAsync() Logger.LogTrace("Instance NetDaemonHost Disposed"); } - public void EnableApplicationDiscoveryServiceAsync() + public void EnableApplicationDiscoveryService() { // For service call reload_apps we do just that... reload the fucking apps yay :) - ListenCompanionServiceCall("reload_apps", async (_) => await ReloadAllApps()); + ListenCompanionServiceCall("reload_apps", async (_) => await ReloadAllApps().ConfigureAwait(false)); RegisterAppSwitchesAndTheirStates(); } @@ -266,25 +250,25 @@ public IEntity Entity(INetDaemonApp app, params string[] entityIds) { this._cancelToken.ThrowIfCancellationRequested(); - return _runningAppInstances.ContainsKey(appInstanceId) ? - _runningAppInstances[appInstanceId] : null; + return InternalRunningAppInstances.ContainsKey(appInstanceId) ? + InternalRunningAppInstances[appInstanceId] : null; } - public async ValueTask GetDataAsync(string id) where T : class + public async Task GetDataAsync(string id) where T : class { this._cancelToken.ThrowIfCancellationRequested(); _ = _repository as IDataRepository ?? - throw new NullReferenceException($"{nameof(_repository)} can not be null!"); + throw new NullReferenceException($"{nameof(_repository)} can not be null!"); - if (_dataCache.ContainsKey(id)) + if (DataCache.ContainsKey(id)) { - return (T)_dataCache[id]; + return (T)DataCache[id]; } var data = await _repository!.Get(id).ConfigureAwait(false); if (data != null) - _dataCache[id] = data; + DataCache[id] = data; return data; } @@ -307,7 +291,7 @@ public async Task Initialize(IInstanceDaemonApp appInstanceManager) _appInstanceManager = appInstanceManager; await LoadAllApps().ConfigureAwait(false); - EnableApplicationDiscoveryServiceAsync(); + EnableApplicationDiscoveryService(); } /// @@ -434,7 +418,7 @@ public async Task Run(string host, short port, bool ssl, string token, Cancellat if (hassConfig.State != "RUNNING") { Logger.LogInformation("Home Assistant is not ready yet, state: {state} ..", hassConfig.State); - await _hassClient.CloseAsync(); + await _hassClient.CloseAsync().ConfigureAwait(false); return; } @@ -515,7 +499,7 @@ public Task SaveDataAsync(string id, T data) if (data == null) throw new ArgumentNullException(nameof(data)); - _dataCache[id] = data; + DataCache[id] = data; return _repository!.Save(id, data); } @@ -553,7 +537,7 @@ public void SetState(string entityId, dynamic state, dynamic? attributes = null) { this._cancelToken.ThrowIfCancellationRequested(); - if (_setStateMessageChannel.Writer.TryWrite((entityId, state, attributes)) == false) + if (!_setStateMessageChannel.Writer.TryWrite((entityId, state, attributes))) throw new ApplicationException("Servicecall queue full!"); } @@ -624,12 +608,12 @@ public async Task Stop() /// public async Task UnloadAllApps() { - foreach (var app in _allAppInstances) + foreach (var app in InternalAllAppInstances) { await app.Value.DisposeAsync().ConfigureAwait(false); } - _allAppInstances.Clear(); - _runningAppInstances.Clear(); + InternalAllAppInstances.Clear(); + InternalRunningAppInstances.Clear(); } /// @@ -835,7 +819,7 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok InternalState[stateData.EntityId] = newState; var tasks = new List(); - foreach (var app in _runningAppInstances) + foreach (var app in InternalRunningAppInstances) { if (app.Value is NetDaemonApp netDaemonApp) { @@ -904,7 +888,7 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok throw new NullReferenceException("ServiceData is null! not expected"); } var tasks = new List(); - foreach (var app in _runningAppInstances) + foreach (var app in InternalRunningAppInstances) { // Call any service call registered if (app.Value is NetDaemonApp netDaemonApp) @@ -999,7 +983,7 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok hassEvent.Data = new FluentExpandoObject(true, true, exObject); } var tasks = new List(); - foreach (var app in _runningAppInstances) + foreach (var app in InternalRunningAppInstances) { if (app.Value is NetDaemonApp netDaemonApp) { @@ -1231,21 +1215,21 @@ private async Task LoadAllApps() // Get all instances var instancedApps = _appInstanceManager.InstanceDaemonApps(); - if (_runningAppInstances.Count() > 0) + if (InternalRunningAppInstances.Count() > 0) throw new ApplicationException("Did not expect running instances!"); foreach (INetDaemonAppBase appInstance in instancedApps!) { - _allAppInstances[appInstance.Id!] = appInstance; + InternalAllAppInstances[appInstance.Id!] = appInstance; if (await RestoreAppState(appInstance).ConfigureAwait(false)) { - _runningAppInstances[appInstance.Id!] = appInstance; + InternalRunningAppInstances[appInstance.Id!] = appInstance; } } // Now run initialize on all sorted by dependencies - foreach (var sortedApp in SortByDependency(_runningAppInstances.Values)) + foreach (var sortedApp in SortByDependency(InternalRunningAppInstances.Values)) { // Init by calling the InitializeAsync var taskInitAsync = sortedApp.InitializeAsync(); @@ -1264,7 +1248,7 @@ private async Task LoadAllApps() Logger.LogInformation("Successfully loaded app {appId} ({class})", sortedApp.Id, sortedApp.GetType().Name); } - await SetDaemonStateAsync(_appInstanceManager.Count, _runningAppInstances.Count).ConfigureAwait(false); + await SetDaemonStateAsync(_appInstanceManager.Count, InternalRunningAppInstances.Count).ConfigureAwait(false); } private void RegisterAppSwitchesAndTheirStates() @@ -1318,14 +1302,14 @@ async Task SetStateOnDaemonAppSwitch(string state, dynamic? data) private async Task SetDependentState(string entityId, string state) { - var app = _allAppInstances.Values.Where(n => n.EntityId == entityId).FirstOrDefault(); + var app = InternalAllAppInstances.Values.Where(n => n.EntityId == entityId).FirstOrDefault(); if (app is not null) { if (state == "off") { // We need to turn off any dependent apps - var depApps = _allAppInstances.Values.Where(n => n.Dependencies.Contains(app.Id)); + var depApps = InternalAllAppInstances.Values.Where(n => n.Dependencies.Contains(app.Id)); foreach (var depApp in depApps) { @@ -1341,7 +1325,7 @@ private async Task SetDependentState(string entityId, string state) // Enable all apps that this app is dependent on foreach (var depOnId in app.Dependencies) { - var depOnApp = _allAppInstances.Values.Where(n => n.Id == depOnId).FirstOrDefault(); + var depOnApp = InternalAllAppInstances.Values.Where(n => n.Id == depOnId).FirstOrDefault(); if (depOnApp is not null) { await SetDependentState(depOnApp.EntityId, state).ConfigureAwait(false); diff --git a/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs b/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs index b63e8c587..ce2e16e51 100644 --- a/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs +++ b/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs @@ -90,7 +90,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) await using var daemonHost = _serviceProvider.GetService() ?? throw new ApplicationException("Failed to get service for NetDaemonHost"); { - await Run(daemonHost, stoppingToken); + await Run(daemonHost, stoppingToken).ConfigureAwait(false); } } catch (OperationCanceledException) @@ -126,7 +126,7 @@ private async Task Run(NetDaemonHost daemonHost, CancellationToken stoppingToken stoppingToken ); - if (await WaitForDaemonToConnect(daemonHost, stoppingToken).ConfigureAwait(false) == false) + if (!await WaitForDaemonToConnect(daemonHost, stoppingToken).ConfigureAwait(false)) { continue; } @@ -139,12 +139,12 @@ private async Task Run(NetDaemonHost daemonHost, CancellationToken stoppingToken { // Generate code if requested if (_sourcePath is string) - await GenerateEntities(daemonHost, _sourcePath); + await GenerateEntities(daemonHost, _sourcePath).ConfigureAwait(false); if (_loadedDaemonApps is null) _loadedDaemonApps = _daemonAppCompiler.GetApps(); - if (_loadedDaemonApps is null || !_loadedDaemonApps.Any()) + if (_loadedDaemonApps?.Any() != true) { _logger.LogError("No NetDaemon apps could be found, exiting..."); return; @@ -155,7 +155,6 @@ private async Task Run(NetDaemonHost daemonHost, CancellationToken stoppingToken // Wait until daemon stops await daemonHostTask.ConfigureAwait(false); - } catch (TaskCanceledException) { @@ -199,7 +198,6 @@ private async Task Run(NetDaemonHost daemonHost, CancellationToken stoppingToken // If we reached here it could be a re-connect _hasConnectedBefore = true; - } } private async Task GenerateEntities(NetDaemonHost daemonHost, string sourceFolder) @@ -221,7 +219,7 @@ private async Task GenerateEntities(NetDaemonHost daemonHost, string sourceFolde await File.WriteAllTextAsync(Path.Combine(sourceFolder!, "_EntityExtensions.cs.gen"), source).ConfigureAwait(false); - var services = await daemonHost.GetAllServices(); + var services = await daemonHost.GetAllServices().ConfigureAwait(false); var sourceRx = codeGen.GenerateCodeRx( "Netdaemon.Generated.Reactive", daemonHost.State.Select(n => n.EntityId).Distinct(), @@ -231,7 +229,7 @@ private async Task GenerateEntities(NetDaemonHost daemonHost, string sourceFolde await File.WriteAllTextAsync(Path.Combine(sourceFolder!, "_EntityExtensionsRx.cs.gen"), sourceRx).ConfigureAwait(false); } - private async Task WaitForDaemonToConnect(NetDaemonHost daemonHost, CancellationToken stoppingToken) + private static async Task WaitForDaemonToConnect(NetDaemonHost daemonHost, CancellationToken stoppingToken) { var nrOfTimesCheckForConnectedState = 0; diff --git a/src/DevelopmentApps/NetDaemon.DevelopmentApps.csproj b/src/DevelopmentApps/NetDaemon.DevelopmentApps.csproj index 0394c7cfe..c28efeac0 100644 --- a/src/DevelopmentApps/NetDaemon.DevelopmentApps.csproj +++ b/src/DevelopmentApps/NetDaemon.DevelopmentApps.csproj @@ -19,4 +19,5 @@ + \ No newline at end of file diff --git a/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs b/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs index b82cc3ba0..374f80352 100644 --- a/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs +++ b/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs @@ -14,17 +14,11 @@ namespace NetDaemon.Daemon.Fakes { - /// /// Base class for test classes /// public partial class DaemonHostTestBase : IAsyncLifetime { - private readonly NetDaemonHost _defaultDaemonHost; - private readonly Mock _defaultDataRepositoryMock; - private readonly HassClientMock _defaultHassClientMock; - private readonly HttpHandlerMock _defaultHttpHandlerMock; - private readonly LoggerMock _loggerMock; private Task? _fakeConnectedDaemon; /// @@ -32,40 +26,41 @@ public partial class DaemonHostTestBase : IAsyncLifetime /// public DaemonHostTestBase() { - _loggerMock = new LoggerMock(); - _defaultHassClientMock = HassClientMock.DefaultMock; - _defaultDataRepositoryMock = new Mock(); - - _defaultHttpHandlerMock = new HttpHandlerMock(); - _defaultDaemonHost = new NetDaemonHost( - _defaultHassClientMock.Object, - _defaultDataRepositoryMock.Object, - _loggerMock.LoggerFactory, - _defaultHttpHandlerMock.Object); - - _defaultDaemonHost.InternalDelayTimeForTts = 0; // Allow no extra waittime + LoggerMock = new LoggerMock(); + DefaultHassClientMock = HassClientMock.DefaultMock; + DefaultDataRepositoryMock = new Mock(); + + DefaultHttpHandlerMock = new HttpHandlerMock(); + DefaultDaemonHost = new NetDaemonHost( + DefaultHassClientMock.Object, + DefaultDataRepositoryMock.Object, + LoggerMock.LoggerFactory, + DefaultHttpHandlerMock.Object) + { + InternalDelayTimeForTts = 0 // Allow no extra waittime + }; } /// /// Returns default DaemonHost mock /// - public NetDaemonHost DefaultDaemonHost => _defaultDaemonHost; + public NetDaemonHost DefaultDaemonHost { get; } /// /// Returns default data repository mock /// - public Mock DefaultDataRepositoryMock => _defaultDataRepositoryMock; + public Mock DefaultDataRepositoryMock { get; } /// /// Returns default HassClient mock /// - public HassClientMock DefaultHassClientMock => _defaultHassClientMock; + public HassClientMock DefaultHassClientMock { get; } /// /// Returns default HttpHandler mock /// - public HttpHandlerMock DefaultHttpHandlerMock => _defaultHttpHandlerMock; + public HttpHandlerMock DefaultHttpHandlerMock { get; } /// /// Returns default logger mock /// - public LoggerMock LoggerMock => _loggerMock; + public LoggerMock LoggerMock { get; } /// /// Cleans up test @@ -79,7 +74,7 @@ public Task DisposeAsync() /// Gets a object as dynamic /// /// The object to turn into dynamic - public dynamic GetDynamicDataObject(string testData = "testdata") + public static dynamic GetDynamicDataObject(string testData = "testdata") { var expandoObject = new ExpandoObject(); dynamic dynamicData = expandoObject; @@ -90,7 +85,7 @@ public dynamic GetDynamicDataObject(string testData = "testdata") /// /// Converts parameters to dynamics /// - public (dynamic, FluentExpandoObject) GetDynamicObject(params (string, object)[] dynamicParameters) + public static (dynamic, FluentExpandoObject) GetDynamicObject(params (string, object)[] dynamicParameters) { var expandoObject = new FluentExpandoObject(); var dict = expandoObject as IDictionary; @@ -147,7 +142,7 @@ public async Task AddAppInstance(INetDaemonAppBase app) app.Id = Guid.NewGuid().ToString(); DefaultDaemonHost.InternalAllAppInstances[app.Id] = app; DefaultDaemonHost.InternalRunningAppInstances[app.Id] = app; - await app.StartUpAsync(DefaultDaemonHost); + await app.StartUpAsync(DefaultDaemonHost).ConfigureAwait(false); } /// @@ -259,15 +254,16 @@ public void VerifyCallService(string domain, string service, object? data = null /// Service domain /// Service to verify /// EntityId to verify - /// Data sent by service /// If service was waiting for response /// Number of times called /// Attributes to verify - public void VerifyCallService(string domain, string service, string entityId, object? data = null, bool waitForResponse = false, Moq.Times? times = null, + public void VerifyCallService(string domain, string service, string entityId, bool waitForResponse = false, Moq.Times? times = null, params (string attribute, object value)[] attributesTuples) { - var serviceObject = new FluentExpandoObject(); - serviceObject["entity_id"] = entityId; + var serviceObject = new FluentExpandoObject + { + ["entity_id"] = entityId + }; foreach (var (attr, val) in attributesTuples) { serviceObject[attr] = val; @@ -302,7 +298,7 @@ public void VerifyCallServiceTimes(string service, Times times) /// /// Task to wait for /// - private async Task WaitUntilCanceled(Task task) + private static async Task WaitUntilCanceled(Task task) { try { @@ -321,12 +317,12 @@ private async Task InitApps() { foreach (var inst in DefaultDaemonHost.InternalAllAppInstances) { - await inst.Value.StartUpAsync(_defaultDaemonHost); + await inst.Value.StartUpAsync(DefaultDaemonHost).ConfigureAwait(false); } foreach (var inst in DefaultDaemonHost.InternalRunningAppInstances) { - await inst.Value.InitializeAsync(); + await inst.Value.InitializeAsync().ConfigureAwait(false); inst.Value.Initialize(); } } @@ -344,7 +340,7 @@ public void VerifySetState(string entityId, string state, } /// - /// Verifies that state being set + /// Verifies that state being set /// /// Unique identifier of the entity /// How many times it being set @@ -360,11 +356,11 @@ public void VerifySetStateTimes(string entityId, Times times) /// True if running debug mode should not cancel on timeout protected async Task InitializeFakeDaemon(short timeout = 300, bool overrideDebugNotCancel = false) { - _fakeConnectedDaemon = await GetConnectedNetDaemonTask(timeout, overrideDebugNotCancel); + _fakeConnectedDaemon = await GetConnectedNetDaemonTask(timeout, overrideDebugNotCancel).ConfigureAwait(false); } /// - /// + /// Runs the fake daemon service until timed out /// /// protected async Task RunFakeDaemonUntilTimeout() @@ -387,14 +383,14 @@ private async Task GetConnectedNetDaemonTask(short milliSeconds = 100, boo ? new CancellationTokenSource() : new CancellationTokenSource(milliSeconds); - await InitApps(); + await InitApps().ConfigureAwait(false); - var daemonTask = _defaultDaemonHost.Run("host", 8123, false, "token", cancelSource.Token); - await WaitForDefaultDaemonToConnect(DefaultDaemonHost, cancelSource.Token); + var daemonTask = DefaultDaemonHost.Run("host", 8123, false, "token", cancelSource.Token); + await WaitForDefaultDaemonToConnect(DefaultDaemonHost, cancelSource.Token).ConfigureAwait(false); return daemonTask; } - private async Task WaitForDefaultDaemonToConnect(NetDaemonHost daemonHost, CancellationToken cancellationToken) + private static async Task WaitForDefaultDaemonToConnect(NetDaemonHost daemonHost, CancellationToken cancellationToken) { var nrOfTimesCheckForConnectedState = 0; diff --git a/tests/NetDaemon.Daemon.Tests/Daemon/NetDaemonHostTests.cs b/tests/NetDaemon.Daemon.Tests/Daemon/NetDaemonHostTests.cs index a6ac578f4..12abad9dc 100644 --- a/tests/NetDaemon.Daemon.Tests/Daemon/NetDaemonHostTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Daemon/NetDaemonHostTests.cs @@ -28,7 +28,7 @@ public async Task EventShouldCallCorrectFunction() await InitializeFakeDaemon().ConfigureAwait(false); - dynamic helloWorldDataObject = GetDynamicDataObject(HelloWorldData); + dynamic helloWorldDataObject = DaemonHostTestBase.GetDynamicDataObject(HelloWorldData); DefaultHassClientMock.AddCustomEvent("CUSTOM_EVENT", helloWorldDataObject); @@ -106,7 +106,7 @@ public async Task OtherEventShouldNotCallCorrectFunction() { // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); - dynamic dataObject = GetDynamicDataObject(); + dynamic dataObject = DaemonHostTestBase.GetDynamicDataObject(); DefaultHassClientMock.AddCustomEvent("CUSTOM_EVENT", dataObject); @@ -153,7 +153,7 @@ public async Task SendEventShouldCallCorrectMethod() { // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); - var eventData = GetDynamicDataObject(); + var eventData = DaemonHostTestBase.GetDynamicDataObject(); // ACT @@ -350,7 +350,7 @@ public async Task CallServiceEventShouldCallCorrectFunction() { // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); - var dynObject = GetDynamicDataObject(HelloWorldData); + var dynObject = DaemonHostTestBase.GetDynamicDataObject(HelloWorldData); var isCalled = false; @@ -378,7 +378,7 @@ public async Task CallServiceEventOtherShouldNotCallFunction() { // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); - var dynObject = GetDynamicDataObject(HelloWorldData); + var dynObject = DaemonHostTestBase.GetDynamicDataObject(HelloWorldData); DefaultHassClientMock.AddCallServiceEvent("custom_domain", "other_service", dynObject); diff --git a/tests/NetDaemon.Daemon.Tests/TimeManagerMock.cs b/tests/NetDaemon.Daemon.Tests/TimeManagerMock.cs index 97f5c1d9a..114be676b 100644 --- a/tests/NetDaemon.Daemon.Tests/TimeManagerMock.cs +++ b/tests/NetDaemon.Daemon.Tests/TimeManagerMock.cs @@ -21,7 +21,7 @@ public TimeManagerMock() { SetupGet(n => n.Current).Returns(_time); Setup(n => n.Delay(It.IsAny(), It.IsAny())) - .Returns(async (TimeSpan s, CancellationToken c) => await Task.Delay(s, c)); + .Returns(async (TimeSpan s, CancellationToken c) => await Task.Delay(s, c).ConfigureAwait(false)); } } } \ No newline at end of file From 0cca3b074c4e2716358369a3d2724843061d9de0 Mon Sep 17 00:00:00 2001 From: helto4real Date: Sat, 26 Dec 2020 01:15:43 +0100 Subject: [PATCH 02/10] Next batch --- .../NetDaemon.Daemon/Daemon/NetDaemonHost.cs | 31 ++++----- .../NetDaemon.Daemon/Daemon/Scheduler.cs | 16 ++--- .../Daemon/Storage/DataRepository.cs | 2 +- .../Mapping/EntityStateMapper.cs | 4 +- .../Config/SerilogConfigurator.cs | 2 +- .../DaemonRunner/Service/API/WsHandler.cs | 52 +++++++-------- .../DaemonRunner/Service/ApiService.cs | 8 +-- .../DaemonRunner/Service/App/CodeGenerator.cs | 10 +-- .../Service/App/DaemonAppCompiler.cs | 7 +- .../Service/App/DaemonCompiler.cs | 45 ++++++------- .../Service/App/IDaemonAppCompiler.cs | 2 - .../Service/App/LocalDaemonAppCompiler.cs | 2 +- .../DaemonRunner/Service/RunnerService.cs | 4 +- src/Fakes/NetDaemon.Fakes/HassClientMock.cs | 20 +++--- src/Fakes/NetDaemon.Fakes/HttpHandlerMock.cs | 2 +- .../Daemon/DataRepositoryTests.cs | 3 - .../Daemon/HttpTests.cs | 8 +-- .../Daemon/NetDaemonHostTests.cs | 12 ++-- .../DaemonRunner/Api/ApiTests.cs | 65 ++++++++++--------- .../DaemonRunner/App/AssemblyDaemonApps.cs | 58 +++++++++-------- .../DaemonRunner/App/CodeGenTests.cs | 3 +- .../DaemonRunner/App/DaemonAppTests.cs | 34 +++++----- .../DaemonRunner/Config/AppComplexConfig.cs | 8 +-- .../DaemonRunner/Config/ConfigTest.cs | 21 +++--- .../FakesTests/FakeTests.cs | 9 +-- .../Fluent/FluentEventTests.cs | 56 ++++++++++------ .../NetDaemonApp/FaultyAppsTests.cs | 6 +- .../NetDaemonApp/FaultyRxAppsTests.cs | 6 +- .../NetDaemonApp/NetDaemonAppTests.cs | 8 +-- .../Reactive/RxAppTest.cs | 8 +-- .../Reactive/RxSchedulers.cs | 12 ++-- .../NetDaemon.Daemon.Tests/SchedulerTests.cs | 2 +- .../NetDaemon.Daemon.Tests/TestBaseClasses.cs | 20 +++--- 33 files changed, 272 insertions(+), 274 deletions(-) diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs b/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs index 05b06b3aa..65d8040fd 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs @@ -688,20 +688,17 @@ internal static bool FixStateTypes(HassStateChangedEventData stateData) internal string? GetAreaForEntityId(string entityId) { - HassEntity? entity; - if (_hassEntities.TryGetValue(entityId, out entity) && entity is not null) + if (_hassEntities.TryGetValue(entityId, out HassEntity? entity) && entity is not null) { if (entity.DeviceId is not null) { // The entity is on a device - HassDevice? device; - if (_hassDevices.TryGetValue(entity.DeviceId, out device) && device is not null) + if (_hassDevices.TryGetValue(entity.DeviceId, out HassDevice? device) && device is not null) { if (device.AreaId is not null) { // This device is in an area - HassArea? area; - if (_hassAreas.TryGetValue(device.AreaId, out area) && area is not null) + if (_hassAreas.TryGetValue(device.AreaId, out HassArea? area) && area is not null) { return area.Name; } @@ -747,9 +744,9 @@ internal async Task RefreshInternalStatesAndSetArea() } } - internal IList SortByDependency(IEnumerable unsortedList) + internal static IList SortByDependency(IEnumerable unsortedList) { - if (unsortedList.SelectMany(n => n.Dependencies).Count() > 0) + if (unsortedList.SelectMany(n => n.Dependencies).Any()) { // There are dependecies defined var edges = new HashSet>(); @@ -857,7 +854,7 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok observer.OnError(e); netDaemonRxApp.LogError(e, $"Fail to OnNext on state change observer. {newState.EntityId}:{newState?.State}({oldState?.State})"); } - })); + }, token)); } } } @@ -936,7 +933,7 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok } Logger.LogError(e, "Fail to OnNext on event observer (service_call)"); } - })); + }, token)); } } } @@ -1019,7 +1016,7 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok observer.OnError(e); Logger.LogError(e, "Fail to OnNext on event observer (event)"); } - })); + }, token)); } } } @@ -1130,7 +1127,7 @@ private async Task HandleAsyncServiceCalls(CancellationToken cancellationToken) if (hasLoggedError == false) Logger.LogDebug(e, "Failure sending call service"); hasLoggedError = true; - await Task.Delay(100); // Do a delay to avoid loop + await Task.Delay(100, cancellationToken); // Do a delay to avoid loop } } } @@ -1161,7 +1158,7 @@ private async Task HandleAsyncSetState(CancellationToken cancellationToken) if (hasLoggedError == false) Logger.LogDebug(e, "Failure setting state"); hasLoggedError = true; - await Task.Delay(100); // Do a delay to avoid loop + await Task.Delay(100, cancellationToken); // Do a delay to avoid loop } } } @@ -1179,7 +1176,7 @@ private async Task HandleTextToSpeechMessages(CancellationToken cancellationToke attributes.entity_id = entityId; attributes.message = message; await _hassClient.CallService("tts", "google_cloud_say", attributes, true).ConfigureAwait(false); - await Task.Delay(InternalDelayTimeForTts).ConfigureAwait(false); // Wait 2 seconds to wait for status to complete + await Task.Delay(InternalDelayTimeForTts, cancellationToken).ConfigureAwait(false); // Wait 2 seconds to wait for status to complete EntityState? currentPlayState = GetState(entityId); @@ -1189,7 +1186,7 @@ private async Task HandleTextToSpeechMessages(CancellationToken cancellationToke if (delayInMilliSeconds > 0) { - await Task.Delay(delayInMilliSeconds).ConfigureAwait(false); // Wait remainder of text message + await Task.Delay(delayInMilliSeconds, cancellationToken).ConfigureAwait(false); // Wait remainder of text message } } } @@ -1215,7 +1212,7 @@ private async Task LoadAllApps() // Get all instances var instancedApps = _appInstanceManager.InstanceDaemonApps(); - if (InternalRunningAppInstances.Count() > 0) + if (!InternalRunningAppInstances.IsEmpty) throw new ApplicationException("Did not expect running instances!"); foreach (INetDaemonAppBase appInstance in instancedApps!) @@ -1396,7 +1393,7 @@ public void SubscribeToExternalEvents(Func func) private async Task PostExternalEvent(ExternalEventBase ev) { - if (_externalEventCallSubscribers.Count == 0) + if (_externalEventCallSubscribers.IsEmpty) return; var callbackTaskList = new List(_externalEventCallSubscribers.Count); diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs index dec352453..08026e22b 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs @@ -28,19 +28,19 @@ public class Scheduler : IScheduler /// /// Used to cancel all running tasks /// - private CancellationTokenSource _cancelSource = new CancellationTokenSource(); + private CancellationTokenSource _cancelSource = new(); private readonly ConcurrentDictionary _scheduledTasks - = new ConcurrentDictionary(); + = new(); private readonly IManageTime? _timeManager; private readonly ILogger _logger; - private Task _schedulerTask; + private readonly Task _schedulerTask; public Scheduler(IManageTime? timerManager = null, ILoggerFactory? loggerFactory = null) { _timeManager = timerManager ?? new TimeManager(); - loggerFactory = loggerFactory ?? DefaultLoggerFactory; + loggerFactory ??= DefaultLoggerFactory; _logger = loggerFactory.CreateLogger(); _schedulerTask = Task.Run(SchedulerLoop, _cancelSource.Token); } @@ -144,9 +144,8 @@ public ISchedulerResult RunDaily(string time, IEnumerable? runOnDays, public ISchedulerResult RunDailyAsync(string time, IEnumerable? runOnDays, Func func) { var cancelSource = new CancellationTokenSource(); - DateTime timeOfDayToTrigger; - if (!DateTime.TryParseExact(time, "HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out timeOfDayToTrigger)) + if (!DateTime.TryParseExact(time, "HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime timeOfDayToTrigger)) { throw new FormatException($"{time} is not a valid time for the current locale"); } @@ -222,7 +221,6 @@ private async Task RunEveryMinuteInternalAsync(short second, Func func, Ca while (!linkedCts.IsCancellationRequested) { - var now = _timeManager?.Current; var diff = CalculateEveryMinuteTimeBetweenNowAndTargetTime(second); _logger.LogTrace("RunEveryMinute, Delay {diff}", diff); await _timeManager!.Delay(diff, linkedCts.Token).ConfigureAwait(false); @@ -289,7 +287,7 @@ public async Task Stop() var taskResult = await Task.WhenAny( Task.WhenAll(_scheduledTasks.Values.ToArray()), Task.Delay(1000)).ConfigureAwait(false); - if (_scheduledTasks.Values.Count(n => n.IsCompleted == false) > 0) + if (_scheduledTasks.Values.Any(n => n.IsCompleted == false)) // Todo: Some kind of logging have to be done here to tell user which task caused timeout throw new ApplicationException("Failed to cancel all tasks"); } @@ -309,7 +307,7 @@ private async Task SchedulerLoop() try { while (!_cancelSource.Token.IsCancellationRequested) - if (_scheduledTasks.Count > 0) + if (!_scheduledTasks.IsEmpty) { // Make sure we do cleaning and handle new task every 100 ms ScheduleTask(Task.Delay(DefaultSchedulerTimeout, diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs index fe4a1a0b7..27be1a7c4 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs @@ -40,7 +40,7 @@ public DataRepository(string dataStoragePath) { } #pragma warning disable CS8603, CS8653 - return default(T); + return default; #pragma warning restore CS8603, CS8653 } diff --git a/src/Daemon/NetDaemon.Daemon/Mapping/EntityStateMapper.cs b/src/Daemon/NetDaemon.Daemon/Mapping/EntityStateMapper.cs index a961bcc37..409a77be9 100644 --- a/src/Daemon/NetDaemon.Daemon/Mapping/EntityStateMapper.cs +++ b/src/Daemon/NetDaemon.Daemon/Mapping/EntityStateMapper.cs @@ -37,9 +37,7 @@ private static void MapAttributes(EntityState entityState, HassState hassState) return; // Cast so we can work with the expando object - var dict = entityState.Attribute as IDictionary; - - if (dict == null) + if (entityState.Attribute is not IDictionary dict) throw new ArgumentNullException(nameof(dict), "Expando object should always be dictionary!"); foreach (var (key, value) in hassState.Attributes) diff --git a/src/DaemonRunner/DaemonRunner/Infrastructure/Config/SerilogConfigurator.cs b/src/DaemonRunner/DaemonRunner/Infrastructure/Config/SerilogConfigurator.cs index 336964621..1a5990592 100644 --- a/src/DaemonRunner/DaemonRunner/Infrastructure/Config/SerilogConfigurator.cs +++ b/src/DaemonRunner/DaemonRunner/Infrastructure/Config/SerilogConfigurator.cs @@ -9,7 +9,7 @@ namespace NetDaemon.Infrastructure.Config { public static class SerilogConfigurator { - private static readonly LoggingLevelSwitch LevelSwitch = new LoggingLevelSwitch(); + private static readonly LoggingLevelSwitch LevelSwitch = new(); public static LoggerConfiguration Configure(LoggerConfiguration loggerConfiguration, IHostEnvironment hostingEnvironment) { diff --git a/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs b/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs index 80b78b244..6bf99de91 100644 --- a/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs +++ b/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs @@ -20,7 +20,7 @@ namespace NetDaemon.Service.Api public class ApiWebsocketMiddleware { - private static ConcurrentDictionary _sockets = new(); + private static readonly ConcurrentDictionary _sockets = new(); private readonly RequestDelegate _next; @@ -30,7 +30,7 @@ public class ApiWebsocketMiddleware private readonly NetDaemonHost? _host; - private JsonSerializerOptions _jsonOptions = new JsonSerializerOptions + private readonly JsonSerializerOptions _jsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; @@ -194,7 +194,7 @@ public async Task Invoke(HttpContext context) currentSocket.Dispose(); } - public async Task BroadCast(string message, CancellationToken ct = default(CancellationToken)) + public async Task BroadCast(string message, CancellationToken ct = default) { _logger.LogTrace("Broadcasting to {count} clients", _sockets.Count); foreach (var socket in _sockets) @@ -208,47 +208,43 @@ public async Task Invoke(HttpContext context) } } - private static Task SendStringAsync(WebSocket socket, string data, CancellationToken ct = default(CancellationToken)) + private static Task SendStringAsync(WebSocket socket, string data, CancellationToken ct = default) { var buffer = Encoding.UTF8.GetBytes(data); var segment = new ArraySegment(buffer); return socket.SendAsync(segment, WebSocketMessageType.Text, true, ct); } - private static async Task GetNextMessageAsync(WebSocket socket, CancellationToken ct = default(CancellationToken)) + private static async Task GetNextMessageAsync(WebSocket socket, CancellationToken ct = default) { // System.Text.Json.JsonSerializer.DeserializeAsync(socket.) var buffer = new ArraySegment(new byte[8192]); _ = buffer.Array ?? throw new NullReferenceException("Failed to allocate memory buffer"); - using (var ms = new MemoryStream()) + using var ms = new MemoryStream(); + WebSocketReceiveResult result; + do { - WebSocketReceiveResult result; - do - { - ct.ThrowIfCancellationRequested(); - - result = await socket.ReceiveAsync(buffer, ct); - if (!result.CloseStatus.HasValue) - ms.Write(buffer.Array, buffer.Offset, result.Count); - else - return null; - - } - while (!result.EndOfMessage); + ct.ThrowIfCancellationRequested(); - ms.Seek(0, SeekOrigin.Begin); - if (result.MessageType != WebSocketMessageType.Text) - { + result = await socket.ReceiveAsync(buffer, ct); + if (!result.CloseStatus.HasValue) + ms.Write(buffer.Array, buffer.Offset, result.Count); + else return null; - } - using (var reader = new StreamReader(ms, Encoding.UTF8)) - { - var msgString = await reader.ReadToEndAsync(); - return JsonSerializer.Deserialize(msgString); - } } + while (!result.EndOfMessage); + + ms.Seek(0, SeekOrigin.Begin); + if (result.MessageType != WebSocketMessageType.Text) + { + return null; + } + + using var reader = new StreamReader(ms, Encoding.UTF8); + var msgString = await reader.ReadToEndAsync(); + return JsonSerializer.Deserialize(msgString); } } diff --git a/src/DaemonRunner/DaemonRunner/Service/ApiService.cs b/src/DaemonRunner/DaemonRunner/Service/ApiService.cs index 2b92c3295..6c72b1e1b 100644 --- a/src/DaemonRunner/DaemonRunner/Service/ApiService.cs +++ b/src/DaemonRunner/DaemonRunner/Service/ApiService.cs @@ -22,22 +22,20 @@ namespace NetDaemon.Service { public class ApiStartup { - bool _useAdmin = false; + readonly bool _useAdmin = false; public ApiStartup(IConfiguration configuration) { Configuration = configuration; var enableAdminValue = Configuration.GetSection("NetDaemon").GetSection("Admin").Value; - bool.TryParse(enableAdminValue, out _useAdmin); + _ = bool.TryParse(enableAdminValue, out _useAdmin); } public IConfiguration Configuration { get; } - public void ConfigureServices(IServiceCollection services) + public static void ConfigureServices(IServiceCollection services) { - // services.Configure(Context.Configuration.GetSection("HomeAssistant")); - // services.Configure(context.Configuration.GetSection("NetDaemon")); services.AddHostedService(); services.AddTransient(); services.AddTransient(n => new DataRepository( diff --git a/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs b/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs index b39eebb3e..9f1bdd51f 100644 --- a/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs +++ b/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs @@ -20,7 +20,7 @@ public class CodeGenerator /// Mapps the domain to corresponding implemented Fluent API, will be added as /// more and more entity types are supported /// - private static IDictionary _FluentApiMapper = new Dictionary + private static readonly IDictionary _FluentApiMapper = new Dictionary { ["light"] = ("Entity", "IEntity"), ["script"] = ("Entity", "IEntity"), @@ -34,7 +34,7 @@ public class CodeGenerator // ["climate"], }; - public string? GenerateCode(string nameSpace, IEnumerable entities) + public static string? GenerateCode(string nameSpace, IEnumerable entities) { var code = SyntaxFactory.CompilationUnit(); @@ -112,7 +112,7 @@ public class CodeGenerator return code.NormalizeWhitespace(indentation: " ", eol: "\n").ToFullString(); } - public string? GenerateCodeRx(string nameSpace, IEnumerable entities, IEnumerable services) + public static string? GenerateCodeRx(string nameSpace, IEnumerable entities, IEnumerable services) { var code = SyntaxFactory.CompilationUnit(); @@ -143,7 +143,7 @@ public class CodeGenerator { var camelCaseDomain = domain.ToCamelCase(); - var isSingleServiceDomain = Array.IndexOf(singleServiceDomains, domain) == 0 ? false : true; + var isSingleServiceDomain = Array.IndexOf(singleServiceDomains, domain) != 0; var property = isSingleServiceDomain ? $@"public {camelCaseDomain}Entities {camelCaseDomain} => new {camelCaseDomain}Entities(this);" : @@ -197,7 +197,7 @@ public class CodeGenerator { name = "s_" + name; } - var hasEntityId = (s.Fields is not null && s.Fields.Count(c => c.Field == "entity_id") > 0) ? true : false; + var hasEntityId = s.Fields is not null && s.Fields.Any(c => c.Field == "entity_id"); var entityAssignmentStatement = hasEntityId ? @"serviceData[""entity_id""] = EntityId;" : ""; var methodCode = $@"public void {name.ToCamelCase()}(dynamic? data=null) diff --git a/src/DaemonRunner/DaemonRunner/Service/App/DaemonAppCompiler.cs b/src/DaemonRunner/DaemonRunner/Service/App/DaemonAppCompiler.cs index 445700e4f..c6dfc5f62 100644 --- a/src/DaemonRunner/DaemonRunner/Service/App/DaemonAppCompiler.cs +++ b/src/DaemonRunner/DaemonRunner/Service/App/DaemonAppCompiler.cs @@ -16,7 +16,7 @@ public class DaemonAppCompiler : IDaemonAppCompiler private readonly ILogger _logger; private readonly IOptions _netDaemonSettings; - private string? _sourceFolder = null; + private readonly string? _sourceFolder = null; public DaemonAppCompiler(ILogger logger, IOptions netDaemonSettings) { _logger = logger; @@ -25,6 +25,8 @@ public DaemonAppCompiler(ILogger logger, IOptions NetDaemonSettings => _netDaemonSettings; + public IEnumerable GetApps() { _logger.LogDebug("Loading dynamically compiled apps..."); @@ -41,8 +43,7 @@ public IEnumerable GetApps() public Assembly Load() { - CollectibleAssemblyLoadContext alc; - return DaemonCompiler.GetCompiledAppAssembly(out alc, _sourceFolder!, _logger); + return DaemonCompiler.GetCompiledAppAssembly(out _, _sourceFolder!, _logger); } } } \ No newline at end of file diff --git a/src/DaemonRunner/DaemonRunner/Service/App/DaemonCompiler.cs b/src/DaemonRunner/DaemonRunner/Service/App/DaemonCompiler.cs index 815104cbd..f8e24aa29 100644 --- a/src/DaemonRunner/DaemonRunner/Service/App/DaemonCompiler.cs +++ b/src/DaemonRunner/DaemonRunner/Service/App/DaemonCompiler.cs @@ -39,9 +39,8 @@ public static (IEnumerable, CollectibleAssemblyLoadContext?) GetDaemonApps { var loadedApps = new List(50); - CollectibleAssemblyLoadContext alc; // Load the compiled apps - var (compiledApps, compileErrorText) = GetCompiledApps(out alc, codeFolder, logger); + var (compiledApps, compileErrorText) = GetCompiledApps(out CollectibleAssemblyLoadContext alc, codeFolder, logger); if (compiledApps is not null) loadedApps.AddRange(compiledApps); @@ -79,22 +78,20 @@ public static Assembly GetCompiledAppAssembly(out CollectibleAssemblyLoadContext InterceptAppInfo(syntaxTree, compilation); } - using (var peStream = new MemoryStream()) - { - var emitResult = compilation.Emit(peStream: peStream); + using var peStream = new MemoryStream(); + var emitResult = compilation.Emit(peStream: peStream); - if (emitResult.Success) - { - peStream.Seek(0, SeekOrigin.Begin); + if (emitResult.Success) + { + peStream.Seek(0, SeekOrigin.Begin); - return alc!.LoadFromStream(peStream); - } - else - { - PrettyPrintCompileError(emitResult, logger); + return alc!.LoadFromStream(peStream); + } + else + { + PrettyPrintCompileError(emitResult, logger); - return null!; - } + return null!; } } finally @@ -115,12 +112,10 @@ private static List LoadSyntaxTree(string codeFolder) foreach (var csFile in csFiles) { - using (var fs = new FileStream(csFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - var sourceText = SourceText.From(fs, encoding: Encoding.UTF8, canBeEmbedded: true); - var syntaxTree = SyntaxFactory.ParseSyntaxTree(sourceText, path: csFile); - result.Add(syntaxTree); - } + using var fs = new FileStream(csFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + var sourceText = SourceText.From(fs, encoding: Encoding.UTF8, canBeEmbedded: true); + var syntaxTree = SyntaxFactory.ParseSyntaxTree(sourceText, path: csFile); + result.Add(syntaxTree); } return result; @@ -300,18 +295,18 @@ private static void WarnIfExecuteIsMissing(SyntaxTree syntaxTree, CSharpCompilat while (parentInvocationExpression is not null) { - if (parentInvocationExpression is MethodDeclarationSyntax) + if (parentInvocationExpression is MethodDeclarationSyntax methodDeclarationSyntax) { - if (ExpressionContainsDisableLogging((MethodDeclarationSyntax)parentInvocationExpression)) + if (ExpressionContainsDisableLogging(methodDeclarationSyntax)) { disableLogging = true; } } - if (parentInvocationExpression is InvocationExpressionSyntax) + if (parentInvocationExpression is InvocationExpressionSyntax invocationExpressionSyntax) { var parentSymbol = (IMethodSymbol?)semModel?.GetSymbolInfo(invocationExpression).Symbol; if (parentSymbol?.Name == symbolName) - topInvocationExpression = (InvocationExpressionSyntax)parentInvocationExpression; + topInvocationExpression = invocationExpressionSyntax; } parentInvocationExpression = parentInvocationExpression.Parent; } diff --git a/src/DaemonRunner/DaemonRunner/Service/App/IDaemonAppCompiler.cs b/src/DaemonRunner/DaemonRunner/Service/App/IDaemonAppCompiler.cs index f4a6498ea..7f737d7ae 100644 --- a/src/DaemonRunner/DaemonRunner/Service/App/IDaemonAppCompiler.cs +++ b/src/DaemonRunner/DaemonRunner/Service/App/IDaemonAppCompiler.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Reflection; namespace NetDaemon.Service.App { @@ -10,7 +9,6 @@ public interface IDaemonAppCompiler /// Temporary /// /// - [Obsolete("Only exists while migrating the world to IOC.")] IEnumerable GetApps(); } } \ No newline at end of file diff --git a/src/DaemonRunner/DaemonRunner/Service/App/LocalDaemonAppCompiler.cs b/src/DaemonRunner/DaemonRunner/Service/App/LocalDaemonAppCompiler.cs index ddd6027ad..a6beda61c 100644 --- a/src/DaemonRunner/DaemonRunner/Service/App/LocalDaemonAppCompiler.cs +++ b/src/DaemonRunner/DaemonRunner/Service/App/LocalDaemonAppCompiler.cs @@ -29,7 +29,7 @@ public IEnumerable GetApps() if (!apps.Any()) _logger.LogWarning("No local daemon apps found."); else - _logger.LogDebug("Found total of {nr_of_apps} apps", apps.Count()); + _logger.LogDebug("Found total of {nr_of_apps} apps", apps.Count); return apps; } diff --git a/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs b/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs index ce2e16e51..0ca62e89b 100644 --- a/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs +++ b/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs @@ -212,7 +212,7 @@ private async Task GenerateEntities(NetDaemonHost daemonHost, string sourceFolde _entitiesGenerated = true; var codeGen = new CodeGenerator(); - var source = codeGen.GenerateCode( + var source = CodeGenerator.GenerateCode( "Netdaemon.Generated.Extensions", daemonHost.State.Select(n => n.EntityId).Distinct() ); @@ -220,7 +220,7 @@ private async Task GenerateEntities(NetDaemonHost daemonHost, string sourceFolde await File.WriteAllTextAsync(Path.Combine(sourceFolder!, "_EntityExtensions.cs.gen"), source).ConfigureAwait(false); var services = await daemonHost.GetAllServices().ConfigureAwait(false); - var sourceRx = codeGen.GenerateCodeRx( + var sourceRx = CodeGenerator.GenerateCodeRx( "Netdaemon.Generated.Reactive", daemonHost.State.Select(n => n.EntityId).Distinct(), services diff --git a/src/Fakes/NetDaemon.Fakes/HassClientMock.cs b/src/Fakes/NetDaemon.Fakes/HassClientMock.cs index a96fa7575..ed8b2618f 100644 --- a/src/Fakes/NetDaemon.Fakes/HassClientMock.cs +++ b/src/Fakes/NetDaemon.Fakes/HassClientMock.cs @@ -21,18 +21,18 @@ public class HassClientMock : Mock /// Fake areas in HassClient /// /// - public HassAreas Areas = new HassAreas(); + public HassAreas Areas = new(); /// /// Fake devices in HassClient /// /// - public HassDevices Devices = new HassDevices(); + public HassDevices Devices = new(); /// /// Fake entities in HassClient /// - public HassEntities Entities = new HassEntities(); + public HassEntities Entities = new(); /// /// Fake events in HassClient @@ -101,7 +101,7 @@ public HassClientMock() /// /// Default instance of mock /// - public static HassClientMock DefaultMock => new HassClientMock(); + public static HassClientMock DefaultMock => new(); /// /// Returns a mock that will always return false when connect to Home Assistant @@ -254,7 +254,7 @@ public void AddCustomEvent(string eventType, dynamic? data) /// /// HassClient state instance /// NetDaemon state instance - public void AssertEqual(HassState hassState, EntityState entity) + public static void AssertEqual(HassState hassState, EntityState entity) { Assert.Equal(hassState.EntityId, entity.EntityId); Assert.Equal(hassState.State, entity.State); @@ -280,7 +280,7 @@ public void AssertEqual(HassState hassState, EntityState entity) /// /// /// - public CancellationTokenSource GetSourceWithTimeout(int milliSeconds = 100) + public static CancellationTokenSource GetSourceWithTimeout(int milliSeconds = 100) { return (Debugger.IsAttached) ? new CancellationTokenSource() @@ -297,8 +297,8 @@ public void VerifyCallServiceTuple(string domain, string service, params (string attribute, object value)[] attributesTuples) { var attributes = new FluentExpandoObject(); - foreach (var attributesTuple in attributesTuples) - ((IDictionary)attributes)[attributesTuple.attribute] = attributesTuple.value; + foreach (var (attribute, value) in attributesTuples) + ((IDictionary)attributes)[attribute] = value; Verify(n => n.CallService(domain, service, attributes, It.IsAny()), Times.AtLeastOnce); } @@ -357,8 +357,8 @@ public void VerifySetState(string entity, string state, params (string attribute, object value)[] attributesTuples) { var attributes = new FluentExpandoObject(); - foreach (var attributesTuple in attributesTuples) - ((IDictionary)attributes)[attributesTuple.attribute] = attributesTuple.value; + foreach (var (attribute, value) in attributesTuples) + ((IDictionary)attributes)[attribute] = value; Verify(n => n.SetState(entity, state, attributes), Times.AtLeastOnce); } diff --git a/src/Fakes/NetDaemon.Fakes/HttpHandlerMock.cs b/src/Fakes/NetDaemon.Fakes/HttpHandlerMock.cs index 5c3595dc7..f286ad0b4 100644 --- a/src/Fakes/NetDaemon.Fakes/HttpHandlerMock.cs +++ b/src/Fakes/NetDaemon.Fakes/HttpHandlerMock.cs @@ -110,7 +110,7 @@ protected override async Task SendAsync(HttpRequestMessage var responseMessage = new HttpResponseMessage(_StatusCode); if (request is not null && request.Content is not null) - _requestContent = await request.Content.ReadAsStringAsync().ConfigureAwait(false); + _requestContent = await request.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); responseMessage.Content = new ByteArrayContent(Encoding.ASCII.GetBytes(_response)); return responseMessage; diff --git a/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs b/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs index 3f17c1bfa..530100290 100644 --- a/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs @@ -70,7 +70,6 @@ public async Task RepositoryLoadSavedDataUsingExpando() // ARRANGE var dataRepository = new DataRepository(DataReposityryPath); dynamic dataBeingSaved = new FluentExpandoObject(false, true); - var now = DateTime.Now; dataBeingSaved.SomeString = "this data should be saved!"; dataBeingSaved.SomeInt = 123456; dataBeingSaved.SomeFloat = 1.23456; @@ -84,8 +83,6 @@ public async Task RepositoryLoadSavedDataUsingExpando() returnedFluentExpandoObject.CopyFrom(dataReturned!); dynamic dynamicDataReturned = returnedFluentExpandoObject; - var x = dataBeingSaved.SomeString; - var y = dynamicDataReturned!.SomeString; if (dataBeingSaved.SomeString == dynamicDataReturned?.SomeString) { diff --git a/tests/NetDaemon.Daemon.Tests/Daemon/HttpTests.cs b/tests/NetDaemon.Daemon.Tests/Daemon/HttpTests.cs index 7f3be7b0e..81f89d49d 100644 --- a/tests/NetDaemon.Daemon.Tests/Daemon/HttpTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Daemon/HttpTests.cs @@ -84,7 +84,7 @@ public async Task HttpHandlerGetJsonShouldReturnCorrectContent() // ARRANGE var response = "{\"json_prop\": \"hello world\"}"; - HttpClientFactoryMock factoryMock = new HttpClientFactoryMock(); + HttpClientFactoryMock factoryMock = new(); factoryMock.SetResponse(response); var httpHandler = new HttpHandler(factoryMock.Object); @@ -102,7 +102,7 @@ public void HttpHandlerGetJsonBadFormatShouldReturnThrowException() // ARRANGE var response = "{\"json_prop\": \"hello world\"}"; - HttpClientFactoryMock factoryMock = new HttpClientFactoryMock(); + HttpClientFactoryMock factoryMock = new(); factoryMock.SetResponse(response); var httpHandler = new HttpHandler(factoryMock.Object); @@ -117,7 +117,7 @@ public async Task HttpHandlerPostJsonShouldReturnCorrectContent() // ARRANGE var response = "{\"json_prop\": \"hello world\"}"; - HttpClientFactoryMock factoryMock = new HttpClientFactoryMock(); + HttpClientFactoryMock factoryMock = new(); factoryMock.SetResponse(response); var httpHandler = new HttpHandler(factoryMock.Object); @@ -136,7 +136,7 @@ public async Task HttpHandlerPostJsonNoReturnShouldReturnCorrectContent() // ARRANGE var response = "{\"json_prop\": \"hello world\"}"; - HttpClientFactoryMock factoryMock = new HttpClientFactoryMock(); + HttpClientFactoryMock factoryMock = new(); factoryMock.SetResponse(response); var httpHandler = new HttpHandler(factoryMock.Object); diff --git a/tests/NetDaemon.Daemon.Tests/Daemon/NetDaemonHostTests.cs b/tests/NetDaemon.Daemon.Tests/Daemon/NetDaemonHostTests.cs index 12abad9dc..8e0b09bdd 100644 --- a/tests/NetDaemon.Daemon.Tests/Daemon/NetDaemonHostTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Daemon/NetDaemonHostTests.cs @@ -8,6 +8,7 @@ using NetDaemon.Common; using Xunit; using NetDaemon.Daemon.Fakes; +using NetDaemon.Daemon.Tests.DaemonRunner.App; namespace NetDaemon.Daemon.Tests.Daemon { @@ -55,9 +56,10 @@ public async Task AttributeServiceCallShouldFindCorrectFunction() { // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); - var app = new AssmeblyDaemonApp(); - app.Id = "id"; - + var app = new AssmeblyDaemonApp + { + Id = "id" + }; DefaultDaemonHost.InternalRunningAppInstances[app.Id] = app; @@ -224,7 +226,7 @@ public async Task SpeakShouldWaitUntilMediaPlays() await InitializeFakeDaemon(500).ConfigureAwait(false); // Expected data call service - var (expectedAttruibutes, expectedAttributesExpObject) = GetDynamicObject( + var (_, expectedAttributesExpObject) = GetDynamicObject( ("entity_id", "media_player.fakeplayer"), ("message", "Hello test!") ); @@ -634,7 +636,7 @@ public async Task SetStateShouldKeepSameArea() public void FixStateTypesShouldReturnCorrectValues( bool result, dynamic? newState, dynamic? oldState, dynamic? expectedNewState, dynamic? expectedOldState) { - HassStateChangedEventData state = new HassStateChangedEventData + HassStateChangedEventData state = new() { NewState = new HassState { diff --git a/tests/NetDaemon.Daemon.Tests/DaemonRunner/Api/ApiTests.cs b/tests/NetDaemon.Daemon.Tests/DaemonRunner/Api/ApiTests.cs index 4fed71b5e..58701b968 100644 --- a/tests/NetDaemon.Daemon.Tests/DaemonRunner/Api/ApiTests.cs +++ b/tests/NetDaemon.Daemon.Tests/DaemonRunner/Api/ApiTests.cs @@ -54,22 +54,28 @@ public ApiFakeStartup(IConfiguration configuration) _loggerMock.LoggerFactory, _defaultHttpHandlerMock.Object); - _defaultDaemonApp = new BaseTestApp(); - _defaultDaemonApp.Id = "app_id"; - _defaultDaemonApp.IsEnabled = true; + _defaultDaemonApp = new BaseTestApp + { + Id = "app_id", + IsEnabled = true + }; _defaultDaemonHost.InternalRunningAppInstances[_defaultDaemonApp.Id!] = _defaultDaemonApp; _defaultDaemonHost.InternalAllAppInstances[_defaultDaemonApp.Id!] = _defaultDaemonApp; - _defaultDaemonApp2 = new BaseTestApp(); - _defaultDaemonApp2.Id = "app_id2"; + _defaultDaemonApp2 = new BaseTestApp + { + Id = "app_id2" + }; _defaultDaemonApp2.RuntimeInfo.NextScheduledEvent = DateTime.Now; _defaultDaemonApp2.IsEnabled = false; _defaultDaemonHost.InternalRunningAppInstances[_defaultDaemonApp2.Id!] = _defaultDaemonApp2; _defaultDaemonHost.InternalAllAppInstances[_defaultDaemonApp2.Id!] = _defaultDaemonApp2; - _defaultDaemonRxApp = new BaseTestRxApp(); - _defaultDaemonRxApp.Id = "app_rx_id"; - _defaultDaemonRxApp.IsEnabled = true; + _defaultDaemonRxApp = new BaseTestRxApp + { + Id = "app_rx_id", + IsEnabled = true + }; _defaultDaemonRxApp.RuntimeInfo.NextScheduledEvent = DateTime.Now; _defaultDaemonHost.InternalRunningAppInstances[_defaultDaemonRxApp.Id!] = _defaultDaemonRxApp; _defaultDaemonHost.InternalAllAppInstances[_defaultDaemonRxApp.Id!] = _defaultDaemonRxApp; @@ -93,9 +99,8 @@ public void ConfigureServices(IServiceCollection services) services.AddHttpClient(); } - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + public static void Configure(IApplicationBuilder app, IWebHostEnvironment _) { - var webSocketOptions = new WebSocketOptions() { KeepAliveInterval = TimeSpan.FromSeconds(120) @@ -113,11 +118,13 @@ public class ApiTests : IAsyncLifetime private readonly ArraySegment _buffer; - private JsonSerializerOptions _jsonOptions = new JsonSerializerOptions + private readonly JsonSerializerOptions _jsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + public ArraySegment Buffer => _buffer; + public ApiTests() { _buffer = new ArraySegment(new byte[8192]); @@ -139,32 +146,28 @@ public Task InitializeAsync() return Task.CompletedTask; } - private async Task ReadString(WebSocket ws) + private static async Task ReadString(WebSocket ws) { var buffer = new ArraySegment(new byte[8192]); _ = buffer.Array ?? throw new NullReferenceException("Failed to allocate memory buffer"); - using (var ms = new MemoryStream()) + using var ms = new MemoryStream(); + WebSocketReceiveResult result; + do + { + result = await ws.ReceiveAsync(buffer, CancellationToken.None); + ms.Write(buffer.Array, buffer.Offset, result.Count); + } + while (!result.EndOfMessage); + + ms.Seek(0, SeekOrigin.Begin); + if (result.MessageType != WebSocketMessageType.Text) { - WebSocketReceiveResult result; - do - { - result = await ws.ReceiveAsync(buffer, CancellationToken.None); - ms.Write(buffer.Array, buffer.Offset, result.Count); - } - while (!result.EndOfMessage); - - ms.Seek(0, SeekOrigin.Begin); - if (result.MessageType != WebSocketMessageType.Text) - { - throw new Exception("Unexpected type"); - } - - using (var reader = new StreamReader(ms, Encoding.UTF8)) - { - return await reader.ReadToEndAsync(); - } + throw new Exception("Unexpected type"); } + + using var reader = new StreamReader(ms, Encoding.UTF8); + return await reader.ReadToEndAsync(); } private async Task GetWsClient() diff --git a/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/AssemblyDaemonApps.cs b/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/AssemblyDaemonApps.cs index 3e70f194d..e6a678b0d 100644 --- a/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/AssemblyDaemonApps.cs +++ b/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/AssemblyDaemonApps.cs @@ -2,43 +2,47 @@ using System.Threading.Tasks; using NetDaemon.Common; -/// -/// Greets (or insults) people when coming home :) -/// -public class AssmeblyDaemonApp : NetDaemonApp +namespace NetDaemon.Daemon.Tests.DaemonRunner.App { - #region -- Test config -- - public string? StringConfig { get; set; } = null; - public int? IntConfig { get; set; } = null; - public IEnumerable? EnumerableConfig { get; set; } = null; + /// + /// Greets (or insults) people when coming home :) + /// + public class AssmeblyDaemonApp : NetDaemon.Common.NetDaemonApp + { + #region -- Test config -- - #endregion -- Test config -- + public string? StringConfig { get; set; } = null; + public int? IntConfig { get; set; } = null; + public IEnumerable? EnumerableConfig { get; set; } = null; - #region -- Test secrets -- + #endregion -- Test config -- - public string? TestSecretString { get; set; } - public int? TestSecretInt { get; set; } + #region -- Test secrets -- - public string? TestNormalString { get; set; } - public int? TestNormalInt { get; set; } + public string? TestSecretString { get; set; } + public int? TestSecretInt { get; set; } - #endregion -- Test secrets -- + public string? TestNormalString { get; set; } + public int? TestNormalInt { get; set; } - // For testing - public bool HandleServiceCallIsCalled { get; set; } = false; + #endregion -- Test secrets -- - public override Task InitializeAsync() - { - // Do nothing + // For testing + public bool HandleServiceCallIsCalled { get; set; } = false; - return Task.CompletedTask; - } + public override Task InitializeAsync() + { + // Do nothing - [HomeAssistantServiceCall] - public Task HandleServiceCall(dynamic data) - { - HandleServiceCallIsCalled = true; - return Task.CompletedTask; + return Task.CompletedTask; + } + + [HomeAssistantServiceCall] + public Task HandleServiceCall(dynamic _) + { + HandleServiceCallIsCalled = true; + return Task.CompletedTask; + } } } \ No newline at end of file diff --git a/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/CodeGenTests.cs b/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/CodeGenTests.cs index f2fb5f295..53c51ebd0 100644 --- a/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/CodeGenTests.cs +++ b/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/CodeGenTests.cs @@ -10,9 +10,8 @@ public class CodeGenerationTests public void TestGenerateCode() { // ARRANGE - var x = new CodeGenerator(); // ACT - var code = x.GenerateCode("Netdaemon.Generated.Extensions", new string[] { "light.koket_fonster", "media_player.my_player" }); + var code = CodeGenerator.GenerateCode("Netdaemon.Generated.Extensions", new string[] { "light.koket_fonster", "media_player.my_player" }); // ASSERT diff --git a/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/DaemonAppTests.cs b/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/DaemonAppTests.cs index 03434c17b..998289066 100644 --- a/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/DaemonAppTests.cs +++ b/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/DaemonAppTests.cs @@ -24,11 +24,11 @@ public class AppTests public static readonly string FaultyAppPath = Path.Combine(AppContext.BaseDirectory, "DaemonRunner", "FaultyApp"); - public string GetFixtureContent(string filename) => File.ReadAllText(Path.Combine(AppTests.ConfigFixturePath, filename)); + public static string GetFixtureContent(string filename) => File.ReadAllText(Path.Combine(AppTests.ConfigFixturePath, filename)); - public string GetFixturePath(string filename) => Path.Combine(AppTests.ConfigFixturePath, filename); + public static string GetFixturePath(string filename) => Path.Combine(AppTests.ConfigFixturePath, filename); - public IOptions CreateSettings(string appSource) => new OptionsWrapper(new NetDaemonSettings + public static IOptions CreateSettings(string appSource) => new OptionsWrapper(new NetDaemonSettings { AppSource = appSource }); @@ -44,7 +44,7 @@ public void FaultyApplicationShouldLogError() var netDaemonSettings = CreateSettings(path); // ACT - var cm = new CodeManager(daemonApps, loggerMock.Logger, new YamlConfig(netDaemonSettings)); + _ = new CodeManager(daemonApps, loggerMock.Logger, new YamlConfig(netDaemonSettings)); // ASSERT loggerMock.AssertLogged(LogLevel.Error, Times.AtLeastOnce()); @@ -91,7 +91,7 @@ public void InstanceAppFromConfigShouldReturnCorrectType() .Returns(new[] { Path.Combine(ConfigFixturePath, "level2", "level3") }); IEnumerable types = new List() { typeof(AssmeblyDaemonApp) }; - var yamlConfig = "app:\n class: AssmeblyDaemonApp"; + var yamlConfig = "app:\n class: NetDaemon.Daemon.Tests.DaemonRunner.App.AssmeblyDaemonApp"; // ACT var instances = new YamlAppConfig(types, new StringReader(yamlConfig), yamlConfigMock.Object, "").Instances; // ASSERT @@ -130,7 +130,7 @@ public void InstanceAppFromConfigShouldHaveCorrectProperties() IEnumerable types = new List() { typeof(AssmeblyDaemonApp) }; var yamlConfig = @" app: - class: AssmeblyDaemonApp + class: NetDaemon.Daemon.Tests.DaemonRunner.App.AssmeblyDaemonApp StringConfig: a string IntConfig: 10 EnumerableConfig: @@ -155,7 +155,7 @@ public void InstanceAppFromConfigWithSecretsShouldHaveCorrectProperties() IEnumerable types = new List() { typeof(AssmeblyDaemonApp) }; var yamlConfig = @" app: - class: AssmeblyDaemonApp + class: NetDaemon.Daemon.Tests.DaemonRunner.App.AssmeblyDaemonApp test_secret_string: !secret a_secret_string test_secret_int: !secret a_secret_int test_normal_string: not a secret string @@ -186,7 +186,7 @@ public void InstanceAppFromConfigShouldHaveCorrectPropertiesCamelCaseConvert() IEnumerable types = new List() { typeof(AssmeblyDaemonApp) }; var yamlConfig = @" app: - class: AssmeblyDaemonApp + class: NetDaemon.Daemon.Tests.DaemonRunner.App.AssmeblyDaemonApp string_config: a string int_config: 10 enumerable_config: @@ -247,7 +247,7 @@ public async Task StorageShouldReturnSameValueAsSet(object data) IEnumerable types = new List() { typeof(AssmeblyDaemonApp) }; var yamlConfig = @" app: - class: AssmeblyDaemonApp + class: NetDaemon.Daemon.Tests.DaemonRunner.App.AssmeblyDaemonApp "; var daemonMock = new Mock(); @@ -282,15 +282,17 @@ public async Task StorageShouldRestoreWithCorrectValues() IEnumerable types = new List() { typeof(AssmeblyDaemonApp) }; var yamlConfig = @" app: - class: AssmeblyDaemonApp + class: NetDaemon.Daemon.Tests.DaemonRunner.App.AssmeblyDaemonApp "; await using var instance = new YamlAppConfig(types, new StringReader(yamlConfig), yamlConfigMock.Object, "").Instances.FirstOrDefault() as AssmeblyDaemonApp; var daemonMock = new Mock(); daemonMock.SetupGet(x => x.Logger).Returns(new Mock().Object); - var storageItem = new FluentExpandoObject(); - storageItem["Data"] = "SomeData"; + var storageItem = new FluentExpandoObject + { + ["Data"] = "SomeData" + }; daemonMock.SetupGet(x => x.Logger).Returns(new Mock().Object); daemonMock.Setup(n => n.GetDataAsync>(It.IsAny())) @@ -359,13 +361,12 @@ public void InsanceAppsThatHasMissingExecuteShouldLogError() { // ARRANGE var path = Path.Combine(FaultyAppPath, "ExecuteWarnings"); - var moqDaemon = new Mock(); var moqLogger = new LoggerMock(); var (daemonApps, _) = DaemonCompiler.GetDaemonApps(path, moqLogger.Logger); var configMock = new Mock(); // ACT - var codeManager = new CodeManager(daemonApps, moqLogger.Logger, configMock.Object); + _ = new CodeManager(daemonApps, moqLogger.Logger, configMock.Object); // ASSERT moqLogger.AssertLogged(LogLevel.Error, Times.Exactly(13)); @@ -376,19 +377,18 @@ public void InsanceAppsThatHasMissingExecuteAndSupressLogsShouldNotLogError() { // ARRANGE var path = Path.Combine(FaultyAppPath, "SupressLogs"); - var moqDaemon = new Mock(); var moqLogger = new LoggerMock(); var (daemonApps, _) = DaemonCompiler.GetDaemonApps(path, moqLogger.Logger); var configMock = new Mock(); // ACT - var codeManager = new CodeManager(daemonApps, moqLogger.Logger, configMock.Object); + _ = new CodeManager(daemonApps, moqLogger.Logger, configMock.Object); // ASSERT moqLogger.AssertLogged(LogLevel.Error, Times.Exactly(1)); } - public CodeManager CM(string path) + public static CodeManager CM(string path) { var loggerMock = new LoggerMock().Logger; var config = new YamlConfig(CreateSettings(path)); diff --git a/tests/NetDaemon.Daemon.Tests/DaemonRunner/Config/AppComplexConfig.cs b/tests/NetDaemon.Daemon.Tests/DaemonRunner/Config/AppComplexConfig.cs index 4103d362f..420f578d2 100644 --- a/tests/NetDaemon.Daemon.Tests/DaemonRunner/Config/AppComplexConfig.cs +++ b/tests/NetDaemon.Daemon.Tests/DaemonRunner/Config/AppComplexConfig.cs @@ -23,12 +23,12 @@ public override Task InitializeAsync() public class Device { - public string? name { get; set; } - public IEnumerable? commands { get; set; } + public string? Name { get; set; } + public IEnumerable? Commands { get; set; } } public class Command { - public string? name { get; set; } - public string? data { get; set; } + public string? Name { get; set; } + public string? Data { get; set; } } } \ No newline at end of file diff --git a/tests/NetDaemon.Daemon.Tests/DaemonRunner/Config/ConfigTest.cs b/tests/NetDaemon.Daemon.Tests/DaemonRunner/Config/ConfigTest.cs index e23653452..821d9bfad 100644 --- a/tests/NetDaemon.Daemon.Tests/DaemonRunner/Config/ConfigTest.cs +++ b/tests/NetDaemon.Daemon.Tests/DaemonRunner/Config/ConfigTest.cs @@ -16,11 +16,11 @@ public class YamlTests public static readonly string ConfigFixturePath = Path.Combine(AppContext.BaseDirectory, "DaemonRunner", "Fixtures"); - public string GetFixtureContent(string filename) => File.ReadAllText(Path.Combine(YamlTests.ConfigFixturePath, filename)); + public static string GetFixtureContent(string filename) => File.ReadAllText(Path.Combine(YamlTests.ConfigFixturePath, filename)); - public string GetFixturePath(string filename) => Path.Combine(YamlTests.ConfigFixturePath, filename); + public static string GetFixturePath(string filename) => Path.Combine(YamlTests.ConfigFixturePath, filename); - public IOptions CreateSettings(string appSource) => new OptionsWrapper(new NetDaemonSettings + public static IOptions CreateSettings(string appSource) => new OptionsWrapper(new NetDaemonSettings { AppSource = appSource }); @@ -109,7 +109,6 @@ public void YamlScalarNodeToObjectUsingString() var scalar = root.Children.First(); - string? map = ((YamlScalarNode)scalar.Key).Value; var scalarValue = (YamlScalarNode)scalar.Value; // ACT & ASSERT Assert.Equal("string", scalarValue.ToObject(typeof(string))); @@ -126,7 +125,6 @@ public void YamlScalarNodeToObjectUsingInt() var scalar = root.Children.First(); - string? map = ((YamlScalarNode)scalar.Key).Value; var scalarValue = (YamlScalarNode)scalar.Value; // ACT & ASSERT Assert.Equal(1234, scalarValue.ToObject(typeof(int))); @@ -143,7 +141,6 @@ public void YamlScalarNodeToObjectUsingBoolean() var scalar = root.Children.First(); - string? map = ((YamlScalarNode)scalar.Key).Value; var scalarValue = (YamlScalarNode)scalar.Value; // ACT & ASSERT Assert.Equal(true, scalarValue.ToObject(typeof(bool))); @@ -160,7 +157,6 @@ public void YamlScalarNodeToObjectUsingLong() var scalar = root.Children.First(); - string? map = ((YamlScalarNode)scalar.Key).Value; var scalarValue = (YamlScalarNode)scalar.Value; // ACT & ASSERT Assert.Equal((long)1234, scalarValue.ToObject(typeof(long))); @@ -177,7 +173,6 @@ public void YamlScalarNodeToObjectUsingDeciaml() var scalar = root.Children.First(); - string? map = ((YamlScalarNode)scalar.Key).Value; var scalarValue = (YamlScalarNode)scalar.Value; // ACT & ASSERT Assert.Equal((decimal)1.5, scalarValue.ToObject(typeof(decimal))); @@ -223,11 +218,11 @@ public void YamlAdvancedObjectsShouldReturnCorrectData() Assert.Equal(true, instance?.ABool); Assert.NotNull(instance?.Devices); Assert.Equal(1, instance?.Devices?.Count()); - Assert.Equal("tv", instance?.Devices?.First().name); - Assert.Equal("command1", instance?.Devices?.First()?.commands?.ElementAt(0).name); - Assert.Equal("some code", instance?.Devices?.First()?.commands?.ElementAt(0).data); - Assert.Equal("command2", instance?.Devices?.First()?.commands?.ElementAt(1).name); - Assert.Equal("some code2", instance?.Devices?.First()?.commands?.ElementAt(1).data); + Assert.Equal("tv", instance?.Devices?.First().Name); + Assert.Equal("command1", instance?.Devices?.First()?.Commands?.ElementAt(0).Name); + Assert.Equal("some code", instance?.Devices?.First()?.Commands?.ElementAt(0).Data); + Assert.Equal("command2", instance?.Devices?.First()?.Commands?.ElementAt(1).Name); + Assert.Equal("some code2", instance?.Devices?.First()?.Commands?.ElementAt(1).Data); } } } \ No newline at end of file diff --git a/tests/NetDaemon.Daemon.Tests/FakesTests/FakeTests.cs b/tests/NetDaemon.Daemon.Tests/FakesTests/FakeTests.cs index 4a1a8a273..92aa8488e 100644 --- a/tests/NetDaemon.Daemon.Tests/FakesTests/FakeTests.cs +++ b/tests/NetDaemon.Daemon.Tests/FakesTests/FakeTests.cs @@ -30,7 +30,7 @@ public async Task CallServiceShouldCallCorrectFunction() { // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); - var (dynObj, expObj) = GetDynamicObject( + var (dynObj, _) = GetDynamicObject( ("attr", "value")); // ACT @@ -139,9 +139,6 @@ public async Task RunScriptShouldCallCorrectFunction() // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); - var (dynObj, expObj) = GetDynamicObject( - ("attr", "value")); - // ACT DefaultDaemonRxApp.RunScript("myscript"); @@ -158,8 +155,6 @@ public async Task RunScriptWithDomainShouldCallCorrectFunction() { // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); - var (dynObj, expObj) = GetDynamicObject( - ("attr", "value")); // ACT DefaultDaemonRxApp.RunScript("script.myscript"); @@ -271,7 +266,7 @@ public async Task EntityIdsShouldReturnCorrectItems() await RunFakeDaemonUntilTimeout().ConfigureAwait(false); // ASSERT Assert.NotNull(entities); - Assert.Equal(8, entities.Count()); + Assert.Equal(8, entities.Count); } [Fact] diff --git a/tests/NetDaemon.Daemon.Tests/Fluent/FluentEventTests.cs b/tests/NetDaemon.Daemon.Tests/Fluent/FluentEventTests.cs index 7d18d1879..2d5d1f942 100644 --- a/tests/NetDaemon.Daemon.Tests/Fluent/FluentEventTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Fluent/FluentEventTests.cs @@ -17,13 +17,15 @@ public async Task ACustomEventNullValueCallThrowsNullReferenceException() var hcMock = HassClientMock.DefaultMock; await using var daemonHost = new NetDaemonHost(hcMock.Object, new Mock().Object); - var app = new FluentTestApp(); - app.Id = "id"; + var app = new FluentTestApp + { + Id = "id" + }; daemonHost.InternalRunningAppInstances[app.Id] = app; await app.StartUpAsync(daemonHost); - var cancelSource = hcMock.GetSourceWithTimeout(); + var cancelSource = HassClientMock.GetSourceWithTimeout(); Assert.Throws(() => app .Event("CUSTOM_EVENT") @@ -37,8 +39,10 @@ public async Task ACustomEventShouldDoCorrectCall() var hcMock = HassClientMock.DefaultMock; await using var daemonHost = new NetDaemonHost(hcMock.Object, new Mock().Object); - var app = new FluentTestApp(); - app.Id = "id"; + var app = new FluentTestApp + { + Id = "id" + }; daemonHost.InternalRunningAppInstances[app.Id] = app; await app.StartUpAsync(daemonHost); @@ -48,7 +52,7 @@ public async Task ACustomEventShouldDoCorrectCall() hcMock.AddCustomEvent("CUSTOM_EVENT", dynObject); - var cancelSource = hcMock.GetSourceWithTimeout(); + var cancelSource = HassClientMock.GetSourceWithTimeout(); var isCalled = false; string? message = ""; @@ -79,8 +83,10 @@ public async Task ACustomEventShouldUsingSelectorFuncDoCorrectCall() // ARRANGE var hcMock = HassClientMock.DefaultMock; await using var daemonHost = new NetDaemonHost(hcMock.Object, new Mock().Object); - var app = new FluentTestApp(); - app.Id = "id"; + var app = new FluentTestApp + { + Id = "id" + }; daemonHost.InternalRunningAppInstances[app.Id] = app; await app.StartUpAsync(daemonHost); @@ -90,7 +96,7 @@ public async Task ACustomEventShouldUsingSelectorFuncDoCorrectCall() hcMock.AddCustomEvent("CUSTOM_EVENT", dynObject); - var cancelSource = hcMock.GetSourceWithTimeout(); + var cancelSource = HassClientMock.GetSourceWithTimeout(); var isCalled = false; string? message = ""; @@ -122,8 +128,10 @@ public async Task ACustomEventShouldUsingSelectorUsingDataFuncDoCorrectCall() // ARRANGE var hcMock = HassClientMock.DefaultMock; await using var daemonHost = new NetDaemonHost(hcMock.Object, new Mock().Object); - var app = new FluentTestApp(); - app.Id = "id"; + var app = new FluentTestApp + { + Id = "id" + }; daemonHost.InternalRunningAppInstances[app.Id] = app; await app.StartUpAsync(daemonHost); @@ -133,7 +141,7 @@ public async Task ACustomEventShouldUsingSelectorUsingDataFuncDoCorrectCall() hcMock.AddCustomEvent("CUSTOM_EVENT", dynObject); - var cancelSource = hcMock.GetSourceWithTimeout(); + var cancelSource = HassClientMock.GetSourceWithTimeout(); var isCalled = false; string? message = ""; @@ -165,8 +173,10 @@ public async Task ACustomEventShouldUsingSelectorUsingDataFuncNotCall() // ARRANGE var hcMock = HassClientMock.DefaultMock; await using var daemonHost = new NetDaemonHost(hcMock.Object, new Mock().Object); - var app = new FluentTestApp(); - app.Id = "id"; + var app = new FluentTestApp + { + Id = "id" + }; daemonHost.InternalRunningAppInstances[app.Id] = app; await app.StartUpAsync(daemonHost); @@ -176,7 +186,7 @@ public async Task ACustomEventShouldUsingSelectorUsingDataFuncNotCall() hcMock.AddCustomEvent("CUSTOM_EVENT", dynObject); - var cancelSource = hcMock.GetSourceWithTimeout(); + var cancelSource = HassClientMock.GetSourceWithTimeout(); var isCalled = false; string? message = ""; @@ -207,8 +217,10 @@ public async Task ACustomEventShouldUsingSelectorUsingDataNotExisstFuncNotCall() // ARRANGE var hcMock = HassClientMock.DefaultMock; await using var daemonHost = new NetDaemonHost(hcMock.Object, new Mock().Object); - var app = new FluentTestApp(); - app.Id = "id"; + var app = new FluentTestApp + { + Id = "id" + }; daemonHost.InternalRunningAppInstances[app.Id] = app; await app.StartUpAsync(daemonHost); @@ -217,7 +229,7 @@ public async Task ACustomEventShouldUsingSelectorUsingDataNotExisstFuncNotCall() hcMock.AddCustomEvent("CUSTOM_EVENT", dynObject); - var cancelSource = hcMock.GetSourceWithTimeout(); + var cancelSource = HassClientMock.GetSourceWithTimeout(); var isCalled = false; string? message = ""; @@ -248,8 +260,10 @@ public async Task ACustomEventsShouldDoCorrectCall() // ARRANGE var hcMock = HassClientMock.DefaultMock; await using var daemonHost = new NetDaemonHost(hcMock.Object, new Mock().Object); - var app = new FluentTestApp(); - app.Id = "id"; + var app = new FluentTestApp + { + Id = "id" + }; daemonHost.InternalRunningAppInstances[app.Id] = app; await app.StartUpAsync(daemonHost); @@ -259,7 +273,7 @@ public async Task ACustomEventsShouldDoCorrectCall() hcMock.AddCustomEvent("CUSTOM_EVENT", dynObject); - var cancelSource = hcMock.GetSourceWithTimeout(); + var cancelSource = HassClientMock.GetSourceWithTimeout(); var isCalled = false; string? message = ""; diff --git a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyAppsTests.cs b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyAppsTests.cs index 40cf46ce1..158ac4065 100644 --- a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyAppsTests.cs +++ b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyAppsTests.cs @@ -17,8 +17,10 @@ public class FaultyAppTests : DaemonHostTestBase public FaultyAppTests() : base() { - _app = new DaemonAppTestApp(); - _app.Id = "id"; + _app = new DaemonAppTestApp + { + Id = "id" + }; DefaultDaemonHost.InternalRunningAppInstances[_app.Id] = App; _app.StartUpAsync(DefaultDaemonHost).Wait(); } diff --git a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyRxAppsTests.cs b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyRxAppsTests.cs index 83dfa470e..5645b1c96 100644 --- a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyRxAppsTests.cs +++ b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyRxAppsTests.cs @@ -18,8 +18,10 @@ public class FaultyRxAppTests : DaemonHostTestBase public FaultyRxAppTests() : base() { - _app = new DaemonRxAppTestApp(); - _app.Id = "id"; + _app = new DaemonRxAppTestApp + { + Id = "id" + }; DefaultDaemonHost.InternalRunningAppInstances[_app.Id] = App; _app.StartUpAsync(DefaultDaemonHost).Wait(); } diff --git a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/NetDaemonAppTests.cs b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/NetDaemonAppTests.cs index a25def45c..1aa879c5b 100644 --- a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/NetDaemonAppTests.cs +++ b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/NetDaemonAppTests.cs @@ -18,8 +18,8 @@ public class NetDaemonApptests { private const string appTemplate = " app: "; private readonly LoggerMock _logMock; - private NetDaemon.Common.NetDaemonApp _app; - private Mock _netDaemonMock; + private readonly NetDaemon.Common.NetDaemonApp _app; + private readonly Mock _netDaemonMock; public NetDaemonApptests() { @@ -27,13 +27,13 @@ public NetDaemonApptests() _netDaemonMock = new Mock(); _netDaemonMock.SetupGet(n => n.Logger).Returns(_logMock.Logger); - _appMock = new Mock(); + AppMock = new Mock(); _app = new AppTestApp(); _app.StartUpAsync(_netDaemonMock.Object); _app.Id = "app"; } - public Mock _appMock { get; } + public Mock AppMock { get; } [Fact] public void CallServiceShouldCallCorrectDaemonCallService() diff --git a/tests/NetDaemon.Daemon.Tests/Reactive/RxAppTest.cs b/tests/NetDaemon.Daemon.Tests/Reactive/RxAppTest.cs index fbed47a59..e87689014 100644 --- a/tests/NetDaemon.Daemon.Tests/Reactive/RxAppTest.cs +++ b/tests/NetDaemon.Daemon.Tests/Reactive/RxAppTest.cs @@ -27,7 +27,7 @@ public async Task CallServiceShouldCallCorrectFunction() { // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); - var (dynObj, expObj) = GetDynamicObject( + var (dynObj, _) = GetDynamicObject( ("attr", "value")); // ACT @@ -141,8 +141,6 @@ public async Task RunScriptShouldCallCorrectFunction() { // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); - var (dynObj, expObj) = GetDynamicObject( - ("attr", "value")); // ACT DefaultDaemonRxApp.RunScript("myscript"); @@ -160,8 +158,6 @@ public async Task RunScriptWithDomainShouldCallCorrectFunction() { // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); - var (dynObj, expObj) = GetDynamicObject( - ("attr", "value")); // ACT DefaultDaemonRxApp.RunScript("script.myscript"); @@ -277,7 +273,7 @@ public async Task EntityIdsShouldReturnCorrectItems() await RunFakeDaemonUntilTimeout().ConfigureAwait(false); // ASSERT Assert.NotNull(entities); - Assert.Equal(8, entities.Count()); + Assert.Equal(8, entities.Count); } [Fact] diff --git a/tests/NetDaemon.Daemon.Tests/Reactive/RxSchedulers.cs b/tests/NetDaemon.Daemon.Tests/Reactive/RxSchedulers.cs index 0737b6efe..65ac8ab53 100644 --- a/tests/NetDaemon.Daemon.Tests/Reactive/RxSchedulers.cs +++ b/tests/NetDaemon.Daemon.Tests/Reactive/RxSchedulers.cs @@ -40,8 +40,10 @@ public async Task CreateObservableIntervallFailureShouldLogError() public async Task CreateObservableIntervallShouldCallFunction() { // ARRANGE - var app = new BaseTestRxApp(); - app.IsEnabled = true; + var app = new BaseTestRxApp + { + IsEnabled = true + }; var called = false; @@ -73,8 +75,10 @@ public async Task CreateObservableTimerFailureShouldLogError() public async Task CreateObservableTimerShouldCallFunction() { // ARRANGE - var app = new BaseTestRxApp(); - app.IsEnabled = true; + var app = new BaseTestRxApp + { + IsEnabled = true + }; var called = false; diff --git a/tests/NetDaemon.Daemon.Tests/SchedulerTests.cs b/tests/NetDaemon.Daemon.Tests/SchedulerTests.cs index d19caee0a..41a39ef15 100644 --- a/tests/NetDaemon.Daemon.Tests/SchedulerTests.cs +++ b/tests/NetDaemon.Daemon.Tests/SchedulerTests.cs @@ -136,7 +136,7 @@ public void DailyTimeBetweenNowAndTargetTime(string nowTime, string targetTime, { // ARRANGE DateTime timePart = DateTime.ParseExact(nowTime, "HH:mm:ss", CultureInfo.InvariantCulture); - DateTime fakeTimeNow = new DateTime(2001, 01, 01, timePart.Hour, timePart.Minute, timePart.Second); + DateTime fakeTimeNow = new(2001, 01, 01, timePart.Hour, timePart.Minute, timePart.Second); DateTime timeTarget = DateTime.ParseExact(targetTime, "HH:mm:ss", CultureInfo.InvariantCulture); var mockTimeManager = new TimeManagerMock(fakeTimeNow); diff --git a/tests/NetDaemon.Daemon.Tests/TestBaseClasses.cs b/tests/NetDaemon.Daemon.Tests/TestBaseClasses.cs index c5f5a7e32..da7780314 100644 --- a/tests/NetDaemon.Daemon.Tests/TestBaseClasses.cs +++ b/tests/NetDaemon.Daemon.Tests/TestBaseClasses.cs @@ -26,15 +26,19 @@ public class CoreDaemonHostTestBase : DaemonHostTestBase, IAsyncLifetime public CoreDaemonHostTestBase() : base() { - _defaultDaemonApp = new BaseTestApp(); - _defaultDaemonApp.Id = "app_id"; - _defaultDaemonApp.IsEnabled = true; + _defaultDaemonApp = new BaseTestApp + { + Id = "app_id", + IsEnabled = true + }; DefaultDaemonHost.InternalRunningAppInstances[_defaultDaemonApp.Id!] = _defaultDaemonApp; - _defaultDaemonRxApp = new BaseTestRxApp(); - _defaultDaemonRxApp.Id = "app_rx_id"; - _defaultDaemonRxApp.IsEnabled = true; + _defaultDaemonRxApp = new BaseTestRxApp + { + Id = "app_rx_id", + IsEnabled = true + }; DefaultDaemonHost.InternalRunningAppInstances[_defaultDaemonRxApp.Id!] = _defaultDaemonRxApp; _defaultMockedRxApp = new Mock() { CallBase = true }; @@ -119,7 +123,7 @@ public void SetupFakeData() } }); - SetEntityState( new() + SetEntityState(new() { EntityId = "light.ligth_in_area", State = "off", @@ -157,7 +161,7 @@ public void SetupFakeData() public BaseTestRxApp DefaultDaemonRxApp => _defaultDaemonRxApp; public Mock DefaultMockedRxApp => _defaultMockedRxApp; public Common.NetDaemonApp DefaultDaemonApp => _defaultDaemonApp; - public string HelloWorldData => "Hello world!"; + public static string HelloWorldData => "Hello world!"; public (Task, CancellationTokenSource) ReturnRunningNotConnectedDaemonHostTask(short milliSeconds = 100, bool overrideDebugNotCancel = false) { From 4e63fc9f9671d406c9ddf2c4dc0193f79a1fc1b6 Mon Sep 17 00:00:00 2001 From: helto4real Date: Sat, 26 Dec 2020 10:17:19 +0100 Subject: [PATCH 03/10] Roslynator batch 3 --- .linting/roslynator.config | 16 +++++ .linting/roslynator.ruleset | 31 ++++++++++ .vscode/settings.json | 8 ++- src/App/NetDaemon.App/NetDaemon.App.csproj | 4 +- .../NetDaemon.Daemon/Daemon/Scheduler.cs | 60 ++++++------------- .../Daemon/Storage/DataRepository.cs | 1 - .../NetDaemon.Daemon/NetDaemon.Daemon.csproj | 6 +- .../DaemonRunner/DaemonRunner.csproj | 9 ++- .../Extensions/AssemblyExtensions.cs | 5 +- .../DaemonRunner/NetDaemonExtensions.cs | 18 ++---- .../DaemonRunner/Service/API/ApiData.cs | 2 - .../DaemonRunner/Service/API/WsHandler.cs | 2 +- .../DaemonRunner/Service/App/CodeGenerator.cs | 8 +-- .../DaemonRunner/Service/RunnerService.cs | 1 - .../NetDaemon.DevelopmentApps.csproj | 4 +- .../NetDaemon.Fakes/NetDaemon.Fakes.csproj | 3 + src/Service/Program.cs | 3 +- src/Service/Service.csproj | 3 + .../NetDaemon.Daemon.Tests.csproj | 5 +- 19 files changed, 112 insertions(+), 77 deletions(-) create mode 100644 .linting/roslynator.config create mode 100644 .linting/roslynator.ruleset diff --git a/.linting/roslynator.config b/.linting/roslynator.config new file mode 100644 index 000000000..edcfc08ca --- /dev/null +++ b/.linting/roslynator.config @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.linting/roslynator.ruleset b/.linting/roslynator.ruleset new file mode 100644 index 000000000..f82e955cb --- /dev/null +++ b/.linting/roslynator.ruleset @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index ba38fd66b..e34b2a0a8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,11 @@ "**/obj": true, "**/Properties": true, "**/TestResults": true - } + }, + "omnisharp.enableRoslynAnalyzers": true, + "cSpell.words": [ + "Expando", + "Hass", + "dayofweek" + ] } \ No newline at end of file diff --git a/src/App/NetDaemon.App/NetDaemon.App.csproj b/src/App/NetDaemon.App/NetDaemon.App.csproj index 72f6946b4..46b545ffc 100644 --- a/src/App/NetDaemon.App/NetDaemon.App.csproj +++ b/src/App/NetDaemon.App/NetDaemon.App.csproj @@ -26,5 +26,7 @@ - + + ..\..\..\.linting\roslynator.ruleset + diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs index 08026e22b..a4bc63cfc 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs @@ -53,19 +53,13 @@ public Scheduler(IManageTime? timerManager = null, ILoggerFactory? loggerFactory }); /// - public ISchedulerResult RunEvery(int millisecondsDelay, Func func) => RunEveryAsync(millisecondsDelay, func); - - /// - public ISchedulerResult RunEveryAsync(int millisecondsDelay, Func func) + public ISchedulerResult RunEvery(int millisecondsDelay, Func func) { - return RunEveryAsync(TimeSpan.FromMilliseconds(millisecondsDelay), func); + return RunEvery(TimeSpan.FromMilliseconds(millisecondsDelay), func); } /// - public ISchedulerResult RunEvery(TimeSpan timeSpan, Func func) => RunEveryAsync(timeSpan, func); - - /// - public ISchedulerResult RunEveryAsync(TimeSpan timeSpan, Func func) + public ISchedulerResult RunEvery(TimeSpan timeSpan, Func func) { var cancelSource = new CancellationTokenSource(); var task = RunEveryInternalAsync(timeSpan, func, cancelSource.Token); @@ -131,17 +125,10 @@ internal TimeSpan CalculateEveryMinuteTimeBetweenNowAndTargetTime(short second) } /// - public ISchedulerResult RunDaily(string time, Func func) => RunDailyAsync(time, func); - - /// - public ISchedulerResult RunDaily(string time, IEnumerable? runOnDays, Func func) => - RunDailyAsync(time, runOnDays, func); + public ISchedulerResult RunDaily(string time, Func func) => RunDaily(time, null, func); /// - public ISchedulerResult RunDailyAsync(string time, Func func) => RunDailyAsync(time, null, func); - - /// - public ISchedulerResult RunDailyAsync(string time, IEnumerable? runOnDays, Func func) + public ISchedulerResult RunDaily(string time, IEnumerable? runOnDays, Func func) { var cancelSource = new CancellationTokenSource(); @@ -182,7 +169,9 @@ private async Task RunDailyInternalAsync(DateTime timeOfDayToTrigger, IEnumerabl } } else + { _logger.LogTrace("RunDaily, Time: {time}, parsed time: {timeOfDayToTrigger}, Not run, due to dayofweek", _timeManager!.Current, timeOfDayToTrigger); + } } else { @@ -201,10 +190,7 @@ private async Task RunDailyInternalAsync(DateTime timeOfDayToTrigger, IEnumerabl } /// - public ISchedulerResult RunEveryMinute(short second, Func func) => RunEveryMinuteAsync(second, func); - - /// - public ISchedulerResult RunEveryMinuteAsync(short second, Func func) + public ISchedulerResult RunEveryMinute(short second, Func func) { var cancelSource = new CancellationTokenSource(); var task = RunEveryMinuteInternalAsync(second, func, cancelSource.Token); @@ -236,19 +222,10 @@ private async Task RunEveryMinuteInternalAsync(short second, Func func, Ca } /// - public ISchedulerResult RunIn(int millisecondsDelay, Func func) => RunInAsync(millisecondsDelay, func); + public ISchedulerResult RunIn(int millisecondsDelay, Func func) => RunIn(TimeSpan.FromMilliseconds(millisecondsDelay), func); /// - public ISchedulerResult RunInAsync(int millisecondsDelay, Func func) - { - return RunInAsync(TimeSpan.FromMilliseconds(millisecondsDelay), func); - } - - /// - public ISchedulerResult RunIn(TimeSpan timeSpan, Func func) => RunInAsync(timeSpan, func); - - /// - public ISchedulerResult RunInAsync(TimeSpan timeSpan, Func func) + public ISchedulerResult RunIn(TimeSpan timeSpan, Func func) { var cancelSource = new CancellationTokenSource(); var task = InternalRunInAsync(timeSpan, func, cancelSource.Token); @@ -287,9 +264,11 @@ public async Task Stop() var taskResult = await Task.WhenAny( Task.WhenAll(_scheduledTasks.Values.ToArray()), Task.Delay(1000)).ConfigureAwait(false); - if (_scheduledTasks.Values.Any(n => n.IsCompleted == false)) + if (_scheduledTasks.Values.Any(n => !n.IsCompleted)) + { // Todo: Some kind of logging have to be done here to tell user which task caused timeout throw new ApplicationException("Failed to cancel all tasks"); + } } /// @@ -307,6 +286,7 @@ private async Task SchedulerLoop() try { while (!_cancelSource.Token.IsCancellationRequested) + { if (!_scheduledTasks.IsEmpty) { // Make sure we do cleaning and handle new task every 100 ms @@ -323,6 +303,7 @@ private async Task SchedulerLoop() { await Task.Delay(DefaultSchedulerTimeout, _cancelSource.Token).ConfigureAwait(false); } + } } catch (OperationCanceledException) {// Normal, just ignore @@ -384,16 +365,13 @@ public async Task Delay(TimeSpan timeSpan, CancellationToken token) public class SchedulerResult : ISchedulerResult { - private readonly Task _scheduledTask; - private readonly CancellationTokenSource _cancelSource; - public SchedulerResult(Task scheduledTask, CancellationTokenSource cancelSource) { - _scheduledTask = scheduledTask; - _cancelSource = cancelSource; + Task = scheduledTask; + CancelSource = cancelSource; } - public Task Task => _scheduledTask; - public CancellationTokenSource CancelSource => _cancelSource; + public Task Task { get; } + public CancellationTokenSource CancelSource { get; } } } \ No newline at end of file diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs index 27be1a7c4..5fe1d52be 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs @@ -26,7 +26,6 @@ public DataRepository(string dataStoragePath) { try { - var storageJsonFile = Path.Combine(_dataStoragePath, $"{id}_store.json"); if (!File.Exists(storageJsonFile)) diff --git a/src/Daemon/NetDaemon.Daemon/NetDaemon.Daemon.csproj b/src/Daemon/NetDaemon.Daemon/NetDaemon.Daemon.csproj index ee532db4a..667c9b43a 100644 --- a/src/Daemon/NetDaemon.Daemon/NetDaemon.Daemon.csproj +++ b/src/Daemon/NetDaemon.Daemon/NetDaemon.Daemon.csproj @@ -26,9 +26,11 @@ - + - + + ..\..\..\.linting\roslynator.ruleset + \ No newline at end of file diff --git a/src/DaemonRunner/DaemonRunner/DaemonRunner.csproj b/src/DaemonRunner/DaemonRunner/DaemonRunner.csproj index 24dff151e..271cfec80 100644 --- a/src/DaemonRunner/DaemonRunner/DaemonRunner.csproj +++ b/src/DaemonRunner/DaemonRunner/DaemonRunner.csproj @@ -19,7 +19,7 @@ First alpha version, expect things to change! Home Assistant - + @@ -34,10 +34,13 @@ - + - + + + ..\..\..\.linting\roslynator.ruleset + \ No newline at end of file diff --git a/src/DaemonRunner/DaemonRunner/Infrastructure/Extensions/AssemblyExtensions.cs b/src/DaemonRunner/DaemonRunner/Infrastructure/Extensions/AssemblyExtensions.cs index 388b0552b..fe0523080 100644 --- a/src/DaemonRunner/DaemonRunner/Infrastructure/Extensions/AssemblyExtensions.cs +++ b/src/DaemonRunner/DaemonRunner/Infrastructure/Extensions/AssemblyExtensions.cs @@ -10,10 +10,7 @@ internal static class AssemblyExtensions public static IEnumerable GetTypesWhereSubclassOf(this Assembly assembly) { return assembly.GetTypes() - .Where(type => type.IsClass) - .Where(type => !type.IsGenericType) - .Where(type => !type.IsAbstract) - .Where(type => type.IsSubclassOf(typeof(T))); + .Where(type => type.IsClass && !type.IsGenericType && !type.IsAbstract && type.IsSubclassOf(typeof(T))); } } } \ No newline at end of file diff --git a/src/DaemonRunner/DaemonRunner/NetDaemonExtensions.cs b/src/DaemonRunner/DaemonRunner/NetDaemonExtensions.cs index c7c1cd562..1e175bff4 100644 --- a/src/DaemonRunner/DaemonRunner/NetDaemonExtensions.cs +++ b/src/DaemonRunner/DaemonRunner/NetDaemonExtensions.cs @@ -16,7 +16,7 @@ namespace NetDaemon { public static class NetDaemonExtensions { - const string HassioConfigPath = "/data/options.json"; + private const string HassioConfigPath = "/data/options.json"; public static IHostBuilder UseNetDaemon(this IHostBuilder hostBuilder) { @@ -34,17 +34,14 @@ public static IHostBuilder UseNetDaemon(this IHostBuilder hostBuilder) }) .ConfigureWebHostDefaults(webbuilder => { - webbuilder.UseKestrel(options => { }); + webbuilder.UseKestrel(_ => { }); webbuilder.UseStartup(); }); } public static IHostBuilder UseDefaultNetDaemonLogging(this IHostBuilder hostBuilder) { - return hostBuilder.UseSerilog((context, loggerConfiguration) => - { - SerilogConfigurator.Configure(loggerConfiguration, context.HostingEnvironment); - }); + return hostBuilder.UseSerilog((context, loggerConfiguration) => SerilogConfigurator.Configure(loggerConfiguration, context.HostingEnvironment)); } public static void CleanupNetDaemon() @@ -61,7 +58,7 @@ private static void RegisterNetDaemonAssembly(IServiceCollection services) } /// - /// Returns true if local loading of assemblies should be preferred. + /// Returns true if local loading of assemblies should be preferred. /// This is typically when running in container. When running in dev /// you want the local loading /// @@ -72,17 +69,14 @@ private static bool UseLocalAssemblyLoading() if (string.IsNullOrEmpty(appSource)) return true; - if (appSource.EndsWith(".csproj") || appSource.EndsWith(".dll")) - return true; - else - return false; + return appSource.EndsWith(".csproj") || appSource.EndsWith(".dll"); } /// /// Reads the Home Assistant (hassio) configuration file /// /// - static void ReadHassioConfig() + private static void ReadHassioConfig() { try { diff --git a/src/DaemonRunner/DaemonRunner/Service/API/ApiData.cs b/src/DaemonRunner/DaemonRunner/Service/API/ApiData.cs index 660d7db1b..9b8d346a0 100644 --- a/src/DaemonRunner/DaemonRunner/Service/API/ApiData.cs +++ b/src/DaemonRunner/DaemonRunner/Service/API/ApiData.cs @@ -4,7 +4,6 @@ namespace NetDaemon.Service.Api { - public class ApiApplication { public string? Id { get; set; } @@ -24,5 +23,4 @@ public class ApiConfig public NetDaemonSettings? DaemonSettings { get; set; } public HomeAssistantSettings? HomeAssistantSettings { get; set; } } - } \ No newline at end of file diff --git a/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs b/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs index 6bf99de91..72cff6bfd 100644 --- a/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs +++ b/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs @@ -67,7 +67,7 @@ private async Task NewEvent(ExternalEventBase ev) LastErrorMessage = n.IsEnabled ? n.RuntimeInfo.LastErrorMessage : null }) }; - await BroadCast(JsonSerializer.Serialize(eventMessage, _jsonOptions)); + await BroadCast(JsonSerializer.Serialize(eventMessage, _jsonOptions)).ConfigureAwait(false); } } public async Task Invoke(HttpContext context) diff --git a/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs b/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs index 9f1bdd51f..5c8be2b27 100644 --- a/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs +++ b/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs @@ -14,7 +14,7 @@ namespace NetDaemon.Service.App { - public class CodeGenerator + public static class CodeGenerator { /// /// Mapps the domain to corresponding implemented Fluent API, will be added as @@ -165,7 +165,7 @@ public class CodeGenerator public string? Area => DaemonRxApp?.State(EntityId)?.Area; public dynamic? Attribute => DaemonRxApp?.State(EntityId)?.Attribute; - + public DateTime? LastChanged => DaemonRxApp?.State(EntityId)?.LastChanged; public DateTime? LastUpdated => DaemonRxApp?.State(EntityId)?.LastUpdated; @@ -173,7 +173,7 @@ public class CodeGenerator public dynamic? State => DaemonRxApp?.State(EntityId)?.State; public {domain.ToCamelCase()}Entity(INetDaemonReactive daemon, IEnumerable entityIds) : base(daemon, entityIds) - {{ + {{ }} }}"; var entityClass = CSharpSyntaxTree.ParseText(classDeclaration).GetRoot().ChildNodes().OfType().FirstOrDefault() @@ -207,7 +207,7 @@ public class CodeGenerator if (data is ExpandoObject) {{ serviceData.CopyFrom(data); - }} + }} else if (data is not null) {{ var expObject = ((object)data).ToExpandoObject(); diff --git a/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs b/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs index 0ca62e89b..7aa31a2d4 100644 --- a/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs +++ b/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs @@ -211,7 +211,6 @@ private async Task GenerateEntities(NetDaemonHost daemonHost, string sourceFolde _logger.LogTrace("Generating entities from Home Assistant instance .."); _entitiesGenerated = true; - var codeGen = new CodeGenerator(); var source = CodeGenerator.GenerateCode( "Netdaemon.Generated.Extensions", daemonHost.State.Select(n => n.EntityId).Distinct() diff --git a/src/DevelopmentApps/NetDaemon.DevelopmentApps.csproj b/src/DevelopmentApps/NetDaemon.DevelopmentApps.csproj index c28efeac0..cfd71b893 100644 --- a/src/DevelopmentApps/NetDaemon.DevelopmentApps.csproj +++ b/src/DevelopmentApps/NetDaemon.DevelopmentApps.csproj @@ -19,5 +19,7 @@ - + + ..\..\.linting\roslynator.ruleset + \ No newline at end of file diff --git a/src/Fakes/NetDaemon.Fakes/NetDaemon.Fakes.csproj b/src/Fakes/NetDaemon.Fakes/NetDaemon.Fakes.csproj index 037b002e4..7b3307b0c 100644 --- a/src/Fakes/NetDaemon.Fakes/NetDaemon.Fakes.csproj +++ b/src/Fakes/NetDaemon.Fakes/NetDaemon.Fakes.csproj @@ -35,4 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + ..\..\..\.linting\roslynator.ruleset + diff --git a/src/Service/Program.cs b/src/Service/Program.cs index 8bd342b42..f0aef6c46 100644 --- a/src/Service/Program.cs +++ b/src/Service/Program.cs @@ -8,7 +8,8 @@ await Host.CreateDefaultBuilder(args) .UseDefaultNetDaemonLogging() .UseNetDaemon() .Build() - .RunAsync(); + .RunAsync() + .ConfigureAwait(false); } catch (Exception e) { diff --git a/src/Service/Service.csproj b/src/Service/Service.csproj index d4d95834c..319b3d82c 100644 --- a/src/Service/Service.csproj +++ b/src/Service/Service.csproj @@ -24,4 +24,7 @@ Always + + ..\..\.linting\roslynator.ruleset + diff --git a/tests/NetDaemon.Daemon.Tests/NetDaemon.Daemon.Tests.csproj b/tests/NetDaemon.Daemon.Tests/NetDaemon.Daemon.Tests.csproj index 3c3e6a919..3f8ddb65b 100644 --- a/tests/NetDaemon.Daemon.Tests/NetDaemon.Daemon.Tests.csproj +++ b/tests/NetDaemon.Daemon.Tests/NetDaemon.Daemon.Tests.csproj @@ -59,7 +59,8 @@ Always - - + + ..\..\.linting\roslynator.ruleset + From 2dfad8b39653e338d936f05405af3558d8ffbdd0 Mon Sep 17 00:00:00 2001 From: helto4real Date: Sat, 26 Dec 2020 11:31:11 +0100 Subject: [PATCH 04/10] Roslynator batch 4 --- .vscode/settings.json | 17 +- .../NetDaemon.Daemon/Daemon/NetDaemonHost.cs | 92 ++++------- .../Daemon/Storage/DataRepository.cs | 2 +- .../DaemonRunner/Service/API/WsHandler.cs | 29 ++-- .../DaemonRunner/Service/ApiService.cs | 4 +- .../DaemonRunner/Service/App/CodeGenerator.cs | 14 +- .../Service/App/DaemonAppCompiler.cs | 6 +- .../Service/App/DaemonCompiler.cs | 31 ++-- .../Service/App/LocalDaemonAppCompiler.cs | 2 +- src/DevelopmentApps/apps/DebugApp/DebugApp.cs | 6 +- src/Fakes/NetDaemon.Fakes/HassClientMock.cs | 19 +-- src/Fakes/NetDaemon.Fakes/LoggerMock.cs | 12 +- .../Daemon/DataRepositoryTests.cs | 22 +-- .../Daemon/ExtensionMethodsTests.cs | 28 ++-- .../Daemon/HttpHandlerMock.cs | 74 --------- .../Daemon/HttpTests.cs | 16 +- .../Daemon/NetDaemonHostTests.cs | 42 +++-- .../DaemonRunner/Api/ApiTests.cs | 34 ++-- .../DaemonRunner/App/AssemblyDaemonApps.cs | 3 +- .../DaemonRunner/App/DaemonAppTests.cs | 106 ++++-------- .../DaemonRunner/Config/ConfigTest.cs | 16 +- .../FakesTests/FakeApp.cs | 11 +- .../FakesTests/FakeTests.cs | 67 ++------ .../Fluent/FluentCameraTests.cs | 63 ++++---- .../Fluent/FluentEventTests.cs | 38 ++--- .../Fluent/FluentTests.cs | 92 +++++------ .../NetDaemonApp/FaultyAppsTests.cs | 58 ++----- .../NetDaemonApp/FaultyRxAppsTests.cs | 153 +++--------------- .../NetDaemonApp/NetDaemonAppTests.cs | 17 +- .../Reactive/RxAppTest.cs | 76 ++------- .../Reactive/RxSchedulers.cs | 14 +- .../NetDaemon.Daemon.Tests/SchedulerTests.cs | 102 ++++++------ .../NetDaemon.Daemon.Tests/TestBaseClasses.cs | 45 +++--- .../NetDaemon.Daemon.Tests/TimeManagerMock.cs | 2 +- 34 files changed, 440 insertions(+), 873 deletions(-) delete mode 100644 tests/NetDaemon.Daemon.Tests/Daemon/HttpHandlerMock.cs diff --git a/.vscode/settings.json b/.vscode/settings.json index e34b2a0a8..0c3496fd9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,7 +11,22 @@ "omnisharp.enableRoslynAnalyzers": true, "cSpell.words": [ "Expando", + "Finalizers", "Hass", - "dayofweek" + "Usings", + "Xunit", + "dayofweek", + "entityid", + "hassio", + "idguid", + "mulitinstance", + "mydomain", + "myevent", + "mylight", + "myscript", + "noexist", + "noquotes", + "parentidguid", + "useridguid" ] } \ No newline at end of file diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs b/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs index 65d8040fd..5221f458b 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs @@ -425,7 +425,7 @@ public async Task Run(string host, short port, bool ssl, string token, Cancellat // Setup TTS Task handleTextToSpeechMessagesTask = HandleTextToSpeechMessages(cancellationToken); Task handleAsyncServiceCalls = HandleAsyncServiceCalls(cancellationToken); - Task hanldeAsyncSetState = HandleAsyncSetState(cancellationToken); + Task handleAsyncSetState = HandleAsyncSetState(cancellationToken); await RefreshInternalStatesAndSetArea().ConfigureAwait(false); @@ -527,7 +527,7 @@ public async Task SetDaemonStateAsync(int numberOfLoadedApps, int numberOfRunnin await SetStateAsync( "netdaemon.status", - "Connected", // State will alawys be connected, otherwise state could not be set. + "Connected", // State will always be connected, otherwise state could not be set. ("number_of_loaded_apps", numberOfLoadedApps), ("number_of_running_apps", numberOfRunningApps), ("version", GetType().Assembly.GetName().Version?.ToString() ?? "N/A")).ConfigureAwait(false); @@ -590,10 +590,8 @@ public async Task Stop() await _scheduler.Stop().ConfigureAwait(false); - InternalState.Clear(); - Connected = false; await _hassClient.CloseAsync().ConfigureAwait(false); Logger.LogTrace("Stopped Instance NetDaemonHost"); @@ -622,7 +620,7 @@ public async Task UnloadAllApps() /// /// The state data to be fixed /// - /// If a sensor is unavailable that normally has a primtive value + /// If a sensor is unavailable that normally has a primitive value /// it can be a string. The automations might expect a integer. /// Another scenario is that a value of 10 is cast as long and /// next time a value of 11.3 is cast to double. T @@ -748,14 +746,14 @@ internal static IList SortByDependency(IEnumerable n.Dependencies).Any()) { - // There are dependecies defined + // There are dependencies defined var edges = new HashSet>(); foreach (var instance in unsortedList) { foreach (var dependency in instance.Dependencies) { - var dependentApp = unsortedList.Where(n => n.Id == dependency).FirstOrDefault(); + var dependentApp = unsortedList.FirstOrDefault(n => n.Id == dependency); if (dependentApp == null) throw new ApplicationException($"There is no app named {dependency}, please check dependencies or make sure you have not disabled the dependent app!"); @@ -796,13 +794,13 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok if (stateData?.NewState?.State != stateData?.OldState?.State) { var sb = new StringBuilder(); - sb.AppendLine($"Can not fix state typing for {stateData?.NewState?.EntityId}"); - sb.AppendLine($"NewStateObject: {stateData?.NewState}"); - sb.AppendLine($"OldStateObject: {stateData?.OldState}"); - sb.AppendLine($"NewState: {stateData?.NewState?.State}"); - sb.AppendLine($"OldState: {stateData?.OldState?.State}"); - sb.AppendLine($"NewState type: {stateData?.NewState?.State?.GetType().ToString() ?? "null"}"); - sb.AppendLine($"OldState type: {stateData?.OldState?.State?.GetType().ToString() ?? "null"}"); + sb.Append("Can not fix state typing for ").AppendLine(stateData?.NewState?.EntityId); + sb.Append("NewStateObject: ").Append(stateData?.NewState).AppendLine(); + sb.Append("OldStateObject: ").Append(stateData?.OldState).AppendLine(); + sb.Append("NewState: ").AppendLine(stateData?.NewState?.State); + sb.Append("OldState: ").AppendLine(stateData?.OldState?.State); + sb.Append("NewState type: ").AppendLine(stateData?.NewState?.State?.GetType().ToString() ?? "null"); + sb.Append("OldState type: ").AppendLine(stateData?.OldState?.State?.GetType().ToString() ?? "null"); Logger.LogTrace(sb.ToString()); } return; @@ -863,7 +861,6 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok // Todo: Make it timeout! Maybe it should be handling in it's own task like scheduler if (tasks.Count > 0) { - await tasks.WhenAll(token).ConfigureAwait(false); await tasks.WhenAll(token).ConfigureAwait(false); @@ -1032,17 +1029,6 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok } } - private static string GetDomainFromEntity(string entity) - { - string[] entityParts = entity.Split('.'); - if (entityParts.Length != 2) - { - throw new ApplicationException($"entity_id is mal formatted {entity}"); - } - - return entityParts[0]; - } - /// /// Topological Sorting (Kahn's algorithm) /// @@ -1057,10 +1043,10 @@ private static string GetDomainFromEntity(string entity) var L = new List(); // Set of all nodes with no incoming edges - var S = new HashSet(nodes.Where(n => edges.All(e => e.Item2.Equals(n) == false))); + var S = new HashSet(nodes.Where(n => edges.All(e => !e.Item2.Equals(n)))); // while S is non-empty do - while (S.Any()) + while (S.Count > 0) { // remove a node n from S var n = S.First(); @@ -1078,7 +1064,7 @@ private static string GetDomainFromEntity(string entity) edges.Remove(e); // if m has no other incoming edges then - if (edges.All(me => me.Item2.Equals(m) == false)) + if (edges.All(me => !me.Item2.Equals(m))) { // insert m into S S.Add(m); @@ -1087,7 +1073,7 @@ private static string GetDomainFromEntity(string entity) } // if graph has edges then - if (edges.Any()) + if (edges.Count > 0) { // return error (graph has at least one cycle) return null; @@ -1114,7 +1100,7 @@ private async Task HandleAsyncServiceCalls(CancellationToken cancellationToken) (string domain, string service, dynamic? data) = await _serviceCallMessageChannel.Reader.ReadAsync(cancellationToken).ConfigureAwait(false); - await _hassClient.CallService(domain, service, data, false).ConfigureAwait(false); ; + await _hassClient.CallService(domain, service, data, false).ConfigureAwait(false); hasLoggedError = false; } @@ -1124,10 +1110,10 @@ private async Task HandleAsyncServiceCalls(CancellationToken cancellationToken) } catch (Exception e) { - if (hasLoggedError == false) + if (!hasLoggedError) Logger.LogDebug(e, "Failure sending call service"); hasLoggedError = true; - await Task.Delay(100, cancellationToken); // Do a delay to avoid loop + await Task.Delay(100, cancellationToken).ConfigureAwait(false); // Do a delay to avoid loop } } } @@ -1155,10 +1141,10 @@ private async Task HandleAsyncSetState(CancellationToken cancellationToken) } catch (Exception e) { - if (hasLoggedError == false) + if (!hasLoggedError) Logger.LogDebug(e, "Failure setting state"); hasLoggedError = true; - await Task.Delay(100, cancellationToken); // Do a delay to avoid loop + await Task.Delay(100, cancellationToken).ConfigureAwait(false); // Do a delay to avoid loop } } } @@ -1180,7 +1166,7 @@ private async Task HandleTextToSpeechMessages(CancellationToken cancellationToke EntityState? currentPlayState = GetState(entityId); - if (currentPlayState != null && currentPlayState.Attribute?.media_duration != null) + if (currentPlayState?.Attribute?.media_duration != null) { int delayInMilliSeconds = (int)Math.Round(currentPlayState?.Attribute?.media_duration * 1000) - InternalDelayTimeForTts; @@ -1222,7 +1208,6 @@ private async Task LoadAllApps() { InternalRunningAppInstances[appInstance.Id!] = appInstance; } - } // Now run initialize on all sorted by dependencies @@ -1241,7 +1226,7 @@ private async Task LoadAllApps() Logger.LogWarning("Initialize of application {app} took longer that 5 seconds, make sure Initialize function is not blocking!", sortedApp.Id); // Todo: refactor - await sortedApp.HandleAttributeInitialization(this); + await sortedApp.HandleAttributeInitialization(this).ConfigureAwait(false); Logger.LogInformation("Successfully loaded app {appId} ({class})", sortedApp.Id, sortedApp.GetType().Name); } @@ -1250,15 +1235,9 @@ private async Task LoadAllApps() private void RegisterAppSwitchesAndTheirStates() { - ListenServiceCall("switch", "turn_on", async (data) => - { - await SetStateOnDaemonAppSwitch("on", data).ConfigureAwait(false); - }); + ListenServiceCall("switch", "turn_on", async (data) => await SetStateOnDaemonAppSwitch("on", data).ConfigureAwait(false)); - ListenServiceCall("switch", "turn_off", async (data) => - { - await SetStateOnDaemonAppSwitch("off", data).ConfigureAwait(false); - }); + ListenServiceCall("switch", "turn_off", async (data) => await SetStateOnDaemonAppSwitch("off", data).ConfigureAwait(false)); ListenServiceCall("switch", "toggle", async (data) => { @@ -1290,16 +1269,16 @@ async Task SetStateOnDaemonAppSwitch(string state, dynamic? data) if (!entityId.StartsWith("switch.netdaemon_")) return; // We only want app switches - await SetDependentState(entityId, state); - await ReloadAllApps(); + await SetDependentState(entityId, state).ConfigureAwait(false); + await ReloadAllApps().ConfigureAwait(false); - await PostExternalEvent(new AppsInformationEvent()); + await PostExternalEvent(new AppsInformationEvent()).ConfigureAwait(false); } } private async Task SetDependentState(string entityId, string state) { - var app = InternalAllAppInstances.Values.Where(n => n.EntityId == entityId).FirstOrDefault(); + var app = InternalAllAppInstances.Values.FirstOrDefault(n => n.EntityId == entityId); if (app is not null) { @@ -1313,7 +1292,7 @@ private async Task SetDependentState(string entityId, string state) await SetDependentState(depApp.EntityId, state).ConfigureAwait(false); } app.IsEnabled = false; - await PersistAppStateAsync((NetDaemonAppBase)app); + await PersistAppStateAsync((NetDaemonAppBase)app).ConfigureAwait(false); Logger.LogDebug("SET APP {app} state = disabled", app.Id); } else if (state == "on") @@ -1322,14 +1301,13 @@ private async Task SetDependentState(string entityId, string state) // Enable all apps that this app is dependent on foreach (var depOnId in app.Dependencies) { - var depOnApp = InternalAllAppInstances.Values.Where(n => n.Id == depOnId).FirstOrDefault(); + var depOnApp = InternalAllAppInstances.Values.FirstOrDefault(n => n.Id == depOnId); if (depOnApp is not null) { await SetDependentState(depOnApp.EntityId, state).ConfigureAwait(false); - } } - await PersistAppStateAsync((NetDaemonAppBase)app); + await PersistAppStateAsync((NetDaemonAppBase)app).ConfigureAwait(false); Logger.LogDebug("SET APP {app} state = enabled", app.Id); } } @@ -1345,18 +1323,16 @@ private async Task SetDependentState(string entityId, string state) await SetStateAsync(entityId, state, attributes.ToArray()).ConfigureAwait(false); else await SetStateAsync(entityId, state).ConfigureAwait(false); - } //TODO: Refactor this private async Task PersistAppStateAsync(NetDaemonAppBase app) { - var obj = await GetDataAsync>(app.GetUniqueIdForStorage()).ConfigureAwait(false) ?? new Dictionary(); obj["__IsDisabled"] = !app.IsEnabled; - await SaveDataAsync>(app.GetUniqueIdForStorage(), obj); + await SaveDataAsync>(app.GetUniqueIdForStorage(), obj).ConfigureAwait(false); } private async Task RestoreAppState(INetDaemonAppBase appInstance) @@ -1403,7 +1379,7 @@ private async Task PostExternalEvent(ExternalEventBase ev) callbackTaskList.Add(Task.Run(() => callback(ev))); } - await callbackTaskList.WhenAll(_cancelToken); + await callbackTaskList.WhenAll(_cancelToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs index 5fe1d52be..f3c8fe37c 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs @@ -94,7 +94,7 @@ public override void Write( public static class ExpandoExtensions { - public static object? ParseString(string? strToParse) + public static object? ParseString(this string? strToParse) { if (DateTime.TryParse(strToParse, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTime)) return dateTime; diff --git a/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs b/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs index 72cff6bfd..063159e77 100644 --- a/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs +++ b/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs @@ -17,7 +17,6 @@ namespace NetDaemon.Service.Api { - public class ApiWebsocketMiddleware { private static readonly ConcurrentDictionary _sockets = new(); @@ -74,27 +73,21 @@ public async Task Invoke(HttpContext context) { if (!context.WebSockets.IsWebSocketRequest && context.Request.Path != "/api/ws") { - await _next.Invoke(context); + await _next.Invoke(context).ConfigureAwait(false); return; } CancellationToken ct = context.RequestAborted; - WebSocket? currentSocket = await context.WebSockets.AcceptWebSocketAsync(); + WebSocket? currentSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false); var socketId = Guid.NewGuid().ToString(); _sockets.TryAdd(socketId, currentSocket); _logger.LogDebug("New websocket client {socketId}", socketId); try { - - while (true) + while (!ct.IsCancellationRequested) { - if (ct.IsCancellationRequested) - { - break; - } - - var msg = await GetNextMessageAsync(currentSocket, ct); + var msg = await GetNextMessageAsync(currentSocket, ct).ConfigureAwait(false); if (currentSocket.State != WebSocketState.Open) { @@ -121,7 +114,7 @@ public async Task Invoke(HttpContext context) }) }; - await BroadCast(JsonSerializer.Serialize(eventMessage, _jsonOptions)); + await BroadCast(JsonSerializer.Serialize(eventMessage, _jsonOptions)).ConfigureAwait(false); break; case "settings": @@ -142,7 +135,7 @@ public async Task Invoke(HttpContext context) Data = tempResult }; - await BroadCast(JsonSerializer.Serialize(settingsMessage, _jsonOptions)); + await BroadCast(JsonSerializer.Serialize(settingsMessage, _jsonOptions)).ConfigureAwait(false); break; case "app": @@ -189,7 +182,7 @@ public async Task Invoke(HttpContext context) currentSocket.State == WebSocketState.CloseReceived || currentSocket.State == WebSocketState.CloseSent) { - await currentSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", ct); + await currentSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", ct).ConfigureAwait(false); } currentSocket.Dispose(); } @@ -204,7 +197,7 @@ public async Task BroadCast(string message, CancellationToken ct = default) continue; } - await SendStringAsync(socket.Value, message, ct); + await SendStringAsync(socket.Value, message, ct).ConfigureAwait(false); } } @@ -227,12 +220,11 @@ private static Task SendStringAsync(WebSocket socket, string data, CancellationT { ct.ThrowIfCancellationRequested(); - result = await socket.ReceiveAsync(buffer, ct); + result = await socket.ReceiveAsync(buffer, ct).ConfigureAwait(false); if (!result.CloseStatus.HasValue) ms.Write(buffer.Array, buffer.Offset, result.Count); else return null; - } while (!result.EndOfMessage); @@ -243,9 +235,8 @@ private static Task SendStringAsync(WebSocket socket, string data, CancellationT } using var reader = new StreamReader(ms, Encoding.UTF8); - var msgString = await reader.ReadToEndAsync(); + var msgString = await reader.ReadToEndAsync().ConfigureAwait(false); return JsonSerializer.Deserialize(msgString); } - } } \ No newline at end of file diff --git a/src/DaemonRunner/DaemonRunner/Service/ApiService.cs b/src/DaemonRunner/DaemonRunner/Service/ApiService.cs index 6c72b1e1b..56263cf82 100644 --- a/src/DaemonRunner/DaemonRunner/Service/ApiService.cs +++ b/src/DaemonRunner/DaemonRunner/Service/ApiService.cs @@ -22,7 +22,7 @@ namespace NetDaemon.Service { public class ApiStartup { - readonly bool _useAdmin = false; + private readonly bool _useAdmin = false; public ApiStartup(IConfiguration configuration) { @@ -49,7 +49,7 @@ public static void ConfigureServices(IServiceCollection services) public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - if (_useAdmin == true) + if (_useAdmin) { if (env.IsDevelopment()) { diff --git a/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs b/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs index 5c8be2b27..8bfa8b09b 100644 --- a/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs +++ b/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs @@ -17,7 +17,7 @@ namespace NetDaemon.Service.App public static class CodeGenerator { /// - /// Mapps the domain to corresponding implemented Fluent API, will be added as + /// Maps the domain to corresponding implemented Fluent API, will be added as /// more and more entity types are supported /// private static readonly IDictionary _FluentApiMapper = new Dictionary @@ -59,7 +59,7 @@ public static class CodeGenerator if (_FluentApiMapper.ContainsKey(domain)) { var camelCaseDomain = domain.ToCamelCase(); - var method = $@"public static {camelCaseDomain}Entities {camelCaseDomain}Ex(this NetDaemonApp app) => new {camelCaseDomain}Entities(app);"; + var method = $"public static {camelCaseDomain}Entities {camelCaseDomain}Ex(this NetDaemonApp app) => new {camelCaseDomain}Entities(app);"; var methodDeclaration = CSharpSyntaxTree.ParseText(method).GetRoot().ChildNodes().OfType().FirstOrDefault() ?? throw new NullReferenceException("Method parsing failed"); @@ -71,7 +71,6 @@ public static class CodeGenerator // Add the classes implementing the specific entities foreach (var domain in GetDomainsFromEntities(entities)) { - if (_FluentApiMapper.ContainsKey(domain)) { var classDeclaration = $@"public partial class {domain.ToCamelCase()}Entities @@ -100,8 +99,6 @@ public static class CodeGenerator var propDeclaration = CSharpSyntaxTree.ParseText(propertyCode).GetRoot().ChildNodes().OfType().FirstOrDefault() ?? throw new NullReferenceException("Property parsing failed!"); entityClass = entityClass.AddMembers(propDeclaration); - - } namespaceDeclaration = namespaceDeclaration.AddMembers(entityClass); } @@ -146,7 +143,7 @@ public static class CodeGenerator var isSingleServiceDomain = Array.IndexOf(singleServiceDomains, domain) != 0; var property = isSingleServiceDomain ? - $@"public {camelCaseDomain}Entities {camelCaseDomain} => new {camelCaseDomain}Entities(this);" : + $"public {camelCaseDomain}Entities {camelCaseDomain} => new {camelCaseDomain}Entities(this);" : $@"public {camelCaseDomain}Entity {camelCaseDomain} => new {domain.ToCamelCase()}Entity(this, new string[] {{""""}});"; var propertyDeclaration = CSharpSyntaxTree.ParseText(property).GetRoot().ChildNodes().OfType().FirstOrDefault() @@ -157,7 +154,6 @@ public static class CodeGenerator foreach (var domain in GetDomainsFromEntities(entities)) { - var classDeclaration = $@"public partial class {domain.ToCamelCase()}Entity : RxEntity {{ public string EntityId => EntityIds.First(); @@ -221,16 +217,13 @@ public static class CodeGenerator var methodDeclaration = CSharpSyntaxTree.ParseText(methodCode).GetRoot().ChildNodes().OfType().FirstOrDefault() ?? throw new NullReferenceException("Failed to parse method"); entityClass = entityClass.AddMembers(methodDeclaration); - } namespaceDeclaration = namespaceDeclaration.AddMembers(entityClass); - } // Add the classes implementing the specific entities foreach (var domain in GetDomainsFromEntities(entities)) { - var classDeclaration = $@"public partial class {domain.ToCamelCase()}Entities {{ private readonly {nameof(NetDaemonRxApp)} _app; @@ -244,7 +237,6 @@ public static class CodeGenerator ?? throw new NullReferenceException("Failed to parse entity class"); foreach (var entity in entities.Where(n => n.StartsWith(domain))) { - var name = entity[(entity.IndexOf(".") + 1)..]; // Quick check to make sure the name is a valid C# identifier. Should really check to make // sure it doesn't collide with a reserved keyword as well. diff --git a/src/DaemonRunner/DaemonRunner/Service/App/DaemonAppCompiler.cs b/src/DaemonRunner/DaemonRunner/Service/App/DaemonAppCompiler.cs index c6dfc5f62..2cdb5d7da 100644 --- a/src/DaemonRunner/DaemonRunner/Service/App/DaemonAppCompiler.cs +++ b/src/DaemonRunner/DaemonRunner/Service/App/DaemonAppCompiler.cs @@ -14,18 +14,16 @@ namespace NetDaemon.Service.App public class DaemonAppCompiler : IDaemonAppCompiler { private readonly ILogger _logger; - private readonly IOptions _netDaemonSettings; private readonly string? _sourceFolder = null; public DaemonAppCompiler(ILogger logger, IOptions netDaemonSettings) { _logger = logger; - _netDaemonSettings = netDaemonSettings; + NetDaemonSettings = netDaemonSettings; _sourceFolder = netDaemonSettings.Value.GetAppSourceDirectory(); - } - public IOptions NetDaemonSettings => _netDaemonSettings; + public IOptions NetDaemonSettings { get; } public IEnumerable GetApps() { diff --git a/src/DaemonRunner/DaemonRunner/Service/App/DaemonCompiler.cs b/src/DaemonRunner/DaemonRunner/Service/App/DaemonCompiler.cs index f8e24aa29..b2d3ae958 100644 --- a/src/DaemonRunner/DaemonRunner/Service/App/DaemonCompiler.cs +++ b/src/DaemonRunner/DaemonRunner/Service/App/DaemonCompiler.cs @@ -44,7 +44,7 @@ public static (IEnumerable, CollectibleAssemblyLoadContext?) GetDaemonApps if (compiledApps is not null) loadedApps.AddRange(compiledApps); - else if (string.IsNullOrEmpty(compileErrorText) == false) + else if (!string.IsNullOrEmpty(compileErrorText)) logger.LogError(compileErrorText); else if (loadedApps.Count == 0) logger.LogWarning("No .cs files files found, please add files to netdaemonfolder {codeFolder}", codeFolder); @@ -119,7 +119,6 @@ private static List LoadSyntaxTree(string codeFolder) } return result; - } public static IEnumerable GetDefaultReferences() @@ -151,7 +150,7 @@ public static IEnumerable GetDefaultReferences() metaDataReference.Add(MetadataReference.CreateFromFile(assembly.Location)); } - metaDataReference.Add(MetadataReference.CreateFromFile(Assembly.GetEntryAssembly()?.Location!)); + metaDataReference.Add(MetadataReference.CreateFromFile((Assembly.GetEntryAssembly()?.Location)!)); return metaDataReference; } @@ -161,7 +160,6 @@ private static CSharpCompilation GetCsCompilation(string codeFolder) var syntaxTrees = LoadSyntaxTree(codeFolder); var metaDataReference = GetDefaultReferences(); - return CSharpCompilation.Create( $"net_{Path.GetRandomFileName()}.dll", syntaxTrees.ToArray(), @@ -177,7 +175,7 @@ private static CSharpCompilation GetCsCompilation(string codeFolder) private static void PrettyPrintCompileError(EmitResult emitResult, ILogger logger) { var msg = new StringBuilder(); - msg.AppendLine($"Compiler error!"); + msg.AppendLine("Compiler error!"); foreach (var emitResultDiagnostic in emitResult.Diagnostics) { @@ -244,7 +242,7 @@ private static void InterceptAppInfo(SyntaxTree syntaxTree, CSharpCompilation co comment += commentRow + "\n"; } - var app_key = symbol.ContainingNamespace.Name == "" ? symbol.Name : symbol.ContainingNamespace.Name + "." + symbol.Name; + var app_key = symbol.ContainingNamespace.Name?.Length == 0 ? symbol.Name : symbol.ContainingNamespace.Name + "." + symbol.Name; if (!NetDaemonAppBase.CompileTimeProperties.ContainsKey(app_key)) { @@ -277,10 +275,12 @@ private static void WarnIfExecuteIsMissing(SyntaxTree syntaxTree, CSharpCompilat continue; if (string.IsNullOrEmpty(symbol?.Name) || - ExecuteWarningOnInvocationNames.Contains(symbol?.Name) == false) + !ExecuteWarningOnInvocationNames.Contains(symbol?.Name)) + { // The invocation name is empty or not in list of invocations // that needs to be closed with Execute or ExecuteAsync continue; + } // Now find top invocation to match whole expression InvocationExpressionSyntax topInvocationExpression = invocationExpression; @@ -313,10 +313,10 @@ private static void WarnIfExecuteIsMissing(SyntaxTree syntaxTree, CSharpCompilat // Now when we have the top InvocationExpression, // lets check for Execute and ExecuteAsync - if (ExpressionContainsExecuteInvocations(topInvocationExpression) == false && disableLogging == false) + if (!ExpressionContainsExecuteInvocations(topInvocationExpression) && !disableLogging) { var x = syntaxTree.GetLineSpan(topInvocationExpression.Span); - if (linesReported.Contains(x.StartLinePosition.Line) == false) + if (!linesReported.Contains(x.StartLinePosition.Line)) { logger.LogError($"Missing Execute or ExecuteAsync in {syntaxTree.FilePath} ({x.StartLinePosition.Line + 1},{x.StartLinePosition.Character + 1}) near {topInvocationExpression.ToFullString().Trim()}"); linesReported.Add(x.StartLinePosition.Line); @@ -330,11 +330,7 @@ private static void WarnIfExecuteIsMissing(SyntaxTree syntaxTree, CSharpCompilat private static bool ExpressionContainsDisableLogging(MethodDeclarationSyntax methodInvocationExpression) { var invocationString = methodInvocationExpression.ToFullString(); - if (invocationString.Contains("[DisableLog") && invocationString.Contains("SupressLogType.MissingExecute")) - { - return true; - } - return false; + return invocationString.Contains("[DisableLog") && invocationString.Contains("SupressLogType.MissingExecute"); } // Todo: Refactor using something smarter than string match. In the future use Roslyn @@ -342,12 +338,7 @@ private static bool ExpressionContainsExecuteInvocations(InvocationExpressionSyn { var invocationString = invocation.ToFullString(); - if (invocationString.Contains("ExecuteAsync()") || invocationString.Contains("Execute()")) - { - return true; - } - - return false; + return invocationString.Contains("ExecuteAsync()") || invocationString.Contains("Execute()"); } } } \ No newline at end of file diff --git a/src/DaemonRunner/DaemonRunner/Service/App/LocalDaemonAppCompiler.cs b/src/DaemonRunner/DaemonRunner/Service/App/LocalDaemonAppCompiler.cs index a6beda61c..f78bbf6b6 100644 --- a/src/DaemonRunner/DaemonRunner/Service/App/LocalDaemonAppCompiler.cs +++ b/src/DaemonRunner/DaemonRunner/Service/App/LocalDaemonAppCompiler.cs @@ -26,7 +26,7 @@ public IEnumerable GetApps() var assemblies = LoadAll(); var apps = assemblies.SelectMany(x => x.GetTypesWhereSubclassOf()).ToList(); - if (!apps.Any()) + if (apps.Count == 0) _logger.LogWarning("No local daemon apps found."); else _logger.LogDebug("Found total of {nr_of_apps} apps", apps.Count); diff --git a/src/DevelopmentApps/apps/DebugApp/DebugApp.cs b/src/DevelopmentApps/apps/DebugApp/DebugApp.cs index c0bcccb35..7305fd1e0 100644 --- a/src/DevelopmentApps/apps/DebugApp/DebugApp.cs +++ b/src/DevelopmentApps/apps/DebugApp/DebugApp.cs @@ -4,14 +4,13 @@ namespace NetDaemon.DevelopmentApps.apps.DebugApp { - - /// Use this class as startingpoint for debugging + /// Use this class as startingpoint for debugging /// public class DebugApp : NetDaemonRxApp { // Use two guids, one when instanced and one when initialized // can track errors with instancing - Guid _instanceId = Guid.NewGuid(); + private Guid _instanceId = Guid.NewGuid(); public DebugApp() : base() { } @@ -28,5 +27,4 @@ public void CallMeFromHass(dynamic data) Log("A call from hass! {data}", data); } } - } diff --git a/src/Fakes/NetDaemon.Fakes/HassClientMock.cs b/src/Fakes/NetDaemon.Fakes/HassClientMock.cs index ed8b2618f..dca29af7c 100644 --- a/src/Fakes/NetDaemon.Fakes/HassClientMock.cs +++ b/src/Fakes/NetDaemon.Fakes/HassClientMock.cs @@ -57,13 +57,13 @@ public HassClientMock() SetupGet(x => x.States).Returns(FakeStates); Setup(x => x.GetAllStates(It.IsAny())) - .ReturnsAsync(() => { return (IEnumerable)FakeStates.Values; }); + .ReturnsAsync(() => (IEnumerable)FakeStates.Values); Setup(x => x.ReadEventAsync()) - .ReturnsAsync(() => { return FakeEvents.TryDequeue(out var ev) ? ev : null; }); + .ReturnsAsync(() => FakeEvents.TryDequeue(out var ev) ? ev : null); Setup(x => x.ReadEventAsync(It.IsAny())) - .ReturnsAsync(() => { return FakeEvents.TryDequeue(out var ev) ? ev : null; }); + .ReturnsAsync(() => FakeEvents.TryDequeue(out var ev) ? ev : null); Setup(x => x.GetConfig()).ReturnsAsync(new HassConfig { State = "RUNNING" }); @@ -93,7 +93,7 @@ public HassClientMock() Areas.Add(new HassArea { Name = "Area", Id = "area_idd" }); Entities.Add(new HassEntity { - EntityId = "light.ligth_in_area", + EntityId = "light.light_in_area", DeviceId = "device_idd" }); } @@ -126,7 +126,7 @@ public static HassClientMock MockConnectFalse /// Data sent by service public void AddCallServiceEvent(string domain, string service, dynamic? data = null) { - // Todo: Refactor to smth smarter + // Todo: Refactor to something smarter FakeEvents.Enqueue(new HassEvent { EventType = "call_service", @@ -149,7 +149,7 @@ public void AddCallServiceEvent(string domain, string service, dynamic? data = n /// Last changed public void AddChangedEvent(string entityId, object fromState, object toState, DateTime lastUpdated, DateTime lastChanged) { - // Todo: Refactor to smth smarter + // Todo: Refactor to something smarter FakeEvents.Enqueue(new HassEvent { EventType = "state_changed", @@ -250,7 +250,7 @@ public void AddCustomEvent(string eventType, dynamic? data) } /// - /// Assert if the HassClient entity state is equeal to NetDaemon entity state + /// Assert if the HassClient entity state is equals to NetDaemon entity state /// /// HassClient state instance /// NetDaemon state instance @@ -267,7 +267,7 @@ public static void AssertEqual(HassState hassState, EntityState entity) foreach (var attribute in hassState.Attributes!.Keys) { var attr = entity.Attribute as IDictionary ?? - throw new NullReferenceException($"{nameof(entity.Attribute)} catn be null"); + throw new NullReferenceException($"{nameof(entity.Attribute)} cant be null"); Assert.True(attr.ContainsKey(attribute)); Assert.Equal(hassState.Attributes[attribute], @@ -317,7 +317,6 @@ public void VerifyCallService(string domain, string service, object? data = null Verify(n => n.CallService(domain, service, data!, waitForResponse), times.Value); else Verify(n => n.CallService(domain, service, data!, waitForResponse), Times.AtLeastOnce); - } /// @@ -345,8 +344,6 @@ public void VerifyCallServiceTimes(string service, Times times) Verify(n => n.CallService(It.IsAny(), service, It.IsAny(), It.IsAny()), times); } - - /// /// Verify state if entity /// diff --git a/src/Fakes/NetDaemon.Fakes/LoggerMock.cs b/src/Fakes/NetDaemon.Fakes/LoggerMock.cs index eb58b1ee3..d6c071ece 100644 --- a/src/Fakes/NetDaemon.Fakes/LoggerMock.cs +++ b/src/Fakes/NetDaemon.Fakes/LoggerMock.cs @@ -47,9 +47,9 @@ public void AssertLogged(LogLevel level, Times times) x => x.Log( level, It.IsAny(), - It.Is((v, t) => true), + It.Is((_, __) => true), It.IsAny(), - It.Is>((v, t) => true)), times); + It.Is>((_, _) => true)), times); } /// @@ -64,9 +64,9 @@ public void AssertLogged(LogLevel level, string message, Times times) x => x.Log( level, It.IsAny(), - It.Is((v, t) => v.ToString() == message), + It.Is((v, _) => v.ToString() == message), It.IsAny(), - It.Is>((v, t) => true)), times); + It.Is>((_, _) => true)), times); } /// @@ -82,9 +82,9 @@ public void AssertLogged(LogLevel level, Exception exception, string message, Ti x => x.Log( level, It.IsAny(), - It.Is((v, t) => v.ToString() == message), + It.Is((v, _) => v.ToString() == message), exception, - It.Is>((v, t) => true)), times); + It.Is>((_, _) => true)), times); } } } \ No newline at end of file diff --git a/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs b/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs index 530100290..caad4c270 100644 --- a/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs @@ -13,7 +13,7 @@ namespace NetDaemon.Daemon.Tests.Daemon { public class DataRepositoryTests : DaemonHostTestBase { - public static readonly string DataReposityryPath = + public static readonly string DataRepositoryPath = Path.Combine(AppContext.BaseDirectory, "datarepository"); public DataRepositoryTests() : base() @@ -27,7 +27,7 @@ public async Task GetNonExistantValueShouldReturnNull() var daemon = DefaultDaemonHost; // ACT - var data = await daemon.GetDataAsync("not_exists"); + var data = await daemon.GetDataAsync("not_exists").ConfigureAwait(false); // ASSERT Assert.Null(data); } @@ -42,7 +42,7 @@ public async Task SavedDataShouldReturnSameDataUsingExpando() // ACT await daemon.SaveDataAsync("data_exists", data); - var collectedData = await daemon.GetDataAsync("data_exists"); + var collectedData = await daemon.GetDataAsync("data_exists").ConfigureAwait(false); // ASSERT Assert.Equal(data, collectedData); @@ -55,9 +55,9 @@ public async Task GetDataShouldReturnCachedValue() var daemon = DefaultDaemonHost; // ACT - await daemon.SaveDataAsync("GetDataShouldReturnCachedValue_id", "saved data"); + await daemon.SaveDataAsync("GetDataShouldReturnCachedValue_id", "saved data").ConfigureAwait(false); - await daemon.GetDataAsync("GetDataShouldReturnCachedValue_id"); + await daemon.GetDataAsync("GetDataShouldReturnCachedValue_id").ConfigureAwait(false); // ASSERT DefaultDataRepositoryMock.Verify(n => n.Get(It.IsAny()), Times.Never); @@ -68,7 +68,7 @@ public async Task GetDataShouldReturnCachedValue() public async Task RepositoryLoadSavedDataUsingExpando() { // ARRANGE - var dataRepository = new DataRepository(DataReposityryPath); + var dataRepository = new DataRepository(DataRepositoryPath); dynamic dataBeingSaved = new FluentExpandoObject(false, true); dataBeingSaved.SomeString = "this data should be saved!"; dataBeingSaved.SomeInt = 123456; @@ -78,7 +78,7 @@ public async Task RepositoryLoadSavedDataUsingExpando() // ACT await dataRepository.Save>("RepositoryLoadSavedData_id", dataBeingSaved); - var dataReturned = await dataRepository.Get>("RepositoryLoadSavedData_id"); + var dataReturned = await dataRepository.Get>("RepositoryLoadSavedData_id").ConfigureAwait(false); var returnedFluentExpandoObject = new FluentExpandoObject(); returnedFluentExpandoObject.CopyFrom(dataReturned!); @@ -93,7 +93,7 @@ public async Task RepositoryLoadSavedDataUsingExpando() Assert.Equal(dataBeingSaved.SomeString, dynamicDataReturned?.SomeString); Assert.Equal(dataBeingSaved.SomeInt, dynamicDataReturned!.SomeInt); Assert.Equal(dataBeingSaved.SomeFloat, dynamicDataReturned!.SomeFloat); - // There is no way for json serilizer to know this is a datetime + // There is no way for json serializer to know this is a datetime Assert.NotNull(dynamicDataReturned!.SomeDateTime); } @@ -101,7 +101,7 @@ public async Task RepositoryLoadSavedDataUsingExpando() public async Task RepositoryShouldLoadSavedDataUsingDto() { // ARRANGE - var dataRepository = new DataRepository(DataReposityryPath); + var dataRepository = new DataRepository(DataRepositoryPath); var storeData = new TestStorage { AString = "Some String", @@ -109,9 +109,9 @@ public async Task RepositoryShouldLoadSavedDataUsingDto() ADateTime = DateTime.Now }; // ACT - await dataRepository.Save("RepositoryShouldLoadSavedDataUsingDto_id", storeData); + await dataRepository.Save("RepositoryShouldLoadSavedDataUsingDto_id", storeData).ConfigureAwait(false); - var dataReturned = await dataRepository.Get("RepositoryShouldLoadSavedDataUsingDto_id"); + var dataReturned = await dataRepository.Get("RepositoryShouldLoadSavedDataUsingDto_id").ConfigureAwait(false); // ASSERT Assert.Equal(storeData.AString, dataReturned!.AString); diff --git a/tests/NetDaemon.Daemon.Tests/Daemon/ExtensionMethodsTests.cs b/tests/NetDaemon.Daemon.Tests/Daemon/ExtensionMethodsTests.cs index ec883bb21..97991bb31 100644 --- a/tests/NetDaemon.Daemon.Tests/Daemon/ExtensionMethodsTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Daemon/ExtensionMethodsTests.cs @@ -19,7 +19,7 @@ public void JsonElementToDynamicValueWhenStringShouldReturnString() var prop = doc.RootElement.GetProperty("str"); // ACT - var obj = JsonElementExtensions.ConvertToDynamicValue(prop); + var obj = prop.ConvertToDynamicValue(); // ASSERT Assert.IsType(obj); @@ -34,7 +34,7 @@ public void JsonElementToDynamicValueWhenTrueShouldReturnBoolTrue() var prop = doc.RootElement.GetProperty("bool"); // ACT - var obj = JsonElementExtensions.ConvertToDynamicValue(prop); + var obj = prop.ConvertToDynamicValue(); // ASSERT Assert.IsType(obj); @@ -49,7 +49,7 @@ public void JsonElementToDynamicValueWhenFalseShouldReturnBoolFalse() var prop = doc.RootElement.GetProperty("bool"); // ACT - var obj = JsonElementExtensions.ConvertToDynamicValue(prop); + var obj = prop.ConvertToDynamicValue(); // ASSERT Assert.IsType(obj); @@ -62,9 +62,9 @@ public void JsonElementToDynamicValueWhenIntegerShouldReturnInteger() // ARRANGE var doc = JsonDocument.Parse("{\"int\": 10}"); var prop = doc.RootElement.GetProperty("int"); - long expectedValue = 10; + const long expectedValue = 10; // ACT - var obj = JsonElementExtensions.ConvertToDynamicValue(prop); + var obj = prop.ConvertToDynamicValue(); // ASSERT Assert.IsType(obj); @@ -77,9 +77,9 @@ public void JsonElementToDynamicValueWhenDoubleShouldReturnDouble() // ARRANGE var doc = JsonDocument.Parse("{\"int\": 10.5}"); var prop = doc.RootElement.GetProperty("int"); - double expectedValue = 10.5; + const double expectedValue = 10.5; // ACT - var obj = JsonElementExtensions.ConvertToDynamicValue(prop); + var obj = prop.ConvertToDynamicValue(); // ASSERT Assert.IsType(obj); @@ -94,7 +94,7 @@ public void JsonElementToDynamicValueWhenArrayShouldReturnArray() var prop = doc.RootElement.GetProperty("array"); // ACT - var obj = JsonElementExtensions.ConvertToDynamicValue(prop); + var obj = prop.ConvertToDynamicValue(); var arr = obj as IEnumerable; // ASSERT @@ -109,7 +109,7 @@ public void JsonElementToDynamicValueWhenObjectShouldReturnObject() var doc = JsonDocument.Parse("{\"str\": \"string\"}"); // ACT - var obj = JsonElementExtensions.ConvertToDynamicValue(doc.RootElement) as IDictionary; + var obj = doc.RootElement.ConvertToDynamicValue() as IDictionary; // ASSERT Assert.Equal("string", obj?["str"]); @@ -119,7 +119,7 @@ public void JsonElementToDynamicValueWhenObjectShouldReturnObject() public void ParseDataTypeIntShouldReturnInt() { // ARRANGE - long expectedValue = 10; + const long expectedValue = 10; // ACT var longValue = StringParser.ParseDataType("10"); // ASSERT @@ -131,12 +131,12 @@ public void ParseDataTypeIntShouldReturnInt() public void ParseDataTypeDoubleShouldReturnInt() { // ARRANGE - double expectedValue = 10.5; + const double expectedValue = 10.5; // ACT - var doubeValue = StringParser.ParseDataType("10.5"); + var doubleValue = StringParser.ParseDataType("10.5"); // ASSERT - Assert.IsType(doubeValue); - Assert.Equal(expectedValue, doubeValue); + Assert.IsType(doubleValue); + Assert.Equal(expectedValue, doubleValue); } [Fact] diff --git a/tests/NetDaemon.Daemon.Tests/Daemon/HttpHandlerMock.cs b/tests/NetDaemon.Daemon.Tests/Daemon/HttpHandlerMock.cs deleted file mode 100644 index a44db111c..000000000 --- a/tests/NetDaemon.Daemon.Tests/Daemon/HttpHandlerMock.cs +++ /dev/null @@ -1,74 +0,0 @@ -// using Moq; -// using System.Net; -// using System.Net.Http; -// using System.Text; -// using System.Threading; -// using System.Threading.Tasks; -// using NetDaemon.Common; - -// namespace NetDaemon.Daemon.Tests.Daemon -// { -// public class HttpClientFactoryMock : Mock -// { -// private HttpClient? _httpClient; -// private MockHttpMessageHandler? _handler; -// public MockHttpMessageHandler? MessageHandler => _handler; - -// public HttpClientFactoryMock() -// { -// } - -// public void SetResponse(string response, HttpStatusCode statusCode = HttpStatusCode.OK) -// { -// _handler = new MockHttpMessageHandler(response, statusCode); -// _httpClient = new HttpClient(_handler); -// Setup(x => x.CreateClient(It.IsAny())).Returns(_httpClient!); -// } -// } - -// public class HttpHandlerMock : Mock -// { -// private HttpClient? _httpClient; -// private MockHttpMessageHandler? _handler; - -// public MockHttpMessageHandler? MessageHandler => _handler; - -// public HttpHandlerMock() -// { -// } - -// public void SetResponse(string response, HttpStatusCode statusCode = HttpStatusCode.OK) -// { -// _handler = new MockHttpMessageHandler(response, statusCode); -// _httpClient = new HttpClient(_handler); -// Setup(x => x.CreateHttpClient(It.IsAny())).Returns(_httpClient!); -// } -// } - -// public class MockHttpMessageHandler : HttpMessageHandler -// { -// private readonly string _response; -// private readonly HttpStatusCode _StatusCode; - -// private string? _requestContent; - -// public string? RequestContent => _requestContent; - -// public MockHttpMessageHandler(string response, HttpStatusCode statusCode = HttpStatusCode.OK) -// { -// _response = response; -// _StatusCode = statusCode; -// } - -// protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) -// { -// var responseMessage = new HttpResponseMessage(_StatusCode); - -// if (request is not null && request.Content is not null) -// _requestContent = await request.Content.ReadAsStringAsync().ConfigureAwait(false); - -// responseMessage.Content = new ByteArrayContent(Encoding.ASCII.GetBytes(_response)); -// return responseMessage; -// } -// } -// } \ No newline at end of file diff --git a/tests/NetDaemon.Daemon.Tests/Daemon/HttpTests.cs b/tests/NetDaemon.Daemon.Tests/Daemon/HttpTests.cs index 81f89d49d..e0392f5db 100644 --- a/tests/NetDaemon.Daemon.Tests/Daemon/HttpTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Daemon/HttpTests.cs @@ -23,7 +23,7 @@ public HttpTests() : base() public async Task HttpClientShouldReturnCorrectContent() { // ARRANGE - var response = "{\"json_prop\", \"hello world\"}"; + const string? response = "{\"json_prop\", \"hello world\"}"; DefaultHttpHandlerMock.SetResponse(response); // ACT @@ -38,7 +38,7 @@ public async Task HttpClientShouldReturnCorrectContent() public void HttpClientShouldNotReturnContentOnBadStatusCode() { // ARRANGE - var response = ""; + const string? response = ""; DefaultHttpHandlerMock.SetResponse(response, HttpStatusCode.NotFound); // ACT @@ -52,7 +52,7 @@ public void HttpClientShouldNotReturnContentOnBadStatusCode() public async Task HttpClientShouldReturnCorrectStatusCode() { // ARRANGE - var response = "{\"json_prop\", \"hello world\"}"; + const string? response = "{\"json_prop\", \"hello world\"}"; DefaultHttpHandlerMock.SetResponse(response); // ACT @@ -67,7 +67,7 @@ public async Task HttpClientShouldReturnCorrectStatusCode() public async Task HttpClientShouldReturnCorrectStatusCodeError() { // ARRANGE - var response = "{\"json_prop\", \"hello world\"}"; + const string? response = "{\"json_prop\", \"hello world\"}"; DefaultHttpHandlerMock.SetResponse(response, HttpStatusCode.Forbidden); // ACT @@ -82,7 +82,7 @@ public async Task HttpClientShouldReturnCorrectStatusCodeError() public async Task HttpHandlerGetJsonShouldReturnCorrectContent() { // ARRANGE - var response = "{\"json_prop\": \"hello world\"}"; + const string? response = "{\"json_prop\": \"hello world\"}"; HttpClientFactoryMock factoryMock = new(); factoryMock.SetResponse(response); @@ -100,7 +100,7 @@ public async Task HttpHandlerGetJsonShouldReturnCorrectContent() public void HttpHandlerGetJsonBadFormatShouldReturnThrowException() { // ARRANGE - var response = "{\"json_prop\": \"hello world\"}"; + const string? response = "{\"json_prop\": \"hello world\"}"; HttpClientFactoryMock factoryMock = new(); factoryMock.SetResponse(response); @@ -115,7 +115,7 @@ public void HttpHandlerGetJsonBadFormatShouldReturnThrowException() public async Task HttpHandlerPostJsonShouldReturnCorrectContent() { // ARRANGE - var response = "{\"json_prop\": \"hello world\"}"; + const string? response = "{\"json_prop\": \"hello world\"}"; HttpClientFactoryMock factoryMock = new(); factoryMock.SetResponse(response); @@ -134,7 +134,7 @@ public async Task HttpHandlerPostJsonShouldReturnCorrectContent() public async Task HttpHandlerPostJsonNoReturnShouldReturnCorrectContent() { // ARRANGE - var response = "{\"json_prop\": \"hello world\"}"; + const string? response = "{\"json_prop\": \"hello world\"}"; HttpClientFactoryMock factoryMock = new(); factoryMock.SetResponse(response); diff --git a/tests/NetDaemon.Daemon.Tests/Daemon/NetDaemonHostTests.cs b/tests/NetDaemon.Daemon.Tests/Daemon/NetDaemonHostTests.cs index 8e0b09bdd..788ca1a70 100644 --- a/tests/NetDaemon.Daemon.Tests/Daemon/NetDaemonHostTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Daemon/NetDaemonHostTests.cs @@ -28,7 +28,6 @@ public async Task EventShouldCallCorrectFunction() // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); - dynamic helloWorldDataObject = DaemonHostTestBase.GetDynamicDataObject(HelloWorldData); DefaultHassClientMock.AddCustomEvent("CUSTOM_EVENT", helloWorldDataObject); @@ -37,7 +36,7 @@ public async Task EventShouldCallCorrectFunction() var message = ""; // ACT - DefaultDaemonApp.ListenEvent("CUSTOM_EVENT", (ev, data) => + DefaultDaemonApp.ListenEvent("CUSTOM_EVENT", (_, data) => { isCalled = true; message = data.Test; @@ -56,7 +55,7 @@ public async Task AttributeServiceCallShouldFindCorrectFunction() { // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); - var app = new AssmeblyDaemonApp + var app = new AssemblyDaemonApp { Id = "id" }; @@ -115,7 +114,7 @@ public async Task OtherEventShouldNotCallCorrectFunction() var isCalled = false; // ACT - DefaultDaemonApp.ListenEvent("OTHER_EVENT", (ev, data) => + DefaultDaemonApp.ListenEvent("OTHER_EVENT", (_, _) => { isCalled = true; return Task.CompletedTask; @@ -134,7 +133,7 @@ public async Task RunNotConnectedCompletesTask() // ACTION var (runTask, _) = ReturnRunningNotConnectedDaemonHostTask(); - await runTask; + await runTask.ConfigureAwait(false); // ASSERT Assert.True(runTask.IsCompleted); @@ -157,7 +156,6 @@ public async Task SendEventShouldCallCorrectMethod() await InitializeFakeDaemon().ConfigureAwait(false); var eventData = DaemonHostTestBase.GetDynamicDataObject(); - // ACT await DefaultDaemonHost.SendEvent("test_event", eventData); @@ -174,7 +172,7 @@ public async Task SendEventWithNullDataShouldCallCorrectMethod() // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); // ACT - await DefaultDaemonHost.SendEvent("test_event"); + await DefaultDaemonHost.SendEvent("test_event").ConfigureAwait(false); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); // ASSERT @@ -237,20 +235,19 @@ public async Task SpeakShouldWaitUntilMediaPlays() // ASSERT - await Task.Delay(50); + await Task.Delay(50).ConfigureAwait(false); VerifyCallService("tts", "google_cloud_say", expectedAttributesExpObject, true, Times.Once()); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); // Called twice VerifyCallService("tts", "google_cloud_say", expectedAttributesExpObject, true, Times.Exactly(2)); - } [Fact] public async Task StopCallsCloseClient() { - await DefaultDaemonHost.Stop(); + await DefaultDaemonHost.Stop().ConfigureAwait(false); DefaultHassClientMock.Verify(n => n.CloseAsync(), Times.Once); } @@ -264,7 +261,7 @@ public async Task SubscribeChangedStateForEntityWillMakeCorrectCallback() string? reportedState = ""; // ACT - DefaultDaemonApp.ListenState("binary_sensor.pir", (entityId, newState, oldState) => + DefaultDaemonApp.ListenState("binary_sensor.pir", (_, newState, _) => { reportedState = newState?.State; @@ -287,7 +284,7 @@ public async Task SubscribeChangedStateForAllChangesWillMakeCorrectCallbacks() int nrOfTimesCalled = 0; // ACT - DefaultDaemonApp.ListenState("", (entityId, newState, oldState) => + DefaultDaemonApp.ListenState("", (_, _, _) => { nrOfTimesCalled++; @@ -297,7 +294,7 @@ public async Task SubscribeChangedStateForAllChangesWillMakeCorrectCallbacks() AddChangedEvent("binary_sensor.pir", fromState: "off", toState: "on"); AddChangedEvent("light.mylight", fromState: "on", toState: "off"); - await RunFakeDaemonUntilTimeout(); + await RunFakeDaemonUntilTimeout().ConfigureAwait(false); // ASSERT Assert.Equal(2, nrOfTimesCalled); @@ -329,7 +326,7 @@ public async Task CancelChangedStateForSubscriptionWillNotMakeCallback() bool isCalled = false; // ACT - var id = DefaultDaemonApp.ListenState("binary_sensor.pir", (entityId, newState, oldState) => + var id = DefaultDaemonApp.ListenState("binary_sensor.pir", (_, _, _) => { isCalled = true; @@ -354,7 +351,6 @@ public async Task CallServiceEventShouldCallCorrectFunction() await InitializeFakeDaemon().ConfigureAwait(false); var dynObject = DaemonHostTestBase.GetDynamicDataObject(HelloWorldData); - var isCalled = false; string? message = ""; @@ -403,7 +399,7 @@ public async Task CallServiceEventOtherShouldNotCallFunction() [Fact] public async Task SetStateShouldCallCorrectFunction() { - await DefaultDaemonHost.SetStateAsync("sensor.any_sensor", "on", ("attr", "value")); + await DefaultDaemonHost.SetStateAsync("sensor.any_sensor", "on", ("attr", "value")).ConfigureAwait(false); var (dynObj, expObj) = GetDynamicObject( ("attr", "value") @@ -414,7 +410,7 @@ public async Task SetStateShouldCallCorrectFunction() [Fact] public async Task SetStateShouldReturnCorrectData() { - await DefaultDaemonHost.SetStateAsync("sensor.any_sensor", "on", ("attr", "value")); + await DefaultDaemonHost.SetStateAsync("sensor.any_sensor", "on", ("attr", "value")).ConfigureAwait(false); var (dynObj, expObj) = GetDynamicObject( ("attr", "value") @@ -459,7 +455,7 @@ public async Task DelayStateChangeWithToAndFromShouldReturnTrue() [Fact] public async Task DelayStateChangeWithToAndFromWrongShouldNotComplete() { - // ARRANGE + // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); // ACT @@ -480,7 +476,7 @@ public async Task DelayStateLambdaChangeShouldReturnTrue() // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); // ACT - using var delayResult = DefaultDaemonApp.DelayUntilStateChange(new string[] { "binary_sensor.pir" }, (n, o) => n?.State == "on"); + using var delayResult = DefaultDaemonApp.DelayUntilStateChange(new string[] { "binary_sensor.pir" }, (n, _) => n?.State == "on"); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", fromState: "off", toState: "on"); @@ -497,7 +493,7 @@ public async Task DelayStateLambdaChangeShouldNotComplete() // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); // ACT - using var delayResult = DefaultDaemonApp.DelayUntilStateChange(new string[] { "binary_sensor.pir" }, (n, o) => n?.State == "on"); + using var delayResult = DefaultDaemonApp.DelayUntilStateChange(new string[] { "binary_sensor.pir" }, (n, _) => n?.State == "on"); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", fromState: "on", toState: "off"); @@ -553,7 +549,7 @@ public async Task ClearShouldReturnNullGetApp() } [Fact] - public void EntityShouldReturCorrectValueForArea() + public void EntityShouldReturnCorrectValueForArea() { // ARRANGE DefaultDaemonHost._hassDevices["device_id"] = new HassDevice { AreaId = "area_id" }; @@ -571,7 +567,7 @@ public void EntityShouldReturCorrectValueForArea() } [Fact] - public void EntityShouldReturNullForAreaNotExist() + public void EntityShouldReturnNullForAreaNotExist() { // ARRANGE DefaultDaemonHost._hassDevices["device_id"] = new HassDevice { AreaId = "area_id" }; @@ -617,7 +613,7 @@ public async Task SetStateShouldKeepSameArea() // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); // ACT - var state = await DefaultDaemonHost.SetStateAsync("light.ligth_in_area", "on", ("attr", "value")); + var state = await DefaultDaemonHost.SetStateAsync("light.light_in_area", "on", ("attr", "value")).ConfigureAwait(false); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); /// ASSERT Assert.Equal("Area", state?.Area); diff --git a/tests/NetDaemon.Daemon.Tests/DaemonRunner/Api/ApiTests.cs b/tests/NetDaemon.Daemon.Tests/DaemonRunner/Api/ApiTests.cs index 58701b968..1e1468563 100644 --- a/tests/NetDaemon.Daemon.Tests/DaemonRunner/Api/ApiTests.cs +++ b/tests/NetDaemon.Daemon.Tests/DaemonRunner/Api/ApiTests.cs @@ -92,10 +92,10 @@ public void ConfigureServices(IServiceCollection services) services.Configure(Configuration.GetSection("HomeAssistant")); services.Configure(Configuration.GetSection("NetDaemon")); - services.AddTransient(n => _defaultHassClientMock.Object); - services.AddTransient(n => _defaultDataRepositoryMock.Object); + services.AddTransient(_ => _defaultHassClientMock.Object); + services.AddTransient(_ => _defaultDataRepositoryMock.Object); services.AddTransient(); - services.AddSingleton(n => _defaultDaemonHost); + services.AddSingleton(_ => _defaultDaemonHost); services.AddHttpClient(); } @@ -116,18 +116,16 @@ public class ApiTests : IAsyncLifetime // protected readonly EventQueueManager EventQueueManager; private readonly TestServer _server; - private readonly ArraySegment _buffer; - private readonly JsonSerializerOptions _jsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - public ArraySegment Buffer => _buffer; + public ArraySegment Buffer { get; } public ApiTests() { - _buffer = new ArraySegment(new byte[8192]); + Buffer = new ArraySegment(new byte[8192]); var builder = WebHost.CreateDefaultBuilder() .UseEnvironment("Testing") @@ -155,7 +153,7 @@ private static async Task ReadString(WebSocket ws) WebSocketReceiveResult result; do { - result = await ws.ReceiveAsync(buffer, CancellationToken.None); + result = await ws.ReceiveAsync(buffer, CancellationToken.None).ConfigureAwait(false); ms.Write(buffer.Array, buffer.Offset, result.Count); } while (!result.EndOfMessage); @@ -167,7 +165,7 @@ private static async Task ReadString(WebSocket ws) } using var reader = new StreamReader(ms, Encoding.UTF8); - return await reader.ReadToEndAsync(); + return await reader.ReadToEndAsync().ConfigureAwait(false); } private async Task GetWsClient() @@ -178,21 +176,20 @@ private async Task GetWsClient() private async Task ReadObject(WebSocket ws, Type t) { - var s = await ReadString(ws); + var s = await ReadString(ws).ConfigureAwait(false); return JsonSerializer.Deserialize(s, t, _jsonOptions); } [Fact] public async Task TestGetApps() { + var websocket = await GetWsClient().ConfigureAwait(false); - var websocket = await GetWsClient(); - - await websocket.SendAsync(Encoding.UTF8.GetBytes(@"{""type"": ""apps""}"), WebSocketMessageType.Text, true, CancellationToken.None); + await websocket.SendAsync(Encoding.UTF8.GetBytes(@"{""type"": ""apps""}"), WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false); - var res = (WsAppsResult?)await ReadObject(websocket, typeof(WsAppsResult)); + var res = (WsAppsResult?)await ReadObject(websocket, typeof(WsAppsResult)).ConfigureAwait(false); Assert.NotNull(res); - var response = res?.Data!; + var response = (res?.Data)!; Assert.Equal(4, response?.Count()); @@ -216,18 +213,17 @@ public async Task TestGetApps() [Fact] public async Task TestGetConfig() { - var websocket = await GetWsClient(); + var websocket = await GetWsClient().ConfigureAwait(false); - await websocket.SendAsync(Encoding.UTF8.GetBytes(@"{""type"": ""settings""}"), WebSocketMessageType.Text, true, CancellationToken.None); + await websocket.SendAsync(Encoding.UTF8.GetBytes(@"{""type"": ""settings""}"), WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false); - var res = (WsConfigResult?)await ReadObject(websocket, typeof(WsConfigResult)); + var res = (WsConfigResult?)await ReadObject(websocket, typeof(WsConfigResult)).ConfigureAwait(false); Assert.NotNull(res); var response = res?.Data; Assert.NotNull(response); Assert.NotNull(response?.DaemonSettings?.AppSource); - } } } \ No newline at end of file diff --git a/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/AssemblyDaemonApps.cs b/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/AssemblyDaemonApps.cs index e6a678b0d..48e9458b6 100644 --- a/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/AssemblyDaemonApps.cs +++ b/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/AssemblyDaemonApps.cs @@ -4,11 +4,10 @@ namespace NetDaemon.Daemon.Tests.DaemonRunner.App { - /// /// Greets (or insults) people when coming home :) /// - public class AssmeblyDaemonApp : NetDaemon.Common.NetDaemonApp + public class AssemblyDaemonApp : NetDaemon.Common.NetDaemonApp { #region -- Test config -- diff --git a/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/DaemonAppTests.cs b/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/DaemonAppTests.cs index 998289066..7f29692a1 100644 --- a/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/DaemonAppTests.cs +++ b/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/DaemonAppTests.cs @@ -33,7 +33,6 @@ public class AppTests AppSource = appSource }); - [Fact] public void FaultyApplicationShouldLogError() { @@ -90,8 +89,8 @@ public void InstanceAppFromConfigShouldReturnCorrectType() yamlConfigMock.Setup(x => x.GetAllConfigFilePaths()) .Returns(new[] { Path.Combine(ConfigFixturePath, "level2", "level3") }); - IEnumerable types = new List() { typeof(AssmeblyDaemonApp) }; - var yamlConfig = "app:\n class: NetDaemon.Daemon.Tests.DaemonRunner.App.AssmeblyDaemonApp"; + IEnumerable types = new List() { typeof(AssemblyDaemonApp) }; + const string? yamlConfig = "app:\n class: NetDaemon.Daemon.Tests.DaemonRunner.App.AssemblyDaemonApp"; // ACT var instances = new YamlAppConfig(types, new StringReader(yamlConfig), yamlConfigMock.Object, "").Instances; // ASSERT @@ -109,8 +108,8 @@ public void InstanceAppFromConfigNotFoundShouldReturnNull() yamlConfigMock.Setup(x => x.GetAllConfigFilePaths()) .Returns(new[] { Path.Combine(ConfigFixturePath, "level2", "level3") }); - IEnumerable types = new List() { typeof(AssmeblyDaemonApp) }; - var yamlConfig = "app:\n class: NotFoundApp"; + IEnumerable types = new List() { typeof(AssemblyDaemonApp) }; + const string? yamlConfig = "app:\n class: NotFoundApp"; // ACT var instances = new YamlAppConfig(types, new StringReader(yamlConfig), yamlConfigMock.Object, "").Instances; @@ -127,10 +126,10 @@ public void InstanceAppFromConfigShouldHaveCorrectProperties() yamlConfigMock.Setup(x => x.GetAllConfigFilePaths()) .Returns(new[] { Path.Combine(ConfigFixturePath, "level2", "level3") }); - IEnumerable types = new List() { typeof(AssmeblyDaemonApp) }; - var yamlConfig = @" + IEnumerable types = new List() { typeof(AssemblyDaemonApp) }; + const string? yamlConfig = @" app: - class: NetDaemon.Daemon.Tests.DaemonRunner.App.AssmeblyDaemonApp + class: NetDaemon.Daemon.Tests.DaemonRunner.App.AssemblyDaemonApp StringConfig: a string IntConfig: 10 EnumerableConfig: @@ -139,7 +138,7 @@ public void InstanceAppFromConfigShouldHaveCorrectProperties() "; // ACT var instances = new YamlAppConfig(types, new StringReader(yamlConfig), yamlConfigMock.Object, "").Instances; - var instance = instances.FirstOrDefault() as AssmeblyDaemonApp; + var instance = instances.FirstOrDefault() as AssemblyDaemonApp; // ASSERT Assert.Equal("a string", instance?.StringConfig); Assert.Equal(10, instance?.IntConfig); @@ -152,10 +151,10 @@ public void InstanceAppFromConfigWithSecretsShouldHaveCorrectProperties() // ARRANGE var config = new YamlConfig(CreateSettings(Path.Combine(ConfigFixturePath, "level2", "level3"))); - IEnumerable types = new List() { typeof(AssmeblyDaemonApp) }; - var yamlConfig = @" + IEnumerable types = new List() { typeof(AssemblyDaemonApp) }; + const string? yamlConfig = @" app: - class: NetDaemon.Daemon.Tests.DaemonRunner.App.AssmeblyDaemonApp + class: NetDaemon.Daemon.Tests.DaemonRunner.App.AssemblyDaemonApp test_secret_string: !secret a_secret_string test_secret_int: !secret a_secret_int test_normal_string: not a secret string @@ -168,7 +167,7 @@ public void InstanceAppFromConfigWithSecretsShouldHaveCorrectProperties() config, Path.Combine(ConfigFixturePath, "level2", "level3", "any.cs") ).Instances; - var instance = instances.FirstOrDefault() as AssmeblyDaemonApp; + var instance = instances.FirstOrDefault() as AssemblyDaemonApp; // ASSERT Assert.Equal("this is a secret string", instance?.TestSecretString); Assert.Equal(99, instance?.TestSecretInt); @@ -183,10 +182,10 @@ public void InstanceAppFromConfigShouldHaveCorrectPropertiesCamelCaseConvert() yamlConfigMock.Setup(x => x.GetAllConfigFilePaths()) .Returns(new[] { Path.Combine(ConfigFixturePath, "level2", "level3") }); - IEnumerable types = new List() { typeof(AssmeblyDaemonApp) }; - var yamlConfig = @" + IEnumerable types = new List() { typeof(AssemblyDaemonApp) }; + const string? yamlConfig = @" app: - class: NetDaemon.Daemon.Tests.DaemonRunner.App.AssmeblyDaemonApp + class: NetDaemon.Daemon.Tests.DaemonRunner.App.AssemblyDaemonApp string_config: a string int_config: 10 enumerable_config: @@ -195,7 +194,7 @@ public void InstanceAppFromConfigShouldHaveCorrectPropertiesCamelCaseConvert() "; // ACT var instances = new YamlAppConfig(types, new StringReader(yamlConfig), yamlConfigMock.Object, "").Instances; - var instance = instances.FirstOrDefault() as AssmeblyDaemonApp; + var instance = instances.FirstOrDefault() as AssemblyDaemonApp; // ASSERT Assert.Equal("a string", instance?.StringConfig); Assert.Equal(10, instance?.IntConfig); @@ -244,29 +243,27 @@ public async Task StorageShouldReturnSameValueAsSet(object data) yamlConfigMock.Setup(x => x.GetAllConfigFilePaths()) .Returns(new[] { Path.Combine(ConfigFixturePath, "level2", "level3") }); - IEnumerable types = new List() { typeof(AssmeblyDaemonApp) }; - var yamlConfig = @" + IEnumerable types = new List() { typeof(AssemblyDaemonApp) }; + const string? yamlConfig = @" app: - class: NetDaemon.Daemon.Tests.DaemonRunner.App.AssmeblyDaemonApp + class: NetDaemon.Daemon.Tests.DaemonRunner.App.AssemblyDaemonApp "; var daemonMock = new Mock(); daemonMock.SetupGet(x => x.Logger).Returns(new Mock().Object); - await using var instance = new YamlAppConfig(types, new StringReader(yamlConfig), yamlConfigMock.Object, "").Instances.FirstOrDefault() as AssmeblyDaemonApp; - + await using var instance = new YamlAppConfig(types, new StringReader(yamlConfig), yamlConfigMock.Object, "").Instances.FirstOrDefault() as AssemblyDaemonApp; instance!.Id = "somefake_id"; instance.InternalStorageObject = new FluentExpandoObject(false, true, daemon: instance); instance.Logger = new Mock().Object; - // await instance!.StartUpAsync(daemonMock.Object); // ACT instance!.Storage.Data = data; // ASSERT Assert.Equal(data, instance.Storage.Data); - var stateQueueResult = await instance.InternalLazyStoreStateQueue.Reader.WaitToReadAsync(); + var stateQueueResult = await instance.InternalLazyStoreStateQueue.Reader.WaitToReadAsync().ConfigureAwait(false); Assert.True(stateQueueResult); } @@ -279,13 +276,13 @@ public async Task StorageShouldRestoreWithCorrectValues() yamlConfigMock.Setup(x => x.GetAllConfigFilePaths()) .Returns(new[] { Path.Combine(ConfigFixturePath, "level2", "level3") }); - IEnumerable types = new List() { typeof(AssmeblyDaemonApp) }; - var yamlConfig = @" + IEnumerable types = new List() { typeof(AssemblyDaemonApp) }; + const string? yamlConfig = @" app: - class: NetDaemon.Daemon.Tests.DaemonRunner.App.AssmeblyDaemonApp + class: NetDaemon.Daemon.Tests.DaemonRunner.App.AssemblyDaemonApp "; - await using var instance = new YamlAppConfig(types, new StringReader(yamlConfig), yamlConfigMock.Object, "").Instances.FirstOrDefault() as AssmeblyDaemonApp; + await using var instance = new YamlAppConfig(types, new StringReader(yamlConfig), yamlConfigMock.Object, "").Instances.FirstOrDefault() as AssemblyDaemonApp; var daemonMock = new Mock(); daemonMock.SetupGet(x => x.Logger).Returns(new Mock().Object); @@ -298,64 +295,15 @@ public async Task StorageShouldRestoreWithCorrectValues() daemonMock.Setup(n => n.GetDataAsync>(It.IsAny())) .ReturnsAsync((IDictionary)storageItem); - await instance!.StartUpAsync(daemonMock.Object); + await instance!.StartUpAsync(daemonMock.Object).ConfigureAwait(false); // ACT - await instance.RestoreAppStateAsync(); + await instance.RestoreAppStateAsync().ConfigureAwait(false); // ASSERT Assert.Equal("SomeData", instance.Storage.Data); } - // Todo: Make test to test sorting in NetDaemon instead - // [Fact] - // public void InstanceAppFromConfigFilesInFolderWithDependenciesShouldReturnCorrectInstances() - // { - // // ARRANGE - // var path = Path.Combine(ConfigFixturePath, "dependtests"); - // var moqDaemon = new Mock(); - // var moqLogger = new LoggerMock(); - - // moqDaemon.SetupGet(n => n.Logger).Returns(moqLogger.Logger); - // // ACT - // var codeManager = CM(path); - // // ASSERT - // var instances = codeManager.InstanceDaemonApps(); - - // // Todo: refactor this, only had to make test work after refactor - // var realDaemon = new NetDaemonHost(new Mock().Object, new Mock().Object, null, null); - // instances = realDaemon.SortByDependency(instances); - - // Assert.Collection(instances, - // i => Assert.Equal("app_global", i.Id), - // i => Assert.Equal("app_dep_on_global", i.Id), - // i => Assert.Equal("app_dep_on_global_and_other", i.Id), - // i => Assert.Equal("app_dep_app_depend_on_global_and_other", i.Id) - // ); - - // // moqDaemon.Verify(n => n.RegisterAppInstance("app_global", It.IsAny())); - // // moqDaemon.Verify(n => n.RegisterAppInstance("app_dep_on_global", It.IsAny())); - // // moqDaemon.Verify(n => n.RegisterAppInstance("app_dep_on_global_and_other", It.IsAny())); - // // moqDaemon.Verify(n => n.RegisterAppInstance("app_dep_app_depend_on_global_and_other", It.IsAny())); - // } - - //FaultyAppPath - // [Fact] - // public async Task InstanceAppsThatHasCircularDependenciesShouldReturnNull() - // { - // // ARRANGE - // var path = Path.Combine(FaultyAppPath, "CircularDependencies"); - // var moqDaemon = new Mock(); - // var moqLogger = new LoggerMock(); - - // moqDaemon.SetupGet(n => n.Logger).Returns(moqLogger.Logger); - // var codeManager = CM(path); - // // ACT - // // ASSERT - // var ex = await Assert.Throws( () => { codeManager.InstanceDaemonApps(); }); - // Assert.Contains("Application dependencies is wrong", ex.Message); - // } - [Fact] public void InsanceAppsThatHasMissingExecuteShouldLogError() { diff --git a/tests/NetDaemon.Daemon.Tests/DaemonRunner/Config/ConfigTest.cs b/tests/NetDaemon.Daemon.Tests/DaemonRunner/Config/ConfigTest.cs index 821d9bfad..542ad2cd5 100644 --- a/tests/NetDaemon.Daemon.Tests/DaemonRunner/Config/ConfigTest.cs +++ b/tests/NetDaemon.Daemon.Tests/DaemonRunner/Config/ConfigTest.cs @@ -46,7 +46,7 @@ public void NormalLoadSecretsShouldGetCorrectValues() public void SecretsLoadFaultyYamlThrowsException() { // ARRANGE - var faultyYaml = + const string? faultyYaml = "yaml: correctLine\n" + "yaml_missing: \"missing" + "yaml_correct: 10"; @@ -102,7 +102,7 @@ public void SecretShouldBeRelevantDependingOnFolderLevel(string secret, string c public void YamlScalarNodeToObjectUsingString() { // ARRANGE - var yaml = "yaml: string\n"; + const string? yaml = "yaml: string\n"; var yamlStream = new YamlStream(); yamlStream.Load(new StringReader(yaml)); var root = (YamlMappingNode)yamlStream.Documents[0].RootNode; @@ -118,7 +118,7 @@ public void YamlScalarNodeToObjectUsingString() public void YamlScalarNodeToObjectUsingInt() { // ARRANGE - var yaml = "yaml: 1234\n"; + const string? yaml = "yaml: 1234\n"; var yamlStream = new YamlStream(); yamlStream.Load(new StringReader(yaml)); var root = (YamlMappingNode)yamlStream.Documents[0].RootNode; @@ -134,7 +134,7 @@ public void YamlScalarNodeToObjectUsingInt() public void YamlScalarNodeToObjectUsingBoolean() { // ARRANGE - var yaml = "yaml: true\n"; + const string? yaml = "yaml: true\n"; var yamlStream = new YamlStream(); yamlStream.Load(new StringReader(yaml)); var root = (YamlMappingNode)yamlStream.Documents[0].RootNode; @@ -150,7 +150,7 @@ public void YamlScalarNodeToObjectUsingBoolean() public void YamlScalarNodeToObjectUsingLong() { // ARRANGE - var yaml = "yaml: 1234\n"; + const string? yaml = "yaml: 1234\n"; var yamlStream = new YamlStream(); yamlStream.Load(new StringReader(yaml)); var root = (YamlMappingNode)yamlStream.Documents[0].RootNode; @@ -163,10 +163,10 @@ public void YamlScalarNodeToObjectUsingLong() } [Fact] - public void YamlScalarNodeToObjectUsingDeciaml() + public void YamlScalarNodeToObjectUsingDecimal() { // ARRANGE - var yaml = "yaml: 1.5\n"; + const string? yaml = "yaml: 1.5\n"; var yamlStream = new YamlStream(); yamlStream.Load(new StringReader(yaml)); var root = (YamlMappingNode)yamlStream.Documents[0].RootNode; @@ -183,7 +183,7 @@ public void YamlScalarNodeToObjectUsingDeciaml() [Fact] public void YamlAdvancedObjectsShouldReturnCorrectData() { - var yaml = @" + const string? yaml = @" a_string: hello world an_int: 10 a_bool: true diff --git a/tests/NetDaemon.Daemon.Tests/FakesTests/FakeApp.cs b/tests/NetDaemon.Daemon.Tests/FakesTests/FakeApp.cs index d96d5c21f..cb65eb10f 100644 --- a/tests/NetDaemon.Daemon.Tests/FakesTests/FakeApp.cs +++ b/tests/NetDaemon.Daemon.Tests/FakesTests/FakeApp.cs @@ -5,7 +5,6 @@ namespace NetDaemon.Daemon.Tests.Reactive { - /// cool multiple lines public class FakeApp : NetDaemonRxApp { @@ -14,18 +13,12 @@ public override void Initialize() Entity("binary_sensor.kitchen") .StateChanges .Where(e => e.New?.State == "on" && e.Old?.State == "off") - .Subscribe(s => - { - Entity("light.kitchen").TurnOn(); - }); + .Subscribe(_ => Entity("light.kitchen").TurnOn()); Entity("sensor.temperature") .StateAllChanges .Where(e => e.New?.Attribute?.battery_level < 15) - .Subscribe(s => - { - this.CallService("notify", "notify", new { title = "Hello from Home Assistant" }); - }); + .Subscribe(_ => this.CallService("notify", "notify", new { title = "Hello from Home Assistant" })); } } } diff --git a/tests/NetDaemon.Daemon.Tests/FakesTests/FakeTests.cs b/tests/NetDaemon.Daemon.Tests/FakesTests/FakeTests.cs index 92aa8488e..4b37ab428 100644 --- a/tests/NetDaemon.Daemon.Tests/FakesTests/FakeTests.cs +++ b/tests/NetDaemon.Daemon.Tests/FakesTests/FakeTests.cs @@ -50,10 +50,7 @@ public async Task NewAllEventDataShouldCallFunction() // ACT DefaultDaemonRxApp.StateAllChanges - .Subscribe(s => - { - called = true; - }); + .Subscribe(_ => called = true); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "on", "on"); @@ -72,10 +69,7 @@ public async Task NewEventShouldCallFunction() // ACT DefaultDaemonRxApp.EventChanges - .Subscribe(s => - { - called = true; - }); + .Subscribe(_ => called = true); DefaultHassClientMock.AddCustomEvent("AN_EVENT", new { somedata = "hello" }); @@ -94,10 +88,7 @@ public async Task NewEventMissingDataAttributeShouldReturnNull() // ACT DefaultDaemonRxApp.EventChanges - .Subscribe(s => - { - missingAttribute = s.Data?.missing_data; - }); + .Subscribe(s => missingAttribute = s.Data?.missing_data); var expandoObj = new ExpandoObject(); dynamic dynExpObject = expandoObj; @@ -120,10 +111,7 @@ public async Task NewStateEventShouldCallFunction() // ACT DefaultDaemonRxApp.StateChanges - .Subscribe(s => - { - called = true; - }); + .Subscribe(_ => called = true); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); @@ -146,7 +134,6 @@ public async Task RunScriptShouldCallCorrectFunction() // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("myscript", Times.Once()); } @@ -173,10 +160,7 @@ public async Task SameStateEventShouldNotCallFunction() // ACT DefaultDaemonRxApp.StateChanges - .Subscribe(s => - { - called = true; - }); + .Subscribe(_ => called = true); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "on", "on"); @@ -208,7 +192,7 @@ public async Task StartupAsyncShouldThrowIfDaemonIsNull() INetDaemonHost? host = null; // ARRANGE ACT ASSERT - await Assert.ThrowsAsync(() => DefaultDaemonRxApp.StartUpAsync(host!)); + await Assert.ThrowsAsync(() => DefaultDaemonRxApp.StartUpAsync(host!)).ConfigureAwait(false); } [Fact] @@ -279,10 +263,7 @@ public async Task UsingEntitiesLambdaNewEventShouldCallFunction() // ACT DefaultDaemonRxApp.Entities(n => n.EntityId.StartsWith("binary_sensor.pir")) .StateChanges - .Subscribe(s => - { - called = true; - }); + .Subscribe(_ => called = true); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir_2", "off", "on"); @@ -303,10 +284,7 @@ public async Task CallbackObserverAttributeMissingShouldReturnNull() // ACT DefaultDaemonRxApp.Entities(n => n.EntityId.StartsWith("binary_sensor.pir")) .StateChanges - .Subscribe(s => - { - missingString = s.New.Attribute?.missing_attribute; - }); + .Subscribe(s => missingString = s.New.Attribute?.missing_attribute); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir_2", "off", "on"); @@ -326,10 +304,7 @@ public async Task UsingEntitiesNewEventShouldCallFunction() // ACT DefaultDaemonRxApp.Entities("binary_sensor.pir", "binary_sensor.pir_2") .StateChanges - .Subscribe(s => - { - called = true; - }); + .Subscribe(_ => called = true); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir_2", "off", "on"); @@ -349,10 +324,7 @@ public async Task UsingEntityNewEventShouldCallFunction() // ACT DefaultDaemonRxApp.Entity("binary_sensor.pir") .StateChanges - .Subscribe(s => - { - called = true; - }); + .Subscribe(_ => called = true); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); @@ -372,10 +344,7 @@ public async Task UsingEntityNewEventShouldNotCallFunction() // ACT DefaultDaemonRxApp.Entity("binary_sensor.other_pir") .StateChanges - .Subscribe(s => - { - called = true; - }); + .Subscribe(_ => called = true); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); @@ -393,10 +362,7 @@ public async Task WhenStateStaysSameForTimeItShouldCallFunction() using var ctx = DefaultDaemonRxApp.StateChanges .Where(t => t.New.EntityId == "binary_sensor.pir") .NDSameStateFor(TimeSpan.FromMilliseconds(50)) - .Subscribe(e => - { - isRun = true; - }); + .Subscribe(_ => isRun = true); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); @@ -444,7 +410,7 @@ public async Task GetDataShouldReturnCachedValue() public async Task TestFakeAppTurnOnCorrectLight() { // Add the app to test - await AddAppInstance(new FakeApp()); + await AddAppInstance(new FakeApp()).ConfigureAwait(false); // Init NetDaemon core runtime await InitializeFakeDaemon().ConfigureAwait(false); @@ -455,16 +421,15 @@ public async Task TestFakeAppTurnOnCorrectLight() // Run the NetDemon Core to process the messages await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // Verify that netdaemon called light.turn_on + // Verify that netdaemon called light.turn_on VerifyCallService("light", "turn_on", "light.kitchen"); } - [Fact] public async Task TestFakeAppCallNoteWhenBatteryLevelBelowValue() { // Add the app to test - await AddAppInstance(new FakeApp()); + await AddAppInstance(new FakeApp()).ConfigureAwait(false); // Init NetDaemon core runtime await InitializeFakeDaemon().ConfigureAwait(false); @@ -492,7 +457,7 @@ public async Task TestFakeAppCallNoteWhenBatteryLevelBelowValue() // Run the NetDemon Core to process the messages await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // Verify that netdaemon called light.turn_on + // Verify that netdaemon called light.turn_on VerifyCallService("notify", "notify", new { title = "Hello from Home Assistant" }); } } diff --git a/tests/NetDaemon.Daemon.Tests/Fluent/FluentCameraTests.cs b/tests/NetDaemon.Daemon.Tests/Fluent/FluentCameraTests.cs index e187702bf..49eedd308 100644 --- a/tests/NetDaemon.Daemon.Tests/Fluent/FluentCameraTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Fluent/FluentCameraTests.cs @@ -25,14 +25,15 @@ public FluentCameraTests() : base() public async Task CameraDisableMotionDetectionCallsCorrectServiceCall() { // ARRANGE - var entityId = "camera.camera1"; - var service_call = "disable_motion_detection"; + const string? entityId = "camera.camera1"; + const string? service_call = "disable_motion_detection"; // ACT await DefaultDaemonApp .Camera(entityId) .DisableMotionDetection() - .ExecuteAsync(); + .ExecuteAsync() + .ConfigureAwait(false); // ASSERT DefaultHassClientMock.VerifyCallServiceTimes(service_call, Times.Once()); @@ -43,14 +44,15 @@ await DefaultDaemonApp public async Task CamerasDisableMotionDetectionCallsCorrectServiceCall() { // ARRANGE - var entityId = "camera.camera1"; - var service_call = "disable_motion_detection"; + const string? entityId = "camera.camera1"; + const string? service_call = "disable_motion_detection"; // ACT await DefaultDaemonApp .Cameras(new string[] { entityId }) .DisableMotionDetection() - .ExecuteAsync(); + .ExecuteAsync() + .ConfigureAwait(false); // ASSERT DefaultHassClientMock.VerifyCallServiceTimes(service_call, Times.Once()); @@ -61,8 +63,8 @@ await DefaultDaemonApp public async Task CamerasFuncDisableMotionDetectionCallsCorrectServiceCall() { // ARRANGE - var entityId = "camera.camera1"; - var service_call = "disable_motion_detection"; + const string? entityId = "camera.camera1"; + const string? service_call = "disable_motion_detection"; DefaultDaemonHost.InternalState["camera.camera1"] = new EntityState { @@ -74,7 +76,8 @@ public async Task CamerasFuncDisableMotionDetectionCallsCorrectServiceCall() await DefaultDaemonApp .Cameras(n => n.EntityId == entityId) .DisableMotionDetection() - .ExecuteAsync(); + .ExecuteAsync() + .ConfigureAwait(false); // ASSERT DefaultHassClientMock.VerifyCallServiceTimes(service_call, Times.Once()); @@ -85,14 +88,14 @@ await DefaultDaemonApp public async Task CameraEnableMotionDetectionCallsCorrectServiceCall() { // ARRANGE - var entityId = "camera.camera1"; - var service_call = "enable_motion_detection"; + const string? entityId = "camera.camera1"; + const string? service_call = "enable_motion_detection"; // ACT await DefaultDaemonApp .Camera(entityId) .EnableMotionDetection() - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT DefaultHassClientMock.VerifyCallServiceTimes(service_call, Times.Once()); @@ -103,14 +106,14 @@ await DefaultDaemonApp public async Task CameraPlayStreamCallsCorrectServiceCall() { // ARRANGE - var entityId = "camera.camera1"; - var service_call = "play_stream"; + const string? entityId = "camera.camera1"; + const string? service_call = "play_stream"; // ACT await DefaultDaemonApp .Camera(entityId) .PlayStream("media_player.player", "anyformat") - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT VerifyCallServiceTimes(service_call, Times.Once()); @@ -125,14 +128,14 @@ await DefaultDaemonApp public async Task CameraRecordCallsCorrectServiceCall() { // ARRANGE - var entityId = "camera.camera1"; - var service_call = "record"; + const string? entityId = "camera.camera1"; + const string? service_call = "record"; // ACT await DefaultDaemonApp .Camera(entityId) .Record("filename", 1, 2) - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT VerifyCallServiceTimes(service_call, Times.Once()); @@ -148,14 +151,14 @@ await DefaultDaemonApp public async Task CameraSnapshotCallsCorrectServiceCall() { // ARRANGE - var entityId = "camera.camera1"; - var service_call = "snapshot"; + const string? entityId = "camera.camera1"; + const string? service_call = "snapshot"; // ACT await DefaultDaemonApp .Camera(entityId) .Snapshot("filename") - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT VerifyCallServiceTimes(service_call, Times.Once()); @@ -169,14 +172,14 @@ await DefaultDaemonApp public async Task CameraTurnOnCallsCorrectServiceCall() { // ARRANGE - var entityId = "camera.camera1"; - var service_call = "turn_on"; + const string? entityId = "camera.camera1"; + const string? service_call = "turn_on"; // ACT await DefaultDaemonApp .Camera(entityId) .TurnOn() - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT DefaultHassClientMock.VerifyCallServiceTimes(service_call, Times.Once()); @@ -187,14 +190,14 @@ await DefaultDaemonApp public async Task CameraTurnOffCallsCorrectServiceCall() { // ARRANGE - var entityId = "camera.camera1"; - var service_call = "turn_off"; + const string? entityId = "camera.camera1"; + const string? service_call = "turn_off"; // ACT await DefaultDaemonApp .Camera(entityId) .TurnOff() - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT DefaultHassClientMock.VerifyCallServiceTimes(service_call, Times.Once()); @@ -202,16 +205,16 @@ await DefaultDaemonApp } [Fact] - public async Task CamerassFuncExceptionLogsError() + public async Task CamerasFuncExceptionLogsError() { // ARRANGE DefaultDaemonHost.InternalState["id"] = new EntityState { EntityId = "id" }; // ACT var x = await Assert.ThrowsAsync(() => DefaultDaemonApp - .Cameras(n => throw new Exception("Some error")) + .Cameras(_ => throw new Exception("Some error")) .TurnOn() - .ExecuteAsync()); + .ExecuteAsync()).ConfigureAwait(false); // ASSERT DefaultHassClientMock.VerifyCallServiceTimes("turn_on", Times.Never()); diff --git a/tests/NetDaemon.Daemon.Tests/Fluent/FluentEventTests.cs b/tests/NetDaemon.Daemon.Tests/Fluent/FluentEventTests.cs index 2d5d1f942..ad25b00da 100644 --- a/tests/NetDaemon.Daemon.Tests/Fluent/FluentEventTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Fluent/FluentEventTests.cs @@ -23,7 +23,7 @@ public async Task ACustomEventNullValueCallThrowsNullReferenceException() }; daemonHost.InternalRunningAppInstances[app.Id] = app; - await app.StartUpAsync(daemonHost); + await app.StartUpAsync(daemonHost).ConfigureAwait(false); var cancelSource = HassClientMock.GetSourceWithTimeout(); @@ -45,7 +45,7 @@ public async Task ACustomEventShouldDoCorrectCall() }; daemonHost.InternalRunningAppInstances[app.Id] = app; - await app.StartUpAsync(daemonHost); + await app.StartUpAsync(daemonHost).ConfigureAwait(false); dynamic dynObject = new ExpandoObject(); dynObject.Test = "Hello World!"; @@ -58,7 +58,7 @@ public async Task ACustomEventShouldDoCorrectCall() app .Event("CUSTOM_EVENT") - .Call((ev, data) => + .Call((_, data) => { isCalled = true; message = data?.Test; @@ -71,7 +71,7 @@ public async Task ACustomEventShouldDoCorrectCall() } catch (TaskCanceledException) { - // Expected behaviour + // Expected behavior } Assert.True(isCalled); @@ -89,7 +89,7 @@ public async Task ACustomEventShouldUsingSelectorFuncDoCorrectCall() }; daemonHost.InternalRunningAppInstances[app.Id] = app; - await app.StartUpAsync(daemonHost); + await app.StartUpAsync(daemonHost).ConfigureAwait(false); dynamic dynObject = new ExpandoObject(); dynObject.Test = "Hello World!"; @@ -102,7 +102,7 @@ public async Task ACustomEventShouldUsingSelectorFuncDoCorrectCall() app .Events(n => n.EventId == "CUSTOM_EVENT") - .Call((ev, data) => + .Call((_, data) => { isCalled = true; message = data?.Test; @@ -115,7 +115,7 @@ public async Task ACustomEventShouldUsingSelectorFuncDoCorrectCall() } catch (TaskCanceledException) { - // Expected behaviour + // Expected behavior } Assert.True(isCalled); @@ -134,7 +134,7 @@ public async Task ACustomEventShouldUsingSelectorUsingDataFuncDoCorrectCall() }; daemonHost.InternalRunningAppInstances[app.Id] = app; - await app.StartUpAsync(daemonHost); + await app.StartUpAsync(daemonHost).ConfigureAwait(false); dynamic dynObject = new ExpandoObject(); dynObject.Test = "Hello World!"; @@ -147,7 +147,7 @@ public async Task ACustomEventShouldUsingSelectorUsingDataFuncDoCorrectCall() app .Events(n => n.EventId == "CUSTOM_EVENT" && n?.Data?.Test == "Hello World!") - .Call((ev, data) => + .Call((_, data) => { isCalled = true; message = data?.Test; @@ -160,7 +160,7 @@ public async Task ACustomEventShouldUsingSelectorUsingDataFuncDoCorrectCall() } catch (TaskCanceledException) { - // Expected behaviour + // Expected behavior } Assert.True(isCalled); @@ -179,7 +179,7 @@ public async Task ACustomEventShouldUsingSelectorUsingDataFuncNotCall() }; daemonHost.InternalRunningAppInstances[app.Id] = app; - await app.StartUpAsync(daemonHost); + await app.StartUpAsync(daemonHost).ConfigureAwait(false); dynamic dynObject = new ExpandoObject(); dynObject.Test = "Hello World!"; @@ -192,7 +192,7 @@ public async Task ACustomEventShouldUsingSelectorUsingDataFuncNotCall() app .Events(n => n.EventId == "CUSTOM_EVENT" && n?.Data?.Test == "Hello Test!") - .Call((ev, data) => + .Call((_, data) => { isCalled = true; message = data?.Test; @@ -205,7 +205,7 @@ public async Task ACustomEventShouldUsingSelectorUsingDataFuncNotCall() } catch (TaskCanceledException) { - // Expected behaviour + // Expected behavior } Assert.False(isCalled); @@ -223,7 +223,7 @@ public async Task ACustomEventShouldUsingSelectorUsingDataNotExisstFuncNotCall() }; daemonHost.InternalRunningAppInstances[app.Id] = app; - await app.StartUpAsync(daemonHost); + await app.StartUpAsync(daemonHost).ConfigureAwait(false); dynamic dynObject = new ExpandoObject(); dynObject.Test = "Hello World!"; @@ -235,7 +235,7 @@ public async Task ACustomEventShouldUsingSelectorUsingDataNotExisstFuncNotCall() app .Events(n => n.EventId == "CUSTOM_EVENT" && n?.Data?.NotExist == "Hello Test!") - .Call((ev, data) => + .Call((_, data) => { isCalled = true; message = data?.Test; @@ -248,7 +248,7 @@ public async Task ACustomEventShouldUsingSelectorUsingDataNotExisstFuncNotCall() } catch (TaskCanceledException) { - // Expected behaviour + // Expected behavior } Assert.False(isCalled); @@ -266,7 +266,7 @@ public async Task ACustomEventsShouldDoCorrectCall() }; daemonHost.InternalRunningAppInstances[app.Id] = app; - await app.StartUpAsync(daemonHost); + await app.StartUpAsync(daemonHost).ConfigureAwait(false); dynamic dynObject = new ExpandoObject(); dynObject.Test = "Hello World!"; @@ -279,7 +279,7 @@ public async Task ACustomEventsShouldDoCorrectCall() app .Events(new string[] { "CUSTOM_EVENT" }) - .Call((ev, data) => + .Call((_, data) => { isCalled = true; message = data?.Test; @@ -292,7 +292,7 @@ public async Task ACustomEventsShouldDoCorrectCall() } catch (TaskCanceledException) { - // Expected behaviour + // Expected behavior } Assert.True(isCalled); diff --git a/tests/NetDaemon.Daemon.Tests/Fluent/FluentTests.cs b/tests/NetDaemon.Daemon.Tests/Fluent/FluentTests.cs index 407407b84..f093fcbcf 100644 --- a/tests/NetDaemon.Daemon.Tests/Fluent/FluentTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Fluent/FluentTests.cs @@ -46,9 +46,9 @@ public async Task EntityOnStateChangedForTimeTurnOffLightCallsCorrectServiceCall lastUpdated, lastChanged); // ASSERT - await Task.Delay(10); // After 10ms we should not have call + await Task.Delay(10).ConfigureAwait(false); // After 10ms we should not have call VerifyCallServiceTimes("turn_off", Times.Never()); - await Task.Delay(300); // After 30ms we should have call + await Task.Delay(300).ConfigureAwait(false); // After 30ms we should have call VerifyCallServiceTimes("turn_off", Times.Once()); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); } @@ -79,7 +79,7 @@ public async Task EntityOnStateChangedLamdaWithMultipleEntitiesCallsCorrectServi // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); - var MotionEnabled = true; + const bool MotionEnabled = true; DefaultDaemonApp .Entities(new string[] { "binary_sensor.pir", "binary_sensor-pir2" }) @@ -128,7 +128,6 @@ public async Task EntityOnStateChangedMultipleTimesCallsCorrectServiceCall() .TurnOn() .Execute(); - AddChangedEvent("binary_sensor.pir", "off", "on"); AddChangedEvent("binary_sensor.pir", "off", "on"); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); @@ -184,8 +183,6 @@ public async Task EntityOnStateChangedEntitiesLambdaTurnOnLightCallsCorrectServi // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); - - // Fake the DefaultDaemonHost.InternalState["light.correct_entity"] = new EntityState { @@ -212,7 +209,6 @@ public async Task EntityOnStateChangedTurnOnLightCallsCorrectServiceCallButNoTur // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); - DefaultDaemonApp .Entity("binary_sensor.pir") .WhenStateChange("on") @@ -272,7 +268,7 @@ public async Task EntityOnStateDefaultTriggerOnAnyStateChange() DefaultDaemonApp .Entity("binary_sensor.pir") .WhenStateChange() - .Call((e, n, o) => + .Call((_, _, _) => { triggered = true; return Task.CompletedTask; @@ -298,7 +294,7 @@ public async Task EntityOnStateNotTriggerOnSameState() DefaultDaemonApp .Entity("binary_sensor.pir") .WhenStateChange() - .Call((e, n, o) => + .Call((_, _, _) => { triggered = true; return Task.CompletedTask; @@ -325,7 +321,7 @@ public async Task EntityOnStateIncludeAttributesTriggerOnSameState() DefaultDaemonApp .Entity("binary_sensor.pir") .WhenStateChange(allChanges: true) - .Call((e, n, o) => + .Call((_, _, _) => { triggered = true; return Task.CompletedTask; @@ -347,7 +343,8 @@ public async Task ToggleEntityCallsCorrectServiceCall() await DefaultDaemonApp .Entity("light.correct_entity") .Toggle() - .ExecuteAsync(); + .ExecuteAsync() + .ConfigureAwait(false); // ASSERT VerifyCallServiceTimes("toggle", Times.Once()); @@ -363,7 +360,8 @@ public async Task TurnOffEntityCallsCorrectServiceCall() await DefaultDaemonApp .Entity("light.correct_entity") .TurnOff() - .ExecuteAsync(); + .ExecuteAsync() + .ConfigureAwait(false); // ASSERT VerifyCallServiceTimes("turn_off", Times.Once()); @@ -378,9 +376,9 @@ public async Task EntityFuncExceptionLogsError() // ACT var x = await Assert.ThrowsAsync(() => DefaultDaemonApp - .Entities(n => throw new Exception("Some error")) + .Entities(_ => throw new Exception("Some error")) .TurnOff() - .ExecuteAsync()); + .ExecuteAsync()).ConfigureAwait(false); // ASSERT VerifyCallServiceTimes("turn_off", Times.Never()); @@ -398,7 +396,7 @@ public async Task TurnOffEntityLambdaAttributeSelectionCallsCorrectServiceCall() await DefaultDaemonApp .Entities(n => n?.Attribute?.test >= 100) .TurnOff() - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT @@ -418,7 +416,7 @@ public async Task TurnOffEntityLambdaAttributeSelectionNoExistCallsCorrectServic await DefaultDaemonApp .Entities(n => n?.Attribute?.not_exists == "test") .TurnOff() - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT @@ -434,7 +432,7 @@ public async Task TurnOffEntityLamdaSelectionCallsCorrectServiceCall() await DefaultDaemonApp .Entities(n => n.EntityId.StartsWith("light.correct_entity")) .TurnOff() - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT @@ -454,7 +452,7 @@ public async Task TurnOffMultipleEntityCallsCorrectServiceCall() await DefaultDaemonApp .Entity("light.correct_entity", "light.correct_entity2") .TurnOff() - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT VerifyCallServiceTimes("turn_off", Times.Exactly(2)); @@ -471,7 +469,7 @@ public async Task TurnOnEntityCallsCorrectServiceCall() await DefaultDaemonApp .Entity("light.correct_entity") .TurnOn() - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT VerifyCallServiceTimes("turn_on", Times.Once()); @@ -487,7 +485,7 @@ await DefaultDaemonApp .Entity("light.correct_entity") .TurnOn() .WithAttribute("brightness", 100) - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT VerifyCallServiceTimes("turn_on", Times.Once()); @@ -506,7 +504,7 @@ await DefaultDaemonApp .TurnOn() .WithAttribute("brightness", 100) .WithAttribute("color_temp", 230) - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT VerifyCallServiceTimes("turn_on", Times.Once()); @@ -524,7 +522,7 @@ public async Task SetStateEntityCallsCorrectServiceCall() await DefaultDaemonApp .Entity("light.correct_entity") .SetState(50) - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT VerifySetStateTimes("light.correct_entity", Times.Once()); @@ -540,7 +538,7 @@ await DefaultDaemonApp .Entity("light.correct_entity") .SetState(50) .WithAttribute("attr1", "str_value") - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT VerifySetStateTimes("light.correct_entity", Times.Once()); @@ -605,14 +603,14 @@ public async Task SpeakCallsCorrectServiceCall() await DefaultDaemonApp .MediaPlayer("media_player.correct_player") .Speak("a message") - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); - await Task.Delay(20); + await Task.Delay(20).ConfigureAwait(false); var expObject = new FluentExpandoObject(); - dynamic expectedAttruibutes = expObject; - expectedAttruibutes.entity_id = "media_player.correct_player"; - expectedAttruibutes.message = "a message"; + dynamic expectedAttributes = expObject; + expectedAttributes.entity_id = "media_player.correct_player"; + expectedAttributes.message = "a message"; // ASSERT VerifyCallService("tts", "google_cloud_say", expObject, true); @@ -629,7 +627,7 @@ public async Task MediaPlayerPlayCallsCorrectServiceCall() await DefaultDaemonApp .MediaPlayer("media_player.player") .Play() - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT VerifyCallServiceTimes("media_play", Times.Once()); @@ -650,7 +648,7 @@ public async Task MediaPlayersFuncPlayCallsCorrectServiceCall() await DefaultDaemonApp .MediaPlayers(n => n.EntityId == "media_player.player") .Play() - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT VerifyCallServiceTimes("media_play", Times.Once()); @@ -665,9 +663,9 @@ public async Task MediaPlayersFuncExceptionLogsError() // ACT var x = await Assert.ThrowsAsync(() => DefaultDaemonApp - .MediaPlayers(n => throw new Exception("Some error")) + .MediaPlayers(_ => throw new Exception("Some error")) .Play() - .ExecuteAsync()); + .ExecuteAsync()).ConfigureAwait(false); // ASSERT VerifyCallServiceTimes("turn_off", Times.Never()); @@ -683,7 +681,7 @@ public async Task MediaPlayersPlayCallsCorrectServiceCall() await DefaultDaemonApp .MediaPlayers(new string[] { "media_player.player" }) .Play() - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT VerifyCallServiceTimes("media_play", Times.Once()); @@ -698,7 +696,7 @@ public async Task MediaPlayerPauseCallsCorrectServiceCall() await DefaultDaemonApp .MediaPlayer("media_player.player") .Pause() - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT VerifyCallServiceTimes("media_pause", Times.Once()); @@ -713,7 +711,7 @@ public async Task MediaPlayerPlayPauseCallsCorrectServiceCall() await DefaultDaemonApp .MediaPlayer("media_player.player") .PlayPause() - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT VerifyCallServiceTimes("media_play_pause", Times.Once()); @@ -728,7 +726,7 @@ public async Task MediaPlayerStopCallsCorrectServiceCall() await DefaultDaemonApp .MediaPlayer("media_player.player") .Stop() - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT VerifyCallServiceTimes("media_stop", Times.Once()); @@ -743,7 +741,7 @@ public async Task InputSelectSetOptionShouldCallCorrectCallService() await DefaultDaemonApp .InputSelect("input_select.myselect") .SetOption("option1") - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT VerifyCallServiceTimes("select_option", Times.Once()); @@ -760,7 +758,7 @@ public async Task InputSelectSetOptionIEnumerableShouldCallCorrectCallService() await DefaultDaemonApp .InputSelects(new string[] { "input_select.myselect" }) .SetOption("option1") - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT VerifyCallServiceTimes("select_option", Times.Once()); @@ -778,7 +776,7 @@ public async Task InputSelectSetOptionFuncShouldCallCorrectCallService() await DefaultDaemonApp .InputSelects(n => n.EntityId == "input_select.myselect") .SetOption("option1") - .ExecuteAsync(); + .ExecuteAsync().ConfigureAwait(false); // ASSERT VerifyCallServiceTimes("select_option", Times.Once()); @@ -810,7 +808,6 @@ public async Task EntityDelayUntilStateChangeLamdaShouldReturnTrue() // ARRANGE await InitializeFakeDaemon().ConfigureAwait(false); - var delayResult = DefaultDaemonApp .Entity("binary_sensor.pir") .DelayUntilStateChange((to, _) => to?.State == "on"); @@ -829,7 +826,7 @@ public async Task EntityInAreaOnStateChangedTurnOnLight() await InitializeFakeDaemon().ConfigureAwait(false); // Fake the state - SetEntityState("light.ligth_in_area", area: "Area"); + SetEntityState("light.light_in_area", area: "Area"); // ACT DefaultDaemonApp @@ -840,7 +837,7 @@ public async Task EntityInAreaOnStateChangedTurnOnLight() .Execute(); // light.light_in_area is setup so it has area = Area - AddChangedEvent("light.ligth_in_area", "off", "on"); + AddChangedEvent("light.light_in_area", "off", "on"); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); @@ -857,7 +854,7 @@ public async Task EntityInAreaOnStateChangedShouldTurnOn() await InitializeFakeDaemon().ConfigureAwait(false); // Fake the state - SetEntityState("light.ligth_in_area", area: "Area"); + SetEntityState("light.light_in_area", area: "Area"); // ACT DefaultDaemonApp @@ -873,7 +870,7 @@ public async Task EntityInAreaOnStateChangedShouldTurnOn() await RunFakeDaemonUntilTimeout().ConfigureAwait(false); // ASSERT VerifyCallServiceTimes("turn_on", Times.Once()); - VerifyCallServiceTuple("light", "turn_on", ("entity_id", "light.ligth_in_area")); + VerifyCallServiceTuple("light", "turn_on", ("entity_id", "light.light_in_area")); } [Fact] @@ -883,8 +880,8 @@ public async Task RunScriptShouldCallCorrectService() await InitializeFakeDaemon().ConfigureAwait(false); // ACT - await DefaultDaemonApp.RunScript("testscript").ExecuteAsync(); - await DefaultDaemonApp.RunScript("script.testscript").ExecuteAsync(); + await DefaultDaemonApp.RunScript("testscript").ExecuteAsync().ConfigureAwait(false); + await DefaultDaemonApp.RunScript("script.testscript").ExecuteAsync().ConfigureAwait(false); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); // ASSERT @@ -898,12 +895,11 @@ public async Task SendEventShouldCallCorrectService() await InitializeFakeDaemon().ConfigureAwait(false); // ACT - await DefaultDaemonApp.SendEvent("myevent"); + await DefaultDaemonApp.SendEvent("myevent").ConfigureAwait(false); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); // ASSERT VerifyEventSent("myevent"); } } - } \ No newline at end of file diff --git a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyAppsTests.cs b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyAppsTests.cs index 158ac4065..a81d01f87 100644 --- a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyAppsTests.cs +++ b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyAppsTests.cs @@ -13,19 +13,17 @@ public class DaemonAppTestApp : NetDaemon.Common.NetDaemonApp { } public class FaultyAppTests : DaemonHostTestBase { - private readonly NetDaemon.Common.NetDaemonApp _app; - public FaultyAppTests() : base() { - _app = new DaemonAppTestApp + App = new DaemonAppTestApp { Id = "id" }; - DefaultDaemonHost.InternalRunningAppInstances[_app.Id] = App; - _app.StartUpAsync(DefaultDaemonHost).Wait(); + DefaultDaemonHost.InternalRunningAppInstances[App.Id] = App; + App.StartUpAsync(DefaultDaemonHost).Wait(); } - public NetDaemon.Common.NetDaemonApp App => _app; + public NetDaemon.Common.NetDaemonApp App { get; } [Fact] public async Task ARunTimeErrorShouldLogError() @@ -36,7 +34,7 @@ public async Task ARunTimeErrorShouldLogError() App .Entity("binary_sensor.pir") .WhenStateChange("on") - .Call((entity, from, to) => + .Call((_, _, _) => { // Do conversion error int x = int.Parse("ss"); @@ -59,7 +57,7 @@ public async Task ARunTimeErrorShouldNotBreakOtherApps() App .Entity("binary_sensor.pir") .WhenStateChange("on") - .Call((entity, from, to) => + .Call((_, _, _) => { // Do conversion error int x = int.Parse("ss"); @@ -69,7 +67,7 @@ public async Task ARunTimeErrorShouldNotBreakOtherApps() App .Entity("binary_sensor.pir") .WhenStateChange("on") - .Call((entity, from, to) => + .Call((_, _, _) => { // Do conversion error eventRun = true; @@ -82,7 +80,6 @@ public async Task ARunTimeErrorShouldNotBreakOtherApps() LoggerMock.AssertLogged(LogLevel.Error, Times.Once()); Assert.True(eventRun); - } [Fact] @@ -94,7 +91,7 @@ public async Task MissingAttributeShouldNotBreakOtherApps() App .Entities(e => e.Attribute!.does_not_exist == "yay") .WhenStateChange() - .Call((entity, from, to) => + .Call((_, _, _) => { // Do conversion error int x = int.Parse("ss"); @@ -104,7 +101,7 @@ public async Task MissingAttributeShouldNotBreakOtherApps() App .Entity("binary_sensor.pir") .WhenStateChange("on") - .Call((entity, from, to) => + .Call((_, _, _) => { // Do conversion error eventRun = true; @@ -115,9 +112,7 @@ public async Task MissingAttributeShouldNotBreakOtherApps() await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // LoggerMock.AssertLogged(LogLevel.Error, Times.Once()); Assert.True(eventRun); - } [Fact] @@ -129,7 +124,7 @@ public async Task MissingEntityShouldNotBreakOtherApps() App .Entity("binary_sensor.pir") .WhenStateChange() - .Call((entity, from, to) => + .Call((_, _, _) => { // Do conversion error App.Entity("does_not_exist").TurnOn(); @@ -139,7 +134,7 @@ public async Task MissingEntityShouldNotBreakOtherApps() App .Entity("binary_sensor.pir") .WhenStateChange("on") - .Call((entity, from, to) => + .Call((_, _, _) => { // Do conversion error eventRun = true; @@ -151,37 +146,6 @@ public async Task MissingEntityShouldNotBreakOtherApps() await RunFakeDaemonUntilTimeout().ConfigureAwait(false); Assert.True(eventRun); - - } - - private void AddDefaultEvent() - { - DefaultHassClientMock.FakeEvents.Enqueue(new HassEvent - { - EventType = "state_changed", - Data = new HassStateChangedEventData - { - EntityId = "binary_sensor.pir", - NewState = new HassState - { - State = "on", - Attributes = new Dictionary - { - ["device_class"] = "motion" - }, - LastChanged = DateTime.Now, - LastUpdated = DateTime.Now - }, - OldState = new HassState - { - State = "off", - Attributes = new Dictionary - { - ["device_class"] = "motion" - } - } - } - }); } } } \ No newline at end of file diff --git a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyRxAppsTests.cs b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyRxAppsTests.cs index 5645b1c96..26d691353 100644 --- a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyRxAppsTests.cs +++ b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/FaultyRxAppsTests.cs @@ -14,19 +14,17 @@ public class DaemonRxAppTestApp : NetDaemon.Common.Reactive.NetDaemonRxApp { } public class FaultyRxAppTests : DaemonHostTestBase { - private readonly NetDaemon.Common.Reactive.NetDaemonRxApp _app; - public FaultyRxAppTests() : base() { - _app = new DaemonRxAppTestApp + App = new DaemonRxAppTestApp { Id = "id" }; - DefaultDaemonHost.InternalRunningAppInstances[_app.Id] = App; - _app.StartUpAsync(DefaultDaemonHost).Wait(); + DefaultDaemonHost.InternalRunningAppInstances[App.Id] = App; + App.StartUpAsync(DefaultDaemonHost).Wait(); } - public NetDaemon.Common.Reactive.NetDaemonRxApp App => _app; + public NetDaemon.Common.Reactive.NetDaemonRxApp App { get; } [Fact] public async Task ARunTimeErrorShouldLogError() @@ -37,7 +35,7 @@ public async Task ARunTimeErrorShouldLogError() App .Entity("binary_sensor.pir") .StateChanges - .Subscribe(s => + .Subscribe(_ => { int x = int.Parse("ss"); }); @@ -58,10 +56,7 @@ public async Task MissingEntityShouldNotLogError() App .Entity("binary_sensor.pir") .StateChanges - .Subscribe(s => - { - App.Entity("light.do_not_exist").TurnOn(); - }); + .Subscribe(_ => App.Entity("light.do_not_exist").TurnOn()); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); @@ -81,26 +76,17 @@ public async Task MissingEntityShouldNotLogErrorAndNotBreakOtherApps() App .Entity("binary_sensor.pir") .StateChanges - .Subscribe(s => - { - eventRun = true; - }); + .Subscribe(_ => eventRun = true); App .Entity("binary_sensor.pir") .StateChanges - .Subscribe(s => - { - App.Entity("light.do_not_exist").TurnOn(); - }); + .Subscribe(_ => App.Entity("light.do_not_exist").TurnOn()); App .Entity("binary_sensor.pir") .StateChanges - .Subscribe(s => - { - event2Run = true; - }); + .Subscribe(_ => event2Run = true); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); @@ -109,7 +95,6 @@ public async Task MissingEntityShouldNotLogErrorAndNotBreakOtherApps() LoggerMock.AssertLogged(LogLevel.Error, Times.Never()); Assert.True(eventRun); Assert.True(event2Run); - } [Fact] @@ -123,15 +108,12 @@ public async Task ARunTimeErrorShouldNotBreakOtherApps() App .Entity("binary_sensor.pir") .StateChanges - .Subscribe(s => - { - eventRun = true; - }); + .Subscribe(_ => eventRun = true); App .Entity("binary_sensor.pir") .StateChanges - .Subscribe(s => + .Subscribe(_ => { int x = int.Parse("ss"); }); @@ -139,10 +121,7 @@ public async Task ARunTimeErrorShouldNotBreakOtherApps() App .Entity("binary_sensor.pir") .StateChanges - .Subscribe(s => - { - event2Run = true; - }); + .Subscribe(_ => event2Run = true); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); @@ -151,7 +130,6 @@ public async Task ARunTimeErrorShouldNotBreakOtherApps() LoggerMock.AssertLogged(LogLevel.Error, Times.Once()); Assert.True(eventRun); Assert.True(event2Run); - } [Fact] @@ -165,16 +143,13 @@ public async Task ARunTimeErrorInAttributeSelectorShouldNotBreakOtherApps() App .Entity("binary_sensor.pir") .StateChanges - .Subscribe(s => - { - eventRun = true; - }); + .Subscribe(_ => eventRun = true); App .Entity("binary_sensor.pir") .StateChanges .Where(e => e.New.Attribute!.an_int == "WTF this is not an int!!") - .Subscribe(s => + .Subscribe(_ => { int x = int.Parse("ss"); }); @@ -182,10 +157,7 @@ public async Task ARunTimeErrorInAttributeSelectorShouldNotBreakOtherApps() App .Entity("binary_sensor.pir") .StateChanges - .Subscribe(s => - { - event2Run = true; - }); + .Subscribe(_ => event2Run = true); AddDefaultEvent(); @@ -194,10 +166,8 @@ public async Task ARunTimeErrorInAttributeSelectorShouldNotBreakOtherApps() LoggerMock.AssertLogged(LogLevel.Error, Times.Once()); Assert.True(eventRun); Assert.True(event2Run); - } - [Fact] public async Task ToUnavailableShouldNotBreak() { @@ -209,16 +179,13 @@ public async Task ToUnavailableShouldNotBreak() App .Entity("binary_sensor.pir") .StateChanges - .Subscribe(s => - { - eventRun = true; - }); + .Subscribe(_ => eventRun = true); App .Entity("binary_sensor.pir") .StateChanges .Where(e => e.New.State == "on") - .Subscribe(s => + .Subscribe(_ => { int x = int.Parse("ss"); }); @@ -226,10 +193,7 @@ public async Task ToUnavailableShouldNotBreak() App .Entity("binary_sensor.pir") .StateChanges - .Subscribe(s => - { - event2Run = true; - }); + .Subscribe(_ => event2Run = true); AddEventFakeGoingUnavailable(); @@ -238,7 +202,6 @@ public async Task ToUnavailableShouldNotBreak() LoggerMock.AssertLogged(LogLevel.Error, Times.Never()); Assert.False(eventRun); Assert.False(event2Run); - } [Fact] @@ -252,16 +215,13 @@ public async Task FromUnavailableShouldNotBreak() App .Entity("binary_sensor.pir") .StateChanges - .Subscribe(s => - { - eventRun = true; - }); + .Subscribe(_ => eventRun = true); App .Entity("binary_sensor.pir") .StateChanges .Where(e => e.New.State == "on") - .Subscribe(s => + .Subscribe(_ => { int x = int.Parse("ss"); }); @@ -269,10 +229,7 @@ public async Task FromUnavailableShouldNotBreak() App .Entity("binary_sensor.pir") .StateChanges - .Subscribe(s => - { - event2Run = true; - }); + .Subscribe(_ => event2Run = true); AddEventFakeFromUnavailable(); @@ -281,76 +238,8 @@ public async Task FromUnavailableShouldNotBreak() LoggerMock.AssertLogged(LogLevel.Error, Times.Never()); Assert.False(eventRun); Assert.False(event2Run); - } - // [Fact] - // public async Task MissingAttributeShouldNotBreakOtherApps() - // { - // // ARRANGE - // bool eventRun = false; - // App - // .Entities(e => e.Attribute.does_not_exist == "yay") - // .WhenStateChange() - // .Call((entity, from, to) => - // { - // // Do conversion error - // int x = int.Parse("ss"); - // return Task.CompletedTask; - // }).Execute(); - - // App - // .Entity("binary_sensor.pir") - // .WhenStateChange("on") - // .Call((entity, from, to) => - // { - // // Do conversion error - // eventRun = true; - // return Task.CompletedTask; - // }).Execute(); - - // DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - - // await RunDefauldDaemonUntilCanceled(); - - // // LoggerMock.AssertLogged(LogLevel.Error, Times.Once()); - // Assert.True(eventRun); - - // } - - // [Fact] - // public async Task MissingEntityShouldNotBreakOtherApps() - // { - // // ARRANGE - // bool eventRun = false; - // App - // .Entity("binary_sensor.pir") - // .WhenStateChange() - // .Call((entity, from, to) => - // { - // // Do conversion error - // App.Entity("does_not_exist").TurnOn(); - // return Task.CompletedTask; - // }).Execute(); - - // App - // .Entity("binary_sensor.pir") - // .WhenStateChange("on") - // .Call((entity, from, to) => - // { - // // Do conversion error - // eventRun = true; - // return Task.CompletedTask; - // }).Execute(); - - // DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); - - // await RunDefauldDaemonUntilCanceled(); - - // Assert.True(eventRun); - - // } - private void AddDefaultEvent() { DefaultHassClientMock.FakeEvents.Enqueue(new HassEvent diff --git a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/NetDaemonAppTests.cs b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/NetDaemonAppTests.cs index 1aa879c5b..ad510f8b4 100644 --- a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/NetDaemonAppTests.cs +++ b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/NetDaemonAppTests.cs @@ -87,7 +87,7 @@ public void EnitiesFuncShouldCallCorrectDaemonEntity() } [Fact] - public void EnitiesShouldCallCorrectDaemonEntity() + public void EntitiesShouldCallCorrectDaemonEntity() { // ARRANGE and ACT _app.Entities(new string[] { "light.somelight" }); @@ -96,7 +96,7 @@ public void EnitiesShouldCallCorrectDaemonEntity() } [Fact] - public void EnityShouldCallCorrectDaemonEntity() + public void EntityShouldCallCorrectDaemonEntity() { // ARRANGE and ACT _app.Entity("light.somelight"); @@ -120,7 +120,7 @@ public void GetStateShouldCallCorrectDaemonGetState() public async Task GlobalShouldReturnCorrectData(string key, object value) { await using var _app_two = new AppTestApp2(); - await _app_two.StartUpAsync(_netDaemonMock.Object); + await _app_two.StartUpAsync(_netDaemonMock.Object).ConfigureAwait(false); _app_two.Id = "app2"; // ARRANGE and ACT @@ -139,11 +139,10 @@ public async Task GlobalShouldReturnCorrectData(string key, object value) public void LogMessageWithDifferentLogLevelsShoulCallCorrectLogger(LogLevel level, string methodName) { // ARRANGE - var message = "message"; - MethodInfo? methodInfo; + const string? message = "message"; // ACT - methodInfo = _app.GetType().GetMethod(methodName, new Type[] { typeof(string) }); + MethodInfo? methodInfo = _app.GetType().GetMethod(methodName, new Type[] { typeof(string) }); methodInfo?.Invoke(_app, new object[] { message }); // ASSERT @@ -159,7 +158,7 @@ public void LogMessageWithDifferentLogLevelsShoulCallCorrectLogger(LogLevel leve public void LogMessageWithExceptionAndDifferentLogLevelsShoulCallCorrectLogger(LogLevel level, string methodName) { // ARRANGE - var message = "message"; + const string? message = "message"; var exception = new NullReferenceException("Null"); // ACT var methodInfo = _app.GetType().GetMethod(methodName, new Type[] { typeof(Exception), typeof(string) }); @@ -178,7 +177,7 @@ public void LogMessageWithExceptionAndDifferentLogLevelsShoulCallCorrectLogger(L public void LogMessageWithParamsAndDifferentLogLevelsShoulCallCorrectLogger(LogLevel level, string methodName) { // ARRANGE - var message = "Hello {name}"; + const string? message = "Hello {name}"; // ACT var methodInfo = _app.GetType().GetMethod(methodName, new Type[] { typeof(string), typeof(object[]) }); @@ -197,7 +196,7 @@ public void LogMessageWithParamsAndDifferentLogLevelsShoulCallCorrectLogger(LogL public void LogMessageWithParamsExceptionAndDifferentLogLevelsShoulCallCorrectLogger(LogLevel level, string methodName) { // ARRANGE - var message = "Hello {name}"; + const string? message = "Hello {name}"; var exception = new NullReferenceException("Null"); // ACT var methodInfo = _app.GetType().GetMethod(methodName, new Type[] { typeof(Exception), typeof(string), typeof(object[]) }); diff --git a/tests/NetDaemon.Daemon.Tests/Reactive/RxAppTest.cs b/tests/NetDaemon.Daemon.Tests/Reactive/RxAppTest.cs index e87689014..e9c153c9e 100644 --- a/tests/NetDaemon.Daemon.Tests/Reactive/RxAppTest.cs +++ b/tests/NetDaemon.Daemon.Tests/Reactive/RxAppTest.cs @@ -34,7 +34,6 @@ public async Task CallServiceShouldCallCorrectFunction() DefaultDaemonRxApp.CallService("mydomain", "myservice", dynObj); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // ASSERT VerifyCallServiceTuple("mydomain", "myservice", ("attr", "value")); } @@ -48,16 +47,12 @@ public async Task NewAllEventDataShouldCallFunction() // ACT DefaultDaemonRxApp.StateAllChanges - .Subscribe(s => - { - called = true; - }); + .Subscribe(_ => called = true); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "on", "on"); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // ASSERT Assert.True(called); } @@ -71,16 +66,12 @@ public async Task NewEventShouldCallFunction() // ACT DefaultDaemonRxApp.EventChanges - .Subscribe(s => - { - called = true; - }); + .Subscribe(_ => called = true); DefaultHassClientMock.AddCustomEvent("AN_EVENT", new { somedata = "hello" }); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // ASSERT Assert.True(called); } @@ -95,10 +86,7 @@ public async Task NewEventMissingDataAttributeShouldReturnNull() // ACT DefaultDaemonRxApp.EventChanges - .Subscribe(s => - { - missingAttribute = s.Data?.missing_data; - }); + .Subscribe(s => missingAttribute = s.Data?.missing_data); var expandoObj = new ExpandoObject(); dynamic dynExpObject = expandoObj; @@ -108,7 +96,6 @@ public async Task NewEventMissingDataAttributeShouldReturnNull() await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // ASSERT Assert.Null(missingAttribute); } @@ -122,16 +109,12 @@ public async Task NewStateEventShouldCallFunction() // ACT DefaultDaemonRxApp.StateChanges - .Subscribe(s => - { - called = true; - }); + .Subscribe(_ => called = true); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // ASSERT Assert.True(called); } @@ -146,10 +129,8 @@ public async Task RunScriptShouldCallCorrectFunction() DefaultDaemonRxApp.RunScript("myscript"); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // ASSERT - DefaultHassClientMock.VerifyCallServiceTimes("myscript", Times.Once()); } @@ -163,7 +144,6 @@ public async Task RunScriptWithDomainShouldCallCorrectFunction() DefaultDaemonRxApp.RunScript("script.myscript"); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // ASSERT DefaultHassClientMock.VerifyCallServiceTimes("myscript", Times.Once()); } @@ -177,16 +157,12 @@ public async Task SameStateEventShouldNotCallFunction() // ACT DefaultDaemonRxApp.StateChanges - .Subscribe(s => - { - called = true; - }); + .Subscribe(_ => called = true); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "on", "on"); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // ASSERT Assert.False(called); } @@ -202,7 +178,6 @@ public async Task SetStateShouldReturnCorrectData() DefaultDaemonRxApp.SetState("sensor.any_sensor", "on", dynObj); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // ASSERT DefaultHassClientMock.Verify(n => n.SetState("sensor.any_sensor", "on", expObj)); } @@ -213,7 +188,7 @@ public async Task StartupAsyncShouldThrowIfDaemonIsNull() INetDaemonHost? host = null; // ARRANGE ACT ASSERT - await Assert.ThrowsAsync(() => DefaultDaemonRxApp.StartUpAsync(host!)); + await Assert.ThrowsAsync(() => DefaultDaemonRxApp.StartUpAsync(host!)).ConfigureAwait(false); } [Fact] @@ -227,7 +202,6 @@ public async Task StateShouldReturnCorrectEntity() await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // ASSERT Assert.NotNull(entity); } @@ -243,7 +217,6 @@ public async Task StateShouldReturnNullIfAttributeNotExist() await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // ASSERT Assert.Null(entity?.Attribute?.not_exists); } @@ -286,16 +259,12 @@ public async Task UsingEntitiesLambdaNewEventShouldCallFunction() // ACT DefaultDaemonRxApp.Entities(n => n.EntityId.StartsWith("binary_sensor.pir")) .StateChanges - .Subscribe(s => - { - called = true; - }); + .Subscribe(_ => called = true); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir_2", "off", "on"); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // ASSERT Assert.True(called); } @@ -310,16 +279,12 @@ public async Task CallbackObserverAttributeMissingShouldReturnNull() // ACT DefaultDaemonRxApp.Entities(n => n.EntityId.StartsWith("binary_sensor.pir")) .StateChanges - .Subscribe(s => - { - missingString = s.New.Attribute?.missing_attribute; - }); + .Subscribe(s => missingString = s.New.Attribute?.missing_attribute); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir_2", "off", "on"); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // ASSERT Assert.Null(missingString); } @@ -334,16 +299,12 @@ public async Task UsingEntitiesNewEventShouldCallFunction() // ACT DefaultDaemonRxApp.Entities("binary_sensor.pir", "binary_sensor.pir_2") .StateChanges - .Subscribe(s => - { - called = true; - }); + .Subscribe(_ => called = true); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir_2", "off", "on"); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // ASSERT Assert.True(called); } @@ -358,16 +319,12 @@ public async Task UsingEntityNewEventShouldCallFunction() // ACT DefaultDaemonRxApp.Entity("binary_sensor.pir") .StateChanges - .Subscribe(s => - { - called = true; - }); + .Subscribe(_ => called = true); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // ASSERT Assert.True(called); } @@ -382,16 +339,12 @@ public async Task UsingEntityNewEventShouldNotCallFunction() // ACT DefaultDaemonRxApp.Entity("binary_sensor.other_pir") .StateChanges - .Subscribe(s => - { - called = true; - }); + .Subscribe(_ => called = true); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // ASSERT Assert.False(called); } @@ -404,16 +357,12 @@ public async Task WhenStateStaysSameForTimeItShouldCallFunction() using var ctx = DefaultDaemonRxApp.StateChanges .Where(t => t.New.EntityId == "binary_sensor.pir") .NDSameStateFor(TimeSpan.FromMilliseconds(50)) - .Subscribe(e => - { - isRun = true; - }); + .Subscribe(_ => isRun = true); DefaultHassClientMock.AddChangedEvent("binary_sensor.pir", "off", "on"); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - Assert.True(isRun); } @@ -431,7 +380,6 @@ public async Task SavedDataShouldReturnSameDataUsingExpando() await RunFakeDaemonUntilTimeout().ConfigureAwait(false); - // ASSERT Assert.Equal(data, collectedData); } diff --git a/tests/NetDaemon.Daemon.Tests/Reactive/RxSchedulers.cs b/tests/NetDaemon.Daemon.Tests/Reactive/RxSchedulers.cs index 65ac8ab53..697f5da72 100644 --- a/tests/NetDaemon.Daemon.Tests/Reactive/RxSchedulers.cs +++ b/tests/NetDaemon.Daemon.Tests/Reactive/RxSchedulers.cs @@ -31,7 +31,7 @@ public async Task CreateObservableIntervallFailureShouldLogError() // ACT using var disposable = app.CreateObservableIntervall(TimeSpan.FromMilliseconds(10), () => throw new Exception("hello!")); - await Task.Delay(150); + await Task.Delay(150).ConfigureAwait(false); // ASSERT LoggerMock.AssertLogged(LogLevel.Error, Times.AtLeastOnce()); } @@ -50,7 +50,7 @@ public async Task CreateObservableIntervallShouldCallFunction() // ACT using var disposable = app.CreateObservableIntervall(TimeSpan.FromMilliseconds(10), () => called = true); - await Task.Delay(150); + await Task.Delay(150).ConfigureAwait(false); // ASSERT Assert.True(called); } @@ -66,7 +66,7 @@ public async Task CreateObservableTimerFailureShouldLogError() // ACT using var disposable = app.CreateObservableTimer(DateTime.Now, TimeSpan.FromMilliseconds(10), () => throw new Exception("Hello!")); - await Task.Delay(150); + await Task.Delay(150).ConfigureAwait(false); // ASSERT LoggerMock.AssertLogged(LogLevel.Error, Times.AtLeastOnce()); } @@ -85,7 +85,7 @@ public async Task CreateObservableTimerShouldCallFunction() // ACT using var disposable = app.CreateObservableTimer(DateTime.Now, TimeSpan.FromMilliseconds(10), () => called = true); - await Task.Delay(100); + await Task.Delay(100).ConfigureAwait(false); // ASSERT Assert.True(called); } @@ -168,7 +168,7 @@ public void RunEveryMinuteShouldCallCreateObservableIntervall() { // ARRANGE // ACT - DefaultMockedRxApp.Object.RunEveryMinute(0, () => System.Console.WriteLine("Test")); + DefaultMockedRxApp.Object.RunEveryMinute(0, () => Console.WriteLine("Test")); // ASSERT DefaultMockedRxApp.Verify(n => n.CreateObservableTimer(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once()); @@ -204,7 +204,7 @@ public async Task RunInFailureShouldLogError() DefaultDaemonRxApp.RunIn(TimeSpan.FromMilliseconds(100), () => throw new Exception("RxError")); // ASSERT - await Task.Delay(150); + await Task.Delay(150).ConfigureAwait(false); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); LoggerMock.AssertLogged(LogLevel.Error, Times.Once()); } @@ -222,7 +222,7 @@ public async Task RunInShouldCallFunction() // ASSERT Assert.False(called); - await Task.Delay(150); + await Task.Delay(150).ConfigureAwait(false); await RunFakeDaemonUntilTimeout().ConfigureAwait(false); Assert.True(called); diff --git a/tests/NetDaemon.Daemon.Tests/SchedulerTests.cs b/tests/NetDaemon.Daemon.Tests/SchedulerTests.cs index 41a39ef15..27d9ef488 100644 --- a/tests/NetDaemon.Daemon.Tests/SchedulerTests.cs +++ b/tests/NetDaemon.Daemon.Tests/SchedulerTests.cs @@ -28,21 +28,21 @@ public async void TestRunInShouldStartAndCompleteCorrectly() scheduledResult = scheduler.RunIn(200, async () => { isTaskRun = true; - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); }); // ASSERT // Assert not run before time - await Task.Delay(100); + await Task.Delay(100).ConfigureAwait(false); Assert.False(isTaskRun); - await Task.Delay(150); + await Task.Delay(150).ConfigureAwait(false); } try { - await scheduledResult.Task; + await scheduledResult.Task.ConfigureAwait(false); } catch { @@ -72,12 +72,12 @@ public async void RunInShouldLogWarningForFaultyRun() return Task.CompletedTask; }); - await Task.Delay(100); + await Task.Delay(100).ConfigureAwait(false); } try { - await scheduledResult.Task; + await scheduledResult.Task.ConfigureAwait(false); } catch { @@ -102,21 +102,21 @@ public async void TestRunInShouldStartAndAncCancelCorrectly() scheduledResult = scheduler.RunIn(200, async () => { isTaskRun = true; - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); }); // ASSERT // Assert not run before time - await Task.Delay(100); + await Task.Delay(100).ConfigureAwait(false); Assert.False(isTaskRun); scheduledResult.CancelSource.Cancel(); - await Task.Delay(150); + await Task.Delay(150).ConfigureAwait(false); } try { - await scheduledResult.Task; + await scheduledResult.Task.ConfigureAwait(false); } catch { @@ -184,18 +184,18 @@ public async void TestRunDailyUsingStartTimeCallsFuncCorrectly() scheduledResult = scheduler.RunDaily("10:00:01", async () => { nrOfRuns++; - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); }); - await Task.Delay(600); + await Task.Delay(600).ConfigureAwait(false); // ASSERT Assert.True(nrOfRuns == 0); - await Task.Delay(500); + await Task.Delay(500).ConfigureAwait(false); Assert.True(nrOfRuns == 1); } try { - await scheduledResult.Task; + await scheduledResult.Task.ConfigureAwait(false); } catch { @@ -221,11 +221,11 @@ public async void RunDailyFaultShouldLogWarning() int i = int.Parse("Not an integer makes runtime error!"); return Task.CompletedTask; }); - await Task.Delay(1500); + await Task.Delay(1500).ConfigureAwait(false); } try { - await scheduledResult.Task; + await scheduledResult.Task.ConfigureAwait(false); } catch { @@ -254,11 +254,11 @@ public async void RunDailyOnDaysFaultShouldLogWarning() int i = int.Parse("Not an integer makes runtime error!"); return Task.CompletedTask; }); - await Task.Delay(1500); + await Task.Delay(1500).ConfigureAwait(false); } try { - await scheduledResult.Task; + await scheduledResult.Task.ConfigureAwait(false); } catch { @@ -284,19 +284,19 @@ public async void TestRunDailyUsingStartTimeCancelsCorrectly() scheduledResult = scheduler.RunDaily("10:00:01", async () => { nrOfRuns++; - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); }); - await Task.Delay(600); + await Task.Delay(600).ConfigureAwait(false); // ASSERT Assert.True(nrOfRuns == 0); scheduledResult.CancelSource.Cancel(); - await Task.Delay(500); + await Task.Delay(500).ConfigureAwait(false); Assert.True(nrOfRuns == 0); } try { - await scheduledResult.Task; + await scheduledResult.Task.ConfigureAwait(false); } catch { @@ -327,19 +327,19 @@ public async void TestRunDailyUsingStartTimeOnWeekdayCallsFuncCorrectly(string t scheduledResult = scheduler.RunDaily("10:00:01", new DayOfWeek[] { dayOfWeek }, async () => { nrOfRuns++; - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); }); - await Task.Delay(600); + await Task.Delay(600).ConfigureAwait(false); // ASSERT Assert.True(nrOfRuns == 0); - await Task.Delay(800); + await Task.Delay(800).ConfigureAwait(false); Assert.True(nrOfRuns >= 1); } try { - await scheduledResult.Task; + await scheduledResult.Task.ConfigureAwait(false); } catch { @@ -369,19 +369,19 @@ public async void TestRunDailyUsingStartTimeOnWeekdayNotCalled(string time) scheduledResult = scheduler.RunDaily("10:00:01", new DayOfWeek[] { DayOfWeek.Monday }, async () => { nrOfRuns++; - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); }); - await Task.Delay(600); + await Task.Delay(600).ConfigureAwait(false); // ASSERT Assert.True(nrOfRuns == 0); - await Task.Delay(500); + await Task.Delay(500).ConfigureAwait(false); Assert.False(nrOfRuns == 1); } try { - await scheduledResult.Task; + await scheduledResult.Task.ConfigureAwait(false); } catch { @@ -405,19 +405,19 @@ public async void TestRunEveryMinuteStartTimeCallsFuncCorrectly() scheduledResult = scheduler.RunEveryMinute(0, async () => { nrOfRuns++; - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); }); - await Task.Delay(300); + await Task.Delay(300).ConfigureAwait(false); // ASSERT Assert.Equal(0, nrOfRuns); - await Task.Delay(1500); + await Task.Delay(1500).ConfigureAwait(false); Assert.True(nrOfRuns >= 1); } try { - await scheduledResult.Task; + await scheduledResult.Task.ConfigureAwait(false); } catch { @@ -443,12 +443,12 @@ public async void RunEveryMinuteFaultyShouldLogWarning() int i = int.Parse("Not an integer makes runtime error!"); return Task.CompletedTask; }); - await Task.Delay(1000); + await Task.Delay(1000).ConfigureAwait(false); } try { - await scheduledResult.Task; + await scheduledResult.Task.ConfigureAwait(false); } catch { @@ -474,20 +474,20 @@ public async void TestRunEveryMinuteStartTimeCanceledCorrectly() scheduledResult = scheduler.RunEveryMinute(0, async () => { nrOfRuns++; - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); }); - await Task.Delay(300); + await Task.Delay(300).ConfigureAwait(false); // ASSERT Assert.Equal(0, nrOfRuns); scheduledResult.CancelSource.Cancel(); - await Task.Delay(1500); + await Task.Delay(1500).ConfigureAwait(false); Assert.Equal(0, nrOfRuns); } try { - await scheduledResult.Task; + await scheduledResult.Task.ConfigureAwait(false); } catch { @@ -513,19 +513,19 @@ public async void TestRunEveryMinuteStartTimeNotZeroCallsFuncCorrectly() scheduledResult = scheduler.RunEveryMinute(20, async () => { nrOfRuns++; - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); }); - await Task.Delay(600); + await Task.Delay(600).ConfigureAwait(false); // ASSERT Assert.True(nrOfRuns == 0); - await Task.Delay(500); + await Task.Delay(500).ConfigureAwait(false); Assert.True(nrOfRuns == 1); } try { - await scheduledResult.Task; + await scheduledResult.Task.ConfigureAwait(false); } catch { @@ -542,12 +542,9 @@ public async Task ScheduleLongTaskWillCompensateTimeToZero() await using (IScheduler scheduler = new Scheduler(mockTimeManager.Object)) { // ACT - var runTask = scheduler.RunEvery(20, async () => - { - await Task.Delay(50); - }); + var runTask = scheduler.RunEvery(20, async () => await Task.Delay(50).ConfigureAwait(false)); - await Task.WhenAny(runTask.Task, Task.Delay(500)); + await Task.WhenAny(runTask.Task, Task.Delay(500)).ConfigureAwait(false); } // ASSERT mockTimeManager.Verify(n => n.Delay(It.IsAny(), It.IsAny()), Times.Never); @@ -563,12 +560,9 @@ public async Task ScheduleLongTaskWillCompensateTime() await using (IScheduler scheduler = new Scheduler(mockTimeManager.Object)) { // ACT - var runTask = scheduler.RunEvery(20, async () => - { - await Task.Delay(1); - }); + var runTask = scheduler.RunEvery(20, async () => await Task.Delay(1).ConfigureAwait(false)); - await Task.WhenAny(runTask.Task, Task.Delay(100)); + await Task.WhenAny(runTask.Task, Task.Delay(100)).ConfigureAwait(false); } // ASSERT diff --git a/tests/NetDaemon.Daemon.Tests/TestBaseClasses.cs b/tests/NetDaemon.Daemon.Tests/TestBaseClasses.cs index da7780314..cb56686f3 100644 --- a/tests/NetDaemon.Daemon.Tests/TestBaseClasses.cs +++ b/tests/NetDaemon.Daemon.Tests/TestBaseClasses.cs @@ -18,34 +18,30 @@ public class BaseTestRxApp : NetDaemonRxApp { } public class CoreDaemonHostTestBase : DaemonHostTestBase, IAsyncLifetime { - private readonly Common.NetDaemonApp _defaultDaemonApp; - private readonly BaseTestRxApp _defaultDaemonRxApp; - private readonly Mock _defaultMockedRxApp; - private readonly NetDaemonHost _notConnectedDaemonHost; public CoreDaemonHostTestBase() : base() { - _defaultDaemonApp = new BaseTestApp + DefaultDaemonApp = new BaseTestApp { Id = "app_id", IsEnabled = true }; - DefaultDaemonHost.InternalRunningAppInstances[_defaultDaemonApp.Id!] = _defaultDaemonApp; + DefaultDaemonHost.InternalRunningAppInstances[DefaultDaemonApp.Id!] = DefaultDaemonApp; - _defaultDaemonRxApp = new BaseTestRxApp + DefaultDaemonRxApp = new BaseTestRxApp { Id = "app_rx_id", IsEnabled = true }; - DefaultDaemonHost.InternalRunningAppInstances[_defaultDaemonRxApp.Id!] = _defaultDaemonRxApp; + DefaultDaemonHost.InternalRunningAppInstances[DefaultDaemonRxApp.Id!] = DefaultDaemonRxApp; - _defaultMockedRxApp = new Mock() { CallBase = true }; - _defaultMockedRxApp.Object.Id = "app_rx_mock_id"; - _defaultMockedRxApp.Object.IsEnabled = true; - _defaultMockedRxApp.Setup(n => n.CreateObservableIntervall(It.IsAny(), It.IsAny())).Returns(new Mock().Object); - DefaultDaemonHost.InternalRunningAppInstances[_defaultMockedRxApp.Object.Id!] = _defaultMockedRxApp.Object; + DefaultMockedRxApp = new Mock() { CallBase = true }; + DefaultMockedRxApp.Object.Id = "app_rx_mock_id"; + DefaultMockedRxApp.Object.IsEnabled = true; + DefaultMockedRxApp.Setup(n => n.CreateObservableIntervall(It.IsAny(), It.IsAny())).Returns(new Mock().Object); + DefaultDaemonHost.InternalRunningAppInstances[DefaultMockedRxApp.Object.Id!] = DefaultMockedRxApp.Object; _notConnectedDaemonHost = new NetDaemonHost(HassClientMock.MockConnectFalse.Object, DefaultDataRepositoryMock.Object, LoggerMock.LoggerFactory); @@ -75,7 +71,6 @@ public void SetupFakeData() State = "off" }); - SetEntityState(new() { EntityId = "switch.correct_entity", @@ -125,7 +120,7 @@ public void SetupFakeData() SetEntityState(new() { - EntityId = "light.ligth_in_area", + EntityId = "light.light_in_area", State = "off", Attributes = new Dictionary { @@ -141,9 +136,9 @@ public void SetupFakeData() { await base.DisposeAsync().ConfigureAwait(false); - await _defaultDaemonApp.DisposeAsync().ConfigureAwait(false); - await _defaultDaemonRxApp.DisposeAsync().ConfigureAwait(false); - await _defaultMockedRxApp.Object.DisposeAsync().ConfigureAwait(false); + await DefaultDaemonApp.DisposeAsync().ConfigureAwait(false); + await DefaultDaemonRxApp.DisposeAsync().ConfigureAwait(false); + await DefaultMockedRxApp.Object.DisposeAsync().ConfigureAwait(false); } /// @@ -153,14 +148,14 @@ public void SetupFakeData() { await base.InitializeAsync().ConfigureAwait(false); - await _defaultDaemonApp.StartUpAsync(DefaultDaemonHost).ConfigureAwait(false); - await _defaultDaemonRxApp.StartUpAsync(DefaultDaemonHost).ConfigureAwait(false); - await _defaultMockedRxApp.Object.StartUpAsync(DefaultDaemonHost).ConfigureAwait(false); + await DefaultDaemonApp.StartUpAsync(DefaultDaemonHost).ConfigureAwait(false); + await DefaultDaemonRxApp.StartUpAsync(DefaultDaemonHost).ConfigureAwait(false); + await DefaultMockedRxApp.Object.StartUpAsync(DefaultDaemonHost).ConfigureAwait(false); } - public BaseTestRxApp DefaultDaemonRxApp => _defaultDaemonRxApp; - public Mock DefaultMockedRxApp => _defaultMockedRxApp; - public Common.NetDaemonApp DefaultDaemonApp => _defaultDaemonApp; + public BaseTestRxApp DefaultDaemonRxApp { get; } + public Mock DefaultMockedRxApp { get; } + public Common.NetDaemonApp DefaultDaemonApp { get; } public static string HelloWorldData => "Hello world!"; public (Task, CancellationTokenSource) ReturnRunningNotConnectedDaemonHostTask(short milliSeconds = 100, bool overrideDebugNotCancel = false) @@ -170,7 +165,5 @@ public void SetupFakeData() : new CancellationTokenSource(milliSeconds); return (_notConnectedDaemonHost.Run("host", 8123, false, "token", cancelSource.Token), cancelSource); } - - } } \ No newline at end of file diff --git a/tests/NetDaemon.Daemon.Tests/TimeManagerMock.cs b/tests/NetDaemon.Daemon.Tests/TimeManagerMock.cs index 114be676b..ae2c8d5b7 100644 --- a/tests/NetDaemon.Daemon.Tests/TimeManagerMock.cs +++ b/tests/NetDaemon.Daemon.Tests/TimeManagerMock.cs @@ -14,7 +14,7 @@ public TimeManagerMock(DateTime time) _time = time; SetupGet(n => n.Current).Returns(_time); Setup(n => n.Delay(It.IsAny(), It.IsAny())) - .Returns(async (TimeSpan s, CancellationToken c) => await Task.Delay(s, c)); + .Returns(async (TimeSpan s, CancellationToken c) => await Task.Delay(s, c).ConfigureAwait(false)); } public TimeManagerMock() From 3402d6d50f702a7ce6e15ddac3948530dc052588 Mon Sep 17 00:00:00 2001 From: helto4real Date: Sat, 26 Dec 2020 17:45:55 +0100 Subject: [PATCH 05/10] Roslynator plus .net 5 analysers --- .linting/roslynator.ruleset | 1 + .../NetDaemon.App/Common/AppRuntimeInfo.cs | 2 +- src/App/NetDaemon.App/Common/Attributes.cs | 14 ++- .../Configuration/HomeAssistantSettings.cs | 2 +- .../Common/Configuration/NetDaemonSettings.cs | 9 +- src/App/NetDaemon.App/Common/DelayResult.cs | 4 +- .../Common/Exceptions/Exceptions.cs | 66 +++++++++++ .../NetDaemon.App/Common/ExtensionMethods.cs | 33 ++++-- .../NetDaemon.App/Common/Fluent/EntityBase.cs | 15 ++- .../Common/Fluent/EntityManager.cs | 16 ++- src/App/NetDaemon.App/Common/Fluent/Fluent.cs | 6 + .../Common/Fluent/FluentCamera.cs | 15 ++- .../Common/Fluent/FluentEvent.cs | 9 +- .../Common/Fluent/FluentExpandoObject.cs | 29 ++++- .../Common/Fluent/FluentInputSelect.cs | 9 +- .../Common/Fluent/FluentMediaPlayer.cs | 5 +- src/App/NetDaemon.App/Common/IHttpHandler.cs | 4 +- src/App/NetDaemon.App/Common/INetDaemon.cs | 4 + src/App/NetDaemon.App/Common/Messages.cs | 37 +++--- src/App/NetDaemon.App/Common/NetDaemonApp.cs | 110 ++++++++++-------- .../NetDaemon.App/Common/NetDaemonAppBase.cs | 95 ++++++++------- .../Common/Reactive/AppDaemonRxApp.cs | 83 ++++++------- .../Common/Reactive/DisposableTimerResult.cs | 2 + .../Common/Reactive/ObservableBase.cs | 8 +- .../Reactive/ObservableExtensionMethods.cs | 12 +- .../NetDaemon.App/Common/Reactive/RxEntity.cs | 11 +- .../NetDaemon.App/Common/Reactive/RxEvent.cs | 2 +- src/App/NetDaemon.App/NetDaemon.App.csproj | 6 + .../NetDaemon.Daemon/Daemon/CodeManager.cs | 5 +- .../Daemon/Config/YamlAppConfig.cs | 11 +- .../Daemon/Config/YamlConfig.cs | 3 +- .../NetDaemon.Daemon/Daemon/HttpHandler.cs | 13 ++- .../NetDaemon.Daemon/Daemon/NetDaemonHost.cs | 27 ++--- .../NetDaemon.Daemon/Daemon/Scheduler.cs | 3 +- .../Daemon/Storage/DataRepository.cs | 3 +- .../DaemonRunner/NetDaemonExtensions.cs | 3 +- .../DaemonRunner/Service/API/WsHandler.cs | 3 +- .../DaemonRunner/Service/App/CodeGenerator.cs | 17 +-- .../DaemonRunner/Service/RunnerService.cs | 3 +- .../NetDaemon.Fakes/DaemonHostTestBase.cs | 3 +- src/Fakes/NetDaemon.Fakes/HassClientMock.cs | 4 +- .../DaemonRunner/Api/ApiTests.cs | 3 +- .../FakesTests/FakeTests.cs | 3 +- .../Fluent/FluentEventTests.cs | 5 +- .../NetDaemonApp/NetDaemonAppTests.cs | 11 +- .../Reactive/RxAppTest.cs | 3 +- 46 files changed, 453 insertions(+), 279 deletions(-) create mode 100644 src/App/NetDaemon.App/Common/Exceptions/Exceptions.cs diff --git a/.linting/roslynator.ruleset b/.linting/roslynator.ruleset index f82e955cb..967ecd465 100644 --- a/.linting/roslynator.ruleset +++ b/.linting/roslynator.ruleset @@ -25,6 +25,7 @@ Just add ruleset file to a solution and open it. + diff --git a/src/App/NetDaemon.App/Common/AppRuntimeInfo.cs b/src/App/NetDaemon.App/Common/AppRuntimeInfo.cs index a558f0645..018c6e47e 100644 --- a/src/App/NetDaemon.App/Common/AppRuntimeInfo.cs +++ b/src/App/NetDaemon.App/Common/AppRuntimeInfo.cs @@ -33,7 +33,7 @@ public sealed class AppRuntimeInfo /// in app switch /// [JsonPropertyName("app_attributes")] - public Dictionary AppAttributes { get; set; } = new(); + public Dictionary AppAttributes { get; } = new(); } } diff --git a/src/App/NetDaemon.App/Common/Attributes.cs b/src/App/NetDaemon.App/Common/Attributes.cs index ba33e68e0..85de630bb 100644 --- a/src/App/NetDaemon.App/Common/Attributes.cs +++ b/src/App/NetDaemon.App/Common/Attributes.cs @@ -21,21 +21,23 @@ public sealed class DisableLogAttribute : System.Attribute { // See the attribute guidelines at // http://go.microsoft.com/fwlink/?LinkId=85236 - private readonly SupressLogType[] _logTypesToSupress; + private readonly SupressLogType[]? _logTypesToSupress; /// /// Default constructor /// /// List of logtypes to supress - public DisableLogAttribute(params SupressLogType[] logTypes) - { - _logTypesToSupress = logTypes; - } + public DisableLogAttribute(params SupressLogType[] logTypes) => _logTypesToSupress = logTypes; /// /// Log types to supress /// - public IEnumerable LogTypesToSupress => _logTypesToSupress; + public IEnumerable? LogTypesToSupress => _logTypesToSupress; + + /// + /// Log tupes used + /// + public SupressLogType[]? LogTypes { get; } } /// diff --git a/src/App/NetDaemon.App/Common/Configuration/HomeAssistantSettings.cs b/src/App/NetDaemon.App/Common/Configuration/HomeAssistantSettings.cs index 77d292629..463415acc 100644 --- a/src/App/NetDaemon.App/Common/Configuration/HomeAssistantSettings.cs +++ b/src/App/NetDaemon.App/Common/Configuration/HomeAssistantSettings.cs @@ -16,7 +16,7 @@ public class HomeAssistantSettings /// /// Connect using ssl /// - public bool Ssl { get; set; } = false; + public bool Ssl { get; set; } /// /// Token to authorize /// diff --git a/src/App/NetDaemon.App/Common/Configuration/NetDaemonSettings.cs b/src/App/NetDaemon.App/Common/Configuration/NetDaemonSettings.cs index a48847469..297851b50 100644 --- a/src/App/NetDaemon.App/Common/Configuration/NetDaemonSettings.cs +++ b/src/App/NetDaemon.App/Common/Configuration/NetDaemonSettings.cs @@ -1,5 +1,7 @@ using System; +using System.Globalization; using System.IO; +using NetDaemon.Common.Exceptions; namespace NetDaemon.Common.Configuration { @@ -27,7 +29,7 @@ public class NetDaemonSettings /// the apps to be in the file paths and tries to find /// all apps recursivly /// - public string? AppSource { get; set; } = null; + public string? AppSource { get; set; } /// /// Returns the directory path of AppSource @@ -36,12 +38,13 @@ public string GetAppSourceDirectory() { var source = AppSource?.Trim() ?? Directory.GetCurrentDirectory(); - if (source.EndsWith(".csproj") || source.EndsWith(".dll")) + if (source.EndsWith(".csproj", true, CultureInfo.InvariantCulture) + || source.EndsWith(".dll", true, CultureInfo.InvariantCulture)) { source = Path.GetDirectoryName(source); } - return source ?? throw new NullReferenceException("Source cannot be null!"); + return source ?? throw new NetDaemonNullReferenceException("Source cannot be null!"); } } } \ No newline at end of file diff --git a/src/App/NetDaemon.App/Common/DelayResult.cs b/src/App/NetDaemon.App/Common/DelayResult.cs index 19f822854..46c4b3949 100644 --- a/src/App/NetDaemon.App/Common/DelayResult.cs +++ b/src/App/NetDaemon.App/Common/DelayResult.cs @@ -14,7 +14,7 @@ public class DelayResult : IDelayResult { private readonly INetDaemonApp _daemonApp; private readonly TaskCompletionSource _delayTaskCompletionSource; - private bool _isCanceled = false; + private bool _isCanceled; /// /// Constructor @@ -52,7 +52,7 @@ public void Cancel() #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls + private bool disposedValue; // To detect redundant calls /// /// Disposes the object and cancel delay diff --git a/src/App/NetDaemon.App/Common/Exceptions/Exceptions.cs b/src/App/NetDaemon.App/Common/Exceptions/Exceptions.cs new file mode 100644 index 000000000..efa63350d --- /dev/null +++ b/src/App/NetDaemon.App/Common/Exceptions/Exceptions.cs @@ -0,0 +1,66 @@ +using System; + +namespace NetDaemon.Common.Exceptions +{ + /// + public class NetDaemonNullReferenceException : NullReferenceException + { + /// + public NetDaemonNullReferenceException() + { + } + + /// + public NetDaemonNullReferenceException(string? message) : base(message) + { + } + + /// + public NetDaemonNullReferenceException(string? message, Exception? innerException) : base(message, innerException) + { + } + } + + /// + public class NetDaemonException : Exception + { + /// + public NetDaemonException() + { + } + + /// + public NetDaemonException(string? message) : base(message) + { + } + + /// + public NetDaemonException(string? message, Exception? innerException) : base(message, innerException) + { + } + } + + /// + public class NetDaemonArgumentNullException : ArgumentNullException + { + /// + public NetDaemonArgumentNullException() + { + } + + /// + public NetDaemonArgumentNullException(string? paramName) : base(paramName) + { + } + + /// + public NetDaemonArgumentNullException(string? message, Exception? innerException) : base(message, innerException) + { + } + + /// + public NetDaemonArgumentNullException(string? paramName, string? message) : base(paramName, message) + { + } + } +} \ No newline at end of file diff --git a/src/App/NetDaemon.App/Common/ExtensionMethods.cs b/src/App/NetDaemon.App/Common/ExtensionMethods.cs index 1c6529e94..39dab6826 100644 --- a/src/App/NetDaemon.App/Common/ExtensionMethods.cs +++ b/src/App/NetDaemon.App/Common/ExtensionMethods.cs @@ -4,13 +4,13 @@ using System.Globalization; using System.Text; using NetDaemon.Common.Fluent; - +using System.Diagnostics.CodeAnalysis; namespace NetDaemon.Common { /// /// Useful extension methods used /// - public static class Extensions + public static class NetDaemonExtensions { /// /// Converts a valuepair to dynamic object @@ -20,30 +20,39 @@ public static dynamic ToDynamic(this (string name, object val)[] attributeNameVa { // Convert the tuple name/value pair to tuple that can be serialized dynamically var attributes = new FluentExpandoObject(true, true); - foreach (var (attribute, value) in attributeNameValuePair) + if (attributeNameValuePair is not null) { - ((IDictionary)attributes).Add(attribute, value); + foreach (var (attribute, value) in attributeNameValuePair) + { + if (value is not null) + { + // We only add non-null values since the FluentExpandoObject will + // return null on missing anyway + attributes.Add(attribute, value); + } + } } - dynamic result = attributes; - return result; + return (dynamic)attributes; } /// /// Converts a anoumous type to expando object /// /// - /// public static ExpandoObject? ToExpandoObject(this object obj) { // Null-check IDictionary expando = new ExpandoObject(); - foreach (PropertyDescriptor? property in TypeDescriptor.GetProperties(obj.GetType())) + if (obj is not null) { - if (property is not null) - expando.Add(property.Name, property.GetValue(obj)); + foreach (PropertyDescriptor? property in TypeDescriptor.GetProperties(obj.GetType())) + { + if (property is not null) + expando.Add(property.Name, property.GetValue(obj)); + } } return (ExpandoObject)expando; @@ -53,8 +62,12 @@ public static dynamic ToDynamic(this (string name, object val)[] attributeNameVa /// Converts any unicode string to a safe Home Assistant name /// /// The unicode string to convert + [SuppressMessage(category: "Microsoft.Globalization", checkId: "CA1308")] public static string ToSafeHomeAssistantEntityId(this string str) { + if (str is null) + return string.Empty; + string normalizedString = str.Normalize(NormalizationForm.FormD); StringBuilder stringBuilder = new(str.Length); diff --git a/src/App/NetDaemon.App/Common/Fluent/EntityBase.cs b/src/App/NetDaemon.App/Common/Fluent/EntityBase.cs index 9e7926dad..c7ae8ab72 100644 --- a/src/App/NetDaemon.App/Common/Fluent/EntityBase.cs +++ b/src/App/NetDaemon.App/Common/Fluent/EntityBase.cs @@ -1,7 +1,7 @@ -using System; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; +using NetDaemon.Common.Exceptions; namespace NetDaemon.Common.Fluent { @@ -20,17 +20,17 @@ public class EntityBase //: EntityState /// /// The daemon used in the API /// - protected readonly INetDaemonApp App; + protected INetDaemonApp App { get; } /// /// The daemon used in the API /// - protected readonly INetDaemon Daemon; + protected INetDaemon Daemon { get; } /// /// The EntityIds used /// - protected readonly IEnumerable EntityIds; + protected IEnumerable EntityIds { get; } /// /// Constructor @@ -48,9 +48,12 @@ public EntityBase(IEnumerable entityIds, INetDaemon daemon, INetDaemonAp /// protected static string GetDomainFromEntity(string entity) { + if (string.IsNullOrEmpty(entity)) + throw new NetDaemonNullReferenceException(nameof(entity)); + var entityParts = entity.Split('.'); if (entityParts.Length != 2) - throw new ApplicationException($"entity_id is mal formatted {entity}"); + throw new NetDaemonException($"entity_id is mal formatted {entity}"); return entityParts[0]; } diff --git a/src/App/NetDaemon.App/Common/Fluent/EntityManager.cs b/src/App/NetDaemon.App/Common/Fluent/EntityManager.cs index 07ea11dd4..a720f5b37 100644 --- a/src/App/NetDaemon.App/Common/Fluent/EntityManager.cs +++ b/src/App/NetDaemon.App/Common/Fluent/EntityManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -9,7 +10,7 @@ namespace NetDaemon.Common.Fluent /// /// Implements interface for managing entities in the fluent API /// - public class EntityManager : EntityBase, IEntity, IAction, + public sealed class EntityManager : EntityBase, IEntity, IAction, IStateEntity, IState, IStateAction, IScript { /// @@ -39,16 +40,14 @@ public IExecute Call(Func func) /// IDelayResult IDelayStateChange.DelayUntilStateChange(object? to, object? from, bool allChanges) { - return App.DelayUntilStateChange(this.EntityIds, to, from, allChanges); + return App.DelayUntilStateChange(EntityIds, to, from, allChanges); } /// - IDelayResult IDelayStateChange.DelayUntilStateChange(Func stateFunc) - { - return App.DelayUntilStateChange(this.EntityIds, stateFunc); - } + IDelayResult IDelayStateChange.DelayUntilStateChange(Func stateFunc) => App.DelayUntilStateChange(EntityIds, stateFunc); /// + [SuppressMessage("Microsoft.Design", "CA1031")] public void Execute() { foreach (var entityId in EntityIds) @@ -200,8 +199,8 @@ async Task IScript.ExecuteAsync() foreach (var scriptName in EntityIds) { var name = scriptName; - if (scriptName.Contains('.')) - name = scriptName[(scriptName.IndexOf('.') + 1)..]; + if (scriptName.Contains('.', StringComparison.InvariantCulture)) + name = scriptName[(scriptName.IndexOf('.', StringComparison.InvariantCulture) + 1)..]; var task = Daemon.CallServiceAsync("script", name); taskList.Add(task); } @@ -220,7 +219,6 @@ async Task IScript.ExecuteAsync() /// You want to keep the items when using this as part of an automation /// that are kept over time. Not keeping when just doing a command /// - /// public async Task ExecuteAsync(bool keepItems) { if (keepItems) diff --git a/src/App/NetDaemon.App/Common/Fluent/Fluent.cs b/src/App/NetDaemon.App/Common/Fluent/Fluent.cs index 476a22a7a..7f17bc4c0 100644 --- a/src/App/NetDaemon.App/Common/Fluent/Fluent.cs +++ b/src/App/NetDaemon.App/Common/Fluent/Fluent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; namespace NetDaemon.Common.Fluent @@ -42,6 +43,7 @@ public interface IDelayStateChange /// The state change to, or null if any state /// The state changed from or null if any state /// Get all changed, even only attribute changes + [SuppressMessage("Microsoft.Naming", "CA1716")] IDelayResult DelayUntilStateChange(object? to = null, object? from = null, bool allChanges = false); /// @@ -95,6 +97,8 @@ public interface IScript /// /// Represent state change actions /// + [SuppressMessage("Microsoft.Naming", "CA1716")] + public interface IState { /// @@ -150,6 +154,7 @@ public interface IStateAction : IExecute /// /// When state change /// + [SuppressMessage("Microsoft.Naming", "CA1716")] public interface IStateChanged { /// @@ -229,6 +234,7 @@ public interface ISpeak /// Generic interface for stop /// /// Return type of stop operation + [SuppressMessage("Microsoft.Naming", "CA1716")] public interface IStop { /// diff --git a/src/App/NetDaemon.App/Common/Fluent/FluentCamera.cs b/src/App/NetDaemon.App/Common/Fluent/FluentCamera.cs index 6fad012d7..53c612f32 100644 --- a/src/App/NetDaemon.App/Common/Fluent/FluentCamera.cs +++ b/src/App/NetDaemon.App/Common/Fluent/FluentCamera.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; +using NetDaemon.Common.Exceptions; namespace NetDaemon.Common.Fluent { @@ -31,6 +33,7 @@ public interface IEnableMotionDetection /// /// Generic interface for PlayStream /// + [SuppressMessage("Microsoft.Naming", "CA1711")] public interface IPlayStream { /// @@ -79,9 +82,9 @@ public interface ICamera : IExecuteAsync, IDisableMotionDetection /// /// Implements the fluent camera interface /// - public class CameraManager : EntityBase, ICamera, IExecuteAsync + public class CameraManager : EntityBase, ICamera { - private (string, dynamic?)? _serviceCall = null; + private (string, dynamic?)? _serviceCall; /// /// Constructor @@ -122,14 +125,14 @@ public IExecuteAsync PlayStream(string mediaPlayerId, string? format = null) } /// - public IExecuteAsync Record(string fileName, int? duration = null, int? lookback = null) + public IExecuteAsync Record(string fileName, int? seconds = null, int? lookback = null) { dynamic serviceData = new FluentExpandoObject(); serviceData.filename = fileName; - if (duration is not null) - serviceData.duration = duration; + if (seconds is not null) + serviceData.duration = seconds; if (lookback is not null) serviceData.lookback = lookback; @@ -165,7 +168,7 @@ public IExecuteAsync TurnOn() /// public Task ExecuteAsync() { - (string service, dynamic? data) = _serviceCall ?? throw new NullReferenceException($"{nameof(_serviceCall)} is Null!"); + (string service, dynamic? data) = _serviceCall ?? throw new NetDaemonNullReferenceException($"{nameof(_serviceCall)} is Null!"); return CallServiceOnAllEntities(service, data); } diff --git a/src/App/NetDaemon.App/Common/Fluent/FluentEvent.cs b/src/App/NetDaemon.App/Common/Fluent/FluentEvent.cs index 5b69d341c..2cb47cf1a 100644 --- a/src/App/NetDaemon.App/Common/Fluent/FluentEvent.cs +++ b/src/App/NetDaemon.App/Common/Fluent/FluentEvent.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; +using NetDaemon.Common.Exceptions; namespace NetDaemon.Common.Fluent { /// /// Handles events in fluent API /// + [SuppressMessage("Microsoft.Naming", "CA1716")] public interface IFluentEvent { /// @@ -52,7 +55,7 @@ public FluentEventManager(Func func, INetDaemonApp da public IExecute Call(Func? func) { if (func == null) - throw new NullReferenceException("Call function is null listening to event"); + throw new NetDaemonNullReferenceException("Call function is null listening to event"); _functionToCall = func; return this; @@ -62,7 +65,7 @@ public IExecute Call(Func? func) public void Execute() { if (_events == null && _funcSelector == null) - throw new NullReferenceException($"Both {nameof(_events)} or {nameof(_events)} cant be null"); + throw new NetDaemonNullReferenceException($"Both {nameof(_events)} or {nameof(_events)} cant be null"); if (_events != null) { @@ -89,6 +92,6 @@ public class FluentEventProperty /// /// Data of the event /// - public dynamic? Data { get; set; } = null; + public dynamic? Data { get; set; } } } \ No newline at end of file diff --git a/src/App/NetDaemon.App/Common/Fluent/FluentExpandoObject.cs b/src/App/NetDaemon.App/Common/Fluent/FluentExpandoObject.cs index 2a8d6b08b..593ae1caf 100644 --- a/src/App/NetDaemon.App/Common/Fluent/FluentExpandoObject.cs +++ b/src/App/NetDaemon.App/Common/Fluent/FluentExpandoObject.cs @@ -1,10 +1,13 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Dynamic; +using System.Globalization; using System.Linq; using System.Text.Json; using JoySoftware.HomeAssistant.Client; +using NetDaemon.Common.Exceptions; namespace NetDaemon.Common.Fluent { @@ -15,6 +18,7 @@ namespace NetDaemon.Common.Fluent /// Thanks to @lukevendediger for original code and inspiration /// https://gist.github.com/lukevenediger/6327599 /// + [SuppressMessage("Microsoft.Naming", "CA1710")] public class FluentExpandoObject : DynamicObject, IDictionary { private readonly Dictionary _dict = new(); @@ -128,6 +132,9 @@ public object this[string key] /// public override bool TrySetMember(SetMemberBinder binder, object? value) { + if (binder is null) + throw new NetDaemonNullReferenceException(nameof(binder)); + UpdateDictionary(binder.Name, value); // It is supposed to persist, this is the only reason _daemon is present _daemonApp?.SaveAppState(); @@ -138,6 +145,9 @@ public override bool TrySetMember(SetMemberBinder binder, object? value) /// public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object? value) { + if (indexes is null) + throw new NetDaemonNullReferenceException(nameof(indexes)); + if (!(indexes[0] is string)) return base.TrySetIndex(binder, indexes, value); if (indexes[0] is string key) UpdateDictionary(NormalizePropertyName(key), value); @@ -147,6 +157,9 @@ public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object /// public override bool TryGetMember(GetMemberBinder binder, out object? result) { + if (binder is null) + throw new NetDaemonNullReferenceException(nameof(binder)); + var key = NormalizePropertyName(binder.Name); if (_dict.ContainsKey(key)) @@ -170,6 +183,9 @@ public override bool TryGetMember(GetMemberBinder binder, out object? result) /// Existing properties are not overwritten. public dynamic Augment(FluentExpandoObject obj) { + if (obj is null) + throw new NetDaemonNullReferenceException(nameof(obj)); + obj._dict .Where(pair => !_dict.ContainsKey(NormalizePropertyName(pair.Key))) .ToList() @@ -183,6 +199,9 @@ public dynamic Augment(FluentExpandoObject obj) /// The object to copy from public dynamic CopyFrom(IDictionary obj) { + if (obj is null) + throw new NetDaemonNullReferenceException(nameof(obj)); + // Clear any items before copy Clear(); @@ -209,7 +228,6 @@ public dynamic CopyFrom(IDictionary obj) /// Combine two instances together to get a union. /// /// the object to combine - /// public dynamic Augment(ExpandoObject obj) { obj @@ -222,6 +240,9 @@ public dynamic Augment(ExpandoObject obj) /// public T ValueOrDefault(string propertyName, T defaultValue) { + if (propertyName is null) + throw new NetDaemonNullReferenceException(nameof(propertyName)); + propertyName = NormalizePropertyName(propertyName); return _dict.ContainsKey(propertyName) ? (T)_dict[propertyName] @@ -234,6 +255,9 @@ public T ValueOrDefault(string propertyName, T defaultValue) /// Respects the case sensitivity setting public bool HasProperty(string name) { + if (name is null) + throw new NetDaemonNullReferenceException(nameof(name)); + return _dict.ContainsKey(NormalizePropertyName(name)); } @@ -252,9 +276,10 @@ private void UpdateDictionary(string name, object? value) _dict[key] = value; } + [SuppressMessage("", "CA1308")] private string NormalizePropertyName(string propertyName) { - return _ignoreCase ? propertyName.ToLower() : propertyName; + return _ignoreCase ? propertyName.ToLower(CultureInfo.InvariantCulture) : propertyName; } } } \ No newline at end of file diff --git a/src/App/NetDaemon.App/Common/Fluent/FluentInputSelect.cs b/src/App/NetDaemon.App/Common/Fluent/FluentInputSelect.cs index 7c9f5b3e8..ed3ffecfe 100644 --- a/src/App/NetDaemon.App/Common/Fluent/FluentInputSelect.cs +++ b/src/App/NetDaemon.App/Common/Fluent/FluentInputSelect.cs @@ -30,9 +30,8 @@ public interface IFluentSetOption /// /// Set option value of the selected input selects /// - /// - /// - T SetOption(string option); + /// + T SetOption(string selectOption); } /// @@ -75,9 +74,9 @@ public Task ExecuteAsync() } /// - public IFluentExecuteAsync SetOption(string option) + public IFluentExecuteAsync SetOption(string selectOption) { - _option = option; + _option = selectOption; return this; } } diff --git a/src/App/NetDaemon.App/Common/Fluent/FluentMediaPlayer.cs b/src/App/NetDaemon.App/Common/Fluent/FluentMediaPlayer.cs index 1ad93283d..122ddc1cc 100644 --- a/src/App/NetDaemon.App/Common/Fluent/FluentMediaPlayer.cs +++ b/src/App/NetDaemon.App/Common/Fluent/FluentMediaPlayer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using NetDaemon.Common.Exceptions; namespace NetDaemon.Common.Fluent { @@ -80,7 +81,7 @@ public IMediaPlayerExecuteAsync Stop() /// public async Task ExecuteAsync() { - _ = _currentAction ?? throw new NullReferenceException("Missing fluent action type!"); + _ = _currentAction ?? throw new NetDaemonNullReferenceException("Missing fluent action type!"); var executeTask = _currentAction.ActionType switch { @@ -100,7 +101,7 @@ Task Speak() foreach (var entityId in EntityIds) { var message = _currentAction?.MessageToSpeak ?? - throw new NullReferenceException("Message to speak is null or empty!"); + throw new NetDaemonNullReferenceException("Message to speak is null or empty!"); Daemon.Speak(entityId, message); } diff --git a/src/App/NetDaemon.App/Common/IHttpHandler.cs b/src/App/NetDaemon.App/Common/IHttpHandler.cs index cf8c69ea7..1e848c5d1 100644 --- a/src/App/NetDaemon.App/Common/IHttpHandler.cs +++ b/src/App/NetDaemon.App/Common/IHttpHandler.cs @@ -1,4 +1,5 @@ -using System.Net.Http; +using System.Diagnostics.CodeAnalysis; +using System.Net.Http; using System.Text.Json; using System.Threading.Tasks; @@ -7,6 +8,7 @@ namespace NetDaemon.Common /// /// Implements all Http features of NetDaemon /// + [SuppressMessage("", "CA1054")] public interface IHttpHandler { /// diff --git a/src/App/NetDaemon.App/Common/INetDaemon.cs b/src/App/NetDaemon.App/Common/INetDaemon.cs index d4a1d1375..315d09eab 100644 --- a/src/App/NetDaemon.App/Common/INetDaemon.cs +++ b/src/App/NetDaemon.App/Common/INetDaemon.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using NetDaemon.Common.Fluent; @@ -147,6 +148,7 @@ public interface INetDaemon : INetDaemonCommon /// /// Base interface that all NetDaemon apps needs to implement /// + [SuppressMessage("", "CA1716")] public interface INetDaemonApp : INetDaemonAppBase { /// @@ -326,6 +328,7 @@ void ListenEvent(Func funcSelector, /// /// Shared features in both Reactive and async/await models /// + [SuppressMessage("", "CA1716")] public interface INetDaemonAppBase : INetDaemonInitialableApp, IDoLogging, IAsyncDisposable, IEquatable { @@ -594,6 +597,7 @@ public interface INetDaemonCommon /// exactly when it happens but it should be fine. The SetState function /// updates the state before sending. /// + [SuppressMessage("", "CA1721")] IEnumerable State { get; } /// diff --git a/src/App/NetDaemon.App/Common/Messages.cs b/src/App/NetDaemon.App/Common/Messages.cs index 3e032d607..b30b2a50e 100644 --- a/src/App/NetDaemon.App/Common/Messages.cs +++ b/src/App/NetDaemon.App/Common/Messages.cs @@ -1,6 +1,8 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; using System.Text.Json; using NetDaemon.Common.Fluent; @@ -14,52 +16,51 @@ public record Config /// /// Components being installed and used /// - public List? Components { get; init; } = null; + public IList? Components { get; } /// /// Local config directory /// - public string? ConfigDir { get; init; } = null; + public string? ConfigDir { get; } /// /// Elevation of Home Assistant instance /// - public int? Elevation { get; init; } = null; + public int? Elevation { get; } /// /// Latitude of Home Assistant instance /// - public float? Latitude { get; init; } = null; + public float? Latitude { get; } /// /// The name of the location /// - public string? LocationName { get; init; } = null; + public string? LocationName { get; } /// /// Longitude of Home Assistant instance /// - public float? Longitude { get; init; } = null; + public float? Longitude { get; } /// /// The timezone /// - public string? TimeZone { get; init; } = null; - + public string? TimeZone { get; } /// /// Unity system being configured /// - public HassUnitSystem? UnitSystem { get; init; } = null; + public HassUnitSystem? UnitSystem { get; init; } /// /// Current Home Assistant version /// - public string? Version { get; init; } = null; + public string? Version { get; init; } /// /// Whitelisted external directories /// - public List? WhitelistExternalDirs { get; init; } = null; + public ReadOnlyCollection? WhitelistExternalDirs { get; } } /// @@ -105,7 +106,6 @@ public record EntityState : IEntityProperties /// /// returns a pretty print of EntityState /// - /// public override string ToString() { string attributes = ""; @@ -115,7 +115,7 @@ public override string ToString() { if (key is not null) { - attributes += string.Format(" {0}:{1}", key, attr[key]); + attributes += string.Format(CultureInfo.InvariantCulture, " {0}:{1}", key, attr[key]); } } } @@ -139,22 +139,22 @@ public record HassUnitSystem /// /// Lenght unit /// - public string? Length { get; init; } = null; + public string? Length { get; init; } /// /// Mass unit /// - public string? Mass { get; init; } = null; + public string? Mass { get; init; } /// /// Temperature unit /// - public string? Temperature { get; init; } = null; + public string? Temperature { get; init; } /// /// Volume unit /// - public string? Volume { get; init; } = null; + public string? Volume { get; init; } } /// @@ -175,7 +175,6 @@ public record ServiceEvent /// /// Data being sent by the service /// - /// - public JsonElement? ServiceData { get; init; } = null; + public JsonElement? ServiceData { get; init; } } } \ No newline at end of file diff --git a/src/App/NetDaemon.App/Common/NetDaemonApp.cs b/src/App/NetDaemon.App/Common/NetDaemonApp.cs index 586e760e4..ab380b55a 100644 --- a/src/App/NetDaemon.App/Common/NetDaemonApp.cs +++ b/src/App/NetDaemon.App/Common/NetDaemonApp.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using NetDaemon.Common.Exceptions; using NetDaemon.Common.Fluent; [assembly: InternalsVisibleTo("NetDaemon.Daemon.Tests")] @@ -13,6 +15,9 @@ namespace NetDaemon.Common /// /// Base class för all NetDaemon apps /// + [SuppressMessage("", "CA1065"), + SuppressMessage("", "CA1721") + ] public abstract class NetDaemonApp : NetDaemonAppBase, INetDaemonApp, INetDaemonCommon { /// @@ -24,14 +29,14 @@ public abstract class NetDaemonApp : NetDaemonAppBase, INetDaemonApp, INetDaemon /// /// All actions being performed for lambda selected events /// - public List<(Func, Func)> EventFunctionCallbacks { get; } = new(); + public IList<(Func, Func)> EventFunctionCallbacks { get; } = new List<(Func, Func)>(); /// - public IScheduler Scheduler => _daemon?.Scheduler ?? - throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); + public IScheduler Scheduler => Daemon?.Scheduler ?? + throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); /// - public IEnumerable State => _daemon?.State ?? - throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); + public IEnumerable State => Daemon?.State ?? + throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); /// public ConcurrentDictionary action)> @@ -44,29 +49,29 @@ public abstract class NetDaemonApp : NetDaemonAppBase, INetDaemonApp, INetDaemon /// public Task CallService(string domain, string service, dynamic? data = null, bool waitForResponse = false) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return _daemon!.CallServiceAsync(domain, service, data, waitForResponse); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return Daemon!.CallServiceAsync(domain, service, data, waitForResponse); } /// public ICamera Camera(params string[] entityIds) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return _daemon!.Camera(this, entityIds); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return Daemon!.Camera(this, entityIds); } /// public ICamera Cameras(IEnumerable entityIds) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return _daemon!.Cameras(this, entityIds); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return Daemon!.Cameras(this, entityIds); } /// public ICamera Cameras(Func func) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return _daemon!.Cameras(this, func); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return Daemon!.Cameras(this, func); } /// @@ -83,6 +88,9 @@ public IDelayResult DelayUntilStateChange(string entityId, object? to = null, ob /// public IDelayResult DelayUntilStateChange(IEnumerable entityIds, object? to = null, object? from = null, bool allChanges = false) { + _ = entityIds ?? + throw new NetDaemonArgumentNullException(nameof(entityIds)); + // Use TaskCompletionSource to simulate a task that we can control var taskCompletionSource = new TaskCompletionSource(); var result = new DelayResult(taskCompletionSource, this); @@ -91,16 +99,14 @@ public IDelayResult DelayUntilStateChange(IEnumerable entityIds, object? { result.StateSubscriptions.Add(ListenState(entityId, (_, newState, oldState) => { - if (to != null) + if (to != null && (dynamic)to != newState?.State) { - if ((dynamic)to != newState?.State) - return Task.CompletedTask; + return Task.CompletedTask; } - if (from != null) + if (from != null && (dynamic)from != oldState?.State) { - if ((dynamic)from != oldState?.State) - return Task.CompletedTask; + return Task.CompletedTask; } // If we don´t accept all changes in the state change @@ -121,8 +127,12 @@ public IDelayResult DelayUntilStateChange(IEnumerable entityIds, object? } /// + [SuppressMessage("", "CA1031")] public IDelayResult DelayUntilStateChange(IEnumerable entityIds, Func stateFunc) { + _ = entityIds ?? + throw new NetDaemonArgumentNullException(nameof(entityIds)); + // Use TaskCompletionSource to simulate a task that we can control var taskCompletionSource = new TaskCompletionSource(); var result = new DelayResult(taskCompletionSource, this); @@ -168,22 +178,22 @@ public async override ValueTask DisposeAsync() /// public IEntity Entities(Func func) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return _daemon!.Entities(this, func); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return Daemon!.Entities(this, func); } /// - public IEntity Entities(IEnumerable entityIds) + public IEntity Entities(IEnumerable entityId) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return _daemon!.Entities(this, entityIds); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return Daemon!.Entities(this, entityId); } /// public IEntity Entity(params string[] entityId) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return _daemon!.Entity(this, entityId); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return Daemon!.Entity(this, entityId); } /// @@ -198,39 +208,39 @@ public IEntity Entity(params string[] entityId) /// public async Task GetDataAsync(string id) where T : class { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return await _daemon!.GetDataAsync(id).ConfigureAwait(false); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return await Daemon!.GetDataAsync(id).ConfigureAwait(false); } /// - public EntityState? GetState(string entityId) => _daemon?.GetState(entityId); + public EntityState? GetState(string entityId) => Daemon?.GetState(entityId); /// public IFluentInputSelect InputSelect(params string[] inputSelectParams) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return _daemon!.InputSelect(this, inputSelectParams); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return Daemon!.InputSelect(this, inputSelectParams); } /// public IFluentInputSelect InputSelects(IEnumerable inputSelectParams) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return _daemon!.InputSelects(this, inputSelectParams); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return Daemon!.InputSelects(this, inputSelectParams); } /// public IFluentInputSelect InputSelects(Func func) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return _daemon!.InputSelects(this, func); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return Daemon!.InputSelects(this, func); } /// public void ListenEvent(string ev, Func action) => EventCallbacks.Add((ev, action)); /// - public void ListenEvent(Func funcSelector, Func func) => EventFunctionCallbacks.Add((funcSelector, func)); + public void ListenEvent(Func funcSelector, Func action) => EventFunctionCallbacks.Add((funcSelector, action)); /// public string? ListenState(string pattern, @@ -246,50 +256,50 @@ public IFluentInputSelect InputSelects(Func func) /// public IMediaPlayer MediaPlayer(params string[] entityIds) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return _daemon!.MediaPlayer(this, entityIds); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return Daemon!.MediaPlayer(this, entityIds); } /// public IMediaPlayer MediaPlayers(IEnumerable entityIds) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return _daemon!.MediaPlayers(this, entityIds); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return Daemon!.MediaPlayers(this, entityIds); } /// public IMediaPlayer MediaPlayers(Func func) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return _daemon!.MediaPlayers(this, func); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return Daemon!.MediaPlayers(this, func); } /// public IScript RunScript(params string[] entityIds) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return _daemon!.RunScript(this, entityIds); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return Daemon!.RunScript(this, entityIds); } /// public Task SaveDataAsync(string id, T data) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return _daemon!.SaveDataAsync(id, data); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return Daemon!.SaveDataAsync(id, data); } /// public async Task SendEvent(string eventId, dynamic? data = null) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return await _daemon!.SendEvent(eventId, data).ConfigureAwait(false); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return await Daemon!.SendEvent(eventId, data).ConfigureAwait(false); } /// public async Task SetStateAsync(string entityId, dynamic state, params (string name, object val)[] attributes) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return await _daemon!.SetStateAsync(entityId, state, attributes).ConfigureAwait(false); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return await Daemon!.SetStateAsync(entityId, state, attributes).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/App/NetDaemon.App/Common/NetDaemonAppBase.cs b/src/App/NetDaemon.App/Common/NetDaemonAppBase.cs index 870d5eea5..ef8171942 100644 --- a/src/App/NetDaemon.App/Common/NetDaemonAppBase.cs +++ b/src/App/NetDaemon.App/Common/NetDaemonAppBase.cs @@ -7,6 +7,7 @@ using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using NetDaemon.Common.Exceptions; using NetDaemon.Common.Fluent; namespace NetDaemon.Common @@ -19,29 +20,21 @@ public abstract class NetDaemonAppBase : INetDaemonAppBase /// /// A set of properties found in static analysis of code for each app /// - public static Dictionary> CompileTimeProperties { get; set; } = new(); + public static Dictionary> CompileTimeProperties { get; } = new(); - /// - /// The NetDaemonHost instance - /// - protected INetDaemon? _daemon; private Task? _manageRuntimeInformationUpdatesTask; /// /// All actions being performed for service call events /// - public List<(string, string, Func)> DaemonCallBacksForServiceCalls { get; } = new(); + public IList<(string, string, Func)> DaemonCallBacksForServiceCalls { get; } + = new List<(string, string, Func)>(); // This is declared as static since it will contain state shared globally private static readonly ConcurrentDictionary _global = new(); private readonly Channel _updateRuntimeInfoChannel = Channel.CreateBounded(5); - - // /// - // /// The last error message logged och catched - // /// - // public string? RuntimeInfo.LastErrorMessage { get; set; } = null; private readonly CancellationTokenSource _cancelSource = new(); private Task? _lazyStoreStateTask; @@ -54,12 +47,13 @@ public abstract class NetDaemonAppBase : INetDaemonAppBase public ConcurrentDictionary Global => _global; /// + [SuppressMessage("", "CA1065")] public IHttpHandler Http { get { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return _daemon!.Http; + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return Daemon!.Http; } } @@ -74,17 +68,14 @@ public string Description { get { - var app_key = this.GetType().FullName; + var app_key = GetType().FullName; if (app_key is null) return ""; - if (CompileTimeProperties.ContainsKey(app_key)) + if (CompileTimeProperties.ContainsKey(app_key) && CompileTimeProperties[app_key].ContainsKey("description")) { - if (CompileTimeProperties[app_key].ContainsKey("description")) - { - return CompileTimeProperties[app_key]["description"]; - } + return CompileTimeProperties[app_key]["description"]; } return ""; } @@ -94,7 +85,8 @@ public string Description public ILogger? Logger { get; set; } /// - public dynamic Storage => InternalStorageObject ?? throw new NullReferenceException($"{nameof(InternalStorageObject)} cant be null"); + [SuppressMessage("", "CA1065")] + public dynamic Storage => InternalStorageObject ?? throw new NetDaemonNullReferenceException($"{nameof(InternalStorageObject)} cant be null"); internal Channel InternalLazyStoreStateQueue { get; } = Channel.CreateBounded(1); @@ -129,9 +121,9 @@ public virtual Task InitializeAsync() /// public async Task RestoreAppStateAsync() { - _ = _daemon as INetDaemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); - var obj = await _daemon!.GetDataAsync>(GetUniqueIdForStorage()).ConfigureAwait(false); + var obj = await Daemon!.GetDataAsync>(GetUniqueIdForStorage()).ConfigureAwait(false); if (obj != null) { @@ -139,8 +131,8 @@ public async Task RestoreAppStateAsync() expStore.CopyFrom(obj); } - bool isDisabled = this.Storage.__IsDisabled ?? false; - var appInfo = _daemon!.State.FirstOrDefault(s => s.EntityId == EntityId); + bool isDisabled = Storage.__IsDisabled ?? false; + var appInfo = Daemon!.State.FirstOrDefault(s => s.EntityId == EntityId); var appState = appInfo?.State as string; if (isDisabled) @@ -150,8 +142,8 @@ public async Task RestoreAppStateAsync() { dynamic serviceData = new FluentExpandoObject(); serviceData.entity_id = EntityId; - await _daemon.SetStateAsync(EntityId, "off").ConfigureAwait(false); - await _daemon.CallServiceAsync("switch", "turn_off", serviceData); + await Daemon.SetStateAsync(EntityId, "off").ConfigureAwait(false); + await Daemon.CallServiceAsync("switch", "turn_off", serviceData).ConfigureAwait(false); } return; } @@ -162,8 +154,8 @@ public async Task RestoreAppStateAsync() { dynamic serviceData = new FluentExpandoObject(); serviceData.entity_id = EntityId; - await _daemon.SetStateAsync(EntityId, "on").ConfigureAwait(false); - await _daemon.CallServiceAsync("switch", "turn_on", serviceData); + await Daemon.SetStateAsync(EntityId, "on").ConfigureAwait(false); + await Daemon.CallServiceAsync("switch", "turn_on", serviceData).ConfigureAwait(false); } return; } @@ -176,8 +168,14 @@ public async Task RestoreAppStateAsync() public AppRuntimeInfo RuntimeInfo { get; } = new AppRuntimeInfo { HasError = false }; /// - public IEnumerable EntityIds => _daemon?.State.Select(n => n.EntityId) ?? - throw new NullReferenceException("Deamon not expected to be null"); + [SuppressMessage("", "CA1065")] + public IEnumerable EntityIds => Daemon?.State.Select(n => n.EntityId) ?? + throw new NetDaemonNullReferenceException("Deamon not expected to be null"); + + /// + /// Instance to Daemon service + /// + protected INetDaemon? Daemon { get; set; } /// public void SaveAppState() @@ -191,14 +189,16 @@ public void SaveAppState() /// public void Speak(string entityId, string message) { - _ = _daemon as INetDaemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - _daemon!.Speak(entityId, message); + _ = Daemon as INetDaemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + Daemon!.Speak(entityId, message); } /// public virtual Task StartUpAsync(INetDaemon daemon) { - _daemon = daemon; + _ = daemon ?? throw new NetDaemonArgumentNullException(nameof(daemon)); + + Daemon = daemon; _manageRuntimeInformationUpdatesTask = ManageRuntimeInformationUpdates(); _lazyStoreStateTask = Task.Run(async () => await HandleLazyStorage().ConfigureAwait(false)); InternalStorageObject = new FluentExpandoObject(false, true, daemon: this); @@ -206,7 +206,7 @@ public virtual Task StartUpAsync(INetDaemon daemon) Logger.LogDebug("Startup: {app}", GetUniqueIdForStorage()); - var appInfo = _daemon!.State.FirstOrDefault(s => s.EntityId == EntityId); + var appInfo = Daemon!.State.FirstOrDefault(s => s.EntityId == EntityId); if (appInfo?.State is not string appState || (appState != "on" && appState != "off")) { IsEnabled = true; @@ -222,13 +222,15 @@ public virtual Task StartUpAsync(INetDaemon daemon) /// /// Returns unique Id for instance /// - public string GetUniqueIdForStorage() => $"{this.GetType().Name}_{Id}".ToLowerInvariant(); + [SuppressMessage("Microsoft.Globalization", "CA1308")] + public string GetUniqueIdForStorage() => $"{GetType().Name}_{Id}".ToLowerInvariant(); + [SuppressMessage("Microsoft.Design", "CA1031")] private async Task HandleLazyStorage() { _ = InternalStorageObject ?? - throw new NullReferenceException($"{nameof(InternalStorageObject)} cant be null!"); - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); + throw new NetDaemonNullReferenceException($"{nameof(InternalStorageObject)} cant be null!"); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); while (!_cancelSource.IsCancellationRequested) { @@ -237,7 +239,7 @@ private async Task HandleLazyStorage() // Dont care about the result, just that it is time to store state _ = await InternalLazyStoreStateQueue.Reader.ReadAsync(_cancelSource.Token).ConfigureAwait(false); - await _daemon!.SaveDataAsync>(GetUniqueIdForStorage(), (IDictionary)Storage) + await Daemon!.SaveDataAsync(GetUniqueIdForStorage(), (IDictionary)Storage) .ConfigureAwait(false); } catch (OperationCanceledException) @@ -267,17 +269,20 @@ public async virtual ValueTask DisposeAsync() this.IsEnabled = false; _lazyStoreStateTask = null; InternalStorageObject = null; - _daemon = null; + _cancelSource.Dispose(); + Daemon = null; } /// public INetDaemonAppBase? GetApp(string appInstanceId) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return _daemon!.GetApp(appInstanceId); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return Daemon!.GetApp(appInstanceId); } /// + [SuppressMessage("Microsoft.Design", "CA1062")] + [SuppressMessage("Microsoft.Design", "CA1308")] public void ListenServiceCall(string domain, string service, Func action) => DaemonCallBacksForServiceCalls.Add((domain.ToLowerInvariant(), service.ToLowerInvariant(), action)); @@ -290,7 +295,7 @@ public void ListenServiceCall(string domain, string service, Func public void Log(LogLevel level, string message, params object[] param) { - if (param is not null && param.Length > 0) + if (param.Length > 0) { var result = param.Prepend(Id).ToArray(); Logger.Log(level, $" {{Id}}: {message}", result); @@ -307,7 +312,7 @@ public void Log(LogLevel level, string message, params object[] param) /// public void Log(LogLevel level, Exception exception, string message, params object[] param) { - if (param is not null && param.Length > 0) + if (param.Length > 0) { var result = param.Prepend(Id).ToArray(); Logger.Log(level, exception, $" {{Id}}: {message}", result); @@ -457,14 +462,14 @@ private async Task ManageRuntimeInformationUpdates() private async Task HandleUpdateRuntimeInformation() { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); if (RuntimeInfo.LastErrorMessage is not null) { RuntimeInfo.HasError = true; } - await _daemon!.SetStateAsync(EntityId, IsEnabled ? "on" : "off", ("runtime_info", RuntimeInfo)).ConfigureAwait(false); + await Daemon!.SetStateAsync(EntityId, IsEnabled ? "on" : "off", ("runtime_info", RuntimeInfo)).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/App/NetDaemon.App/Common/Reactive/AppDaemonRxApp.cs b/src/App/NetDaemon.App/Common/Reactive/AppDaemonRxApp.cs index 596f0c532..858f73e57 100644 --- a/src/App/NetDaemon.App/Common/Reactive/AppDaemonRxApp.cs +++ b/src/App/NetDaemon.App/Common/Reactive/AppDaemonRxApp.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reactive.Concurrency; @@ -8,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using NetDaemon.Common.Exceptions; using NetDaemon.Common.Fluent; // For mocking @@ -22,13 +24,19 @@ public abstract class NetDaemonRxApp : NetDaemonAppBase, INetDaemonReactive { private readonly CancellationTokenSource _cancelTimers = new(); private EventObservable? _eventObservables; - private ReactiveEvent? _reactiveEvent = null; - private ReactiveState? _reactiveState = null; private StateChangeObservable? _stateObservables; + /// + /// Default constructor + /// + protected NetDaemonRxApp() + { + StateAllChanges = new ReactiveState(this); + EventChanges = new ReactiveEvent(this); + } + /// - public IRxEvent EventChanges => - _reactiveEvent ?? throw new ApplicationException("Application not initialized correctly (EventChanges)"); + public IRxEvent EventChanges { get; } /// /// Returns the observables events implementation of AppDaemonRxApps @@ -36,12 +44,10 @@ public abstract class NetDaemonRxApp : NetDaemonAppBase, INetDaemonReactive public ObservableBase EventChangesObservable => _eventObservables!; /// - public IObservable<(EntityState Old, EntityState New)> StateAllChanges => - _reactiveState ?? throw new ApplicationException("Application not initialized correctly (StateAllChanges>"); + public IObservable<(EntityState Old, EntityState New)> StateAllChanges { get; } /// - public IObservable<(EntityState Old, EntityState New)> StateChanges => - _reactiveState?.Where(e => e.New?.State != e.Old?.State) ?? throw new ApplicationException("Application not initialized correctly (StateAllChanges>"); + public IObservable<(EntityState Old, EntityState New)> StateChanges => StateAllChanges.Where(e => e.New?.State != e.Old?.State); /// /// Returns the observables states implementation of AppDaemonRxApps @@ -50,13 +56,13 @@ public abstract class NetDaemonRxApp : NetDaemonAppBase, INetDaemonReactive /// public IEnumerable States => - _daemon?.State ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); + Daemon?.State ?? new List(); /// public void CallService(string domain, string service, dynamic? data) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - _daemon.CallService(domain, service, data); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + Daemon.CallService(domain, service, data); } /// @@ -73,29 +79,25 @@ public async override ValueTask DisposeAsync() if (_stateObservables is not null) _stateObservables!.Clear(); - // Make sure we release all references so the apps can be - // unloaded correctly - _reactiveEvent = null; - _reactiveState = null; - await base.DisposeAsync().ConfigureAwait(false); + _cancelTimers.Dispose(); LogDebug("RxApp {app} is Disposed", Id!); } /// public RxEntity Entities(Func func) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); try { - IEnumerable x = _daemon.State.Where(func); + IEnumerable x = Daemon.State.Where(func); return new RxEntity(this, x.Select(n => n.EntityId).ToArray()); } catch (Exception e) { - _daemon.Logger.LogDebug(e, "Failed to select entities func in app {appId}", Id); + Daemon.Logger.LogDebug(e, "Failed to select entities func in app {appId}", Id); throw; } } @@ -106,22 +108,22 @@ public RxEntity Entities(Func func) /// public RxEntity Entities(IEnumerable entityIds) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); return new RxEntity(this, entityIds); } /// public RxEntity Entity(string entityId) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); return new RxEntity(this, new string[] { entityId }); } /// public T? GetData(string id) where T : class { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - return _daemon.GetDataAsync(id).Result; + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + return Daemon.GetDataAsync(id).Result; } /// @@ -195,6 +197,7 @@ public IDisposable RunEveryMinute(short second, Action action) } /// + [SuppressMessage("", "CA1031")] // In this case we want just to log the message public IDisposable RunIn(TimeSpan timespan, Action action) { var result = new DisposableTimerResult(_cancelTimers.Token); @@ -204,7 +207,7 @@ public IDisposable RunIn(TimeSpan timespan, Action action) { try { - if (this.IsEnabled) + if (IsEnabled) action(); } catch (OperationCanceledException) @@ -224,53 +227,52 @@ public IDisposable RunIn(TimeSpan timespan, Action action) /// public void RunScript(params string[] script) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); foreach (var scriptName in script) { var name = scriptName; - if (scriptName.Contains('.')) - name = scriptName[(scriptName.IndexOf('.') + 1)..]; + if (scriptName.Contains('.', StringComparison.InvariantCultureIgnoreCase)) + name = scriptName[(scriptName.IndexOf('.', StringComparison.InvariantCultureIgnoreCase) + 1)..]; - _daemon.CallService("script", name); + Daemon.CallService("script", name); } } /// public void SaveData(string id, T data) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - _daemon.SaveDataAsync(id, data).Wait(); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + Daemon.SaveDataAsync(id, data).Wait(); } /// public void SetState(string entityId, dynamic state, dynamic? attributes = null) { - _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - _daemon.SetState(entityId, state, attributes); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + Daemon.SetState(entityId, state, attributes); } /// public async override Task StartUpAsync(INetDaemon daemon) { await base.StartUpAsync(daemon).ConfigureAwait(false); - _ = _daemon as INetDaemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!"); - _ = Logger ?? throw new NullReferenceException("Logger can not be null!"); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + _ = Logger ?? throw new NetDaemonNullReferenceException("Logger can not be null!"); _eventObservables = new EventObservable(Logger, this); _stateObservables = new StateChangeObservable(Logger, this); - _reactiveState = new ReactiveState(this); - _reactiveEvent = new ReactiveEvent(this); } /// - public EntityState? State(string entityId) => _daemon?.GetState(entityId); + public EntityState? State(string entityId) => Daemon?.GetState(entityId); /// /// Creates an observable intervall /// /// Time span for intervall /// The action to call + [SuppressMessage("Microsoft.Design", "CA1031")] internal virtual IDisposable CreateObservableIntervall(TimeSpan timespan, Action action) { var result = new DisposableTimerResult(_cancelTimers.Token); @@ -283,7 +285,7 @@ internal virtual IDisposable CreateObservableIntervall(TimeSpan timespan, Action { try { - if (this.IsEnabled) + if (IsEnabled) { action(); RuntimeInfo.NextScheduledEvent = DateTime.Now + timespan; @@ -311,7 +313,8 @@ internal virtual IDisposable CreateObservableIntervall(TimeSpan timespan, Action /// When to start the timer /// The intervall /// Action to call each intervall - /// + [SuppressMessage("Microsoft.Design", "CA1031")] + internal virtual IDisposable CreateObservableTimer(DateTime timeOfDayToTrigger, TimeSpan interval, Action action) { var result = new DisposableTimerResult(_cancelTimers.Token); @@ -328,7 +331,7 @@ internal virtual IDisposable CreateObservableTimer(DateTime timeOfDayToTrigger, { try { - if (this.IsEnabled) + if (IsEnabled) { action(); RuntimeInfo.NextScheduledEvent = DateTime.Now + interval; diff --git a/src/App/NetDaemon.App/Common/Reactive/DisposableTimerResult.cs b/src/App/NetDaemon.App/Common/Reactive/DisposableTimerResult.cs index 3370d6302..4b9491374 100644 --- a/src/App/NetDaemon.App/Common/Reactive/DisposableTimerResult.cs +++ b/src/App/NetDaemon.App/Common/Reactive/DisposableTimerResult.cs @@ -30,6 +30,8 @@ public DisposableTimerResult(CancellationToken token) public void Dispose() { _internalToken.Cancel(); + _combinedToken.Dispose(); + _internalToken.Dispose(); } } } \ No newline at end of file diff --git a/src/App/NetDaemon.App/Common/Reactive/ObservableBase.cs b/src/App/NetDaemon.App/Common/Reactive/ObservableBase.cs index 84a5d83f9..5a9c59d11 100644 --- a/src/App/NetDaemon.App/Common/Reactive/ObservableBase.cs +++ b/src/App/NetDaemon.App/Common/Reactive/ObservableBase.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; namespace NetDaemon.Common.Reactive @@ -36,6 +37,8 @@ public ObservableBase(ILogger logger, INetDaemonAppBase app) /// /// Clear all observers /// + [SuppressMessage("Microsoft.Design", "CA1031")] + public void Clear() { foreach (var eventObservable in _observersTuples) @@ -75,8 +78,8 @@ private class UnsubscriberObservable : IDisposable public UnsubscriberObservable( ConcurrentDictionary, IObserver> observers, IObserver observer) { - this._observers = observers; - this._observer = observer; + _observer = observer; + _observers = observers; } public void Dispose() @@ -86,7 +89,6 @@ public void Dispose() _observers.TryRemove(_observer, out _); _observer.OnCompleted(); } - // System.Console.WriteLine($"Subscribers:{_observers.Count}"); } } } diff --git a/src/App/NetDaemon.App/Common/Reactive/ObservableExtensionMethods.cs b/src/App/NetDaemon.App/Common/Reactive/ObservableExtensionMethods.cs index c09651dd9..d26a96e65 100644 --- a/src/App/NetDaemon.App/Common/Reactive/ObservableExtensionMethods.cs +++ b/src/App/NetDaemon.App/Common/Reactive/ObservableExtensionMethods.cs @@ -13,7 +13,6 @@ public static class ObservableExtensionMethods /// /// /// - /// public static IObservable<(EntityState Old, EntityState New)> NDSameStateFor(this IObservable<(EntityState Old, EntityState New)> observable, TimeSpan span) { return observable.Throttle(span); @@ -26,16 +25,17 @@ public static class ObservableExtensionMethods /// Timeout waiting for state public static IObservable<(EntityState Old, EntityState New)> NDWaitForState(this IObservable<(EntityState Old, EntityState New)> observable, TimeSpan timeout) { - return observable.Timeout(timeout, Observable.Return((new NetDaemon.Common.EntityState() { State = "TimeOut" }, new NetDaemon.Common.EntityState() { State = "TimeOut" }))).Take(1); + return observable + .Timeout(timeout, + Observable.Return((new EntityState() { State = "TimeOut" }, new EntityState() { State = "TimeOut" }))).Take(1); } /// /// Wait for state the default time /// /// - public static IObservable<(EntityState Old, EntityState New)> NDWaitForState(this IObservable<(EntityState Old, EntityState New)> observable) - { - return observable.Timeout(TimeSpan.FromSeconds(5), Observable.Return((new NetDaemon.Common.EntityState() { State = "TimeOut" }, new NetDaemon.Common.EntityState() { State = "TimeOut" }))).Take(1); - } + public static IObservable<(EntityState Old, EntityState New)> NDWaitForState(this IObservable<(EntityState Old, EntityState New)> observable) => observable + .Timeout(TimeSpan.FromSeconds(5), + Observable.Return((new EntityState() { State = "TimeOut" }, new EntityState() { State = "TimeOut" }))).Take(1); } } \ No newline at end of file diff --git a/src/App/NetDaemon.App/Common/Reactive/RxEntity.cs b/src/App/NetDaemon.App/Common/Reactive/RxEntity.cs index c3b9030d1..cdc827b63 100644 --- a/src/App/NetDaemon.App/Common/Reactive/RxEntity.cs +++ b/src/App/NetDaemon.App/Common/Reactive/RxEntity.cs @@ -3,6 +3,7 @@ using System.Dynamic; using System.Linq; using System.Reactive.Linq; +using NetDaemon.Common.Exceptions; using NetDaemon.Common.Fluent; namespace NetDaemon.Common.Reactive @@ -68,11 +69,11 @@ public class RxEntity : ICanTurnOnAndOff, ISetState, IObserve /// /// The protected daemon app instance /// - protected readonly INetDaemonReactive DaemonRxApp; + protected INetDaemonReactive DaemonRxApp { get; } /// /// Entity ids being handled by the RxEntity /// - protected readonly IEnumerable EntityIds; + protected IEnumerable EntityIds { get; } /// /// Constructor @@ -125,7 +126,7 @@ internal static string GetDomainFromEntity(string entity) { var entityParts = entity.Split('.'); if (entityParts.Length != 2) - throw new ApplicationException($"entity_id is mal formatted {entity}"); + throw new NetDaemonException($"entity_id is mal formatted {entity}"); return entityParts[0]; } @@ -137,7 +138,7 @@ internal static string GetDomainFromEntity(string entity) /// Data to provide public void CallService(string service, dynamic? data = null) { - if (EntityIds is null || (EntityIds is not null && !EntityIds.Any())) + if (EntityIds?.Any() != true) return; foreach (var entityId in EntityIds!) @@ -169,7 +170,7 @@ public void CallService(string service, dynamic? data = null) private void CallServiceOnEntity(string service, dynamic? attributes = null) { - if (EntityIds is null || (EntityIds is not null && !EntityIds.Any())) + if (EntityIds?.Any() != true) return; dynamic? data = null; diff --git a/src/App/NetDaemon.App/Common/Reactive/RxEvent.cs b/src/App/NetDaemon.App/Common/Reactive/RxEvent.cs index 57a6d39ad..7b2de97f7 100644 --- a/src/App/NetDaemon.App/Common/Reactive/RxEvent.cs +++ b/src/App/NetDaemon.App/Common/Reactive/RxEvent.cs @@ -3,7 +3,7 @@ /// /// Represent an event from eventstream /// - public struct RxEvent + public class RxEvent { private readonly string? _domain; diff --git a/src/App/NetDaemon.App/NetDaemon.App.csproj b/src/App/NetDaemon.App/NetDaemon.App.csproj index 46b545ffc..927ae709e 100644 --- a/src/App/NetDaemon.App/NetDaemon.App.csproj +++ b/src/App/NetDaemon.App/NetDaemon.App.csproj @@ -24,9 +24,15 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + ..\..\..\.linting\roslynator.ruleset + true + AllEnabledByDefault diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs b/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs index 15bc89595..1c5139237 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs @@ -5,6 +5,7 @@ using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; using NetDaemon.Common; +using NetDaemon.Common.Exceptions; using NetDaemon.Daemon.Config; [assembly: InternalsVisibleTo("NetDaemon.Daemon.Tests")] @@ -30,7 +31,7 @@ public CodeManager(IEnumerable daemonAppTypes, ILogger logger, IYamlConfig _yamlConfig = yamlConfig; } - public int Count => _loadedDaemonApps?.Count() ?? throw new NullReferenceException("_loadedDaemonApps cannot be null"); + public int Count => _loadedDaemonApps?.Count() ?? throw new NetDaemonNullReferenceException("_loadedDaemonApps cannot be null"); // Internal for testing public IEnumerable DaemonAppTypes => _loadedDaemonApps!; @@ -67,7 +68,7 @@ public IEnumerable InstanceDaemonApps() { _logger.LogTrace(e, "Error instance the app from the file {file}", file); _logger.LogError("Error instance the app from the file {file}, use trace flag for details", file); - throw new ApplicationException($"Error instance the app from the file {file}", e); + throw new NetDaemonException($"Error instance the app from the file {file}", e); } } return result; diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs index 8d7f003af..8b0c3604b 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using NetDaemon.Common; +using NetDaemon.Common.Exceptions; using YamlDotNet.RepresentationModel; namespace NetDaemon.Daemon.Config @@ -60,7 +61,7 @@ public IEnumerable Instances } catch (System.Exception e) { - throw new ApplicationException($"Error instancing application {appId}", e); + throw new NetDaemonException($"Error instancing application {appId}", e); } } @@ -98,7 +99,7 @@ public IEnumerable Instances } catch (Exception e) { - throw new ApplicationException($"Failed to set value {scalarPropertyName} for app {appId}", e); + throw new NetDaemonException($"Failed to set value {scalarPropertyName} for app {appId}", e); } } @@ -118,10 +119,10 @@ public IEnumerable Instances if (instanceType.IsGenericType && instanceType?.GetGenericTypeDefinition() == typeof(IEnumerable<>)) { Type listType = instanceType?.GetGenericArguments()[0] ?? - throw new NullReferenceException($"The property {instanceType?.Name} of Class {parent?.GetType().Name} is not compatible with configuration"); + throw new NetDaemonNullReferenceException($"The property {instanceType?.Name} of Class {parent?.GetType().Name} is not compatible with configuration"); IList list = listType.CreateListOfPropertyType() ?? - throw new NullReferenceException("Failed to create listtype, plese check {prop.Name} of Class {app.GetType().Name}"); + throw new NetDaemonNullReferenceException("Failed to create listtype, plese check {prop.Name} of Class {app.GetType().Name}"); foreach (YamlNode item in ((YamlSequenceNode)node).Children) { @@ -179,7 +180,7 @@ private void ReplaceSecretIfExists(YamlScalarNode scalarNode) var secretReplacement = _yamlConfig.GetSecretFromPath(scalarNode.Value!, Path.GetDirectoryName(_yamlFilePath)!); - scalarNode.Value = secretReplacement ?? throw new ApplicationException($"{scalarNode.Value!} not found in secrets.yaml"); + scalarNode.Value = secretReplacement ?? throw new NetDaemonException($"{scalarNode.Value!} not found in secrets.yaml"); } private static string? GetTypeNameFromClassConfig(YamlMappingNode appNode) diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlConfig.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlConfig.cs index 97855d261..d7965edaf 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlConfig.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlConfig.cs @@ -4,6 +4,7 @@ using System.Runtime.CompilerServices; using Microsoft.Extensions.Options; using NetDaemon.Common.Configuration; +using NetDaemon.Common.Exceptions; using YamlDotNet.RepresentationModel; [assembly: InternalsVisibleTo("NetDaemon.Daemon.Tests")] @@ -43,7 +44,7 @@ public IEnumerable GetAllConfigFilePaths() } if (configPath != _configFolder) { - var parentPath = Directory.GetParent(configPath)?.FullName ?? throw new ApplicationException("Parent folder of config path does not exist"); + var parentPath = Directory.GetParent(configPath)?.FullName ?? throw new NetDaemonException("Parent folder of config path does not exist"); return GetSecretFromPath(secret, parentPath); } diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs b/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs index 5c80b5f9c..165e665f8 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs @@ -4,6 +4,7 @@ using System.Text.Json; using System.Threading.Tasks; using NetDaemon.Common; +using NetDaemon.Common.Exceptions; namespace NetDaemon.Daemon { @@ -18,27 +19,27 @@ public HttpHandler(IHttpClientFactory? httpClientFactory) public HttpClient CreateHttpClient(string? name = null) { - _ = _httpClientFactory ?? throw new NullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!"); + _ = _httpClientFactory ?? throw new NetDaemonNullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!"); return _httpClientFactory.CreateClient(name); } public async Task GetJson(string url, JsonSerializerOptions? options = null, params (string, object)[] headers) { - _ = _httpClientFactory ?? throw new NullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!"); + _ = _httpClientFactory ?? throw new NetDaemonNullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!"); var httpClient = _httpClientFactory.CreateClient(); AddHeaders(httpClient, headers); var streamTask = httpClient.GetStreamAsync(url) - ?? throw new ApplicationException($"Unexpected, nothing returned from {url}"); + ?? throw new NetDaemonException($"Unexpected, nothing returned from {url}"); return await JsonSerializer.DeserializeAsync(await streamTask.ConfigureAwait(false), options).ConfigureAwait(false); } public async Task PostJson(string url, object request, JsonSerializerOptions? options = null, params (string, object)[] headers) { - _ = _httpClientFactory ?? throw new NullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!"); + _ = _httpClientFactory ?? throw new NetDaemonNullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!"); var httpClient = _httpClientFactory.CreateClient(); @@ -56,7 +57,7 @@ public HttpClient CreateHttpClient(string? name = null) public async Task PostJson(string url, object request, JsonSerializerOptions? options = null, params (string, object)[] headers) { - _ = _httpClientFactory ?? throw new NullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!"); + _ = _httpClientFactory ?? throw new NetDaemonNullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!"); var httpClient = _httpClientFactory.CreateClient(); @@ -81,7 +82,7 @@ private static void AddHeaders(HttpClient httpClient, (string, object)[] headers else if (header is IEnumerable headerStrings) httpClient.DefaultRequestHeaders.Add(name, headerStrings); else - throw new ApplicationException($"Unsupported header, expected string or IEnumerable for {name}"); + throw new NetDaemonException($"Unsupported header, expected string or IEnumerable for {name}"); } } } diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs b/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs index 5221f458b..2c7d1f162 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs @@ -11,6 +11,7 @@ using JoySoftware.HomeAssistant.Client; using Microsoft.Extensions.Logging; using NetDaemon.Common; +using NetDaemon.Common.Exceptions; using NetDaemon.Common.Fluent; using NetDaemon.Common.Reactive; using NetDaemon.Daemon.Storage; @@ -114,7 +115,7 @@ public IHttpHandler Http { get { - _ = _httpHandler ?? throw new NullReferenceException("HttpHandler can not be null!"); + _ = _httpHandler ?? throw new NetDaemonNullReferenceException("HttpHandler can not be null!"); return _httpHandler; } } @@ -153,7 +154,7 @@ public void CallService(string domain, string service, dynamic? data = null) this._cancelToken.ThrowIfCancellationRequested(); if (!_serviceCallMessageChannel.Writer.TryWrite((domain, service, data))) - throw new ApplicationException("Servicecall queue full!"); + throw new NetDaemonException("Servicecall queue full!"); } public async Task CallServiceAsync(string domain, string service, dynamic? data = null, bool waitForResponse = false) @@ -259,7 +260,7 @@ public IEntity Entity(INetDaemonApp app, params string[] entityIds) this._cancelToken.ThrowIfCancellationRequested(); _ = _repository as IDataRepository ?? - throw new NullReferenceException($"{nameof(_repository)} can not be null!"); + throw new NetDaemonNullReferenceException($"{nameof(_repository)} can not be null!"); if (DataCache.ContainsKey(id)) { @@ -286,7 +287,7 @@ public IEntity Entity(INetDaemonApp app, params string[] entityIds) public async Task Initialize(IInstanceDaemonApp appInstanceManager) { if (!Connected) - throw new ApplicationException("NetDaemon is not connected, no use in initializing"); + throw new NetDaemonException("NetDaemon is not connected, no use in initializing"); _appInstanceManager = appInstanceManager; @@ -389,7 +390,7 @@ public async Task Run(string host, short port, bool ssl, string token, Cancellat if (_hassClient == null) { - throw new NullReferenceException("HassClient cant be null when running daemon, check constructor!"); + throw new NetDaemonNullReferenceException("HassClient cant be null when running daemon, check constructor!"); } try @@ -494,7 +495,7 @@ public Task SaveDataAsync(string id, T data) this._cancelToken.ThrowIfCancellationRequested(); _ = _repository as IDataRepository ?? - throw new NullReferenceException($"{nameof(_repository)} can not be null!"); + throw new NetDaemonNullReferenceException($"{nameof(_repository)} can not be null!"); if (data == null) throw new ArgumentNullException(nameof(data)); @@ -538,7 +539,7 @@ public void SetState(string entityId, dynamic state, dynamic? attributes = null) this._cancelToken.ThrowIfCancellationRequested(); if (!_setStateMessageChannel.Writer.TryWrite((entityId, state, attributes))) - throw new ApplicationException("Servicecall queue full!"); + throw new NetDaemonException("Servicecall queue full!"); } public async Task SetStateAsync(string entityId, dynamic state, @@ -755,13 +756,13 @@ internal static IList SortByDependency(IEnumerable n.Id == dependency); if (dependentApp == null) - throw new ApplicationException($"There is no app named {dependency}, please check dependencies or make sure you have not disabled the dependent app!"); + throw new NetDaemonException($"There is no app named {dependency}, please check dependencies or make sure you have not disabled the dependent app!"); edges.Add(new Tuple(instance, dependentApp)); } } var sortedInstances = TopologicalSort(unsortedList.ToHashSet(), edges) ?? - throw new ApplicationException("Application dependencies is wrong, please check dependencies for circular dependencies!"); + throw new NetDaemonException("Application dependencies is wrong, please check dependencies for circular dependencies!"); return sortedInstances; } @@ -780,7 +781,7 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok if (stateData is null) { - throw new NullReferenceException("StateData is null!"); + throw new NetDaemonNullReferenceException("StateData is null!"); } if (stateData.NewState is null || stateData.OldState is null) @@ -879,7 +880,7 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok if (serviceCallData == null) { - throw new NullReferenceException("ServiceData is null! not expected"); + throw new NetDaemonNullReferenceException("ServiceData is null! not expected"); } var tasks = new List(); foreach (var app in InternalRunningAppInstances) @@ -1190,7 +1191,7 @@ private async Task HandleTextToSpeechMessages(CancellationToken cancellationToke /// private async Task LoadAllApps() { - _ = _appInstanceManager ?? throw new NullReferenceException(nameof(_appInstanceManager)); + _ = _appInstanceManager ?? throw new NetDaemonNullReferenceException(nameof(_appInstanceManager)); // First unload any apps running await UnloadAllApps().ConfigureAwait(false); @@ -1199,7 +1200,7 @@ private async Task LoadAllApps() var instancedApps = _appInstanceManager.InstanceDaemonApps(); if (!InternalRunningAppInstances.IsEmpty) - throw new ApplicationException("Did not expect running instances!"); + throw new NetDaemonException("Did not expect running instances!"); foreach (INetDaemonAppBase appInstance in instancedApps!) { diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs index a4bc63cfc..d2ea08cd4 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using NetDaemon.Common; +using NetDaemon.Common.Exceptions; namespace NetDaemon.Daemon { @@ -267,7 +268,7 @@ public async Task Stop() if (_scheduledTasks.Values.Any(n => !n.IsCompleted)) { // Todo: Some kind of logging have to be done here to tell user which task caused timeout - throw new ApplicationException("Failed to cancel all tasks"); + throw new NetDaemonException("Failed to cancel all tasks"); } } diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs index f3c8fe37c..c784bc044 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs @@ -5,6 +5,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; +using NetDaemon.Common.Exceptions; namespace NetDaemon.Daemon.Storage { @@ -70,7 +71,7 @@ public class ExpandoDictionaryConverter : JsonConverter>(ref reader) - ?? throw new ApplicationException("Null result deserializing dictionary"); + ?? throw new NetDaemonException("Null result deserializing dictionary"); var returnObject = new Dictionary(); var returnDict = (IDictionary)returnObject; foreach (var x in dict.Keys) diff --git a/src/DaemonRunner/DaemonRunner/NetDaemonExtensions.cs b/src/DaemonRunner/DaemonRunner/NetDaemonExtensions.cs index 1e175bff4..2804d4300 100644 --- a/src/DaemonRunner/DaemonRunner/NetDaemonExtensions.cs +++ b/src/DaemonRunner/DaemonRunner/NetDaemonExtensions.cs @@ -11,6 +11,7 @@ using NetDaemon.Service.App; using Serilog; using NetDaemon.Infrastructure.Config; +using NetDaemon.Common.Exceptions; namespace NetDaemon { @@ -95,7 +96,7 @@ private static void ReadHassioConfig() Environment.SetEnvironmentVariable("HASSCLIENT_MSGLOGLEVEL", "Default"); _ = hassAddOnSettings?.AppSource ?? - throw new NullReferenceException("AppSource cannot be null"); + throw new NetDaemonNullReferenceException("AppSource cannot be null"); if (hassAddOnSettings.AppSource.StartsWith("/") || hassAddOnSettings.AppSource[1] == ':') { diff --git a/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs b/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs index 063159e77..7075530be 100644 --- a/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs +++ b/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs @@ -13,6 +13,7 @@ using Microsoft.Extensions.Options; using NetDaemon.Common; using NetDaemon.Common.Configuration; +using NetDaemon.Common.Exceptions; using NetDaemon.Daemon; namespace NetDaemon.Service.Api @@ -212,7 +213,7 @@ private static Task SendStringAsync(WebSocket socket, string data, CancellationT { // System.Text.Json.JsonSerializer.DeserializeAsync(socket.) var buffer = new ArraySegment(new byte[8192]); - _ = buffer.Array ?? throw new NullReferenceException("Failed to allocate memory buffer"); + _ = buffer.Array ?? throw new NetDaemonNullReferenceException("Failed to allocate memory buffer"); using var ms = new MemoryStream(); WebSocketReceiveResult result; diff --git a/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs b/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs index 8bfa8b09b..ba0ef1c24 100644 --- a/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs +++ b/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs @@ -7,6 +7,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using NetDaemon.Common; +using NetDaemon.Common.Exceptions; using NetDaemon.Common.Reactive; using NetDaemon.Daemon.Config; @@ -61,7 +62,7 @@ public static class CodeGenerator var camelCaseDomain = domain.ToCamelCase(); var method = $"public static {camelCaseDomain}Entities {camelCaseDomain}Ex(this NetDaemonApp app) => new {camelCaseDomain}Entities(app);"; var methodDeclaration = CSharpSyntaxTree.ParseText(method).GetRoot().ChildNodes().OfType().FirstOrDefault() - ?? throw new NullReferenceException("Method parsing failed"); + ?? throw new NetDaemonNullReferenceException("Method parsing failed"); extensionClass = extensionClass.AddMembers(methodDeclaration); } @@ -83,7 +84,7 @@ public static class CodeGenerator }} }}"; var entityClass = CSharpSyntaxTree.ParseText(classDeclaration).GetRoot().ChildNodes().OfType().FirstOrDefault() - ?? throw new NullReferenceException($"Parse class {nameof(NetDaemonApp)} failed"); + ?? throw new NetDaemonNullReferenceException($"Parse class {nameof(NetDaemonApp)} failed"); foreach (var entity in entities.Where(n => n.StartsWith(domain))) { var (fluent, fluentInterface) = _FluentApiMapper[domain]; @@ -97,7 +98,7 @@ public static class CodeGenerator var propertyCode = $@"public {fluentInterface} {name.ToCamelCase()} => _app.{fluent}(""{entity}"");"; var propDeclaration = CSharpSyntaxTree.ParseText(propertyCode).GetRoot().ChildNodes().OfType().FirstOrDefault() - ?? throw new NullReferenceException("Property parsing failed!"); + ?? throw new NetDaemonNullReferenceException("Property parsing failed!"); entityClass = entityClass.AddMembers(propDeclaration); } namespaceDeclaration = namespaceDeclaration.AddMembers(entityClass); @@ -147,7 +148,7 @@ public static class CodeGenerator $@"public {camelCaseDomain}Entity {camelCaseDomain} => new {domain.ToCamelCase()}Entity(this, new string[] {{""""}});"; var propertyDeclaration = CSharpSyntaxTree.ParseText(property).GetRoot().ChildNodes().OfType().FirstOrDefault() - ?? throw new NullReferenceException($"Parse of property {camelCaseDomain} Entities/Entity failed"); + ?? throw new NetDaemonNullReferenceException($"Parse of property {camelCaseDomain} Entities/Entity failed"); extensionClass = extensionClass.AddMembers(propertyDeclaration); } namespaceDeclaration = namespaceDeclaration.AddMembers(extensionClass); @@ -173,7 +174,7 @@ public static class CodeGenerator }} }}"; var entityClass = CSharpSyntaxTree.ParseText(classDeclaration).GetRoot().ChildNodes().OfType().FirstOrDefault() - ?? throw new NullReferenceException("Failed to parse class declaration"); + ?? throw new NetDaemonNullReferenceException("Failed to parse class declaration"); // They allready have default implementation var skipServices = new string[] { "turn_on", "turn_off", "toggle" }; @@ -215,7 +216,7 @@ public static class CodeGenerator }} "; var methodDeclaration = CSharpSyntaxTree.ParseText(methodCode).GetRoot().ChildNodes().OfType().FirstOrDefault() - ?? throw new NullReferenceException("Failed to parse method"); + ?? throw new NetDaemonNullReferenceException("Failed to parse method"); entityClass = entityClass.AddMembers(methodDeclaration); } namespaceDeclaration = namespaceDeclaration.AddMembers(entityClass); @@ -234,7 +235,7 @@ public static class CodeGenerator }} }}"; var entityClass = CSharpSyntaxTree.ParseText(classDeclaration).GetRoot().ChildNodes().OfType().FirstOrDefault() - ?? throw new NullReferenceException("Failed to parse entity class"); + ?? throw new NetDaemonNullReferenceException("Failed to parse entity class"); foreach (var entity in entities.Where(n => n.StartsWith(domain))) { var name = entity[(entity.IndexOf(".") + 1)..]; @@ -247,7 +248,7 @@ public static class CodeGenerator var propertyCode = $@"public {domain.ToCamelCase()}Entity {name.ToCamelCase()} => new {domain.ToCamelCase()}Entity(_app, new string[] {{""{entity}""}});"; var propDeclaration = CSharpSyntaxTree.ParseText(propertyCode).GetRoot().ChildNodes().OfType().FirstOrDefault() - ?? throw new NullReferenceException("Failed to parse property"); + ?? throw new NetDaemonNullReferenceException("Failed to parse property"); entityClass = entityClass.AddMembers(propDeclaration); } namespaceDeclaration = namespaceDeclaration.AddMembers(entityClass); diff --git a/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs b/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs index 7aa31a2d4..c32fd3ae7 100644 --- a/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs +++ b/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NetDaemon.Common.Configuration; +using NetDaemon.Common.Exceptions; using NetDaemon.Daemon; using NetDaemon.Daemon.Config; using NetDaemon.Service.App; @@ -88,7 +89,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) _loadedDaemonApps = null; await using var daemonHost = _serviceProvider.GetService() - ?? throw new ApplicationException("Failed to get service for NetDaemonHost"); + ?? throw new NetDaemonException("Failed to get service for NetDaemonHost"); { await Run(daemonHost, stoppingToken).ConfigureAwait(false); } diff --git a/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs b/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs index 374f80352..0747678cc 100644 --- a/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs +++ b/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs @@ -7,6 +7,7 @@ using JoySoftware.HomeAssistant.Client; using Moq; using NetDaemon.Common; +using NetDaemon.Common.Exceptions; using NetDaemon.Common.Fluent; using NetDaemon.Common.Reactive; using NetDaemon.Daemon.Storage; @@ -366,7 +367,7 @@ protected async Task InitializeFakeDaemon(short timeout = 300, bool overrideDebu protected async Task RunFakeDaemonUntilTimeout() { _ = _fakeConnectedDaemon ?? - throw new NullReferenceException("No task to process, did you forget to run InitFakeDaemon at the beginning of the test?"); + throw new NetDaemonNullReferenceException("No task to process, did you forget to run InitFakeDaemon at the beginning of the test?"); await WaitUntilCanceled(_fakeConnectedDaemon).ConfigureAwait(false); } diff --git a/src/Fakes/NetDaemon.Fakes/HassClientMock.cs b/src/Fakes/NetDaemon.Fakes/HassClientMock.cs index dca29af7c..bb1efdd88 100644 --- a/src/Fakes/NetDaemon.Fakes/HassClientMock.cs +++ b/src/Fakes/NetDaemon.Fakes/HassClientMock.cs @@ -9,6 +9,7 @@ using NetDaemon.Common; using NetDaemon.Common.Fluent; using Xunit; +using NetDaemon.Common.Exceptions; namespace NetDaemon.Daemon.Fakes { @@ -267,7 +268,7 @@ public static void AssertEqual(HassState hassState, EntityState entity) foreach (var attribute in hassState.Attributes!.Keys) { var attr = entity.Attribute as IDictionary ?? - throw new NullReferenceException($"{nameof(entity.Attribute)} cant be null"); + throw new NetDaemonNullReferenceException($"{nameof(entity.Attribute)} cant be null"); Assert.True(attr.ContainsKey(attribute)); Assert.Equal(hassState.Attributes[attribute], @@ -279,7 +280,6 @@ public static void AssertEqual(HassState hassState, EntityState entity) /// Gets a cancellation source that does not timeout if debugger is attached /// /// - /// public static CancellationTokenSource GetSourceWithTimeout(int milliSeconds = 100) { return (Debugger.IsAttached) diff --git a/tests/NetDaemon.Daemon.Tests/DaemonRunner/Api/ApiTests.cs b/tests/NetDaemon.Daemon.Tests/DaemonRunner/Api/ApiTests.cs index 1e1468563..f9736b46a 100644 --- a/tests/NetDaemon.Daemon.Tests/DaemonRunner/Api/ApiTests.cs +++ b/tests/NetDaemon.Daemon.Tests/DaemonRunner/Api/ApiTests.cs @@ -24,6 +24,7 @@ using System.Linq; using NetDaemon.Common.Configuration; using NetDaemon.Daemon.Fakes; +using NetDaemon.Common.Exceptions; namespace NetDaemon.Daemon.Tests.DaemonRunner.Api { @@ -147,7 +148,7 @@ public Task InitializeAsync() private static async Task ReadString(WebSocket ws) { var buffer = new ArraySegment(new byte[8192]); - _ = buffer.Array ?? throw new NullReferenceException("Failed to allocate memory buffer"); + _ = buffer.Array ?? throw new NetDaemonNullReferenceException("Failed to allocate memory buffer"); using var ms = new MemoryStream(); WebSocketReceiveResult result; diff --git a/tests/NetDaemon.Daemon.Tests/FakesTests/FakeTests.cs b/tests/NetDaemon.Daemon.Tests/FakesTests/FakeTests.cs index 4b37ab428..908177974 100644 --- a/tests/NetDaemon.Daemon.Tests/FakesTests/FakeTests.cs +++ b/tests/NetDaemon.Daemon.Tests/FakesTests/FakeTests.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using JoySoftware.HomeAssistant.Client; using Moq; +using NetDaemon.Common.Exceptions; using NetDaemon.Common.Fluent; using NetDaemon.Common.Reactive; using NetDaemon.Daemon.Fakes; @@ -192,7 +193,7 @@ public async Task StartupAsyncShouldThrowIfDaemonIsNull() INetDaemonHost? host = null; // ARRANGE ACT ASSERT - await Assert.ThrowsAsync(() => DefaultDaemonRxApp.StartUpAsync(host!)).ConfigureAwait(false); + await Assert.ThrowsAsync(() => DefaultDaemonRxApp.StartUpAsync(host!)).ConfigureAwait(false); } [Fact] diff --git a/tests/NetDaemon.Daemon.Tests/Fluent/FluentEventTests.cs b/tests/NetDaemon.Daemon.Tests/Fluent/FluentEventTests.cs index ad25b00da..ebbc5e39f 100644 --- a/tests/NetDaemon.Daemon.Tests/Fluent/FluentEventTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Fluent/FluentEventTests.cs @@ -2,6 +2,7 @@ using System.Dynamic; using System.Threading.Tasks; using Moq; +using NetDaemon.Common.Exceptions; using NetDaemon.Daemon.Fakes; using NetDaemon.Daemon.Storage; using Xunit; @@ -11,7 +12,7 @@ namespace NetDaemon.Daemon.Tests.Fluent public class FluentEventTests { [Fact] - public async Task ACustomEventNullValueCallThrowsNullReferenceException() + public async Task ACustomEventNullValueCallThrowsNetDaemonNullReferenceException() { // ARRANGE var hcMock = HassClientMock.DefaultMock; @@ -27,7 +28,7 @@ public async Task ACustomEventNullValueCallThrowsNullReferenceException() var cancelSource = HassClientMock.GetSourceWithTimeout(); - Assert.Throws(() => app + Assert.Throws(() => app .Event("CUSTOM_EVENT") .Call(null).Execute()); } diff --git a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/NetDaemonAppTests.cs b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/NetDaemonAppTests.cs index ad510f8b4..ae95800ca 100644 --- a/tests/NetDaemon.Daemon.Tests/NetDaemonApp/NetDaemonAppTests.cs +++ b/tests/NetDaemon.Daemon.Tests/NetDaemonApp/NetDaemonAppTests.cs @@ -7,18 +7,19 @@ using NetDaemon.Common.Fluent; using Xunit; using NetDaemon.Daemon.Fakes; +using NetDaemon.Common.Exceptions; namespace NetDaemon.Daemon.Tests.NetDaemonApp { - public class AppTestApp : NetDaemon.Common.NetDaemonApp { } + public class AppTestApp : Common.NetDaemonApp { } - public class AppTestApp2 : NetDaemon.Common.NetDaemonApp { } + public class AppTestApp2 : Common.NetDaemonApp { } public class NetDaemonApptests { private const string appTemplate = " app: "; private readonly LoggerMock _logMock; - private readonly NetDaemon.Common.NetDaemonApp _app; + private readonly Common.NetDaemonApp _app; private readonly Mock _netDaemonMock; public NetDaemonApptests() @@ -159,7 +160,7 @@ public void LogMessageWithExceptionAndDifferentLogLevelsShoulCallCorrectLogger(L { // ARRANGE const string? message = "message"; - var exception = new NullReferenceException("Null"); + var exception = new NetDaemonNullReferenceException("Null"); // ACT var methodInfo = _app.GetType().GetMethod(methodName, new Type[] { typeof(Exception), typeof(string) }); @@ -197,7 +198,7 @@ public void LogMessageWithParamsExceptionAndDifferentLogLevelsShoulCallCorrectLo { // ARRANGE const string? message = "Hello {name}"; - var exception = new NullReferenceException("Null"); + var exception = new NetDaemonNullReferenceException("Null"); // ACT var methodInfo = _app.GetType().GetMethod(methodName, new Type[] { typeof(Exception), typeof(string), typeof(object[]) }); diff --git a/tests/NetDaemon.Daemon.Tests/Reactive/RxAppTest.cs b/tests/NetDaemon.Daemon.Tests/Reactive/RxAppTest.cs index e9c153c9e..865d40b89 100644 --- a/tests/NetDaemon.Daemon.Tests/Reactive/RxAppTest.cs +++ b/tests/NetDaemon.Daemon.Tests/Reactive/RxAppTest.cs @@ -4,6 +4,7 @@ using System.Reactive.Linq; using System.Threading.Tasks; using Moq; +using NetDaemon.Common.Exceptions; using NetDaemon.Common.Reactive; using Xunit; @@ -188,7 +189,7 @@ public async Task StartupAsyncShouldThrowIfDaemonIsNull() INetDaemonHost? host = null; // ARRANGE ACT ASSERT - await Assert.ThrowsAsync(() => DefaultDaemonRxApp.StartUpAsync(host!)).ConfigureAwait(false); + await Assert.ThrowsAsync(() => DefaultDaemonRxApp.StartUpAsync(host!)).ConfigureAwait(false); } [Fact] From d9e8c4061fae16766adc489a3a2352e1c29c9b7f Mon Sep 17 00:00:00 2001 From: helto4real Date: Sat, 26 Dec 2020 22:55:51 +0100 Subject: [PATCH 06/10] More refactorings .net 5 analyzers --- .linting/roslynator.ruleset | 7 + .vscode/daemon.code-snippets | 8 + .../NetDaemon.Daemon/Daemon/CodeManager.cs | 5 +- .../Daemon/Config/ConfigExtensions.cs | 17 +- .../Daemon/Config/YamlAppConfig.cs | 17 +- .../Daemon/Config/YamlConfig.cs | 13 +- .../Daemon/Config/YamlExtensions.cs | 11 ++ .../Daemon/DaemonAppExtensions.cs | 28 +-- .../NetDaemon.Daemon/Daemon/HttpHandler.cs | 16 +- .../NetDaemon.Daemon/Daemon/NetDaemonHost.cs | 170 ++++++++++-------- .../NetDaemon.Daemon/Daemon/Scheduler.cs | 21 ++- .../Daemon/Storage/DataRepository.cs | 2 + .../Mapping/EntityStateMapper.cs | 4 +- .../NetDaemon.Daemon/NetDaemon.Daemon.csproj | 9 + .../Daemon/DataRepositoryTests.cs | 2 +- 15 files changed, 225 insertions(+), 105 deletions(-) diff --git a/.linting/roslynator.ruleset b/.linting/roslynator.ruleset index 967ecd465..52a4ec943 100644 --- a/.linting/roslynator.ruleset +++ b/.linting/roslynator.ruleset @@ -25,7 +25,14 @@ Just add ruleset file to a solution and open it. + + + + + + + diff --git a/.vscode/daemon.code-snippets b/.vscode/daemon.code-snippets index 4918506db..f2c7e35cb 100644 --- a/.vscode/daemon.code-snippets +++ b/.vscode/daemon.code-snippets @@ -12,6 +12,14 @@ // "description": "Log output to console" // } { + "nullcheck": { + "scope": "csharp", + "prefix": "nullcheck", + "body": [ + "_ = $1 ??", + " throw new NetDaemonArgumentNullException(nameof($1));" + ] + }, "fact": { "scope": "csharp", "prefix": "fact", diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs b/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs index 1c5139237..4c1615908 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -31,6 +32,7 @@ public CodeManager(IEnumerable daemonAppTypes, ILogger logger, IYamlConfig _yamlConfig = yamlConfig; } + [SuppressMessage("", "CA1065")] public int Count => _loadedDaemonApps?.Count() ?? throw new NetDaemonNullReferenceException("_loadedDaemonApps cannot be null"); // Internal for testing @@ -57,7 +59,8 @@ public IEnumerable InstanceDaemonApps() { try { - var yamlAppConfig = new YamlAppConfig(_loadedDaemonApps, File.OpenText(file), _yamlConfig, file); + using var fileReader = File.OpenText(file); + var yamlAppConfig = new YamlAppConfig(_loadedDaemonApps, fileReader, _yamlConfig, file); foreach (var appInstance in yamlAppConfig.Instances) { diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Config/ConfigExtensions.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Config/ConfigExtensions.cs index 9f668ef94..f413e67c6 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Config/ConfigExtensions.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Config/ConfigExtensions.cs @@ -1,7 +1,9 @@ -using System.Reflection; +using System.Globalization; +using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; +using NetDaemon.Common.Exceptions; [assembly: InternalsVisibleTo("NetDaemon.Daemon.Tests")] @@ -15,6 +17,9 @@ public static class TaskExtensions { public static async Task InvokeAsync(this MethodInfo mi, object? obj, params object?[]? parameters) { + _ = mi ?? + throw new NetDaemonArgumentNullException(nameof(mi)); + dynamic? awaitable = mi.Invoke(obj, parameters); if (awaitable != null) await awaitable.ConfigureAwait(false); @@ -25,6 +30,9 @@ public static class ConfigStringExtensions { public static string ToPythonStyle(this string str) { + _ = str ?? + throw new NetDaemonArgumentNullException(nameof(str)); + var build = new StringBuilder(str.Length); bool isStart = true; foreach (char c in str) @@ -33,13 +41,16 @@ public static string ToPythonStyle(this string str) build.Append('_'); else isStart = false; - build.Append(char.ToLower(c)); + build.Append(char.ToLower(c, CultureInfo.InvariantCulture)); } return build.ToString(); } public static string ToCamelCase(this string str) { + _ = str ?? + throw new NetDaemonArgumentNullException(nameof(str)); + var build = new StringBuilder(); bool nextIsUpper = false; bool isFirstCharacter = true; @@ -51,7 +62,7 @@ public static string ToCamelCase(this string str) continue; } - build.Append(nextIsUpper || isFirstCharacter ? char.ToUpper(c) : c); + build.Append(nextIsUpper || isFirstCharacter ? char.ToUpper(c, CultureInfo.InvariantCulture) : c); nextIsUpper = false; isFirstCharacter = false; } diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs index 8b0c3604b..6cb10e8b2 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using NetDaemon.Common; @@ -26,6 +27,8 @@ public YamlAppConfig(IEnumerable types, TextReader reader, IYamlConfig yam _yamlFilePath = yamlFilePath; } + [SuppressMessage("", "CA1508")] + [SuppressMessage("", "CA1065")] public IEnumerable Instances { get @@ -59,7 +62,7 @@ public IEnumerable Instances } } } - catch (System.Exception e) + catch (Exception e) { throw new NetDaemonException($"Error instancing application {appId}", e); } @@ -74,10 +77,10 @@ public IEnumerable Instances YamlMappingNode appNode, string? appId) { - var netDaemonApp = (INetDaemonAppBase?)Activator.CreateInstance(netDaemonAppType); + _ = appNode ?? + throw new NetDaemonArgumentNullException(nameof(appNode)); - if (netDaemonApp == null) - return null; + var netDaemonApp = (INetDaemonAppBase?)Activator.CreateInstance(netDaemonAppType); foreach (KeyValuePair entry in appNode.Children) { @@ -106,7 +109,8 @@ public IEnumerable Instances return netDaemonApp; } - private object? InstanceProperty(Object? parent, Type instanceType, YamlNode node) + [SuppressMessage("", "CA1508")] // Weird bug that this should not warn! + private object? InstanceProperty(object? parent, Type instanceType, YamlNode node) { if (node.NodeType == YamlNodeType.Scalar) { @@ -197,7 +201,8 @@ private void ReplaceSecretIfExists(YamlScalarNode scalarNode) { return null; } - return ((YamlScalarNode)classChild.Value)?.Value?.ToLowerInvariant(); + var scalarNode = (YamlScalarNode)classChild.Value; + return scalarNode.Value?.ToLowerInvariant(); } } } \ No newline at end of file diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlConfig.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlConfig.cs index d7965edaf..538137a71 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlConfig.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlConfig.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.CompilerServices; using Microsoft.Extensions.Options; @@ -24,7 +25,11 @@ public class YamlConfig : IYamlConfig public YamlConfig(IOptions netDaemonSettings) { + _ = netDaemonSettings ?? + throw new NetDaemonArgumentNullException(nameof(netDaemonSettings)); + _configFolder = netDaemonSettings.Value.GetAppSourceDirectory(); + _secrets = GetAllSecretsFromPath(_configFolder); } @@ -53,9 +58,11 @@ public IEnumerable GetAllConfigFilePaths() internal static Dictionary GetSecretsFromSecretsYaml(string file) { - return GetSecretsFromSecretsYaml(File.OpenText(file)); + using var fileReader = File.OpenText(file); + return GetSecretsFromSecretsYaml(fileReader); } + [SuppressMessage("", "CA1508")] // TODO: Need to refactor this internal static Dictionary GetSecretsFromSecretsYaml(TextReader reader) { var result = new Dictionary(); @@ -94,8 +101,8 @@ internal static Dictionary> GetAllSecretsFrom if (!result.ContainsKey(fileDirectory)) { - var secretsFromFile = GetSecretsFromSecretsYaml(file); - result[fileDirectory] = secretsFromFile; + result[fileDirectory] = (Dictionary?)GetSecretsFromSecretsYaml(file) ?? + new Dictionary(); } } return result; diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlExtensions.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlExtensions.cs index 9d121c82e..0b13fe40f 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlExtensions.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlExtensions.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; +using NetDaemon.Common.Exceptions; using YamlDotNet.RepresentationModel; [assembly: InternalsVisibleTo("NetDaemon.Daemon.Tests")] @@ -25,14 +26,24 @@ public static class YamlExtensions { public static PropertyInfo? GetYamlProperty(this Type type, string propertyName) { + _ = type ?? + throw new NetDaemonArgumentNullException(nameof(type)); + // Lets try convert from python style to CamelCase + var prop = type.GetProperty(propertyName) ?? type.GetProperty(propertyName.ToCamelCase()); return prop; } public static object? ToObject(this YamlScalarNode node, Type valueType) { + _ = valueType ?? + throw new NetDaemonArgumentNullException(nameof(valueType)); + _ = node ?? + throw new NetDaemonArgumentNullException(nameof(node)); + Type? underlyingNullableType = Nullable.GetUnderlyingType(valueType); + if (underlyingNullableType != null) { // It is nullable type diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/DaemonAppExtensions.cs b/src/Daemon/NetDaemon.Daemon/Daemon/DaemonAppExtensions.cs index 985c9f2e3..3a0c0e36e 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/DaemonAppExtensions.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/DaemonAppExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Dynamic; using System.Linq; using System.Reflection; @@ -6,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using NetDaemon.Common; +using NetDaemon.Common.Exceptions; using NetDaemon.Common.Fluent; using NetDaemon.Common.Reactive; using NetDaemon.Daemon.Config; @@ -16,9 +18,15 @@ namespace NetDaemon.Daemon { public static class DaemonAppExtensions { - public static async Task HandleAttributeInitialization(this INetDaemonAppBase netDaemonApp, INetDaemon _daemon) + public static async Task HandleAttributeInitialization(this INetDaemonAppBase netDaemonApp, INetDaemon daemon) { + _ = daemon ?? + throw new NetDaemonArgumentNullException(nameof(daemon)); + _ = netDaemonApp ?? + throw new NetDaemonArgumentNullException(nameof(netDaemonApp)); + var netDaemonAppType = netDaemonApp.GetType(); + foreach (var method in netDaemonAppType.GetMethods()) { foreach (var attr in method.GetCustomAttributes(false)) @@ -28,11 +36,11 @@ public static async Task HandleAttributeInitialization(this INetDaemonAppBase ne switch (attr) { case HomeAssistantServiceCallAttribute: - await HandleServiceCallAttribute(_daemon, daemonApp, method, true).ConfigureAwait(false); + await HandleServiceCallAttribute(daemon, daemonApp, method, true).ConfigureAwait(false); break; case HomeAssistantStateChangedAttribute hassStateChangedAttribute: - HandleStateChangedAttribute(_daemon, hassStateChangedAttribute, daemonApp, method); + HandleStateChangedAttribute(daemon, hassStateChangedAttribute, daemonApp, method); break; } } @@ -41,7 +49,7 @@ public static async Task HandleAttributeInitialization(this INetDaemonAppBase ne switch (attr) { case HomeAssistantServiceCallAttribute: - await HandleServiceCallAttribute(_daemon, daemonRxApp, method, false).ConfigureAwait(false); + await HandleServiceCallAttribute(daemon, daemonRxApp, method, false).ConfigureAwait(false); break; } } @@ -91,6 +99,7 @@ private static (bool, string) CheckIfStateChangedSignatureIsOk(MethodInfo method return (true, string.Empty); } + [SuppressMessage("", "CA1031")] private static async Task HandleServiceCallAttribute(INetDaemon _daemon, NetDaemonAppBase netDaemonApp, MethodInfo method, bool async = true) { var (signatureOk, err) = CheckIfServiceCallSignatureIsOk(method, async); @@ -121,6 +130,7 @@ private static async Task HandleServiceCallAttribute(INetDaemon _daemon, NetDaem }); } + [SuppressMessage("", "CA1031")] private static void HandleStateChangedAttribute( INetDaemon _daemon, HomeAssistantStateChangedAttribute hassStateChangedAttribute, @@ -141,16 +151,14 @@ MethodInfo method { try { - if (hassStateChangedAttribute.To != null) + if (hassStateChangedAttribute.To != null && (dynamic)hassStateChangedAttribute.To != to?.State) { - if ((dynamic)hassStateChangedAttribute.To != to?.State) - return; + return; } - if (hassStateChangedAttribute.From != null) + if (hassStateChangedAttribute.From != null && (dynamic)hassStateChangedAttribute.From != from?.State) { - if ((dynamic)hassStateChangedAttribute.From != from?.State) - return; + return; } // If we don´t accept all changes in the state change diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs b/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs index 165e665f8..1e0f01ecd 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs @@ -27,11 +27,11 @@ public HttpClient CreateHttpClient(string? name = null) { _ = _httpClientFactory ?? throw new NetDaemonNullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!"); - var httpClient = _httpClientFactory.CreateClient(); + using var httpClient = _httpClientFactory.CreateClient(); AddHeaders(httpClient, headers); - var streamTask = httpClient.GetStreamAsync(url) + var streamTask = httpClient.GetStreamAsync(new Uri(url)) ?? throw new NetDaemonException($"Unexpected, nothing returned from {url}"); return await JsonSerializer.DeserializeAsync(await streamTask.ConfigureAwait(false), options).ConfigureAwait(false); @@ -40,14 +40,16 @@ public HttpClient CreateHttpClient(string? name = null) public async Task PostJson(string url, object request, JsonSerializerOptions? options = null, params (string, object)[] headers) { _ = _httpClientFactory ?? throw new NetDaemonNullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!"); + _ = request ?? throw new NetDaemonArgumentNullException(nameof(request)); - var httpClient = _httpClientFactory.CreateClient(); + using var httpClient = _httpClientFactory.CreateClient(); AddHeaders(httpClient, headers); var bytesToPost = JsonSerializer.SerializeToUtf8Bytes(request, request.GetType(), options); + using var content = new ByteArrayContent(bytesToPost); - var response = await httpClient.PostAsync(url, new ByteArrayContent(bytesToPost)).ConfigureAwait(false); + var response = await httpClient.PostAsync(new Uri(url), content).ConfigureAwait(false); response.EnsureSuccessStatusCode(); @@ -58,14 +60,16 @@ public HttpClient CreateHttpClient(string? name = null) public async Task PostJson(string url, object request, JsonSerializerOptions? options = null, params (string, object)[] headers) { _ = _httpClientFactory ?? throw new NetDaemonNullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!"); + _ = request ?? throw new NetDaemonArgumentNullException(nameof(request)); - var httpClient = _httpClientFactory.CreateClient(); + using var httpClient = _httpClientFactory.CreateClient(); AddHeaders(httpClient, headers); var bytesToPost = JsonSerializer.SerializeToUtf8Bytes(request, request.GetType(), options); + using var content = new ByteArrayContent(bytesToPost); - var response = await httpClient.PostAsync(url, new ByteArrayContent(bytesToPost)).ConfigureAwait(false); + var response = await httpClient.PostAsync(new Uri(url), content).ConfigureAwait(false); response.EnsureSuccessStatusCode(); } diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs b/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs index 2c7d1f162..ef565a1fe 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Dynamic; +using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using System.Text; @@ -128,6 +130,7 @@ public IHttpHandler Http public IScheduler Scheduler => _scheduler; + [SuppressMessage("", "CA1721")] public IEnumerable State => InternalState.Select(n => n.Value); // For testing @@ -144,22 +147,23 @@ public IHttpHandler Http public async Task> GetAllServices() { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); return await _hassClient.GetServices().ConfigureAwait(false); } public void CallService(string domain, string service, dynamic? data = null) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); if (!_serviceCallMessageChannel.Writer.TryWrite((domain, service, data))) throw new NetDaemonException("Servicecall queue full!"); } + [SuppressMessage("", "CA1031")] public async Task CallServiceAsync(string domain, string service, dynamic? data = null, bool waitForResponse = false) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); try { @@ -174,21 +178,23 @@ public async Task CallServiceAsync(string domain, string service, dynamic? data /// public ICamera Camera(INetDaemonApp app, params string[] entityIds) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); return new CameraManager(entityIds, this, app); } /// public ICamera Cameras(INetDaemonApp app, IEnumerable entityIds) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); return new CameraManager(entityIds, this, app); } /// public ICamera Cameras(INetDaemonApp app, Func func) { - this._cancelToken.ThrowIfCancellationRequested(); + _ = app ?? + throw new NetDaemonArgumentNullException(nameof(app)); + _cancelToken.ThrowIfCancellationRequested(); try { IEnumerable x = State.Where(func); @@ -206,6 +212,9 @@ public async ValueTask DisposeAsync() { _cancelDaemon.Cancel(); await Stop().ConfigureAwait(false); + _cancelDaemon.Dispose(); + await _scheduler.DisposeAsync().ConfigureAwait(false); + _cancelTokenSource?.Dispose(); Logger.LogTrace("Instance NetDaemonHost Disposed"); } @@ -219,7 +228,9 @@ public void EnableApplicationDiscoveryService() public IEntity Entities(INetDaemonApp app, Func func) { - this._cancelToken.ThrowIfCancellationRequested(); + _ = app ?? + throw new NetDaemonArgumentNullException(nameof(app)); + _cancelToken.ThrowIfCancellationRequested(); try { @@ -234,22 +245,22 @@ public IEntity Entities(INetDaemonApp app, Func func) } } - public IEntity Entities(INetDaemonApp app, IEnumerable entityIds) + public IEntity Entities(INetDaemonApp app, IEnumerable entityId) { - this._cancelToken.ThrowIfCancellationRequested(); - return new EntityManager(entityIds, this, app); + _cancelToken.ThrowIfCancellationRequested(); + return new EntityManager(entityId, this, app); } - public IEntity Entity(INetDaemonApp app, params string[] entityIds) + public IEntity Entity(INetDaemonApp app, params string[] entityId) { - this._cancelToken.ThrowIfCancellationRequested(); - return new EntityManager(entityIds, this, app); + _cancelToken.ThrowIfCancellationRequested(); + return new EntityManager(entityId, this, app); } /// public INetDaemonAppBase? GetApp(string appInstanceId) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); return InternalRunningAppInstances.ContainsKey(appInstanceId) ? InternalRunningAppInstances[appInstanceId] : null; @@ -257,7 +268,7 @@ public IEntity Entity(INetDaemonApp app, params string[] entityIds) public async Task GetDataAsync(string id) where T : class { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); _ = _repository as IDataRepository ?? throw new NetDaemonNullReferenceException($"{nameof(_repository)} can not be null!"); @@ -274,11 +285,13 @@ public IEntity Entity(INetDaemonApp app, params string[] entityIds) return data; } - public EntityState? GetState(string entity) + public EntityState? GetState(string entityId) { - this._cancelToken.ThrowIfCancellationRequested(); + _ = entityId ?? + throw new NetDaemonArgumentNullException(nameof(entityId)); + _cancelToken.ThrowIfCancellationRequested(); - return InternalState.TryGetValue(entity, out EntityState? returnValue) + return InternalState.TryGetValue(entityId, out EntityState? returnValue) ? returnValue : null; } @@ -298,21 +311,21 @@ public async Task Initialize(IInstanceDaemonApp appInstanceManager) /// public IFluentInputSelect InputSelect(INetDaemonApp app, params string[] inputSelectParams) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); return new InputSelectManager(inputSelectParams, this, app); } /// public IFluentInputSelect InputSelects(INetDaemonApp app, IEnumerable inputSelectParams) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); return new InputSelectManager(inputSelectParams, this, app); } /// public IFluentInputSelect InputSelects(INetDaemonApp app, Func func) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); IEnumerable x = State.Where(func).Select(n => n.EntityId); return new InputSelectManager(x, this, app); } @@ -320,31 +333,41 @@ public IFluentInputSelect InputSelects(INetDaemonApp app, Func public void ListenCompanionServiceCall(string service, Func action) { - this._cancelToken.ThrowIfCancellationRequested(); + _ = service ?? + throw new NetDaemonArgumentNullException(nameof(service)); + _cancelToken.ThrowIfCancellationRequested(); _daemonServiceCallFunctions.Add(("netdaemon", service.ToLowerInvariant(), action)); } public void ListenServiceCall(string domain, string service, Func action) - => _daemonServiceCallFunctions.Add((domain.ToLowerInvariant(), service.ToLowerInvariant(), action)); + { + _ = service ?? + throw new NetDaemonArgumentNullException(nameof(service)); + _ = domain ?? + throw new NetDaemonArgumentNullException(nameof(domain)); + _daemonServiceCallFunctions.Add((domain.ToLowerInvariant(), service.ToLowerInvariant(), action)); + } /// public IMediaPlayer MediaPlayer(INetDaemonApp app, params string[] entityIds) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); return new MediaPlayerManager(entityIds, this, app); } /// public IMediaPlayer MediaPlayers(INetDaemonApp app, IEnumerable entityIds) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); return new MediaPlayerManager(entityIds, this, app); } /// public IMediaPlayer MediaPlayers(INetDaemonApp app, Func func) { - this._cancelToken.ThrowIfCancellationRequested(); + _ = app ?? + throw new NetDaemonArgumentNullException(nameof(app)); + _cancelToken.ThrowIfCancellationRequested(); try { IEnumerable x = State.Where(func); @@ -378,6 +401,7 @@ public async Task ReloadAllApps() /// /// /// + [SuppressMessage("", "CA1031")] public async Task Run(string host, short port, bool ssl, string token, CancellationToken cancellationToken) { // Create combine cancellation token @@ -444,16 +468,13 @@ public async Task Run(string host, short port, bool ssl, string token, Cancellat HassEvent changedEvent = await _hassClient.ReadEventAsync(cancellationToken).ConfigureAwait(false); if (changedEvent != null) { - if (changedEvent.Data is HassServiceEventData hseData) - { - if (hseData.Domain == "homeassistant" && + if (changedEvent.Data is HassServiceEventData hseData && hseData.Domain == "homeassistant" && (hseData.Service == "stop" || hseData.Service == "restart")) - { - // The user stopped HA so just stop processing messages - Logger.LogInformation("User {action} Home Assistant, will try to reconnect...", - hseData.Service == "stop" ? "stopping" : "restarting"); - return; - } + { + // The user stopped HA so just stop processing messages + Logger.LogInformation("User {action} Home Assistant, will try to reconnect...", + hseData.Service == "stop" ? "stopping" : "restarting"); + return; } // Remove all completed Tasks _eventHandlerTasks.RemoveAll(x => x.IsCompleted); @@ -484,29 +505,30 @@ public async Task Run(string host, short port, bool ssl, string token, Cancellat } } - public IScript RunScript(INetDaemonApp app, params string[] entityId) + public IScript RunScript(INetDaemonApp app, params string[] entityIds) { - this._cancelToken.ThrowIfCancellationRequested(); - return new EntityManager(entityId, this, app); + _cancelToken.ThrowIfCancellationRequested(); + return new EntityManager(entityIds, this, app); } public Task SaveDataAsync(string id, T data) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); - _ = _repository as IDataRepository ?? + _ = _repository ?? throw new NetDaemonNullReferenceException($"{nameof(_repository)} can not be null!"); if (data == null) - throw new ArgumentNullException(nameof(data)); + throw new NetDaemonArgumentNullException(nameof(data)); DataCache[id] = data; return _repository!.Save(id, data); } + [SuppressMessage("", "CA1031")] public async Task SendEvent(string eventId, dynamic? data = null) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); if (!Connected) return false; @@ -524,7 +546,7 @@ public async Task SendEvent(string eventId, dynamic? data = null) /// public async Task SetDaemonStateAsync(int numberOfLoadedApps, int numberOfRunningApps) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); await SetStateAsync( "netdaemon.status", @@ -536,7 +558,7 @@ await SetStateAsync( public void SetState(string entityId, dynamic state, dynamic? attributes = null) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); if (!_setStateMessageChannel.Writer.TryWrite((entityId, state, attributes))) throw new NetDaemonException("Servicecall queue full!"); @@ -545,7 +567,7 @@ public void SetState(string entityId, dynamic state, dynamic? attributes = null) public async Task SetStateAsync(string entityId, dynamic state, params (string name, object val)[] attributes) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); try { // Use expando object as all other methods @@ -576,11 +598,12 @@ public void SetState(string entityId, dynamic state, dynamic? attributes = null) public void Speak(string entityId, string message) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); _ttsMessageChannel.Writer.TryWrite((entityId, message)); } + [SuppressMessage("", "CA1031")] public async Task Stop() { try @@ -687,21 +710,15 @@ internal static bool FixStateTypes(HassStateChangedEventData stateData) internal string? GetAreaForEntityId(string entityId) { - if (_hassEntities.TryGetValue(entityId, out HassEntity? entity) && entity is not null) + if (_hassEntities.TryGetValue(entityId, out HassEntity? entity) && entity is not null && entity.DeviceId is not null) { - if (entity.DeviceId is not null) + // The entity is on a device + if (_hassDevices.TryGetValue(entity.DeviceId, out HassDevice? device) && device is not null && device.AreaId is not null) { - // The entity is on a device - if (_hassDevices.TryGetValue(entity.DeviceId, out HassDevice? device) && device is not null) + // This device is in an area + if (_hassAreas.TryGetValue(device.AreaId, out HassArea? area) && area is not null) { - if (device.AreaId is not null) - { - // This device is in an area - if (_hassAreas.TryGetValue(device.AreaId, out HassArea? area) && area is not null) - { - return area.Name; - } - } + return area.Name; } } } @@ -710,7 +727,7 @@ internal static bool FixStateTypes(HassStateChangedEventData stateData) internal async Task RefreshInternalStatesAndSetArea() { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); foreach (var device in await _hassClient.GetDevices().ConfigureAwait(false)) { @@ -769,9 +786,12 @@ internal static IList SortByDependency(IEnumerable await SetStateOnDaemonAppSwitch("on", data).ConfigureAwait(false)); @@ -1267,7 +1290,7 @@ async Task SetStateOnDaemonAppSwitch(string state, dynamic? data) if (entityId is null) return; - if (!entityId.StartsWith("switch.netdaemon_")) + if (!entityId.StartsWith("switch.netdaemon_", true, CultureInfo.InvariantCulture)) return; // We only want app switches await SetDependentState(entityId, state).ConfigureAwait(false); @@ -1336,6 +1359,7 @@ private async Task PersistAppStateAsync(NetDaemonAppBase app) await SaveDataAsync>(app.GetUniqueIdForStorage(), obj).ConfigureAwait(false); } + [SuppressMessage("", "CA1031")] private async Task RestoreAppState(INetDaemonAppBase appInstance) { try diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs index d2ea08cd4..2e42a12c4 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Threading; @@ -62,6 +63,9 @@ public ISchedulerResult RunEvery(int millisecondsDelay, Func func) /// public ISchedulerResult RunEvery(TimeSpan timeSpan, Func func) { + _ = func ?? + throw new NetDaemonArgumentNullException(nameof(func)); + var cancelSource = new CancellationTokenSource(); var task = RunEveryInternalAsync(timeSpan, func, cancelSource.Token); @@ -70,6 +74,7 @@ public ISchedulerResult RunEvery(TimeSpan timeSpan, Func func) return new SchedulerResult(task, cancelSource); } + [SuppressMessage("", "CA1031")] private async Task RunEveryInternalAsync(TimeSpan timeSpan, Func func, CancellationToken token) { using CancellationTokenSource linkedCts = @@ -131,6 +136,9 @@ internal TimeSpan CalculateEveryMinuteTimeBetweenNowAndTargetTime(short second) /// public ISchedulerResult RunDaily(string time, IEnumerable? runOnDays, Func func) { + _ = func ?? + throw new NetDaemonArgumentNullException(nameof(func)); + var cancelSource = new CancellationTokenSource(); if (!DateTime.TryParseExact(time, "HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime timeOfDayToTrigger)) @@ -144,6 +152,7 @@ public ISchedulerResult RunDaily(string time, IEnumerable? runOnDays, return new SchedulerResult(task, cancelSource); } + [SuppressMessage("", "CA1031")] private async Task RunDailyInternalAsync(DateTime timeOfDayToTrigger, IEnumerable? runOnDays, Func func, CancellationToken token) { using CancellationTokenSource linkedCts = @@ -193,6 +202,8 @@ private async Task RunDailyInternalAsync(DateTime timeOfDayToTrigger, IEnumerabl /// public ISchedulerResult RunEveryMinute(short second, Func func) { + _ = func ?? + throw new NetDaemonArgumentNullException(nameof(func)); var cancelSource = new CancellationTokenSource(); var task = RunEveryMinuteInternalAsync(second, func, cancelSource.Token); @@ -201,6 +212,7 @@ public ISchedulerResult RunEveryMinute(short second, Func func) return new SchedulerResult(task, cancelSource); } + [SuppressMessage("", "CA1031")] private async Task RunEveryMinuteInternalAsync(short second, Func func, CancellationToken token) { using CancellationTokenSource linkedCts = @@ -223,11 +235,15 @@ private async Task RunEveryMinuteInternalAsync(short second, Func func, Ca } /// + [SuppressMessage("", "CA1031")] public ISchedulerResult RunIn(int millisecondsDelay, Func func) => RunIn(TimeSpan.FromMilliseconds(millisecondsDelay), func); /// public ISchedulerResult RunIn(TimeSpan timeSpan, Func func) { + _ = func ?? + throw new NetDaemonArgumentNullException(nameof(func)); + var cancelSource = new CancellationTokenSource(); var task = InternalRunInAsync(timeSpan, func, cancelSource.Token); ScheduleTask(task); @@ -235,6 +251,7 @@ public ISchedulerResult RunIn(TimeSpan timeSpan, Func func) return new SchedulerResult(task, cancelSource); } + [SuppressMessage("", "CA1031")] private async Task InternalRunInAsync(TimeSpan timeSpan, Func func, CancellationToken token) { using CancellationTokenSource linkedCts = @@ -265,6 +282,8 @@ public async Task Stop() var taskResult = await Task.WhenAny( Task.WhenAll(_scheduledTasks.Values.ToArray()), Task.Delay(1000)).ConfigureAwait(false); + _cancelSource.Dispose(); + if (_scheduledTasks.Values.Any(n => !n.IsCompleted)) { // Todo: Some kind of logging have to be done here to tell user which task caused timeout @@ -316,6 +335,7 @@ private void ScheduleTask(Task addedTask) _scheduledTasks[addedTask.Id] = addedTask; } + [SuppressMessage("", "CA1031")] public async ValueTask DisposeAsync() { try @@ -337,7 +357,6 @@ public class TimeManager : IManageTime /// /// Returns current local time /// - /// public DateTime Current => DateTime.Now; /// diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs index c784bc044..8ffb496e6 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Text.Json; @@ -23,6 +24,7 @@ public DataRepository(string dataStoragePath) } /// + [SuppressMessage("", "CA1031")] public async ValueTask Get(string id) where T : class { try diff --git a/src/Daemon/NetDaemon.Daemon/Mapping/EntityStateMapper.cs b/src/Daemon/NetDaemon.Daemon/Mapping/EntityStateMapper.cs index 409a77be9..5ca4ca720 100644 --- a/src/Daemon/NetDaemon.Daemon/Mapping/EntityStateMapper.cs +++ b/src/Daemon/NetDaemon.Daemon/Mapping/EntityStateMapper.cs @@ -4,6 +4,7 @@ using JoySoftware.HomeAssistant.Client; using NetDaemon.Infrastructure.Extensions; using NetDaemon.Common; +using NetDaemon.Common.Exceptions; namespace NetDaemon.Mapping { @@ -13,9 +14,10 @@ public static class EntityStateMapper /// Converts HassState to EntityState /// /// - /// public static EntityState Map(this HassState hassState) { + _ = hassState ?? + throw new NetDaemonArgumentNullException(nameof(hassState)); var entityState = new EntityState { EntityId = hassState.EntityId, diff --git a/src/Daemon/NetDaemon.Daemon/NetDaemon.Daemon.csproj b/src/Daemon/NetDaemon.Daemon/NetDaemon.Daemon.csproj index 667c9b43a..8faf00c9a 100644 --- a/src/Daemon/NetDaemon.Daemon/NetDaemon.Daemon.csproj +++ b/src/Daemon/NetDaemon.Daemon/NetDaemon.Daemon.csproj @@ -25,6 +25,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + @@ -33,4 +37,9 @@ ..\..\..\.linting\roslynator.ruleset + + ..\..\..\.linting\roslynator.ruleset + true + AllEnabledByDefault + \ No newline at end of file diff --git a/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs b/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs index caad4c270..31286a7f2 100644 --- a/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs @@ -16,7 +16,7 @@ public class DataRepositoryTests : DaemonHostTestBase public static readonly string DataRepositoryPath = Path.Combine(AppContext.BaseDirectory, "datarepository"); - public DataRepositoryTests() : base() + public DataRepositoryTests() { } From 6a854914a8a8dd26596d87a36aa3f8127c8510bc Mon Sep 17 00:00:00 2001 From: helto4real Date: Sat, 26 Dec 2020 23:33:52 +0100 Subject: [PATCH 07/10] dotnet 5 anayzer continues --- .../DaemonRunner/DaemonRunner.csproj | 6 +++ .../Config/NetDaemonConsoleThemes.cs | 34 +++++++------- .../Config/SerilogConfigurator.cs | 13 +++++- .../DaemonRunner/NetDaemonExtensions.cs | 12 +++-- .../DaemonRunner/Service/API/WsHandler.cs | 15 ++++-- .../DaemonRunner/Service/ApiService.cs | 9 +--- .../DaemonRunner/Service/App/CodeGenerator.cs | 24 ++++------ .../Service/App/DaemonAppCompiler.cs | 6 ++- .../Service/App/DaemonCompiler.cs | 46 ++++++++----------- .../DaemonRunner/Service/RunnerService.cs | 11 ++++- .../NetDaemon.Fakes/DaemonHostTestBase.cs | 13 ++---- .../FakesTests/FakeTests.cs | 2 +- 12 files changed, 104 insertions(+), 87 deletions(-) diff --git a/src/DaemonRunner/DaemonRunner/DaemonRunner.csproj b/src/DaemonRunner/DaemonRunner/DaemonRunner.csproj index 271cfec80..b33d4e45f 100644 --- a/src/DaemonRunner/DaemonRunner/DaemonRunner.csproj +++ b/src/DaemonRunner/DaemonRunner/DaemonRunner.csproj @@ -33,6 +33,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + @@ -42,5 +46,7 @@ ..\..\..\.linting\roslynator.ruleset + true + AllEnabledByDefault \ No newline at end of file diff --git a/src/DaemonRunner/DaemonRunner/Infrastructure/Config/NetDaemonConsoleThemes.cs b/src/DaemonRunner/DaemonRunner/Infrastructure/Config/NetDaemonConsoleThemes.cs index fc8fe7146..9c491ab7a 100644 --- a/src/DaemonRunner/DaemonRunner/Infrastructure/Config/NetDaemonConsoleThemes.cs +++ b/src/DaemonRunner/DaemonRunner/Infrastructure/Config/NetDaemonConsoleThemes.cs @@ -8,22 +8,22 @@ public static class NetDaemonConsoleThemes { private static SystemConsoleTheme SystemTheme { get; } = new(new Dictionary { - [ConsoleThemeStyle.Text] = new SystemConsoleThemeStyle {Foreground = ConsoleColor.Gray}, - [ConsoleThemeStyle.SecondaryText] = new SystemConsoleThemeStyle {Foreground = ConsoleColor.DarkGray}, - [ConsoleThemeStyle.TertiaryText] = new SystemConsoleThemeStyle {Foreground = ConsoleColor.DarkGray}, - [ConsoleThemeStyle.Invalid] = new SystemConsoleThemeStyle {Foreground = ConsoleColor.Yellow}, - [ConsoleThemeStyle.Null] = new SystemConsoleThemeStyle {Foreground = ConsoleColor.Green}, - [ConsoleThemeStyle.Name] = new SystemConsoleThemeStyle {Foreground = ConsoleColor.Green}, - [ConsoleThemeStyle.String] = new SystemConsoleThemeStyle {Foreground = ConsoleColor.Green}, - [ConsoleThemeStyle.Number] = new SystemConsoleThemeStyle {Foreground = ConsoleColor.Green}, - [ConsoleThemeStyle.Boolean] = new SystemConsoleThemeStyle {Foreground = ConsoleColor.Green}, - [ConsoleThemeStyle.Scalar] = new SystemConsoleThemeStyle {Foreground = ConsoleColor.Green}, - [ConsoleThemeStyle.LevelVerbose] = new SystemConsoleThemeStyle {Foreground = ConsoleColor.Gray}, - [ConsoleThemeStyle.LevelDebug] = new SystemConsoleThemeStyle {Foreground = ConsoleColor.DarkYellow}, - [ConsoleThemeStyle.LevelInformation] = new SystemConsoleThemeStyle {Foreground = ConsoleColor.DarkGreen}, - [ConsoleThemeStyle.LevelWarning] = new SystemConsoleThemeStyle {Foreground = ConsoleColor.Yellow}, - [ConsoleThemeStyle.LevelError] = new SystemConsoleThemeStyle {Foreground = ConsoleColor.Red}, - [ConsoleThemeStyle.LevelFatal] = new SystemConsoleThemeStyle {Foreground = ConsoleColor.DarkRed}, + [ConsoleThemeStyle.Text] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Gray }, + [ConsoleThemeStyle.SecondaryText] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.DarkGray }, + [ConsoleThemeStyle.TertiaryText] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.DarkGray }, + [ConsoleThemeStyle.Invalid] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Yellow }, + [ConsoleThemeStyle.Null] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Green }, + [ConsoleThemeStyle.Name] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Green }, + [ConsoleThemeStyle.String] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Green }, + [ConsoleThemeStyle.Number] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Green }, + [ConsoleThemeStyle.Boolean] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Green }, + [ConsoleThemeStyle.Scalar] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Green }, + [ConsoleThemeStyle.LevelVerbose] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Gray }, + [ConsoleThemeStyle.LevelDebug] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.DarkYellow }, + [ConsoleThemeStyle.LevelInformation] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.DarkGreen }, + [ConsoleThemeStyle.LevelWarning] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Yellow }, + [ConsoleThemeStyle.LevelError] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Red }, + [ConsoleThemeStyle.LevelFatal] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.DarkRed }, }); private static AnsiConsoleTheme AnsiTheme { get; } = new(new Dictionary @@ -48,7 +48,7 @@ public static class NetDaemonConsoleThemes public static ConsoleTheme GetThemeByType(string type) { - return string.Equals(type, "system", StringComparison.InvariantCultureIgnoreCase) ? SystemTheme : AnsiTheme; + return string.Equals(type, "system", StringComparison.OrdinalIgnoreCase) ? SystemTheme : AnsiTheme; } } } \ No newline at end of file diff --git a/src/DaemonRunner/DaemonRunner/Infrastructure/Config/SerilogConfigurator.cs b/src/DaemonRunner/DaemonRunner/Infrastructure/Config/SerilogConfigurator.cs index 1a5990592..e2f42b2b6 100644 --- a/src/DaemonRunner/DaemonRunner/Infrastructure/Config/SerilogConfigurator.cs +++ b/src/DaemonRunner/DaemonRunner/Infrastructure/Config/SerilogConfigurator.cs @@ -1,6 +1,8 @@ -using System.IO; +using System.Globalization; +using System.IO; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; +using NetDaemon.Common.Exceptions; using Serilog; using Serilog.Core; using Serilog.Events; @@ -13,6 +15,11 @@ public static class SerilogConfigurator public static LoggerConfiguration Configure(LoggerConfiguration loggerConfiguration, IHostEnvironment hostingEnvironment) { + _ = loggerConfiguration ?? + throw new NetDaemonArgumentNullException(nameof(loggerConfiguration)); + _ = hostingEnvironment ?? + throw new NetDaemonArgumentNullException(nameof(hostingEnvironment)); + var loggingConfiguration = GetLoggingConfiguration(hostingEnvironment); SetMinimumLogLevel(loggingConfiguration.MinimumLevel); @@ -40,7 +47,9 @@ private static LoggingConfiguration GetLoggingConfiguration(IHostEnvironment hos public static void SetMinimumLogLevel(string level) { - LevelSwitch.MinimumLevel = level.ToLower() switch + _ = level ?? + throw new NetDaemonArgumentNullException(nameof(level)); + LevelSwitch.MinimumLevel = level.ToLower(CultureInfo.InvariantCulture) switch { "info" => LogEventLevel.Information, "debug" => LogEventLevel.Debug, diff --git a/src/DaemonRunner/DaemonRunner/NetDaemonExtensions.cs b/src/DaemonRunner/DaemonRunner/NetDaemonExtensions.cs index 2804d4300..06b64eb7c 100644 --- a/src/DaemonRunner/DaemonRunner/NetDaemonExtensions.cs +++ b/src/DaemonRunner/DaemonRunner/NetDaemonExtensions.cs @@ -12,6 +12,8 @@ using Serilog; using NetDaemon.Infrastructure.Config; using NetDaemon.Common.Exceptions; +using System.Globalization; +using System.Diagnostics.CodeAnalysis; namespace NetDaemon { @@ -21,6 +23,9 @@ public static class NetDaemonExtensions public static IHostBuilder UseNetDaemon(this IHostBuilder hostBuilder) { + _ = hostBuilder ?? + throw new NetDaemonArgumentNullException(nameof(hostBuilder)); + if (File.Exists(HassioConfigPath)) ReadHassioConfig(); @@ -70,13 +75,14 @@ private static bool UseLocalAssemblyLoading() if (string.IsNullOrEmpty(appSource)) return true; - return appSource.EndsWith(".csproj") || appSource.EndsWith(".dll"); + return appSource.EndsWith(".csproj", true, CultureInfo.InvariantCulture) + || appSource.EndsWith(".dll", true, CultureInfo.InvariantCulture); } /// /// Reads the Home Assistant (hassio) configuration file /// - /// + [SuppressMessage("", "CA1031")] private static void ReadHassioConfig() { try @@ -98,7 +104,7 @@ private static void ReadHassioConfig() _ = hassAddOnSettings?.AppSource ?? throw new NetDaemonNullReferenceException("AppSource cannot be null"); - if (hassAddOnSettings.AppSource.StartsWith("/") || hassAddOnSettings.AppSource[1] == ':') + if (hassAddOnSettings.AppSource.StartsWith("/", true, CultureInfo.InvariantCulture) || hassAddOnSettings.AppSource[1] == ':') { // Hard codede path Environment.SetEnvironmentVariable("NETDAEMON__APPSOURCE", hassAddOnSettings.AppSource); diff --git a/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs b/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs index 7075530be..2efa208ab 100644 --- a/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs +++ b/src/DaemonRunner/DaemonRunner/Service/API/WsHandler.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Net.WebSockets; @@ -42,6 +43,10 @@ public ApiWebsocketMiddleware( NetDaemonHost? host = null ) { + _ = netDaemonSettings ?? + throw new NetDaemonArgumentNullException(nameof(netDaemonSettings)); + _ = homeAssistantSettings ?? + throw new NetDaemonArgumentNullException(nameof(homeAssistantSettings)); _logger = loggerFactory.CreateLogger(); _host = host; _netdaemonSettings = netDaemonSettings.Value; @@ -67,11 +72,15 @@ private async Task NewEvent(ExternalEventBase ev) LastErrorMessage = n.IsEnabled ? n.RuntimeInfo.LastErrorMessage : null }) }; - await BroadCast(JsonSerializer.Serialize(eventMessage, _jsonOptions)).ConfigureAwait(false); + await BroadCast(JsonSerializer.Serialize(eventMessage, _jsonOptions)).ConfigureAwait(false); } } + + [SuppressMessage("", "CA1031")] public async Task Invoke(HttpContext context) { + _ = context ?? + throw new NetDaemonArgumentNullException(nameof(context)); if (!context.WebSockets.IsWebSocketRequest && context.Request.Path != "/api/ws") { await _next.Invoke(context).ConfigureAwait(false); @@ -115,7 +124,7 @@ public async Task Invoke(HttpContext context) }) }; - await BroadCast(JsonSerializer.Serialize(eventMessage, _jsonOptions)).ConfigureAwait(false); + await BroadCast(JsonSerializer.Serialize(eventMessage, _jsonOptions)).ConfigureAwait(false); break; case "settings": @@ -156,7 +165,7 @@ public async Task Invoke(HttpContext context) } if (command.IsEnabled is not null) { - if (command.IsEnabled ?? false) + if (command.IsEnabled.Value) { _host?.CallService("switch", "turn_on", new { entity_id = $"switch.netdaemon_{msg.App.ToSafeHomeAssistantEntityId()}" }); } diff --git a/src/DaemonRunner/DaemonRunner/Service/ApiService.cs b/src/DaemonRunner/DaemonRunner/Service/ApiService.cs index 56263cf82..0aa37a06d 100644 --- a/src/DaemonRunner/DaemonRunner/Service/ApiService.cs +++ b/src/DaemonRunner/DaemonRunner/Service/ApiService.cs @@ -1,20 +1,15 @@ -using System.Reflection; using JoySoftware.HomeAssistant.Client; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using NetDaemon.Daemon; using NetDaemon.Daemon.Storage; -using NetDaemon.Service; using Microsoft.Extensions.Options; using System.IO; using Microsoft.Extensions.Hosting; using NetDaemon.Common; using System; -using System.Net.WebSockets; -using Microsoft.AspNetCore.WebSockets; using NetDaemon.Service.Api; using NetDaemon.Common.Configuration; @@ -22,7 +17,7 @@ namespace NetDaemon.Service { public class ApiStartup { - private readonly bool _useAdmin = false; + private readonly bool _useAdmin; public ApiStartup(IConfiguration configuration) { @@ -42,7 +37,7 @@ public static void ConfigureServices(IServiceCollection services) Path.Combine( n.GetRequiredService>().Value.GetAppSourceDirectory() , ".storage"))); - services.AddTransient(); + services.AddTransient(); services.AddSingleton(); services.AddHttpClient(); } diff --git a/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs b/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs index ba0ef1c24..149e641ee 100644 --- a/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs +++ b/src/DaemonRunner/DaemonRunner/Service/App/CodeGenerator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using JoySoftware.HomeAssistant.Client; @@ -30,9 +31,6 @@ public static class CodeGenerator ["camera"] = ("Camera", "ICamera"), ["media_player"] = ("MediaPlayer", "IMediaPlayer"), ["automation"] = ("Entity", "IEntity"), - // ["input_boolean"], - // ["remote"], - // ["climate"], }; public static string? GenerateCode(string nameSpace, IEnumerable entities) @@ -41,7 +39,7 @@ public static class CodeGenerator // Add Usings statements code = code.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(typeof(NetDaemonApp).Namespace!))); - code = code.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(typeof(NetDaemon.Common.Fluent.IMediaPlayer).Namespace!))); + code = code.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(typeof(Common.Fluent.IMediaPlayer).Namespace!))); // Add namespace var namespaceDeclaration = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(nameSpace)).NormalizeWhitespace(); @@ -53,9 +51,7 @@ public static class CodeGenerator SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.PartialKeyword)); // Get all available domains, this is used to create the extensionmethods - var domains = GetDomainsFromEntities(entities); - - foreach (var domain in domains) + foreach (var domain in GetDomainsFromEntities(entities)) { if (_FluentApiMapper.ContainsKey(domain)) { @@ -85,10 +81,10 @@ public static class CodeGenerator }}"; var entityClass = CSharpSyntaxTree.ParseText(classDeclaration).GetRoot().ChildNodes().OfType().FirstOrDefault() ?? throw new NetDaemonNullReferenceException($"Parse class {nameof(NetDaemonApp)} failed"); - foreach (var entity in entities.Where(n => n.StartsWith(domain))) + foreach (var entity in entities.Where(n => n.StartsWith(domain, true, CultureInfo.InvariantCulture))) { var (fluent, fluentInterface) = _FluentApiMapper[domain]; - var name = entity[(entity.IndexOf(".") + 1)..]; + var name = entity[(entity.IndexOf(".", StringComparison.InvariantCultureIgnoreCase) + 1)..]; // Quick check to make sure the name is a valid C# identifier. Should really check to make // sure it doesn't collide with a reserved keyword as well. if (!char.IsLetter(name[0]) && (name[0] != '_')) @@ -121,7 +117,7 @@ public static class CodeGenerator code = code.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Linq"))); code = code.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(typeof(NetDaemonApp).Namespace!))); code = code.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(typeof(NetDaemonRxApp).Namespace!))); - code = code.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(typeof(NetDaemon.Common.Fluent.FluentExpandoObject).Namespace!))); + code = code.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(typeof(Common.Fluent.FluentExpandoObject).Namespace!))); // Add namespace var namespaceDeclaration = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(nameSpace)).NormalizeWhitespace(); @@ -183,7 +179,7 @@ public static class CodeGenerator if (s.Service is null) continue; - var name = s.Service[(s.Service.IndexOf(".") + 1)..]; + var name = s.Service[(s.Service.IndexOf(".", StringComparison.InvariantCultureIgnoreCase) + 1)..]; if (Array.IndexOf(skipServices, name) >= 0) continue; @@ -236,9 +232,9 @@ public static class CodeGenerator }}"; var entityClass = CSharpSyntaxTree.ParseText(classDeclaration).GetRoot().ChildNodes().OfType().FirstOrDefault() ?? throw new NetDaemonNullReferenceException("Failed to parse entity class"); - foreach (var entity in entities.Where(n => n.StartsWith(domain))) + foreach (var entity in entities.Where(n => n.StartsWith(domain, StringComparison.InvariantCultureIgnoreCase))) { - var name = entity[(entity.IndexOf(".") + 1)..]; + var name = entity[(entity.IndexOf(".", StringComparison.InvariantCultureIgnoreCase) + 1)..]; // Quick check to make sure the name is a valid C# identifier. Should really check to make // sure it doesn't collide with a reserved keyword as well. if (!char.IsLetter(name[0]) && (name[0] != '_')) @@ -264,6 +260,6 @@ public static class CodeGenerator /// /// A list of entities internal static IEnumerable GetDomainsFromEntities(IEnumerable entities) => - entities.Select(n => n[0..n.IndexOf(".")]).Distinct(); + entities.Select(n => n[0..n.IndexOf(".", StringComparison.InvariantCultureIgnoreCase)]).Distinct(); } } \ No newline at end of file diff --git a/src/DaemonRunner/DaemonRunner/Service/App/DaemonAppCompiler.cs b/src/DaemonRunner/DaemonRunner/Service/App/DaemonAppCompiler.cs index 2cdb5d7da..175a27731 100644 --- a/src/DaemonRunner/DaemonRunner/Service/App/DaemonAppCompiler.cs +++ b/src/DaemonRunner/DaemonRunner/Service/App/DaemonAppCompiler.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Reflection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NetDaemon.Common; using NetDaemon.Common.Configuration; +using NetDaemon.Common.Exceptions; using NetDaemon.Infrastructure.Extensions; namespace NetDaemon.Service.App @@ -15,9 +15,11 @@ public class DaemonAppCompiler : IDaemonAppCompiler { private readonly ILogger _logger; - private readonly string? _sourceFolder = null; + private readonly string? _sourceFolder; public DaemonAppCompiler(ILogger logger, IOptions netDaemonSettings) { + _ = netDaemonSettings ?? + throw new NetDaemonArgumentNullException(nameof(netDaemonSettings)); _logger = logger; NetDaemonSettings = netDaemonSettings; _sourceFolder = netDaemonSettings.Value.GetAppSourceDirectory(); diff --git a/src/DaemonRunner/DaemonRunner/Service/App/DaemonCompiler.cs b/src/DaemonRunner/DaemonRunner/Service/App/DaemonCompiler.cs index b2d3ae958..df7180e85 100644 --- a/src/DaemonRunner/DaemonRunner/Service/App/DaemonCompiler.cs +++ b/src/DaemonRunner/DaemonRunner/Service/App/DaemonCompiler.cs @@ -27,7 +27,7 @@ public CollectibleAssemblyLoadContext() : base(isCollectible: true) { } - protected override Assembly? Load(AssemblyName _) => null; + protected override Assembly? Load(AssemblyName assemblyName) => null; } /// @@ -108,16 +108,12 @@ private static List LoadSyntaxTree(string codeFolder) var result = new List(50); // Get the paths for all .cs files recursively in app folder - var csFiles = Directory.EnumerateFiles(codeFolder, "*.cs", SearchOption.AllDirectories); - - foreach (var csFile in csFiles) - { - using var fs = new FileStream(csFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - var sourceText = SourceText.From(fs, encoding: Encoding.UTF8, canBeEmbedded: true); - var syntaxTree = SyntaxFactory.ParseSyntaxTree(sourceText, path: csFile); - result.Add(syntaxTree); - } - + IEnumerable? csFiles = Directory.EnumerateFiles(codeFolder, "*.cs", SearchOption.AllDirectories); + result.AddRange(from csFile in csFiles + let fs = new FileStream(csFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite) + let sourceText = SourceText.From(fs, encoding: Encoding.UTF8, canBeEmbedded: true) + let syntaxTree = SyntaxFactory.ParseSyntaxTree(sourceText, path: csFile) + select syntaxTree); return result; } @@ -129,7 +125,7 @@ public static IEnumerable GetDefaultReferences() MetadataReference.CreateFromFile(typeof(System.Text.RegularExpressions.Regex).Assembly.Location), MetadataReference.CreateFromFile(typeof(Console).Assembly.Location), MetadataReference.CreateFromFile(typeof(System.ComponentModel.DataAnnotations.DisplayAttribute).Assembly.Location), - MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location), MetadataReference.CreateFromFile(typeof(System.ComponentModel.INotifyPropertyChanged).Assembly.Location), MetadataReference.CreateFromFile(typeof(System.Linq.Expressions.DynamicExpression).Assembly.Location), MetadataReference.CreateFromFile(typeof(Microsoft.Extensions.Logging.Abstractions.NullLogger).Assembly.Location), @@ -143,8 +139,7 @@ public static IEnumerable GetDefaultReferences() MetadataReference.CreateFromFile(typeof(System.Reactive.Linq.Observable).Assembly.Location), }; - var assembliesFromCurrentAppDomain = AppDomain.CurrentDomain.GetAssemblies(); - foreach (var assembly in assembliesFromCurrentAppDomain) + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { if (!assembly.IsDynamic && !string.IsNullOrEmpty(assembly.Location)) metaDataReference.Add(MetadataReference.CreateFromFile(assembly.Location)); @@ -209,9 +204,7 @@ private static void InterceptAppInfo(SyntaxTree syntaxTree, CSharpCompilation co { var semModel = compilation.GetSemanticModel(syntaxTree); - var classDeclarationExpressions = syntaxTree.GetRoot().DescendantNodes().OfType(); - - foreach (var classDeclaration in classDeclarationExpressions) + foreach (var classDeclaration in syntaxTree.GetRoot().DescendantNodes().OfType()) { var symbol = semModel?.GetDeclaredSymbol(classDeclaration); @@ -274,8 +267,8 @@ private static void WarnIfExecuteIsMissing(SyntaxTree syntaxTree, CSharpCompilat if (symbol is null) continue; - if (string.IsNullOrEmpty(symbol?.Name) || - !ExecuteWarningOnInvocationNames.Contains(symbol?.Name)) + if (string.IsNullOrEmpty(symbol.Name) || + !ExecuteWarningOnInvocationNames.Contains(symbol.Name)) { // The invocation name is empty or not in list of invocations // that needs to be closed with Execute or ExecuteAsync @@ -285,7 +278,7 @@ private static void WarnIfExecuteIsMissing(SyntaxTree syntaxTree, CSharpCompilat // Now find top invocation to match whole expression InvocationExpressionSyntax topInvocationExpression = invocationExpression; - if (symbol is not null && symbol.ContainingType.Name == "NetDaemonApp") + if (symbol.ContainingType.Name == "NetDaemonApp") { var disableLogging = false; @@ -295,12 +288,9 @@ private static void WarnIfExecuteIsMissing(SyntaxTree syntaxTree, CSharpCompilat while (parentInvocationExpression is not null) { - if (parentInvocationExpression is MethodDeclarationSyntax methodDeclarationSyntax) + if (parentInvocationExpression is MethodDeclarationSyntax methodDeclarationSyntax && ExpressionContainsDisableLogging(methodDeclarationSyntax)) { - if (ExpressionContainsDisableLogging(methodDeclarationSyntax)) - { - disableLogging = true; - } + disableLogging = true; } if (parentInvocationExpression is InvocationExpressionSyntax invocationExpressionSyntax) { @@ -330,7 +320,8 @@ private static void WarnIfExecuteIsMissing(SyntaxTree syntaxTree, CSharpCompilat private static bool ExpressionContainsDisableLogging(MethodDeclarationSyntax methodInvocationExpression) { var invocationString = methodInvocationExpression.ToFullString(); - return invocationString.Contains("[DisableLog") && invocationString.Contains("SupressLogType.MissingExecute"); + return invocationString.Contains("[DisableLog", StringComparison.InvariantCultureIgnoreCase) + && invocationString.Contains("SupressLogType.MissingExecute", StringComparison.InvariantCultureIgnoreCase); } // Todo: Refactor using something smarter than string match. In the future use Roslyn @@ -338,7 +329,8 @@ private static bool ExpressionContainsExecuteInvocations(InvocationExpressionSyn { var invocationString = invocation.ToFullString(); - return invocationString.Contains("ExecuteAsync()") || invocationString.Contains("Execute()"); + return invocationString.Contains("ExecuteAsync()", StringComparison.InvariantCultureIgnoreCase) + || invocationString.Contains("Execute()", StringComparison.InvariantCultureIgnoreCase); } } } \ No newline at end of file diff --git a/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs b/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs index c32fd3ae7..6b17fb3fc 100644 --- a/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs +++ b/src/DaemonRunner/DaemonRunner/Service/RunnerService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Threading; @@ -36,9 +37,9 @@ public class RunnerService : BackgroundService private bool _entitiesGenerated; private IEnumerable? _loadedDaemonApps; - private string? _sourcePath = null; + private string? _sourcePath; - private bool _hasConnectedBefore = false; + private bool _hasConnectedBefore; public RunnerService( ILoggerFactory loggerFactory, @@ -49,6 +50,10 @@ public RunnerService( IDaemonAppCompiler daemonAppCompiler ) { + _ = homeAssistantSettings ?? + throw new NetDaemonArgumentNullException(nameof(homeAssistantSettings)); + _ = netDaemonSettings ?? + throw new NetDaemonArgumentNullException(nameof(netDaemonSettings)); _logger = loggerFactory.CreateLogger(); _homeAssistantSettings = homeAssistantSettings.Value; _netDaemonSettings = netDaemonSettings.Value; @@ -63,6 +68,7 @@ public override async Task StopAsync(CancellationToken cancellationToken) await base.StopAsync(cancellationToken).ConfigureAwait(false); } + [SuppressMessage("", "CA1031")] protected override async Task ExecuteAsync(CancellationToken stoppingToken) { try @@ -105,6 +111,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) _logger.LogInformation("NetDaemon service exited!"); } + [SuppressMessage("", "CA1031")] private async Task Run(NetDaemonHost daemonHost, CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) diff --git a/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs b/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs index 0747678cc..48439367b 100644 --- a/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs +++ b/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs @@ -18,7 +18,7 @@ namespace NetDaemon.Daemon.Fakes /// /// Base class for test classes /// - public partial class DaemonHostTestBase : IAsyncLifetime + public class DaemonHostTestBase : IAsyncLifetime { private Task? _fakeConnectedDaemon; @@ -77,8 +77,7 @@ public Task DisposeAsync() /// The object to turn into dynamic public static dynamic GetDynamicDataObject(string testData = "testdata") { - var expandoObject = new ExpandoObject(); - dynamic dynamicData = expandoObject; + dynamic dynamicData = new ExpandoObject(); dynamicData.Test = testData; return dynamicData; } @@ -123,14 +122,12 @@ public void SetEntityState(HassState state) /// Area of entity public void SetEntityState(string entityId, dynamic? state = null, string? area = null) { - var entity = new EntityState + DefaultDaemonHost.InternalState[entityId] = new EntityState { EntityId = entityId, Area = area, State = state }; - - DefaultDaemonHost.InternalState[entityId] = entity; } /// @@ -355,7 +352,7 @@ public void VerifySetStateTimes(string entityId, Times times) /// /// Timeout (ms) of how long fake daemon will stay connected and process events /// True if running debug mode should not cancel on timeout - protected async Task InitializeFakeDaemon(short timeout = 300, bool overrideDebugNotCancel = false) + protected async Task InitializeFakeDaemon(short timeout = 50, bool overrideDebugNotCancel = false) { _fakeConnectedDaemon = await GetConnectedNetDaemonTask(timeout, overrideDebugNotCancel).ConfigureAwait(false); } @@ -363,7 +360,6 @@ protected async Task InitializeFakeDaemon(short timeout = 300, bool overrideDebu /// /// Runs the fake daemon service until timed out /// - /// protected async Task RunFakeDaemonUntilTimeout() { _ = _fakeConnectedDaemon ?? @@ -377,7 +373,6 @@ protected async Task RunFakeDaemonUntilTimeout() /// /// Timeout in milliseconds /// True to use timeout while debugging - /// private async Task GetConnectedNetDaemonTask(short milliSeconds = 100, bool overrideDebugNotCancel = false) { var cancelSource = Debugger.IsAttached && !overrideDebugNotCancel diff --git a/tests/NetDaemon.Daemon.Tests/FakesTests/FakeTests.cs b/tests/NetDaemon.Daemon.Tests/FakesTests/FakeTests.cs index 908177974..557581086 100644 --- a/tests/NetDaemon.Daemon.Tests/FakesTests/FakeTests.cs +++ b/tests/NetDaemon.Daemon.Tests/FakesTests/FakeTests.cs @@ -357,7 +357,7 @@ public async Task UsingEntityNewEventShouldNotCallFunction() [Fact] public async Task WhenStateStaysSameForTimeItShouldCallFunction() { - await InitializeFakeDaemon().ConfigureAwait(false); + await InitializeFakeDaemon(100).ConfigureAwait(false); bool isRun = false; using var ctx = DefaultDaemonRxApp.StateChanges From 84f36d6eb1062ae83a48d40930c45e17c2ae5afd Mon Sep 17 00:00:00 2001 From: helto4real Date: Sun, 27 Dec 2020 00:04:46 +0100 Subject: [PATCH 08/10] More love --- .../NetDaemon.Fakes/DaemonHostTestBase.cs | 17 ++++--- src/Fakes/NetDaemon.Fakes/HassClientMock.cs | 27 +++++----- src/Fakes/NetDaemon.Fakes/HttpHandlerMock.cs | 49 ++++++++++++++++++- .../NetDaemon.Fakes/NetDaemon.Fakes.csproj | 6 +++ src/Service/Program.cs | 1 + src/Service/Service.csproj | 6 +++ .../DaemonRunner/App/AssemblyDaemonApps.cs | 10 ++-- .../CompileErrors/AssemblyDaemonApps.cs | 2 + 8 files changed, 93 insertions(+), 25 deletions(-) diff --git a/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs b/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs index 48439367b..ee3032e9d 100644 --- a/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs +++ b/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs @@ -21,6 +21,7 @@ namespace NetDaemon.Daemon.Fakes public class DaemonHostTestBase : IAsyncLifetime { private Task? _fakeConnectedDaemon; + private CancellationTokenSource? _cancelSource; /// /// Default contructor @@ -68,6 +69,7 @@ public DaemonHostTestBase() /// public Task DisposeAsync() { + _cancelSource?.Dispose(); return Task.CompletedTask; } @@ -111,6 +113,8 @@ public virtual Task InitializeAsync() /// The state to sett public void SetEntityState(HassState state) { + _ = state ?? + throw new NetDaemonArgumentNullException(nameof(state)); DefaultHassClientMock.FakeStates[state.EntityId] = state; } @@ -136,6 +140,8 @@ public void SetEntityState(string entityId, dynamic? state = null, string? area /// The instance of the app to add public async Task AddAppInstance(INetDaemonAppBase app) { + _ = app ?? + throw new NetDaemonArgumentNullException(nameof(app)); if (string.IsNullOrEmpty(app.Id)) app.Id = Guid.NewGuid().ToString(); DefaultDaemonHost.InternalAllAppInstances[app.Id] = app; @@ -241,7 +247,7 @@ public void VerifyCallServiceTuple(string domain, string service, /// Data sent by service /// If service was waiting for response /// Number of times called - public void VerifyCallService(string domain, string service, object? data = null, bool waitForResponse = false, Moq.Times? times = null) + public void VerifyCallService(string domain, string service, object? data = null, bool waitForResponse = false, Times? times = null) { DefaultHassClientMock.VerifyCallService(domain, service, data, waitForResponse, times); } @@ -255,7 +261,7 @@ public void VerifyCallService(string domain, string service, object? data = null /// If service was waiting for response /// Number of times called /// Attributes to verify - public void VerifyCallService(string domain, string service, string entityId, bool waitForResponse = false, Moq.Times? times = null, + public void VerifyCallService(string domain, string service, string entityId, bool waitForResponse = false, Times? times = null, params (string attribute, object value)[] attributesTuples) { var serviceObject = new FluentExpandoObject @@ -295,7 +301,6 @@ public void VerifyCallServiceTimes(string service, Times times) /// Wait for task until canceled /// /// Task to wait for - /// private static async Task WaitUntilCanceled(Task task) { try @@ -375,14 +380,14 @@ protected async Task RunFakeDaemonUntilTimeout() /// True to use timeout while debugging private async Task GetConnectedNetDaemonTask(short milliSeconds = 100, bool overrideDebugNotCancel = false) { - var cancelSource = Debugger.IsAttached && !overrideDebugNotCancel + _cancelSource = Debugger.IsAttached && !overrideDebugNotCancel ? new CancellationTokenSource() : new CancellationTokenSource(milliSeconds); await InitApps().ConfigureAwait(false); - var daemonTask = DefaultDaemonHost.Run("host", 8123, false, "token", cancelSource.Token); - await WaitForDefaultDaemonToConnect(DefaultDaemonHost, cancelSource.Token).ConfigureAwait(false); + var daemonTask = DefaultDaemonHost.Run("host", 8123, false, "token", _cancelSource.Token); + await WaitForDefaultDaemonToConnect(DefaultDaemonHost, _cancelSource.Token).ConfigureAwait(false); return daemonTask; } diff --git a/src/Fakes/NetDaemon.Fakes/HassClientMock.cs b/src/Fakes/NetDaemon.Fakes/HassClientMock.cs index bb1efdd88..03b3e9c71 100644 --- a/src/Fakes/NetDaemon.Fakes/HassClientMock.cs +++ b/src/Fakes/NetDaemon.Fakes/HassClientMock.cs @@ -21,28 +21,27 @@ public class HassClientMock : Mock /// /// Fake areas in HassClient /// - /// - public HassAreas Areas = new(); + public HassAreas Areas { get; } = new(); /// /// Fake devices in HassClient /// /// - public HassDevices Devices = new(); + public HassDevices Devices { get; } = new(); /// /// Fake entities in HassClient /// - public HassEntities Entities = new(); + public HassEntities Entities { get; } = new(); /// /// Fake events in HassClient /// - public ConcurrentQueue FakeEvents = new(); + public ConcurrentQueue FakeEvents { get; } = new(); /// /// All current fake entities and it's states /// - public ConcurrentDictionary FakeStates = new(); + public ConcurrentDictionary FakeStates { get; } = new(); /// /// Default constructor @@ -58,7 +57,7 @@ public HassClientMock() SetupGet(x => x.States).Returns(FakeStates); Setup(x => x.GetAllStates(It.IsAny())) - .ReturnsAsync(() => (IEnumerable)FakeStates.Values); + .ReturnsAsync(() => FakeStates.Values); Setup(x => x.ReadEventAsync()) .ReturnsAsync(() => FakeEvents.TryDequeue(out var ev) ? ev : null); @@ -257,6 +256,10 @@ public void AddCustomEvent(string eventType, dynamic? data) /// NetDaemon state instance public static void AssertEqual(HassState hassState, EntityState entity) { + _ = hassState ?? + throw new NetDaemonArgumentNullException(nameof(hassState)); + _ = entity ?? + throw new NetDaemonArgumentNullException(nameof(entity)); Assert.Equal(hassState.EntityId, entity.EntityId); Assert.Equal(hassState.State, entity.State); Assert.Equal(hassState.LastChanged, entity.LastChanged); @@ -282,7 +285,7 @@ public static void AssertEqual(HassState hassState, EntityState entity) /// public static CancellationTokenSource GetSourceWithTimeout(int milliSeconds = 100) { - return (Debugger.IsAttached) + return Debugger.IsAttached ? new CancellationTokenSource() : new CancellationTokenSource(milliSeconds); } @@ -298,7 +301,7 @@ public void VerifyCallServiceTuple(string domain, string service, { var attributes = new FluentExpandoObject(); foreach (var (attribute, value) in attributesTuples) - ((IDictionary)attributes)[attribute] = value; + attributes[attribute] = value; Verify(n => n.CallService(domain, service, attributes, It.IsAny()), Times.AtLeastOnce); } @@ -311,7 +314,7 @@ public void VerifyCallServiceTuple(string domain, string service, /// Data sent by service /// If service was waiting for response /// Number of times called - public void VerifyCallService(string domain, string service, object? data = null, bool waitForResponse = false, Moq.Times? times = null) + public void VerifyCallService(string domain, string service, object? data = null, bool waitForResponse = false, Times? times = null) { if (times is not null) Verify(n => n.CallService(domain, service, data!, waitForResponse), times.Value); @@ -326,7 +329,7 @@ public void VerifyCallService(string domain, string service, object? data = null /// Service to verify /// If service was waiting for response /// Number of times called - public void VerifyCallService(string domain, string service, bool waitForResponse = false, Moq.Times? times = null) + public void VerifyCallService(string domain, string service, bool waitForResponse = false, Times? times = null) { if (times is not null) Verify(n => n.CallService(domain, service, It.IsAny(), waitForResponse), times.Value); @@ -355,7 +358,7 @@ public void VerifySetState(string entity, string state, { var attributes = new FluentExpandoObject(); foreach (var (attribute, value) in attributesTuples) - ((IDictionary)attributes)[attribute] = value; + attributes[attribute] = value; Verify(n => n.SetState(entity, state, attributes), Times.AtLeastOnce); } diff --git a/src/Fakes/NetDaemon.Fakes/HttpHandlerMock.cs b/src/Fakes/NetDaemon.Fakes/HttpHandlerMock.cs index f286ad0b4..6a884070a 100644 --- a/src/Fakes/NetDaemon.Fakes/HttpHandlerMock.cs +++ b/src/Fakes/NetDaemon.Fakes/HttpHandlerMock.cs @@ -5,13 +5,14 @@ using System.Threading; using System.Threading.Tasks; using NetDaemon.Common; +using System; namespace NetDaemon.Daemon.Fakes { /// /// HttpClient Mock /// - public class HttpClientFactoryMock : Mock + public class HttpClientFactoryMock : Mock, IDisposable { private HttpClient? _httpClient; private MockHttpMessageHandler? _handler; @@ -38,12 +39,34 @@ public void SetResponse(string response, HttpStatusCode statusCode = HttpStatusC _httpClient = new HttpClient(_handler); Setup(x => x.CreateClient(It.IsAny())).Returns(_httpClient!); } + + /// + /// Dispose + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes the object and cancel delay + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Make sure any subscriptions are canceled + _httpClient?.Dispose(); + _handler?.Dispose(); + } + } } /// /// Mock of HttpHandler /// - public class HttpHandlerMock : Mock + public class HttpHandlerMock : Mock, IDisposable { private HttpClient? _httpClient; private MockHttpMessageHandler? _handler; @@ -71,6 +94,28 @@ public void SetResponse(string response, HttpStatusCode statusCode = HttpStatusC _httpClient = new HttpClient(_handler); Setup(x => x.CreateHttpClient(It.IsAny())).Returns(_httpClient!); } + + /// + /// Dispose + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes the object and cancel delay + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Make sure any subscriptions are canceled + _handler?.Dispose(); + _httpClient?.Dispose(); + } + } } /// diff --git a/src/Fakes/NetDaemon.Fakes/NetDaemon.Fakes.csproj b/src/Fakes/NetDaemon.Fakes/NetDaemon.Fakes.csproj index 7b3307b0c..48ae107c0 100644 --- a/src/Fakes/NetDaemon.Fakes/NetDaemon.Fakes.csproj +++ b/src/Fakes/NetDaemon.Fakes/NetDaemon.Fakes.csproj @@ -34,8 +34,14 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + ..\..\..\.linting\roslynator.ruleset + true + AllEnabledByDefault diff --git a/src/Service/Program.cs b/src/Service/Program.cs index f0aef6c46..855a3f100 100644 --- a/src/Service/Program.cs +++ b/src/Service/Program.cs @@ -14,6 +14,7 @@ await Host.CreateDefaultBuilder(args) catch (Exception e) { Console.WriteLine($"Failed to start host... {e}"); + throw; } finally { diff --git a/src/Service/Service.csproj b/src/Service/Service.csproj index 319b3d82c..6fd8a3804 100644 --- a/src/Service/Service.csproj +++ b/src/Service/Service.csproj @@ -9,6 +9,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + @@ -26,5 +30,7 @@ ..\..\.linting\roslynator.ruleset + true + AllEnabledByDefault diff --git a/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/AssemblyDaemonApps.cs b/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/AssemblyDaemonApps.cs index 48e9458b6..b0ea544ed 100644 --- a/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/AssemblyDaemonApps.cs +++ b/tests/NetDaemon.Daemon.Tests/DaemonRunner/App/AssemblyDaemonApps.cs @@ -7,13 +7,13 @@ namespace NetDaemon.Daemon.Tests.DaemonRunner.App /// /// Greets (or insults) people when coming home :) /// - public class AssemblyDaemonApp : NetDaemon.Common.NetDaemonApp + public class AssemblyDaemonApp : Common.NetDaemonApp { #region -- Test config -- - public string? StringConfig { get; set; } = null; - public int? IntConfig { get; set; } = null; - public IEnumerable? EnumerableConfig { get; set; } = null; + public string? StringConfig { get; set; } + public int? IntConfig { get; set; } + public IEnumerable? EnumerableConfig { get; set; } #endregion -- Test config -- @@ -28,7 +28,7 @@ public class AssemblyDaemonApp : NetDaemon.Common.NetDaemonApp #endregion -- Test secrets -- // For testing - public bool HandleServiceCallIsCalled { get; set; } = false; + public bool HandleServiceCallIsCalled { get; set; } public override Task InitializeAsync() { diff --git a/tests/NetDaemon.Daemon.Tests/DaemonRunner/FaultyApp/CompileErrors/AssemblyDaemonApps.cs b/tests/NetDaemon.Daemon.Tests/DaemonRunner/FaultyApp/CompileErrors/AssemblyDaemonApps.cs index b166c4194..f963bb85a 100644 --- a/tests/NetDaemon.Daemon.Tests/DaemonRunner/FaultyApp/CompileErrors/AssemblyDaemonApps.cs +++ b/tests/NetDaemon.Daemon.Tests/DaemonRunner/FaultyApp/CompileErrors/AssemblyDaemonApps.cs @@ -5,6 +5,8 @@ /// /// Greets (or insults) people when coming home :) /// +[SuppressMessage("", "CS1002")] +[SuppressMessage("", "CS1038")] public class AssmeblyFaultyCompileErrorDaemonApp : NetDaemonApp { #region -- Test config -- From a69a21d7c9e9a0eef3a91adf880c93784476ad44 Mon Sep 17 00:00:00 2001 From: helto4real Date: Sun, 27 Dec 2020 17:34:37 +0100 Subject: [PATCH 09/10] More cleanup and fix save state over restart --- .../NetDaemon.App/Common/NetDaemonAppBase.cs | 26 +++++++--- .../Common/Reactive/AppDaemonRxApp.cs | 18 +++++-- .../Daemon/IInstanceDaemonApp.cs | 1 - .../NetDaemon.Daemon/Daemon/NetDaemonHost.cs | 50 ++++++++++++++----- .../NetDaemon.Daemon/Daemon/Scheduler.cs | 8 ++- 5 files changed, 80 insertions(+), 23 deletions(-) diff --git a/src/App/NetDaemon.App/Common/NetDaemonAppBase.cs b/src/App/NetDaemon.App/Common/NetDaemonAppBase.cs index ef8171942..7d801d038 100644 --- a/src/App/NetDaemon.App/Common/NetDaemonAppBase.cs +++ b/src/App/NetDaemon.App/Common/NetDaemonAppBase.cs @@ -35,9 +35,19 @@ public abstract class NetDaemonAppBase : INetDaemonAppBase private readonly Channel _updateRuntimeInfoChannel = Channel.CreateBounded(5); - private readonly CancellationTokenSource _cancelSource = new(); + private readonly CancellationTokenSource _cancelSource; + private bool _isDisposed; private Task? _lazyStoreStateTask; + /// + /// Constructor + /// + protected NetDaemonAppBase() + { + _cancelSource = new(); + _isDisposed = false; + } + /// /// Dependencies on other applications that will be initialized before this app /// @@ -123,7 +133,7 @@ public async Task RestoreAppStateAsync() { _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); - var obj = await Daemon!.GetDataAsync>(GetUniqueIdForStorage()).ConfigureAwait(false); + var obj = await Daemon.GetDataAsync>(GetUniqueIdForStorage()).ConfigureAwait(false); if (obj != null) { @@ -143,7 +153,6 @@ public async Task RestoreAppStateAsync() dynamic serviceData = new FluentExpandoObject(); serviceData.entity_id = EntityId; await Daemon.SetStateAsync(EntityId, "off").ConfigureAwait(false); - await Daemon.CallServiceAsync("switch", "turn_off", serviceData).ConfigureAwait(false); } return; } @@ -155,7 +164,6 @@ public async Task RestoreAppStateAsync() dynamic serviceData = new FluentExpandoObject(); serviceData.entity_id = EntityId; await Daemon.SetStateAsync(EntityId, "on").ConfigureAwait(false); - await Daemon.CallServiceAsync("switch", "turn_on", serviceData).ConfigureAwait(false); } return; } @@ -189,7 +197,7 @@ public void SaveAppState() /// public void Speak(string entityId, string message) { - _ = Daemon as INetDaemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); + _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!"); Daemon!.Speak(entityId, message); } @@ -260,13 +268,19 @@ private async Task HandleLazyStorage() /// public async virtual ValueTask DisposeAsync() { + lock (_cancelSource) + { + if (_isDisposed) + return; + _isDisposed = true; + } _cancelSource.Cancel(); if (_manageRuntimeInformationUpdatesTask is not null) await _manageRuntimeInformationUpdatesTask.ConfigureAwait(false); DaemonCallBacksForServiceCalls.Clear(); - this.IsEnabled = false; + IsEnabled = false; _lazyStoreStateTask = null; InternalStorageObject = null; _cancelSource.Dispose(); diff --git a/src/App/NetDaemon.App/Common/Reactive/AppDaemonRxApp.cs b/src/App/NetDaemon.App/Common/Reactive/AppDaemonRxApp.cs index 858f73e57..3d3ede231 100644 --- a/src/App/NetDaemon.App/Common/Reactive/AppDaemonRxApp.cs +++ b/src/App/NetDaemon.App/Common/Reactive/AppDaemonRxApp.cs @@ -22,17 +22,20 @@ namespace NetDaemon.Common.Reactive /// public abstract class NetDaemonRxApp : NetDaemonAppBase, INetDaemonReactive { - private readonly CancellationTokenSource _cancelTimers = new(); + private readonly CancellationTokenSource _cancelTimers; private EventObservable? _eventObservables; private StateChangeObservable? _stateObservables; + private bool _isDisposed; /// /// Default constructor /// protected NetDaemonRxApp() { + _cancelTimers = new(); StateAllChanges = new ReactiveState(this); EventChanges = new ReactiveEvent(this); + _isDisposed = false; } /// @@ -70,8 +73,16 @@ public void CallService(string domain, string service, dynamic? data) /// public async override ValueTask DisposeAsync() { + lock (_cancelTimers) + { + if (_isDisposed) + return; + _isDisposed = true; + } + + LogDebug("RxApp {app} is being Disposes", Id!); // To end timers - _cancelTimers.Cancel(); + _cancelTimers?.Cancel(); if (_eventObservables is not null) _eventObservables!.Clear(); @@ -79,8 +90,9 @@ public async override ValueTask DisposeAsync() if (_stateObservables is not null) _stateObservables!.Clear(); + _cancelTimers?.Dispose(); + await base.DisposeAsync().ConfigureAwait(false); - _cancelTimers.Dispose(); LogDebug("RxApp {app} is Disposed", Id!); } diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/IInstanceDaemonApp.cs b/src/Daemon/NetDaemon.Daemon/Daemon/IInstanceDaemonApp.cs index 4c8271453..0de6fc692 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/IInstanceDaemonApp.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/IInstanceDaemonApp.cs @@ -19,7 +19,6 @@ public interface IInstanceDaemonApp /// /// Returns a list of instanced daemonapps /// - /// IEnumerable InstanceDaemonApps(); } } \ No newline at end of file diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs b/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs index ef565a1fe..10d391b6d 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs @@ -80,6 +80,7 @@ public class NetDaemonHost : INetDaemonHost, IAsyncDisposable internal ConcurrentDictionary InternalAllAppInstances { get; } = new(); private readonly Scheduler _scheduler; + private bool _isDisposed; // Following token source and token are set at RUN private CancellationToken _cancelToken; @@ -93,7 +94,6 @@ public class NetDaemonHost : INetDaemonHost, IAsyncDisposable /// Repository to use /// The loggerfactory /// Http handler to use - /// Handles instances of apps public NetDaemonHost( IHassClient? hassClient, IDataRepository? repository, @@ -108,6 +108,7 @@ public NetDaemonHost( ?? throw new ArgumentNullException(nameof(hassClient)); _scheduler = new Scheduler(loggerFactory: loggerFactory); _repository = repository; + _isDisposed = false; Logger.LogTrace("Instance NetDaemonHost"); } @@ -210,11 +211,19 @@ public ICamera Cameras(INetDaemonApp app, Func func) public async ValueTask DisposeAsync() { + lock (_cancelDaemon) + { + if (_isDisposed) + return; + _isDisposed = true; + } + _cancelDaemon.Cancel(); await Stop().ConfigureAwait(false); - _cancelDaemon.Dispose(); await _scheduler.DisposeAsync().ConfigureAwait(false); + _cancelDaemon.Dispose(); _cancelTokenSource?.Dispose(); + Logger.LogTrace("Instance NetDaemonHost Disposed"); } @@ -269,8 +278,7 @@ public IEntity Entity(INetDaemonApp app, params string[] entityId) public async Task GetDataAsync(string id) where T : class { _cancelToken.ThrowIfCancellationRequested(); - - _ = _repository as IDataRepository ?? + _ = _repository ?? throw new NetDaemonNullReferenceException($"{nameof(_repository)} can not be null!"); if (DataCache.ContainsKey(id)) @@ -615,6 +623,14 @@ public async Task Stop() await _scheduler.Stop().ConfigureAwait(false); InternalState.Clear(); + InternalAllAppInstances.Clear(); + InternalRunningAppInstances.Clear(); + _hassAreas.Clear(); + _hassDevices.Clear(); + _hassEntities.Clear(); + _daemonServiceCallFunctions.Clear(); + _externalEventCallSubscribers.Clear(); + _eventHandlerTasks.Clear(); Connected = false; await _hassClient.CloseAsync().ConfigureAwait(false); @@ -628,11 +644,20 @@ public async Task Stop() } /// + [SuppressMessage("", "1031")] public async Task UnloadAllApps() { + Logger.LogTrace("Unloading all apps ({instances}, {running})", InternalAllAppInstances.Count, InternalRunningAppInstances.Count); foreach (var app in InternalAllAppInstances) { - await app.Value.DisposeAsync().ConfigureAwait(false); + try + { + await app.Value.DisposeAsync().ConfigureAwait(false); + } + catch (Exception e) + { + Logger.LogError(e, "Failed to unload apps, {app_id}", app.Value.Id); + } } InternalAllAppInstances.Clear(); InternalRunningAppInstances.Clear(); @@ -1214,7 +1239,7 @@ private async Task HandleTextToSpeechMessages(CancellationToken cancellationToke private async Task LoadAllApps() { _ = _appInstanceManager ?? throw new NetDaemonNullReferenceException(nameof(_appInstanceManager)); - + Logger.LogTrace("Loading all apps ({instances}, {running})", InternalAllAppInstances.Count, InternalRunningAppInstances.Count); // First unload any apps running await UnloadAllApps().ConfigureAwait(false); @@ -1222,7 +1247,10 @@ private async Task LoadAllApps() var instancedApps = _appInstanceManager.InstanceDaemonApps(); if (!InternalRunningAppInstances.IsEmpty) - throw new NetDaemonException("Did not expect running instances!"); + { + Logger.LogWarning("Old apps not unloaded correctly. {nr} apps still loaded.", InternalRunningAppInstances.Count); + InternalRunningAppInstances.Clear(); + } foreach (INetDaemonAppBase appInstance in instancedApps!) { @@ -1278,7 +1306,7 @@ private void RegisterAppSwitchesAndTheirStates() else await SetStateOnDaemonAppSwitch("on", data).ConfigureAwait(false); } - catch (System.Exception e) + catch (Exception e) { Logger.LogWarning(e, "Failed to set state from netdaemon switch"); } @@ -1309,9 +1337,7 @@ private async Task SetDependentState(string entityId, string state) if (state == "off") { // We need to turn off any dependent apps - var depApps = InternalAllAppInstances.Values.Where(n => n.Dependencies.Contains(app.Id)); - - foreach (var depApp in depApps) + foreach (var depApp in InternalAllAppInstances.Values.Where(n => n.Dependencies.Contains(app.Id))) { await SetDependentState(depApp.EntityId, state).ConfigureAwait(false); } @@ -1356,7 +1382,7 @@ private async Task PersistAppStateAsync(NetDaemonAppBase app) new Dictionary(); obj["__IsDisabled"] = !app.IsEnabled; - await SaveDataAsync>(app.GetUniqueIdForStorage(), obj).ConfigureAwait(false); + await SaveDataAsync(app.GetUniqueIdForStorage(), obj).ConfigureAwait(false); } [SuppressMessage("", "CA1031")] diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs index 2e42a12c4..049c2f05e 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs @@ -31,7 +31,7 @@ public class Scheduler : IScheduler /// Used to cancel all running tasks /// private CancellationTokenSource _cancelSource = new(); - + private bool _isStopped; private readonly ConcurrentDictionary _scheduledTasks = new(); @@ -274,6 +274,11 @@ private async Task InternalRunInAsync(TimeSpan timeSpan, Func func, Cancel /// public async Task Stop() { + if (_isStopped) + return; + + _isStopped = true; + _cancelSource.Cancel(); // Make sure we are waiting for the scheduler task as well @@ -298,6 +303,7 @@ public async Task Stop() public async Task Restart() { await Stop().ConfigureAwait(false); + _isStopped = false; _cancelSource = new CancellationTokenSource(); } From b35fa53c257ca6b7361cbde421a3a8d2900c8074 Mon Sep 17 00:00:00 2001 From: helto4real Date: Sun, 27 Dec 2020 17:44:58 +0100 Subject: [PATCH 10/10] Adjusted back timeout --- src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs b/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs index ee3032e9d..052bde16c 100644 --- a/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs +++ b/src/Fakes/NetDaemon.Fakes/DaemonHostTestBase.cs @@ -357,7 +357,7 @@ public void VerifySetStateTimes(string entityId, Times times) /// /// Timeout (ms) of how long fake daemon will stay connected and process events /// True if running debug mode should not cancel on timeout - protected async Task InitializeFakeDaemon(short timeout = 50, bool overrideDebugNotCancel = false) + protected async Task InitializeFakeDaemon(short timeout = 300, bool overrideDebugNotCancel = false) { _fakeConnectedDaemon = await GetConnectedNetDaemonTask(timeout, overrideDebugNotCancel).ConfigureAwait(false); }