#region License Information /* HeuristicLab * Copyright (C) Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using HeuristicLab.PluginInfrastructure; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace HeuristicLab.Tests { [TestClass] public class PluginDependenciesTest { private static Dictionary loadedPlugins; private static Dictionary pluginNames; private static Dictionary pluginVersions; private static Dictionary pluginFilesToPluginLookup = new Dictionary(); // Use ClassInitialize to run code before running the first test in the class [ClassInitialize] public static void MyClassInitialize(TestContext testContext) { try { loadedPlugins = PluginLoader.Assemblies.Where(PluginLoader.IsPluginAssembly).ToDictionary(a => a, GetPluginFromAssembly); } catch (BadImageFormatException e) { var message = string.Join(Environment.NewLine, "Could not load all types. Check if test process architecture is set to x64.", string.Empty, "Exception:", e); Assert.Fail(message); } pluginNames = loadedPlugins.ToDictionary(a => a.Key.GetName().FullName, a => GetPluginName(a.Value)); pluginVersions = loadedPlugins.ToDictionary(a => a.Key.GetName().FullName, a => GetPluginVersion(a.Value)); foreach (Assembly pluginAssembly in loadedPlugins.Keys) { Type pluginType = GetPluginFromAssembly(pluginAssembly); var pluginFileAttributes = Attribute.GetCustomAttributes(pluginType, false).OfType(); foreach (var pluginFileAttribute in pluginFileAttributes) { string fillNameWithoutExtension = Path.GetFileNameWithoutExtension(pluginFileAttribute.FileName); pluginFilesToPluginLookup.Add(fillNameWithoutExtension, pluginAssembly); } } } [TestMethod] [TestCategory("General")] [TestCategory("Essential")] [TestProperty("Time", "short")] public void CheckReferenceAssembliesForPluginDependencies() { StringBuilder errorMessage = new StringBuilder(); foreach (Assembly pluginAssembly in loadedPlugins.Keys) { Type plugin = loadedPlugins[pluginAssembly]; var pluginFiles = new HashSet(Attribute.GetCustomAttributes(plugin, false) .OfType().Where(pf => pf.FileType == PluginFileType.Assembly).Select(pf => pf.FileName)); var pluginAssemblies = PluginLoader.Assemblies.Where(a => pluginFiles.Contains(Path.GetFileName(a.Location))).ToList(); var referencedAssemblies = pluginAssemblies.SelectMany(a => a.GetReferencedAssemblies()).ToList(); var pluginDependencies = Attribute.GetCustomAttributes(plugin, false).OfType().ToArray(); bool versionLessThan(Version a, Version b) => a.Major < b.Major || ((a.Major == b.Major) && (a.Minor <= b.Minor)); bool compatiblePluginAvailable(string referencedAssemblyName) => pluginDependencies.Any(pd => pd.Dependency == pluginNames[referencedAssemblyName] && versionLessThan(pd.Version, pluginVersions[referencedAssemblyName])); foreach (AssemblyName referencedAssemblyName in referencedAssemblies) { if (IsPluginAssemblyName(referencedAssemblyName)) { if (!compatiblePluginAvailable(referencedAssemblyName.FullName)) errorMessage.AppendLine("Missing dependency in plugin " + plugin + " to referenced plugin " + pluginNames[referencedAssemblyName.FullName] + " in version >= " + pluginVersions[referencedAssemblyName.FullName] + "."); } else { //no plugin assembly => test if the assembly is delivered by another plugin if (pluginFilesToPluginLookup.ContainsKey(referencedAssemblyName.Name)) { string containingPluginFullName = pluginFilesToPluginLookup[referencedAssemblyName.Name].FullName; if (containingPluginFullName != pluginAssembly.FullName && !compatiblePluginAvailable(containingPluginFullName)) errorMessage.AppendLine("Missing dependency in plugin " + plugin + " to plugin " + pluginNames[containingPluginFullName] + " in version >= " + pluginVersions[containingPluginFullName] + " due to a reference to " + referencedAssemblyName.FullName + "."); } } } } Assert.IsTrue(errorMessage.Length == 0, errorMessage.ToString()); } [TestMethod] [TestCategory("General")] [TestCategory("Essential")] [TestProperty("Time", "short")] public void CheckPluginDependenciesForReferencedAssemblies() { StringBuilder errorMessage = new StringBuilder(); foreach (Assembly pluginAssembly in loadedPlugins.Keys) { Type plugin = loadedPlugins[pluginAssembly]; Dictionary pluginDependencies = Attribute.GetCustomAttributes(plugin, false).OfType().ToDictionary(a => a, a => a.Dependency); var pluginFiles = new HashSet(Attribute.GetCustomAttributes(plugin, false) .OfType().Where(pf => pf.FileType == PluginFileType.Assembly).Select(pf => pf.FileName)); var pluginAssemblies = PluginLoader.Assemblies.Where(a => pluginFiles.Contains(Path.GetFileName(a.Location))).ToList(); var referencedAssemblies = pluginAssemblies.SelectMany(a => a.GetReferencedAssemblies()).ToList(); foreach (PluginDependencyAttribute attribute in pluginDependencies.Keys) { string pluginDependencyName = pluginDependencies[attribute]; if (pluginDependencyName == "HeuristicLab.MathJax") continue; //is never referenced as this plugin contains HTML files if (pluginDependencyName == "HeuristicLab.MatlabConnector") continue; //the matlab connector is loaded dynamically and hence not referenced by the dll var referencedPluginAssemblies = referencedAssemblies.Where(IsPluginAssemblyName); if (referencedPluginAssemblies.Any(a => pluginNames[a.FullName] == pluginDependencyName)) continue; var referencedNonPluginAssemblies = referencedAssemblies.Where(a => !IsPluginAssemblyName(a)); bool found = (from referencedNonPluginAssembly in referencedNonPluginAssemblies select referencedNonPluginAssembly.Name into assemblyName where pluginFilesToPluginLookup.ContainsKey(assemblyName) select GetPluginFromAssembly(pluginFilesToPluginLookup[assemblyName]) into pluginType select GetPluginName(pluginType)).Any(pluginName => pluginName == pluginDependencyName); if (!found) errorMessage.AppendLine("Unnecessary plugin dependency in " + GetPluginName(plugin) + " to " + pluginDependencyName + "."); } } Assert.IsTrue(errorMessage.Length == 0, errorMessage.ToString()); } private static Type GetPluginFromAssembly(Assembly assembly) { return assembly.GetExportedTypes().FirstOrDefault(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface); } private static string GetPluginName(Type plugin) { string name = string.Empty; PluginAttribute pluginAttribute = (PluginAttribute)Attribute.GetCustomAttribute(plugin, typeof(PluginAttribute)); if (pluginAttribute != null) name = pluginAttribute.Name; return name; } private static Version GetPluginVersion(Type plugin) { var version = new Version(); PluginAttribute pluginAttribute = (PluginAttribute)Attribute.GetCustomAttribute(plugin, typeof(PluginAttribute)); if (pluginAttribute != null) { version = pluginAttribute.Version; } return version; } private static bool IsPluginAssemblyName(AssemblyName assemblyName) { return pluginNames.ContainsKey(assemblyName.FullName); } } }