From fb3cb4d0a40dc3f8d50b96f8037a09c47bb2472d Mon Sep 17 00:00:00 2001 From: Adam Ralph Date: Tue, 1 Feb 2022 20:21:16 +0100 Subject: [PATCH 1/2] add option masking tests --- MinVerTests.Infra/MinVerCli.cs | 4 +- MinVerTests.Packages/OptionMasking.cs | 148 ++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 MinVerTests.Packages/OptionMasking.cs diff --git a/MinVerTests.Infra/MinVerCli.cs b/MinVerTests.Infra/MinVerCli.cs index d5010ff3..dcf87b7d 100644 --- a/MinVerTests.Infra/MinVerCli.cs +++ b/MinVerTests.Infra/MinVerCli.cs @@ -7,12 +7,12 @@ namespace MinVerTests.Infra { public static class MinVerCli { - public static async Task ReadAsync(string workingDirectory, string configuration = Configuration.Current, params (string, string)[] envVars) + public static async Task ReadAsync(string workingDirectory, string configuration = Configuration.Current, string args = "", params (string, string)[] envVars) { var environmentVariables = envVars.ToDictionary(envVar => envVar.Item1, envVar => envVar.Item2, StringComparer.OrdinalIgnoreCase); _ = environmentVariables.TryAdd("MinVerVerbosity".ToAltCase(), "trace"); - return await CommandEx.ReadLoggedAsync("dotnet", $"exec {GetPath(configuration)}", workingDirectory, environmentVariables).ConfigureAwait(false); + return await CommandEx.ReadLoggedAsync("dotnet", $"exec {GetPath(configuration)} {args}", workingDirectory, environmentVariables).ConfigureAwait(false); } public static string GetPath(string configuration) => diff --git a/MinVerTests.Packages/OptionMasking.cs b/MinVerTests.Packages/OptionMasking.cs new file mode 100644 index 00000000..d6b36aee --- /dev/null +++ b/MinVerTests.Packages/OptionMasking.cs @@ -0,0 +1,148 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; +using MinVerTests.Infra; +using Xunit; + +namespace MinVerTests.Packages +{ + public static class OptionMasking + { + [Theory] + [InlineData("patch")] + public static async Task AutoIncrementBackToDefault(string value) + { + // arrange + var path = MethodBase.GetCurrentMethod().GetTestDirectory(); + FileSystem.EnsureEmptyDirectory(path); + + await Git.Init(path); + await Git.Commit(path); + await Git.Tag(path, "2.3.4"); + await Git.Commit(path); + + var envVars = ("MinVerAutoIncrement".ToAltCase(), "minor"); + var args = $"--auto-increment {value}"; + + var expected = Package.WithVersion(2, 3, 5, new[] { "alpha", "0" }, 1); + + // act + var cli = await MinVerCli.ReadAsync(path, args: args, envVars: envVars); + + // assert + Assert.Equal(expected.Version, cli.StandardOutput.Trim()); + } + + [Theory] + [InlineData("\"\"")] + + public static async Task BuildMetadataBackToDefault(string value) + { + // arrange + var path = MethodBase.GetCurrentMethod().GetTestDirectory(); + FileSystem.EnsureEmptyDirectory(path); + + var envVars = ("MinVerBuildMetadata", "build.123"); + var args = $"--build-metadata {value}"; + + var expected = Package.WithVersion(0, 0, 0, new[] { "alpha", "0" }, 0); + + // act + var cli = await MinVerCli.ReadAsync(path, args: args, envVars: envVars); + + // assert + Assert.Equal(expected.Version, cli.StandardOutput.Trim()); + } + + [Theory] + [InlineData("alpha")] + public static async Task DefaultPreReleasePhaseBackToDefault(string value) + { + // arrange + var path = MethodBase.GetCurrentMethod().GetTestDirectory(); + FileSystem.EnsureEmptyDirectory(path); + + await Git.Init(path); + await Git.Commit(path); + await Git.Tag(path, "2.3.4"); + await Git.Commit(path); + + var envVars = ("MinVerDefaultPreReleasePhase".ToAltCase(), "preview"); + var args = $"--default-pre-release-phase {value}"; + + var expected = Package.WithVersion(2, 3, 5, new[] { "alpha", "0" }, 1); + + // act + var cli = await MinVerCli.ReadAsync(path, args: args, envVars: envVars); + + // assert + Assert.Equal(expected.Version, cli.StandardOutput.Trim()); + } + + [Theory] + [InlineData("0.0")] + public static async Task MinimumMajorMinorBackToDefault(string value) + { + // arrange + var path = MethodBase.GetCurrentMethod().GetTestDirectory(); + FileSystem.EnsureEmptyDirectory(path); + + await Git.Init(path); + await Git.Commit(path); + await Git.Tag(path, "2.3.4"); + + var envVars = ("MinVerMinimumMajorMinor".ToAltCase(), "3.0"); + var args = $"--minimum-major-minor {value}"; + + var expected = Package.WithVersion(2, 3, 4); + + // act + var cli = await MinVerCli.ReadAsync(path, args: args, envVars: envVars); + + // assert + Assert.Equal(expected.Version, cli.StandardOutput.Trim()); + } + + [Theory] + [InlineData("\"\"")] + public static async Task TagPrefixBackToDefault(string value) + { + // arrange + var path = MethodBase.GetCurrentMethod().GetTestDirectory(); + FileSystem.EnsureEmptyDirectory(path); + + await Git.Init(path); + await Git.Commit(path); + await Git.Tag(path, "2.3.4-alpha.5"); + + var envVars = ("MinVerTagPrefix", "v."); + var args = $"--tag-prefix {value}"; + + var expected = Package.WithVersion(2, 3, 4, new[] { "alpha", "5" }); + + // act + var cli = await MinVerCli.ReadAsync(path, args: args, envVars: envVars); + + // assert + Assert.Equal(expected.Version, cli.StandardOutput.Trim()); + } + + [Theory] + [InlineData("info")] + public static async Task VerbosityBackToDefault(string value) + { + // arrange + var path = MethodBase.GetCurrentMethod().GetTestDirectory(); + FileSystem.EnsureEmptyDirectory(path); + + var envVars = ("MinVerVerbosity", "error"); + var args = $"--verbosity {value}"; + + // act + var cli = await MinVerCli.ReadAsync(path, args: args, envVars: envVars); + + // assert + Assert.Contains("MinVer:", cli.StandardError, StringComparison.Ordinal); + } + } +} From 0a1041c11390d6276263f788a0a56f5ae30e8562 Mon Sep 17 00:00:00 2001 From: Adam Ralph Date: Wed, 5 Jan 2022 15:30:17 +0100 Subject: [PATCH 2/2] fix null handling --- MinVer.Lib/MajorMinor.cs | 5 +- MinVer.Lib/Version.cs | 6 +- MinVer.Lib/Versioner.cs | 2 +- MinVer/VerbosityMap.cs | 5 +- MinVerTests.Infra/Sdk.cs | 2 +- minver-cli/Logger.cs | 2 +- minver-cli/Options.cs | 137 ++++++++++++++++++------------------- minver-cli/Program.cs | 18 ++--- minver-cli/VerbosityMap.cs | 5 +- 9 files changed, 92 insertions(+), 90 deletions(-) diff --git a/MinVer.Lib/MajorMinor.cs b/MinVer.Lib/MajorMinor.cs index 71534f89..131a5610 100644 --- a/MinVer.Lib/MajorMinor.cs +++ b/MinVer.Lib/MajorMinor.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; namespace MinVer.Lib { @@ -30,9 +31,9 @@ public MajorMinor(int major, int minor) public static string ValidValues => "1.0, 1.1, 2.0, etc."; - public static bool TryParse(string value, out MajorMinor majorMinor) + public static bool TryParse(string value, [NotNullWhen(returnValue: true)] out MajorMinor? majorMinor) { - majorMinor = Zero; + majorMinor = null; if (string.IsNullOrWhiteSpace(value)) { diff --git a/MinVer.Lib/Version.cs b/MinVer.Lib/Version.cs index d787cc28..50355522 100644 --- a/MinVer.Lib/Version.cs +++ b/MinVer.Lib/Version.cs @@ -101,7 +101,7 @@ public int CompareTo(Version? other) public Version Satisfying(MajorMinor minMajorMinor, string defaultPreReleasePhase) { - minMajorMinor ??= MajorMinor.Zero; + minMajorMinor = minMajorMinor ?? throw new ArgumentNullException(nameof(minMajorMinor)); return minMajorMinor.Major < this.major || (minMajorMinor.Major == this.major && minMajorMinor.Minor <= this.minor) ? this @@ -127,8 +127,8 @@ public Version AddBuildMetadata(string buildMetadata) public static bool TryParse(string text, [NotNullWhen(returnValue: true)] out Version? version, string prefix = "") { - text ??= ""; - prefix ??= ""; + text = text ?? throw new ArgumentNullException(nameof(text)); + prefix = prefix ?? throw new ArgumentNullException(nameof(prefix)); version = null; diff --git a/MinVer.Lib/Versioner.cs b/MinVer.Lib/Versioner.cs index 3ec8e4c8..fa946048 100644 --- a/MinVer.Lib/Versioner.cs +++ b/MinVer.Lib/Versioner.cs @@ -54,7 +54,7 @@ private static Version GetVersion(string workDir, string tagPrefix, VersionPart .OrderBy(candidate => candidate.Version) .ThenByDescending(candidate => candidate.Index).ToList(); - var tagWidth = log.IsDebugEnabled ? orderedCandidates.Max(candidate => candidate.Tag?.Length ?? 2) : 0; + var tagWidth = log.IsDebugEnabled ? orderedCandidates.Max(candidate => candidate.Tag.Length) : 0; var versionWidth = log.IsDebugEnabled ? orderedCandidates.Max(candidate => candidate.Version.ToString().Length) : 0; var heightWidth = log.IsDebugEnabled ? orderedCandidates.Max(candidate => candidate.Height).ToString(CultureInfo.CurrentCulture).Length : 0; diff --git a/MinVer/VerbosityMap.cs b/MinVer/VerbosityMap.cs index 119d4e8e..d27b7280 100644 --- a/MinVer/VerbosityMap.cs +++ b/MinVer/VerbosityMap.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace MinVer { internal static class VerbosityMap { - private static readonly Dictionary map = new Dictionary(StringComparer.OrdinalIgnoreCase); + private static readonly Dictionary map = new Dictionary(StringComparer.OrdinalIgnoreCase); static VerbosityMap() { @@ -26,6 +27,6 @@ static void Add(Verbosity verbosity, int shortLength) public static string ValidValues => "q[uiet], m[inimal] (default), n[ormal], d[etailed], or diag[nostic] (case insensitive)"; // spell-checker:enable - public static bool TryMap(string value, out Verbosity verbosity) => map.TryGetValue(value, out verbosity); + public static bool TryMap(string value, [NotNullWhen(returnValue: true)] out Verbosity? verbosity) => map.TryGetValue(value, out verbosity); } } diff --git a/MinVerTests.Infra/Sdk.cs b/MinVerTests.Infra/Sdk.cs index 9639a779..b7f1416b 100644 --- a/MinVerTests.Infra/Sdk.cs +++ b/MinVerTests.Infra/Sdk.cs @@ -20,7 +20,7 @@ public static class Sdk public static async Task CreateSolution(string path, string[] projectNames, string configuration = Configuration.Current) { - projectNames ??= Array.Empty(); + projectNames = projectNames ?? throw new ArgumentNullException(nameof(projectNames)); FileSystem.EnsureEmptyDirectory(path); diff --git a/minver-cli/Logger.cs b/minver-cli/Logger.cs index fe6f43d1..97583bdb 100644 --- a/minver-cli/Logger.cs +++ b/minver-cli/Logger.cs @@ -27,7 +27,7 @@ internal class Logger : ILogger public static void ErrorInvalidEnvVar(string name, string value, string validValueString) { - if (validValueString == null) + if (validValueString.Length == 0) { Error($"Invalid environment variable '{name}' '{value}'."); } diff --git a/minver-cli/Options.cs b/minver-cli/Options.cs index 3d9da952..b2ed4958 100644 --- a/minver-cli/Options.cs +++ b/minver-cli/Options.cs @@ -10,12 +10,12 @@ namespace MinVer internal class Options { public Options( - VersionPart autoIncrement, - string buildMeta, - string defaultPreReleasePhase, - MajorMinor minMajorMinor, - string tagPrefix, - Verbosity verbosity, + VersionPart? autoIncrement, + string? buildMeta, + string? defaultPreReleasePhase, + MajorMinor? minMajorMinor, + string? tagPrefix, + Verbosity? verbosity, Lib.Version? versionOverride) { this.AutoIncrement = autoIncrement; @@ -32,15 +32,19 @@ public static bool TryParseEnvVars([NotNullWhen(returnValue: true)] out Options? { options = null; - var autoIncrement = default(VersionPart); - var minMajorMinor = MajorMinor.Zero; - var verbosity = default(Verbosity); - var versionOverride = default(Lib.Version?); + VersionPart? autoIncrement = null; + MajorMinor? minMajorMinor = null; + Verbosity? verbosity = null; + Lib.Version? versionOverride = null; var autoIncrementEnvVar = GetEnvVar("MinVerAutoIncrement"); if (!string.IsNullOrEmpty(autoIncrementEnvVar)) { - if (!Enum.TryParse(autoIncrementEnvVar, true, out autoIncrement)) + if (Enum.TryParse(autoIncrementEnvVar, true, out var versionPart)) + { + autoIncrement = versionPart; + } + else { Logger.ErrorInvalidEnvVar("MinVerAutoIncrement", autoIncrementEnvVar, VersionPartExtensions.ValidValues); return false; @@ -48,38 +52,30 @@ public static bool TryParseEnvVars([NotNullWhen(returnValue: true)] out Options? } var buildMeta = GetEnvVar("MinVerBuildMetadata"); + var defaultPreReleasePhase = GetEnvVar("MinVerDefaultPreReleasePhase"); var minMajorMinorEnvVar = GetEnvVar("MinVerMinimumMajorMinor"); - if (!string.IsNullOrEmpty(minMajorMinorEnvVar)) + if (!string.IsNullOrEmpty(minMajorMinorEnvVar) && !MajorMinor.TryParse(minMajorMinorEnvVar, out minMajorMinor)) { - if (!MajorMinor.TryParse(minMajorMinorEnvVar, out minMajorMinor)) - { - Logger.ErrorInvalidEnvVar("MinVerMinimumMajorMinor", minMajorMinorEnvVar, MajorMinor.ValidValues); - return false; - } + Logger.ErrorInvalidEnvVar("MinVerMinimumMajorMinor", minMajorMinorEnvVar, MajorMinor.ValidValues); + return false; } var tagPrefix = GetEnvVar("MinVerTagPrefix"); var verbosityEnvVar = GetEnvVar("MinVerVerbosity"); - if (!string.IsNullOrEmpty(verbosityEnvVar)) + if (!string.IsNullOrEmpty(verbosityEnvVar) && !VerbosityMap.TryMap(verbosityEnvVar, out verbosity)) { - if (!VerbosityMap.TryMap(verbosityEnvVar, out verbosity)) - { - Logger.ErrorInvalidEnvVar("MinVerVerbosity", verbosityEnvVar, VerbosityMap.ValidValues); - return false; - } + Logger.ErrorInvalidEnvVar("MinVerVerbosity", verbosityEnvVar, VerbosityMap.ValidValues); + return false; } var versionOverrideEnvVar = GetEnvVar("MinVerVersionOverride"); - if (!string.IsNullOrEmpty(versionOverrideEnvVar)) + if (!string.IsNullOrEmpty(versionOverrideEnvVar) && !Lib.Version.TryParse(versionOverrideEnvVar, out versionOverride)) { - if (!Lib.Version.TryParse(versionOverrideEnvVar, out versionOverride)) - { - Logger.ErrorInvalidEnvVar("MinVerVersionOverride", versionOverrideEnvVar, ""); - return false; - } + Logger.ErrorInvalidEnvVar("MinVerVersionOverride", versionOverrideEnvVar, ""); + return false; } options = new Options(autoIncrement, buildMeta, defaultPreReleasePhase, minMajorMinor, tagPrefix, verbosity, versionOverride); @@ -87,64 +83,65 @@ public static bool TryParseEnvVars([NotNullWhen(returnValue: true)] out Options? return true; } - private static string GetEnvVar(string name) + private static string? GetEnvVar(string name) { var vars = Environment.GetEnvironmentVariables(); var key = vars.Keys .Cast() - .OrderBy(_ => _) - .FirstOrDefault(k => string.Equals(k, name, StringComparison.OrdinalIgnoreCase)); + .OrderBy(_ => _, StringComparer.Ordinal) + .FirstOrDefault(key => string.Equals(key, name, StringComparison.OrdinalIgnoreCase)); - return key == null - ? "" - : (string?)vars[key] ?? ""; + return key == null ? null : (string?)vars[key]; } #endif public static bool TryParse( - string autoIncrementOption, - string buildMetaOption, - string defaultPreReleasePhaseOption, - string minMajorMinorOption, - string tagPrefixOption, - string verbosityOption, + string? autoIncrementOption, + string? buildMetaOption, + string? defaultPreReleasePhaseOption, + string? minMajorMinorOption, + string? tagPrefixOption, + string? verbosityOption, #if MINVER - string versionOverrideOption, + string? versionOverrideOption, #endif [NotNullWhen(returnValue: true)] out Options? options) { options = null; - var autoIncrement = default(VersionPart); - var minMajorMinor = MajorMinor.Zero; - var verbosity = default(Verbosity); - var versionOverride = default(Lib.Version?); + VersionPart? autoIncrement = null; + MajorMinor? minMajorMinor = null; + Verbosity? verbosity = null; + Lib.Version? versionOverride = null; - if (!string.IsNullOrEmpty(autoIncrementOption) && - !Enum.TryParse(autoIncrementOption, true, out autoIncrement)) + if (!string.IsNullOrEmpty(autoIncrementOption)) { - Logger.ErrorInvalidAutoIncrement(autoIncrementOption); - return false; + if (Enum.TryParse(autoIncrementOption, true, out var versionPart)) + { + autoIncrement = versionPart; + } + else + { + Logger.ErrorInvalidAutoIncrement(autoIncrementOption); + return false; + } } - if (!string.IsNullOrEmpty(minMajorMinorOption) && - !MajorMinor.TryParse(minMajorMinorOption, out minMajorMinor)) + if (!string.IsNullOrEmpty(minMajorMinorOption) && !MajorMinor.TryParse(minMajorMinorOption, out minMajorMinor)) { Logger.ErrorInvalidMinMajorMinor(minMajorMinorOption); return false; } - if (!string.IsNullOrEmpty(verbosityOption) && - !VerbosityMap.TryMap(verbosityOption, out verbosity)) + if (!string.IsNullOrEmpty(verbosityOption) && !VerbosityMap.TryMap(verbosityOption, out verbosity)) { Logger.ErrorInvalidVerbosity(verbosityOption); return false; } #if MINVER - if (!string.IsNullOrEmpty(versionOverrideOption) && - !Lib.Version.TryParse(versionOverrideOption, out versionOverride)) + if (!string.IsNullOrEmpty(versionOverrideOption) && !Lib.Version.TryParse(versionOverrideOption, out versionOverride)) { Logger.ErrorInvalidVersionOverride(versionOverrideOption); return false; @@ -158,25 +155,27 @@ public static bool TryParse( public Options Mask(Options other) => new Options( - this.AutoIncrement == default ? other.AutoIncrement : this.AutoIncrement, - string.IsNullOrEmpty(this.BuildMeta) ? other.BuildMeta : this.BuildMeta, - string.IsNullOrEmpty(this.DefaultPreReleasePhase) ? other.DefaultPreReleasePhase : this.DefaultPreReleasePhase, - this.MinMajorMinor == MajorMinor.Zero ? other.MinMajorMinor : this.MinMajorMinor, - string.IsNullOrEmpty(this.TagPrefix) ? other.TagPrefix : this.TagPrefix, - this.Verbosity == default ? other.Verbosity : this.Verbosity, - this.VersionOverride ?? other.VersionOverride); +#pragma warning disable format + this.AutoIncrement ?? other.AutoIncrement, + this.BuildMeta ?? other.BuildMeta, + this.DefaultPreReleasePhase ?? other.DefaultPreReleasePhase, + this.MinMajorMinor ?? other.MinMajorMinor, + this.TagPrefix ?? other.TagPrefix, + this.Verbosity ?? other.Verbosity, + this.VersionOverride ?? other.VersionOverride); +#pragma warning restore format - public VersionPart AutoIncrement { get; private set; } + public VersionPart? AutoIncrement { get; private set; } - public string BuildMeta { get; private set; } + public string? BuildMeta { get; private set; } - public string DefaultPreReleasePhase { get; private set; } + public string? DefaultPreReleasePhase { get; private set; } - public MajorMinor MinMajorMinor { get; private set; } + public MajorMinor? MinMajorMinor { get; private set; } - public string TagPrefix { get; private set; } + public string? TagPrefix { get; private set; } - public Verbosity Verbosity { get; private set; } + public Verbosity? Verbosity { get; private set; } public Lib.Version? VersionOverride { get; private set; } } diff --git a/minver-cli/Program.cs b/minver-cli/Program.cs index adf9ce83..edcbba2b 100644 --- a/minver-cli/Program.cs +++ b/minver-cli/Program.cs @@ -40,14 +40,14 @@ private static int Main(string[] args) } if (!Options.TryParse( - autoIncrementOption.Value() ?? "", - buildMetaOption.Value() ?? "", - defaultPreReleasePhaseOption.Value() ?? "", - minMajorMinorOption.Value() ?? "", - tagPrefixOption.Value() ?? "", - verbosityOption.Value() ?? "", + autoIncrementOption.Value(), + buildMetaOption.Value(), + defaultPreReleasePhaseOption.Value(), + minMajorMinorOption.Value(), + tagPrefixOption.Value(), + verbosityOption.Value(), #if MINVER - versionOverrideOption.Value() ?? "", + versionOverrideOption.Value(), #endif out var options)) { @@ -63,7 +63,7 @@ private static int Main(string[] args) options = options.Mask(envOptions); #endif - var log = new Logger(options.Verbosity); + var log = new Logger(options.Verbosity ?? default); _ = log.IsDebugEnabled && log.Debug($"MinVer {informationalVersion}."); @@ -76,7 +76,7 @@ private static int Main(string[] args) return 0; } - var version = Versioner.GetVersion(workDir, options.TagPrefix, options.MinMajorMinor, options.BuildMeta, options.AutoIncrement, options.DefaultPreReleasePhase, log); + var version = Versioner.GetVersion(workDir, options.TagPrefix ?? "", options.MinMajorMinor ?? MajorMinor.Zero, options.BuildMeta ?? "", options.AutoIncrement ?? default, options.DefaultPreReleasePhase ?? "", log); Console.Out.WriteLine(version); diff --git a/minver-cli/VerbosityMap.cs b/minver-cli/VerbosityMap.cs index 60cc8bb6..046469ea 100644 --- a/minver-cli/VerbosityMap.cs +++ b/minver-cli/VerbosityMap.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace MinVer { internal static class VerbosityMap { - private static readonly Dictionary map = new Dictionary(StringComparer.OrdinalIgnoreCase); + private static readonly Dictionary map = new Dictionary(StringComparer.OrdinalIgnoreCase); static VerbosityMap() { @@ -26,6 +27,6 @@ static void Add(Verbosity verbosity) public static string ValidValues => "e[rror], w[arn], i[nfo] (default), d[ebug], or t[race] (case insensitive)"; // spell-checker:enable - public static bool TryMap(string value, out Verbosity verbosity) => map.TryGetValue(value, out verbosity); + public static bool TryMap(string value, [NotNullWhen(returnValue: true)] out Verbosity? verbosity) => map.TryGetValue(value, out verbosity); } }