From 7b8e6865c0154b0baf0002b18f242751df247a34 Mon Sep 17 00:00:00 2001 From: Ts-Pytham Date: Sat, 1 Feb 2025 13:28:04 -0500 Subject: [PATCH 1/8] =?UTF-8?q?A=C3=B1adir=20m=C3=A9todo=20abstracto=20y?= =?UTF-8?q?=20clase=20PluginConfig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Se ha añadido un nuevo método abstracto `GetPluginConfigFiles` en la clase `CPluginConfigurationBase` que devuelve una colección de objetos `PluginConfig`. Este método nunca devuelve `null` y proporciona información detallada sobre la ubicación de los archivos de plugins. En las clases `CPluginEnvConfiguration` y `CPluginJsonConfiguration`, se ha implementado el método `GetPluginConfigFiles` lanzando una excepción `NotImplementedException`, indicando que aún no se ha proporcionado una implementación concreta. Se ha añadido una nueva clase `PluginConfig` en el archivo `PluginConfig.cs` que contiene dos propiedades: `Name` (nombre del plugin) y `DependsOn` (lista de dependencias del plugin). Se han eliminado algunas declaraciones `using` innecesarias en el archivo `CPluginJsonConfiguration.cs`. --- .../Configuration/CPluginConfigurationBase.cs | 18 ++++++++++++++++++ .../Configuration/CPluginEnvConfiguration.cs | 5 +++++ .../Configuration/CPluginJsonConfiguration.cs | 10 ++++++---- src/Core/Configuration/PluginConfig.cs | 6 ++++++ 4 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 src/Core/Configuration/PluginConfig.cs diff --git a/src/Core/Configuration/CPluginConfigurationBase.cs b/src/Core/Configuration/CPluginConfigurationBase.cs index e570d75..8724731 100644 --- a/src/Core/Configuration/CPluginConfigurationBase.cs +++ b/src/Core/Configuration/CPluginConfigurationBase.cs @@ -27,6 +27,24 @@ public abstract class CPluginConfigurationBase /// public abstract IEnumerable GetPluginFiles(); + /// + /// Gets the full path to each plugin file from a configuration source. + /// + /// + /// A collection of plugin files that also contains the paths; + /// or + /// Returns an empty enumerable when the plugin files could not be obtained. + /// This method never returns null. + /// + /// + /// Plugin files must be in the plugins directory of the current directory + /// where the host application is running. + /// Each plugin file must have a .dll extension and must be in its own directory. + /// Example: + /// /HostApp/bin/Debug/net7.0/plugins/MyPlugin1/MyPlugin1.dll + /// + public abstract IEnumerable GetPluginConfigFiles(); + /// /// Gets the full path of a plugin file. /// diff --git a/src/Core/Configuration/CPluginEnvConfiguration.cs b/src/Core/Configuration/CPluginEnvConfiguration.cs index 6c4f675..694dd08 100644 --- a/src/Core/Configuration/CPluginEnvConfiguration.cs +++ b/src/Core/Configuration/CPluginEnvConfiguration.cs @@ -21,6 +21,11 @@ public class CPluginEnvConfiguration : CPluginConfigurationBase /// public CPluginEnvConfiguration() { } + public override IEnumerable GetPluginConfigFiles() + { + throw new NotImplementedException(); + } + /// public override IEnumerable GetPluginFiles() { diff --git a/src/Core/Configuration/CPluginJsonConfiguration.cs b/src/Core/Configuration/CPluginJsonConfiguration.cs index c22dc84..04db387 100644 --- a/src/Core/Configuration/CPluginJsonConfiguration.cs +++ b/src/Core/Configuration/CPluginJsonConfiguration.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration; namespace CPlugin.Net; @@ -34,6 +31,11 @@ public CPluginJsonConfiguration(IConfiguration configuration) _configuration = configuration; } + public override IEnumerable GetPluginConfigFiles() + { + throw new NotImplementedException(); + } + /// public override IEnumerable GetPluginFiles() { diff --git a/src/Core/Configuration/PluginConfig.cs b/src/Core/Configuration/PluginConfig.cs new file mode 100644 index 0000000..e5fd074 --- /dev/null +++ b/src/Core/Configuration/PluginConfig.cs @@ -0,0 +1,6 @@ +namespace CPlugin.Net; +public class PluginConfig +{ + public string Name { get; set; } = string.Empty; + public List DependsOn { get; set; } = []; +} From bb8e7f0b913dc6c69fe8deadbbd46bbed466d007 Mon Sep 17 00:00:00 2001 From: Ts-Pytham Date: Sat, 1 Feb 2025 13:49:48 -0500 Subject: [PATCH 2/8] =?UTF-8?q?Implementaci=C3=B3n=20de=20GetPluginConfigF?= =?UTF-8?q?iles=20y=20nuevas=20pruebas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Se ha modificado la clase `CPluginJsonConfiguration` para implementar el método `GetPluginConfigFiles`, que ahora devuelve una lista de objetos `PluginConfig` obtenidos de la configuración JSON. Anteriormente, este método lanzaba una excepción `NotImplementedException`. Se ha añadido una nueva prueba en la clase `CPluginJsonConfigurationTests` llamada `GetPluginConfigFiles_WhenPluginFilesArePresent_ShouldReturnsFullPaths`. Esta prueba verifica que el método `GetPluginConfigFiles` devuelve las rutas completas de los plugins cuando están presentes en la configuración JSON. Se ha añadido un archivo JSON `settingsWithDependencies.json` que contiene la configuración de los plugins con sus dependencias, utilizado en la nueva prueba para verificar el comportamiento del método `GetPluginConfigFiles`. --- .../Configuration/CPluginJsonConfiguration.cs | 10 ++++++- .../Core/CPluginJsonConfigurationTests.cs | 28 +++++++++++++++++++ .../Resources/settingsWithDependencies.json | 14 ++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 tests/CPlugin.Net/Resources/settingsWithDependencies.json diff --git a/src/Core/Configuration/CPluginJsonConfiguration.cs b/src/Core/Configuration/CPluginJsonConfiguration.cs index 04db387..19fe1b5 100644 --- a/src/Core/Configuration/CPluginJsonConfiguration.cs +++ b/src/Core/Configuration/CPluginJsonConfiguration.cs @@ -33,7 +33,15 @@ public CPluginJsonConfiguration(IConfiguration configuration) public override IEnumerable GetPluginConfigFiles() { - throw new NotImplementedException(); + var values = _configuration + .GetSection("Plugins") + .Get(); + + return values is null ? [] : values.Select(p => new PluginConfig + { + Name = GetPluginPath(p.Name), + DependsOn = p.DependsOn + }); } /// diff --git a/tests/CPlugin.Net/Core/CPluginJsonConfigurationTests.cs b/tests/CPlugin.Net/Core/CPluginJsonConfigurationTests.cs index c122afd..b29a6d7 100644 --- a/tests/CPlugin.Net/Core/CPluginJsonConfigurationTests.cs +++ b/tests/CPlugin.Net/Core/CPluginJsonConfigurationTests.cs @@ -79,6 +79,34 @@ public void GetPluginFiles_WhenPluginFileDoesNotHaveDllExtension_ShouldBeAddedBy actual.Should().BeEquivalentTo(expectedPaths); } + [Test] + public void GetPluginConfigFiles_WhenPluginFilesArePresent_ShouldReturnsFullPaths() + { + // Arrange + var configurationRoot = new ConfigurationBuilder() + .AddJsonFile("./Resources/settingsWithDependencies.json") + .Build(); + var jsonConfiguration = new CPluginJsonConfiguration(configurationRoot); + var basePath = AppContext.BaseDirectory; + PluginConfig[] expectedPaths = + [ + new PluginConfig + { + Name = Path.Combine(basePath, "plugins", "TestProject.OldJsonPlugin", "TestProject.OldJsonPlugin.dll"), + DependsOn = [] + }, + new PluginConfig + { + Name = Path.Combine(basePath, "plugins", "TestProject.JsonPlugin", "TestProject.JsonPlugin.dll"), + DependsOn = ["TestProject.OldJsonPlugin"] + }, + ]; + // Act + var actual = jsonConfiguration.GetPluginConfigFiles().ToList(); + // Assert + actual.Should().BeEquivalentTo(expectedPaths); + } + [Test] public void Constructor_WhenArgumentIsNull_ShouldThrowArgumentNullException() { diff --git a/tests/CPlugin.Net/Resources/settingsWithDependencies.json b/tests/CPlugin.Net/Resources/settingsWithDependencies.json new file mode 100644 index 0000000..25e10f7 --- /dev/null +++ b/tests/CPlugin.Net/Resources/settingsWithDependencies.json @@ -0,0 +1,14 @@ +{ + "Plugins": [ + { + "Name": "TestProject.JsonPlugin", + "DependsOn": [ + "TestProject.OldJsonPlugin" + ] + }, + { + "Name": "TestProject.OldJsonPlugin", + "DependsOn": [] + } + ] +} \ No newline at end of file From a6e12a407741087363ddc39d43d1b51453e43577 Mon Sep 17 00:00:00 2001 From: Ts-Pytham Date: Sat, 1 Feb 2025 13:52:51 -0500 Subject: [PATCH 3/8] =?UTF-8?q?A=C3=B1adir=20ejemplo=20de=20dependencias?= =?UTF-8?q?=20en=20CPluginJsonConfiguration.cs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Se ha ampliado la documentación en el archivo `CPluginJsonConfiguration.cs` para incluir un ejemplo detallado sobre cómo manejar dependencias entre plugins en la configuración JSON. Este ejemplo muestra cómo estructurar el JSON para definir plugins que dependen de otros plugins, proporcionando una guía clara para los desarrolladores sobre cómo configurar estas dependencias correctamente. --- .../Configuration/CPluginJsonConfiguration.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Core/Configuration/CPluginJsonConfiguration.cs b/src/Core/Configuration/CPluginJsonConfiguration.cs index 19fe1b5..838d769 100644 --- a/src/Core/Configuration/CPluginJsonConfiguration.cs +++ b/src/Core/Configuration/CPluginJsonConfiguration.cs @@ -11,6 +11,23 @@ namespace CPlugin.Net; /// /// { "Plugins": [ "MyPlugin1.dll", "MyPlugin2.dll" ] } /// +/// if you uses dependencies: +/// +/// { +/// "Plugins": [ +/// { +/// "Name": "TestProject.JsonPlugin", +/// "DependsOn": [ +/// "TestProject.OldJsonPlugin" +/// ] +/// }, +/// { +/// "Name": "TestProject.OldJsonPlugin", +/// "DependsOn": [] +/// } +/// ] +/// } +/// /// public class CPluginJsonConfiguration : CPluginConfigurationBase { From 26e715a6a52dd100bfc59fcdf61acf82e8afd8f4 Mon Sep 17 00:00:00 2001 From: Ts-Pytham Date: Sat, 1 Feb 2025 15:01:05 -0500 Subject: [PATCH 4/8] =?UTF-8?q?Implementaci=C3=B3n=20de=20manejo=20de=20de?= =?UTF-8?q?pendencias=20en=20plugins?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Se ha añadido documentación adicional en `CPluginEnvConfiguration.cs` y `CPluginJsonConfiguration.cs` para explicar cómo manejar plugins con dependencias en la variable de entorno `PLUGINS` y en la configuración JSON, respectivamente. Se ha eliminado el método `GetPluginConfigFiles` que lanzaba una excepción `NotImplementedException` y se ha implementado una nueva versión que obtiene la variable de entorno `PLUGINS`, la divide en archivos de plugins y sus dependencias, y devuelve una lista de objetos `PluginConfig`. Se ha añadido una nueva prueba en `CPluginEnvConfigurationTests.cs` para verificar que el método `GetPluginConfigFiles` obtiene correctamente los archivos de plugins y sus dependencias desde un archivo de entorno. Se ha añadido un archivo de entorno de prueba `testwithdependencies.env` que define la variable `PLUGINS` con dos plugins, uno de los cuales depende del otro. --- .../Configuration/CPluginEnvConfiguration.cs | 36 ++++++++++++++++--- .../Configuration/CPluginJsonConfiguration.cs | 2 +- .../Core/CPluginEnvConfigurationTests.cs | 32 +++++++++++++++++ .../Resources/testwithdependencies.env | 4 +++ 4 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 tests/CPlugin.Net/Resources/testwithdependencies.env diff --git a/src/Core/Configuration/CPluginEnvConfiguration.cs b/src/Core/Configuration/CPluginEnvConfiguration.cs index 694dd08..59f3987 100644 --- a/src/Core/Configuration/CPluginEnvConfiguration.cs +++ b/src/Core/Configuration/CPluginEnvConfiguration.cs @@ -11,6 +11,13 @@ namespace CPlugin.Net; /// The variable must be called PLUGINS and its value must be a string separated by spaces or new lines. /// Example: /// PLUGINS=MyPlugin1.dll MyPlugin2.dll +/// if you have plugins with dependencies, you can do this: +/// +/// PLUGINS=" +/// MyPlugin1.dll->MyPlugin2.dll +/// MyPlugin2.dll +///" +/// /// public class CPluginEnvConfiguration : CPluginConfigurationBase { @@ -21,11 +28,6 @@ public class CPluginEnvConfiguration : CPluginConfigurationBase /// public CPluginEnvConfiguration() { } - public override IEnumerable GetPluginConfigFiles() - { - throw new NotImplementedException(); - } - /// public override IEnumerable GetPluginFiles() { @@ -40,4 +42,28 @@ public override IEnumerable GetPluginFiles() return pluginFiles; } + + public override IEnumerable GetPluginConfigFiles() + { + var retrievedValue = Environment.GetEnvironmentVariable("PLUGINS"); + if (retrievedValue is null) + return []; + + var pluginFiles = retrievedValue + .Split(s_separator, StringSplitOptions.None) + .Where(pluginFile => !string.IsNullOrWhiteSpace(pluginFile)) + .ToList(); + + return pluginFiles.Select(p => + { + var str = p.Split("->"); + var dependsOn = str.Length == 1 ? [] : str[1].Split(","); + + return new PluginConfig + { + Name = GetPluginPath(str[0]), + DependsOn = [.. dependsOn] + }; + }); + } } diff --git a/src/Core/Configuration/CPluginJsonConfiguration.cs b/src/Core/Configuration/CPluginJsonConfiguration.cs index 838d769..cdecf8f 100644 --- a/src/Core/Configuration/CPluginJsonConfiguration.cs +++ b/src/Core/Configuration/CPluginJsonConfiguration.cs @@ -11,7 +11,7 @@ namespace CPlugin.Net; /// /// { "Plugins": [ "MyPlugin1.dll", "MyPlugin2.dll" ] } /// -/// if you uses dependencies: +/// if you have plugins with dependencies, you can do this: /// /// { /// "Plugins": [ diff --git a/tests/CPlugin.Net/Core/CPluginEnvConfigurationTests.cs b/tests/CPlugin.Net/Core/CPluginEnvConfigurationTests.cs index c50186a..d8881f7 100644 --- a/tests/CPlugin.Net/Core/CPluginEnvConfigurationTests.cs +++ b/tests/CPlugin.Net/Core/CPluginEnvConfigurationTests.cs @@ -87,6 +87,38 @@ public void GetPluginFiles_WhenPluginFilesAreObtainedFromEnvFile_ShouldReturnsFu actual.Should().BeEquivalentTo(expectedPaths); } + [Test] + public void GetPluginConfigFiles_WhenPluginFilesAreObtainedFromEnvFile_ShouldReturnsFullPaths() + { + // Arrange + new EnvLoader() + .AllowOverwriteExistingVars() + .EnableFileNotFoundException() + .AddEnvFile("./Resources/testwithdependencies.env") + .Load(); + var envConfiguration = new CPluginEnvConfiguration(); + var basePath = AppContext.BaseDirectory; + PluginConfig[] expectedPaths = + [ + new PluginConfig + { + Name = Path.Combine(basePath, "plugins", "TestProject.OldJsonPlugin", "TestProject.OldJsonPlugin.dll"), + DependsOn = [] + }, + new PluginConfig + { + Name = Path.Combine(basePath, "plugins", "TestProject.JsonPlugin", "TestProject.JsonPlugin.dll"), + DependsOn = ["TestProject.OldJsonPlugin.dll"] + }, + ]; + + // Act + var actual = envConfiguration.GetPluginConfigFiles().ToList(); + + // Assert + actual.Should().BeEquivalentTo(expectedPaths); + } + [Test] public void GetPluginFiles_WhenPluginFilesAreNotPresent_ShouldReturnsEmptyEnumerable() { diff --git a/tests/CPlugin.Net/Resources/testwithdependencies.env b/tests/CPlugin.Net/Resources/testwithdependencies.env new file mode 100644 index 0000000..0b78664 --- /dev/null +++ b/tests/CPlugin.Net/Resources/testwithdependencies.env @@ -0,0 +1,4 @@ +PLUGINS=" + TestProject.JsonPlugin.dll->TestProject.OldJsonPlugin.dll + TestProject.OldJsonPlugin.dll +" \ No newline at end of file From ded4b24a23b241b756c9be4ad6d2b649e0b72f1e Mon Sep 17 00:00:00 2001 From: Ts-Pytham Date: Sat, 1 Feb 2025 15:04:22 -0500 Subject: [PATCH 5/8] =?UTF-8?q?Refactorizaci=C3=B3n=20de=20importaciones?= =?UTF-8?q?=20y=20ajustes=20en=20comentarios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Se han eliminado las importaciones innecesarias de `System`, `System.Collections.Generic` y `System.Linq`. Se ha modificado la declaración del espacio de nombres a `namespace CPlugin.Net;`. Se han ajustado los comentarios de ejemplo para mejorar la claridad en la representación de las dependencias de los plugins. --- src/Core/Configuration/CPluginEnvConfiguration.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Core/Configuration/CPluginEnvConfiguration.cs b/src/Core/Configuration/CPluginEnvConfiguration.cs index 59f3987..5ee0e3a 100644 --- a/src/Core/Configuration/CPluginEnvConfiguration.cs +++ b/src/Core/Configuration/CPluginEnvConfiguration.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace CPlugin.Net; +namespace CPlugin.Net; /// /// Represents a configuration to get the plugin files from an environment variable. @@ -14,8 +10,9 @@ namespace CPlugin.Net; /// if you have plugins with dependencies, you can do this: /// /// PLUGINS=" -/// MyPlugin1.dll->MyPlugin2.dll +/// MyPlugin1.dll->MyPlugin2.dll,MyPlugin3.dll /// MyPlugin2.dll +/// MyPlugin3.dll ///" /// /// From f562f63323f09ba5068c32e08ae21b8c590526af Mon Sep 17 00:00:00 2001 From: Ts-Pytham Date: Sat, 1 Feb 2025 15:48:08 -0500 Subject: [PATCH 6/8] =?UTF-8?q?A=C3=B1adir=20manejo=20de=20excepciones=20e?= =?UTF-8?q?n=20PluginLoader?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Se ha añadido un nuevo espacio de nombres `CPlugin.Net.Exceptions` en `PluginLoader.cs`. Se ha añadido el método `LoadPluginsWithDependencies` en `PluginLoader` para cargar plugins con sus dependencias y lanzar `PluginNotFoundException` si alguna dependencia no se encuentra. Se ha creado la clase `PluginNotFoundException` en `PluginNotFoundException.cs`. Se han añadido dos tests en `PluginLoaderTests.cs` para verificar la carga de plugins y el lanzamiento de excepciones. Se ha añadido el uso del espacio de nombres `CPlugin.Net.Exceptions` en `PluginLoaderTests.cs`. --- .../Exceptions/PluginNotFoundException.cs | 10 ++++ src/Core/PluginLoader.cs | 27 +++++++++- tests/CPlugin.Net/Core/PluginLoaderTests.cs | 49 ++++++++++++++++++- 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 src/Core/Exceptions/PluginNotFoundException.cs diff --git a/src/Core/Exceptions/PluginNotFoundException.cs b/src/Core/Exceptions/PluginNotFoundException.cs new file mode 100644 index 0000000..34361a4 --- /dev/null +++ b/src/Core/Exceptions/PluginNotFoundException.cs @@ -0,0 +1,10 @@ +namespace CPlugin.Net.Exceptions; +/// +/// Represents an exception that is thrown when a plugin is not found. +/// +/// The missing plugin. +/// The dependent plugin. +public class PluginNotFoundException(string missingPlugin, string dependentPlugin) + : Exception($"The plugin '{dependentPlugin}' depends on '{missingPlugin}', but '{missingPlugin}' was not found.") +{ +} diff --git a/src/Core/PluginLoader.cs b/src/Core/PluginLoader.cs index c5cc223..5f87b1e 100644 --- a/src/Core/PluginLoader.cs +++ b/src/Core/PluginLoader.cs @@ -1,4 +1,5 @@ -using System; +using CPlugin.Net.Exceptions; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -42,6 +43,30 @@ public static void Load(CPluginConfigurationBase configuration) } } + public static void LoadPluginsWithDependencies(CPluginConfigurationBase configuration) + { + ArgumentNullException.ThrowIfNull(configuration); + var pluginConfigs = configuration.GetPluginConfigFiles(); + foreach (var pluginConfig in pluginConfigs) + { + if (pluginConfig.DependsOn?.Count > 0) + { + foreach (var dependency in pluginConfig.DependsOn) + { + if (!pluginConfigs.Any(pc => pc.Name.Contains(dependency))) + { + string pluginName = Path.GetFileName(pluginConfig.Name); + throw new PluginNotFoundException(dependency, pluginName); + } + } + } + + Assembly currentAssembly = FindAssembly(pluginConfig.Name); + if (currentAssembly is null) + LoadAssembly(pluginConfig.Name); + } + } + private static void LoadAssembly(string assemblyFile) { var loadContext = new PluginLoadContext(assemblyFile); diff --git a/tests/CPlugin.Net/Core/PluginLoaderTests.cs b/tests/CPlugin.Net/Core/PluginLoaderTests.cs index b7309f2..ec9b7ce 100644 --- a/tests/CPlugin.Net/Core/PluginLoaderTests.cs +++ b/tests/CPlugin.Net/Core/PluginLoaderTests.cs @@ -1,4 +1,6 @@ -namespace CPlugin.Net.Tests.Core; +using CPlugin.Net.Exceptions; + +namespace CPlugin.Net.Tests.Core; public class PluginLoaderTests { @@ -79,4 +81,49 @@ public void Load_WhenMethodIsCalledMultipleTimes_ShouldNotLoadSamePluginsIntoMem .Should() .Be(expectedAssemblies); } + + [Test] + public void LoadPluginsWithDependencies_WhenPluginsAreFound_ShouldBeLoadedIntoMemory() + { + // Arrange + var value = + """ + TestProject.OldJsonPlugin.dll->TestProject.JsonPlugin.dll + TestProject.JsonPlugin.dll + """; + Environment.SetEnvironmentVariable("PLUGINS", value); + var envConfiguration = new CPluginEnvConfiguration(); + int expectedAssemblies = 2; + + // Act + PluginLoader.LoadPluginsWithDependencies(envConfiguration); + + AppDomain + .CurrentDomain + .GetAssemblies() + .Where(assembly => assembly.GetName().Name == "TestProject.OldJsonPlugin" + || assembly.GetName().Name == "TestProject.JsonPlugin") + .Count() + .Should() + .Be(expectedAssemblies); + } + + [Test] + public void LoadPluginsWithDependencies_WhenDependencyIsNotFound_ShouldThrowPluginNotFoundException() + { + // Arrange + var dependentPlugin = "TestProject.JsonPlugin.dll"; + var missingPlugin = "TestProject.OldJsonPlugin.dll"; + var value = $"{dependentPlugin}->{missingPlugin}"; + Environment.SetEnvironmentVariable("PLUGINS", value); + var envConfiguration = new CPluginEnvConfiguration(); + + // Act + Action act = () => PluginLoader.LoadPluginsWithDependencies(envConfiguration); + + // Assert + act.Should() + .Throw() + .WithMessage($"The plugin '{dependentPlugin}' depends on '{missingPlugin}', but '{missingPlugin}' was not found."); + } } From 8a6ce30612270a93dae5a164613d7fcf0cf0a23a Mon Sep 17 00:00:00 2001 From: Ts-Pytham Date: Sat, 1 Feb 2025 15:58:58 -0500 Subject: [PATCH 7/8] =?UTF-8?q?A=C3=B1adir=20pruebas=20para=20carga=20de?= =?UTF-8?q?=20plugins=20en=20PluginLoaderTests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Se han añadido dos nuevos métodos de prueba a la clase `PluginLoaderTests` en el archivo `PluginLoaderTests.cs`: 1. `LoadPluginsWithDependencies_WhenPluginsAreIndependent_ShouldBeLoadedIntoMemory`: - Verifica la carga correcta de plugins independientes en memoria. - Configura la variable de entorno "PLUGINS". - Crea una instancia de `CPluginEnvConfiguration`. - Espera la carga de 2 ensamblados. - Llama a `PluginLoader.LoadPluginsWithDependencies`. - Verifica que los ensamblados cargados coincidan con los esperados. 2. `LoadPluginsWithDependencies_WhenPluginsHaveMultipleDependencies_ShouldBeLoaded`: - Verifica la carga correcta de plugins con múltiples dependencias. - Define una lista de plugins y sus dependencias. - Configura la variable de entorno "PLUGINS". - Crea una instancia de `CPluginEnvConfiguration`. - Espera la carga de 3 ensamblados. - Llama a `PluginLoader.LoadPluginsWithDependencies`. - Verifica que los ensamblados cargados coincidan con los esperados. --- tests/CPlugin.Net/Core/PluginLoaderTests.cs | 59 +++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/CPlugin.Net/Core/PluginLoaderTests.cs b/tests/CPlugin.Net/Core/PluginLoaderTests.cs index ec9b7ce..d2c7ea7 100644 --- a/tests/CPlugin.Net/Core/PluginLoaderTests.cs +++ b/tests/CPlugin.Net/Core/PluginLoaderTests.cs @@ -108,6 +108,65 @@ public void LoadPluginsWithDependencies_WhenPluginsAreFound_ShouldBeLoadedIntoMe .Be(expectedAssemblies); } + [Test] + public void LoadPluginsWithDependencies_WhenPluginsAreIndependent_ShouldBeLoadedIntoMemory() + { + // Arrange + var value = + """ + TestProject.OldJsonPlugin.dll + TestProject.JsonPlugin.dll + """; + Environment.SetEnvironmentVariable("PLUGINS", value); + var envConfiguration = new CPluginEnvConfiguration(); + int expectedAssemblies = 2; + + // Act + PluginLoader.LoadPluginsWithDependencies(envConfiguration); + + AppDomain + .CurrentDomain + .GetAssemblies() + .Where(assembly => assembly.GetName().Name == "TestProject.OldJsonPlugin" + || assembly.GetName().Name == "TestProject.JsonPlugin") + .Count() + .Should() + .Be(expectedAssemblies); + } + + [Test] + public void LoadPluginsWithDependencies_WhenPluginsHaveMultipleDependencies_ShouldBeLoaded() + { + // Arrange + List plugins = + [ + "TestProject.OldJsonPlugin", + "TestProject.JsonPlugin", + "TestProject.HelloPlugin" + ]; + + var value = + """ + TestProject.OldJsonPlugin.dll + TestProject.JsonPlugin.dll->TestProject.OldJsonPlugin.dll,TestProject.HelloPlugin.dll + TestProject.HelloPlugin.dll + """; + Environment.SetEnvironmentVariable("PLUGINS", value); + var envConfiguration = new CPluginEnvConfiguration(); + int expectedAssemblies = 3; + + // Act + PluginLoader.LoadPluginsWithDependencies(envConfiguration); + + AppDomain + .CurrentDomain + .GetAssemblies() + .Where(assembly => plugins.Contains(assembly.GetName().Name)) + .Count() + .Should() + .Be(expectedAssemblies); + } + [Test] public void LoadPluginsWithDependencies_WhenDependencyIsNotFound_ShouldThrowPluginNotFoundException() { From 1afe1c7c13d1dd29f0a227fd14f8b6c8b5a104bc Mon Sep 17 00:00:00 2001 From: Ts-Pytham Date: Sat, 1 Feb 2025 16:08:03 -0500 Subject: [PATCH 8/8] =?UTF-8?q?A=C3=B1adir=20m=C3=A9todo=20para=20cargar?= =?UTF-8?q?=20plugins=20con=20dependencias?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Se ha añadido el método `LoadPluginsWithDependencies` a la clase `PluginLoader` en `PluginLoader.cs`. Este método carga plugins y sus dependencias desde una fuente de configuración especificada, asegurando que todas las dependencias se resuelvan antes de cargar un plugin. El método es idempotente y lanza excepciones `PluginNotFoundException` y `ArgumentNullException` en caso de errores. En `PluginLoaderTests.cs`, se ha añadido una aserción para verificar que los ensamblados esperados se han cargado tras llamar a `LoadPluginsWithDependencies`. --- src/Core/PluginLoader.cs | 20 ++++++++++++++++++++ tests/CPlugin.Net/Core/PluginLoaderTests.cs | 1 + 2 files changed, 21 insertions(+) diff --git a/src/Core/PluginLoader.cs b/src/Core/PluginLoader.cs index 5f87b1e..1b1d4be 100644 --- a/src/Core/PluginLoader.cs +++ b/src/Core/PluginLoader.cs @@ -43,7 +43,27 @@ public static void Load(CPluginConfigurationBase configuration) } } + /// + /// Loads plugins and their dependencies from a specified configuration source. + /// The plugin list can be retrieved from a JSON file, an environment variable (.env), or another configuration source. + /// This method ensures that all required dependencies are resolved before loading a plugin. + /// + /// + /// A configuration source that provides the list of plugin files and their dependencies. + /// + /// + /// This method is idempotent, meaning that if it is called multiple times, + /// it will not reload assemblies that have already been loaded. + /// If a plugin depends on another plugin that is missing, a is thrown. + /// + /// + /// Thrown when is null. + /// + /// + /// Thrown when a required plugin dependency is missing. + /// public static void LoadPluginsWithDependencies(CPluginConfigurationBase configuration) + { ArgumentNullException.ThrowIfNull(configuration); var pluginConfigs = configuration.GetPluginConfigFiles(); diff --git a/tests/CPlugin.Net/Core/PluginLoaderTests.cs b/tests/CPlugin.Net/Core/PluginLoaderTests.cs index d2c7ea7..b15049b 100644 --- a/tests/CPlugin.Net/Core/PluginLoaderTests.cs +++ b/tests/CPlugin.Net/Core/PluginLoaderTests.cs @@ -158,6 +158,7 @@ public void LoadPluginsWithDependencies_WhenPluginsHaveMultipleDependencies_Shou // Act PluginLoader.LoadPluginsWithDependencies(envConfiguration); + // Assert AppDomain .CurrentDomain .GetAssemblies()