commit 7eca519d03d0dceb14cd30c3a430071153c07913 parent f126e676656a0fee443c45758635f12df975616b Author: Ruben Guerrero <rubengu@microsoft.com> Date: Mon, 8 May 2023 15:46:39 -0700 Allow prerelease modules for configuration (#3217) This PR allows a configuration file to have a boolean allowPrerelease directive to use prerelease DSC resources. ``` properties: configurationVersion: 0.2 resources: - resource: FakeModule/FakeResource directives: version: '1.0.0-preview' allowPrerelease: true settings: Args: 'args' ``` Internally, allow prerelease is used only for a module that it needs to get install. When is enabled, the processor will use `-AllowPrerelease` to the Find-DscResource command. It also fixes and infinite loop in `SplitIntoLines`. If the found offset is the one of "\r\n", it uses the same offset for `pos` in `find_first_of`, so it keeps returning the offset making `nextOffset - currentOffset = 0` Diffstat:
13 files changed, 492 insertions(+), 43 deletions(-)
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt @@ -212,6 +212,7 @@ malware mapview maxvalue maybenull +Maxed MBH mdmp MDs diff --git a/src/AppInstallerCLITests/Strings.cpp b/src/AppInstallerCLITests/Strings.cpp @@ -248,3 +248,15 @@ TEST_CASE("Format", "[strings]") // Note: C++20 std::format will throw an exception for this test case REQUIRE("First {1}" == Format("{0} {1}", "First")); } + +TEST_CASE("SplitIntoLines", "[string]") +{ + REQUIRE(SplitIntoLines("Boring test") == std::vector<std::string>{ "Boring test" }); + REQUIRE(SplitIntoLines( + "I'm Luffy! The Man Who Will Become the Pirate King!\r-Monkey D. Luffy") == std::vector<std::string>{ "I'm Luffy! The Man Who Will Become the Pirate King!", "-Monkey D. Luffy" }); + REQUIRE(SplitIntoLines( + "I want live!\n-Nico Robin") == std::vector<std::string>{ "I want live!", "-Nico Robin" }); + REQUIRE(SplitIntoLines( + "You want my treasure?\rYou can have it!\nI left everything I gathered in one place!\r\nYou just have to find it!") + == std::vector<std::string>{ "You want my treasure?", "You can have it!", "I left everything I gathered in one place!", "You just have to find it!" }); +} diff --git a/src/AppInstallerSharedLib/AppInstallerStrings.cpp b/src/AppInstallerSharedLib/AppInstallerStrings.cpp @@ -721,7 +721,7 @@ namespace AppInstaller::Utility result.emplace_back(input.substr(currentOffset, nextOffset - currentOffset)); } - currentOffset = nextOffset; + currentOffset = nextOffset + 1; } return result; @@ -746,7 +746,7 @@ namespace AppInstaller::Utility { size_t actualWidth = 0; std::string trimmedLine = UTF8TrimRightToColumnWidth(lines[currentLine], (availableLines * lineWidth) - 1, actualWidth); - trimmedLine += "\xE2\x80\xA6"; // UTF8 encoding of ellipsis (…) character + trimmedLine += "\xE2\x80\xA6"; // UTF8 encoding of ellipsis (�) character lines[currentLine] = trimmedLine; currentLineActualLineCount = availableLines; diff --git a/src/Microsoft.Management.Configuration.Processor/Constants/DirectiveConstants.cs b/src/Microsoft.Management.Configuration.Processor/Constants/DirectiveConstants.cs @@ -18,6 +18,7 @@ namespace Microsoft.Management.Configuration.Processor.Constants public const string ModuleGuid = "moduleGuid"; public const string Repository = "repository"; public const string Version = "version"; + public const string AllowPrerelease = "allowPrerelease"; #pragma warning restore SA1600 // ElementsMustBeDocumented } } diff --git a/src/Microsoft.Management.Configuration.Processor/Constants/PowerShellConstants.cs b/src/Microsoft.Management.Configuration.Processor/Constants/PowerShellConstants.cs @@ -47,6 +47,7 @@ namespace Microsoft.Management.Configuration.Processor.Constants internal static class Parameters { + public const string AllowPrerelease = "AllowPrerelease"; public const string Force = "Force"; public const string FullyQualifiedName = "FullyQualifiedName"; public const string Guid = "GUID"; diff --git a/src/Microsoft.Management.Configuration.Processor/Helpers/ConfigurationUnitAndResource.cs b/src/Microsoft.Management.Configuration.Processor/Helpers/ConfigurationUnitAndResource.cs @@ -81,7 +81,19 @@ namespace Microsoft.Management.Configuration.Processor.Helpers /// </summary> /// <param name="directiveName">Name of directive.</param> /// <returns>The value of the directive. Null if doesn't exist.</returns> - public string? GetDirective(string directiveName) + /// <typeparam name="TType">Directive type value.</typeparam> + public TType? GetDirective<TType>(string directiveName) + where TType : class + { + return this.UnitInternal.GetDirective<TType>(directiveName); + } + + /// <summary> + /// Gets a directive bool value. + /// </summary> + /// <param name="directiveName">Name of directive.</param> + /// <returns>The value of the directive. False if not set.</returns> + public bool? GetDirective(string directiveName) { return this.UnitInternal.GetDirective(directiveName); } diff --git a/src/Microsoft.Management.Configuration.Processor/Helpers/ConfigurationUnitInternal.cs b/src/Microsoft.Management.Configuration.Processor/Helpers/ConfigurationUnitInternal.cs @@ -31,7 +31,7 @@ namespace Microsoft.Management.Configuration.Processor.Helpers this.DirectivesOverlay = directivesOverlay; this.InitializeDirectives(); - string? moduleName = this.GetDirective(DirectiveConstants.Module); + string? moduleName = this.GetDirective<string>(DirectiveConstants.Module); if (string.IsNullOrEmpty(moduleName)) { this.Module = null; @@ -40,10 +40,10 @@ namespace Microsoft.Management.Configuration.Processor.Helpers { this.Module = PowerShellHelpers.CreateModuleSpecification( moduleName, - this.GetDirective(DirectiveConstants.Version), - this.GetDirective(DirectiveConstants.MinVersion), - this.GetDirective(DirectiveConstants.MaxVersion), - this.GetDirective(DirectiveConstants.ModuleGuid)); + this.GetDirective<string>(DirectiveConstants.Version), + this.GetDirective<string>(DirectiveConstants.MinVersion), + this.GetDirective<string>(DirectiveConstants.MaxVersion), + this.GetDirective<string>(DirectiveConstants.ModuleGuid)); } } @@ -72,16 +72,79 @@ namespace Microsoft.Management.Configuration.Processor.Helpers } /// <summary> - /// Get a directive from the unit taking into account the directives overlay. + /// Gets the directive value from the unit taking into account the directives overlay. /// </summary> /// <param name="directiveName">Directive name.</param> /// <returns>Value of directive, null if not found.</returns> - public string? GetDirective(string directiveName) + /// <typeparam name="TType">Directive type value.</typeparam> + public TType? GetDirective<TType>(string directiveName) + where TType : class { var normalizedDirectiveName = StringHelpers.Normalize(directiveName); if (this.normalizedDirectives.TryGetValue(normalizedDirectiveName, out object? value)) { - return value as string; + return value as TType; + } + + return null; + } + + /// <summary> + /// Gets the bool value of a directive from the unit taking into account the directives overlay. + /// </summary> + /// <param name="directiveName">Directive name.</param> + /// <returns>Value of directive, false if not found.</returns> + public bool? GetDirective(string directiveName) + { + var normalizedDirectiveName = StringHelpers.Normalize(directiveName); + if (this.normalizedDirectives.TryGetValue(normalizedDirectiveName, out object? value)) + { + return value as bool?; + } + + return null; + } + + /// <summary> + /// Gets the semantic version, if any. + /// </summary> + /// <returns>SemanticVersion, null if not specified.</returns> + public SemanticVersion? GetSemanticVersion() + { + string? semanticVersion = this.GetDirective<string>(DirectiveConstants.Version); + if (!string.IsNullOrWhiteSpace(semanticVersion)) + { + return new SemanticVersion(semanticVersion); + } + + return null; + } + + /// <summary> + /// Gets the semantic min version, if any. + /// </summary> + /// <returns>SemanticVersion, null if not specified.</returns> + public SemanticVersion? GetSemanticMinVersion() + { + string? semanticVersion = this.GetDirective<string>(DirectiveConstants.MinVersion); + if (!string.IsNullOrWhiteSpace(semanticVersion)) + { + return new SemanticVersion(semanticVersion); + } + + return null; + } + + /// <summary> + /// Gets the semantic max version, if any. + /// </summary> + /// <returns>SemanticVersion, null if not specified.</returns> + public SemanticVersion? GetSemanticMaxVersion() + { + string? semanticVersion = this.GetDirective<string>(DirectiveConstants.MaxVersion); + if (!string.IsNullOrWhiteSpace(semanticVersion)) + { + return new SemanticVersion(semanticVersion); } return null; diff --git a/src/Microsoft.Management.Configuration.Processor/Helpers/PowerShellHelpers.cs b/src/Microsoft.Management.Configuration.Processor/Helpers/PowerShellHelpers.cs @@ -15,8 +15,6 @@ namespace Microsoft.Management.Configuration.Processor.Helpers /// </summary> internal static class PowerShellHelpers { - private const string MaxRange = "999999999"; - /// <summary> /// Creates a module specification object. /// </summary> @@ -42,22 +40,26 @@ namespace Microsoft.Management.Configuration.Processor.Helpers } var moduleInfo = new Hashtable - { - { Parameters.ModuleName, moduleName }, - }; + { + { Parameters.ModuleName, moduleName }, + }; if (!string.IsNullOrEmpty(version)) { - moduleInfo.Add(Parameters.RequiredVersion, version); + var semanticVersion = new SemanticVersion(version); + moduleInfo.Add(Parameters.RequiredVersion, semanticVersion.Version); } if (!string.IsNullOrEmpty(minVersion)) { - moduleInfo.Add(Parameters.ModuleVersion, minVersion); + var semanticVersion = new SemanticVersion(minVersion); + moduleInfo.Add(Parameters.ModuleVersion, semanticVersion.Version); } if (!string.IsNullOrEmpty(maxVersion)) { + var semanticVersion = new SemanticVersion(maxVersion); + // For some reason, the constructor of ModuleSpecification that takes // a hashtable calls ModuleCmdletBase.GetMaximumVersion. This method will // validate the max version and replace * for 999999999 only if its the last @@ -65,7 +67,7 @@ namespace Microsoft.Management.Configuration.Processor.Helpers // ModuleSpecification's MaximumVersion property. If we want to set a // MaximumVersion with a wildcard and pass this to Install-Module it will // fail with "Cannot convert value 'x.*' to type 'System.Version'." - moduleInfo.Add(Parameters.MaximumVersion, maxVersion.Replace("*", MaxRange)); + moduleInfo.Add(Parameters.MaximumVersion, semanticVersion.Version.ToString()); } if (!string.IsNullOrEmpty(guid)) diff --git a/src/Microsoft.Management.Configuration.Processor/Helpers/SemanticVersion.cs b/src/Microsoft.Management.Configuration.Processor/Helpers/SemanticVersion.cs @@ -0,0 +1,73 @@ +// ----------------------------------------------------------------------------- +// <copyright file="SemanticVersion.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Helpers +{ + using System; + + /// <summary> + /// A semantic version. + /// </summary> + internal class SemanticVersion + { + private const string MaxRange = "999999999"; + + private readonly string semanticVersion; + + /// <summary> + /// Initializes a new instance of the <see cref="SemanticVersion"/> class. + /// </summary> + /// <param name="version">Version.</param> + public SemanticVersion(string version) + { + this.semanticVersion = GetMaximumVersion(version); + + // Prerelease versions append the prerelease tag after a - + // PowerShell doesn't handle semantic versions. + if (this.semanticVersion.Contains("-")) + { + var indexOf = this.semanticVersion.IndexOf("-"); + this.Version = new Version(this.semanticVersion[..indexOf]); + this.PrereleaseTag = this.semanticVersion[(indexOf + 1) ..]; + } + else + { + this.Version = new Version(this.semanticVersion); + } + } + + /// <summary> + /// Gets the version without a prerelease tag. + /// </summary> + public Version Version { get; } + + /// <summary> + /// Gets prerelease tag if any. + /// </summary> + public string? PrereleaseTag { get; } = null; + + /// <summary> + /// Gets a value indicating whether if the semantic version is prerelease. + /// </summary> + public bool IsPrerelease => !string.IsNullOrEmpty(this.PrereleaseTag); + + /// <inheritdoc/> + public override string ToString() + { + return this.semanticVersion; + } + + /// <summary> + /// Max out a version by replacing * if needed. + /// </summary> + /// <param name="version">Version.</param> + /// <returns>Maxed version.</returns> + private static string GetMaximumVersion(string version) + { + return version.Replace("*", MaxRange); + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/ProcessorEnvironments/HostedEnvironment.cs b/src/Microsoft.Management.Configuration.Processor/ProcessorEnvironments/HostedEnvironment.cs @@ -268,41 +268,61 @@ namespace Microsoft.Management.Configuration.Processor.Runspaces { Parameters.Name, unitInternal.Unit.UnitName }, }; - if (unitInternal.Module is not null) + // Don't use ModuleSpecification here. Each parameter is independent and + // we need version even if a module was not specified. + string? moduleName = unitInternal.GetDirective<string>(DirectiveConstants.Module); + var semanticVersion = unitInternal.GetSemanticVersion(); + var semanticMinVersion = unitInternal.GetSemanticMinVersion(); + var semanticMaxVersion = unitInternal.GetSemanticMaxVersion(); + string? repository = unitInternal.GetDirective<string>(DirectiveConstants.Repository); + + bool? allowPrerelease = unitInternal.GetDirective(DirectiveConstants.AllowPrerelease); + bool implicitAllowPrerelease = false; + + if (!string.IsNullOrEmpty(moduleName)) { - parameters.Add(Parameters.ModuleName, unitInternal.Module.Name); + parameters.Add(Parameters.ModuleName, moduleName); + } - if (unitInternal.Module.Version is not null) - { - parameters.Add(Parameters.MinimumVersion, unitInternal.Module.Version); - } + if (semanticVersion != null) + { + implicitAllowPrerelease |= semanticVersion.IsPrerelease; + parameters.Add(Parameters.RequiredVersion, semanticVersion.ToString()); + } - if (unitInternal.Module.MaximumVersion is not null) - { - parameters.Add(Parameters.MaximumVersion, unitInternal.Module.MaximumVersion); - } + if (semanticMinVersion != null) + { + implicitAllowPrerelease |= semanticMinVersion.IsPrerelease; + parameters.Add(Parameters.MinimumVersion, semanticMinVersion.ToString()); + } - if (unitInternal.Module.RequiredVersion is not null) - { - parameters.Add(Parameters.RequiredVersion, unitInternal.Module.RequiredVersion); - } + if (semanticMaxVersion != null) + { + implicitAllowPrerelease |= semanticMaxVersion.IsPrerelease; + parameters.Add(Parameters.MaximumVersion, semanticMaxVersion.ToString()); } - string? repository = unitInternal.GetDirective(DirectiveConstants.Repository); if (!string.IsNullOrEmpty(repository)) { parameters.Add(Parameters.Repository, repository); } + if (allowPrerelease.HasValue || implicitAllowPrerelease) + { + // If explicit allowPrerelease = false don't use implicit. + bool allow = allowPrerelease.HasValue ? allowPrerelease.Value : implicitAllowPrerelease; + parameters.Add(Parameters.AllowPrerelease, allow); + } + using PowerShell pwsh = PowerShell.Create(this.Runspace); - // TODO: Implement prerelease directive. + pwsh.AddCommand(Commands.FindDscResource) + .AddParameters(parameters); + // The result is just a PSCustomObject with a type name of Microsoft.PowerShell.Commands.PSGetDscResourceInfo. // When no module is passed and a resource is not found, this will return an empty list. If a module // is specified and no resource is found then it will fail earlier because of a Write-Error. - var result = pwsh.AddCommand(Commands.FindDscResource) - .AddParameters(parameters) - .Invoke() + var result = pwsh.Invoke() .FirstOrDefault(); this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationUnitInternalTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationUnitInternalTests.cs @@ -54,10 +54,18 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests string anotherDirective = "another"; string overlayAnother = "insert another text"; + string boolDirective = "boolDirective"; + bool boolDirectiveValue = true; + + string boolDirective2 = "boolDirective2"; + bool boolDirective2Value = false; + var unit = new ConfigurationUnit(); unit.Directives.Add(moduleDirective, unitModule); unit.Directives.Add(versionDirective, unitVersion); unit.Directives.Add(descriptionDirective, unitDescription); + unit.Directives.Add(boolDirective, boolDirectiveValue); + unit.Directives.Add(boolDirective2, boolDirective2Value); var overlays = new Dictionary<string, object>() { @@ -67,21 +75,25 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var unitInternal = new ConfigurationUnitInternal(unit, overlays); - var description = unitInternal.GetDirective(descriptionDirective); + var description = unitInternal.GetDirective<string>(descriptionDirective); Assert.Equal(description, overlayDescription); - var another = unitInternal.GetDirective(anotherDirective); + var another = unitInternal.GetDirective<string>(anotherDirective); Assert.Equal(another, overlayAnother); - var fake = unitInternal.GetDirective("fake"); + var fake = unitInternal.GetDirective<string>("fake"); Assert.Null(fake); - var description2 = unitInternal.GetDirective("DESCRIPTION"); + var description2 = unitInternal.GetDirective<string>("DESCRIPTION"); Assert.Equal(description2, overlayDescription); Assert.Equal(unitModule, unitInternal.Module!.Name); Assert.Equal(Version.Parse(unitVersion), unitInternal.Module!.RequiredVersion); + + Assert.Equal(boolDirectiveValue, unitInternal.GetDirective(boolDirective)); + Assert.Equal(boolDirective2Value, unitInternal.GetDirective(boolDirective2)); + Assert.Null(unitInternal.GetDirective("fakeBool")); } /// <summary> @@ -94,7 +106,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests unit.Directives.Add("module", "module"); unit.Directives.Add("version", "not a version"); - Assert.Throws<PSInvalidCastException>( + Assert.Throws<ArgumentException>( () => new ConfigurationUnitInternal(unit, null)); } } diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/PowerShellHelperTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/PowerShellHelperTests.cs @@ -0,0 +1,176 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PowerShellHelperTests.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Xunit; + using Xunit.Abstractions; + + /// <summary> + /// PowerShell helper tests. + /// </summary> + [Collection("UnitTestCollection")] + public class PowerShellHelperTests + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// <summary> + /// Initializes a new instance of the <see cref="PowerShellHelperTests"/> class. + /// </summary> + /// <param name="fixture">Unit test fixture.</param> + /// <param name="log">Log helper.</param> + public PowerShellHelperTests(UnitTestFixture fixture, ITestOutputHelper log) + { + this.fixture = fixture; + this.log = log; + } + + /// <summary> + /// Tests CreateModuleSpecification. + /// </summary> + [Fact] + public void CreateModuleSpecification_Module_Test() + { + string moduleName = "MyModule"; + + var moduleSpecification = PowerShellHelpers.CreateModuleSpecification(moduleName); + Assert.Equal(moduleName, moduleSpecification.Name); + Assert.Null(moduleSpecification.RequiredVersion); + Assert.Null(moduleSpecification.Version); + Assert.Null(moduleSpecification.MaximumVersion); + Assert.Null(moduleSpecification.Guid); + } + + /// <summary> + /// Tests CreateModuleSpecification with arguments. use version. + /// </summary> + [Fact] + public void CreateModuleSpecification_Required_Test() + { + string moduleName = "MyModule"; + string version = "0.1.0"; + string guid = Guid.NewGuid().ToString(); + + var moduleSpecification = PowerShellHelpers.CreateModuleSpecification( + moduleName, + version, + null, + null, + guid); + Assert.Equal(moduleName, moduleSpecification.Name); + Assert.NotNull(moduleSpecification.RequiredVersion); + Assert.Equal(version, moduleSpecification.RequiredVersion.ToString()); + Assert.Null(moduleSpecification.Version); + Assert.Null(moduleSpecification.MaximumVersion); + Assert.NotNull(moduleSpecification.Guid); + Assert.Equal(guid, moduleSpecification.Guid.ToString()); + } + + /// <summary> + /// Tests CreateModuleSpecification with arguments. Use min version. + /// </summary> + [Fact] + public void CreateModuleSpecification_MinMax_Test() + { + string moduleName = "MyModule"; + string minVersion = "0.0.1"; + string maxVersion = "1.0.0"; + string guid = Guid.NewGuid().ToString(); + + var moduleSpecification = PowerShellHelpers.CreateModuleSpecification( + moduleName, + null, + minVersion, + maxVersion, + guid); + Assert.Equal(moduleName, moduleSpecification.Name); + Assert.Null(moduleSpecification.RequiredVersion); + Assert.NotNull(moduleSpecification.Version); + Assert.Equal(minVersion, moduleSpecification.Version.ToString()); + Assert.NotNull(moduleSpecification.MaximumVersion); + Assert.Equal(maxVersion, moduleSpecification.MaximumVersion.ToString()); + Assert.NotNull(moduleSpecification.Guid); + Assert.Equal(guid, moduleSpecification.Guid.ToString()); + } + + /// <summary> + /// Tests CreateModuleSpecification with prerelease versions. + /// </summary> + [Fact] + public void CreateModuleSpecification_PrereleaseVersions_Required_Test() + { + string moduleName = "MyModule"; + string preVersion = "0.1.0-pre"; + string version = "0.1.0"; + + var moduleSpecification = PowerShellHelpers.CreateModuleSpecification( + moduleName, + preVersion); + Assert.Equal(moduleName, moduleSpecification.Name); + Assert.NotNull(moduleSpecification.RequiredVersion); + Assert.Equal(version, moduleSpecification.RequiredVersion.ToString()); + Assert.Null(moduleSpecification.Version); + Assert.Null(moduleSpecification.MaximumVersion); + Assert.Null(moduleSpecification.Guid); + } + + /// <summary> + /// Tests CreateModuleSpecification with prerelease versions. + /// </summary> + [Fact] + public void CreateModuleSpecification_PrereleaseVersions_Test() + { + string moduleName = "MyModule"; + string preMinVersion = "0.0.1-pre"; + string minVersion = "0.0.1"; + string preMaxVersion = "1.0.0-pre"; + string maxVersion = "1.0.0"; + + var moduleSpecification = PowerShellHelpers.CreateModuleSpecification( + moduleName, + null, + preMinVersion, + preMaxVersion); + Assert.Equal(moduleName, moduleSpecification.Name); + Assert.Null(moduleSpecification.RequiredVersion); + Assert.NotNull(moduleSpecification.Version); + Assert.Equal(minVersion, moduleSpecification.Version.ToString()); + Assert.NotNull(moduleSpecification.MaximumVersion); + Assert.Equal(maxVersion, moduleSpecification.MaximumVersion.ToString()); + Assert.Null(moduleSpecification.Guid); + } + + /// <summary> + /// Tests CreateModuleSpecification with arguments. Don't use minVersion and version. + /// </summary> + [Fact] + public void CreateModuleSpecification_Args_Throws_Test() + { + string moduleName = "MyModule"; + string version = "0.1.0"; + string minVersion = "0.0.1"; + string maxVersion = "1.0.0"; + + Assert.Throws<ArgumentException>(() => PowerShellHelpers.CreateModuleSpecification( + moduleName, + version, + minVersion, + null, + null)); + + Assert.Throws<ArgumentException>(() => PowerShellHelpers.CreateModuleSpecification( + moduleName, + version, + null, + maxVersion, + null)); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/SemanticVersionTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/SemanticVersionTests.cs @@ -0,0 +1,76 @@ +// ----------------------------------------------------------------------------- +// <copyright file="SemanticVersionTests.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Xunit; + using Xunit.Abstractions; + + /// <summary> + /// Semantic version tests. + /// </summary> + [Collection("UnitTestCollection")] + public class SemanticVersionTests + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// <summary> + /// Initializes a new instance of the <see cref="SemanticVersionTests"/> class. + /// </summary> + /// <param name="fixture">Unit test fixture.</param> + /// <param name="log">Log helper.</param> + public SemanticVersionTests(UnitTestFixture fixture, ITestOutputHelper log) + { + this.fixture = fixture; + this.log = log; + } + + /// <summary> + /// Test version. + /// </summary> + [Fact] + public void SemanticVersion_Test() + { + var semanticVersion = new SemanticVersion("1.0.1"); + Assert.Equal("1.0.1", semanticVersion.ToString()); + Assert.Equal(new Version("1.0.1"), semanticVersion.Version); + Assert.False(semanticVersion.IsPrerelease); + Assert.Null(semanticVersion.PrereleaseTag); + } + + /// <summary> + /// Tests prerelease version. + /// </summary> + [Fact] + public void PrereleaseVersion_Test() + { + var semanticVersion = new SemanticVersion("1.0.1-pre"); + Assert.Equal("1.0.1-pre", semanticVersion.ToString()); + Assert.Equal(new Version("1.0.1"), semanticVersion.Version); + Assert.True(semanticVersion.IsPrerelease); + Assert.Equal("pre", semanticVersion.PrereleaseTag); + } + + /// <summary> + /// Tests GetMaximumVersion. + /// </summary> + /// <param name="input">Input.</param> + /// <param name="expected">Expected.</param> + [Theory] + [InlineData("1.0.1", "1.0.1")] + [InlineData("1.0.*", "1.0.999999999")] + [InlineData("*.*.1", "999999999.999999999.1")] + public void MaximumVersion_Test(string input, string expected) + { + var semanticVersion = new SemanticVersion(input); + Assert.Equal(expected, semanticVersion.ToString()); + } + } +}