diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/LocalCollector/ProcessInfoCollectorData.cs b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/LocalCollector/ProcessInfoCollectorData.cs index 0bad39c6e..7e90f414a 100644 --- a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/LocalCollector/ProcessInfoCollectorData.cs +++ b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/LocalCollector/ProcessInfoCollectorData.cs @@ -40,6 +40,12 @@ public void UpdateConnection(ConnectionInfo connection) lock (_locker) { var index = Connections.IndexOf(connection); + if (index == -1) + { + var element = Connections.FirstOrDefault(c => c.Id == connection.Id); + if(element != null) + index = Connections.IndexOf(element); + } if (index >= 0) { Connections[index] = connection; @@ -47,7 +53,7 @@ public void UpdateConnection(ConnectionInfo connection) } } - public void UpdateEnvironmentVariables(IEnumerable> envs) + public void UpdateOrAddEnvironmentVariables(IEnumerable> envs) { foreach (var item in envs) { @@ -55,13 +61,13 @@ public void UpdateEnvironmentVariables(IEnumerable> } } - public void UpdateRegistrations(IEnumerable services) + public void UpdateOrAddRegistrations(IEnumerable services) { UpdateOrAdd(services, Registrations, (item) => reg => reg.ImplementationType == item.ImplementationType && reg.ServiceType == item.ServiceType && reg.LifeTime == item.LifeTime); } - public void UpdateModules(IEnumerable currentModules) + public void UpdateOrAddModules(IEnumerable currentModules) { UpdateOrAdd(currentModules, Modules, (item) => (item2) => item.Name == item2.Name && item.PublicKeyToken == item2.PublicKeyToken); } @@ -90,6 +96,10 @@ private void UpdateOrAdd(IEnumerable source, SynchronizedCollection tar target.Add(item); } } + else + { + target.Add(item); + } } } } diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Abstractions/IProcessInfoAggregator.cs b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Abstractions/IProcessInfoAggregator.cs index d48862436..7b67f905a 100644 --- a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Abstractions/IProcessInfoAggregator.cs +++ b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Abstractions/IProcessInfoAggregator.cs @@ -27,11 +27,14 @@ public interface IProcessInfoAggregator : IDisposable public int MainProcessId { get; } /// - /// Adds a runtime information to the collection. + /// Delay for actually sending terminate request for the UI. /// - /// - /// - Task AddRuntimeInformation(string assemblyId, ProcessInfoCollectorData processInfo); + public int TerminatingProcessDelay { get; } + + /// + /// Controls the initialized subsystems. + /// + public ISubsystemController? SubsystemController { get; } /// /// Removes a module information from the collection. @@ -71,41 +74,6 @@ public interface IProcessInfoAggregator : IDisposable /// void RemoveUiConnection(KeyValuePair handler); - /// - /// Adds or updates the connections in the collection. - /// - /// - /// - Task AddConnectionCollection(string assemblyId, IEnumerable connections); - - /// - /// Updates a connection. - /// - /// - /// - Task UpdateConnectionInfo(string assemblyId, ConnectionInfo connectionInfo); - - /// - /// Updates the environment variables. - /// - /// - /// - Task UpdateEnvironmentVariablesInfo(string assemblyId, IEnumerable> environmentVariables); - - /// - /// Updates the registrations in the collection. - /// - /// - /// - Task UpdateRegistrationInfo(string assemblyId, IEnumerable registrations); - - /// - /// Updates the modules in the collection. - /// - /// - /// - Task UpdateModuleInfo(string assemblyId, IEnumerable modules); - /// /// Enables to watch processes through ProcessMonitor. /// Only available for Windows OS. @@ -124,62 +92,72 @@ public interface IProcessInfoAggregator : IDisposable void InitProcesses(ReadOnlySpan processIds); /// - /// Initializes the subsystems taken from the user defined manifest. + /// Puts the given subsystem into the queue to send subsystem state changed information to the UI's. /// - /// - /// - Task InitializeSubsystems(IEnumerable> subsystems); + /// + /// + void ScheduleSubsystemStateChanged(Guid instanceId, string state); /// - /// Terminates the given subsystems through the user defined ISubsystemLauncher. + /// Returns the initialized runtime information. /// - /// /// - Task ShutdownSubsystems(IEnumerable subsystemIds); + IEnumerable> GetRuntimeInformation(); /// - /// Restarts the given subsystems through the user defined ISubsystemLauncher. + /// Returns the connected clients. /// - /// /// - Task RestartSubsystems(IEnumerable subsystemIds); + IEnumerable> GetUiClients(); /// - /// Launch the given subsystems through the user defined ISubsystemLauncher. + /// Adds a runtime information to the collection. /// - /// - /// - Task LaunchSubsystems(IEnumerable subsystemIds); + /// + /// + Task AddRuntimeInformation(string assemblyId, ProcessInfoCollectorData processInfo); /// - /// Launch the given subsystem through the user defined ISubsystemLauncher and . + /// Adds or updates the connections in the collection. /// - /// - /// - /// - Task LaunchSubsystemWithDelay(Guid id, int periodOfTime); + /// + /// + Task AddConnectionCollection(string assemblyId, IEnumerable connections); /// - /// Modifies a state of a subsystem with the given data. Send update to the registered UIs. + /// Updates a connection. /// - /// - /// - /// - Task ModifySubsystemState(Guid subsystemId, string state); + /// + /// + Task UpdateOrAddConnectionInfo(string assemblyId, ConnectionInfo connectionInfo); /// - /// Adds processes to watch to the existing watchable process ids list. + /// Updates the environment variables. /// - /// - /// - ValueTask AddProcesses(ReadOnlySpan processIds); + /// + /// + Task UpdateOrAddEnvironmentVariablesInfo(string assemblyId, IEnumerable> environmentVariables); /// - /// Puts the given subsystem into the queue to send subsystem state changed information to the UI's. + /// Adds the registrations to the collection. /// - /// - /// - void ScheduleSubsystemStateChanged(Guid instanceId, string state); + /// + /// + Task UpdateRegistrations(string assemblyId, IEnumerable registrations); + + /// + /// Updates the modules to the collection. + /// + /// + /// + Task UpdateOrAddModuleInfo(string assemblyId, IEnumerable modules); + + /// + /// Adds processes to watch to the existing watchable process ids list. + /// + /// + /// + Task AddProcesses(ReadOnlySpan processIds); /// /// Asynchronusly dequeue the changes of the registered subsystems, and send to the initialized UI's. diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Abstractions/Processes/IProcessInfoManager.cs b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Abstractions/Processes/IProcessInfoManager.cs new file mode 100644 index 000000000..b18c0322f --- /dev/null +++ b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Abstractions/Processes/IProcessInfoManager.cs @@ -0,0 +1,41 @@ +// Morgan Stanley makes this available to you under the Apache License, +// Version 2.0 (the "License"). You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0. +// +// See the NOTICE file distributed with this work for additional information +// regarding copyright ownership. Unless required by applicable law or agreed +// to in writing, software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using ProcessExplorer.Abstractions.Handlers; + +namespace ProcessExplorer.Abstractions.Processes; + +//TODO(Lilla): Add description +public interface IProcessInfoManager : IDisposable +{ + ReadOnlySpan AddChildProcesses(int processId, string? processName); + void AddProcess(int processId); + bool CheckIfIsComposeProcess(int processId); + void ClearProcessIds(); + bool ContainsId(int processId); + float GetCpuUsage(int processId, string processName); + float GetMemoryUsage(int processId, string processName); + int? GetParentId(int processId, string processName); + ReadOnlySpan GetProcessIds(); + void RemoveProcessId(int processId); + void SendNewProcessUpdate(int processId); + void SendProcessModifiedUpdate(int processId); + void SendTerminatedProcessUpdate(int processId); + void SetHandlers( + ProcessModifiedHandler processModifiedHandler, + ProcessTerminatedHandler processTerminatedHandler, + ProcessCreatedHandler processCreatedHandler, + ProcessesModifiedHandler processesModifiedHandler, + ProcessStatusChangedHandler processStatusChangedHandler); + void SetProcessIds(int mainProcessId, ReadOnlySpan processIds); + void WatchProcesses(int mainProcessId); +} diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Abstractions/Processes/ProcessInfoManager.cs b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Abstractions/Processes/ProcessInfoManager.cs index 899729e8d..fe5b6ee16 100644 --- a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Abstractions/Processes/ProcessInfoManager.cs +++ b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Abstractions/Processes/ProcessInfoManager.cs @@ -20,7 +20,7 @@ namespace ProcessExplorer.Abstractions.Processes; -public abstract class ProcessInfoManager : IDisposable //Create an interface +public abstract class ProcessInfoManager : IProcessInfoManager { private ProcessCreatedHandler? _processCreatedHandler; private ProcessModifiedHandler? _processModifiedHandler; @@ -30,8 +30,6 @@ public abstract class ProcessInfoManager : IDisposable //Create an interface private readonly ILogger _logger; private readonly ObservableCollection _processIds = new(); private readonly object _locker = new(); - private int _composePid; - private int _delayTime; public ProcessInfoManager(ILogger? logger) { @@ -39,7 +37,6 @@ public ProcessInfoManager(ILogger? logger) _processIds.CollectionChanged += ProcessIdsChanged; } - private void ProcessIdsChanged(object? sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) @@ -78,11 +75,6 @@ public void SetHandlers( _processesModifiedHandler = processesModifiedHandler; } - public void SetComposePid(int processId) - { - _composePid = processId; - } - public bool ContainsId(int processId) { lock (_locker) @@ -109,7 +101,9 @@ public void RemoveProcessId(int processId) } } - public void SetProcessIds(ReadOnlySpan processIds) + public void SetProcessIds( + int mainProcessId, + ReadOnlySpan processIds) { lock (_locker) { @@ -120,7 +114,8 @@ public void SetProcessIds(ReadOnlySpan processIds) AddChildProcesses(id, Process.GetProcessById(id).ProcessName); } - if(_composePid != 0 && !_processIds.Contains(_composePid)) _processIds.Add(_composePid); + if (mainProcessId != 0 && !_processIds.Contains(mainProcessId)) + _processIds.Add(mainProcessId); } } @@ -162,11 +157,11 @@ public void ClearProcessIds() /// /// Continuously watching created processes. /// - public virtual void WatchProcesses() + public virtual void WatchProcesses(int mainProcessId) { - if(_composePid == 0) return; - AddProcess(_composePid); - AddChildProcesses(_composePid, Process.GetProcessById(_composePid).ProcessName); + if (mainProcessId == 0) return; + AddProcess(mainProcessId); + AddChildProcesses(mainProcessId, Process.GetProcessById(mainProcessId).ProcessName); } /// @@ -179,7 +174,7 @@ private bool IsComposeProcess(int processId) //snapshot if the process has already exited if (!Process.GetProcesses().Any(p => p.Id == processId)) return false; - if (processId == _composePid || ContainsId(processId)) return true; + if (ContainsId(processId)) return true; var parentProcessId = GetParentId(processId, Process.GetProcessById(processId).ProcessName); @@ -229,11 +224,6 @@ public void SendProcessModifiedUpdate(int processId) _processModifiedHandler?.Invoke(processId); } - public void SetDeadProcessRemovalDelay(int delay) - { - _delayTime = delay * 100; - } - ~ProcessInfoManager() { Dispose(); @@ -270,14 +260,10 @@ private bool TryDeleteProcess(int processId) return false; } - private async void RemoveProcessAfterTimeout(int processId) + private void RemoveProcessAfterTimeout(int processId) { - await Task.Run(() => - { - Task.Delay(_delayTime); - var ids = GetProcessIds(); - _processesModifiedHandler?.Invoke(ids); - _processTerminatedHandler?.Invoke(processId); - }); + var ids = GetProcessIds(); + _processesModifiedHandler?.Invoke(ids); + _processTerminatedHandler?.Invoke(processId); } } diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Abstractions/Subsystems/ISubsystemControllerCommunicator.cs b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Abstractions/Subsystems/ISubsystemControllerCommunicator.cs deleted file mode 100644 index 23892a22a..000000000 --- a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Abstractions/Subsystems/ISubsystemControllerCommunicator.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Morgan Stanley makes this available to you under the Apache License, -// Version 2.0 (the "License"). You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0. -// -// See the NOTICE file distributed with this work for additional information -// regarding copyright ownership. Unless required by applicable law or agreed -// to in writing, software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -// or implied. See the License for the specific language governing permissions -// and limitations under the License. - -namespace ProcessExplorer.Abstractions.Subsystems; - -public interface ISubsystemControllerCommunicator -{ - /// - /// Sets the route of the communication to send messages to the ModuleLoader, if the user wants to launch/restart/stop multiple subsystems, - /// or launch a subsystem after a given period of time. - /// - /// - ValueTask InitializeCommunicationRoute(); -} diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Abstractions/Subsystems/SubsystemInfo.cs b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Abstractions/Subsystems/SubsystemInfo.cs index 9348b4f11..66d3a902f 100644 --- a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Abstractions/Subsystems/SubsystemInfo.cs +++ b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Abstractions/Subsystems/SubsystemInfo.cs @@ -26,7 +26,7 @@ public class SubsystemInfo public string? Description { get; set; } public bool AutomatedStart { get; set; } = false; - public static SubsystemInfo FromModule(Module module) => new SubsystemInfo() + public static SubsystemInfo FromModule(Module module) => new() { Name = module.Name, StartupType = module.StartupType, diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/DependencyInjection/ServiceCollectionProcessExplorerExtensions.cs b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/DependencyInjection/ServiceCollectionProcessExplorerExtensions.cs index d2537458f..910b7b577 100644 --- a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/DependencyInjection/ServiceCollectionProcessExplorerExtensions.cs +++ b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/DependencyInjection/ServiceCollectionProcessExplorerExtensions.cs @@ -34,7 +34,9 @@ public static IServiceCollection AddProcessExplorerAggregator( public static IServiceCollection AddProcessMonitorWindows( this IServiceCollection serviceCollection) { - serviceCollection.TryAddSingleton(); +#pragma warning disable CA1416 // Validate platform compatibility + serviceCollection.TryAddSingleton(); +#pragma warning restore CA1416 // Validate platform compatibility return serviceCollection; } diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/Infrastructure/Communicator.cs b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/Infrastructure/Communicator.cs index 5bf282da2..fa2126903 100644 --- a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/Infrastructure/Communicator.cs +++ b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/Infrastructure/Communicator.cs @@ -65,7 +65,7 @@ public async ValueTask UpdateConnectionInformation(IEnumerable _uiClients = new(); - internal readonly object _uiClientLocker = new(); - - public void AddUiConnection(IUIHandler uiHandler) - { - lock (_uiClientLocker) - { - var element = _uiClients.FirstOrDefault(uih => uih == uiHandler); - if (element == null) - { - _uiClients.Add(uiHandler); - } - } - } - - public void RemoveUiConnection(IUIHandler uiHandler) - { - lock (_uiClientLocker) - { - var element = _uiClients - .FirstOrDefault(uih => - uih == uiHandler); - - if (element != null) - { - _uiClients.Remove(uiHandler); - } - } - } -} diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/ProcessInfoAggregator.cs b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/ProcessInfoAggregator.cs index cb31a8b7a..6180906b4 100644 --- a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/ProcessInfoAggregator.cs +++ b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/ProcessInfoAggregator.cs @@ -34,50 +34,31 @@ internal class ProcessInfoAggregator : IProcessInfoAggregator private readonly object _processsInformationLock = new(); //putting subsystem change messages to the queue and remove it if it has been sent ~ FIFO private readonly ConcurrentQueue> _subsystemStateChanges = new(); - private readonly ProcessInfoManager _processInfoManager; - private ISubsystemController? _subsystemController; + private readonly IProcessInfoManager _processInfoManager; private readonly ConcurrentDictionary _uiClients = new(); private readonly object _uiClientLock = new(); private bool _disposed; + public ISubsystemController? SubsystemController { get; private set; } public int MainProcessId { get; private set; } + public int TerminatingProcessDelay { get; private set; } = 1000; public ProcessInfoAggregator( ILogger? logger, - ProcessInfoManager processInfoManager, + IProcessInfoManager processInfoManager, ISubsystemController? subsystemController = null) { _logger = logger ?? NullLogger.Instance; _processInfoManager = processInfoManager; - _subsystemController = subsystemController; + SubsystemController = subsystemController; - if (_subsystemController != null) - _subsystemController.SetUiDelegate(UpdateInfoOnUI); + if (SubsystemController != null) + SubsystemController.SetUiDelegate(UpdateInfoOnUI); SetUiCommunicatorsToWatchProcessChanges(); } - public async Task RunSubsystemStateQueue(CancellationToken cancellationToken) - { - //TODO(Lilla): should i send here a warning if cancellationToken is default? - while (!cancellationToken.IsCancellationRequested) - { - if (_uiClients.IsEmpty || !_subsystemStateChanges.IsEmpty && _subsystemController != null) - { - var succeed = _subsystemStateChanges.TryDequeue(out var subsystemInfo); - - if (succeed) await ModifySubsystemState(subsystemInfo.Key, subsystemInfo.Value); - } - - if (cancellationToken.IsCancellationRequested) - { - // Perform any cleanup actions here - break; - } - } - } - private IEnumerable CreateCopyOfClients() { lock (_uiClientLock) @@ -86,28 +67,30 @@ private IEnumerable CreateCopyOfClients() } } - public async Task AddRuntimeInformation(string assemblyId, ProcessInfoCollectorData runtimeInfo) + private void DisposeCore() { - lock (_processsInformationLock) - { - _processInformation.AddOrUpdate(assemblyId, runtimeInfo, (_, _) => runtimeInfo); - } - - await UpdateInfoOnUI(handler => handler.AddRuntimeInfo(assemblyId, runtimeInfo)); + _processInfoManager.Dispose(); } - public void RemoveRuntimeInformation(string assembly) + private Task UpdateInfoOnUI(Func handlerAction) { - lock (_processsInformationLock) + try { - _processInformation.TryRemove(assembly, out _); + return Task.WhenAll(CreateCopyOfClients().Select(handlerAction)); + } + catch (Exception exception) + { + _logger.UiInformationCannotBeUpdatedError(exception); + return Task.CompletedTask; } } - public void SetMainProcessId(int processId) + private void UpdateProcessInfoCollectorData(string assemblyId, ProcessInfoCollectorData data) { - MainProcessId = processId; - _processInfoManager.SetComposePid(processId); + lock (_processsInformationLock) + { + _processInformation.AddOrUpdate(assemblyId, data, (_, _) => data); + } } private void SetUiCommunicatorsToWatchProcessChanges() @@ -120,11 +103,11 @@ private void SetUiCommunicatorsToWatchProcessChanges() ProcessStatusChanged); } - private IEnumerable GetProcesses(ReadOnlySpan ids) + private IEnumerable GetProcesses(ReadOnlySpan processIds) { var processes = new List(); - foreach (var id in ids) + foreach (var id in processIds) { var process = ProcessInformation.GetProcessInfoWithCalculatedData(Process.GetProcessById(id), _processInfoManager); processes.Add(process.ProcessInfo); @@ -147,10 +130,15 @@ private void ProcessesModified(ReadOnlySpan ids) } } - private void ProcessTerminated(int processId) + private async void ProcessTerminated(int processId) { _logger.ProcessTerminatedInformation(processId); + await Task.Run(() => + { + Task.Delay(TerminatingProcessDelay); + }); + lock (_uiClientLock) { var processes = GetProcesses(_processInfoManager.GetProcessIds()); @@ -167,7 +155,7 @@ private void ProcessCreated(int processId) { var process = GetProcess(processId); - if (process.Equals(default)) return; + if (process == null) return; _logger.ProcessCreatedInformation(processId); @@ -184,7 +172,7 @@ private void ProcessCreated(int processId) { var process = GetProcesses(_processInfoManager.GetProcessIds()).FirstOrDefault(proc => proc.ProcessId == processId); - if (process.Equals(default)) return null; + if (process == null) return null; return process; } @@ -192,7 +180,7 @@ private void ProcessCreated(int processId) private void ProcessModified(int processId) { var process = GetProcess(processId); - if (process.Equals(default)) return; + if (process == null) return; _logger.ProcessModifiedDebug(processId); @@ -216,9 +204,52 @@ private void ProcessStatusChanged(KeyValuePair process) } } + public async Task RunSubsystemStateQueue(CancellationToken cancellationToken) + { + //TODO(Lilla): should i send here a warning if cancellationToken is default? + while (!cancellationToken.IsCancellationRequested) + { + if (_uiClients.IsEmpty || !_subsystemStateChanges.IsEmpty && SubsystemController != null) + { + var succeed = _subsystemStateChanges.TryDequeue(out var subsystemInfo); + + if (succeed && SubsystemController != null && SubsystemController != null) await SubsystemController.ModifySubsystemState(subsystemInfo.Key, subsystemInfo.Value); + } + + if (cancellationToken.IsCancellationRequested) + { + // Perform any cleanup actions here + break; + } + } + } + + public async Task AddRuntimeInformation(string assemblyId, ProcessInfoCollectorData runtimeInfo) + { + lock (_processsInformationLock) + { + _processInformation.AddOrUpdate(assemblyId, runtimeInfo, (_, _) => runtimeInfo); + } + + await UpdateInfoOnUI(handler => handler.AddRuntimeInfo(assemblyId, runtimeInfo)); + } + + public void RemoveRuntimeInformation(string assembly) + { + lock (_processsInformationLock) + { + _processInformation.TryRemove(assembly, out _); + } + } + + public void SetMainProcessId(int processId) + { + MainProcessId = processId; + } + public void SetDeadProcessRemovalDelay(int delay) { - _processInfoManager?.SetDeadProcessRemovalDelay(delay); + TerminatingProcessDelay = delay * 100; } public void AddUiConnection(Guid id, IUIHandler uiHandler) @@ -231,21 +262,21 @@ public void AddUiConnection(Guid id, IUIHandler uiHandler) if (!success) return; var processsIds = _processInfoManager.GetProcessIds(); - if(processsIds.Length > 0) + if (processsIds.Length > 0) { ProcessesModified(processsIds); } lock (_processsInformationLock) { - if(_processInformation.Any()) + if (_processInformation.Any()) uiHandler.AddRuntimeInfo(_processInformation); } - if (_subsystemController != null) + if (SubsystemController != null) { - var subsystems = _subsystemController.GetSubsystems(); - if(subsystems.Any()) + var subsystems = SubsystemController.GetSubsystems(); + if (subsystems.Any()) uiHandler.AddSubsystems(subsystems); } @@ -276,14 +307,6 @@ public void RemoveUiConnection(KeyValuePair handler) return data; } - private void UpdateProcessInfoCollectorData(string assemblyId, ProcessInfoCollectorData data) - { - lock (_processsInformationLock) - { - _processInformation.AddOrUpdate(assemblyId, data, (_, _) => data); - } - } - public async Task AddConnectionCollection(string assemblyId, IEnumerable connections) { var runtimeInfoToModify = GetRuntimeInformation(assemblyId); @@ -303,7 +326,7 @@ public async Task AddConnectionCollection(string assemblyId, IEnumerable handler.AddConnections(assemblyId, connections)); } - public async Task UpdateConnectionInfo(string assemblyId, ConnectionInfo connectionInfo) + public async Task UpdateOrAddConnectionInfo(string assemblyId, ConnectionInfo connectionInfo) { var runtimeInfoToModify = GetRuntimeInformation(assemblyId); @@ -322,7 +345,7 @@ public async Task UpdateConnectionInfo(string assemblyId, ConnectionInfo connect await UpdateInfoOnUI(handler => handler.UpdateConnection(assemblyId, connectionInfo)); } - public async Task UpdateEnvironmentVariablesInfo(string assemblyId, IEnumerable> environmentVariables) + public async Task UpdateOrAddEnvironmentVariablesInfo(string assemblyId, IEnumerable> environmentVariables) { var runtimeInfoToModify = GetRuntimeInformation(assemblyId); @@ -330,7 +353,7 @@ public async Task UpdateEnvironmentVariablesInfo(string assemblyId, IEnumerable< try { - runtimeInfoToModify.UpdateEnvironmentVariables(environmentVariables); + runtimeInfoToModify.UpdateOrAddEnvironmentVariables(environmentVariables); UpdateProcessInfoCollectorData(assemblyId, runtimeInfoToModify); } catch (Exception exception) @@ -341,7 +364,7 @@ public async Task UpdateEnvironmentVariablesInfo(string assemblyId, IEnumerable< await UpdateInfoOnUI(handler => handler.UpdateEnvironmentVariables(assemblyId, environmentVariables)); } - public async Task UpdateRegistrationInfo(string assemblyId, IEnumerable registrations) + public async Task UpdateRegistrations(string assemblyId, IEnumerable registrations) { var runtimeInfoToModify = GetRuntimeInformation(assemblyId); @@ -349,7 +372,7 @@ public async Task UpdateRegistrationInfo(string assemblyId, IEnumerable handler.UpdateRegistrations(assemblyId, registrations)); } - public async Task UpdateModuleInfo(string assemblyId, IEnumerable modules) + public async Task UpdateOrAddModuleInfo(string assemblyId, IEnumerable modules) { var runtimeInfoToModify = GetRuntimeInformation(assemblyId); @@ -368,7 +391,7 @@ public async Task UpdateModuleInfo(string assemblyId, IEnumerable mo try { - runtimeInfoToModify.UpdateModules(modules); + runtimeInfoToModify.UpdateOrAddModules(modules); UpdateProcessInfoCollectorData(assemblyId, runtimeInfoToModify); } catch (Exception exception) @@ -381,7 +404,7 @@ public async Task UpdateModuleInfo(string assemblyId, IEnumerable mo public void EnableWatchingSavedProcesses() { - _processInfoManager.WatchProcesses(); + _processInfoManager.WatchProcesses(MainProcessId); } public void DisableWatchingProcesses() @@ -392,67 +415,29 @@ public void DisableWatchingProcesses() public void InitProcesses(ReadOnlySpan processIds) { _processInfoManager.ClearProcessIds(); - _processInfoManager.SetProcessIds(processIds); - } - - private Task UpdateInfoOnUI(Func handlerAction) - { - try - { - return Task.WhenAll(CreateCopyOfClients().Select(handlerAction)); - } - catch (Exception exception) - { - _logger.UiInformationCannotBeUpdatedError(exception); - return Task.CompletedTask; - } - } - - private void DisposeCore() - { - _processInfoManager.Dispose(); + _processInfoManager.SetProcessIds(MainProcessId, processIds); } - public Task ShutdownSubsystems(IEnumerable subsystemIds) + public void SetSubsystemController(ISubsystemController subsystemController) { - if (_subsystemController == null) return Task.CompletedTask; - return _subsystemController.ShutdownSubsystems(subsystemIds); + SubsystemController = subsystemController; + SubsystemController.SetUiDelegate(UpdateInfoOnUI); } - public Task RestartSubsystems(IEnumerable subsystemIds) + public IEnumerable> GetRuntimeInformation() { - if (_subsystemController == null) return Task.CompletedTask; - return _subsystemController.RestartSubsystems(subsystemIds); - } - - public Task LaunchSubsystems(IEnumerable subsystemIds) - { - if (_subsystemController == null) return Task.CompletedTask; - return _subsystemController.LaunchSubsystems(subsystemIds); - } - - public Task LaunchSubsystemWithDelay(Guid id, int periodOfTime) - { - if (_subsystemController == null) return Task.CompletedTask; - return _subsystemController.LaunchSubsystemAfterTime(id, periodOfTime); - } - - public Task InitializeSubsystems(IEnumerable> subsystems) - { - if (_subsystemController == null) return Task.CompletedTask; - return _subsystemController.InitializeSubsystems(subsystems); - } - - public Task ModifySubsystemState(Guid subsystemId, string state) - { - if (_subsystemController == null) return Task.CompletedTask; - return _subsystemController.ModifySubsystemState(subsystemId, state); + lock (_processsInformationLock) + { + return _processInformation; + } } - public async void SetSubsystemController(ISubsystemController subsystemController) + public IEnumerable> GetUiClients() { - _subsystemController = subsystemController; - _subsystemController.SetUiDelegate(UpdateInfoOnUI); + lock (_uiClientLock) + { + return _uiClients; + } } public void Dispose() @@ -474,9 +459,9 @@ public void ScheduleSubsystemStateChanged(Guid instanceId, string state) _subsystemStateChanges.Enqueue(new(instanceId, state)); } - public ValueTask AddProcesses(ReadOnlySpan processes) + public Task AddProcesses(ReadOnlySpan processes) { - _processInfoManager.SetProcessIds(processes); - return ValueTask.CompletedTask; + _processInfoManager.SetProcessIds(MainProcessId, processes); + return Task.CompletedTask; } } diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/Processes/ProcessInformation.cs b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/Processes/ProcessInformation.cs index 8442aad53..3a0c956e3 100644 --- a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/Processes/ProcessInformation.cs +++ b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/Processes/ProcessInformation.cs @@ -42,14 +42,14 @@ public ProcessInformation(Process process) }; } - internal static ProcessInformation GetProcessInfoWithCalculatedData(Process process, ProcessInfoManager processInfoManager) + internal static ProcessInformation GetProcessInfoWithCalculatedData(Process process, IProcessInfoManager processInfoManager) { var processInformation = new ProcessInformation(process); SetProcessInfoData(ref processInformation, processInfoManager); return processInformation; } - internal static void SetProcessInfoData(ref ProcessInformation processInfo, ProcessInfoManager manager) + internal static void SetProcessInfoData(ref ProcessInformation processInfo, IProcessInfoManager manager) { try { diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/Processes/WindowsProcessInfoManager.cs b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/Processes/WindowsProcessInfoManager.cs index 5a7f44b7a..fdc485c41 100644 --- a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/Processes/WindowsProcessInfoManager.cs +++ b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Core/Processes/WindowsProcessInfoManager.cs @@ -162,9 +162,9 @@ public ReadOnlySpan GetChildProcesses(int id, string processName) return children; } - public override void WatchProcesses() + public override void WatchProcesses(int mainProcessId) { - base.WatchProcesses(); + base.WatchProcesses(mainProcessId); const string wmiQuery = "SELECT TargetInstance.ProcessId " + diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/Abstractions/ProcessExplorerServer.cs b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/Abstractions/ProcessExplorerServer.cs index 6eabad2db..4fe14159b 100644 --- a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/Abstractions/ProcessExplorerServer.cs +++ b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/Abstractions/ProcessExplorerServer.cs @@ -19,7 +19,7 @@ namespace ProcessExplorer.Server.Server.Abstractions; -public abstract class ProcessExplorerServer +internal abstract class ProcessExplorerServer { private readonly ILogger _logger; public int Port { get; } @@ -35,7 +35,7 @@ public ProcessExplorerServer( Port = port; } - internal void SetupProcessExplorer( + public async void SetupProcessExplorer( IOptions options, IProcessInfoAggregator processInfoAggregator) { @@ -58,14 +58,15 @@ internal void SetupProcessExplorer( subsystems.TryAdd(module.Key, SubsystemInfo.FromModule(module.Value)); } - processInfoAggregator.InitializeSubsystems(subsystems); + if (processInfoAggregator != null && processInfoAggregator.SubsystemController != null) + await processInfoAggregator.SubsystemController.InitializeSubsystems(subsystems); } if(options.Value.MainProcessId != null) - processInfoAggregator.SetMainProcessId((int)options.Value.MainProcessId); + processInfoAggregator?.SetMainProcessId((int)options.Value.MainProcessId); if (options.Value.EnableProcessExplorer) - processInfoAggregator.EnableWatchingSavedProcesses(); + processInfoAggregator?.EnableWatchingSavedProcesses(); } catch (Exception exception) { diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/GrpcServer/GrpcListenerService.cs b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/GrpcServer/GrpcListenerService.cs index a59702bb4..a74b8f706 100644 --- a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/GrpcServer/GrpcListenerService.cs +++ b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/GrpcServer/GrpcListenerService.cs @@ -28,11 +28,13 @@ namespace ProcessExplorer.Server.Server.GrpcServer; internal class GrpcListenerService : ProcessExplorerServer, IHostedService { + private readonly CancellationTokenSource _stopTokenSource = new(); + private readonly TaskCompletionSource _startTaskSource = new(TaskCreationOptions.RunContinuationsAsynchronously); + private readonly TaskCompletionSource _stopTaskSource = new(TaskCreationOptions.RunContinuationsAsynchronously); private readonly IProcessInfoAggregator _processInfoAggregator; private readonly ProcessExplorerServerOptions _options; private readonly ILogger _logger; private GRPCServer? _grpcServer; - private CancellationToken _cancellationToken; public GrpcListenerService( IProcessInfoAggregator processInfoAggregator, @@ -63,31 +65,30 @@ public Task StartAsync(CancellationToken cancellationToken) { if (cancellationToken == CancellationToken.None) _logger.GrpcCancellationTokenWarning(); - _cancellationToken = cancellationToken; + Task.Run(() => StartAsyncCore(), _stopTokenSource.Token); + Task.Run(() => _processInfoAggregator.RunSubsystemStateQueue(_stopTokenSource.Token), _stopTokenSource.Token); - var serverTask = Task.Run(() => StartAsyncCore()); - - var queueCts = CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken); - var queueTask = Task.Run(() => _processInfoAggregator.RunSubsystemStateQueue(queueCts.Token), queueCts.Token); - - return Task.WhenAny(serverTask, queueTask); + return _startTaskSource.Task; } private void StartAsyncCore() { _logger.GrpcServerStartedDebug(); + _startTaskSource.SetResult(); SetupGrpcServer(); - if (_grpcServer == null) return; - _grpcServer.Start(); - SetupProcessExplorer(_options, _processInfoAggregator); + if (_grpcServer == null) return; - _cancellationToken.Register(() => + try + { + _grpcServer.Start(); + SetupProcessExplorer(_options, _processInfoAggregator); + } + catch (Exception) { _logger.GrpcServerStoppedDebug(); - _grpcServer.ShutdownAsync().Wait(); - }); + } } private void SetupGrpcServer() @@ -95,14 +96,22 @@ private void SetupGrpcServer() _grpcServer = new GRPCServer { Services = { ProcessExplorerMessageHandler.BindService(new ProcessExplorerMessageHandlerService(_processInfoAggregator, _logger)) }, - Ports = { new ServerPort(Host, Port, ServerCredentials.Insecure) } + Ports = { new ServerPort(Host, Port, ServerCredentials.Insecure) }, }; } public async Task StopAsync(CancellationToken cancellationToken) { + _stopTaskSource.SetResult(); + _stopTokenSource.Cancel(); + if (_grpcServer == null) return; - _processInfoAggregator.Dispose(); + + _processInfoAggregator.Dispose(); + + var shutdown = _grpcServer.ShutdownAsync(); _logger.GrpcServerStoppedDebug(); + + await Task.WhenAll(shutdown, _stopTaskSource.Task); } } diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/Helper/ProtoConvertHelper.cs b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/Helper/ProtoConvertHelper.cs index 1dc250eff..3a8b837d0 100644 --- a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/Helper/ProtoConvertHelper.cs +++ b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/Helper/ProtoConvertHelper.cs @@ -24,6 +24,9 @@ namespace ProcessExplorer.Server.Server.Helper; internal static class ProtoConvertHelper { + //n Proto3, all fields are optional and have a default value. For example, a string field has a default value of empty string ("") and an int field has a default value of zero (0). + //If you want to create a proto message without a certain field, you have to set its value to the default value. + public static Process DeriveProtoProcessType(this ProcessInfoData process) { List threads = new(); diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/Infrastructure/Grpc/ProcessExplorerMessageHandlerService.cs b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/Infrastructure/Grpc/ProcessExplorerMessageHandlerService.cs index 85c695ea4..0ccc20e47 100644 --- a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/Infrastructure/Grpc/ProcessExplorerMessageHandlerService.cs +++ b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/Infrastructure/Grpc/ProcessExplorerMessageHandlerService.cs @@ -46,7 +46,7 @@ public override async Task Subscribe(Empty request, IServerStreamWriter await handler.SubscriptionIsAliveUpdate(); _processInfoAggregator.AddUiConnection(id, handler); - //wait here until the user is alive + //wait here until the user is connected to the service while (!context.CancellationToken.IsCancellationRequested) continue; } @@ -62,7 +62,7 @@ public override async Task Subscribe(Empty request, IServerStreamWriter public override Task Send(Message request, ServerCallContext context) { - //handle here the incoming messages form the clients. + //handle here the incoming messages from the clients. _logger.GrpcClientMessageReceivedDebug(request.Action.ToString()); Task.Run(() => @@ -71,7 +71,7 @@ public override Task Send(Message request, ServerCallContext context) request, _processInfoAggregator, context.CancellationToken); - }); + }, context.CancellationToken); return Task.FromResult(new Empty()); } diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/Infrastructure/SubsystemLauncherCommunicator.cs b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/Infrastructure/SubsystemLauncherCommunicator.cs deleted file mode 100644 index 510cd8879..000000000 --- a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/Infrastructure/SubsystemLauncherCommunicator.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Morgan Stanley makes this available to you under the Apache License, -// Version 2.0 (the "License"). You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0. -// -// See the NOTICE file distributed with this work for additional information -// regarding copyright ownership. Unless required by applicable law or agreed -// to in writing, software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -// or implied. See the License for the specific language governing permissions -// and limitations under the License. - -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using ProcessExplorer.Abstractions.Subsystems; -using ProcessExplorer.Server.Logging; - -namespace ProcessExplorer.Server.Server.Infrastructure; - -internal class SubsystemLauncherCommunicator : ISubsystemLauncherCommunicator -{ - private readonly ILogger _logger; - private readonly ISubsystemLauncher _subsystemLauncher; // we should inject it from the shell where we control the applications states - - public SubsystemLauncherCommunicator( - ILogger logger, - ISubsystemLauncher subsystemLauncher) - { - _logger = logger ?? NullLogger.Instance; - _subsystemLauncher = subsystemLauncher; - } - - //it is not necessary in this case - public Task InitializeCommunicationRoute() - { - return Task.CompletedTask; - } - - public async Task SendLaunchSubsystemAfterTimeRequest(Guid subsystemId, int periodOfTime) - { - var result = await _subsystemLauncher.LaunchSubsystemAfterTime(subsystemId, periodOfTime); - - if (result == SubsystemState.Stopped) _logger.StartSubsystemError(subsystemId.ToString()); - } - - public async Task SendLaunchSubsystemsRequest(IEnumerable subsystems) - { - var result = await _subsystemLauncher.LaunchSubsystems(subsystems); - - foreach (var subsystem in result) - { - if (subsystem.Value == SubsystemState.Stopped) _logger.StartSubsystemError(subsystem.Key.ToString()); - } - } - - public async Task SendRestartSubsystemsRequest(IEnumerable subsystems) - { - var result = await _subsystemLauncher.RestartSubsystems(subsystems); - - foreach (var subsystem in result) - { - if (subsystem.Value == SubsystemState.Stopped) _logger.RestartSubsystemError(subsystem.Key.ToString()); - } - } - - public async Task SendShutdownSubsystemsRequest(IEnumerable subsystems) - { - var result = await _subsystemLauncher.ShutdownSubsystems(subsystems); - - foreach (var subsystem in result) - { - if (subsystem.Value != SubsystemState.Stopped) _logger.ShutdownSubsystemError(subsystem.Key.ToString()); - } - } -} diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/MessageHandler.cs b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/MessageHandler.cs index 6070829f8..6b8f1240f 100644 --- a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/MessageHandler.cs +++ b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/src/ProcessExplorer.Server/Server/MessageHandler.cs @@ -31,20 +31,22 @@ public static async void HandleIncomingGrpcMessages( { var ids = message.Subsystems.Select(subsystem => subsystem.Key); + if (processInfoAggregator.SubsystemController == null) continue; + switch (message.Action) { case ActionType.TerminateSubsystemsAction: - await processInfoAggregator.ShutdownSubsystems(ids); + await processInfoAggregator.SubsystemController.ShutdownSubsystems(ids); break; case ActionType.RestartSubsystemsAction: - await processInfoAggregator.RestartSubsystems(ids); + await processInfoAggregator.SubsystemController.RestartSubsystems(ids); break; case ActionType.LaunchSubsystemsAction: - await processInfoAggregator.LaunchSubsystems(ids); + await processInfoAggregator.SubsystemController.LaunchSubsystems(ids); break; @@ -54,7 +56,7 @@ public static async void HandleIncomingGrpcMessages( if (ids.ElementAt(0) == null) continue; var id = Guid.Parse(ids.ElementAt(0)); - await processInfoAggregator.LaunchSubsystemWithDelay(id, message.PeriodOfDelay); + await processInfoAggregator.SubsystemController.LaunchSubsystemAfterTime(id, message.PeriodOfDelay); } catch(Exception exception) { @@ -63,6 +65,9 @@ public static async void HandleIncomingGrpcMessages( break; } + + if (cancellationToken.IsCancellationRequested) + break; } } catch(Exception exception) diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/test/ProcessExplorer.Core.Tests/ProcessExplorer.Core.Tests.csproj b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/test/ProcessExplorer.Core.Tests/ProcessExplorer.Core.Tests.csproj index 899649db1..4a5386a76 100644 --- a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/test/ProcessExplorer.Core.Tests/ProcessExplorer.Core.Tests.csproj +++ b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/test/ProcessExplorer.Core.Tests/ProcessExplorer.Core.Tests.csproj @@ -8,6 +8,7 @@ + @@ -25,4 +26,9 @@ + + + + + diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/test/ProcessExplorer.Core.Tests/ProcessInfoAggregator.Tests.cs b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/test/ProcessExplorer.Core.Tests/ProcessInfoAggregator.Tests.cs index 1241bfd3b..583f8fb83 100644 --- a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/test/ProcessExplorer.Core.Tests/ProcessInfoAggregator.Tests.cs +++ b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/test/ProcessExplorer.Core.Tests/ProcessInfoAggregator.Tests.cs @@ -1,23 +1,38 @@ -using System; +// Morgan Stanley makes this available to you under the Apache License, +// Version 2.0 (the "License"). You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0. +// +// See the NOTICE file distributed with this work for additional information +// regarding copyright ownership. Unless required by applicable law or agreed +// to in writing, software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; +using FluentAssertions; using LocalCollector; using LocalCollector.Connections; +using LocalCollector.Modules; +using LocalCollector.Registrations; using Microsoft.Extensions.Logging.Abstractions; using Moq; using ProcessExplorer.Abstractions; using ProcessExplorer.Abstractions.Infrastructure; using ProcessExplorer.Abstractions.Processes; using ProcessExplorer.Abstractions.Subsystems; -using ProcessExplorer.Core.Processes; using Xunit; namespace ProcessExplorer.Core.Tests; -//End to end tests server and just send messages, also mock, and test windowsprocess with creating process release/debug folder +//TODO(Lilla): mock, and test windowsprocess with creating process release/debug folder public class ProcessInfoAggregatorTests { [Fact] @@ -52,54 +67,31 @@ public async Task RunSubsystemQueue_will_cancel_after_timeout() Assert.True(cancellationTokenSource.IsCancellationRequested); } - [Fact(Skip = "Run in Windows environment")] //nem szuksegss + [Fact] public void SetComposePid_will_set_the_main_id() { - //Creating mocks to handle method - var mockSubsystemController = new Mock(); - - //due it is an abstarct class we should create an instance of it, to test the set will be called. -#pragma warning disable CA1416 // Validate platform compatibility - var processInfoManager = new WindowsProcessInfoManager(NullLogger.Instance); -#pragma warning restore CA1416 // Validate platform compatibility - var dummyPid = 1; - var processInfoAggregator = new ProcessInfoAggregator( - NullLogger.Instance, - processInfoManager, - mockSubsystemController.Object); + var processInfoAggregator = CreateProcessInfoAggregator(); processInfoAggregator.SetMainProcessId(dummyPid); - //using reflection to compare values - var field = typeof(ProcessInfoManager).GetField("_composePid", BindingFlags.NonPublic | BindingFlags.Instance); - var value = (int)field?.GetValue(processInfoManager); + var result = processInfoAggregator.MainProcessId; - Assert.Equal(dummyPid, value); + result.Should().Be(dummyPid); } - [Fact(Skip = "Run in Windows environment")] + [Fact] public void SetDeadProcessRemovalDelay_will_set_the_delay_of_process_deleting_from_the_collection() { - var mockSubsystemController = new Mock(); -#pragma warning disable CA1416 // Validate platform compatibility - var processInfoManager = new WindowsProcessInfoManager(NullLogger.Instance); -#pragma warning restore CA1416 // Validate platform compatibility - - - var processInfoAggregator = new ProcessInfoAggregator( - NullLogger.Instance, - processInfoManager, - mockSubsystemController.Object); + var processInfoAggregator = CreateProcessInfoAggregator(); var dummyDelay = 0; processInfoAggregator.SetDeadProcessRemovalDelay(dummyDelay); - var delayField = typeof(ProcessInfoManager).GetField("_delayTime", BindingFlags.NonPublic | BindingFlags.Instance); - var result = (int)delayField.GetValue(processInfoManager); + var result = processInfoAggregator.TerminatingProcessDelay; - Assert.Equal(dummyDelay, result); + result.Should().Be(dummyDelay); } [Fact] @@ -118,11 +110,11 @@ public void AddUiConnection_will_add_a_connection_information() processInfoAggregator.AddUiConnection(id, mockUiHandler.Object); - var collectionOfUiHandlers = typeof(ProcessInfoAggregator).GetField("_uiClients", BindingFlags.NonPublic | BindingFlags.Instance); - var result = (ConcurrentDictionary)collectionOfUiHandlers?.GetValue(processInfoAggregator); + var result = processInfoAggregator.GetUiClients(); - Assert.Single(result); - Assert.Equal(mockUiHandler.Object, result[id]); + result.Should().NotBeNull(); + result.Should().HaveCount(1); + result.Should().Contain(new KeyValuePair(id, mockUiHandler.Object)); } [Fact] @@ -141,67 +133,68 @@ public void RemoveUiConnection_will_add_a_connection_information() processInfoAggregator.AddUiConnection(id, mockUiHandler.Object); - var collectionOfUiHandlers = typeof(ProcessInfoAggregator).GetField("_uiClients", BindingFlags.NonPublic | BindingFlags.Instance); - var result = (ConcurrentDictionary)collectionOfUiHandlers?.GetValue(processInfoAggregator); + var result = processInfoAggregator.GetUiClients(); - Assert.Single(result); - Assert.Equal(mockUiHandler.Object, result[id]); + result.Should().NotBeNull(); + result.Should().HaveCount(1); + result.Should().Contain(new KeyValuePair(id, mockUiHandler.Object)); var otherId = Guid.NewGuid(); processInfoAggregator.AddUiConnection(otherId, mockUiHandler.Object); - Assert.Equal(2, result.Count); + result.Should().HaveCount(2); processInfoAggregator.RemoveUiConnection(new(id, mockUiHandler.Object)); - Assert.True(result.ContainsKey(otherId)); - Assert.Single(result); + result.Should().Contain(new KeyValuePair(otherId, mockUiHandler.Object)); + result.Should().NotContain(new KeyValuePair(id, mockUiHandler.Object)); + result.Should().HaveCount(1); } [Theory] [ClassData(typeof(RuntimeInfoTheoryData))] public async Task AddRuntimeInformation_will_add_a_new_info(string id, ProcessInfoCollectorData data) { - var mockSubsystemController = new Mock(); - var mockProcessInfoManager = new Mock(NullLogger.Instance); - - var processInfoAggregator = new ProcessInfoAggregator( - NullLogger.Instance, - mockProcessInfoManager.Object, - mockSubsystemController.Object); + var processInfoAggregator = CreateProcessInfoAggregator(); await processInfoAggregator.AddRuntimeInformation(id, data); - var field = typeof(ProcessInfoAggregator).GetField("_processInformation", BindingFlags.NonPublic | BindingFlags.Instance); - var collection = (ConcurrentDictionary)field?.GetValue(processInfoAggregator); + var collection = processInfoAggregator.GetRuntimeInformation(); - var succeed = collection.TryGetValue(id, out ProcessInfoCollectorData? result); + collection.Should().HaveCount(1); + collection.Should().Contain(new KeyValuePair(id, data)); - if (!succeed || result == null) throw new ArgumentNullException(nameof(result)); + var result = collection.First().Value; - Assert.Single(collection); - Assert.True(collection?.ContainsKey(id)); - Assert.Equal(data.Connections.Count, result.Connections.Count); - Assert.Equal(data.EnvironmentVariables.Count, result.EnvironmentVariables.Count); - Assert.Equal(data.Modules.Count, result.Modules.Count); - Assert.Equal(data.Registrations.Count, result.Registrations.Count); + data.Connections.Count.Should().Be(result.Connections.Count); + data.Connections.Should().BeEquivalentTo(result.Connections); + data.EnvironmentVariables.Count.Should().Be(result.EnvironmentVariables.Count); + data.EnvironmentVariables.Should().BeEquivalentTo(result.EnvironmentVariables); + data.Modules.Count.Should().Be(result.Modules.Count); + data.Modules.Should().BeEquivalentTo(result.Modules); + data.Registrations.Count.Should().Be(result.Registrations.Count); + data.Registrations.Should().BeEquivalentTo(result.Registrations); } [Theory] [ClassData(typeof(RuntimeInfoTheoryData))] public async Task AddRuntimeInformation_will_update_an_info(string id, ProcessInfoCollectorData data) { - var mockSubsystemController = new Mock(); - var mockProcessInfoManager = new Mock(NullLogger.Instance); - - var processInfoAggregator = new ProcessInfoAggregator( - NullLogger.Instance, - mockProcessInfoManager.Object, - mockSubsystemController.Object); + var processInfoAggregator = CreateProcessInfoAggregator(); var dummyRuntimeInfo = new ProcessInfoCollectorData() { Id = 2, - Connections = new() { new() { Id = Guid.NewGuid(), Name = "dummy" }, new() { Id = Guid.NewGuid(), Name = "dummy2" }, new() { Id = Guid.NewGuid(), Name = "dummy3" } }, - Registrations = new() { new() { ImplementationType = "dummyImpl", LifeTime = "dummyLT", ServiceType = "dummyST" }, new() { ImplementationType = "dummyImpl", LifeTime = "dummyLT", ServiceType = "dummyST" }, new() { ImplementationType = "dummyImpl", LifeTime = "dummyLT", ServiceType = "dummyST" } } + Connections = new() + { + new() { Id = Guid.NewGuid(), Name = "dummy" }, + new() { Id = Guid.NewGuid(), Name = "dummy2" }, + new() { Id = Guid.NewGuid(), Name = "dummy3" } + }, + Registrations = new() + { + new() { ImplementationType = "dummyImpl", LifeTime = "dummyLT", ServiceType = "dummyST" }, + new() { ImplementationType = "dummyImpl", LifeTime = "dummyLT", ServiceType = "dummyST" }, + new() { ImplementationType = "dummyImpl", LifeTime = "dummyLT", ServiceType = "dummyST" } + } }; await processInfoAggregator.AddRuntimeInformation(id, dummyRuntimeInfo); @@ -209,37 +202,42 @@ public async Task AddRuntimeInformation_will_update_an_info(string id, ProcessIn //modifying the existing one await processInfoAggregator.AddRuntimeInformation(id, data); - var field = typeof(ProcessInfoAggregator).GetField("_processInformation", BindingFlags.NonPublic | BindingFlags.Instance); - var collection = (ConcurrentDictionary)field?.GetValue(processInfoAggregator); + var collection = processInfoAggregator.GetRuntimeInformation(); - var succeed = collection.TryGetValue(id, out ProcessInfoCollectorData? result); + collection.Should().HaveCount(1); + collection.Should().Contain(new KeyValuePair(id, data)); - if (!succeed || result == null) throw new ArgumentNullException(nameof(result)); + var result = collection.First().Value; - Assert.Single(collection); - Assert.True(collection?.ContainsKey(id)); - Assert.Equal(data.Connections.Count, result.Connections.Count); - Assert.Equal(data.EnvironmentVariables.Count, result.EnvironmentVariables.Count); - Assert.Equal(data.Modules.Count, result.Modules.Count); - Assert.Equal(data.Registrations.Count, result.Registrations.Count); + data.Connections.Count.Should().Be(result.Connections.Count); + data.Connections.Should().BeEquivalentTo(result.Connections); + data.EnvironmentVariables.Count.Should().Be(result.EnvironmentVariables.Count); + data.EnvironmentVariables.Should().BeEquivalentTo(result.EnvironmentVariables); + data.Modules.Count.Should().Be(result.Modules.Count); + data.Modules.Should().BeEquivalentTo(result.Modules); + data.Registrations.Count.Should().Be(result.Registrations.Count); + data.Registrations.Should().BeEquivalentTo(result.Registrations); } [Fact] public async Task RemoveRuntimeInformation_will_remove_item_from_collection() { - var mockSubsystemController = new Mock(); - var mockProcessInfoManager = new Mock(NullLogger.Instance); - - var processInfoAggregator = new ProcessInfoAggregator( - NullLogger.Instance, - mockProcessInfoManager.Object, - mockSubsystemController.Object); + var processInfoAggregator = CreateProcessInfoAggregator(); var dummyRuntimeInfo = new ProcessInfoCollectorData() { Id = 2, - Connections = new() { new() { Id = Guid.NewGuid(), Name = "dummy" }, new() { Id = Guid.NewGuid(), Name = "dummy2" }, new() { Id = Guid.NewGuid(), Name = "dummy3" } }, - Registrations = new() { new() { ImplementationType = "dummyImpl", LifeTime = "dummyLT", ServiceType = "dummyST" }, new() { ImplementationType = "dummyImpl", LifeTime = "dummyLT", ServiceType = "dummyST" }, new() { ImplementationType = "dummyImpl", LifeTime = "dummyLT", ServiceType = "dummyST" } } + Connections = new() + { + new() { Id = Guid.NewGuid(), Name = "dummy" }, new() { Id = Guid.NewGuid(), Name = "dummy2" }, + new() { Id = Guid.NewGuid(), Name = "dummy3" } + }, + Registrations = new() + { + new() { ImplementationType = "dummyImpl", LifeTime = "dummyLT", ServiceType = "dummyST" }, + new() { ImplementationType = "dummyImpl", LifeTime = "dummyLT", ServiceType = "dummyST" }, + new() { ImplementationType = "dummyImpl", LifeTime = "dummyLT", ServiceType = "dummyST" } + } }; var id = "dummyId"; @@ -248,126 +246,253 @@ public async Task RemoveRuntimeInformation_will_remove_item_from_collection() await processInfoAggregator.AddRuntimeInformation(id, dummyRuntimeInfo); await processInfoAggregator.AddRuntimeInformation(id2, dummyRuntimeInfo); - var field = typeof(ProcessInfoAggregator).GetField("_processInformation", BindingFlags.NonPublic | BindingFlags.Instance); - var collection = (ConcurrentDictionary)field?.GetValue(processInfoAggregator); + var collection = processInfoAggregator.GetRuntimeInformation(); - Assert.Equal(2, collection?.Count); - Assert.True(collection?.ContainsKey(id)); + collection.Should().HaveCount(2); + collection.Should().Contain(new KeyValuePair(id, dummyRuntimeInfo)); + collection.Should().Contain(new KeyValuePair(id2, dummyRuntimeInfo)); processInfoAggregator.RemoveRuntimeInformation(id); - Assert.False(collection?.ContainsKey(id)); - Assert.Single(collection); + collection.Should().HaveCount(1); + collection.Should().NotContain(new KeyValuePair(id, dummyRuntimeInfo)); + collection.Should().Contain(new KeyValuePair(id2, dummyRuntimeInfo)); } [Theory] [ClassData(typeof(ConnectionTheoryData))] public async Task AddConnectionCollection_will_add_a_new_connection_collection_information(string id, IEnumerable connections) { + var processInfoAggregator = CreateProcessInfoAggregator(); + + //Add dummy data + await processInfoAggregator.AddRuntimeInformation(id, new ProcessInfoCollectorData()); + await processInfoAggregator.AddConnectionCollection(id, connections); + var collection = processInfoAggregator.GetRuntimeInformation(); + + collection.Should().HaveCount(1); + + var result = collection.First(); + + result.Key.Should().Be(id); + result.Value.Connections.Should().BeEquivalentTo(connections); } - //[Fact] - //public async Task AddConnectionCollection_will_update_connection_collection_information(string id, IEnumerable connections) - //{ + [Fact] + public async Task UpdateConnectionInfo_will_update_a_connection_information() + { + var processInfoAggregator = CreateProcessInfoAggregator(); - //} + var connectionId = Guid.NewGuid(); + var wrongConnectionInfo = new ConnectionInfo { Id = connectionId, Name = "dummyName", LocalEndpoint = "http://dummyLocalEndpontWrong.com" }; + var id = "dummyId"; - //[Fact] - //public async Task UpdateConnectionInfo_will_update_a_connection_information(string id, ConnectionInfo connection) - //{ + await processInfoAggregator.AddRuntimeInformation(id, new ProcessInfoCollectorData() + { + Connections = new() { wrongConnectionInfo } + }); + + var collection = processInfoAggregator.GetRuntimeInformation(); + collection.Should().HaveCount(1); + + var result = collection.First().Value; + result.Connections.Should().HaveCount(1); + result.Connections.Should().Contain(wrongConnectionInfo); + + //updating + var dummyConnectionInfo = new ConnectionInfo { Id = connectionId, Name = "dummyName", LocalEndpoint = "https://dummyLocalEndpoint.com" }; + await processInfoAggregator.UpdateOrAddConnectionInfo(id, dummyConnectionInfo); + + collection = processInfoAggregator.GetRuntimeInformation(); + result = collection.First().Value; + + collection.Should().HaveCount(1); + result.Connections.Should().HaveCount(1); + result.Connections.Should().NotContain(wrongConnectionInfo); + result.Connections.Should().Contain(dummyConnectionInfo); + } - //} + [Fact] + public async Task UpdateEnvironmentVariablesInfo_will_update_environment_variables() + { + var processInfoAggregator = CreateProcessInfoAggregator(); - //[Fact] - //public async Task UpdateEnvironmentVariablesInfo_will_update_environment_variables(string assemblyId, IEnumerable> environmentVariables) - //{ + var id = "dummyId"; + var envs = new ConcurrentDictionary(); - //} + envs.TryAdd("dummyKey", "dummyValue"); + envs.TryAdd("wrongEnv", "wrongValue"); - //[Fact] - //public async Task UpdateRegistrationInfo_will_update_registrations(string assemblyId, IEnumerable registrations) - //{ + await processInfoAggregator.AddRuntimeInformation(id, new() + { + EnvironmentVariables = envs, + }); - //} + var collection = processInfoAggregator.GetRuntimeInformation(); + collection.Should().HaveCount(1); + collection.Should().ContainKey(id); - //[Fact] - //public async Task UpdateModuleInfo_will_update_modules(string assemblyId, IEnumerable modules) - //{ + var result = collection.First().Value; + result.EnvironmentVariables.Should().HaveCount(2); + result.EnvironmentVariables.Should().BeEquivalentTo(envs); - //} + var updatedEnvs = new Dictionary() + { + { "wrongEnv", "newValue" }, + { "newKey", "value" } + }; + var expectedResult = new Dictionary() + { + { "dummyKey", "dummyValue" }, + { "wrongEnv", "newValue" }, + { "newKey", "value" } + }; - //[Fact] - //public void EnableWatchingSavedProcesses_will_begin_to_watch_processes() - //{ + await processInfoAggregator.UpdateOrAddEnvironmentVariablesInfo(id, updatedEnvs); + collection = processInfoAggregator.GetRuntimeInformation(); + collection.Should().HaveCount(1); + collection.Should().ContainKey(id); - //} + result = collection.First().Value; + result.EnvironmentVariables.Should().HaveCount(3); + result.EnvironmentVariables.Should().BeEquivalentTo(expectedResult); + } - //[Fact] - //public void DisableWatchingProcesses_will_dispose_processmonitor() - //{ + [Fact] + public async Task UpdateRegistrationInfo_will_update_registrations() + { + var processInfoAggregator = CreateProcessInfoAggregator(); + + var id = "dummyId"; + var registrations = new SynchronizedCollection() + { + new RegistrationInfo() + { + ImplementationType = "dummyImplementation", + LifeTime = "dummyLifetime", + ServiceType = "dummyServiceType" + } + }; + + await processInfoAggregator.AddRuntimeInformation(id, new() + { + Registrations = registrations + }); + + var collection = processInfoAggregator.GetRuntimeInformation(); + collection.Should().HaveCount(1); + collection.Should().ContainKey(id); + + var result = collection.First().Value; + result.Registrations.Should().HaveCount(1); + result.Registrations.Should().BeEquivalentTo(registrations); + + var update = new List() + { + new() { ServiceType = "dummyImplementation", ImplementationType = "dummyNewImplementationType", LifeTime = "dummyLifeTime" }, + new() { ServiceType = "dummyImplementation2", ImplementationType = "dummyImplementationType2", LifeTime = "dummyLifeTime2" } + }; + + await processInfoAggregator.UpdateRegistrations(id, update); - //} + collection = processInfoAggregator.GetRuntimeInformation(); + collection.Should().HaveCount(1); - //[Fact] - //public async Task ShutdownSubsystems_will_send_shutdown_command(IEnumerable subsystemIds) - //{ + result = collection.First().Value; + result.Registrations.Should().HaveCount(3); + + var expected = new List() + { + new() { ImplementationType = "dummyImplementation", LifeTime = "dummyLifetime", ServiceType = "dummyServiceType" }, + new() { ServiceType = "dummyImplementation", ImplementationType = "dummyNewImplementationType", LifeTime = "dummyLifeTime" }, + new() { ServiceType = "dummyImplementation2", ImplementationType = "dummyImplementationType2", LifeTime = "dummyLifeTime2" } + }; + + result.Registrations.Should().BeEquivalentTo(expected); + } - //} + [Fact] + public async Task UpdateModuleInfo_will_update_modules() + { + var processInfoAggregator = CreateProcessInfoAggregator(); + + var id = "dummyId"; + + var modules = new SynchronizedCollection() + { + new() { Name = "dummyModule", Location = "dummyLocation" } + }; - //[Fact] - //public async Task RestartSubsystems_will_send_restart_command(IEnumerable subsystemIds) - //{ + await processInfoAggregator.AddRuntimeInformation(id, new() + { + Modules = modules + }); - //} + var update = new List() { new() { Name = "dummyModule", Location = "newDummyLocation" } }; - //[Fact] - //public async Task LaunchSubsystems_will_send_launch_command(IEnumerable subsystemIds) - //{ + await processInfoAggregator.UpdateOrAddModuleInfo(id, update); - //} + var collection = processInfoAggregator.GetRuntimeInformation(); + collection.Should().HaveCount(1); + collection.Should().ContainKey(id); - //[Fact] - //public async Task LaunchSubsystemsWithDelay_will_send_launch_with_delay_command(IEnumerable subsystemIds) - //{ + var result = collection.First().Value; + result.Modules.Should().HaveCount(1); + result.Modules.Should().BeEquivalentTo(update); + } - //} - //[Fact] - //public async Task InitializeSubsystems_will_set_subsystems(IEnumerable subsystemIds) - //{ + [Fact] + public void EnableWatchingSavedProcesses_will_begin_to_watch_processes() + { + var mockSubsystemController = new Mock(); + var mockProcessInfoManager = new Mock(); - //} + var processInfoAggregator = new ProcessInfoAggregator( + NullLogger.Instance, + mockProcessInfoManager.Object, + mockSubsystemController.Object); - //[Fact] - //public async Task ModifySubsystemState_will_modify_the_state_of_the_item_in_the_collection(Guid subsystemId, string state) - //{ + processInfoAggregator.EnableWatchingSavedProcesses(); + mockProcessInfoManager.Verify(x => x.WatchProcesses(processInfoAggregator.MainProcessId), Times.Once); + } - //} - //[Fact] - //public void SetSubsystemController_will_set_subsystemcontroller() - //{ + [Fact] + public void ScheduleSubsystemStateChanged_will_put_items_to_the_queue() + { + var id = Guid.NewGuid(); + var state = SubsystemState.Running; - //} + var processInfoAggregator = CreateProcessInfoAggregator(); + processInfoAggregator.ScheduleSubsystemStateChanged(id, state); - //[Fact] - //public void ScheduleSubsystemStateChanged_will_put_items_to_the_queue(Guid instanceId, string state) - //{ + var field = typeof(ProcessInfoAggregator).GetField("_subsystemStateChanges", BindingFlags.NonPublic | BindingFlags.Instance); + if (field == null) throw new ArgumentNullException(nameof(field)); + + var queue = (ConcurrentQueue>)field.GetValue(processInfoAggregator); + if (queue == null) throw new ArgumentNullException(nameof(queue)); - //} + var succeed = queue.TryDequeue(out var result); + succeed.Should().BeTrue(); - //[Fact] - //public async Task InitializeProcesses_will_set_the_process_ids_to_watch() - //{ + result.Key.Should().Be(id); + result.Value.Should().Be(state); + } - //} + private IProcessInfoAggregator CreateProcessInfoAggregator() + { + var mockSubsystemController = new Mock(); + var mockProcessInfoManager = new Mock(); - //[Fact] - //public async Task AddProcesses_will_add_the_processes_to_the_existing_list_without_duplication() - //{ + var processInfoAggregator = new ProcessInfoAggregator( + NullLogger.Instance, + mockProcessInfoManager.Object, + mockSubsystemController.Object); - //} + return processInfoAggregator; + } private class RuntimeInfoTheoryData : TheoryData { @@ -392,9 +517,10 @@ private class ConnectionTheoryData : TheoryData { public ConnectionTheoryData() { - AddRow(); + AddRow("dummyId", new List() + { + new() { Id = Guid.NewGuid(), Name = "dummyConnection", LocalEndpoint = "dummyEndpoint" } + }); } } } - - diff --git a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/test/ProcessExplorer.Server.EndToEndTests/EndToEndTests.cs b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/test/ProcessExplorer.Server.EndToEndTests/EndToEndTests.cs index 64da4d1ce..84a921b72 100644 --- a/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/test/ProcessExplorer.Server.EndToEndTests/EndToEndTests.cs +++ b/Tryouts/Plugins/ApplicationPlugins/MorganStanley.ComposeUI.ProcessExplorer/dotnet/test/ProcessExplorer.Server.EndToEndTests/EndToEndTests.cs @@ -25,7 +25,6 @@ using ProcessExplorer.Server.DependencyInjection; using ProcessExplorer.Server.Server.Abstractions; using Xunit; -using FluentAssertions.Execution; using FluentAssertions; namespace ProcessExplorer.Server.IntegrationTests; @@ -45,22 +44,25 @@ public async Task DisposeAsync() [Fact] public async Task Client_can_connect() { - var channel = GrpcChannel.ForAddress($"http://{Host}:{Port}/"); - var client = new ProcessExplorerMessageHandler.ProcessExplorerMessageHandlerClient(channel); + var client = CreateGrpcClient(); using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(1)); var messages = new List(); - var call = client.Subscribe(new Empty(), null, null, cancellationTokenSource.Token); + var call = client.Subscribe(new Empty(), cancellationToken: cancellationTokenSource.Token); // We want to receive the message, that the subscription is estabilished, and we do not want to wait. // Due to that no processes/subsystems/runtime information have not been declared it will just receive the subscription alive notification - await foreach (var message in call.ResponseStream.ReadAllAsync()) + + try { - messages.Add(message); - break; + await foreach (var message in call.ResponseStream.ReadAllAsync()) + { + messages.Add(message); + } } + catch (RpcException) { } messages.Count.Should().Be(1); messages[0].Action.Should().Be(ActionType.SubscriptionAliveAction); @@ -73,13 +75,13 @@ public async Task Client_can_subscribe_and_receive_messages() var aggregator = _host?.Services.GetRequiredService(); var dummyId = Guid.NewGuid(); - + var dummySubsystemInfo = new SubsystemInfo() { State = SubsystemState.Running, Name = "DummySubsystem", - UIType = "dummy", - StartupType = "dummy", + UIType = "dummyUiType", + StartupType = "dummyStartUpType", Path = "dummyPath", AutomatedStart = false, }; @@ -89,17 +91,16 @@ public async Task Client_can_subscribe_and_receive_messages() { dummyId, dummySubsystemInfo } }; - if (aggregator != null) + if (aggregator != null && aggregator.SubsystemController != null) { - await aggregator.InitializeSubsystems(subsystems); + await aggregator.SubsystemController.InitializeSubsystems(subsystems); } - var channel = GrpcChannel.ForAddress($"http://{Host}:{Port}/"); - var client = new ProcessExplorerMessageHandler.ProcessExplorerMessageHandlerClient(channel); + var client = CreateGrpcClient(); var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(1)); var messages = new List(); - var call = client.Subscribe(new Empty(), null, null, cancellationTokenSource.Token); + var call = client.Subscribe(new Empty(), cancellationToken: cancellationTokenSource.Token); //try catch block to avoid OperationCanceledException due to that we are just waiting for 1 second try @@ -118,16 +119,25 @@ public async Task Client_can_subscribe_and_receive_messages() messages[1].Subsystems.Count.Should().Be(1); messages[1].Subsystems.Should().ContainKey(dummyId.ToString()); + //In Proto3, all fields are optional and have a default value. For example, a string field has a default value of empty string ("") and an int field has a default value of zero (0). + //If you want to create a proto message without a certain field, you have to set its value to the default value. var result = messages[1].Subsystems[dummyId.ToString()]; result.Should().NotBeNull(); result.Name.Should().BeEquivalentTo(dummySubsystemInfo.Name); + result.State.Should().BeEquivalentTo(dummySubsystemInfo.State); + result.UiType.Should().BeEquivalentTo(dummySubsystemInfo.UIType); + result.StartupType.Should().BeEquivalentTo(dummySubsystemInfo.StartupType); + result.Path.Should().BeEquivalentTo(dummySubsystemInfo.Path); + result.AutomatedStart.Should().Be(dummySubsystemInfo.AutomatedStart); + result.Arguments.Should().BeEmpty(); + result.Url.Should().BeNullOrEmpty(); + result.Description.Should().BeNullOrEmpty(); } [Fact] public void Client_can_send_message() { - var channel = GrpcChannel.ForAddress($"http://{Host}:{Port}/"); - var client = new ProcessExplorerMessageHandler.ProcessExplorerMessageHandlerClient(channel); + var client = CreateGrpcClient(); var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(1)); var message = new Message() @@ -161,6 +171,13 @@ public async Task InitializeAsync() await _host.StartAsync(); } + private ProcessExplorerMessageHandler.ProcessExplorerMessageHandlerClient CreateGrpcClient() + { + var channel = GrpcChannel.ForAddress($"http://{Host}:{Port}/"); + var client = new ProcessExplorerMessageHandler.ProcessExplorerMessageHandlerClient(channel); + + return client; + } private static DummyStartType CreateDummyStartType(Guid id, string name) { diff --git a/prototypes/multi-module-prototype/examples/multi-module-example/ModulesPrototype/Program.cs b/prototypes/multi-module-prototype/examples/multi-module-example/ModulesPrototype/Program.cs index ad39fe6d9..7c8725ff8 100644 --- a/prototypes/multi-module-prototype/examples/multi-module-example/ModulesPrototype/Program.cs +++ b/prototypes/multi-module-prototype/examples/multi-module-example/ModulesPrototype/Program.cs @@ -157,7 +157,8 @@ public static async Task Main(string[] args) } } - await infoAggregator.InitializeSubsystems(instances.Select(kvp => new KeyValuePair(kvp.Key, SubsystemInfo.FromModule(kvp.Value)))); + if(infoAggregator.SubsystemController != null) + await infoAggregator.SubsystemController.InitializeSubsystems(instances.Select(kvp => new KeyValuePair(kvp.Key, SubsystemInfo.FromModule(kvp.Value)))); logger.LogInformation("ComposeUI application running, press Ctrl+C to exit");