Skip to content

Commit

Permalink
Allow enabling and disabling extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
CharliePoole committed Feb 23, 2025
1 parent eeccd37 commit f9fd4a7
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 63 deletions.
17 changes: 14 additions & 3 deletions package-tests.cake
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,23 @@ public static class PackageTests
});

// TeamCity Event Listener Test
StandardAndZipLists.Add(new PackageTest(1, "TeamCityListenerTest")
StandardAndZipLists.Add(new PackageTest(1, "TeamCityListenerTest1")
{
Description = "Run mock-assembly with --teamcity enabled",
Description = "Run mock-assembly with --teamcity option",
Arguments = "testdata/net462/mock-assembly.dll --teamcity",
ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2"),
ExtensionsNeeded = new[] { KnownExtensions.TeamCityEventListener }
ExtensionsNeeded = new[] { KnownExtensions.TeamCityEventListener },
OutputCheck = new OutputContains("##teamcity")
});

// TeamCity Event Listener Test
StandardAndZipLists.Add(new PackageTest(1, "TeamCityListenerTest2")
{
Description = "Run mock-assembly with --enable teamcity option",
Arguments = "testdata/net462/mock-assembly.dll --enable:NUnit.Engine.Listeners.TeamCityEventListener",
ExpectedResult = new MockAssemblyExpectedResult("net-4.6.2"),
ExtensionsNeeded = new[] { KnownExtensions.TeamCityEventListener },
OutputCheck = new OutputContains("##teamcity")
});

// V2 Framework Driver Tests
Expand Down
43 changes: 21 additions & 22 deletions src/NUnitConsole/nunit3-console.tests/CommandLineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ public void NoInputFiles()
[TestCase("WaitBeforeExit", "wait")]
[TestCase("NoHeader", "noheader|noh")]
[TestCase("DisposeRunners", "dispose-runners")]
[TestCase("TeamCity", "teamcity")]
[TestCase("SkipNonTestAssemblies", "skipnontestassemblies")]
[TestCase("NoResult", "noresult")]
#if NETFRAMEWORK
Expand Down Expand Up @@ -289,6 +288,9 @@ public void CanRecognizeIntOptions(string propertyName, string pattern)
[TestCase("--test-name-format")]
[TestCase("--params")]
[TestCase("--encoding")]
[TestCase("--extensionDirectory")]
[TestCase("--enable")]
[TestCase("--disable")]
#if NETFRAMEWORK
[TestCase("--process")]
[TestCase("--domain")]
Expand Down Expand Up @@ -595,26 +597,6 @@ public void ExploreOptionWithFilePathUsingEqualSign()
Assert.That(options.ExploreOutputSpecifications[0].OutputPath, Is.EqualTo("C:/nunit/tests/bin/Debug/console-test.xml"));
}

[TestCase(true, true)]
[TestCase(false, false)]
public void ShouldSetTeamCityFlagAccordingToArgsAndDefaults(bool hasTeamcityInCmd, bool expectedTeamCity)
{
// Given
List<string> args = new List<string> { "tests.dll" };
if (hasTeamcityInCmd)
{
args.Add("--teamcity");
}

ConsoleOptions options = ConsoleMocks.Options(args.ToArray());

// When
var actualTeamCity = options.TeamCity;

// Then
Assert.That(expectedTeamCity, Is.EqualTo(actualTeamCity));
}

[Test]
public void ShouldNotFailOnEmptyLine()
{
Expand Down Expand Up @@ -829,13 +811,30 @@ public void DeprecatedLabelsOptionsAreReplacedCorrectly(string oldOption, string
Assert.That(options.DisplayTestLabels, Is.EqualTo(newOption));
}

[Test]
public void UserExtensionDirectoryTest()
{
ConsoleOptions options = ConsoleMocks.Options("--extensionDirectory=/a/b/c");
Assert.That(options.Validate);
Assert.That(options.ExtensionDirectories.Contains("/a/b/c"));
}

[Test]
public void EnableExtensionTest()
{
ConsoleOptions options = ConsoleMocks.Options("--enable=NUnit.Engine.Listeners.TeamCityEventListener");
Assert.That(options.Validate);
Assert.That(options.EnableExtensions.Contains("NUnit.Engine.Listeners.TeamCityEventListener"));
}

[Test]
public void DisableExtensionTest()
{
ConsoleOptions options = ConsoleMocks.Options("--disable=NUnit.Engine.Listeners.TeamCityEventListener");
Assert.That(options.Validate);
Assert.That(options.DisableExtensions.Contains("NUnit.Engine.Listeners.TeamCityEventListener"));
}

private static IFileSystem GetFileSystemContainingFile(string fileName)
{
var fileSystem = new VirtualFileSystem();
Expand All @@ -853,7 +852,7 @@ private static FieldInfo GetFieldInfo(string fieldName)
private static PropertyInfo GetPropertyInfo(string propertyName)
{
PropertyInfo property = typeof(ConsoleOptions).GetProperty(propertyName);
Assert.That(property, Is.Not.Null, "The property '{0}' is not defined", propertyName);
Assert.That(property, Is.Not.Null, $"The property '{propertyName}' is not defined");
return property;
}
}
Expand Down
11 changes: 10 additions & 1 deletion src/NUnitConsole/nunit3-console.tests/ConsoleRunnerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,16 @@ public void ThrowsRequiredExtensionExceptionWhenTeamcityOptionIsSpecifiedButNotA
var ex = Assert.Throws<RequiredExtensionException>(
() => new ConsoleRunner(_testEngine, ConsoleMocks.Options("mock-assembly.dll", "--teamcity"), new ColorConsoleWriter()));

Assert.That(ex, Has.Message.Contains("teamcity"));
Assert.That(ex, Has.Message.EqualTo("Required extension 'NUnit.Engine.Listeners.TeamCityEventListener' is not installed."));
}

[Test]
public void ThrowsRequiredExtensionExceptionWhenEnableOptionSpecifiesUnavailableExtension()
{
var ex = Assert.Throws<RequiredExtensionException>(
() => new ConsoleRunner(_testEngine, ConsoleMocks.Options("mock-assembly.dll", "--enable:Not.An.Extension"), new ColorConsoleWriter()));

Assert.That(ex, Has.Message.EqualTo("Required extension 'Not.An.Extension' is not installed."));
}
}

Expand Down
33 changes: 26 additions & 7 deletions src/NUnitConsole/nunit3-console/ConsoleOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ internal ConsoleOptions(IFileSystem fileSystem, params string[] args)

public bool ListExtensions { get; private set; }

public List<string> EnableExtensions { get; private set; } = new List<string>();
public List<string> DisableExtensions { get; private set; } = new List<string>();

// Additional directories to be used to search for user extensions
public List<string> ExtensionDirectories { get; } = new List<string>();

Expand Down Expand Up @@ -83,8 +86,6 @@ internal ConsoleOptions(IFileSystem fileSystem, params string[] args)

public bool NoColor { get; private set; }

public bool TeamCity { get; private set; }

public string OutFile { get; private set; }
public bool OutFileSpecified { get { return OutFile != null; } }

Expand Down Expand Up @@ -301,9 +302,6 @@ private void ConfigureOptions()
this.Add("trace=", "Set internal trace {LEVEL}.\nValues: Off, Error, Warning, Info, Verbose (Debug)",
v => InternalTraceLevel = parser.RequiredValue(v, "--trace", "Off", "Error", "Warning", "Info", "Verbose", "Debug"));

this.Add("teamcity", "Turns on use of TeamCity service messages. TeamCity engine extension is required.",
v => TeamCity = v != null);

this.Add("noheader|noh", "Suppress display of program information at start of run.",
v => NoHeader = v != null);

Expand Down Expand Up @@ -381,8 +379,29 @@ private void ConfigureOptions()
this.Add("list-extensions", "List all extension points and the extensions for each.",
v => ListExtensions = v != null);

this.Add("extensionDirectory=", "Specifies an additional directory to be examined for extensions. May be repeated.",
v => { ExtensionDirectories.Add(Path.GetFullPath(v)); });
this.Add("extensionDirectory=", "Specifies an additional directory to be examined for extensions. May be repeated.", v =>
{
string dir = parser.RequiredValue(v, "--extensionDirectory");
if (dir != null)
ExtensionDirectories.Add(dir);
});

this.Add("teamcity", "Turns on use of TeamCity service messages. TeamCity engine extension is required.",
v => EnableExtensions.Add("NUnit.Engine.Listeners.TeamCityEventListener")); //TeamCity = v != null);

this.Add("enable=", "Enables the specified extension. May be repeated.", v =>
{
string extension = parser.RequiredValue(v, "--enable");
if (!string.IsNullOrEmpty(extension))
EnableExtensions.Add(extension);
});

this.Add("disable=", "Disables the specified extension. May be repeated.", v =>
{
string extension = parser.RequiredValue(v, "--disable");
if (!string.IsNullOrEmpty(extension))
DisableExtensions.Add(extension);
});

this.AddNetFxOnlyOption("set-principal-policy=", "Set PrincipalPolicy for the test domain.",
NetFxOnlyOption("set-principal-policy=", v => PrincipalPolicy = parser.RequiredValue(v, "--set-principal-policy", "UnauthenticatedPrincipal", "NoPrincipal", "WindowsPrincipal")));
Expand Down
39 changes: 28 additions & 11 deletions src/NUnitConsole/nunit3-console/ConsoleRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,32 +73,49 @@ public ConsoleRunner(ITestEngine engine, ConsoleOptions options, ExtendedTextWri
_extensionService.FindExtensionAssemblies(extensionDirectory);

foreach (string extensionDirectory in _options.ExtensionDirectories)
_extensionService.FindExtensionAssemblies(extensionDirectory);
_extensionService.FindExtensionAssemblies(extensionDirectory);

_workDirectory = options.WorkDirectory;
if (_workDirectory != null)
Directory.CreateDirectory(_workDirectory);
else
_workDirectory = null;

if (_options.TeamCity || RunningUnderTeamCity)
// Attempt to enable extensions as requested by the user
foreach (string typeName in options.EnableExtensions)
{
bool teamcityInstalled = false;
foreach (var node in _extensionService.GetExtensionNodes(EVENT_LISTENER_EXTENSION_PATH))
if (teamcityInstalled = node.TypeName == TEAMCITY_EVENT_LISTENER_FULLNAME)
break;
// Throw if requested extension is not installed
if (!IsExtensionInstalled(typeName))
throw new RequiredExtensionException(typeName);

if (teamcityInstalled)
// Enable TeamCityEventListener immediately, before the console is redirected
_extensionService.EnableExtension("NUnit.Engine.Listeners.TeamCityEventListener", true);
else if (_options.TeamCity)
throw new RequiredExtensionException(TEAMCITY_EVENT_LISTENER_NAME, "--teamcity");
EnableExtension(typeName);
}

// Also enable TeamCity extension under TeamCity, if it is installed
if (RunningUnderTeamCity && IsExtensionInstalled(TEAMCITY_EVENT_LISTENER_FULLNAME))
EnableExtension(TEAMCITY_EVENT_LISTENER_FULLNAME);

// Disable extensions as requested by the user, ignoring any not installed
foreach (string typeName in options.DisableExtensions)
if (IsExtensionInstalled(typeName))
DisableExtension(typeName);
}

private bool RunningUnderTeamCity =>
!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TEAMCITY_PROJECT_NAME"));

private bool IsExtensionInstalled(string typeName)
{
foreach (var node in _extensionService.Extensions)
if (node.TypeName == typeName) return true;

return false;
}

private void EnableExtension(string name) => _extensionService.EnableExtension(name, true);

private void DisableExtension(string name) => _extensionService.EnableExtension(name, false);


/// <summary>
/// Executes tests according to the provided commandline options.
Expand Down
9 changes: 9 additions & 0 deletions src/NUnitConsole/nunit3-console/ConsoleTests.nunit
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<NUnitProject>
<Settings autoconfig="true" processModel="Default" domainUsage="Default" />
<Config name="Debug">
<assembly path="nunit3-console.tests.dll" />
</Config>
<Config name="Release">
<assembly path="nunit3-console.tests.dll" />
</Config>
</NUnitProject>
22 changes: 3 additions & 19 deletions src/NUnitConsole/nunit3-console/RequiredExtensionException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,20 @@ namespace NUnit.ConsoleRunner
/// </summary>
public class RequiredExtensionException : Exception
{
private static string Message1(string extensionName) => $"Required extension {extensionName} is not installed.";
private static string Message2(string extensionName, string option) => $"Option {option} specified but {extensionName} is not installed.";
private static string BuildMessage(string extensionName) => $"Required extension '{extensionName}' is not installed.";

/// <summary>
/// Construct with the name of an extension
/// </summary>
public RequiredExtensionException(string extensionName) : base(Message1(extensionName))
{
}

/// <summary>
/// Construct with the name of an extension and the command-line option requiring that extension.
/// </summary>
public RequiredExtensionException(string extensionName, string option) : base(Message2(extensionName, option))
public RequiredExtensionException(string extensionName) : base(BuildMessage(extensionName))
{
}

/// <summary>
/// Construct with the name of an extension and inner exception
/// </summary>
public RequiredExtensionException(string extensionName, Exception innerException)
: base(Message1(extensionName), innerException)
{
}

/// <summary>
/// Construct with the name of an extension, a command-line option and inner exception
/// </summary>
public RequiredExtensionException(string extensionName, string option, Exception innerException)
: base(Message2(extensionName, option), innerException)
: base(BuildMessage(extensionName), innerException)
{
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/NUnitConsole/nunit3-console/nunit3-console.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,17 @@
<ApplicationIcon>..\..\..\nunit.ico</ApplicationIcon>
</PropertyGroup>

<ItemGroup>
<None Remove="ConsoleTests.nunit" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\..\NUnitEngine\nunit.engine.core\Guard.cs" Link="Utilities\Guard.cs" />
<Compile Include="..\..\NUnitEngine\nunit.engine.core\Internal\ExceptionHelper.cs" Link="Utilities\ExceptionHelper.cs" />
<Content Include="..\..\..\nunit.ico" Link="nunit.ico" />
<Content Include="ConsoleTests.nunit">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>

<ItemGroup>
Expand Down

0 comments on commit f9fd4a7

Please sign in to comment.