Skip to content

Instantly share code, notes, and snippets.

@mfcollins3
Created January 24, 2013 16:44
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mfcollins3/4624831 to your computer and use it in GitHub Desktop.
Save mfcollins3/4624831 to your computer and use it in GitHub Desktop.
Classes for using semantic version numbers with .NET applications. For more information, see <http://www.michaelfcollins3.me/blog/2013/01/23/semantic_versioning_dotnet.html>.

Semantic Versioning in .NET Source Code

This Gist contains the source code that was presented in my post Semantic Versioning in .NET. The source code includes the SemanticVersion class, the SemanticVersionAttribute class, and the unit tests for both classes. In addition, the MSBuild custom task definition is included for generating the VersionInfo.cs files at build time like I discussed in the blog.

Please feel free to contact me through my blog or through the comments section of the post if you have any questions or issues using this source code.

License

Copyright © 2013 Michael F. Collins, III

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including but without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

<!--
The GenerateVersionInfo task will generate the VersionInfo.cs file with the
metadata for the current build.
-->
<UsingTask TaskName="GenerateVersionInfo"
TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<OutputPath ParameterType="System.String" Required="true"/>
<Configuration ParameterType="System.String" Required="true"/>
<ProductVersion ParameterType="System.String" Required="true"/>
<AssemblyVersion ParameterType="System.String" Required="true"/>
<SemanticVersion ParameterType="System.String" Required="true"/>
</ParameterGroup>
<Task>
<Using Namespace="System.CodeDom"/>
<Using Namespace="System.CodeDom.Compiler"/>
<Using Namespace="System.IO"/>
<Using Namespace="System.Reflection"/>
<Code Type="Fragment" Language="cs">
<![CDATA[
var codeCompileUnit = new CodeCompileUnit();
codeCompileUnit.AssemblyCustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(AssemblyConfigurationAttribute)), new CodeAttributeArgument(new CodePrimitiveExpression(Configuration))));
codeCompileUnit.AssemblyCustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(AssemblyFileVersionAttribute)), new CodeAttributeArgument(new CodePrimitiveExpression(ProductVersion))));
codeCompileUnit.AssemblyCustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(AssemblyInformationalVersionAttribute)), new CodeAttributeArgument(new CodePrimitiveExpression(ProductVersion))));
codeCompileUnit.AssemblyCustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(AssemblyVersionAttribute)), new CodeAttributeArgument(new CodePrimitiveExpression(AssemblyVersion))));
codeCompileUnit.AssemblyCustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference("Neudesic.CommandLine.SemanticVersion"), new CodeAttributeArgument(new CodePrimitiveExpression(SemanticVersion))));
using (var provider = CodeDomProvider.CreateProvider("CSharp"))
using (var writer = File.CreateText(OutputPath))
{
provider.GenerateCodeFromCompileUnit(codeCompileUnit, writer, new CodeGeneratorOptions());
}
]]>
</Code>
</Task>
</UsingTask>
//-----------------------------------------------------------------------------
// <copyright file="SemanticVersion.cs" company="ImaginaryRealities">
// Copyright 2013 ImaginaryRealities, LLC
// </copyright>
// <summary>
// This file implements the SemanticVersion class. The SemanticVersion class
// represents a semantic version number for a program.
// </summary>
// <license>
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including but without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// </license>
//-----------------------------------------------------------------------------
namespace ImaginaryRealities.Framework
{
using System;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Text.RegularExpressions;
/// <summary>
/// Stores a semantic version number for a program.
/// </summary>
[Serializable]
public sealed class SemanticVersion : IComparable, IComparable<SemanticVersion>, IEquatable<SemanticVersion>
{
/// <summary>
/// A regular expression to detect whether a string contains only
/// digits.
/// </summary>
private static readonly Regex AllDigitsRegex = new Regex(
@"[0-9]+", RegexOptions.Compiled | RegexOptions.Singleline);
/// <summary>
/// The regular expression to use to parse a semantic version number.
/// </summary>
private static readonly Regex SemanticVersionRegex =
new Regex(
@"^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(-(?<prerelease>[A-Za-z0-9\-\.]+))?(\+(?<build>[A-Za-z0-9\-\.]+))?$",
RegexOptions.Compiled | RegexOptions.Singleline);
/// <summary>
/// Initializes a new instance of the <see cref="SemanticVersion"/> class.
/// </summary>
/// <param name="version">
/// The semantic version number to be parsed.
/// </param>
public SemanticVersion(string version)
{
Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(version));
Contract.Ensures(0 <= this.MajorVersion);
Contract.Ensures(0 <= this.MinorVersion);
Contract.Ensures(0 <= this.PatchVersion);
var match = SemanticVersionRegex.Match(version);
if (!match.Success)
{
var message = string.Format(
CultureInfo.CurrentCulture, SemanticVersionResources.InvalidSemanticVersion, version);
throw new ArgumentException(message, "version");
}
this.MajorVersion = int.Parse(match.Groups["major"].Value, CultureInfo.InvariantCulture);
this.MinorVersion = int.Parse(match.Groups["minor"].Value, CultureInfo.InvariantCulture);
this.PatchVersion = int.Parse(match.Groups["patch"].Value, CultureInfo.InvariantCulture);
this.PrereleaseVersion = match.Groups["prerelease"].Success ? match.Groups["prerelease"].Value : null;
this.BuildVersion = match.Groups["build"].Success ? match.Groups["build"].Value : null;
}
/// <summary>
/// Initializes a new instance of the <see cref="SemanticVersion"/> class.
/// </summary>
/// <param name="majorVersion">
/// The major version number.
/// </param>
/// <param name="minorVersion">
/// The minor version number.
/// </param>
/// <param name="patchVersion">
/// The patch version number.
/// </param>
public SemanticVersion(int majorVersion, int minorVersion, int patchVersion)
{
Contract.Requires<ArgumentException>(0 <= majorVersion);
Contract.Requires<ArgumentException>(0 <= minorVersion);
Contract.Requires<ArgumentException>(0 <= patchVersion);
Contract.Ensures(0 <= this.MajorVersion);
Contract.Ensures(0 <= this.MinorVersion);
Contract.Ensures(0 <= this.PatchVersion);
this.MajorVersion = majorVersion;
this.MinorVersion = minorVersion;
this.PatchVersion = patchVersion;
}
/// <summary>
/// Gets the build number.
/// </summary>
/// <value>
/// The value of this property is a string containing the build
/// identifier for the version number.
/// </value>
public string BuildVersion { get; private set; }
/// <summary>
/// Gets the major version number.
/// </summary>
/// <value>
/// The value of this property is a non-negative integer for the major
/// version number.
/// </value>
public int MajorVersion { get; private set; }
/// <summary>
/// Gets the minor version number.
/// </summary>
/// <value>
/// The value of this property is a non-negative integer for the minor
/// version number.
/// </value>
public int MinorVersion { get; private set; }
/// <summary>
/// Gets the patch version number.
/// </summary>
/// <value>
/// The value of this property is a non-negative integer for the patch
/// version number.
/// </value>
public int PatchVersion { get; private set; }
/// <summary>
/// Gets the pre-release version component.
/// </summary>
/// <value>
/// The value of this property is a string containing the pre-release
/// identifier.
/// </value>
public string PrereleaseVersion { get; private set; }
/// <summary>
/// Compares two <see cref="SemanticVersion"/> objects for equality.
/// </summary>
/// <param name="version">
/// The first <see cref="SemanticVersion"/> object to compare.
/// </param>
/// <param name="other">
/// The second semantic version object to compare.
/// </param>
/// <returns>
/// <b>True</b> if the objects are equal, or <b>false</b> if the
/// objects are not equal.
/// </returns>
public static bool operator ==(SemanticVersion version, SemanticVersion other)
{
if (ReferenceEquals(null, version))
{
return ReferenceEquals(null, other);
}
return version.Equals(other);
}
/// <summary>
/// Compares two <see cref="SemanticVersion"/> objects for equality.
/// </summary>
/// <param name="version">
/// The first <see cref="SemanticVersion"/> object to compare.
/// </param>
/// <param name="other">
/// The second <see cref="SemanticVersion"/> object to compare.
/// </param>
/// <returns>
/// <b>True</b> if the objects are not equal, or <b>false</b> if the
/// objects are equal.
/// </returns>
public static bool operator !=(SemanticVersion version, SemanticVersion other)
{
if (ReferenceEquals(null, version))
{
return !ReferenceEquals(null, other);
}
return !version.Equals(other);
}
/// <summary>
/// Compares two <see cref="SemanticVersion"/> objects to determine if
/// the first object logically precedes the second object.
/// </summary>
/// <param name="version">
/// The first <see cref="SemanticVersion"/> object to compare.
/// </param>
/// <param name="other">
/// The second <see cref="SemanticVersion"/> object to compare.
/// </param>
/// <returns>
/// <b>True</b> if <paramref name="version"/> precedes
/// <paramref name="other"/>, otherwise <b>false</b>.
/// </returns>
[SuppressMessage(
"Microsoft.Design",
"CA1062:Validate arguments of public methods",
MessageId = "0",
Justification = "MFC3: The version argument is being validated using code contracts.")]
public static bool operator <(SemanticVersion version, SemanticVersion other)
{
Contract.Requires<ArgumentNullException>(null != version);
Contract.Requires<ArgumentNullException>(null != other);
return 0 > version.CompareTo(other);
}
/// <summary>
/// Compares two <see cref="SemanticVersion"/> object to determine if
/// the first object logically precedes the second object.
/// </summary>
/// <param name="version">
/// The first <see cref="SemanticVersion"/> object to compare.
/// </param>
/// <param name="other">
/// The second <see cref="SemanticVersion"/> object to compare.
/// </param>
/// <returns>
/// <b>True</b> if <paramref name="version"/> follows
/// <paramref name="other"/>, otherwise <b>false</b>.
/// </returns>
[SuppressMessage(
"Microsoft.Design",
"CA1062:Validate arguments of public methods",
MessageId = "0",
Justification = "MFC3: The version argument is being validated using code contracts.")]
public static bool operator >(SemanticVersion version, SemanticVersion other)
{
Contract.Requires<ArgumentNullException>(null != version);
Contract.Requires<ArgumentNullException>(null != version);
return 0 < version.CompareTo(other);
}
/// <summary>
/// Compares two objects.
/// </summary>
/// <param name="obj">
/// The object to compare to this object.
/// </param>
/// <returns>
/// Returns a value that indicates the relative order of the objects
/// that are being compared.
/// <list type="table">
/// <listheader>
/// <term>Value</term>
/// <description>Meaning</description>
/// </listheader>
/// <item>
/// <term>Less than zero</term>
/// <description>
/// This instance precedes <paramref name="obj"/> in the sort order.
/// </description>
/// </item>
/// <item>
/// <term>Zero</term>
/// <description>
/// This instance occurs in the same position in the sort order as
/// <paramref name="obj"/>.
/// </description>
/// </item>
/// <item>
/// <term>Greater than zero</term>
/// <description>
/// This instance follows <paramref name="obj"/> in the sort order.
/// </description>
/// </item>
/// </list>
/// </returns>
/// <exception cref="ArgumentException">
/// <paramref name="obj"/> is not a <see cref="SemanticVersion"/>
/// object.
/// </exception>
public int CompareTo(object obj)
{
var otherVersion = obj as SemanticVersion;
if (null == otherVersion)
{
throw new ArgumentException(SemanticVersionResources.ObjectIsNotASemanticVersion, "obj");
}
return this.CompareTo(otherVersion);
}
/// <summary>
/// Compares the current object with another
/// <see cref="SemanticVersion"/> object.
/// </summary>
/// <param name="other">
/// The other <see cref="SemanticVersion"/> object to compare to this
/// instance.
/// </param>
/// <returns>
/// Returns a value that indicates the relative order of the objects
/// that are being compared.
/// <list type="table">
/// <listheader>
/// <term>Value</term>
/// <description>Meaning</description>
/// </listheader>
/// <item>
/// <term>Less than zero</term>
/// <description>
/// This instance precedes <paramref name="other"/> in the sort order.
/// </description>
/// </item>
/// <item>
/// <term>Zero</term>
/// <description>
/// This instance occurs in the same position in the sort order as
/// <paramref name="other"/>.
/// </description>
/// </item>
/// <item>
/// <term>Greater than zero</term>
/// <description>
/// This instance follows <paramref name="other"/> in the sort order.
/// </description>
/// </item>
/// </list>
/// </returns>
public int CompareTo(SemanticVersion other)
{
if (null == other)
{
throw new ArgumentNullException("other");
}
if (ReferenceEquals(this, other))
{
return 0;
}
var result = this.MajorVersion.CompareTo(other.MajorVersion);
if (0 == result)
{
result = this.MinorVersion.CompareTo(other.MinorVersion);
if (0 == result)
{
result = this.PatchVersion.CompareTo(other.PatchVersion);
if (0 == result)
{
result = ComparePrereleaseVersions(this.PrereleaseVersion, other.PrereleaseVersion);
if (0 == result)
{
result = CompareBuildVersions(this.BuildVersion, other.BuildVersion);
}
}
}
}
return result;
}
/// <summary>
/// Compares this instance to another object for equality.
/// </summary>
/// <param name="obj">
/// The object to compare to this instance.
/// </param>
/// <returns>
/// <b>True</b> if the objects are equal, or <b>false</b> if the
/// objects are not equal.
/// </returns>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
var other = obj as SemanticVersion;
return null != other ? this.Equals(other) : false;
}
/// <summary>
/// Compares this instance to another <see cref="SemanticVersion"/>
/// object for equality.
/// </summary>
/// <param name="other">
/// The <see cref="SemanticVersion"/> object to compare to this
/// instance.
/// </param>
/// <returns>
/// <b>True</b> if the objects are equal, or false if the objects are
/// not equal.
/// </returns>
public bool Equals(SemanticVersion other)
{
if (ReferenceEquals(this, other))
{
return true;
}
if (ReferenceEquals(other, null))
{
return false;
}
return this.MajorVersion == other.MajorVersion && this.MinorVersion == other.MinorVersion
&& this.PatchVersion == other.PatchVersion && this.PrereleaseVersion == other.PrereleaseVersion
&& this.BuildVersion == other.BuildVersion;
}
/// <summary>
/// Calculates the hash code for the object.
/// </summary>
/// <returns>
/// The hash code for the object.
/// </returns>
public override int GetHashCode()
{
var hashCode = 17;
hashCode = (hashCode * 37) + this.MajorVersion;
hashCode = (hashCode * 37) + this.MinorVersion;
hashCode = (hashCode * 37) + this.PatchVersion;
hashCode = null != this.PrereleaseVersion
? (hashCode * 37) + this.PrereleaseVersion.GetHashCode()
: hashCode;
hashCode = null != this.BuildVersion ? (hashCode * 37) + this.BuildVersion.GetHashCode() : hashCode;
return hashCode;
}
/// <summary>
/// Returns the string representation of the semantic version number.
/// </summary>
/// <returns>
/// The semantic version number.
/// </returns>
public override string ToString()
{
return string.Format(
CultureInfo.InvariantCulture,
"{0}.{1}.{2}{3}{4}",
this.MajorVersion,
this.MinorVersion,
this.PatchVersion,
!string.IsNullOrEmpty(this.PrereleaseVersion) ? "-" + this.PrereleaseVersion : string.Empty,
!string.IsNullOrEmpty(this.BuildVersion) ? "+" + this.BuildVersion : string.Empty);
}
/// <summary>
/// Compares two build version values to determine precedence.
/// </summary>
/// <param name="identifier1">
/// The first identifier to compare.
/// </param>
/// <param name="identifier2">
/// The second identifier to compare.
/// </param>
/// <returns>
/// Returns a value that indicates the relative order of the objects
/// that are being compared.
/// <list type="table">
/// <listheader>
/// <term>Value</term>
/// <description>Meaning</description>
/// </listheader>
/// <item>
/// <term>Less than zero</term>
/// <description>
/// <paramref name="identifier1"/> precedes
/// <paramref name="identifier2"/> in the sort order.
/// </description>
/// </item>
/// <item>
/// <term>Zero</term>
/// <description>
/// The identifiers occur in the same position in the sort order.
/// </description>
/// </item>
/// <item>
/// <term>Greater than zero</term>
/// <description>
/// <paramref name="identifier1"/> follows
/// <paramref name="identifier2"/> in the sort order.
/// </description>
/// </item>
/// </list>
/// </returns>
private static int CompareBuildVersions(string identifier1, string identifier2)
{
var result = 0;
var hasIdentifier1 = !string.IsNullOrEmpty(identifier1);
var hasIdentifier2 = !string.IsNullOrEmpty(identifier2);
if (hasIdentifier1 && !hasIdentifier2)
{
result = 1;
}
else if (!hasIdentifier1 && hasIdentifier2)
{
result = -1;
}
else if (hasIdentifier1)
{
var dotDelimiter = new[] { '.' };
var parts1 = identifier1.Split(dotDelimiter, StringSplitOptions.RemoveEmptyEntries);
var parts2 = identifier2.Split(dotDelimiter, StringSplitOptions.RemoveEmptyEntries);
var max = Math.Max(parts1.Length, parts2.Length);
for (var i = 0; i < max; i++)
{
if (i == parts1.Length && i != parts2.Length)
{
result = -1;
break;
}
if (i != parts1.Length && i == parts2.Length)
{
result = 1;
break;
}
var part1 = parts1[i];
var part2 = parts2[i];
if (AllDigitsRegex.IsMatch(part1) && AllDigitsRegex.IsMatch(part2))
{
var value1 = int.Parse(part1, CultureInfo.InvariantCulture);
var value2 = int.Parse(part2, CultureInfo.InvariantCulture);
result = value1.CompareTo(value2);
}
else
{
result = string.Compare(part1, part2, StringComparison.Ordinal);
}
if (0 != result)
{
break;
}
}
}
return result;
}
/// <summary>
/// Compares two pre-release version values to determine precedence.
/// </summary>
/// <param name="identifier1">
/// The first identifier to compare.
/// </param>
/// <param name="identifier2">
/// The second identifier to compare.
/// </param>
/// <returns>
/// Returns a value that indicates the relative order of the objects
/// that are being compared.
/// <list type="table">
/// <listheader>
/// <term>Value</term>
/// <description>Meaning</description>
/// </listheader>
/// <item>
/// <term>Less than zero</term>
/// <description>
/// <paramref name="identifier1"/> precedes
/// <paramref name="identifier2"/> in the sort order.
/// </description>
/// </item>
/// <item>
/// <term>Zero</term>
/// <description>
/// The identifiers occur in the same position in the sort order.
/// </description>
/// </item>
/// <item>
/// <term>Greater than zero</term>
/// <description>
/// <paramref name="identifier1"/> follows
/// <paramref name="identifier2"/> in the sort order.
/// </description>
/// </item>
/// </list>
/// </returns>
private static int ComparePrereleaseVersions(string identifier1, string identifier2)
{
var result = 0;
var hasIdentifier1 = !string.IsNullOrEmpty(identifier1);
var hasIdentifier2 = !string.IsNullOrEmpty(identifier2);
if (hasIdentifier1 && !hasIdentifier2)
{
result = -1;
}
else if (!hasIdentifier1 && hasIdentifier2)
{
result = 1;
}
else if (hasIdentifier1)
{
var dotDelimiter = new[] { '.' };
var parts1 = identifier1.Split(dotDelimiter, StringSplitOptions.RemoveEmptyEntries);
var parts2 = identifier2.Split(dotDelimiter, StringSplitOptions.RemoveEmptyEntries);
var max = Math.Max(parts1.Length, parts2.Length);
for (var i = 0; i < max; i++)
{
if (i == parts1.Length && i != parts2.Length)
{
result = -1;
break;
}
if (i != parts1.Length && i == parts2.Length)
{
result = 1;
break;
}
var part1 = parts1[i];
var part2 = parts2[i];
if (AllDigitsRegex.IsMatch(part1) && AllDigitsRegex.IsMatch(part2))
{
var value1 = int.Parse(part1, CultureInfo.InvariantCulture);
var value2 = int.Parse(part2, CultureInfo.InvariantCulture);
result = value1.CompareTo(value2);
}
else
{
result = string.Compare(part1, part2, StringComparison.Ordinal);
}
if (0 != result)
{
break;
}
}
}
return result;
}
}
}
//-----------------------------------------------------------------------------
// <copyright file="SemanticVersionAttribute.cs" company="ImaginaryRealities">
// Copyright 2013 ImaginaryRealities, LLC
// </copyright>
// <summary>
// This file implements the SemanticVersionAttribute class. The
// SemanticVersionAttribute attribute is used to decorate an assembly with its
// full semantic version number.
// </summary>
// <license>
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including but without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// </license>
//-----------------------------------------------------------------------------
namespace ImaginaryRealities.Framework
{
using System;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
/// <summary>
/// Attribute that is used to decorate assemblies with a semantic version
/// number.
/// </summary>
[SuppressMessage(
"Microsoft.Design",
"CA1019:DefineAccessorsForAttributeArguments",
Justification = "MFC3: The version parameter to the attribute is converted to a SemanticVersion object and exposed through the Version property.")]
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)]
[Serializable]
public sealed class SemanticVersionAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="SemanticVersionAttribute"/> class.
/// </summary>
/// <param name="semanticNumber">
/// The semantic version number for the assembly.
/// </param>
public SemanticVersionAttribute(string semanticNumber)
{
Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(semanticNumber));
Contract.Ensures(null != this.Version);
this.Version = new SemanticVersion(semanticNumber);
}
/// <summary>
/// Gets the semantic version number for the assembly.
/// </summary>
/// <value>
/// The value of this property is a <see cref="SemanticVersion"/>
/// object.
/// </value>
public SemanticVersion Version { get; private set; }
}
}
//-----------------------------------------------------------------------------
// <copyright file="SemanticVersionAttributeTests.cs"
// company="ImaginaryRealities">
// Copyright 2013 ImaginaryRealities, LLC
// </copyright>
// <summary>
// This file implements the unit tests for the SemanticVersionAttribute class.
// </summary>
// <license>
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including but without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// </license>
//-----------------------------------------------------------------------------
namespace ImaginaryRealities.Framework.UnitTests
{
using Xunit;
/// <summary>
/// Unit tests the <see cref="SemanticVersionAttribute"/> class.
/// </summary>
public static class SemanticVersionAttributeTests
{
/// <summary>
/// Tests that the <see cref="SemanticVersionAttribute"/> constructor
/// initializes the attribute with the correct version number.
/// </summary>
[Fact]
public static void AttributeReturnsCorrectVersion()
{
var expectedVersion = new SemanticVersion("1.2.3-alpha+build.1");
var attribute = new SemanticVersionAttribute("1.2.3-alpha+build.1");
Assert.Equal(expectedVersion, attribute.Version);
}
}
}
//-----------------------------------------------------------------------------
// <copyright file="SemanticVersionTests.cs" company="ImaginaryRealities">
// Copyright 2013 ImaginaryRealities, LLC
// </copyright>
// <summary>
// This file implements the unit tests for the SemanticVersion class.
// </summary>
// <license>
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including but without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// </license>
//-----------------------------------------------------------------------------
namespace ImaginaryRealities.Framework.UnitTests
{
using System;
using Xunit;
/// <summary>
/// Unit tests the <see cref="SemanticVersion"/> class.
/// </summary>
public static class SemanticVersionTests
{
/// <summary>
/// Tests that the <see cref="SemanticVersion.CompareTo(object)"/>
/// method successfully compares two <see cref="SemanticVersion"/>
/// objects.
/// </summary>
[Fact]
public static void CompareToComparesTwoSemanticVersionObjects()
{
var version1 = new SemanticVersion(1, 0, 0);
object version2 = new SemanticVersion(1, 0, 0);
Assert.Equal(0, version1.CompareTo(version2));
}
/// <summary>
/// Tests that the <see cref="SemanticVersion"/> constructor can parse
/// a string containing a major, minor, and patch version.
/// </summary>
[Fact]
public static void ConstructorInitializesBaseVersionNumbers()
{
var version = new SemanticVersion("1.2.3");
Assert.Equal(1, version.MajorVersion);
Assert.Equal(2, version.MinorVersion);
Assert.Equal(3, version.PatchVersion);
Assert.Null(version.PrereleaseVersion);
Assert.Null(version.BuildVersion);
}
/// <summary>
/// Tests that the <see cref="SemanticVersion"/> constructor can parse
/// a full version number.
/// </summary>
[Fact]
public static void ConstructorParsesFullVersionNumber()
{
var version = new SemanticVersion("1.2.3-alpha.1+build.123");
Assert.Equal(1, version.MajorVersion);
Assert.Equal(2, version.MinorVersion);
Assert.Equal(3, version.PatchVersion);
Assert.Equal("alpha.1", version.PrereleaseVersion);
Assert.Equal("build.123", version.BuildVersion);
}
/// <summary>
/// Tests that the <see cref="SemanticVersion"/> constructor will throw
/// an exception if the version number is invalid.
/// </summary>
[Fact]
public static void ConstructorThrowsAnExceptionIfVersionIsInvalid()
{
Assert.Throws<ArgumentException>(() => new SemanticVersion("1.abc.3"));
}
/// <summary>
/// Tests that an exception will be thrown if the major version is
/// less than zero.
/// </summary>
[Fact]
public static void ContractFailsIfMajorVersionIsLessThanZero()
{
Assert.Throws<ArgumentException>(() => new SemanticVersion(-1, 0, 0));
}
/// <summary>
/// Tests that an exception will be thrown if the minor version is
/// less than zero.
/// </summary>
[Fact]
public static void ContractFailsIfMinorVersionIsLessThanZero()
{
Assert.Throws<ArgumentException>(() => new SemanticVersion(0, -1, 0));
}
/// <summary>
/// Tests that an exception will be thrown if the patch version is
/// less than zero.
/// </summary>
[Fact]
public static void ContractFailsIfPatchVersionIsLessThanZero()
{
Assert.Throws<ArgumentException>(() => new SemanticVersion(0, 0, -1));
}
/// <summary>
/// Tests that the <see cref="SemanticVersion.GetHashCode"/> method
/// calculates and returns the correct hash code for a version object.
/// </summary>
[Fact]
public static void HashCodeIsCorrect()
{
var expectedHashCode = 17;
expectedHashCode = (expectedHashCode * 37) + 1;
expectedHashCode = expectedHashCode * 37;
expectedHashCode = expectedHashCode * 37;
expectedHashCode = (expectedHashCode * 37) + "alpha".GetHashCode();
expectedHashCode = (expectedHashCode * 37) + "build.1".GetHashCode();
var version = new SemanticVersion("1.0.0-alpha+build.1");
Assert.Equal(expectedHashCode, version.GetHashCode());
}
/// <summary>
/// Tests that the <see cref="SemanticVersion.GetHashCode"/> method
/// calculates and returns the correct hash code for a version object
/// without either a pre-release version or build version.
/// </summary>
[Fact]
public static void HashCodeIsCorrectForVersionWithoutPrereleaseOrBuildNumbers()
{
var expectedHashCode = 17;
expectedHashCode = (expectedHashCode * 37) + 1;
expectedHashCode = expectedHashCode * 37;
expectedHashCode = expectedHashCode * 37;
var version = new SemanticVersion(1, 0, 0);
Assert.Equal(expectedHashCode, version.GetHashCode());
}
/// <summary>
/// Tests that one version is less than another version based on the
/// major version numbers.
/// </summary>
[Fact]
public static void MajorVersionIsLessThanOther()
{
var version1 = new SemanticVersion(1, 2, 3);
var version2 = new SemanticVersion(2, 0, 0);
Assert.True(version1 < version2);
}
/// <summary>
/// Tests that one version is greater than another version based on the
/// minor version numbers.
/// </summary>
[Fact]
public static void MinorVersionIsGreaterThanOther()
{
var version1 = new SemanticVersion(1, 2, 0);
var version2 = new SemanticVersion(1, 1, 0);
Assert.True(version1 > version2);
}
/// <summary>
/// Tests that one version is less than the other based on the patch
/// version numbers.
/// </summary>
[Fact]
public static void PatchVersionIsLessThanOther()
{
var version1 = new SemanticVersion(1, 1, 3);
var version2 = new SemanticVersion(1, 1, 4);
Assert.True(version1 < version2);
}
/// <summary>
/// Tests that a pre-release version logically precedes a release
/// version.
/// </summary>
[Fact]
public static void ReleaseVersionIsGreaterThanPrereleaseVersion()
{
var version1 = new SemanticVersion("1.0.0-alpha");
var version2 = new SemanticVersion(1, 0, 0);
Assert.True(version1 < version2);
Assert.True(version2 > version1);
}
/// <summary>
/// Tests that the <see cref="SemanticVersion.CompareTo(object)"/>
/// method throws an <see cref="ArgumentException"/> if the parameter
/// is not a <see cref="SemanticVersion"/> object.
/// </summary>
[Fact]
public static void SemanticVersionCannotBeComparedToString()
{
var version = new SemanticVersion(1, 0, 0);
Assert.Throws<ArgumentException>(() => version.CompareTo("1.3.0"));
}
/// <summary>
/// Tests that the <see cref="SemanticVersion.ToString"/> method
/// returns the correct version number.
/// </summary>
[Fact]
public static void ToStringReturnsCorrectVersionNumber()
{
const string VersionNumber = "1.2.3-alpha+build.12";
var version = new SemanticVersion(VersionNumber);
Assert.Equal(VersionNumber, version.ToString());
}
/// <summary>
/// Tests that a <see cref="SemanticVersion"/> object is equal to
/// itself.
/// </summary>
[Fact]
public static void VersionIsEqualToItself()
{
var version = new SemanticVersion(1, 0, 0);
Assert.True(version.Equals(version));
}
/// <summary>
/// Tests that a <see cref="SemanticVersion"/> object is not equal to
/// null.
/// </summary>
[Fact]
public static void VersionIsNotEqualToNull()
{
var version = new SemanticVersion(1, 0, 0);
Assert.False(version == null);
Assert.False(null == version);
Assert.True(null != version);
Assert.True(version != null);
object other = null;
// ReSharper disable ExpressionIsAlwaysNull
Assert.False(version.Equals(other));
// ReSharper restore ExpressionIsAlwaysNull
}
/// <summary>
/// Tests that a <see cref="SemanticVersion"/> object is not equal to
/// a string.
/// </summary>
[Fact]
public static void VersionIsNotEqualToString()
{
var version = new SemanticVersion(1, 0, 0);
object versionNumber = "1.0.0";
Assert.False(version.Equals(versionNumber));
}
/// <summary>
/// Tests that the <see cref="SemanticVersion.CompareTo(SemanticVersion)"/>
/// method returns zero to indicate that the object is equal to itself.
/// </summary>
[Fact]
public static void VersionIsTheSameAsItself()
{
var version = new SemanticVersion(1, 0, 0);
Assert.Equal(0, version.CompareTo(version));
}
/// <summary>
/// Compares multiple version numbers to validate that the precedence
/// rules are followed when comparing version numbers.
/// </summary>
[Fact]
public static void VersionsAreComparedCorrectly()
{
var version1 = new SemanticVersion("1.0.0-alpha");
var version2 = new SemanticVersion("1.0.0-alpha.1");
var version3 = new SemanticVersion("1.0.0-beta.2");
var version4 = new SemanticVersion("1.0.0-beta.11");
var version5 = new SemanticVersion("1.0.0-rc.1");
var version6 = new SemanticVersion("1.0.0-rc.1+build.1");
var version7 = new SemanticVersion("1.0.0");
var version8 = new SemanticVersion("1.0.0+0.3.7");
var version9 = new SemanticVersion("1.3.7+build");
var version10 = new SemanticVersion("1.3.7+build.2.b8f12d7");
var version11 = new SemanticVersion("1.3.7+build.11.e0f985a");
var version12 = new SemanticVersion("1.0.0-beta");
var version13 = new SemanticVersion("1.0.0+0.3");
Assert.True(version1 < version2);
Assert.True(version2 < version3);
Assert.True(version3 < version4);
Assert.True(version4 < version5);
Assert.True(version5 < version6);
Assert.True(version6 < version7);
Assert.True(version7 < version8);
Assert.True(version8 < version9);
Assert.True(version9 < version10);
Assert.True(version10 < version11);
Assert.True(version4 > version12);
Assert.True(version8 > version7);
Assert.True(version8 > version13);
}
/// <summary>
/// Tests that two <see cref="SemanticVersion"/> objects are equal.
/// </summary>
[Fact]
public static void VersionsAreEqual()
{
var version1 = new SemanticVersion("1.0.0-alpha+build.1");
var version2 = new SemanticVersion("1.0.0-alpha+build.1");
object version3 = version2;
object version4 = version1;
Assert.True(version1 == version2);
Assert.True(version1.Equals(version3));
Assert.True(version1.Equals(version4));
}
/// <summary>
/// Tests that two <see cref="SemanticVersion"/> objects are not equal.
/// </summary>
[Fact]
public static void VersionsAreNotEqual()
{
var version1 = new SemanticVersion("1.0.0");
var version2 = new SemanticVersion("1.0.0-alpha+build.1");
object version3 = version2;
Assert.True(version1 != version2);
Assert.False(version1.Equals(version3));
}
/// <summary>
/// Tests that the <see cref="SemanticVersion.CompareTo(SemanticVersion)"/>
/// method will throw an <see cref="ArgumentNullException"/> exception
/// if the version to compare to is null.
/// </summary>
[Fact]
public static void VersionCannotBeComparedToNull()
{
var version1 = new SemanticVersion(1, 0, 0);
SemanticVersion version2 = null;
// ReSharper disable ExpressionIsAlwaysNull
Assert.Throws<ArgumentNullException>(() => version1.CompareTo(version2));
// ReSharper restore ExpressionIsAlwaysNull
}
}
}
@WattsC-90
Copy link

@mfcollins3 , your SemanticVersion.cs references a SemanticVersionResources for InvalidSemanticVersion and ObjectIsNotASemanticVersion but there is no mention in the gists above what these are.. any idea? I know the code was written some time ago but would be great to figure out what they should be.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment