diff --git a/docs/changelog.md b/docs/changelog.md index 76fe4ff8f2..1bba3466a0 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -42,6 +42,8 @@ What's changed since pre-release v3.0.0-B0453: - Bug fixes: - Fixed path handling issue string is missing the terminator with single quote in source paths by @juan-carlos-diaz. [#2885](https://github.com/microsoft/PSRule/issues/2885) + - Fixed module path not found with pre-release by @BernieWhite. + [#2889](https://github.com/microsoft/PSRule/issues/2889) ## v3.0.0-B0453 (pre-release) diff --git a/src/PSRule/Pipeline/SourcePipelineBuilder.cs b/src/PSRule/Pipeline/SourcePipelineBuilder.cs index 32279dfe1b..67bc8aa858 100644 --- a/src/PSRule/Pipeline/SourcePipelineBuilder.cs +++ b/src/PSRule/Pipeline/SourcePipelineBuilder.cs @@ -162,7 +162,7 @@ public void ModuleByName(string name, string? version = null) Source(new Source(info, files, dependency: false)); - // Import dependencies + // TODO: Import dependencies //for (var i = 0; module.RequiredModules != null && i < module.RequiredModules.Count; i++) // Module(module.RequiredModules[i], dependency: true); } @@ -182,15 +182,17 @@ private bool TryPackagedModuleFromCache(string name, string? version, out string if (_CachePath == null) return false; - Log($"[PSRule][S] -- Searching for module in: {_CachePath}"); - if (!string.IsNullOrEmpty(version)) + Log($"[PSRule][S] -- Searching for module in cache: {_CachePath}"); + if (!string.IsNullOrEmpty(version) && version != null) { - path = Environment.GetRootedBasePath(Path.Combine(_CachePath, "Modules", name, version)); + path = Environment.GetRootedBasePath(Path.Combine(_CachePath, "Modules", name, GetVersionPath(version))); + Log($"[PSRule][S] -- Search for module by version in: {path}"); if (File.Exists(Path.Combine(path, GetManifestName(name)))) return true; } path = Environment.GetRootedBasePath(Path.Combine(_CachePath, "Modules", name)); + Log($"[PSRule][S] -- Search for module as version-less in: {path}"); return File.Exists(Path.Combine(path, GetManifestName(name))); } @@ -211,9 +213,9 @@ private bool TryInstalledModule(string name, string? version, out string? path) var searchPath = Environment.GetRootedBasePath(Path.Combine(searchPaths[i], name)); // Try a specific version. - if (!string.IsNullOrEmpty(version)) + if (!string.IsNullOrEmpty(version) && version != null) { - var versionPath = Path.Combine(searchPath, version); + var versionPath = Path.Combine(searchPath, GetVersionPath(version)); var manifestPath = Path.Combine(versionPath, GetManifestName(name)); if (File.Exists(manifestPath)) { @@ -247,6 +249,20 @@ private bool TryInstalledModule(string name, string? version, out string? path) return sorted.Length > 0; } + /// + /// Get the path for version lookups which ignore pre-release version and build segments. + /// If version is pre-release, use major.minor.patch. + /// + /// Return a version string just containing major.minor.patch. + private static string GetVersionPath(string version) + { + if (version.Contains('-') || version.Contains('+')) + { + version = version.Split('-', '+')[0]; + } + return version; + } + private static string[] SortModulePath(IEnumerable values) { var results = values.ToArray(); diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index 84fccd5eed..d277066453 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -183,12 +183,31 @@ PreserveNewest - + - + + + + + + + + + + + + + + diff --git a/tests/PSRule.Tests/SourcePipelineBuilderTests.cs b/tests/PSRule.Tests/SourcePipelineBuilderTests.cs index 5e7214da0b..ad1a8f8c05 100644 --- a/tests/PSRule.Tests/SourcePipelineBuilderTests.cs +++ b/tests/PSRule.Tests/SourcePipelineBuilderTests.cs @@ -11,101 +11,127 @@ namespace PSRule; /// public sealed class SourcePipelineBuilderTests : BaseTests { - [Fact] - public void Add_single_file() + [Theory] + [InlineData("FromFile.Rule.ps1")] + [InlineData("John's Documents/FromFileBaseline.Rule.ps1")] + public void Directory_WithSingleFile_ShouldFindCount(string path) { var builder = new SourcePipelineBuilder(null, null); - builder.Directory(GetSourcePath("FromFile.Rule.ps1")); + builder.Directory(GetSourcePath(path)); var sources = builder.Build(); Assert.Single(sources); Assert.Single(sources[0].File); + Assert.Equal(GetSourcePath(path), sources[0].File[0].Path); } - [Fact] - public void Add_directory() + [Theory] + [InlineData("FromFile.Rule.yaml", 1)] + public void Directory_WithSingleFileAndDisablePowerShell_ShouldFindCount(string path, int count) { - var builder = new SourcePipelineBuilder(null, null); - builder.Directory(GetSourcePath("")); + var option = GetOption(); + option.Execution.RestrictScriptSource = RestrictScriptSource.DisablePowerShell; + var builder = new SourcePipelineBuilder(null, option); + builder.Directory(GetSourcePath(path)); var sources = builder.Build(); Assert.Single(sources); - Assert.Equal(26, sources[0].File.Length); + Assert.Equal(count, sources[0].File.Length); } - [Fact] - public void Add_script_file_with_disable_powershell() + [Theory] + [InlineData("FromFile.Rule.ps1")] + public void Directory_WithScriptFileAndDisablePowerShell_ShouldNotFindAny(string path) { var option = GetOption(); option.Execution.RestrictScriptSource = RestrictScriptSource.DisablePowerShell; var builder = new SourcePipelineBuilder(null, option); - builder.Directory(GetSourcePath("FromFile.Rule.ps1")); + builder.Directory(GetSourcePath(path)); var sources = builder.Build(); Assert.Empty(sources); } - [Fact] - public void Add_script_file_with_module_only() + [Theory] + [InlineData("FromFile.Rule.yaml", 1)] + public void Directory_WithSingleFileAndModuleOnly_ShouldFindCount(string path, int count) { var option = GetOption(); option.Execution.RestrictScriptSource = RestrictScriptSource.ModuleOnly; var builder = new SourcePipelineBuilder(null, option); - builder.Directory(GetSourcePath("FromFile.Rule.ps1")); + builder.Directory(GetSourcePath(path)); var sources = builder.Build(); - Assert.Empty(sources); + Assert.Single(sources); + Assert.Equal(count, sources[0].File.Length); } - [Fact] - public void Add_yaml_file_with_disable_powershell() + [Theory] + [InlineData("FromFile.Rule.ps1")] + public void Directory_WithScriptFileAndModuleOnly_ShouldNotFindAny(string path) { var option = GetOption(); - option.Execution.RestrictScriptSource = RestrictScriptSource.DisablePowerShell; + option.Execution.RestrictScriptSource = RestrictScriptSource.ModuleOnly; var builder = new SourcePipelineBuilder(null, option); - builder.Directory(GetSourcePath("FromFile.Rule.yaml")); + builder.Directory(GetSourcePath(path)); var sources = builder.Build(); - Assert.Single(sources); - Assert.Single(sources[0].File); + Assert.Empty(sources); } - [Fact] - public void Add_yaml_file_with_module_only() + [Theory] + [InlineData("", 28)] + [InlineData("John's Documents", 1)] + public void Directory_WithDirectory_ShouldFindCount(string path, int count) { - var option = GetOption(); - option.Execution.RestrictScriptSource = RestrictScriptSource.ModuleOnly; - var builder = new SourcePipelineBuilder(null, option); - builder.Directory(GetSourcePath("FromFile.Rule.yaml")); + var builder = new SourcePipelineBuilder(null, null); + builder.Directory(GetSourcePath(path)); var sources = builder.Build(); Assert.Single(sources); - Assert.Single(sources[0].File); + Assert.Equal(count, sources[0].File.Length); } - [Fact] - public void Add_directory_with_disable_powershell() + [Theory] + [InlineData("", 21)] + public void Directory_WithDirectoryAndDisablePowerShell_ShouldFindCount(string path, int count) { var option = GetOption(); option.Execution.RestrictScriptSource = RestrictScriptSource.DisablePowerShell; var builder = new SourcePipelineBuilder(null, option); - builder.Directory(GetSourcePath("")); + builder.Directory(GetSourcePath(path)); var sources = builder.Build(); Assert.Single(sources); - Assert.Equal(20, sources[0].File.Length); + Assert.Equal(count, sources[0].File.Length); } - [Fact] - public void Add_directory_with_module_only() + [Theory] + [InlineData("", 21)] + public void Directory_WithDirectoryAndModuleOnly_ShouldFindCount(string path, int count) { var option = GetOption(); option.Execution.RestrictScriptSource = RestrictScriptSource.ModuleOnly; var builder = new SourcePipelineBuilder(null, option); - builder.Directory(GetSourcePath("")); + builder.Directory(GetSourcePath(path)); + var sources = builder.Build(); + + Assert.Single(sources); + Assert.Equal(count, sources[0].File.Length); + } + + [Theory] + [InlineData("TestModule7", "0.0.1")] + [InlineData("TestModule8", "0.0.1-Alpha")] + public void ModuleByName_WithNameAndVersion_ShouldFindModuleFiles(string name, string version) + { + var builder = new SourcePipelineBuilder(null, null, cachePath: GetSourcePath("")); + builder.ModuleByName(name: name, version: version); var sources = builder.Build(); Assert.Single(sources); - Assert.Equal(20, sources[0].File.Length); + Assert.Equal(name, sources[0].Module.Name); + Assert.Equal(version, sources[0].Module.FullVersion); + Assert.NotEmpty(sources[0].File); } } diff --git a/tests/PSRule.Tests/TestModule8/TestModule8.psd1 b/tests/PSRule.Tests/TestModule8/TestModule8.psd1 index 45bcf7699e..35cfd3993b 100644 --- a/tests/PSRule.Tests/TestModule8/TestModule8.psd1 +++ b/tests/PSRule.Tests/TestModule8/TestModule8.psd1 @@ -20,13 +20,13 @@ ModuleVersion = '0.0.1' GUID = 'ff869216-0ca8-42e8-930f-5728719d4f5d' # Author of this module -Author = 'Armaan Mcleod' +Author = 'Bernie White' # Company or vendor of this module -CompanyName = 'Armaan Mcleod' +CompanyName = 'Bernie White' # Copyright statement for this module -Copyright = '(c) Armaan Mcleod. All rights reserved.' +Copyright = '(c) Bernie White. All rights reserved.' # Description of the functionality provided by this module Description = 'A module for Pester testing for PSRule.' @@ -109,7 +109,7 @@ PrivateData = @{ # ReleaseNotes = '' # Prerelease string of this module - # Prerelease = '' + Prerelease = 'Alpha' # Flag to indicate whether the module requires explicit user acceptance for install/update/save # RequireLicenseAcceptance = $false