From 3ad6148d422da3b68cacf2c5bbb258d7c7b93ed0 Mon Sep 17 00:00:00 2001 From: Chris Maddock Date: Sat, 18 Aug 2018 14:34:16 +0100 Subject: [PATCH 1/8] Add TargetFramework to ExtensionAssembly, consider for extension preference, refactor --- .../Extensibility/ExtensionAssembly.cs | 77 +++++++++++++ .../Extensibility/ExtensionSelector.cs | 75 +++++++++++++ .../Extensibility/IExtensionAssembly.cs | 35 ++++++ .../Internal/TargetFrameworkHelper.cs | 6 + .../Services/ExtensionAssembly.cs | 104 ------------------ .../nunit.engine/Services/ExtensionService.cs | 5 + 6 files changed, 198 insertions(+), 104 deletions(-) create mode 100644 src/NUnitEngine/nunit.engine/Extensibility/ExtensionAssembly.cs create mode 100644 src/NUnitEngine/nunit.engine/Extensibility/ExtensionSelector.cs create mode 100644 src/NUnitEngine/nunit.engine/Extensibility/IExtensionAssembly.cs delete mode 100644 src/NUnitEngine/nunit.engine/Services/ExtensionAssembly.cs diff --git a/src/NUnitEngine/nunit.engine/Extensibility/ExtensionAssembly.cs b/src/NUnitEngine/nunit.engine/Extensibility/ExtensionAssembly.cs new file mode 100644 index 000000000..014bbaeff --- /dev/null +++ b/src/NUnitEngine/nunit.engine/Extensibility/ExtensionAssembly.cs @@ -0,0 +1,77 @@ +// *********************************************************************** +// Copyright (c) 2016 Charlie Poole, Rob Prouse +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; +using System.IO; +using Mono.Cecil; +using NUnit.Engine.Internal; + +namespace NUnit.Engine.Extensibility +{ + internal class ExtensionAssembly : IExtensionAssembly + { + private readonly TargetFrameworkHelper _targetFrameworkHelper; + + public ExtensionAssembly(string filePath, bool fromWildCard) + { + FilePath = filePath; + FromWildCard = fromWildCard; + Assembly = GetAssemblyDefinition(); + _targetFrameworkHelper = new TargetFrameworkHelper(Assembly); + } + + public string FilePath { get; } + public bool FromWildCard { get; } + public AssemblyDefinition Assembly { get; } + + public string AssemblyName + { + get { return Assembly.Name.Name; } + } + + public Version AssemblyVersion + { + get { return Assembly.Name.Version; } + } + + public ModuleDefinition MainModule + { + get { return Assembly.MainModule; } + } + + public RuntimeFramework TargetFramework + { + get { return new RuntimeFramework(RuntimeType.Any, _targetFrameworkHelper.TargetRuntimeVersion); } + } + + private AssemblyDefinition GetAssemblyDefinition() + { + var resolver = new DefaultAssemblyResolver(); + resolver.AddSearchDirectory(Path.GetDirectoryName(FilePath)); + resolver.AddSearchDirectory(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)); + var parameters = new ReaderParameters { AssemblyResolver = resolver }; + + return AssemblyDefinition.ReadAssembly(FilePath, parameters); + } + } +} diff --git a/src/NUnitEngine/nunit.engine/Extensibility/ExtensionSelector.cs b/src/NUnitEngine/nunit.engine/Extensibility/ExtensionSelector.cs new file mode 100644 index 000000000..0d356f998 --- /dev/null +++ b/src/NUnitEngine/nunit.engine/Extensibility/ExtensionSelector.cs @@ -0,0 +1,75 @@ +// *********************************************************************** +// Copyright (c) 2016-2018 Charlie Poole, Rob Prouse +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using NUnit.Common; + +namespace NUnit.Engine.Extensibility +{ + internal static class ExtensionSelector + { + /// + /// IsDuplicateOf returns true if two assemblies have the same name. + /// + public static bool IsDuplicateOf(this IExtensionAssembly first, IExtensionAssembly second) + { + return first.AssemblyName == second.AssemblyName; + } + + /// + /// IsBetterVersion determines whether another assembly is + /// a better than the current assembly. It first looks at + /// for the highest assembly version, and then the highest target + /// framework. With a tie situation, assemblies specified directly + /// are prefered to those located via wildcards. + /// + /// It is only intended to be called if IsDuplicateOf + /// has already returned true. This method does no work to check if + /// the target framework found is available under the current engine. + /// + public static bool IsBetterVersionOf(this IExtensionAssembly first, IExtensionAssembly second) + { + Guard.OperationValid(first.IsDuplicateOf(second), "IsBetterVersionOf should only be called on duplicate assemblies"); + + //Look at assembly version + var firstVersion = first.AssemblyVersion; + var secondVersion = second.AssemblyVersion; + if (firstVersion > secondVersion) + return true; + + if (firstVersion < secondVersion) + return false; + + //Look at target runtime + var firstTargetRuntime = first.TargetFramework.FrameworkVersion; + var secondTargetRuntime = second.TargetFramework.FrameworkVersion; + if (firstTargetRuntime > secondTargetRuntime) + return true; + + if (firstTargetRuntime < secondTargetRuntime) + return false; + + //Everything is equal, override only if this one was specified exactly while the other wasn't + return !first.FromWildCard && second.FromWildCard; + } + } +} diff --git a/src/NUnitEngine/nunit.engine/Extensibility/IExtensionAssembly.cs b/src/NUnitEngine/nunit.engine/Extensibility/IExtensionAssembly.cs new file mode 100644 index 000000000..76844ce3f --- /dev/null +++ b/src/NUnitEngine/nunit.engine/Extensibility/IExtensionAssembly.cs @@ -0,0 +1,35 @@ +// *********************************************************************** +// Copyright (c) 2018 Charlie Poole, Rob Prouse +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +using System; + +namespace NUnit.Engine.Extensibility +{ + internal interface IExtensionAssembly + { + bool FromWildCard { get; } + string AssemblyName { get; } + Version AssemblyVersion { get; } + RuntimeFramework TargetFramework { get; } + } +} diff --git a/src/NUnitEngine/nunit.engine/Internal/TargetFrameworkHelper.cs b/src/NUnitEngine/nunit.engine/Internal/TargetFrameworkHelper.cs index 4d7504378..a656700f6 100644 --- a/src/NUnitEngine/nunit.engine/Internal/TargetFrameworkHelper.cs +++ b/src/NUnitEngine/nunit.engine/Internal/TargetFrameworkHelper.cs @@ -45,6 +45,12 @@ public TargetFrameworkHelper(string assemblyPath) } } + public TargetFrameworkHelper(AssemblyDefinition assemblyDef) + { + _assemblyDef = assemblyDef; + _module = _assemblyDef.MainModule; + } + public bool RequiresX86 { get diff --git a/src/NUnitEngine/nunit.engine/Services/ExtensionAssembly.cs b/src/NUnitEngine/nunit.engine/Services/ExtensionAssembly.cs deleted file mode 100644 index 3bfbffbc2..000000000 --- a/src/NUnitEngine/nunit.engine/Services/ExtensionAssembly.cs +++ /dev/null @@ -1,104 +0,0 @@ -// *********************************************************************** -// Copyright (c) 2016 Charlie Poole, Rob Prouse -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// *********************************************************************** - -using Mono.Cecil; - -namespace NUnit.Engine.Services -{ - public class ExtensionAssembly - { - public ExtensionAssembly(string filePath, bool fromWildCard) - { - FilePath = filePath; - FromWildCard = fromWildCard; - } - - public string FilePath { get; private set; } - public bool FromWildCard { get; private set; } - - private AssemblyDefinition _assemblyDefinition; - public AssemblyDefinition Assembly - { - get - { - if (_assemblyDefinition == null) - { - var resolver = new DefaultAssemblyResolver(); - resolver.AddSearchDirectory(System.IO.Path.GetDirectoryName(FilePath)); - resolver.AddSearchDirectory(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)); - var parameters = new ReaderParameters() { AssemblyResolver = resolver }; - - _assemblyDefinition = AssemblyDefinition.ReadAssembly(FilePath, parameters); - } - - return _assemblyDefinition; - } - } - - public ModuleDefinition MainModule - { - get { return Assembly.MainModule; } - } - - public AssemblyNameDefinition AssemblyName - { - get { return Assembly.Name; } - } - - /// - /// IsDuplicateOf returns true if two assemblies have the same name. - /// - public bool IsDuplicateOf(ExtensionAssembly other) - { - return AssemblyName.Name == other.AssemblyName.Name; - } - - /// - /// IsBetterVersion determines whether another assembly is - /// a better (higher) version than the current assembly. - /// It is only intended to be called if IsDuplicateOf - /// has already returned true. - /// - /// - /// - public bool IsBetterVersionOf(ExtensionAssembly other) - { -#if DEBUG - if (!IsDuplicateOf(other)) - throw new NUnitEngineException("IsBetterVersionOf should only be called on duplicate assemblies"); -#endif - - var version = AssemblyName.Version; - var otherVersion = other.AssemblyName.Version; - - if (version > otherVersion) - return true; - - if (version < otherVersion) - return false; - - // Versions are equal, override only if this one was specified exactly while the other wasn't - return !FromWildCard && other.FromWildCard; - } - } -} diff --git a/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs b/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs index 60e43ed2f..d17f16dcf 100644 --- a/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs +++ b/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs @@ -397,6 +397,11 @@ private void ProcessCandidateAssembly(string filePath, bool fromWildCard) if (!fromWildCard) throw new NUnitEngineException(String.Format("Specified extension {0} could not be read", filePath), e); } + catch (NUnitEngineException) + { + if (!fromWildCard) + throw; + } } } From ae8b52c732cc77e7093318ef52a2125b62643c1d Mon Sep 17 00:00:00 2001 From: Chris Maddock Date: Sat, 18 Aug 2018 14:35:16 +0100 Subject: [PATCH 2/8] Handle Extensions which can't be loaded under current target framework --- .../nunit.engine/Services/ExtensionService.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs b/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs index d17f16dcf..499e09a76 100644 --- a/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs +++ b/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs @@ -422,10 +422,24 @@ private void Visit(string filePath) /// For each extension, create an ExtensionNode and link it to the /// correct ExtensionPoint. Public for testing. /// - public void FindExtensionsInAssembly(ExtensionAssembly assembly) + internal void FindExtensionsInAssembly(ExtensionAssembly assembly) { log.Info("Scanning {0} assembly for Extensions", assembly.FilePath); + var assemblyTargetFramework = assembly.TargetFramework; + if (!assemblyTargetFramework.IsAvailable) + { + if (!assembly.FromWildCard) + { + throw new NUnitEngineException($"Extension {assembly.FilePath} targets {assemblyTargetFramework.DisplayName}, which is not available."); + } + else + { + log.Info($"Assembly {assembly.FilePath} targets {assemblyTargetFramework.DisplayName}, which is not available. Assembly found via wildcard."); + return; + } + } + foreach (var type in assembly.MainModule.GetTypes()) { CustomAttribute extensionAttr = type.GetAttribute("NUnit.Engine.Extensibility.ExtensionAttribute"); From 2fbe6a57943f2b17d5f20ea8f5099bda983201ad Mon Sep 17 00:00:00 2001 From: Chris Maddock Date: Sat, 18 Aug 2018 14:35:58 +0100 Subject: [PATCH 3/8] Expose RuntimeFramework through ExtensionNode interface and display in console --- src/NUnitConsole/nunit3-console/ConsoleRunner.cs | 2 +- .../nunit.engine.api/Extensibility/IExtensionNode.cs | 5 +++++ .../nunit.engine/Extensibility/ExtensionNode.cs | 9 ++++++++- .../nunit.engine/Services/ExtensionService.cs | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs index 8b7e065ec..99f4d2f62 100644 --- a/src/NUnitConsole/nunit3-console/ConsoleRunner.cs +++ b/src/NUnitConsole/nunit3-console/ConsoleRunner.cs @@ -313,7 +313,7 @@ private void DisplayExtensionList() foreach (var node in ep.Extensions) { _outWriter.Write(" Extension: "); - _outWriter.Write(ColorStyle.Value, node.TypeName); + _outWriter.Write(ColorStyle.Value, $"{node.TypeName} (.NET {node.TargetFramework.FrameworkVersion})"); _outWriter.WriteLine(node.Enabled ? "" : " (Disabled)"); foreach (var prop in node.PropertyNames) { diff --git a/src/NUnitEngine/nunit.engine.api/Extensibility/IExtensionNode.cs b/src/NUnitEngine/nunit.engine.api/Extensibility/IExtensionNode.cs index 218bc794f..0c0c2cadb 100644 --- a/src/NUnitEngine/nunit.engine.api/Extensibility/IExtensionNode.cs +++ b/src/NUnitEngine/nunit.engine.api/Extensibility/IExtensionNode.cs @@ -55,6 +55,11 @@ public interface IExtensionNode /// string Description { get; } + /// + /// The TargetFramework of the extension assembly. + /// + IRuntimeFramework TargetFramework { get; } + /// /// Gets a collection of the names of all this extension's properties /// diff --git a/src/NUnitEngine/nunit.engine/Extensibility/ExtensionNode.cs b/src/NUnitEngine/nunit.engine/Extensibility/ExtensionNode.cs index d758eb768..f37d67c4f 100644 --- a/src/NUnitEngine/nunit.engine/Extensibility/ExtensionNode.cs +++ b/src/NUnitEngine/nunit.engine/Extensibility/ExtensionNode.cs @@ -42,10 +42,12 @@ public class ExtensionNode : IExtensionNode /// /// The path to the assembly where this extension is found. /// The full name of the Type of the extension object. - public ExtensionNode(string assemblyPath, string typeName) + /// The target framework of the extension assembly. + public ExtensionNode(string assemblyPath, string typeName, IRuntimeFramework targetFramework) { AssemblyPath = assemblyPath; TypeName = typeName; + TargetFramework = targetFramework; Enabled = true; // By default } @@ -56,6 +58,11 @@ public ExtensionNode(string assemblyPath, string typeName) /// public string TypeName { get; private set; } + /// + /// The TargetFramework of the extension assembly. + /// + public IRuntimeFramework TargetFramework { get; } + /// /// Gets or sets a value indicating whether this is enabled. /// diff --git a/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs b/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs index 499e09a76..a665a8011 100644 --- a/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs +++ b/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs @@ -451,7 +451,7 @@ internal void FindExtensionsInAssembly(ExtensionAssembly assembly) if (versionArg != null && new Version((string)versionArg) > ENGINE_VERSION) continue; - var node = new ExtensionNode(assembly.FilePath, type.FullName); + var node = new ExtensionNode(assembly.FilePath, type.FullName, assemblyTargetFramework); node.Path = extensionAttr.GetNamedArgument("Path") as string; node.Description = extensionAttr.GetNamedArgument("Description") as string; From a9331acd92d28c0978b2477a259a232ff3f9350e Mon Sep 17 00:00:00 2001 From: Chris Maddock Date: Sat, 18 Aug 2018 14:36:27 +0100 Subject: [PATCH 4/8] Refactor/Add tests --- .../ExtensionAssemblyTests.cs | 70 +++------ .../Extensibility/ExtensionSelectorTests.cs | 140 ++++++++++++++++++ .../nunit.engine/Properties/AssemblyInfo.cs | 8 +- 3 files changed, 164 insertions(+), 54 deletions(-) rename src/NUnitEngine/nunit.engine.tests/{Services => Extensibility}/ExtensionAssemblyTests.cs (51%) create mode 100644 src/NUnitEngine/nunit.engine.tests/Extensibility/ExtensionSelectorTests.cs diff --git a/src/NUnitEngine/nunit.engine.tests/Services/ExtensionAssemblyTests.cs b/src/NUnitEngine/nunit.engine.tests/Extensibility/ExtensionAssemblyTests.cs similarity index 51% rename from src/NUnitEngine/nunit.engine.tests/Services/ExtensionAssemblyTests.cs rename to src/NUnitEngine/nunit.engine.tests/Extensibility/ExtensionAssemblyTests.cs index 1ff0aec72..6dd9c1b2e 100644 --- a/src/NUnitEngine/nunit.engine.tests/Services/ExtensionAssemblyTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/Extensibility/ExtensionAssemblyTests.cs @@ -22,96 +22,60 @@ // *********************************************************************** using System; -using System.Collections.Generic; using System.Reflection; -using System.Text; +using NUnit.Engine.Extensibility; using NUnit.Framework; -namespace NUnit.Engine.Services.Tests +namespace NUnit.Engine.Tests.Extensibility { public class ExtensionAssemblyTests { private static readonly Assembly THIS_ASSEMBLY = Assembly.GetExecutingAssembly(); private static readonly string THIS_ASSEMBLY_PATH = THIS_ASSEMBLY.Location; - private static readonly string THIS_ASSEMBLY_NAME = THIS_ASSEMBLY.GetName().FullName; - private static readonly Assembly ENGINE_ASSEMBLY = Assembly.GetAssembly(typeof(ExtensionAssembly)); - private static readonly string ENGINE_ASSEMBLY_PATH = ENGINE_ASSEMBLY.Location; + private static readonly string THIS_ASSEMBLY_FULL_NAME = THIS_ASSEMBLY.GetName().FullName; + private static readonly string THIS_ASSEMBLY_NAME = THIS_ASSEMBLY.GetName().Name; + private static readonly Version THIS_ASSEMBLY_VERSION = THIS_ASSEMBLY.GetName().Version; private ExtensionAssembly _ea; - private ExtensionAssembly _eaCopy; - private ExtensionAssembly _eaBetter; - private ExtensionAssembly _eaOther; - [SetUp] + [OneTimeSetUp] public void CreateExtensionAssemblies() { _ea = new ExtensionAssembly(THIS_ASSEMBLY_PATH, false); - _eaCopy = new ExtensionAssembly(THIS_ASSEMBLY_PATH, false); - _eaBetter = new ExtensionAssembly(THIS_ASSEMBLY_PATH, false); - _eaBetter.Assembly.Name.Version = new Version(7, 0); - _eaOther = new ExtensionAssembly(ENGINE_ASSEMBLY_PATH, false); } [Test] public void AssemblyDefinition() { - Assert.That(_ea.Assembly.FullName, Is.EqualTo(THIS_ASSEMBLY_NAME)); + Assert.That(_ea.Assembly.FullName, Is.EqualTo(THIS_ASSEMBLY_FULL_NAME)); } [Test] public void MainModule() { - Assert.That(_ea.MainModule.Assembly.FullName, Is.EqualTo(THIS_ASSEMBLY_NAME)); + Assert.That(_ea.MainModule.Assembly.FullName, Is.EqualTo(THIS_ASSEMBLY_FULL_NAME)); } [Test] public void AssemblyName() { - Assert.That(_ea.AssemblyName.FullName, Is.EqualTo(THIS_ASSEMBLY_NAME)); + Assert.That(_ea.AssemblyName, Is.EqualTo(THIS_ASSEMBLY_NAME)); } [Test] - public void IsDuplicateOf_SameAssembly() + public void AssemblyVersion() { - Assert.That(_ea.IsDuplicateOf(_eaCopy)); - Assert.That(_eaCopy.IsDuplicateOf(_ea)); + Assert.That(_ea.AssemblyVersion, Is.EqualTo(THIS_ASSEMBLY_VERSION)); } [Test] - public void IsDuplicateOf_DifferentAssembly() + public void TargetFramework() { - Assert.False(_ea.IsDuplicateOf(_eaOther)); - Assert.False(_eaOther.IsDuplicateOf(_ea)); + Assert.Multiple(() => + { + Assert.That(_ea.TargetFramework, Has.Property(nameof(RuntimeFramework.Runtime)).EqualTo(RuntimeType.Any)); + Assert.That(_ea.TargetFramework, Has.Property(nameof(RuntimeFramework.FrameworkVersion)).EqualTo(new Version(2, 0))); + }); } - - [Test] - public void IsBetterVersionOf_DifferentVersions() - { - Assert.That(_eaBetter.IsBetterVersionOf(_ea)); - Assert.False(_ea.IsBetterVersionOf(_eaBetter)); - } - - [Test] - public void IsBetterVersionOf_SameVersion() - { - Assert.False(_ea.IsBetterVersionOf(_eaCopy)); - Assert.False(_eaCopy.IsBetterVersionOf(_ea)); - } - - [Test] - public void IsBetterVersionOf_SameVersion_OneWildCard() - { - var eaWild = new ExtensionAssembly(THIS_ASSEMBLY_PATH, true); - Assert.True(_ea.IsBetterVersionOf(eaWild)); - Assert.False(eaWild.IsBetterVersionOf(_ea)); - } - -#if DEBUG - [Test] - public void IsBetterVersionOf_ThrowsIfNotDuplicates() - { - Assert.That(() => { _ea.IsBetterVersionOf(_eaOther); }, Throws.TypeOf()); - } -#endif } } diff --git a/src/NUnitEngine/nunit.engine.tests/Extensibility/ExtensionSelectorTests.cs b/src/NUnitEngine/nunit.engine.tests/Extensibility/ExtensionSelectorTests.cs new file mode 100644 index 000000000..53d0c5585 --- /dev/null +++ b/src/NUnitEngine/nunit.engine.tests/Extensibility/ExtensionSelectorTests.cs @@ -0,0 +1,140 @@ +// *********************************************************************** +// Copyright (c) 2018 Charlie Poole, Rob Prouse +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + + +using System; +using NSubstitute; +using NUnit.Engine.Extensibility; +using NUnit.Framework; + +namespace NUnit.Engine.Tests.Extensibility +{ + internal class ExtensionSelectorTests + { + [Test] + public void IsDuplicateOfWithSame() + { + var first = MockExtension("Extension1"); + var second = MockExtension("Extension1"); + Assert.Multiple(() => + { + Assert.That(first.IsDuplicateOf(second), Is.True); + Assert.That(second.IsDuplicateOf(first), Is.True); + }); + } + + [Test] + public void IsDuplicateOfWithDifferent() + { + var first = MockExtension("Extension1"); + var second = MockExtension("Extension2"); + Assert.Multiple(() => + { + Assert.That(first.IsDuplicateOf(second), Is.False); + Assert.That(second.IsDuplicateOf(first), Is.False); + }); + } + + [Test] + public void IsBetterVersionOfThrowsWhenNotDuplicates() + { + var first = MockExtension("Extension1"); + var second = MockExtension("Extension2"); + Assert.That(() => first.IsBetterVersionOf(second), Throws.InvalidOperationException); + } + + [Test] + public void IsBetterVersionOfChoosesHighestAssemblyVersion() + { + var first = MockExtension(assemblyVersion: new Version(2, 0)); + var second = MockExtension(assemblyVersion: new Version(4, 7)); + Assert.Multiple(() => + { + Assert.That(first.IsBetterVersionOf(second), Is.False); + Assert.That(second.IsBetterVersionOf(first), Is.True); + }); + } + + [Test] + public void IsBetterVersionOfChoosesHighestTargetFramework() + { + var first = MockExtension(targetFramework: new Version(2, 0)); + var second = MockExtension(targetFramework: new Version(4, 7)); + Assert.Multiple(() => + { + Assert.That(first.IsBetterVersionOf(second), Is.False); + Assert.That(second.IsBetterVersionOf(first), Is.True); + }); + } + + [Test] + public void IsBetterVersionOfPrioritisesAssemblyVersionOverTargetFramework() + { + var first = MockExtension(assemblyVersion: new Version(2, 0), targetFramework: new Version(2, 0)); + var second = MockExtension(assemblyVersion: new Version(1, 0), targetFramework: new Version(4, 7)); + Assert.Multiple(() => + { + Assert.That(first.IsBetterVersionOf(second), Is.True); + Assert.That(second.IsBetterVersionOf(first), Is.False); + }); + } + + [Test] + public void IsBetterVersionOfPrefersDirectlySpecifiedToWildcard() + { + var first = MockExtension(fromWildcard: false); + var second = MockExtension(fromWildcard: true); + Assert.Multiple(() => + { + Assert.That(first.IsBetterVersionOf(second), Is.True); + Assert.That(second.IsBetterVersionOf(first), Is.False); + }); + } + + [Test] + public void IsBetterVersionOfPrefersNoChangeIfFromWildcard() + { + var first = MockExtension(fromWildcard: true); + var second = MockExtension(fromWildcard: true); + Assert.Multiple(() => + { + Assert.That(first.IsBetterVersionOf(second), Is.False); + Assert.That(second.IsBetterVersionOf(first), Is.False); + }); + } + + private static IExtensionAssembly MockExtension(string assemblyName = "ExtensionSelectorTestsExtension", + Version assemblyVersion = null, + Version targetFramework = null, + bool fromWildcard = false) + { + var sub = Substitute.For(); + sub.AssemblyName.Returns(assemblyName); + sub.AssemblyVersion.Returns(assemblyVersion ?? new Version(1, 0)); + targetFramework = targetFramework ?? new Version(2, 0); + sub.TargetFramework.Returns(new RuntimeFramework(RuntimeType.Any, targetFramework)); + sub.FromWildCard.Returns(fromWildcard); + return sub; + } + } +} diff --git a/src/NUnitEngine/nunit.engine/Properties/AssemblyInfo.cs b/src/NUnitEngine/nunit.engine/Properties/AssemblyInfo.cs index 8a5ff7b2f..4b7d5b955 100644 --- a/src/NUnitEngine/nunit.engine/Properties/AssemblyInfo.cs +++ b/src/NUnitEngine/nunit.engine/Properties/AssemblyInfo.cs @@ -27,4 +27,10 @@ "fa6d1ea760e1ca6065cee41a1a279ca234933fe977a096222c0e14f9e5a17d5689305c6d7f1206"+ "a85a53c48ca010080799d6eeef61c98abd18767827dc05daea6b6fbd2e868410d9bee5e972a004"+ "ddd692dec8fa404ba4591e847a8cf35de21c2d3723bc8d775a66b594adeb967537729fe2a446b5"+ - "48cd57a6")] \ No newline at end of file + "48cd57a6")] + +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2,PublicKey=002400000480000094" + + "0000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602" + + "f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac" + + "1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924c" + + "ceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] From bd396e7a8946325cb3fd20d4b2e4ca94645449a0 Mon Sep 17 00:00:00 2001 From: Chris Maddock Date: Sat, 18 Aug 2018 14:36:38 +0100 Subject: [PATCH 5/8] Misc formatting/copyright improvements --- src/NUnitEngine/nunit.engine/RuntimeType.cs | 25 ++++++++++++++++++- .../nunit.engine/Services/ExtensionService.cs | 14 +++-------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/NUnitEngine/nunit.engine/RuntimeType.cs b/src/NUnitEngine/nunit.engine/RuntimeType.cs index f2b2b0206..96759ebb5 100644 --- a/src/NUnitEngine/nunit.engine/RuntimeType.cs +++ b/src/NUnitEngine/nunit.engine/RuntimeType.cs @@ -1,4 +1,27 @@ -namespace NUnit.Engine +// *********************************************************************** +// Copyright (c) 2018 Charlie Poole, Rob Prouse +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// *********************************************************************** + +namespace NUnit.Engine { /// /// Enumeration identifying a common language diff --git a/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs b/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs index a665a8011..835d292f8 100644 --- a/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs +++ b/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs @@ -42,11 +42,11 @@ public class ExtensionService : Service, IExtensionService static Logger log = InternalTrace.GetLogger(typeof(ExtensionService)); static readonly Version ENGINE_VERSION = typeof(TestEngine).Assembly.GetName().Version; - private List _extensionPoints = new List(); - private Dictionary _pathIndex = new Dictionary(); + private readonly List _extensionPoints = new List(); + private readonly Dictionary _pathIndex = new Dictionary(); - private List _extensions = new List(); - private List _assemblies = new List(); + private readonly List _extensions = new List(); + private readonly List _assemblies = new List(); #region IExtensionService Members @@ -294,12 +294,6 @@ private void FindExtensionAssemblies(DirectoryInfo startDir) { // First check the directory itself ProcessAddinsFiles(startDir, false); - - //// Use any packages directory we find as well - //var packageDir = DirectoryFinder.GetPackageDirectory(startDir); - //if (packageDir != null) - // foreach (var dir in DirectoryFinder.GetDirectories(packageDir, "NUnit.Extension.*/**/tools/")) - // ProcessDirectory(dir, false); } /// From ec6c8774893e50b0d21b0182456fff3577845e7e Mon Sep 17 00:00:00 2001 From: Chris Maddock Date: Sat, 18 Aug 2018 16:11:59 +0100 Subject: [PATCH 6/8] Add RuntimeFramework.CanLoad() method --- .../RuntimeFrameworkTests.cs | 31 ++++++++++++++++--- .../nunit.engine/RuntimeFramework.cs | 5 +++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/NUnitEngine/nunit.engine.tests/RuntimeFrameworkTests.cs b/src/NUnitEngine/nunit.engine.tests/RuntimeFrameworkTests.cs index ef6d12b89..094ac50fb 100644 --- a/src/NUnitEngine/nunit.engine.tests/RuntimeFrameworkTests.cs +++ b/src/NUnitEngine/nunit.engine.tests/RuntimeFrameworkTests.cs @@ -88,7 +88,7 @@ public void AvailableFrameworksList_ContainsNoDuplicates() Assert.That(names, Is.Unique); } - [TestCaseSource("frameworkData")] + [TestCaseSource(nameof(frameworkData))] public void CanCreateUsingFrameworkVersion(FrameworkData data) { RuntimeFramework framework = new RuntimeFramework(data.runtime, data.frameworkVersion); @@ -97,7 +97,7 @@ public void CanCreateUsingFrameworkVersion(FrameworkData data) Assert.AreEqual(data.clrVersion, framework.ClrVersion); } - [TestCaseSource("frameworkData")] + [TestCaseSource(nameof(frameworkData))] public void CanCreateUsingClrVersion(FrameworkData data) { Assume.That(data.frameworkVersion.Major != 3); @@ -108,7 +108,7 @@ public void CanCreateUsingClrVersion(FrameworkData data) Assert.AreEqual(data.clrVersion, framework.ClrVersion); } - [TestCaseSource("frameworkData")] + [TestCaseSource(nameof(frameworkData))] public void CanParseRuntimeFramework(FrameworkData data) { RuntimeFramework framework = RuntimeFramework.Parse(data.representation); @@ -116,7 +116,7 @@ public void CanParseRuntimeFramework(FrameworkData data) Assert.AreEqual(data.clrVersion, framework.ClrVersion); } - [TestCaseSource("frameworkData")] + [TestCaseSource(nameof(frameworkData))] public void CanDisplayFrameworkAsString(FrameworkData data) { RuntimeFramework framework = new RuntimeFramework(data.runtime, data.frameworkVersion); @@ -124,12 +124,18 @@ public void CanDisplayFrameworkAsString(FrameworkData data) Assert.AreEqual(data.displayName, framework.DisplayName); } - [TestCaseSource("matchData")] + [TestCaseSource(nameof(matchData))] public bool CanMatchRuntimes(RuntimeFramework f1, RuntimeFramework f2) { return f1.Supports(f2); } + [TestCaseSource(nameof(CanLoadData))] + public bool CanLoad(RuntimeFramework f1, RuntimeFramework f2) + { + return f1.CanLoad(f2); + } + #pragma warning disable 414 static TestCaseData[] matchData = new TestCaseData[] { new TestCaseData( @@ -209,6 +215,21 @@ public bool CanMatchRuntimes(RuntimeFramework f1, RuntimeFramework f2) new RuntimeFramework(RuntimeType.Any, RuntimeFramework.DefaultVersion)) .Returns(true) }; + + private static readonly TestCaseData[] CanLoadData = { + new TestCaseData( + new RuntimeFramework(RuntimeType.Any, new Version(2,0)), + new RuntimeFramework(RuntimeType.Any, new Version(2,0))) + .Returns(true), + new TestCaseData( + new RuntimeFramework(RuntimeType.Any, new Version(2,0)), + new RuntimeFramework(RuntimeType.Any, new Version(4,0))) + .Returns(false), + new TestCaseData( + new RuntimeFramework(RuntimeType.Any, new Version(4,0)), + new RuntimeFramework(RuntimeType.Any, new Version(2,0))) + .Returns(true) + }; #pragma warning restore 414 public struct FrameworkData diff --git a/src/NUnitEngine/nunit.engine/RuntimeFramework.cs b/src/NUnitEngine/nunit.engine/RuntimeFramework.cs index 0b98d6aa9..471b13d5b 100644 --- a/src/NUnitEngine/nunit.engine/RuntimeFramework.cs +++ b/src/NUnitEngine/nunit.engine/RuntimeFramework.cs @@ -488,6 +488,11 @@ public bool Supports(RuntimeFramework target) && this.FrameworkVersion.Minor >= target.FrameworkVersion.Minor; } + public bool CanLoad(RuntimeFramework requested) + { + return FrameworkVersion >= requested.FrameworkVersion; + } + #endregion #region Helper Methods - General From bd3517066939dff5d6ecf2b2415457b4ddb73313 Mon Sep 17 00:00:00 2001 From: Chris Maddock Date: Sat, 18 Aug 2018 16:12:26 +0100 Subject: [PATCH 7/8] Extension service to consider "CanLoad" rather than supports() --- src/NUnitEngine/nunit.engine/Services/ExtensionService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs b/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs index 835d292f8..5221bbb31 100644 --- a/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs +++ b/src/NUnitEngine/nunit.engine/Services/ExtensionService.cs @@ -420,8 +420,9 @@ internal void FindExtensionsInAssembly(ExtensionAssembly assembly) { log.Info("Scanning {0} assembly for Extensions", assembly.FilePath); + var currentFramework = RuntimeFramework.CurrentFramework; var assemblyTargetFramework = assembly.TargetFramework; - if (!assemblyTargetFramework.IsAvailable) + if (!currentFramework.CanLoad(assemblyTargetFramework)) { if (!assembly.FromWildCard) { From c670356b3ff2afe13648ed35e17163a6056f3aff Mon Sep 17 00:00:00 2001 From: Chris Maddock Date: Sat, 18 Aug 2018 23:58:07 +0100 Subject: [PATCH 8/8] Add comment --- src/NUnitEngine/nunit.engine/Properties/AssemblyInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NUnitEngine/nunit.engine/Properties/AssemblyInfo.cs b/src/NUnitEngine/nunit.engine/Properties/AssemblyInfo.cs index 4b7d5b955..22af054ee 100644 --- a/src/NUnitEngine/nunit.engine/Properties/AssemblyInfo.cs +++ b/src/NUnitEngine/nunit.engine/Properties/AssemblyInfo.cs @@ -29,6 +29,7 @@ "ddd692dec8fa404ba4591e847a8cf35de21c2d3723bc8d775a66b594adeb967537729fe2a446b5"+ "48cd57a6")] +//Allow NSubstitute to mock out internal types [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2,PublicKey=002400000480000094" + "0000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602" + "f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac" +