Skip to content

Commit 99d0dee

Browse files
authored
Merge pull request #1755 from nunit/issue-1754
Don't throw for unknown extensions; other API changes
2 parents 1ac9edb + e4ba430 commit 99d0dee

File tree

8 files changed

+165
-89
lines changed

8 files changed

+165
-89
lines changed

src/NUnitCommon/nunit.extensibility.api/IExtensionNode.cs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@ namespace NUnit.Extensibility
99
/// The IExtensionNode interface is implemented by a class that represents a
1010
/// single extension being installed on a particular extension point.
1111
/// </summary>
12+
public enum ExtensionStatus
13+
{
14+
/// <summary>Extension is not yet loaded</summary>
15+
Unloaded,
16+
/// <summary>Extension has been loaded</summary>
17+
Loaded,
18+
/// <summary>An error occurred trying to load the extension</summary>
19+
Error,
20+
/// <summary>Extension without a corresponding path./summary>
21+
Unknown,
22+
}
23+
1224
public interface IExtensionNode
1325
{
1426
/// <summary>
@@ -22,6 +34,16 @@ public interface IExtensionNode
2234
/// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value>
2335
bool Enabled { get; }
2436

37+
/// <summary>
38+
/// Status of this extension.
39+
/// </summary>
40+
ExtensionStatus Status { get; }
41+
42+
/// <summary>
43+
/// Exception thrown in creating the ExtensionObject, if Status is error, otherwise null.
44+
/// </summary>
45+
Exception? Exception { get; }
46+
2547
/// <summary>
2648
/// Gets the unique string identifying the ExtensionPoint for which
2749
/// this Extension is intended. This identifier may be supplied by the attribute
@@ -39,14 +61,6 @@ public interface IExtensionNode
3961
/// </summary>
4062
IEnumerable<string> PropertyNames { get; }
4163

42-
/// <summary>
43-
/// Gets a collection of the values of a particular named property
44-
/// If none are present, returns an empty enumerator.
45-
/// </summary>
46-
/// <param name="name">The property name</param>
47-
/// <returns>A collection of values</returns>
48-
IEnumerable<string> GetValues(string name);
49-
5064
/// <summary>
5165
/// The path to the assembly implementing this extension.
5266
/// </summary>
@@ -56,5 +70,13 @@ public interface IExtensionNode
5670
/// The version of the assembly implementing this extension.
5771
/// </summary>
5872
Version AssemblyVersion { get; }
73+
74+
/// <summary>
75+
/// Gets a collection of the values of a particular named property.
76+
/// If none are present, returns an empty enumerator.
77+
/// </summary>
78+
/// <param name="name">The property name</param>
79+
/// <returns>A collection of values</returns>
80+
IEnumerable<string> GetValues(string name);
5981
}
6082
}

src/NUnitCommon/nunit.extensibility.tests/ExtensionManagerTests.cs

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.IO;
99
using System.Reflection;
1010
using System.Collections.Generic;
11+
using System.Net;
1112

1213
namespace NUnit.Extensibility
1314
{
@@ -18,9 +19,11 @@ public class ExtensionManagerTests
1819
private static readonly string THIS_ASSEMBLY_DIRECTORY = Path.GetDirectoryName(THIS_ASSEMBLY.Location)!;
1920
private const string FAKE_EXTENSIONS_FILENAME = "FakeExtensions.dll";
2021
private static readonly string FAKE_EXTENSIONS_PARENT_DIRECTORY =
21-
Path.Combine(new DirectoryInfo(THIS_ASSEMBLY_DIRECTORY).Parent!.FullName, "fakesv2");
22-
private static readonly string FAKE_EXTENSIONS_SOURCE_DIRECTORY =
23-
Path.Combine(new DirectoryInfo(THIS_ASSEMBLY_DIRECTORY).Parent!.Parent!.Parent!.FullName, "src/TestData/FakeExtensions");
22+
#if NETFRAMEWORK
23+
Path.Combine(new DirectoryInfo(THIS_ASSEMBLY_DIRECTORY).Parent!.FullName, "fakesv2/net462");
24+
#else
25+
Path.Combine(new DirectoryInfo(THIS_ASSEMBLY_DIRECTORY).Parent!.FullName, "fakesv2/netstandard2.0");
26+
#endif
2427

2528
private const string FAKE_AGENT_LAUNCHER_EXTENSION = "NUnit.Engine.Fakes.FakeAgentLauncherExtension";
2629
private const string FAKE_FRAMEWORK_DRIVER_EXTENSION = "NUnit.Engine.Fakes.FakeFrameworkDriverExtension";
@@ -30,6 +33,7 @@ public class ExtensionManagerTests
3033
private const string FAKE_SERVICE_EXTENSION = "NUnit.Engine.Fakes.FakeServiceExtension";
3134
private const string FAKE_DISABLED_EXTENSION = "NUnit.Engine.Fakes.FakeDisabledExtension";
3235
private const string FAKE_NUNIT_V2_DRIVER_EXTENSION = "NUnit.Engine.Fakes.V2DriverExtension";
36+
private const string FAKE_EXTENSION_WITH_NO_EXTENSION_POINT = "NUnit.Engine.Fakes.FakeExtension_NoExtensionPointFound";
3337

3438
private readonly string[] KnownExtensions =
3539
{
@@ -39,8 +43,9 @@ public class ExtensionManagerTests
3943
FAKE_RESULT_WRITER_EXTENSION,
4044
FAKE_EVENT_LISTENER_EXTENSION,
4145
FAKE_SERVICE_EXTENSION,
42-
FAKE_DISABLED_EXTENSION
43-
//FAKE_NUNIT_V2_DRIVER_EXTENSION
46+
FAKE_DISABLED_EXTENSION,
47+
//FAKE_NUNIT_V2_DRIVER_EXTENSION,
48+
FAKE_EXTENSION_WITH_NO_EXTENSION_POINT
4449
};
4550

4651
private ExtensionManager _extensionManager;
@@ -94,7 +99,7 @@ public void CreateExtensionManager()
9499
_extensionManager.FindExtensionPoints(typeof(ITestEngine).Assembly);
95100

96101
// Find Fake Extensions using alternate start directory
97-
_extensionManager.FindExtensionAssemblies(FAKE_EXTENSIONS_SOURCE_DIRECTORY);
102+
_extensionManager.FindExtensionAssemblies(FAKE_EXTENSIONS_PARENT_DIRECTORY);
98103
_extensionManager.LoadExtensions();
99104
}
100105

@@ -121,6 +126,18 @@ public void AllExtensionsUseTheLatestVersion()
121126
Assert.That(node.AssemblyVersion.ToString(), Is.EqualTo("2.0.0.0"));
122127
}
123128

129+
[Test]
130+
public void AllExtensionsHaveCorrectStatus()
131+
{
132+
foreach (var node in _extensionManager.Extensions)
133+
{
134+
var expectedStatus = node.TypeName == FAKE_EXTENSION_WITH_NO_EXTENSION_POINT
135+
? ExtensionStatus.Unknown
136+
: ExtensionStatus.Unloaded;
137+
Assert.That(node.Status, Is.EqualTo(expectedStatus));
138+
}
139+
}
140+
124141
[Test]
125142
public void AllKnownExtensionsAreEnabledAsRequired()
126143
{
@@ -216,20 +233,20 @@ public void SkipsGracefullyLoadingOtherFrameworkExtensionAssembly()
216233

217234
#if NETCOREAPP
218235
[TestCase("netstandard2.0", ExpectedResult = true)]
219-
[TestCase("net462", ExpectedResult = false)]
236+
//[TestCase("net462", ExpectedResult = false)]
220237
//[TestCase("net20", ExpectedResult = false)]
221238
#elif NET40_OR_GREATER
222-
[TestCase("netstandard2.0", ExpectedResult = false)]
239+
//[TestCase("netstandard2.0", ExpectedResult = false)]
223240
[TestCase("net462", ExpectedResult = true)]
224241
//[TestCase("net20", ExpectedResult = true)]
225242
#else
226-
[TestCase("netstandard2.0", ExpectedResult = false)]
227-
[TestCase("net462", ExpectedResult = false)]
243+
//[TestCase("netstandard2.0", ExpectedResult = false)]
244+
//[TestCase("net462", ExpectedResult = false)]
228245
//[TestCase("net20", ExpectedResult = true)]
229246
#endif
230247
public bool LoadTargetFramework(string tfm)
231248
{
232-
return _extensionManager.CanLoadTargetFramework(THIS_ASSEMBLY, FakeExtensions(tfm));
249+
return _extensionManager.CanLoadTargetFramework(THIS_ASSEMBLY, FakeExtensions());
233250
}
234251

235252
//[TestCaseSource(nameof(ValidCombos))]
@@ -351,10 +368,10 @@ private static string GetSiblingDirectory(string dir)
351368
/// assembly based on the argument provided.
352369
/// </summary>
353370
/// <param name="tfm">A test framework moniker. Must be one for which the fake extensions are built.</param>
354-
private static ExtensionAssembly FakeExtensions(string tfm)
371+
private static ExtensionAssembly FakeExtensions()
355372
{
356373
return new ExtensionAssembly(
357-
Path.Combine(FAKE_EXTENSIONS_PARENT_DIRECTORY, Path.Combine(tfm, FAKE_EXTENSIONS_FILENAME)), false);
374+
Path.Combine(FAKE_EXTENSIONS_PARENT_DIRECTORY, FAKE_EXTENSIONS_FILENAME), false);
358375
}
359376
}
360377
}

src/NUnitCommon/nunit.extensibility/ExtensionManager.cs

Lines changed: 9 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -547,25 +547,19 @@ public void FindExtensionsInAssembly(ExtensionAssembly extensionAssembly)
547547

548548
_extensions.Add(node);
549549

550-
ExtensionPoint? ep;
551-
if (extensionAttrPath is null)
552-
{
553-
ep = DeduceExtensionPointFromType(extensionType);
554-
if (ep is null)
555-
throw new ExtensibilityException($"Unable to deduce ExtensionPoint for Type {extensionType.FullName}. Specify Path on ExtensionAttribute to resolve.");
550+
ExtensionPoint? ep = extensionAttrPath is not null
551+
? GetExtensionPoint(extensionAttrPath)
552+
: DeduceExtensionPointFromType(extensionType);
556553

557-
node.Path = ep.Path;
558-
}
559-
else
554+
if (ep is null)
560555
{
561-
node.Path = extensionAttrPath;
562-
563-
// TODO: Remove need for the cast
564-
ep = GetExtensionPoint(node.Path) as ExtensionPoint;
565-
if (ep is null)
566-
throw new ExtensibilityException($"Unable to locate ExtensionPoint for Type {extensionType.FullName}. The Path {node.Path} cannot be found.");
556+
log.Warning($"Extension ignored - Unable to deduce ExtensionPoint.");
557+
node.Status = ExtensionStatus.Unknown;
558+
node.Exception = new Exception("Unable to deduce ExtensionPoint");
559+
continue;
567560
}
568561

562+
node.Path = ep.Path;
569563
ep.Install(node);
570564
}
571565
}
@@ -619,28 +613,6 @@ public bool CanLoadTargetFramework(Assembly? runnerAsm, ExtensionAssembly extens
619613
return false;
620614
}
621615
}
622-
623-
//string extensionFrameworkName = AssemblyDefinition.ReadAssembly(extensionAsm.FilePath).GetFrameworkName();
624-
//string runnerFrameworkName = AssemblyDefinition.ReadAssembly(runnerAsm.Location).GetFrameworkName();
625-
//if (runnerFrameworkName?.StartsWith(".NETStandard") == true)
626-
//{
627-
// throw new NUnitEngineException($"{runnerAsm.FullName} test runner must target .NET Core or .NET Framework, not .NET Standard");
628-
//}
629-
//else if (runnerFrameworkName?.StartsWith(".NETCoreApp") == true)
630-
//{
631-
// if (extensionFrameworkName?.StartsWith(".NETStandard") != true && extensionFrameworkName?.StartsWith(".NETCoreApp") != true)
632-
// {
633-
// log.Info($".NET Core runners require .NET Core or .NET Standard extension for {extensionAsm.FilePath}");
634-
// return false;
635-
// }
636-
//}
637-
//else if (extensionFrameworkName?.StartsWith(".NETCoreApp") == true)
638-
//{
639-
// log.Info($".NET Framework runners cannot load .NET Core extension {extensionAsm.FilePath}");
640-
// return false;
641-
//}
642-
643-
//return true;
644616
}
645617

646618
private static System.Runtime.Versioning.FrameworkName GetTargetRuntime(string filePath)

src/NUnitCommon/nunit.extensibility/ExtensionNode.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public ExtensionNode(ExtensionAssembly extensionAssembly, TypeDefinition extensi
3131
AssemblyPath = extensionAssembly.FilePath;
3232
AssemblyVersion = extensionAssembly.AssemblyVersion;
3333
TypeName = extensionType.FullName;
34+
Status = ExtensionStatus.Unloaded;
3435
Enabled = true; // By default
3536
}
3637

@@ -62,6 +63,16 @@ public ExtensionNode(ExtensionAssembly extensionAssembly, TypeDefinition extensi
6263
/// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value>
6364
public bool Enabled { get; set; }
6465

66+
/// <summary>
67+
/// Status of this extension
68+
/// </summary>
69+
public ExtensionStatus Status { get; set; }
70+
71+
/// <summary>
72+
/// Exception thrown in creating the ExtensionObject, if Status is error, otherwise null.
73+
/// </summary>
74+
public Exception? Exception { get; set; }
75+
6576
/// <summary>
6677
/// Gets and sets the unique string identifying the ExtensionPoint for which
6778
/// this Extension is intended. This identifier may be supplied by the attribute
@@ -127,13 +138,18 @@ public object CreateExtensionObject(params object[] args)
127138
object obj = Activator.CreateInstance(type, args)!;
128139
#endif
129140

141+
// TODO: Determine whether to continue support for V3 extensions here,
142+
// defer it to the engine or eliminate it entirely.
130143
return IsV3Extension
131144
? ExtensionWrapper.Wrap(obj, Path)
132145
: obj;
133146
}
134147

135148
#endregion
136149

150+
/// <summary>
151+
/// Used by ExtensionManger to add a value to the node's properties collection.
152+
/// </summary>
137153
public void AddProperty(string name, string val)
138154
{
139155
if (_properties.TryGetValue(name, out List<string>? list))
@@ -145,6 +161,9 @@ public void AddProperty(string name, string val)
145161
}
146162
}
147163

164+
/// <summary>
165+
/// Gets the string representation of this node.
166+
/// </summary>
148167
public override string ToString()
149168
{
150169
return $"{TypeName} - {Path}";

0 commit comments

Comments
 (0)