diff --git a/src/NuGet.Core/NuGet.Protocol/GlobalSuppressions.cs b/src/NuGet.Core/NuGet.Protocol/GlobalSuppressions.cs index 248d2d615b2..b9ed17ef603 100644 --- a/src/NuGet.Core/NuGet.Protocol/GlobalSuppressions.cs +++ b/src/NuGet.Core/NuGet.Protocol/GlobalSuppressions.cs @@ -133,7 +133,6 @@ [assembly: SuppressMessage("Build", "CA1031:Modify 'FireBeforeClose' to catch a more specific allowed exception type, or rethrow the exception.", Justification = "", Scope = "member", Target = "~M:NuGet.Protocol.Plugins.Plugin.FireBeforeClose")] [assembly: SuppressMessage("Build", "CA1031:Modify 'FireClosed' to catch a more specific allowed exception type, or rethrow the exception.", Justification = "", Scope = "member", Target = "~M:NuGet.Protocol.Plugins.Plugin.FireClosed")] [assembly: SuppressMessage("Build", "CA2000:Call System.IDisposable.Dispose on object created by 'new MonitorNuGetProcessExitRequestHandler(plugin)' before all references to it are out of scope.", Justification = "", Scope = "member", Target = "~M:NuGet.Protocol.Plugins.PluginFactory.CreateFromCurrentProcessAsync(NuGet.Protocol.Plugins.IRequestHandlers,NuGet.Protocol.Plugins.ConnectionOptions,System.Threading.CancellationToken)~System.Threading.Tasks.Task{NuGet.Protocol.Plugins.IPlugin}")] -[assembly: SuppressMessage("Build", "CA2000:Use recommended dispose pattern to ensure that object created by 'new PluginProcess(startInfo)' is disposed on all paths. If possible, wrap the creation within a 'using' statement or a 'using' declaration. Otherwise, use a try-finally pattern, with a dedicated local variable declared before the try region and an unconditional Dispose invocation on non-null value in the 'finally' region, say 'x?.Dispose()'. If the object is explicitly disposed within the try region or the dispose ownership is transfered to another object or method, assign 'null' to the local variable just after such an operation to prevent double dispose in 'finally'.", Justification = "The responsibility to dispose the object is transferred to another object or wrapper that's created in the method and returned to the caller", Scope = "member", Target = "~M:NuGet.Protocol.Plugins.PluginFactory.CreatePluginAsync(System.String,System.Collections.Generic.IEnumerable{System.String},NuGet.Protocol.Plugins.IRequestHandlers,NuGet.Protocol.Plugins.ConnectionOptions,System.Threading.CancellationToken)~System.Threading.Tasks.Task{NuGet.Protocol.Plugins.IPlugin}")] [assembly: SuppressMessage("Build", "CA1031:Modify 'SendCloseRequest' to catch a more specific allowed exception type, or rethrow the exception.", Justification = "", Scope = "member", Target = "~M:NuGet.Protocol.Plugins.PluginFactory.SendCloseRequest(NuGet.Protocol.Plugins.IPlugin)")] [assembly: SuppressMessage("Build", "CA1822:Member GetPluginOperationClaimsAsync does not access instance data and can be marked as static (Shared in VisualBasic)", Justification = "", Scope = "member", Target = "~M:NuGet.Protocol.Plugins.PluginManager.GetPluginOperationClaimsAsync(NuGet.Protocol.Plugins.IPlugin,System.String,Newtonsoft.Json.Linq.JObject,System.Threading.CancellationToken)~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyList{NuGet.Protocol.Plugins.OperationClaim}}")] [assembly: SuppressMessage("Build", "CA1031:Modify 'TryCreatePluginAsync' to catch a more specific allowed exception type, or rethrow the exception.", Justification = "", Scope = "member", Target = "~M:NuGet.Protocol.Plugins.PluginManager.TryCreatePluginAsync(NuGet.Protocol.Plugins.PluginDiscoveryResult,NuGet.Protocol.Plugins.OperationClaim,NuGet.Protocol.Plugins.PluginManager.PluginRequestKey,System.String,Newtonsoft.Json.Linq.JObject,System.Threading.CancellationToken)~System.Threading.Tasks.Task{System.Tuple{System.Boolean,NuGet.Protocol.Plugins.PluginCreationResult}}")] diff --git a/src/NuGet.Core/NuGet.Protocol/Plugins/IPluginFactory.cs b/src/NuGet.Core/NuGet.Protocol/Plugins/IPluginFactory.cs index ae187ee4f9f..1c5b0f3c697 100644 --- a/src/NuGet.Core/NuGet.Protocol/Plugins/IPluginFactory.cs +++ b/src/NuGet.Core/NuGet.Protocol/Plugins/IPluginFactory.cs @@ -11,12 +11,12 @@ namespace NuGet.Protocol.Plugins /// /// A plugin factory. /// - public interface IPluginFactory : IDisposable + internal interface IPluginFactory : IDisposable { /// /// Asynchronously gets an existing plugin instance or creates a new instance and connects to it. /// - /// The file path of the plugin. + /// A plugin file. /// Command-line arguments to be supplied to the plugin. /// Request handlers. /// Connection options. @@ -24,7 +24,7 @@ public interface IPluginFactory : IDisposable /// A task that represents the asynchronous operation. /// The task result () returns a /// instance. - /// Thrown if + /// Thrown if /// is either or empty. /// Thrown if /// is . @@ -37,7 +37,7 @@ public interface IPluginFactory : IDisposable /// Thrown if this object is disposed. /// This is intended to be called by NuGet client tools. Task GetOrCreateAsync( - string filePath, + PluginFile pluginFile, IEnumerable arguments, IRequestHandlers requestHandlers, ConnectionOptions options, diff --git a/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoverer.cs b/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoverer.cs index 830ba4d4142..35ae727eb5f 100644 --- a/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoverer.cs +++ b/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoverer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using NuGet.Common; @@ -17,17 +18,42 @@ public sealed class PluginDiscoverer : IPluginDiscoverer { private bool _isDisposed; private List _pluginFiles; - private readonly string _rawPluginPaths; + private readonly string _netCoreOrNetFXPluginPaths; + private readonly string _nuGetPluginPaths; private IEnumerable _results; private readonly SemaphoreSlim _semaphore; + private readonly IEnvironmentVariableReader _environmentVariableReader; + private static bool IsDesktop + { + get + { +#if IS_DESKTOP + return true; +#else + return false; +#endif + } + } - /// - /// Instantiates a new class. - /// - /// The raw semicolon-delimited list of supposed plugin file paths. - public PluginDiscoverer(string rawPluginPaths) + public PluginDiscoverer() + : this(EnvironmentVariableWrapper.Instance) + { + } + + internal PluginDiscoverer(IEnvironmentVariableReader environmentVariableReader) { - _rawPluginPaths = rawPluginPaths; + _environmentVariableReader = environmentVariableReader; +#if IS_DESKTOP + _netCoreOrNetFXPluginPaths = environmentVariableReader.GetEnvironmentVariable(EnvironmentVariableConstants.DesktopPluginPaths); +#else + _netCoreOrNetFXPluginPaths = environmentVariableReader.GetEnvironmentVariable(EnvironmentVariableConstants.CorePluginPaths); +#endif + + if (string.IsNullOrEmpty(_netCoreOrNetFXPluginPaths)) + { + _nuGetPluginPaths = _environmentVariableReader.GetEnvironmentVariable(EnvironmentVariableConstants.PluginPaths); + } + _semaphore = new SemaphoreSlim(initialCount: 1, maxCount: 1); } @@ -75,7 +101,40 @@ public async Task> DiscoverAsync(Cancellation return _results; } - _pluginFiles = GetPluginFiles(cancellationToken); + if (!string.IsNullOrEmpty(_netCoreOrNetFXPluginPaths)) + { + // NUGET_NETFX_PLUGIN_PATHS, NUGET_NETCORE_PLUGIN_PATHS have been set. + var filePaths = _netCoreOrNetFXPluginPaths.Split(new[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries); + _pluginFiles = GetPluginFiles(filePaths, cancellationToken); + } + else if (!string.IsNullOrEmpty(_nuGetPluginPaths)) + { + // NUGET_PLUGIN_PATHS has been set + _pluginFiles = GetPluginsInNuGetPluginPaths(); + } + else + { + // restore to default plugins search. + // Search for plugins in %user%/.nuget/plugins + var directories = new List { PluginDiscoveryUtility.GetNuGetHomePluginsPath() }; +#if IS_DESKTOP + // Internal plugins are only supported for .NET Framework scenarios, namely msbuild.exe + directories.Add(PluginDiscoveryUtility.GetInternalPlugins()); +#endif + var filePaths = PluginDiscoveryUtility.GetConventionBasedPlugins(directories); + _pluginFiles = GetPluginFiles(filePaths, cancellationToken); + + // Search for .Net tools plugins in PATH + if (_pluginFiles != null) + { + _pluginFiles.AddRange(GetPluginsInPath()); + } + else + { + _pluginFiles = GetPluginsInPath(); + } + } + var results = new List(); for (var i = 0; i < _pluginFiles.Count; ++i) @@ -97,14 +156,17 @@ public async Task> DiscoverAsync(Cancellation return _results; } - private List GetPluginFiles(CancellationToken cancellationToken) + private static List GetPluginFiles(IEnumerable filePaths, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var filePaths = GetPluginFilePaths(); - var files = new List(); + if (filePaths == null) + { + return files; + } + foreach (var filePath in filePaths) { var pluginFile = new PluginFile(filePath, new Lazy(() => @@ -117,26 +179,176 @@ private List GetPluginFiles(CancellationToken cancellationToken) { return PluginFileState.InvalidFilePath; } - })); + }), requiresDotnetHost: !IsDesktop); files.Add(pluginFile); } return files; } - private IEnumerable GetPluginFilePaths() + /// + /// Retrieves authentication plugins by searching through directories and files specified in the `NuGET_PLUGIN_PATHS` + /// environment variable. The method looks for files prefixed with 'nuget-plugin-' and verifies their validity for .net tools plugins. + /// + /// A list of valid objects representing the discovered plugins. + internal List GetPluginsInNuGetPluginPaths() { - if (string.IsNullOrEmpty(_rawPluginPaths)) + var pluginFiles = new List(); + string[] paths = _nuGetPluginPaths?.Split(Path.PathSeparator) ?? Array.Empty(); + + foreach (var path in paths) { - var directories = new List { PluginDiscoveryUtility.GetNuGetHomePluginsPath() }; -#if IS_DESKTOP - // Internal plugins are only supported for .NET Framework scenarios, namely msbuild.exe - directories.Add(PluginDiscoveryUtility.GetInternalPlugins()); + if (PathValidator.IsValidLocalPath(path) || PathValidator.IsValidUncPath(path)) + { + if (File.Exists(path)) + { + FileInfo fileInfo = new FileInfo(path); + if (fileInfo.Name.StartsWith("nuget-plugin-", StringComparison.CurrentCultureIgnoreCase)) + { + // A DotNet tool plugin + if (IsValidPluginFile(fileInfo)) + { + PluginFile pluginFile = new PluginFile(fileInfo.FullName, new Lazy(() => PluginFileState.Valid), requiresDotnetHost: false); + pluginFiles.Add(pluginFile); + } + } + else + { + // A non DotNet tool plugin file + var state = new Lazy(() => PluginFileState.Valid); + pluginFiles.Add(new PluginFile(fileInfo.FullName, state, requiresDotnetHost: !IsDesktop)); + } + } + else if (Directory.Exists(path)) + { + pluginFiles.AddRange(GetNetToolsPluginsInDirectory(path) ?? new List()); + } + } + else + { + pluginFiles.Add(new PluginFile(path, new Lazy(() => PluginFileState.InvalidFilePath), requiresDotnetHost: !IsDesktop)); + } + } + + return pluginFiles; + } + + /// + /// Retrieves .NET tools authentication plugins by searching through directories specified in `PATH` + /// + /// A list of valid objects representing the discovered plugins. + internal List GetPluginsInPath() + { + var pluginFiles = new List(); + var nugetPluginPaths = _environmentVariableReader.GetEnvironmentVariable("PATH"); + string[] paths = nugetPluginPaths?.Split(Path.PathSeparator) ?? Array.Empty(); + + foreach (var path in paths) + { + if (PathValidator.IsValidLocalPath(path) || PathValidator.IsValidUncPath(path)) + { + pluginFiles.AddRange(GetNetToolsPluginsInDirectory(path) ?? new List()); + } + } + + return pluginFiles; + } + + private static List GetNetToolsPluginsInDirectory(string directoryPath) + { + var pluginFiles = new List(); + + if (Directory.Exists(directoryPath)) + { + var directoryInfo = new DirectoryInfo(directoryPath); + var files = directoryInfo.GetFiles("nuget-plugin-*"); + + foreach (var file in files) + { + if (IsValidPluginFile(file)) + { + PluginFile pluginFile = new PluginFile(file.FullName, new Lazy(() => PluginFileState.Valid), requiresDotnetHost: false); + pluginFiles.Add(pluginFile); + } + } + } + + return pluginFiles; + } + + /// + /// Checks whether a file is a valid plugin file for windows/Unix. + /// Windows: It should be either .bat or .exe + /// Unix: It should be executable + /// + /// + /// + internal static bool IsValidPluginFile(FileInfo fileInfo) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return fileInfo.Extension.Equals(".exe", StringComparison.OrdinalIgnoreCase) || + fileInfo.Extension.Equals(".bat", StringComparison.OrdinalIgnoreCase); + } + else + { +#if NET8_0_OR_GREATER + var fileMode = File.GetUnixFileMode(fileInfo.FullName); + + return fileInfo.Exists && + ((fileMode & UnixFileMode.UserExecute) != 0 || + (fileMode & UnixFileMode.GroupExecute) != 0 || + (fileMode & UnixFileMode.OtherExecute) != 0); +#else + return fileInfo.Exists && IsExecutable(fileInfo); #endif - return PluginDiscoveryUtility.GetConventionBasedPlugins(directories); } + } - return _rawPluginPaths.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); +#if !NET8_0_OR_GREATER + /// + /// Checks whether a file is executable or not in Unix. + /// This is done by running bash code: `if [ -x {fileInfo.FullName} ]; then echo yes; else echo no; fi` + /// + /// + /// + internal static bool IsExecutable(FileInfo fileInfo) + { +#pragma warning disable CA1031 // Do not catch general exception types + try + { + string output; + using (var process = new System.Diagnostics.Process()) + { + // Use a shell command to check if the file is executable + process.StartInfo.FileName = "/bin/bash"; + process.StartInfo.Arguments = $" -c \"if [ -x '{fileInfo.FullName}' ]; then echo yes; else echo no; fi\""; + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + + process.Start(); + output = process.StandardOutput.ReadToEnd().Trim(); + + if (!process.HasExited && !process.WaitForExit(1000)) + { + process.Kill(); + return false; + } + else if (process.ExitCode != 0) + { + return false; + } + + // Check if the output is "yes" + return output.Equals("yes", StringComparison.OrdinalIgnoreCase); + } + } + catch + { + return false; + } +#pragma warning restore CA1031 // Do not catch general exception types } +#endif } } diff --git a/src/NuGet.Core/NuGet.Protocol/Plugins/PluginFactory.cs b/src/NuGet.Core/NuGet.Protocol/Plugins/PluginFactory.cs index b6bd048c63c..c522406552f 100644 --- a/src/NuGet.Core/NuGet.Protocol/Plugins/PluginFactory.cs +++ b/src/NuGet.Core/NuGet.Protocol/Plugins/PluginFactory.cs @@ -18,7 +18,7 @@ namespace NuGet.Protocol.Plugins /// /// A plugin factory. /// - public sealed class PluginFactory : IPluginFactory + public class PluginFactory : IPluginFactory { private bool _isDisposed; private readonly IPluginLogger _logger; @@ -26,6 +26,8 @@ public sealed class PluginFactory : IPluginFactory private readonly ConcurrentDictionary>> _plugins; private readonly IEnvironmentVariableReader _environmentVariableReader; + internal PluginFactory() { } + /// /// Instantiates a new class. /// @@ -57,7 +59,7 @@ internal PluginFactory(TimeSpan pluginIdleTimeout, IEnvironmentVariableReader en /// /// Disposes of this instance. /// - public void Dispose() + public virtual void Dispose() { if (_isDisposed) { @@ -86,7 +88,7 @@ public void Dispose() /// /// Asynchronously gets an existing plugin instance or creates a new instance and connects to it. /// - /// The file path of the plugin. + /// A plugin file. /// Command-line arguments to be supplied to the plugin. /// Request handlers. /// Connection options. @@ -94,7 +96,7 @@ public void Dispose() /// A task that represents the asynchronous operation. /// The task result () returns a /// instance. - /// Thrown if + /// Thrown if /// is either or empty. /// Thrown if /// is . @@ -108,8 +110,8 @@ public void Dispose() /// Thrown if a plugin protocol error occurs. /// Thrown for a plugin failure during creation. /// This is intended to be called by NuGet client tools. - public async Task GetOrCreateAsync( - string filePath, + public virtual async Task GetOrCreateAsync( + PluginFile pluginFile, IEnumerable arguments, IRequestHandlers requestHandlers, ConnectionOptions options, @@ -120,9 +122,9 @@ public async Task GetOrCreateAsync( throw new ObjectDisposedException(nameof(PluginFactory)); } - if (string.IsNullOrEmpty(filePath)) + if (string.IsNullOrEmpty(pluginFile.Path)) { - throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(filePath)); + throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(pluginFile.Path)); } if (arguments == null) @@ -143,9 +145,9 @@ public async Task GetOrCreateAsync( sessionCancellationToken.ThrowIfCancellationRequested(); var lazyTask = _plugins.GetOrAdd( - filePath, + pluginFile.Path, (path) => new Lazy>( - () => CreatePluginAsync(filePath, arguments, requestHandlers, options, sessionCancellationToken))); + () => CreatePluginAsync(pluginFile, arguments, requestHandlers, options, sessionCancellationToken))); await lazyTask.Value; @@ -154,40 +156,48 @@ public async Task GetOrCreateAsync( } private async Task CreatePluginAsync( - string filePath, + PluginFile pluginFile, IEnumerable arguments, IRequestHandlers requestHandlers, ConnectionOptions options, CancellationToken sessionCancellationToken) { var args = string.Join(" ", arguments); -#if IS_DESKTOP - var startInfo = new ProcessStartInfo(filePath) + + ProcessStartInfo startInfo; + + if (pluginFile.RequiresDotnetHost) { - Arguments = args, - UseShellExecute = false, - RedirectStandardError = false, - RedirectStandardInput = true, - RedirectStandardOutput = true, - StandardOutputEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), - WindowStyle = ProcessWindowStyle.Hidden, - }; -#else - var startInfo = new ProcessStartInfo + startInfo = new ProcessStartInfo + { + FileName = _environmentVariableReader.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? + (NuGet.Common.RuntimeEnvironmentHelper.IsWindows ? + "dotnet.exe" : + "dotnet"), + Arguments = $"\"{pluginFile.Path}\" " + args, + UseShellExecute = false, + RedirectStandardError = false, + RedirectStandardInput = true, + RedirectStandardOutput = true, + StandardOutputEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), + WindowStyle = ProcessWindowStyle.Hidden, + }; + } + else { - FileName = _environmentVariableReader.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? - (NuGet.Common.RuntimeEnvironmentHelper.IsWindows ? - "dotnet.exe" : - "dotnet"), - Arguments = $"\"{filePath}\" " + args, - UseShellExecute = false, - RedirectStandardError = false, - RedirectStandardInput = true, - RedirectStandardOutput = true, - StandardOutputEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), - WindowStyle = ProcessWindowStyle.Hidden, - }; -#endif + // Execute file directly. + startInfo = new ProcessStartInfo(pluginFile.Path) + { + Arguments = args, + UseShellExecute = false, + RedirectStandardError = false, + RedirectStandardInput = true, + RedirectStandardOutput = true, + StandardOutputEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), + WindowStyle = ProcessWindowStyle.Hidden, + }; + } + var pluginProcess = new PluginProcess(startInfo); string pluginId = Plugin.CreateNewId(); @@ -228,7 +238,7 @@ private async Task CreatePluginAsync( connection = new Connection(messageDispatcher, sender, receiver, options, _logger); var plugin = new Plugin( - filePath, + pluginFile.Path, connection, pluginProcess, isOwnProcess: false, diff --git a/src/NuGet.Core/NuGet.Protocol/Plugins/PluginFile.cs b/src/NuGet.Core/NuGet.Protocol/Plugins/PluginFile.cs index 9753e0285bf..0d4f799d0e0 100644 --- a/src/NuGet.Core/NuGet.Protocol/Plugins/PluginFile.cs +++ b/src/NuGet.Core/NuGet.Protocol/Plugins/PluginFile.cs @@ -20,18 +20,25 @@ public sealed class PluginFile /// public Lazy State { get; } + /// + /// Indicates if the plugin file requires a dotnet host. + /// + internal bool RequiresDotnetHost { get; } + /// /// Instantiates a new class. /// /// The plugin's file path. /// A lazy that evaluates the plugin file state. - public PluginFile(string filePath, Lazy state) + /// Indicates if the plugin file requires a dotnet host. + public PluginFile(string filePath, Lazy state, bool requiresDotnetHost) { if (string.IsNullOrEmpty(filePath)) { throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(filePath)); } + RequiresDotnetHost = requiresDotnetHost; Path = filePath; State = state; } diff --git a/src/NuGet.Core/NuGet.Protocol/Plugins/PluginManager.cs b/src/NuGet.Core/NuGet.Protocol/Plugins/PluginManager.cs index 53dfa731769..11acfc56c71 100644 --- a/src/NuGet.Core/NuGet.Protocol/Plugins/PluginManager.cs +++ b/src/NuGet.Core/NuGet.Protocol/Plugins/PluginManager.cs @@ -33,7 +33,6 @@ public sealed class PluginManager : IPluginManager, IDisposable private IPluginFactory _pluginFactory; private ConcurrentDictionary>>> _pluginOperationClaims; private ConcurrentDictionary> _pluginUtilities; - private string _rawPluginPaths; private static Lazy _currentProcessId = new Lazy(GetCurrentProcessId); private Lazy _pluginsCacheDirectoryPath; @@ -56,7 +55,7 @@ private PluginManager() public PluginManager( IEnvironmentVariableReader reader, Lazy pluginDiscoverer, - Func pluginFactoryCreator, + Func pluginFactoryCreator, Lazy pluginsCacheDirectoryPath) { Initialize( @@ -213,12 +212,14 @@ private async Task> TryCreatePluginAsync( { if (result.PluginFile.State.Value == PluginFileState.Valid) { - var plugin = await _pluginFactory.GetOrCreateAsync( - result.PluginFile.Path, - PluginConstants.PluginArguments, - new RequestHandlers(), - _connectionOptions, - cancellationToken); + IPlugin plugin; + + plugin = await _pluginFactory.GetOrCreateAsync( + pluginFile: result.PluginFile, + arguments: PluginConstants.PluginArguments, + requestHandlers: new RequestHandlers(), + options: _connectionOptions, + sessionCancellationToken: cancellationToken); var utilities = await PerformOneTimePluginInitializationAsync(plugin, cancellationToken); @@ -312,15 +313,6 @@ private void Initialize(IEnvironmentVariableReader reader, { throw new ArgumentNullException(nameof(pluginFactoryCreator)); } -#if IS_DESKTOP - _rawPluginPaths = reader.GetEnvironmentVariable(EnvironmentVariableConstants.DesktopPluginPaths); -#else - _rawPluginPaths = reader.GetEnvironmentVariable(EnvironmentVariableConstants.CorePluginPaths); -#endif - if (string.IsNullOrEmpty(_rawPluginPaths)) - { - _rawPluginPaths = reader.GetEnvironmentVariable(EnvironmentVariableConstants.PluginPaths); - } _connectionOptions = ConnectionOptions.CreateDefault(reader); @@ -360,12 +352,20 @@ private async Task> GetPluginOperationClaimsAsync( private PluginDiscoverer InitializeDiscoverer() { - return new PluginDiscoverer(_rawPluginPaths); + return new PluginDiscoverer(); } private bool IsPluginPossiblyAvailable() { - return !string.IsNullOrEmpty(_rawPluginPaths); + string pluginEnvVariable; + +#if IS_DESKTOP + pluginEnvVariable = EnvironmentVariableReader.GetEnvironmentVariable(EnvironmentVariableConstants.DesktopPluginPaths); +#else + pluginEnvVariable = EnvironmentVariableReader.GetEnvironmentVariable(EnvironmentVariableConstants.CorePluginPaths); +#endif + pluginEnvVariable ??= EnvironmentVariableReader.GetEnvironmentVariable(EnvironmentVariableConstants.PluginPaths); + return !string.IsNullOrEmpty(pluginEnvVariable); } private void OnPluginClosed(object sender, EventArgs e) diff --git a/src/NuGet.Core/NuGet.Protocol/PublicAPI/net472/PublicAPI.Shipped.txt b/src/NuGet.Core/NuGet.Protocol/PublicAPI/net472/PublicAPI.Shipped.txt index def020375d4..d17e6c22f10 100644 --- a/src/NuGet.Core/NuGet.Protocol/PublicAPI/net472/PublicAPI.Shipped.txt +++ b/src/NuGet.Core/NuGet.Protocol/PublicAPI/net472/PublicAPI.Shipped.txt @@ -1015,8 +1015,6 @@ NuGet.Protocol.Plugins.IPlugin.Closed -> System.EventHandler ~NuGet.Protocol.Plugins.IPlugin.Name.get -> string NuGet.Protocol.Plugins.IPluginDiscoverer ~NuGet.Protocol.Plugins.IPluginDiscoverer.DiscoverAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> -NuGet.Protocol.Plugins.IPluginFactory -~NuGet.Protocol.Plugins.IPluginFactory.GetOrCreateAsync(string filePath, System.Collections.Generic.IEnumerable arguments, NuGet.Protocol.Plugins.IRequestHandlers requestHandlers, NuGet.Protocol.Plugins.ConnectionOptions options, System.Threading.CancellationToken sessionCancellationToken) -> System.Threading.Tasks.Task NuGet.Protocol.Plugins.IPluginManager ~NuGet.Protocol.Plugins.IPluginManager.CreatePluginsAsync(NuGet.Protocol.Core.Types.SourceRepository source, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> ~NuGet.Protocol.Plugins.IPluginManager.FindAvailablePluginsAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> @@ -1211,12 +1209,9 @@ NuGet.Protocol.Plugins.PluginException ~NuGet.Protocol.Plugins.PluginException.PluginException(string message) -> void ~NuGet.Protocol.Plugins.PluginException.PluginException(string message, System.Exception innerException) -> void NuGet.Protocol.Plugins.PluginFactory -NuGet.Protocol.Plugins.PluginFactory.Dispose() -> void -~NuGet.Protocol.Plugins.PluginFactory.GetOrCreateAsync(string filePath, System.Collections.Generic.IEnumerable arguments, NuGet.Protocol.Plugins.IRequestHandlers requestHandlers, NuGet.Protocol.Plugins.ConnectionOptions options, System.Threading.CancellationToken sessionCancellationToken) -> System.Threading.Tasks.Task NuGet.Protocol.Plugins.PluginFactory.PluginFactory(System.TimeSpan pluginIdleTimeout) -> void NuGet.Protocol.Plugins.PluginFile ~NuGet.Protocol.Plugins.PluginFile.Path.get -> string -~NuGet.Protocol.Plugins.PluginFile.PluginFile(string filePath, System.Lazy state) -> void ~NuGet.Protocol.Plugins.PluginFile.State.get -> System.Lazy NuGet.Protocol.Plugins.PluginFileState NuGet.Protocol.Plugins.PluginFileState.InvalidEmbeddedSignature = 3 -> NuGet.Protocol.Plugins.PluginFileState @@ -1228,7 +1223,6 @@ NuGet.Protocol.Plugins.PluginManager NuGet.Protocol.Plugins.PluginManager.Dispose() -> void ~NuGet.Protocol.Plugins.PluginManager.EnvironmentVariableReader.get -> NuGet.Common.IEnvironmentVariableReader ~NuGet.Protocol.Plugins.PluginManager.FindAvailablePluginsAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> -~NuGet.Protocol.Plugins.PluginManager.PluginManager(NuGet.Common.IEnvironmentVariableReader reader, System.Lazy pluginDiscoverer, System.Func pluginFactoryCreator, System.Lazy pluginsCacheDirectoryPath) -> void ~NuGet.Protocol.Plugins.PluginManager.TryGetSourceAgnosticPluginAsync(NuGet.Protocol.Plugins.PluginDiscoveryResult pluginDiscoveryResult, NuGet.Protocol.Plugins.OperationClaim requestedOperationClaim, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> NuGet.Protocol.Plugins.PluginMulticlientUtilities ~NuGet.Protocol.Plugins.PluginMulticlientUtilities.DoOncePerPluginLifetimeAsync(string key, System.Func taskFunc, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task diff --git a/src/NuGet.Core/NuGet.Protocol/PublicAPI/net472/PublicAPI.Unshipped.txt b/src/NuGet.Core/NuGet.Protocol/PublicAPI/net472/PublicAPI.Unshipped.txt index 3a90c9f9e66..4b50c64bfeb 100644 --- a/src/NuGet.Core/NuGet.Protocol/PublicAPI/net472/PublicAPI.Unshipped.txt +++ b/src/NuGet.Core/NuGet.Protocol/PublicAPI/net472/PublicAPI.Unshipped.txt @@ -1,11 +1,15 @@ #nullable enable +NuGet.Protocol.Plugins.PluginDiscoverer.PluginDiscoverer() -> void +virtual NuGet.Protocol.Plugins.PluginFactory.Dispose() -> void +~NuGet.Protocol.Plugins.PluginFile.PluginFile(string filePath, System.Lazy state, bool requiresDotnetHost) -> void +~NuGet.Protocol.Plugins.PluginManager.PluginManager(NuGet.Common.IEnvironmentVariableReader reader, System.Lazy pluginDiscoverer, System.Func pluginFactoryCreator, System.Lazy pluginsCacheDirectoryPath) -> void +~virtual NuGet.Protocol.Plugins.PluginFactory.GetOrCreateAsync(NuGet.Protocol.Plugins.PluginFile pluginFile, System.Collections.Generic.IEnumerable arguments, NuGet.Protocol.Plugins.IRequestHandlers requestHandlers, NuGet.Protocol.Plugins.ConnectionOptions options, System.Threading.CancellationToken sessionCancellationToken) -> System.Threading.Tasks.Task NuGet.Protocol.Events.ProtocolDiagnostics.ProtocolDiagnosticServiceIndexEntryEventHandler NuGet.Protocol.Events.ProtocolDiagnosticServiceIndexEntryEvent NuGet.Protocol.Events.ProtocolDiagnosticServiceIndexEntryEvent.HttpsSourceHasHttpResource.get -> bool static NuGet.Protocol.Events.ProtocolDiagnostics.ServiceIndexEntryEvent -> NuGet.Protocol.Events.ProtocolDiagnostics.ProtocolDiagnosticServiceIndexEntryEventHandler ~NuGet.Protocol.Events.ProtocolDiagnosticServiceIndexEntryEvent.ProtocolDiagnosticServiceIndexEntryEvent(string source, bool httpsSourceHasHttpResource) -> void ~NuGet.Protocol.Events.ProtocolDiagnosticServiceIndexEntryEvent.Source.get -> string -~NuGet.Protocol.Plugins.PluginDiscoverer.PluginDiscoverer(string rawPluginPaths) -> void ~const NuGet.Protocol.JsonProperties.ReadmeFileUrl = "readmeFileUrl" -> string ~NuGet.Protocol.Core.Types.IPackageSearchMetadata.ReadmeFileUrl.get -> string ~NuGet.Protocol.Core.Types.PackageSearchMetadataBuilder.ClonedPackageSearchMetadata.ReadmeFileUrl.get -> string diff --git a/src/NuGet.Core/NuGet.Protocol/PublicAPI/net8.0/PublicAPI.Shipped.txt b/src/NuGet.Core/NuGet.Protocol/PublicAPI/net8.0/PublicAPI.Shipped.txt index 86a9ec539e4..07c3e1d7c43 100644 --- a/src/NuGet.Core/NuGet.Protocol/PublicAPI/net8.0/PublicAPI.Shipped.txt +++ b/src/NuGet.Core/NuGet.Protocol/PublicAPI/net8.0/PublicAPI.Shipped.txt @@ -1015,8 +1015,6 @@ NuGet.Protocol.Plugins.IPlugin.Closed -> System.EventHandler ~NuGet.Protocol.Plugins.IPlugin.Name.get -> string NuGet.Protocol.Plugins.IPluginDiscoverer ~NuGet.Protocol.Plugins.IPluginDiscoverer.DiscoverAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> -NuGet.Protocol.Plugins.IPluginFactory -~NuGet.Protocol.Plugins.IPluginFactory.GetOrCreateAsync(string filePath, System.Collections.Generic.IEnumerable arguments, NuGet.Protocol.Plugins.IRequestHandlers requestHandlers, NuGet.Protocol.Plugins.ConnectionOptions options, System.Threading.CancellationToken sessionCancellationToken) -> System.Threading.Tasks.Task NuGet.Protocol.Plugins.IPluginManager ~NuGet.Protocol.Plugins.IPluginManager.CreatePluginsAsync(NuGet.Protocol.Core.Types.SourceRepository source, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> ~NuGet.Protocol.Plugins.IPluginManager.FindAvailablePluginsAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> @@ -1211,12 +1209,9 @@ NuGet.Protocol.Plugins.PluginException ~NuGet.Protocol.Plugins.PluginException.PluginException(string message) -> void ~NuGet.Protocol.Plugins.PluginException.PluginException(string message, System.Exception innerException) -> void NuGet.Protocol.Plugins.PluginFactory -NuGet.Protocol.Plugins.PluginFactory.Dispose() -> void -~NuGet.Protocol.Plugins.PluginFactory.GetOrCreateAsync(string filePath, System.Collections.Generic.IEnumerable arguments, NuGet.Protocol.Plugins.IRequestHandlers requestHandlers, NuGet.Protocol.Plugins.ConnectionOptions options, System.Threading.CancellationToken sessionCancellationToken) -> System.Threading.Tasks.Task NuGet.Protocol.Plugins.PluginFactory.PluginFactory(System.TimeSpan pluginIdleTimeout) -> void NuGet.Protocol.Plugins.PluginFile ~NuGet.Protocol.Plugins.PluginFile.Path.get -> string -~NuGet.Protocol.Plugins.PluginFile.PluginFile(string filePath, System.Lazy state) -> void ~NuGet.Protocol.Plugins.PluginFile.State.get -> System.Lazy NuGet.Protocol.Plugins.PluginFileState NuGet.Protocol.Plugins.PluginFileState.InvalidEmbeddedSignature = 3 -> NuGet.Protocol.Plugins.PluginFileState @@ -1228,7 +1223,6 @@ NuGet.Protocol.Plugins.PluginManager NuGet.Protocol.Plugins.PluginManager.Dispose() -> void ~NuGet.Protocol.Plugins.PluginManager.EnvironmentVariableReader.get -> NuGet.Common.IEnvironmentVariableReader ~NuGet.Protocol.Plugins.PluginManager.FindAvailablePluginsAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> -~NuGet.Protocol.Plugins.PluginManager.PluginManager(NuGet.Common.IEnvironmentVariableReader reader, System.Lazy pluginDiscoverer, System.Func pluginFactoryCreator, System.Lazy pluginsCacheDirectoryPath) -> void ~NuGet.Protocol.Plugins.PluginManager.TryGetSourceAgnosticPluginAsync(NuGet.Protocol.Plugins.PluginDiscoveryResult pluginDiscoveryResult, NuGet.Protocol.Plugins.OperationClaim requestedOperationClaim, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> NuGet.Protocol.Plugins.PluginMulticlientUtilities ~NuGet.Protocol.Plugins.PluginMulticlientUtilities.DoOncePerPluginLifetimeAsync(string key, System.Func taskFunc, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task diff --git a/src/NuGet.Core/NuGet.Protocol/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/NuGet.Core/NuGet.Protocol/PublicAPI/net8.0/PublicAPI.Unshipped.txt index 3a90c9f9e66..4b50c64bfeb 100644 --- a/src/NuGet.Core/NuGet.Protocol/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/src/NuGet.Core/NuGet.Protocol/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -1,11 +1,15 @@ #nullable enable +NuGet.Protocol.Plugins.PluginDiscoverer.PluginDiscoverer() -> void +virtual NuGet.Protocol.Plugins.PluginFactory.Dispose() -> void +~NuGet.Protocol.Plugins.PluginFile.PluginFile(string filePath, System.Lazy state, bool requiresDotnetHost) -> void +~NuGet.Protocol.Plugins.PluginManager.PluginManager(NuGet.Common.IEnvironmentVariableReader reader, System.Lazy pluginDiscoverer, System.Func pluginFactoryCreator, System.Lazy pluginsCacheDirectoryPath) -> void +~virtual NuGet.Protocol.Plugins.PluginFactory.GetOrCreateAsync(NuGet.Protocol.Plugins.PluginFile pluginFile, System.Collections.Generic.IEnumerable arguments, NuGet.Protocol.Plugins.IRequestHandlers requestHandlers, NuGet.Protocol.Plugins.ConnectionOptions options, System.Threading.CancellationToken sessionCancellationToken) -> System.Threading.Tasks.Task NuGet.Protocol.Events.ProtocolDiagnostics.ProtocolDiagnosticServiceIndexEntryEventHandler NuGet.Protocol.Events.ProtocolDiagnosticServiceIndexEntryEvent NuGet.Protocol.Events.ProtocolDiagnosticServiceIndexEntryEvent.HttpsSourceHasHttpResource.get -> bool static NuGet.Protocol.Events.ProtocolDiagnostics.ServiceIndexEntryEvent -> NuGet.Protocol.Events.ProtocolDiagnostics.ProtocolDiagnosticServiceIndexEntryEventHandler ~NuGet.Protocol.Events.ProtocolDiagnosticServiceIndexEntryEvent.ProtocolDiagnosticServiceIndexEntryEvent(string source, bool httpsSourceHasHttpResource) -> void ~NuGet.Protocol.Events.ProtocolDiagnosticServiceIndexEntryEvent.Source.get -> string -~NuGet.Protocol.Plugins.PluginDiscoverer.PluginDiscoverer(string rawPluginPaths) -> void ~const NuGet.Protocol.JsonProperties.ReadmeFileUrl = "readmeFileUrl" -> string ~NuGet.Protocol.Core.Types.IPackageSearchMetadata.ReadmeFileUrl.get -> string ~NuGet.Protocol.Core.Types.PackageSearchMetadataBuilder.ClonedPackageSearchMetadata.ReadmeFileUrl.get -> string diff --git a/src/NuGet.Core/NuGet.Protocol/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt b/src/NuGet.Core/NuGet.Protocol/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt index 86a9ec539e4..07c3e1d7c43 100644 --- a/src/NuGet.Core/NuGet.Protocol/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt +++ b/src/NuGet.Core/NuGet.Protocol/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt @@ -1015,8 +1015,6 @@ NuGet.Protocol.Plugins.IPlugin.Closed -> System.EventHandler ~NuGet.Protocol.Plugins.IPlugin.Name.get -> string NuGet.Protocol.Plugins.IPluginDiscoverer ~NuGet.Protocol.Plugins.IPluginDiscoverer.DiscoverAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> -NuGet.Protocol.Plugins.IPluginFactory -~NuGet.Protocol.Plugins.IPluginFactory.GetOrCreateAsync(string filePath, System.Collections.Generic.IEnumerable arguments, NuGet.Protocol.Plugins.IRequestHandlers requestHandlers, NuGet.Protocol.Plugins.ConnectionOptions options, System.Threading.CancellationToken sessionCancellationToken) -> System.Threading.Tasks.Task NuGet.Protocol.Plugins.IPluginManager ~NuGet.Protocol.Plugins.IPluginManager.CreatePluginsAsync(NuGet.Protocol.Core.Types.SourceRepository source, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> ~NuGet.Protocol.Plugins.IPluginManager.FindAvailablePluginsAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> @@ -1211,12 +1209,9 @@ NuGet.Protocol.Plugins.PluginException ~NuGet.Protocol.Plugins.PluginException.PluginException(string message) -> void ~NuGet.Protocol.Plugins.PluginException.PluginException(string message, System.Exception innerException) -> void NuGet.Protocol.Plugins.PluginFactory -NuGet.Protocol.Plugins.PluginFactory.Dispose() -> void -~NuGet.Protocol.Plugins.PluginFactory.GetOrCreateAsync(string filePath, System.Collections.Generic.IEnumerable arguments, NuGet.Protocol.Plugins.IRequestHandlers requestHandlers, NuGet.Protocol.Plugins.ConnectionOptions options, System.Threading.CancellationToken sessionCancellationToken) -> System.Threading.Tasks.Task NuGet.Protocol.Plugins.PluginFactory.PluginFactory(System.TimeSpan pluginIdleTimeout) -> void NuGet.Protocol.Plugins.PluginFile ~NuGet.Protocol.Plugins.PluginFile.Path.get -> string -~NuGet.Protocol.Plugins.PluginFile.PluginFile(string filePath, System.Lazy state) -> void ~NuGet.Protocol.Plugins.PluginFile.State.get -> System.Lazy NuGet.Protocol.Plugins.PluginFileState NuGet.Protocol.Plugins.PluginFileState.InvalidEmbeddedSignature = 3 -> NuGet.Protocol.Plugins.PluginFileState @@ -1228,7 +1223,6 @@ NuGet.Protocol.Plugins.PluginManager NuGet.Protocol.Plugins.PluginManager.Dispose() -> void ~NuGet.Protocol.Plugins.PluginManager.EnvironmentVariableReader.get -> NuGet.Common.IEnvironmentVariableReader ~NuGet.Protocol.Plugins.PluginManager.FindAvailablePluginsAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> -~NuGet.Protocol.Plugins.PluginManager.PluginManager(NuGet.Common.IEnvironmentVariableReader reader, System.Lazy pluginDiscoverer, System.Func pluginFactoryCreator, System.Lazy pluginsCacheDirectoryPath) -> void ~NuGet.Protocol.Plugins.PluginManager.TryGetSourceAgnosticPluginAsync(NuGet.Protocol.Plugins.PluginDiscoveryResult pluginDiscoveryResult, NuGet.Protocol.Plugins.OperationClaim requestedOperationClaim, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> NuGet.Protocol.Plugins.PluginMulticlientUtilities ~NuGet.Protocol.Plugins.PluginMulticlientUtilities.DoOncePerPluginLifetimeAsync(string key, System.Func taskFunc, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task diff --git a/src/NuGet.Core/NuGet.Protocol/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/src/NuGet.Core/NuGet.Protocol/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 3a90c9f9e66..4b50c64bfeb 100644 --- a/src/NuGet.Core/NuGet.Protocol/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/NuGet.Core/NuGet.Protocol/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,11 +1,15 @@ #nullable enable +NuGet.Protocol.Plugins.PluginDiscoverer.PluginDiscoverer() -> void +virtual NuGet.Protocol.Plugins.PluginFactory.Dispose() -> void +~NuGet.Protocol.Plugins.PluginFile.PluginFile(string filePath, System.Lazy state, bool requiresDotnetHost) -> void +~NuGet.Protocol.Plugins.PluginManager.PluginManager(NuGet.Common.IEnvironmentVariableReader reader, System.Lazy pluginDiscoverer, System.Func pluginFactoryCreator, System.Lazy pluginsCacheDirectoryPath) -> void +~virtual NuGet.Protocol.Plugins.PluginFactory.GetOrCreateAsync(NuGet.Protocol.Plugins.PluginFile pluginFile, System.Collections.Generic.IEnumerable arguments, NuGet.Protocol.Plugins.IRequestHandlers requestHandlers, NuGet.Protocol.Plugins.ConnectionOptions options, System.Threading.CancellationToken sessionCancellationToken) -> System.Threading.Tasks.Task NuGet.Protocol.Events.ProtocolDiagnostics.ProtocolDiagnosticServiceIndexEntryEventHandler NuGet.Protocol.Events.ProtocolDiagnosticServiceIndexEntryEvent NuGet.Protocol.Events.ProtocolDiagnosticServiceIndexEntryEvent.HttpsSourceHasHttpResource.get -> bool static NuGet.Protocol.Events.ProtocolDiagnostics.ServiceIndexEntryEvent -> NuGet.Protocol.Events.ProtocolDiagnostics.ProtocolDiagnosticServiceIndexEntryEventHandler ~NuGet.Protocol.Events.ProtocolDiagnosticServiceIndexEntryEvent.ProtocolDiagnosticServiceIndexEntryEvent(string source, bool httpsSourceHasHttpResource) -> void ~NuGet.Protocol.Events.ProtocolDiagnosticServiceIndexEntryEvent.Source.get -> string -~NuGet.Protocol.Plugins.PluginDiscoverer.PluginDiscoverer(string rawPluginPaths) -> void ~const NuGet.Protocol.JsonProperties.ReadmeFileUrl = "readmeFileUrl" -> string ~NuGet.Protocol.Core.Types.IPackageSearchMetadata.ReadmeFileUrl.get -> string ~NuGet.Protocol.Core.Types.PackageSearchMetadataBuilder.ClonedPackageSearchMetadata.ReadmeFileUrl.get -> string diff --git a/test/NuGet.Core.FuncTests/NuGet.Protocol.FuncTest/PluginTests.cs b/test/NuGet.Core.FuncTests/NuGet.Protocol.FuncTest/PluginTests.cs index 157f638d25e..4fc22ae9e0d 100644 --- a/test/NuGet.Core.FuncTests/NuGet.Protocol.FuncTest/PluginTests.cs +++ b/test/NuGet.Core.FuncTests/NuGet.Protocol.FuncTest/PluginTests.cs @@ -26,6 +26,18 @@ namespace NuGet.Protocol.FuncTest { public class PluginTests { + public static bool IsDesktop + { + get + { +#if IS_DESKTOP + return true; +#else + return false; +#endif + } + } + private static readonly FileInfo PluginFile; private static readonly ushort PortNumber = 11000; private static readonly IEnumerable PluginArguments = PluginConstants.PluginArguments @@ -60,7 +72,7 @@ public async Task GetOrCreateAsync_WithUnhandledExceptionInPlugin_Throws() using (var pluginFactory = new PluginFactory(PluginConstants.IdleTimeout)) { var exception = await Assert.ThrowsAsync(() => pluginFactory.GetOrCreateAsync( - PluginFile.FullName, + new PluginFile(filePath: PluginFile.FullName, state: new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop), PluginConstants.PluginArguments.Concat(new[] { "-ThrowException Unhandled" }), new RequestHandlers(), ConnectionOptions.CreateDefault(), @@ -81,7 +93,7 @@ public async Task GetOrCreateAsync_WithHandledExceptionAndExitInPlugin_Throws() using (var pluginFactory = new PluginFactory(PluginConstants.IdleTimeout)) { var exception = await Assert.ThrowsAsync(() => pluginFactory.GetOrCreateAsync( - PluginFile.FullName, + new PluginFile(filePath: PluginFile.FullName, state: new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop), PluginConstants.PluginArguments.Concat(new[] { "-ThrowException Handled" }), new RequestHandlers(), ConnectionOptions.CreateDefault(), @@ -102,7 +114,7 @@ public async Task GetOrCreateAsync_WhenPluginFreezes_Throws() using (var pluginFactory = new PluginFactory(PluginConstants.IdleTimeout)) { var exception = await Assert.ThrowsAsync(() => pluginFactory.GetOrCreateAsync( - PluginFile.FullName, + new PluginFile(filePath: PluginFile.FullName, state: new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop), PluginConstants.PluginArguments.Concat(new[] { "-Freeze" }), new RequestHandlers(), ConnectionOptions.CreateDefault(), @@ -123,7 +135,7 @@ public async Task GetOrCreateAsync_WhenPluginCausesProtocolException_Throws() using (var pluginFactory = new PluginFactory(PluginConstants.IdleTimeout)) { var exception = await Assert.ThrowsAsync(() => pluginFactory.GetOrCreateAsync( - PluginFile.FullName, + new PluginFile(filePath: PluginFile.FullName, state: new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop), PluginConstants.PluginArguments.Concat(new[] { "-CauseProtocolException" }), new RequestHandlers(), ConnectionOptions.CreateDefault(), @@ -318,7 +330,7 @@ internal static async Task CreateAsync() var pluginFactory = new PluginFactory(PluginConstants.IdleTimeout); var options = ConnectionOptions.CreateDefault(); var plugin = await pluginFactory.GetOrCreateAsync( - PluginFile.FullName, + new PluginFile(filePath: PluginFile.FullName, state: new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop), PluginArguments, new RequestHandlers(), options, diff --git a/test/NuGet.Core.Tests/NuGet.Credentials.Test/PluginManagerMock.cs b/test/NuGet.Core.Tests/NuGet.Credentials.Test/PluginManagerMock.cs index 7273edc056e..ce13791271e 100644 --- a/test/NuGet.Core.Tests/NuGet.Credentials.Test/PluginManagerMock.cs +++ b/test/NuGet.Core.Tests/NuGet.Credentials.Test/PluginManagerMock.cs @@ -66,9 +66,21 @@ internal TestExpectation( internal sealed class PluginManagerMock : IDisposable { + public static bool IsDesktop + { + get + { +#if IS_DESKTOP + return true; +#else + return false; +#endif + } + } + private readonly Mock _connection; private readonly TestExpectation _expectations; - private readonly Mock _factory; + private readonly Mock _factory; private readonly Mock _plugin; private readonly Mock _pluginDiscoverer; private readonly Mock _reader; @@ -99,7 +111,7 @@ internal PluginManagerMock( _plugin = new Mock(MockBehavior.Strict); EnsurePluginSetupCalls(); - _factory = new Mock(MockBehavior.Strict); + _factory = new Mock(MockBehavior.Strict); EnsureFactorySetupCalls(pluginFilePath); // Setup connection @@ -238,7 +250,7 @@ private void EnsureDiscovererIsCalled(string pluginFilePath, PluginFileState plu _pluginDiscoverer.Setup(x => x.DiscoverAsync(It.IsAny())) .ReturnsAsync(new[] { - new PluginDiscoveryResult(new PluginFile(pluginFilePath, new Lazy(() => pluginFileState))) + new PluginDiscoveryResult(new PluginFile(pluginFilePath, new Lazy(() => pluginFileState), requiresDotnetHost: !IsDesktop)) }); } @@ -281,7 +293,7 @@ private void EnsureFactorySetupCalls(string pluginFilePath) { _factory.Setup(x => x.Dispose()); _factory.Setup(x => x.GetOrCreateAsync( - It.Is(p => p == pluginFilePath), + It.Is(p => p.Path == pluginFilePath), It.IsNotNull>(), It.IsNotNull(), It.IsNotNull(), diff --git a/test/NuGet.Core.Tests/NuGet.Credentials.Test/SecurePluginCredentialProviderBuilderTests.cs b/test/NuGet.Core.Tests/NuGet.Credentials.Test/SecurePluginCredentialProviderBuilderTests.cs index 9acf4a02f70..49d868ff7cd 100644 --- a/test/NuGet.Core.Tests/NuGet.Credentials.Test/SecurePluginCredentialProviderBuilderTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Credentials.Test/SecurePluginCredentialProviderBuilderTests.cs @@ -17,6 +17,17 @@ namespace NuGet.Credentials.Test { public class SecurePluginCredentialProviderBuilderTests : IDisposable { + public static bool IsDesktop + { + get + { +#if IS_DESKTOP + return true; +#else + return false; +#endif + } + } private readonly TestDirectory _testDirectory; public SecurePluginCredentialProviderBuilderTests() @@ -156,7 +167,7 @@ internal PluginManagerBuilderMock(List> pl PluginManager = new PluginManager( reader.Object, new Lazy(() => pluginDiscoverer.Object), - (TimeSpan idleTimeout) => Mock.Of(), + (TimeSpan idleTimeout) => Mock.Of(), new Lazy(() => _testDirectory.Path)); } @@ -170,7 +181,7 @@ private static IEnumerable GetPluginDiscoveryResults(List var results = new List(); foreach (var plugin in plugins) { - var file = new PluginFile(plugin.Key, new Lazy(() => plugin.Value)); + var file = new PluginFile(plugin.Key, new Lazy(() => plugin.Value), requiresDotnetHost: !IsDesktop); results.Add(new PluginDiscoveryResult(file)); } @@ -183,7 +194,7 @@ private PluginManager CreateDefaultPluginManager() return new PluginManager( Mock.Of(), new Lazy(), - (TimeSpan idleTimeout) => Mock.Of(), + (TimeSpan idleTimeout) => Mock.Of(), new Lazy(() => _testDirectory.Path)); } } diff --git a/test/NuGet.Core.Tests/NuGet.Credentials.Test/SecurePluginCredentialProviderTests.cs b/test/NuGet.Core.Tests/NuGet.Credentials.Test/SecurePluginCredentialProviderTests.cs index bbf4d232df5..d17d61c3c81 100644 --- a/test/NuGet.Core.Tests/NuGet.Credentials.Test/SecurePluginCredentialProviderTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Credentials.Test/SecurePluginCredentialProviderTests.cs @@ -17,6 +17,18 @@ namespace NuGet.Credentials.Test { public sealed class SecurePluginCredentialProviderTests : IDisposable { + public bool IsDesktop + { + get + { +#if IS_DESKTOP + return true; +#else + return false; +#endif + } + } + private static readonly Uri _uri = new Uri("https://unit.test"); private const string _username = "username"; private const string _password = "password"; @@ -94,7 +106,7 @@ public async Task GetAsync_WithValidArguments_ReturnsValidCredentials() pluginFileState: PluginFileState.Valid, expectations: expectation)) { - var discoveryResult = new PluginDiscoveryResult(new PluginFile("a", new Lazy(() => PluginFileState.Valid))); + var discoveryResult = new PluginDiscoveryResult(new PluginFile("a", new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop)); var provider = new SecurePluginCredentialProvider(test.PluginManager, discoveryResult, canShowDialog: true, logger: NullLogger.Instance); IWebProxy proxy = null; @@ -129,7 +141,7 @@ public async Task GetAsync_WhenCalledMultipleTimes_DoesNotCreateMultipleInstance pluginFileState: PluginFileState.Valid, expectations: expectation)) { - var discoveryResult = new PluginDiscoveryResult(new PluginFile("a", new Lazy(() => PluginFileState.Valid))); + var discoveryResult = new PluginDiscoveryResult(new PluginFile("a", new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop)); var provider = new SecurePluginCredentialProvider(test.PluginManager, discoveryResult, canShowDialog: true, logger: NullLogger.Instance); IWebProxy proxy = null; @@ -166,7 +178,7 @@ public async Task GetAsync_WhenPluginClaimsMultipleOperations_ReturnsValidCreden pluginFileState: PluginFileState.Valid, expectations: expectation)) { - var discoveryResult = new PluginDiscoveryResult(new PluginFile("a", new Lazy(() => PluginFileState.Valid))); + var discoveryResult = new PluginDiscoveryResult(new PluginFile("a", new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop)); var provider = new SecurePluginCredentialProvider(test.PluginManager, discoveryResult, canShowDialog: true, logger: NullLogger.Instance); IWebProxy proxy = null; @@ -206,7 +218,7 @@ public async Task GetAsync_WhenProxyIsUsed_SetsProxyCredentials() pluginFileState: PluginFileState.Valid, expectations: expectation)) { - var discoveryResult = new PluginDiscoveryResult(new PluginFile("a", new Lazy(() => PluginFileState.Valid))); + var discoveryResult = new PluginDiscoveryResult(new PluginFile("a", new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop)); var provider = new SecurePluginCredentialProvider(test.PluginManager, discoveryResult, canShowDialog: true, logger: NullLogger.Instance); var proxy = new System.Net.WebProxy() { @@ -243,7 +255,7 @@ public async Task GetAsync_WhenCalledMultipleTimes_CachesCapabilities() pluginFileState: PluginFileState.Valid, expectations: expectation)) { - var discoveryResult = new PluginDiscoveryResult(new PluginFile("a", new Lazy(() => PluginFileState.Valid))); + var discoveryResult = new PluginDiscoveryResult(new PluginFile("a", new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop)); var provider = new SecurePluginCredentialProvider(test.PluginManager, discoveryResult, canShowDialog: true, logger: NullLogger.Instance); IWebProxy proxy = null; @@ -273,7 +285,7 @@ public async Task GetAsync_WhenCalledMultipleTimes_CachesCapabilities() pluginFileState: PluginFileState.Valid, expectations: expectations2)) { - var discoveryResult = new PluginDiscoveryResult(new PluginFile("a", new Lazy(() => PluginFileState.Valid))); + var discoveryResult = new PluginDiscoveryResult(new PluginFile("a", new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop)); var provider = new SecurePluginCredentialProvider(test.PluginManager, discoveryResult, canShowDialog: true, logger: NullLogger.Instance); IWebProxy proxy = null; @@ -311,7 +323,7 @@ public async Task GetAsync_SendsCorrectCanShowDialogValue() pluginFileState: PluginFileState.Valid, expectations: expectation)) { - var discoveryResult = new PluginDiscoveryResult(new PluginFile("a", new Lazy(() => PluginFileState.Valid))); + var discoveryResult = new PluginDiscoveryResult(new PluginFile("a", new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop)); var provider = new SecurePluginCredentialProvider(test.PluginManager, discoveryResult, canShowDialog, logger: NullLogger.Instance); IWebProxy proxy = null; @@ -344,7 +356,7 @@ public async Task GetAsync_WhenPluginManagerReturnsException_ExceptionIsPropagat It.IsAny())) .ReturnsAsync(result); - var pluginDiscoveryResult = new PluginDiscoveryResult(new PluginFile("c", new Lazy(() => PluginFileState.Valid))); + var pluginDiscoveryResult = new PluginDiscoveryResult(new PluginFile("c", new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop)); var logger = new Mock(MockBehavior.Strict); logger.Setup(x => x.LogError(It.Is(data => data == expectedMessage))); @@ -379,7 +391,7 @@ public async Task GetAsync_WhenCredentialPluginIsUnableToAcquireCredentials_Retu pluginFileState: PluginFileState.Valid, expectations: expectation)) { - var discoveryResult = new PluginDiscoveryResult(new PluginFile("a", new Lazy(() => PluginFileState.Valid))); + var discoveryResult = new PluginDiscoveryResult(new PluginFile("a", new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop)); var provider = new SecurePluginCredentialProvider(test.PluginManager, discoveryResult, canShowDialog: true, logger: NullLogger.Instance); IWebProxy proxy = null; @@ -397,7 +409,7 @@ public async Task GetAsync_WhenCredentialPluginIsUnableToAcquireCredentials_Retu private PluginDiscoveryResult CreatePluginDiscoveryResult(PluginFileState pluginState = PluginFileState.Valid) { - return new PluginDiscoveryResult(new PluginFile(Path.Combine(_testDirectory.Path, "plugin.exe"), new Lazy(() => pluginState))); + return new PluginDiscoveryResult(new PluginFile(Path.Combine(_testDirectory.Path, "plugin.exe"), new Lazy(() => pluginState), requiresDotnetHost: !IsDesktop)); } private PluginManager CreateDefaultPluginManager() @@ -405,7 +417,7 @@ private PluginManager CreateDefaultPluginManager() return new PluginManager( Mock.Of(), new Lazy(), - (TimeSpan idleTimeout) => Mock.Of(), + (TimeSpan idleTimeout) => Mock.Of(), new Lazy(() => _testDirectory.Path)); } diff --git a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginDiscovererTests.cs b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginDiscovererTests.cs index f48460dee65..44d148d3b3b 100644 --- a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginDiscovererTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginDiscovererTests.cs @@ -3,10 +3,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Moq; +using NuGet.Common; using NuGet.Test.Utility; using Xunit; @@ -20,15 +23,27 @@ public class PluginDiscovererTests [InlineData(" ")] public void Constructor_AcceptsAnyString(string rawPluginPaths) { - using (new PluginDiscoverer(rawPluginPaths)) + var environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.DesktopPluginPaths)).Returns(rawPluginPaths); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.CorePluginPaths)).Returns(rawPluginPaths); + + Exception exception = Record.Exception(() => { - } + using (new PluginDiscoverer()) + { + } + }); + + Assert.Null(exception); } [Fact] public async Task DiscoverAsync_ThrowsIfCancelled() { - using (var discoverer = new PluginDiscoverer(rawPluginPaths: "")) + var environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.DesktopPluginPaths)).Returns(""); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.CorePluginPaths)).Returns(""); + using (var discoverer = new PluginDiscoverer(environmentalVariableReader.Object)) { await Assert.ThrowsAsync( () => discoverer.DiscoverAsync(new CancellationToken(canceled: true))); @@ -38,7 +53,10 @@ await Assert.ThrowsAsync( [Fact] public async Task DiscoverAsync_DoesNotThrowIfNoValidFilePathsAndFallbackEmbeddedSignatureVerifier() { - using (var discoverer = new PluginDiscoverer(rawPluginPaths: ";")) + var environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.DesktopPluginPaths)).Returns(Path.PathSeparator.ToString()); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.CorePluginPaths)).Returns(Path.PathSeparator.ToString()); + using (var discoverer = new PluginDiscoverer(environmentalVariableReader.Object)) { var pluginFiles = await discoverer.DiscoverAsync(CancellationToken.None); @@ -54,13 +72,11 @@ public async Task DiscoverAsync_PerformsDiscoveryOnlyOnce() var pluginPath = Path.Combine(testDirectory.Path, "a"); File.WriteAllText(pluginPath, string.Empty); + var environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.DesktopPluginPaths)).Returns(pluginPath); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.CorePluginPaths)).Returns(pluginPath); - var responses = new Dictionary() - { - { pluginPath, true } - }; - - using (var discoverer = new PluginDiscoverer(pluginPath)) + using (var discoverer = new PluginDiscoverer(environmentalVariableReader.Object)) { var results = (await discoverer.DiscoverAsync(CancellationToken.None)).ToArray(); @@ -90,9 +106,11 @@ public async Task DiscoverAsync_HandlesAllPluginFileStates() File.WriteAllText(pluginPaths[1], string.Empty); string rawPluginPaths = - $"{pluginPaths[0]};{pluginPaths[1]};c"; - - using (var discoverer = new PluginDiscoverer(rawPluginPaths)) + $"{pluginPaths[0]}{Path.PathSeparator}{pluginPaths[1]}{Path.PathSeparator}c"; + var environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.DesktopPluginPaths)).Returns(rawPluginPaths); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.CorePluginPaths)).Returns(rawPluginPaths); + using (var discoverer = new PluginDiscoverer(environmentalVariableReader.Object)) { var results = (await discoverer.DiscoverAsync(CancellationToken.None)).ToArray(); @@ -121,8 +139,10 @@ public async Task DiscoverAsync_HandlesAllPluginFileStates() public async Task DiscoverAsync_DisallowsNonRootedFilePaths(string pluginPath) { var responses = new Dictionary() { { pluginPath, true } }; - - using (var discoverer = new PluginDiscoverer(pluginPath)) + var environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.DesktopPluginPaths)).Returns(pluginPath); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.CorePluginPaths)).Returns(pluginPath); + using (var discoverer = new PluginDiscoverer(environmentalVariableReader.Object)) { var results = (await discoverer.DiscoverAsync(CancellationToken.None)).ToArray(); @@ -140,8 +160,11 @@ public async Task DiscoverAsync_IsIdempotent() var pluginPath = Path.Combine(testDirectory.Path, "a"); File.WriteAllText(pluginPath, string.Empty); + var environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.DesktopPluginPaths)).Returns(pluginPath); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.CorePluginPaths)).Returns(pluginPath); - using (var discoverer = new PluginDiscoverer(pluginPath)) + using (var discoverer = new PluginDiscoverer(environmentalVariableReader.Object)) { var firstResult = await discoverer.DiscoverAsync(CancellationToken.None); var firstState = firstResult.SingleOrDefault().PluginFile.State.Value; @@ -153,5 +176,567 @@ public async Task DiscoverAsync_IsIdempotent() } } } + + [PlatformTheory(Platform.Windows)] + [InlineData("nuget-plugin-myPlugin.exe")] + [InlineData("nuget-plugin-myPlugin.bat")] + public async Task DiscoverAsync_withValidDotNetToolsPluginWindows_FindsThePlugin(string fileName) + { + using (var testDirectory = TestDirectory.Create()) + { + // Arrange + var pluginPath = Path.Combine(testDirectory.Path, "myPlugin"); + Directory.CreateDirectory(pluginPath); + var myPlugin = Path.Combine(pluginPath, fileName); + Mock environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.PluginPaths)).Returns(pluginPath); + + File.WriteAllText(myPlugin, string.Empty); + + using (var discoverer = new PluginDiscoverer(environmentalVariableReader.Object)) + { + // Act + var result = await discoverer.DiscoverAsync(CancellationToken.None); + + // Assert + var discovered = false; + + foreach (PluginDiscoveryResult discoveryResult in result) + { + if (myPlugin == discoveryResult.PluginFile.Path) discovered = true; + } + + Assert.True(discovered); + } + } + } + + [PlatformTheory(Platform.Windows)] + [InlineData("nuget-plugin-myPlugin.exe")] + [InlineData("nuget-plugin-myPlugin.bat")] + public async Task DiscoverAsync_WithPluginPathSpecifiedInNuGetPluginPathsEnvVariableWindows_FindsThePlugin(string fileName) + { + using (var testDirectory = TestDirectory.Create()) + { + // Arrange + var pluginPath = Path.Combine(testDirectory.Path, "myPlugin"); + Directory.CreateDirectory(pluginPath); + var myPlugin = Path.Combine(pluginPath, fileName); + Mock environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable("NUGET_PLUGIN_PATHS")).Returns(pluginPath); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable("PATHS")).Returns(""); + File.WriteAllText(myPlugin, string.Empty); + + using (var discoverer = new PluginDiscoverer(environmentalVariableReader.Object)) + { + // Act + var result = await discoverer.DiscoverAsync(CancellationToken.None); + + // Assert + var discovered = false; + + foreach (PluginDiscoveryResult discoveryResult in result) + { + if (myPlugin == discoveryResult.PluginFile.Path) discovered = true; + } + + Assert.True(discovered); + } + } + } + + [PlatformTheory(Platform.Windows)] + [InlineData("nugetplugin-myPlugin.exe")] + [InlineData("nugetplugin-myPlugin.bat")] + public async Task DiscoverAsync_withInValidDotNetToolsPluginNameWindows_DoesNotFindThePlugin(string fileName) + { + using (var testDirectory = TestDirectory.Create()) + { + // Arrange + var pluginPath = Path.Combine(testDirectory.Path, "myPlugin"); + Directory.CreateDirectory(pluginPath); + var myPlugin = Path.Combine(pluginPath, fileName); + Mock environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(It.IsAny())).Returns(pluginPath); + + File.WriteAllText(myPlugin, string.Empty); + + using (var discoverer = new PluginDiscoverer(environmentalVariableReader.Object)) + { + // Act + var result = await discoverer.DiscoverAsync(CancellationToken.None); + + // Assert + var discovered = false; + + foreach (PluginDiscoveryResult discoveryResult in result) + { + if (myPlugin == discoveryResult.PluginFile.Path) discovered = true; + } + + Assert.False(discovered); + } + } + } + + [PlatformFact(Platform.Linux)] + public async Task DiscoverAsync_withValidDotNetToolsPluginLinux_FindsThePlugin() + { + using (var testDirectory = TestDirectory.Create()) + { + // Arrange + var pluginPath = Path.Combine(testDirectory.Path, "myPlugins"); + Directory.CreateDirectory(pluginPath); + var myPlugin = Path.Combine(pluginPath, "nuget-plugin-MyPlugin"); + File.WriteAllText(myPlugin, string.Empty); + Mock environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.PluginPaths)).Returns(pluginPath); + + using (var process = new Process()) + { + // Use a shell command to make the file executable + process.StartInfo.FileName = "/bin/bash"; + process.StartInfo.Arguments = $"-c \"chmod +x {myPlugin}\""; + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.Start(); + process.WaitForExit(); + + if (process.ExitCode == 0) + { + using (var discoverer = new PluginDiscoverer(environmentalVariableReader.Object)) + { + // Act + var result = await discoverer.DiscoverAsync(CancellationToken.None); + + // Assert + var discovered = false; + + foreach (PluginDiscoveryResult discoveryResult in result) + { + if (myPlugin == discoveryResult.PluginFile.Path) discovered = true; + } + + Assert.True(discovered); + } + } + } + } + } + + [PlatformFact(Platform.Linux)] + public async Task DiscoverAsync_WithPluginPathSpecifiedInNuGetPluginPathsEnvVariableLinux_FindsThePlugin() + { + using (var testDirectory = TestDirectory.Create()) + { + // Arrange + var pluginPath = Path.Combine(testDirectory.Path, "myPlugins"); + Directory.CreateDirectory(pluginPath); + var myPlugin = Path.Combine(pluginPath, "nuget-plugin-MyPlugin"); + File.WriteAllText(myPlugin, string.Empty); + Mock environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.PluginPaths)).Returns(pluginPath); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable("PATHS")).Returns(""); + + using (var process = new Process()) + { + // Use a shell command to make the file executable + process.StartInfo.FileName = "/bin/bash"; + process.StartInfo.Arguments = $"-c \"chmod +x {myPlugin}\""; + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.Start(); + process.WaitForExit(); + + if (process.ExitCode == 0) + { + using (var discoverer = new PluginDiscoverer(environmentalVariableReader.Object)) + { + // Act + var result = await discoverer.DiscoverAsync(CancellationToken.None); + + // Assert + var discovered = false; + + foreach (PluginDiscoveryResult discoveryResult in result) + { + if (myPlugin == discoveryResult.PluginFile.Path) discovered = true; + } + + Assert.True(discovered); + } + } + } + } + } + + [PlatformFact(Platform.Linux)] + public async Task DiscoverAsync_withNoExecutableValidDotNetToolsPluginLinux_DoesNotFindThePlugin() + { + using (var testDirectory = TestDirectory.Create()) + { + // Arrange + var pluginPath = Path.Combine(testDirectory.Path, "myPlugins"); + Directory.CreateDirectory(pluginPath); + var myPlugin = Path.Combine(pluginPath, "nuget-plugin-MyPlugin"); + File.WriteAllText(myPlugin, string.Empty); + Mock environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.PluginPaths)).Returns(pluginPath); + + using (var process = new Process()) + { + // Use a shell command to make the file not executable + process.StartInfo.FileName = "/bin/bash"; + process.StartInfo.Arguments = $"-c \"chmod -x {myPlugin}\""; + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.Start(); + process.WaitForExit(); + + if (process.ExitCode == 0) + { + using (var discoverer = new PluginDiscoverer(environmentalVariableReader.Object)) + { + // Act + var result = await discoverer.DiscoverAsync(CancellationToken.None); + + // Assert + var discovered = false; + + foreach (PluginDiscoveryResult discoveryResult in result) + { + if (myPlugin == discoveryResult.PluginFile.Path) discovered = true; + } + + Assert.False(discovered); + } + } + } + } + } + + [PlatformFact(Platform.Windows)] + public void GetPluginsInNuGetPluginPaths_WithNuGetPluginPathsSet_ReturnsPluginsInNuGetPluginPathOnly() + { + // Arrange + using TestDirectory pluginPathDirectory = TestDirectory.Create(); + using TestDirectory pathDirectory = TestDirectory.Create(); + var pluginInNuGetPluginPathDirectoryFilePath = Path.Combine(pluginPathDirectory.Path, "nuget-plugin-auth.exe"); + var pluginInPathDirectoryFilePath = Path.Combine(pathDirectory.Path, "nuget-plugin-in-path-directory.exe"); + File.Create(pluginInNuGetPluginPathDirectoryFilePath); + File.Create(pluginInPathDirectoryFilePath); + Mock environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.PluginPaths)).Returns(Directory.GetParent(pluginInNuGetPluginPathDirectoryFilePath).FullName); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable("PATH")).Returns(Directory.GetParent(pluginInPathDirectoryFilePath).FullName); + PluginDiscoverer pluginDiscoverer = new PluginDiscoverer(environmentalVariableReader.Object); + + // Act + var plugins = pluginDiscoverer.GetPluginsInNuGetPluginPaths(); + + // Assert + Assert.Single(plugins); + Assert.Equal(pluginInNuGetPluginPathDirectoryFilePath, plugins[0].Path); + Assert.False(plugins[0].RequiresDotnetHost); + } + + [PlatformFact(Platform.Windows)] + public void GetPluginsInNuGetPluginPaths_WithoutNuGetPluginPaths_ReturnsEmpty() + { + // Arrange + using var pathDirectory = TestDirectory.Create(); + var pluginFilePath = Path.Combine(pathDirectory.Path, "nuget-plugin-fallback.exe"); + File.Create(pluginFilePath); + + var environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable("PATH")).Returns(pathDirectory.Path); + + var pluginDiscoverer = new PluginDiscoverer(environmentalVariableReader.Object); + + // Act + var plugins = pluginDiscoverer.GetPluginsInNuGetPluginPaths(); + + // Assert + Assert.Empty(plugins); + } + + [PlatformFact(Platform.Windows)] + public void GetPluginsInPATH_WithPATHSet_ReturnsPlugin() + { + // Arrange + using TestDirectory pluginPathDirectory = TestDirectory.Create(); + using TestDirectory pathDirectory = TestDirectory.Create(); + var pluginInNuGetPluginPathDirectoryFilePath = Path.Combine(pluginPathDirectory.Path, "nuget-plugin-auth.exe"); + var pluginInPathDirectoryFilePath = Path.Combine(pathDirectory.Path, "nuget-plugin-in-path-directory.exe"); + File.Create(pluginInNuGetPluginPathDirectoryFilePath); + File.Create(pluginInPathDirectoryFilePath); + Mock environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.PluginPaths)).Returns(Directory.GetParent(pluginInNuGetPluginPathDirectoryFilePath).FullName); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable("PATH")).Returns(Directory.GetParent(pluginInPathDirectoryFilePath).FullName); + PluginDiscoverer pluginDiscoverer = new PluginDiscoverer(environmentalVariableReader.Object); + + // Act + var plugins = pluginDiscoverer.GetPluginsInPath(); + + // Assert + Assert.Single(plugins); + Assert.Equal(pluginInPathDirectoryFilePath, plugins[0].Path); + Assert.False(plugins[0].RequiresDotnetHost); + } + + [PlatformFact(Platform.Windows)] + public void GetPluginsInPATH_WithOneValueInPathEmpty_ReturnsPlugin() + { + // Arrange + using TestDirectory pluginPathDirectory = TestDirectory.Create(); + using TestDirectory pathDirectory = TestDirectory.Create(); + var pluginInNuGetPluginPathDirectoryFilePath = Path.Combine(pluginPathDirectory.Path, "nuget-plugin-auth.exe"); + var pluginInPathDirectoryFilePath = Path.Combine(pathDirectory.Path, "nuget-plugin-in-path-directory.exe"); + File.Create(pluginInNuGetPluginPathDirectoryFilePath); + File.Create(pluginInPathDirectoryFilePath); + Mock environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.PluginPaths)).Returns(pluginPathDirectory.Path); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable("PATH")).Returns($"{pathDirectory.Path}{Path.PathSeparator}"); + PluginDiscoverer pluginDiscoverer = new PluginDiscoverer(environmentalVariableReader.Object); + + // Act + var plugins = pluginDiscoverer.GetPluginsInPath(); + + // Assert + Assert.Single(plugins); + Assert.Equal(pluginInPathDirectoryFilePath, plugins[0].Path); + Assert.False(plugins[0].RequiresDotnetHost); + } + + [PlatformFact(Platform.Windows)] + public void GetPluginsInNuGetPluginPaths_NuGetPluginPathsPointsToAFile_TreatsAsPlugin() + { + // Arrange + using TestDirectory testDirectory = TestDirectory.Create(); + var pluginFilePath = Path.Combine(testDirectory.Path, "nuget-plugin-auth.exe"); + File.Create(pluginFilePath); + + var environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.PluginPaths)).Returns(pluginFilePath); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable("PATH")).Returns(string.Empty); + + var pluginDiscoverer = new PluginDiscoverer(environmentalVariableReader.Object); + + // Act + var plugins = pluginDiscoverer.GetPluginsInNuGetPluginPaths(); + + // Assert + Assert.Single(plugins); + Assert.Equal(pluginFilePath, plugins[0].Path); + Assert.False(plugins[0].RequiresDotnetHost); + } + + [PlatformFact(Platform.Windows)] + public void GetPluginsInNuGetPluginPaths_NuGetPluginPathsPointsToAFileThatDoesNotStartWithNugetPlugin_ReturnsNonDotnetPlugin() + { + // Arrange + using TestDirectory testDirectory = TestDirectory.Create(); + var pluginFilePath = Path.Combine(testDirectory.Path, "other-plugin.exe"); + File.Create(pluginFilePath); + + var environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable(EnvironmentVariableConstants.PluginPaths)).Returns(pluginFilePath); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable("PATH")).Returns(string.Empty); + + var pluginDiscoverer = new PluginDiscoverer(environmentalVariableReader.Object); + + // Act + var plugins = pluginDiscoverer.GetPluginsInNuGetPluginPaths(); + + // Assert + Assert.Single(plugins); + } + + [PlatformFact(Platform.Windows)] + public void GetPluginsInPATH_PATHPointsToADirectory_ContainsValidPluginFiles() + { + // Arrange + using var pluginPathDirectory = TestDirectory.Create(); + var validPluginFile = Path.Combine(pluginPathDirectory.Path, "nuget-plugin-auth.exe"); + var invalidPluginFile = Path.Combine(pluginPathDirectory.Path, "not-a-nuget-plugin.exe"); + File.Create(validPluginFile); + File.Create(invalidPluginFile); + + var environmentalVariableReader = new Mock(); + environmentalVariableReader.Setup(env => env.GetEnvironmentVariable("PATH")).Returns(pluginPathDirectory.Path); + + var pluginDiscoverer = new PluginDiscoverer(environmentalVariableReader.Object); + + // Act + var plugins = pluginDiscoverer.GetPluginsInPath(); + + // Assert + Assert.Single(plugins); + Assert.Equal(validPluginFile, plugins[0].Path); + Assert.False(plugins[0].RequiresDotnetHost); + } + + [PlatformFact(Platform.Windows)] + public void GetPluginsInNuGetPluginPaths_NoEnvironmentVariables_ReturnsNoPlugins() + { + // Arrange + var environmentalVariableReader = new Mock(); + var pluginDiscoverer = new PluginDiscoverer(environmentalVariableReader.Object); + + // Act + var plugins = pluginDiscoverer.GetPluginsInNuGetPluginPaths(); + + // Assert + Assert.Empty(plugins); + } + + [PlatformFact(Platform.Windows)] + public void IsValidPluginFile_ExeFile_ReturnsTrue() + { + // Arrange + using TestDirectory testDirectory = TestDirectory.Create(); + var workingPath = testDirectory.Path; + var pluginFilePath = Path.Combine(workingPath, "plugin.exe"); + File.Create(pluginFilePath); + var fileInfo = new FileInfo(pluginFilePath); + + // Act + bool result = PluginDiscoverer.IsValidPluginFile(fileInfo); + + // Assert + Assert.True(result); + } + + [PlatformFact(Platform.Windows)] + public void IsValidPluginFile_Windows_NonExecutableFile_ReturnsFalse() + { + // Arrange + using TestDirectory testDirectory = TestDirectory.Create(); + var workingPath = testDirectory.Path; + var nonPluginFilePath = Path.Combine(workingPath, "plugin.txt"); + File.Create(nonPluginFilePath); + var fileInfo = new FileInfo(nonPluginFilePath); + + // Act + bool result = PluginDiscoverer.IsValidPluginFile(fileInfo); + + // Assert + Assert.False(result); + } + + [PlatformFact(Platform.Linux)] + public void IsValidPluginFile_Unix_ExecutableFile_ReturnsTrue() + { + // Arrange + using TestDirectory testDirectory = TestDirectory.Create(); + var workingPath = testDirectory.Path; + var pluginFilePath = Path.Combine(workingPath, "plugin"); + File.Create(pluginFilePath).Dispose(); + +#if NET8_0_OR_GREATER + // Set execute permissions + File.SetUnixFileMode(pluginFilePath, UnixFileMode.UserExecute | UnixFileMode.UserRead); +#else + // Use chmod to set execute permissions + var process = new Process(); + process.StartInfo.FileName = "/bin/bash"; + process.StartInfo.Arguments = $"-c \"chmod +x {pluginFilePath}\""; + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.Start(); + process.WaitForExit(); +#endif + + var fileInfo = new FileInfo(pluginFilePath); + + // Act + bool result = PluginDiscoverer.IsValidPluginFile(fileInfo); + + // Assert + Assert.True(result); + } + +#if !NET8_0_OR_GREATER + [PlatformFact(Platform.Linux)] + public void IsExecutable_FileIsExecutable_ReturnsTrue() + { + // Arrange + using TestDirectory testDirectory = TestDirectory.Create(); + var workingPath = testDirectory.Path; + var pluginFilePath = Path.Combine(workingPath, "plugin"); + File.Create(pluginFilePath); + + // Set execute permissions + var process = new Process(); + process.StartInfo.FileName = "/bin/bash"; + process.StartInfo.Arguments = $"-c \"chmod +x {pluginFilePath}\""; + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.Start(); + process.WaitForExit(); + + var fileInfo = new FileInfo(pluginFilePath); + + // Act + bool result = PluginDiscoverer.IsExecutable(fileInfo); + + // Assert + Assert.True(result); + } + + [PlatformFact(Platform.Linux)] + public void IsExecutable_FileIsNotExecutable_ReturnsFalse() + { + // Arrange + using TestDirectory testDirectory = TestDirectory.Create(); + var workingPath = testDirectory.Path; + var pluginFilePath = Path.Combine(workingPath, "plugin"); + File.Create(pluginFilePath); + + // Remove execute permissions + var process = new Process(); + process.StartInfo.FileName = "/bin/bash"; + process.StartInfo.Arguments = $"-c \"chmod -x {pluginFilePath}\""; + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.Start(); + process.WaitForExit(); + + var fileInfo = new FileInfo(pluginFilePath); + + // Act + bool result = PluginDiscoverer.IsExecutable(fileInfo); + + // Assert + Assert.False(result); + } + + [PlatformFact(Platform.Linux)] + public void IsExecutable_FileWithSpace_ReturnsTrue() + { + // Arrange + using TestDirectory testDirectory = TestDirectory.Create(); + var workingPath = testDirectory.Path; + var pluginFilePath = Path.Combine(workingPath, "plugin with space"); + File.Create(pluginFilePath).Dispose(); + + // Set execute permissions + var process = new Process(); + process.StartInfo.FileName = "/bin/bash"; + process.StartInfo.Arguments = $"-c \"chmod +x '{pluginFilePath}'\""; + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.Start(); + process.WaitForExit(); + + var fileInfo = new FileInfo(pluginFilePath); + + // Act + bool result = PluginDiscoverer.IsExecutable(fileInfo); + + // Assert + Assert.True(result); + } + +#endif } } diff --git a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginDiscoveryResultTests.cs b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginDiscoveryResultTests.cs index 17493c4913b..2c5b57521e6 100644 --- a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginDiscoveryResultTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginDiscoveryResultTests.cs @@ -8,6 +8,18 @@ namespace NuGet.Protocol.Plugins.Tests { public class PluginDiscoveryResultTests { + public bool IsDesktop + { + get + { +#if IS_DESKTOP + return true; +#else + return false; +#endif + } + } + [Fact] public void Constructor_ThrowsForNullPluginFile() { @@ -20,7 +32,7 @@ public void Constructor_ThrowsForNullPluginFile() [Fact] public void Constructor_InitializesProperties() { - var pluginFile = new PluginFile(filePath: "a", state: new Lazy(() => PluginFileState.InvalidEmbeddedSignature)); + var pluginFile = new PluginFile(filePath: "a", state: new Lazy(() => PluginFileState.InvalidEmbeddedSignature), requiresDotnetHost: !IsDesktop); var result = new PluginDiscoveryResult(pluginFile); diff --git a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginFactoryTests.cs b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginFactoryTests.cs index 4b560bf1acb..bffb021f6f6 100644 --- a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginFactoryTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginFactoryTests.cs @@ -2,14 +2,28 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO; using System.Threading; using System.Threading.Tasks; +using NuGet.Test.Utility; using Xunit; namespace NuGet.Protocol.Plugins.Tests { public class PluginFactoryTests { + public bool IsDesktop + { + get + { +#if IS_DESKTOP + return true; +#else + return false; +#endif + } + } + [Fact] public void Constructor_ThrowsForTimeSpanBelowMinimum() { @@ -46,7 +60,7 @@ public async Task GetOrCreateAsync_ThrowsForNullOrEmptyFilePath(string filePath) var exception = await Assert.ThrowsAsync( () => factory.GetOrCreateAsync( - filePath, + new PluginFile(filePath: filePath, state: new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop), PluginConstants.PluginArguments, new RequestHandlers(), ConnectionOptions.CreateDefault(), @@ -62,7 +76,7 @@ public async Task GetOrCreateAsync_ThrowsForNullArguments() var exception = await Assert.ThrowsAsync( () => factory.GetOrCreateAsync( - filePath: "a", + new PluginFile(filePath: "a", state: new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop), arguments: null, requestHandlers: new RequestHandlers(), options: ConnectionOptions.CreateDefault(), @@ -78,7 +92,7 @@ public async Task GetOrCreateAsync_ThrowsForNullRequestHandlers() var exception = await Assert.ThrowsAsync( () => factory.GetOrCreateAsync( - filePath: "a", + new PluginFile(filePath: "a", state: new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop), arguments: PluginConstants.PluginArguments, requestHandlers: null, options: ConnectionOptions.CreateDefault(), @@ -94,7 +108,7 @@ public async Task GetOrCreateAsync_ThrowsForNullConnectionOptions() var exception = await Assert.ThrowsAsync( () => factory.GetOrCreateAsync( - filePath: "a", + new PluginFile(filePath: "a", state: new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop), arguments: PluginConstants.PluginArguments, requestHandlers: new RequestHandlers(), options: null, @@ -110,7 +124,7 @@ public async Task GetOrCreateAsync_ThrowsIfCancelled() await Assert.ThrowsAsync( () => factory.GetOrCreateAsync( - filePath: "a", + new PluginFile(filePath: "a", state: new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop), arguments: PluginConstants.PluginArguments, requestHandlers: new RequestHandlers(), options: ConnectionOptions.CreateDefault(), @@ -126,7 +140,7 @@ public async Task GetOrCreateAsync_ThrowsIfDisposed() var exception = await Assert.ThrowsAsync( () => factory.GetOrCreateAsync( - filePath: "a", + new PluginFile(filePath: "a", state: new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop), arguments: PluginConstants.PluginArguments, requestHandlers: new RequestHandlers(), options: ConnectionOptions.CreateDefault(), @@ -135,6 +149,34 @@ public async Task GetOrCreateAsync_ThrowsIfDisposed() Assert.Equal(nameof(PluginFactory), exception.ObjectName); } + [PlatformFact(Platform.Windows)] + public async Task GetOrCreateNetPluginAsync_UsingBatchFile_CreatesPluginAndExecutes() + { + using TestDirectory testDirectory = TestDirectory.Create(); + string pluginPath = Path.Combine(testDirectory.Path, "nuget-plugin-batFile.bat"); + string outputPath = Path.Combine(testDirectory.Path, "plugin-output.txt"); + + string batFileContent = $@" + @echo off + echo File executed > ""{outputPath}"" + "; + + File.WriteAllText(pluginPath, batFileContent); + + var args = PluginConstants.PluginArguments; + var reqHandler = new RequestHandlers(); + var options = ConnectionOptions.CreateDefault(); + + var pluginFactory = new PluginFactory(Timeout.InfiniteTimeSpan); + + // Act + var plugin = await Assert.ThrowsAnyAsync(() => pluginFactory.GetOrCreateAsync(new PluginFile(filePath: pluginPath, state: new Lazy(() => PluginFileState.Valid), requiresDotnetHost: false), args, reqHandler, options, CancellationToken.None)); + + // Assert + string outputContent = File.ReadAllText(outputPath); + Assert.Contains("File executed", outputContent); + } + [Fact] public async Task CreateFromCurrentProcessAsync_ThrowsForNullRequestHandlers() { diff --git a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginFileTests.cs b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginFileTests.cs index 660f690216f..923c44842ea 100644 --- a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginFileTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginFileTests.cs @@ -8,12 +8,24 @@ namespace NuGet.Protocol.Plugins.Tests { public class PluginFileTests { + public bool IsDesktop + { + get + { +#if IS_DESKTOP + return true; +#else + return false; +#endif + } + } + [Theory] [InlineData(null)] [InlineData("")] public void Constructor_ThrowsForNullOrEmptyFilePath(string filePath) { - var exception = Assert.Throws(() => new PluginFile(filePath, state: new Lazy(() => PluginFileState.NotFound))); + var exception = Assert.Throws(() => new PluginFile(filePath, state: new Lazy(() => PluginFileState.NotFound), requiresDotnetHost: !IsDesktop)); Assert.Equal("filePath", exception.ParamName); } @@ -21,7 +33,7 @@ public void Constructor_ThrowsForNullOrEmptyFilePath(string filePath) [Fact] public void Constructor_InitializesProperties() { - var pluginFile = new PluginFile(filePath: "a", state: new Lazy(() => PluginFileState.Valid)); + var pluginFile = new PluginFile(filePath: "a", state: new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop); Assert.Equal("a", pluginFile.Path); Assert.Equal(PluginFileState.Valid, pluginFile.State.Value); diff --git a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginManagerTests.cs b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginManagerTests.cs index 07e965955e3..230227c289f 100644 --- a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginManagerTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginManagerTests.cs @@ -17,6 +17,17 @@ namespace NuGet.Protocol.Plugins.Tests public class PluginManagerTests { private const string PluginFilePath = "a"; + public bool IsDesktop + { + get + { +#if IS_DESKTOP + return true; +#else + return false; +#endif + } + } [Fact] public async Task TryGetSourceAgnosticPluginAsync_WhenExceptionIsThrownDuringPluginCreation_PropagatesException() @@ -24,11 +35,11 @@ public async Task TryGetSourceAgnosticPluginAsync_WhenExceptionIsThrownDuringPlu const string message = "b"; var reader = Mock.Of(); - var pluginFactory = new Mock(MockBehavior.Strict); + var pluginFactory = new Mock(MockBehavior.Strict); var exception = new Exception(message); pluginFactory.Setup(x => x.GetOrCreateAsync( - It.Is(filePath => string.Equals(filePath, PluginFilePath, StringComparison.Ordinal)), + It.Is(pluginFile => string.Equals(pluginFile.Path, PluginFilePath, StringComparison.Ordinal)), It.Is>(arguments => arguments != null && arguments.Any()), It.IsNotNull(), It.IsNotNull(), @@ -46,7 +57,7 @@ public async Task TryGetSourceAgnosticPluginAsync_WhenExceptionIsThrownDuringPlu var discoveryResult = new PluginDiscoveryResult( new PluginFile( PluginFilePath, - new Lazy(() => PluginFileState.Valid))); + new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop)); Tuple result = await pluginManager.TryGetSourceAgnosticPluginAsync( discoveryResult, @@ -77,7 +88,7 @@ public async Task TryGetSourceAgnosticPluginAsync_WhenSuccessfullyCreated_Operat var discoveryResult = new PluginDiscoveryResult( new PluginFile( PluginFilePath, - new Lazy(() => PluginFileState.Valid))); + new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop)); Tuple result = await test.PluginManager.TryGetSourceAgnosticPluginAsync( discoveryResult, @@ -115,7 +126,7 @@ public async Task TryGetSourceAgnosticPluginAsync_WhenCacheFileIndicatesIndicate var discoveryResult = new PluginDiscoveryResult( new PluginFile( PluginFilePath, - new Lazy(() => PluginFileState.Valid))); + new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop)); Tuple result = await test.PluginManager.TryGetSourceAgnosticPluginAsync( discoveryResult, @@ -164,7 +175,7 @@ public async Task PluginManager_CreatePlugin_PrefersFrameworkSpecificEnvironment var discoveryResult = new PluginDiscoveryResult( new PluginFile( PluginFilePath, - new Lazy(() => PluginFileState.Valid))); + new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop)); Tuple result = await test.PluginManager.TryGetSourceAgnosticPluginAsync( discoveryResult, @@ -214,7 +225,7 @@ public async Task PluginManager_CreatePlugin_EmptyFrameworkSpecificEnvironmentVa var discoveryResult = new PluginDiscoveryResult( new PluginFile( PluginFilePath, - new Lazy(() => PluginFileState.Valid))); + new Lazy(() => PluginFileState.Valid), requiresDotnetHost: !IsDesktop)); Tuple result = await test.PluginManager.TryGetSourceAgnosticPluginAsync( discoveryResult, @@ -236,8 +247,19 @@ public async Task PluginManager_CreatePlugin_EmptyFrameworkSpecificEnvironmentVa private sealed class PluginManagerTest : IDisposable { + public bool IsDesktop + { + get + { +#if IS_DESKTOP + return true; +#else + return false; +#endif + } + } private readonly Mock _connection; - private readonly Mock _factory; + private readonly Mock _factory; private readonly Mock _plugin; private readonly Mock _pluginDiscoverer; private readonly Mock _reader; @@ -289,7 +311,7 @@ internal PluginManagerTest( _pluginDiscoverer.Setup(x => x.DiscoverAsync(It.IsAny())) .ReturnsAsync(new[] { - new PluginDiscoveryResult(new PluginFile(pluginFilePath, new Lazy(() => pluginFileState))) + new PluginDiscoveryResult(new PluginFile(pluginFilePath, new Lazy(() => pluginFileState), requiresDotnetHost : ! IsDesktop)) }); _connection = new Mock(MockBehavior.Strict); @@ -327,11 +349,11 @@ internal PluginManagerTest( _plugin.SetupGet(x => x.Id) .Returns("id"); - _factory = new Mock(MockBehavior.Strict); + _factory = new Mock(MockBehavior.Strict); _factory.Setup(x => x.Dispose()); _factory.Setup(x => x.GetOrCreateAsync( - It.Is(p => p == pluginFilePath), + It.Is(p => p.Path == pluginFilePath), It.IsNotNull>(), It.IsNotNull(), It.IsNotNull(), diff --git a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginResourceProviderTests.cs b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginResourceProviderTests.cs index 787f6b6db73..d4661dc82f4 100644 --- a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginResourceProviderTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Plugins/PluginResourceProviderTests.cs @@ -226,6 +226,17 @@ private static SourceRepository CreateSourceRepository( private sealed class PluginResourceProviderNegativeTest : IDisposable { + public static bool IsDesktop + { + get + { +#if IS_DESKTOP + return true; +#else + return false; +#endif + } + } private readonly Mock _pluginDiscoverer; private readonly PluginManager _pluginManager; private readonly Mock _environmentVariableReader; @@ -273,7 +284,7 @@ internal PluginResourceProviderNegativeTest(string serviceIndexJson, string sour _pluginManager = new PluginManager( _environmentVariableReader.Object, new Lazy(() => _pluginDiscoverer.Object), - (TimeSpan idleTimeout) => Mock.Of(), + (TimeSpan idleTimeout) => Mock.Of(), new Lazy(() => _testDirectory.Path)); Provider = new PluginResourceProvider(_pluginManager); } @@ -299,7 +310,7 @@ private static IEnumerable GetPluginDiscoveryResults(stri foreach (var path in pluginPaths.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) { var state = path == "a" ? PluginFileState.Valid : PluginFileState.InvalidEmbeddedSignature; - var file = new PluginFile(path, new Lazy(() => state)); + var file = new PluginFile(path, new Lazy(() => state), requiresDotnetHost: !IsDesktop); results.Add(new PluginDiscoveryResult(file)); } @@ -309,9 +320,20 @@ private static IEnumerable GetPluginDiscoveryResults(stri private sealed class PluginResourceProviderPositiveTest : IDisposable { + public static bool IsDesktop + { + get + { +#if IS_DESKTOP + return true; +#else + return false; +#endif + } + } private readonly Mock _connection; private readonly IEnumerable _expectations; - private readonly Mock _factory; + private readonly Mock _factory; private readonly Mock _plugin; private readonly Mock _pluginDiscoverer; private readonly Mock _reader; @@ -355,7 +377,7 @@ internal PluginResourceProviderPositiveTest( _pluginDiscoverer.Setup(x => x.DiscoverAsync(It.IsAny())) .ReturnsAsync(new[] { - new PluginDiscoveryResult(new PluginFile(pluginFilePath, new Lazy(() => pluginFileState))) + new PluginDiscoveryResult(new PluginFile(pluginFilePath, new Lazy(() => pluginFileState), requiresDotnetHost : ! IsDesktop)) }); _connection = new Mock(MockBehavior.Strict); @@ -407,11 +429,11 @@ internal PluginResourceProviderPositiveTest( _plugin.SetupGet(x => x.Id) .Returns("id"); - _factory = new Mock(MockBehavior.Strict); + _factory = new Mock(MockBehavior.Strict); _factory.Setup(x => x.Dispose()); _factory.Setup(x => x.GetOrCreateAsync( - It.Is(p => p == pluginFilePath), + It.Is(p => p.Path == pluginFilePath), It.IsNotNull>(), It.IsNotNull(), It.IsNotNull(),