From 9052adf80d921f34fb36043a526483f0e9eda627 Mon Sep 17 00:00:00 2001 From: Dwayne Bent Date: Wed, 30 Mar 2016 10:09:44 -0400 Subject: [PATCH] Support reading BuildIDs --- .gitignore | 1 + CHANGELOG.md | 1 + Core/CKAN-core.csproj | 18 +- .../IGameVersionProvider.cs | 9 + Core/GameVersionProviders/IKspBuildMap.cs | 11 + .../KspBuildIdVersionProvider.cs | 49 ++ Core/GameVersionProviders/KspBuildMap.cs | 175 +++++ .../KspReadmeVersionProvider.cs | 37 + Core/GameVersionProviders/KspVersionSource.cs | 8 + Core/KSP.cs | 75 +- Core/Net/Repo.cs | 5 + Core/Registry/AvailableModule.cs | 3 +- Core/Registry/IRegistryQuerier.cs | 11 +- Core/Registry/Registry.cs | 11 +- Core/Relationships/RelationshipResolver.cs | 9 +- Core/ServiceLocator.cs | 19 +- Core/Types/CkanModule.cs | 51 +- .../GameComparator/GrasGameComparator.cs | 9 +- Core/Types/GameComparator/IGameComparator.cs | 4 +- .../GameComparator/StrictGameComparator.cs | 58 +- .../GameComparator/YoyoGameComparator.cs | 4 +- Core/Types/KSPVersion.cs | 310 -------- Core/Versioning/KspVersion.cs | 710 ++++++++++++++++++ Core/Versioning/KspVersionBound.cs | 68 ++ Core/Versioning/KspVersionRange.cs | 105 +++ Core/Win32Registry.cs | 14 +- Core/builds.json | 22 + GUI/GUIMod.cs | 3 +- GUI/MainModList.cs | 7 +- Netkan/Sources/Avc/AvcVersion.cs | 7 +- Netkan/Sources/Avc/JsonAvcToKspVersion.cs | 5 +- Netkan/Sources/Spacedock/SDVersion.cs | 5 +- Netkan/Transformers/AvcTransformer.cs | 9 +- Tests/Core/KSPManager.cs | 11 + Tests/Core/Net/Repo.cs | 5 +- Tests/Core/Registry/Registry.cs | 5 +- .../Relationships/RelationshipResolver.cs | 7 +- Tests/Core/Types/GameComparator.cs | 9 +- Tests/Core/Types/KSPVersion.cs | 116 --- Tests/Core/Versioning/KspVersionBoundTests.cs | 150 ++++ Tests/Core/Versioning/KspVersionTests.cs | 436 +++++++++++ Tests/Data/TestData.cs | 7 +- Tests/NetKAN/AVC.cs | 13 +- Tests/NetKAN/Services/ModuleServiceTests.cs | 7 +- .../Transformers/AvcTransformerTests.cs | 15 +- Tests/Tests.csproj | 6 +- 46 files changed, 2013 insertions(+), 607 deletions(-) create mode 100644 Core/GameVersionProviders/IGameVersionProvider.cs create mode 100644 Core/GameVersionProviders/IKspBuildMap.cs create mode 100644 Core/GameVersionProviders/KspBuildIdVersionProvider.cs create mode 100644 Core/GameVersionProviders/KspBuildMap.cs create mode 100644 Core/GameVersionProviders/KspReadmeVersionProvider.cs create mode 100644 Core/GameVersionProviders/KspVersionSource.cs delete mode 100644 Core/Types/KSPVersion.cs create mode 100644 Core/Versioning/KspVersion.cs create mode 100644 Core/Versioning/KspVersionBound.cs create mode 100644 Core/Versioning/KspVersionRange.cs create mode 100644 Core/builds.json delete mode 100644 Tests/Core/Types/KSPVersion.cs create mode 100644 Tests/Core/Versioning/KspVersionBoundTests.cs create mode 100644 Tests/Core/Versioning/KspVersionTests.cs diff --git a/.gitignore b/.gitignore index 3840cfad32..3a2fa46d1d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build/ +/.vs/ packages/ CKAN/*/bin CKAN/*/obj diff --git a/CHANGELOG.md b/CHANGELOG.md index f52626e4c6..98923957ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file. ### Internal +- [Core] CKAN will now read BuildID.txt for more accurate KSP versions (#1645 by: dbent) - [Multiple] Removed various references and code for processing mods on KerbalStuff. Thank you, Sircmpwn, for providing us with such a great service for so long. (#1615 by: Olympic1; reviewed: pjf) - [Spec] Updated Spec with the `kind` field which was introduced in v1.6. (#1597 by: plague006; reviewed: Daz) - [spec] ckan.schema now enforces structure of install directives (#1578 by: Zane6888; reviewed: pjf, Daz) diff --git a/Core/CKAN-core.csproj b/Core/CKAN-core.csproj index be1c49ef6e..b22c605d0c 100644 --- a/Core/CKAN-core.csproj +++ b/Core/CKAN-core.csproj @@ -13,6 +13,7 @@ bin\Debug 4 true + 5 @@ -50,6 +51,11 @@ + + + + + @@ -61,7 +67,6 @@ - @@ -83,6 +88,10 @@ + + + + @@ -99,10 +108,9 @@ + - - - - + + \ No newline at end of file diff --git a/Core/GameVersionProviders/IGameVersionProvider.cs b/Core/GameVersionProviders/IGameVersionProvider.cs new file mode 100644 index 0000000000..96204ec4eb --- /dev/null +++ b/Core/GameVersionProviders/IGameVersionProvider.cs @@ -0,0 +1,9 @@ +using CKAN.Versioning; + +namespace CKAN.GameVersionProviders +{ + public interface IGameVersionProvider + { + bool TryGetVersion(string directory, out KspVersion result); + } +} diff --git a/Core/GameVersionProviders/IKspBuildMap.cs b/Core/GameVersionProviders/IKspBuildMap.cs new file mode 100644 index 0000000000..549441bee2 --- /dev/null +++ b/Core/GameVersionProviders/IKspBuildMap.cs @@ -0,0 +1,11 @@ +using CKAN.Versioning; + +namespace CKAN.GameVersionProviders +{ + public interface IKspBuildMap + { + KspVersion this[string buildId] { get; } + + void Refresh(); + } +} diff --git a/Core/GameVersionProviders/KspBuildIdVersionProvider.cs b/Core/GameVersionProviders/KspBuildIdVersionProvider.cs new file mode 100644 index 0000000000..5e1639aa58 --- /dev/null +++ b/Core/GameVersionProviders/KspBuildIdVersionProvider.cs @@ -0,0 +1,49 @@ +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using CKAN.Versioning; + +namespace CKAN.GameVersionProviders +{ + // ReSharper disable once ClassNeverInstantiated.Global + public sealed class KspBuildIdVersionProvider : IGameVersionProvider + { + private static readonly Regex BuildIdPattern = new Regex(@"^build id\s+=\s+0*(?\d+)", + RegexOptions.IgnoreCase | RegexOptions.Compiled + ); + + private readonly IKspBuildMap _kspBuildMap; + + public KspBuildIdVersionProvider(IKspBuildMap kspBuildMap) + { + _kspBuildMap = kspBuildMap; + } + + public bool TryGetVersion(string directory, out KspVersion result) + { + var buildIdPath = Path.Combine(directory, "buildID.txt"); + + if (File.Exists(buildIdPath)) + { + var match = File + .ReadAllLines(buildIdPath) + .Select(i => BuildIdPattern.Match(i)) + .FirstOrDefault(i => i.Success); + + if (match != null) + { + var version = _kspBuildMap[match.Groups["buildid"].Value]; + + if (version != null) + { + result = version; + return true; + } + } + } + + result = default(KspVersion); + return false; + } + } +} diff --git a/Core/GameVersionProviders/KspBuildMap.cs b/Core/GameVersionProviders/KspBuildMap.cs new file mode 100644 index 0000000000..f85c53f374 --- /dev/null +++ b/Core/GameVersionProviders/KspBuildMap.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using CKAN.Versioning; +using log4net; +using Newtonsoft.Json; + +namespace CKAN.GameVersionProviders +{ + // ReSharper disable once ClassNeverInstantiated.Global + public sealed class KspBuildMap : IKspBuildMap + { + // TODO: Need a way for the client to configure this + private static readonly Uri BuildMapUri = + new Uri("https://raw.githubusercontent.com/KSP-CKAN/CKAN-meta/master/builds.json"); + + // TODO: Get this through dependency injection + private readonly ILog _log = LogManager.GetLogger(typeof(KspBuildMap)); + + private readonly object _buildMapLock = new object(); + private JBuilds _jBuilds; + + private readonly IWin32Registry _registry; + + public KspVersion this[string buildId] + { + get + { + EnsureBuildMap(); + + string version; + return _jBuilds.Builds.TryGetValue(buildId, out version) ? KspVersion.Parse(version) : null; + } + } + + public KspBuildMap(IWin32Registry registry) + { + _registry = registry; + } + + private void EnsureBuildMap() + { + if (ReferenceEquals(_jBuilds, null)) + { + lock(_buildMapLock) + { + if (ReferenceEquals(_jBuilds, null)) + { + Refresh(useCachedVersion: true); + } + } + } + } + + public void Refresh() + { + Refresh(useCachedVersion: false); + } + + private void Refresh(bool useCachedVersion) + { + if (useCachedVersion) + { + // Attempt to set the build map from the cached version in the registry + if (TrySetRegistryBuildMap()) return; + + // Attempt to set the build map from the repository + if (TrySetRemoteBuildMap()) return; + } + else + { + // Attempt to set the build map from the repository + if (TrySetRemoteBuildMap()) return; + + // Attempt to set the build map from the cached version in the registry + if (TrySetRegistryBuildMap()) return; + } + + // If that fails attempt to set the build map from the embedded version + if (TrySetEmbeddedBuildMap()) return; + + _log.Warn("Could not refresh the build map from any source"); + } + + private bool TrySetBuildMap(string buildMapJson) + { + try + { + _jBuilds = JsonConvert.DeserializeObject(buildMapJson); + return true; + } + catch(Exception e) + { + _log.WarnFormat("Could not parse build map"); + _log.DebugFormat("{0}\n{1}", buildMapJson, e); + return false; + } + } + + private bool TrySetRemoteBuildMap() + { + try + { + var json = Net.DownloadText(BuildMapUri); + + if (TrySetBuildMap(json)) + { + _registry.SetKSPBuilds(json); + return true; + } + else + { + return false; + } + } + catch (Exception e) + { + _log.WarnFormat("Could not retrieve latest build map from: {0}", BuildMapUri); + _log.Debug(e); + return false; + } + } + + private bool TrySetRegistryBuildMap() + { + try + { + var json = _registry.GetKSPBuilds(); + return json != null && TrySetBuildMap(json); + } + catch(Exception e) + { + _log.WarnFormat("Could not retrieve build map from registry"); + _log.Debug(e); + return false; + } + } + + private bool TrySetEmbeddedBuildMap() + { + try + { + var resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("CKAN.builds.json"); + + if (resourceStream != null) + { + using (var reader = new StreamReader(resourceStream)) + { + TrySetBuildMap(reader.ReadToEnd()); + return true; + } + } + else + { + return false; + } + } + catch(Exception e) + { + _log.WarnFormat("Could not retrieve build map from embedded resource"); + _log.Debug(e); + return false; + } + } + + // ReSharper disable once ClassNeverInstantiated.Local + private sealed class JBuilds + { + [JsonProperty("builds")] + // ReSharper disable once UnusedAutoPropertyAccessor.Local + public Dictionary Builds { get; set; } + } + } +} diff --git a/Core/GameVersionProviders/KspReadmeVersionProvider.cs b/Core/GameVersionProviders/KspReadmeVersionProvider.cs new file mode 100644 index 0000000000..f1024b8819 --- /dev/null +++ b/Core/GameVersionProviders/KspReadmeVersionProvider.cs @@ -0,0 +1,37 @@ +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using CKAN.Versioning; + +namespace CKAN.GameVersionProviders +{ + // ReSharper disable once ClassNeverInstantiated.Global + public sealed class KspReadmeVersionProvider : IGameVersionProvider + { + private static readonly Regex VersionPattern = new Regex(@"^Version\s+(?\d+\.\d+\.\d+)", + RegexOptions.IgnoreCase | RegexOptions.Compiled + ); + + public bool TryGetVersion(string directory, out KspVersion result) + { + var readmePath = Path.Combine(directory, "readme.txt"); + + if (File.Exists(readmePath)) + { + var match = File + .ReadAllLines(readmePath) + .Select(i => VersionPattern.Match(i)) + .FirstOrDefault(i => i.Success); + + if (match != null) + { + result = KspVersion.Parse(match.Groups["version"].Value); + return true; + } + } + + result = default(KspVersion); + return false; + } + } +} diff --git a/Core/GameVersionProviders/KspVersionSource.cs b/Core/GameVersionProviders/KspVersionSource.cs new file mode 100644 index 0000000000..637e21997e --- /dev/null +++ b/Core/GameVersionProviders/KspVersionSource.cs @@ -0,0 +1,8 @@ +namespace CKAN.GameVersionProviders +{ + public enum KspVersionSource + { + BuildId = 1, + Readme = 2 + } +} diff --git a/Core/KSP.cs b/Core/KSP.cs index 7bf2151833..e21e2e01de 100644 --- a/Core/KSP.cs +++ b/Core/KSP.cs @@ -6,6 +6,9 @@ using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using System.Transactions; +using Autofac; +using CKAN.GameVersionProviders; +using CKAN.Versioning; using log4net; [assembly: InternalsVisibleTo("CKAN.Tests")] @@ -25,7 +28,7 @@ public class KSP : IDisposable private static readonly ILog log = LogManager.GetLogger(typeof(KSP)); private readonly string gamedir; - private KSPVersion version; + private KspVersion version; public NetFileCache Cache { get; private set; } @@ -172,31 +175,7 @@ public static string FindGameDir() /// internal static bool IsKspDir(string directory) { - //first we need to check is directory exists - if (!Directory.Exists(Path.Combine(directory, "GameData"))) - { - log.DebugFormat("Cannot find GameData in {0}", directory); - return false; - } - - if (!File.Exists(Path.Combine(directory, "readme.txt"))) - { - log.DebugFormat("Cannot find readme in {0}", directory); - return false; - } - - //If both exist we should be able to get game version - try - { - DetectVersion(directory); - } - catch (NotKSPDirKraken) - { - log.DebugFormat("Cannot detect KSP version in {0}", directory); - return false; - } - log.DebugFormat("{0} looks like a GameDir", directory); - return true; + return Directory.Exists(Path.Combine(directory, "GameData")); } @@ -204,37 +183,39 @@ internal static bool IsKspDir(string directory) /// Detects the version of KSP in a given directory. /// Throws a NotKSPDirKraken if anything goes wrong. /// - private static KSPVersion DetectVersion(string directory) + private static KspVersion DetectVersion(string directory) { - //Contract.Requires(directory==null); + var version = DetectVersionInternal(directory); - string readme; - try + if (version != null) { - // Slurp our README into memory - readme = File.ReadAllText(Path.Combine(directory, "readme.txt")); + log.DebugFormat("Found version {0}", version); + return version; } - catch + else { - log.Error("Could not open KSP readme.txt in "+directory); - throw new NotKSPDirKraken("readme.txt not found or not readable"); + log.Error("Could not find KSP version"); + throw new NotKSPDirKraken(directory, "Could not find KSP version in readme.txt"); } + } - // And find the KSP version. Easy! :) - Match match = Regex.Match(readme, @"^Version\s+(\d+\.\d+\.\d+)", - RegexOptions.IgnoreCase | RegexOptions.Multiline); + private static KspVersion DetectVersionInternal(string directory) + { + var buildIdVersionProvider = ServiceLocator.Container + .ResolveKeyed(KspVersionSource.BuildId); - if (match.Success) + KspVersion version; + if (buildIdVersionProvider.TryGetVersion(directory, out version)) { - string version = match.Groups[1].Value; - log.DebugFormat("Found version {0}", version); - return new KSPVersion(version); + return version; } + else + { + var readmeVersionProvider = ServiceLocator.Container + .ResolveKeyed(KspVersionSource.Readme); - // Oh noes! We couldn't find the version! - log.Error("Could not find KSP version in readme.txt"); - - throw new NotKSPDirKraken(directory, "Could not find KSP version in readme.txt"); + return readmeVersionProvider.TryGetVersion(directory, out version) ? version : null; + } } /// @@ -344,7 +325,7 @@ public string TempDir() ); } - public KSPVersion Version() + public KspVersion Version() { if (version != null) { diff --git a/Core/Net/Repo.cs b/Core/Net/Repo.cs index d525b36a33..872aaac174 100644 --- a/Core/Net/Repo.cs +++ b/Core/Net/Repo.cs @@ -4,7 +4,9 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; +using Autofac; using ChinhDo.Transactions; +using CKAN.GameVersionProviders; using ICSharpCode.SharpZipLib.GZip; using ICSharpCode.SharpZipLib.Tar; using ICSharpCode.SharpZipLib.Zip; @@ -139,6 +141,9 @@ public static int Update(RegistryManager registry_manager, KSP ksp, IUser user, /// internal static void UpdateRegistry(Uri repo, Registry registry, KSP ksp, IUser user, Boolean clear = true) { + // Use this opportunity to also update the build mappings... kind of hacky + ServiceLocator.Container.Resolve().Refresh(); + log.InfoFormat("Downloading {0}", repo); string repo_file = String.Empty; diff --git a/Core/Registry/AvailableModule.cs b/Core/Registry/AvailableModule.cs index dfa1b316e5..03b8edf783 100644 --- a/Core/Registry/AvailableModule.cs +++ b/Core/Registry/AvailableModule.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Linq; using System.Runtime.Serialization; +using CKAN.Versioning; using log4net; using Newtonsoft.Json; @@ -71,7 +72,7 @@ public void Remove(Version version) /// If not null only consider mods which match this ksp version. /// If not null only consider mods which satisfy the RelationshipDescriptor. /// - public CkanModule Latest(KSPVersion ksp_version = null, RelationshipDescriptor relationship=null) + public CkanModule Latest(KspVersion ksp_version = null, RelationshipDescriptor relationship=null) { var available_versions = new List(module_version.Keys); CkanModule module; diff --git a/Core/Registry/IRegistryQuerier.cs b/Core/Registry/IRegistryQuerier.cs index be3e7e5b6a..36bc855a96 100644 --- a/Core/Registry/IRegistryQuerier.cs +++ b/Core/Registry/IRegistryQuerier.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using CKAN.Versioning; namespace CKAN { @@ -15,7 +16,7 @@ public interface IRegistryQuerier /// the specified version of KSP. /// // TODO: This name is misleading. It's more a LatestAvailable's' - List Available(KSPVersion ksp_version); + List Available(KspVersion ksp_version); /// /// Returns the latest available version of a module that @@ -24,7 +25,7 @@ public interface IRegistryQuerier /// If no ksp_version is provided, the latest module for *any* KSP is returned. /// Throws if asked for a non-existent module. /// - CkanModule LatestAvailable(string identifier, KSPVersion ksp_version, RelationshipDescriptor relationship_descriptor = null); + CkanModule LatestAvailable(string identifier, KspVersion ksp_version, RelationshipDescriptor relationship_descriptor = null); /// /// Returns the latest available version of a module that satisifes the specified version and @@ -33,7 +34,7 @@ public interface IRegistryQuerier /// Returns an empty list if nothing is available for our system, which includes if no such module exists. /// If no KSP version is provided, the latest module for *any* KSP version is given. /// - List LatestAvailableWithProvides(string identifier, KSPVersion ksp_version, RelationshipDescriptor relationship_descriptor = null); + List LatestAvailableWithProvides(string identifier, KspVersion ksp_version, RelationshipDescriptor relationship_descriptor = null); /// /// Checks the sanity of the registry, to ensure that all dependencies are met, @@ -64,7 +65,7 @@ public interface IRegistryQuerier /// Returns a simple array of all incompatible modules for /// the specified version of KSP. /// - List Incompatible(KSPVersion ksp_version); + List Incompatible(KspVersion ksp_version); /// /// Returns a dictionary of all modules installed, along with their @@ -127,7 +128,7 @@ public static bool IsAutodetected(this IRegistryQuerier querier, string identifi /// Is the mod installed and does it have a newer version compatible with version /// We can't update AD mods /// - public static bool HasUpdate(this IRegistryQuerier querier, string identifier, KSPVersion version) + public static bool HasUpdate(this IRegistryQuerier querier, string identifier, KspVersion version) { CkanModule newest_version; try diff --git a/Core/Registry/Registry.cs b/Core/Registry/Registry.cs index 189e4377e4..34abeed78d 100644 --- a/Core/Registry/Registry.cs +++ b/Core/Registry/Registry.cs @@ -5,6 +5,7 @@ using System.Runtime.Serialization; using System.Text.RegularExpressions; using System.Transactions; +using CKAN.Versioning; using log4net; using Newtonsoft.Json; @@ -402,7 +403,7 @@ public void RemoveAvailable(CkanModule module) /// /// /// - public List Available(KSPVersion ksp_version) + public List Available(KspVersion ksp_version) { var candidates = new List(available_modules.Keys); var compatible = new List(); @@ -460,7 +461,7 @@ public List Available(KSPVersion ksp_version) /// /// /// - public List Incompatible(KSPVersion ksp_version) + public List Incompatible(KspVersion ksp_version) { var candidates = new List(available_modules.Keys); var incompatible = new List(); @@ -491,7 +492,7 @@ public List Incompatible(KSPVersion ksp_version) // be calling LatestAvailableWithProvides() public CkanModule LatestAvailable( string module, - KSPVersion ksp_version, + KspVersion ksp_version, RelationshipDescriptor relationship_descriptor =null) { log.DebugFormat("Finding latest available for {0}", module); @@ -513,7 +514,7 @@ public CkanModule LatestAvailable( /// /// /// - public List LatestAvailableWithProvides(string module, KSPVersion ksp_version, RelationshipDescriptor relationship_descriptor = null) + public List LatestAvailableWithProvides(string module, KspVersion ksp_version, RelationshipDescriptor relationship_descriptor = null) { // This public interface calculates a cache of modules which // are compatible with the current version of KSP, and then @@ -529,7 +530,7 @@ public List LatestAvailableWithProvides(string module, KSPVersion ks /// the `available_for_current_version` list has been correctly /// calculated. Not for direct public consumption. ;) /// - private List LatestAvailableWithProvides(string module, KSPVersion ksp_version, + private List LatestAvailableWithProvides(string module, KspVersion ksp_version, IEnumerable available_for_current_version, RelationshipDescriptor relationship_descriptor=null) { log.DebugFormat("Finding latest available with provides for {0}", module); diff --git a/Core/Relationships/RelationshipResolver.cs b/Core/Relationships/RelationshipResolver.cs index 67db1da237..765d969f8c 100644 --- a/Core/Relationships/RelationshipResolver.cs +++ b/Core/Relationships/RelationshipResolver.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using CKAN.Versioning; using log4net; namespace CKAN @@ -88,7 +89,7 @@ public class RelationshipResolver new Dictionary(new NameComparer()); private readonly IRegistryQuerier registry; - private readonly KSPVersion kspversion; + private readonly KspVersion kspversion; private readonly RelationshipResolverOptions options; private readonly HashSet installed_modules; @@ -98,7 +99,7 @@ public class RelationshipResolver /// /// The registry to use /// The version of the install that the registry corresponds to - public RelationshipResolver(RelationshipResolverOptions options, IRegistryQuerier registry, KSPVersion kspversion) + public RelationshipResolver(RelationshipResolverOptions options, IRegistryQuerier registry, KspVersion kspversion) { this.registry = registry; this.kspversion = kspversion; @@ -120,7 +121,7 @@ public RelationshipResolver(RelationshipResolverOptions options, IRegistryQuerie /// /// public RelationshipResolver(IEnumerable module_names, RelationshipResolverOptions options, IRegistryQuerier registry, - KSPVersion kspversion) : + KspVersion kspversion) : this(module_names.Select(name => CkanModule.FromIDandVersion(registry, name, kspversion)).ToList(), options, registry, @@ -133,7 +134,7 @@ public RelationshipResolver(IEnumerable module_names, RelationshipResolv /// Creates a new resolver that will find a way to install all the modules specified. /// public RelationshipResolver(IEnumerable modules, RelationshipResolverOptions options, IRegistryQuerier registry, - KSPVersion kspversion):this(options,registry,kspversion) + KspVersion kspversion):this(options,registry,kspversion) { AddModulesToInstall(modules); } diff --git a/Core/ServiceLocator.cs b/Core/ServiceLocator.cs index 31a259b05a..ab44d1dc6e 100644 --- a/Core/ServiceLocator.cs +++ b/Core/ServiceLocator.cs @@ -1,4 +1,5 @@ using Autofac; +using CKAN.GameVersionProviders; namespace CKAN { @@ -32,7 +33,23 @@ private static void Init() { var builder = new ContainerBuilder(); - builder.RegisterType().As(); + builder.RegisterType() + .As(); + + builder.RegisterType() + .As(); + + builder.RegisterType() + .As() + .SingleInstance(); // Since it stores cached data we want to keep it around + + builder.RegisterType() + .As() + .Keyed(KspVersionSource.BuildId); + + builder.RegisterType() + .As() + .Keyed(KspVersionSource.Readme); _container = builder.Build(); } diff --git a/Core/Types/CkanModule.cs b/Core/Types/CkanModule.cs index 16278ec067..d07283aa49 100644 --- a/Core/Types/CkanModule.cs +++ b/Core/Types/CkanModule.cs @@ -8,6 +8,7 @@ using Newtonsoft.Json; using System.Transactions; using Autofac; +using CKAN.Versioning; namespace CKAN { @@ -159,13 +160,13 @@ public class CkanModule : IEquatable public string identifier; [JsonProperty("ksp_version")] - public KSPVersion ksp_version; + public KspVersion ksp_version; [JsonProperty("ksp_version_max")] - public KSPVersion ksp_version_max; + public KspVersion ksp_version_max; [JsonProperty("ksp_version_min")] - public KSPVersion ksp_version_min; + public KspVersion ksp_version_min; [JsonProperty("ksp_version_strict")] public bool ksp_version_strict = false; @@ -339,31 +340,8 @@ private void DeSerialisationFixes(StreamingContext like_i_could_care) // Make sure our version fields are populated. // TODO: There's got to be a better way of doing this, right? - if (ksp_version_min == null) - { - ksp_version_min = new KSPVersion(null); - } - else - { - ksp_version_min = ksp_version_min.ToLongMin(); - } - - if (ksp_version_max == null) - { - ksp_version_max = new KSPVersion(null); - } - else - { - ksp_version_max = ksp_version_max.ToLongMax(); - } - - if (ksp_version == null) - { - ksp_version = new KSPVersion(null); - } - // Now see if we've got version with version min/max. - if (ksp_version.IsNotAny() && (ksp_version_max.IsNotAny() || ksp_version_min.IsNotAny())) + if (ksp_version != null && (ksp_version_max != null || ksp_version_min != null)) { // KSP version mixed with min/max. throw new InvalidModuleAttributesException("ksp_version mixed with ksp_version_(min|max)", this); @@ -423,7 +401,7 @@ private static bool validate_json_against_schema(string json) /// Tries to parse an identifier in the format Modname=version /// If the module cannot be found in the registry, throws a ModuleNotFoundKraken. /// - public static CkanModule FromIDandVersion(IRegistryQuerier registry, string mod, KSPVersion ksp_version) + public static CkanModule FromIDandVersion(IRegistryQuerier registry, string mod, KspVersion ksp_version) { CkanModule module; @@ -512,16 +490,17 @@ internal static bool UniConflicts(CkanModule mod1, CkanModule mod2) /// public bool IsCompatibleKSP(string version) { - return IsCompatibleKSP(new KSPVersion(version)); + return IsCompatibleKSP(KspVersion.Parse(version)); } /// /// Returns true if our mod is compatible with the KSP version specified. /// - public bool IsCompatibleKSP(KSPVersion version) + public bool IsCompatibleKSP(KspVersion version) { log.DebugFormat("Testing if {0} is compatible with KSP {1}", this, version); + return _comparator.Compatible(version, this); } @@ -536,17 +515,17 @@ public bool IsCompatibleKSP(KSPVersion version) public string HighestCompatibleKSP() { // Find the highest compatible KSP version - if (!String.IsNullOrEmpty(ksp_version_max.ToString())) + if (ksp_version_max != null) { - return ksp_version_max.ToLongMax().ToString(); + return ksp_version_max.ToString(); } - else if (!String.IsNullOrEmpty(ksp_version.ToString())) + else if (ksp_version != null) { - return ksp_version.ToLongMax().ToString(); + return ksp_version.ToString(); } - else if (!String.IsNullOrEmpty(ksp_version_min.ToString())) + else if (ksp_version_min != null ) { - return ksp_version_min.ToLongMin().ToString() + "+"; + return ksp_version_min + "+"; } return "All versions"; diff --git a/Core/Types/GameComparator/GrasGameComparator.cs b/Core/Types/GameComparator/GrasGameComparator.cs index 7ca2f4437b..bfb2518655 100644 --- a/Core/Types/GameComparator/GrasGameComparator.cs +++ b/Core/Types/GameComparator/GrasGameComparator.cs @@ -1,4 +1,4 @@ -using System; +using CKAN.Versioning; namespace CKAN { @@ -10,9 +10,9 @@ namespace CKAN public class GrasGameComparator : IGameComparator { static readonly StrictGameComparator strict = new StrictGameComparator(); - static readonly KSPVersion v103 = new KSPVersion("1.0.3"); + static readonly KspVersion v103 = KspVersion.Parse("1.0.3"); - public bool Compatible(KSPVersion gameVersion, CkanModule module) + public bool Compatible(KspVersion gameVersion, CkanModule module) { // If it's strictly compatible, then it's compatible. if (strict.Compatible(gameVersion, module)) @@ -27,11 +27,10 @@ public bool Compatible(KSPVersion gameVersion, CkanModule module) // If we're running KSP 1.0.4, then allow the mod to run if we would have // considered it compatible under 1.0.3 (as 1.0.4 was "just a hotfix"). - if (gameVersion.Equals("1.0.4")) + if (gameVersion.Equals(KspVersion.Parse("1.0.4"))) return strict.Compatible(v103, module); return false; } } } - diff --git a/Core/Types/GameComparator/IGameComparator.cs b/Core/Types/GameComparator/IGameComparator.cs index e0559ba1e7..79e660b71f 100644 --- a/Core/Types/GameComparator/IGameComparator.cs +++ b/Core/Types/GameComparator/IGameComparator.cs @@ -1,4 +1,4 @@ -using System; +using CKAN.Versioning; namespace CKAN { @@ -11,7 +11,7 @@ public interface IGameComparator /// Returns true if the given module is compatible with the supplied /// gameVersion, false otherwise. /// - bool Compatible(KSPVersion gameVersion, CkanModule module); + bool Compatible(KspVersion gameVersion, CkanModule module); } } diff --git a/Core/Types/GameComparator/StrictGameComparator.cs b/Core/Types/GameComparator/StrictGameComparator.cs index dace6381c0..0049d8195f 100644 --- a/Core/Types/GameComparator/StrictGameComparator.cs +++ b/Core/Types/GameComparator/StrictGameComparator.cs @@ -1,4 +1,4 @@ -using System; +using CKAN.Versioning; namespace CKAN { @@ -8,33 +8,51 @@ namespace CKAN /// public class StrictGameComparator : IGameComparator { - - public bool Compatible(KSPVersion gameVersion, CkanModule module) + public bool Compatible(KspVersion gameVersion, CkanModule module) { - KSPVersion ksp_version = module.ksp_version; - KSPVersion ksp_version_min = module.ksp_version_min; - KSPVersion ksp_version_max = module.ksp_version_max; + var gameVersionRange = gameVersion.ToVersionRange(); - // Check the min and max versions. + var moduleRange = KspVersionRange.Any; - if (ksp_version_min.IsNotAny() && gameVersion < ksp_version_min) + if (module.ksp_version != null) { - return false; + moduleRange = module.ksp_version.ToVersionRange(); } - - if (ksp_version_max.IsNotAny() && gameVersion > ksp_version_max) + else if (module.ksp_version_min != null || module.ksp_version_max != null) { - return false; + if (module.ksp_version_min != null && module.ksp_version_max != null) + { + if (module.ksp_version_min <= module.ksp_version_max) + { + var minRange = module.ksp_version_min.ToVersionRange(); + var maxRange = module.ksp_version_max.ToVersionRange(); + + moduleRange = new KspVersionRange(minRange.Lower, maxRange.Upper); + } + else + { + return false; + } + } + else if (module.ksp_version_min != null) + { + var minRange = module.ksp_version_min.ToVersionRange(); + + moduleRange = new KspVersionRange(minRange.Lower, KspVersionBound.Unbounded); + } + else if (module.ksp_version_max != null) + { + var maxRange = module.ksp_version_max.ToVersionRange(); + + moduleRange = new KspVersionRange(KspVersionBound.Unbounded, maxRange.Upper); + } + } + else + { + return true; } - // We didn't hit the min/max guards. They may not have existed. - - // Note that since ksp_version is "any" if not specified, this - // will work fine if there's no target, or if there were min/max - // fields and we passed them successfully. - - return ksp_version.Targets(gameVersion); + return moduleRange.IsSupersetOf(gameVersionRange); } } } - diff --git a/Core/Types/GameComparator/YoyoGameComparator.cs b/Core/Types/GameComparator/YoyoGameComparator.cs index c0a47b39f0..8824cd43fb 100644 --- a/Core/Types/GameComparator/YoyoGameComparator.cs +++ b/Core/Types/GameComparator/YoyoGameComparator.cs @@ -1,4 +1,4 @@ -using System; +using CKAN.Versioning; namespace CKAN { @@ -8,7 +8,7 @@ namespace CKAN /// public class YoyoGameComparator : IGameComparator { - public bool Compatible(KSPVersion gameVersion, CkanModule module) + public bool Compatible(KspVersion gameVersion, CkanModule module) { return true; } diff --git a/Core/Types/KSPVersion.cs b/Core/Types/KSPVersion.cs deleted file mode 100644 index 7d2d5f5679..0000000000 --- a/Core/Types/KSPVersion.cs +++ /dev/null @@ -1,310 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; -using Newtonsoft.Json; - -namespace CKAN -{ - [JsonConverter(typeof (JsonSimpleStringConverter))] - public class KSPVersion : IComparable - { - private readonly string version; - private Version cached_version_object; - private readonly bool is_short; - - private static readonly Dictionary> NormalCache = - new Dictionary>(); - - public KSPVersion(string v) - { - Tuple normalized; - - if (v != null && NormalCache.TryGetValue(v, out normalized)) - { - version = normalized.Item1; - is_short = version != null && normalized.Item2; - } - else - { - version = Normalise(v); - version = AnyToNull(version); - is_short = version != null && Regex.IsMatch(version, @"^\d+\.\d+$"); - Validate(); // Throws on error. - if (v != null) - { - NormalCache.Add(v, new Tuple(version, is_short)); - } - } - } - - // Casting function - public static explicit operator KSPVersion(string v) - { - return new KSPVersion(v); - } - - /// - /// Normalises a version number. Currently this adds - /// a leading zero if it's missing one. - /// - /// V. - private static string Normalise(string v) - { - if (v == null) - { - return null; - } - - if (Regex.IsMatch(v, @"^\.")) - { - return "0" + v; - } - - return v; - } - - /// - /// If our KSP version is already long (x.y.z) returns itself. If our - /// KSP version is short (x.y) returns the minimum full release that - /// would match (x.y.0). - /// - /// It is a mistake to call this method without using its return value. - /// - public KSPVersion ToLongMin() - { - return IsShortVersion() ? new KSPVersion(version + ".0") : this; - } - - /// - /// If our KSP version is already long (x.y.z) returns itself. If our - /// KSP version is short (x.y) returns the maximum full release that - /// would match (x.y.99). - /// - /// It is a mistake to call this method without using its return value. - /// - public KSPVersion ToLongMax() - { - return IsShortVersion() ? new KSPVersion(version + ".99") : this; - } - - /// - /// Returns true if our version object is short (ie: missing a patch number, - /// such as `0.23`). - /// - public bool IsShortVersion() - { - return is_short; - } - - /// - /// Returns true if our version is long (ie: includes a patch number, such - /// as `0.23.5`). - /// - public bool IsLongVersion() - { - return !is_short; - } - - public bool IsAny() - { - return version == null; - } - - public bool IsNotAny() - { - return !IsAny(); - } - - public string Version() - { - return version; - } - - // Private for now, since we can't guarnatee public code will only call - // us with long versions. - private Version VersionObject() - { - return cached_version_object ?? (cached_version_object = new Version(version)); - } - - private static readonly Dictionary, int> CompareCache - = new Dictionary, int>(); - - public int CompareTo(KSPVersion that) - { - int ret; - var tuple = new Tuple(this, that); - if (CompareCache.TryGetValue(tuple, out ret)) - return ret; - - - // We need two long versions to be able to compare properly. - if ((!IsLongVersion()) && (!that.IsLongVersion())) - { - throw new KSPVersionIncomparableException(this, that, "CompareTo"); - } - - // Hooray, we can hook the regular Version code here. - - Version v1 = VersionObject(); - Version v2 = that.VersionObject(); - ret = v1.CompareTo(v2); - CompareCache.Add(tuple, ret); - return ret; - } - - public bool Targets(string target_version) - { - return Targets(new KSPVersion(target_version)); - } - - /// - /// Returns true if this targets that version of KSP. - /// That must be a long (actual) version. - /// Eg: 0.25 targets 0.25.2 - /// - public bool Targets(KSPVersion that) - { - // Cry if we're not looking at a long version to compare to. - if (!that.IsLongVersion()) - { - throw new KSPVersionIncomparableException(this, that, "Targets"); - } - - // If we target any, then yes, it's a match. - if (IsAny()) - { - return true; - } - else if (IsLongVersion()) - { - return CompareTo(that) == 0; - } - - // We've got a short version, so split it into two separate versions, - // and compare each. - - KSPVersion min = ToLongMin(); - - KSPVersion max = ToLongMax(); - - return (that >= min && that <= max); - } - - /// - /// Returns null if passed null, or the string "any", - /// otherwise returns the string passed. - /// - private static string AnyToNull(string v) - { - if (v != null && v == "any") - { - return null; - } - return v; - } - - /// - /// Throws an exception if our version object is not well-formed. - /// - private void Validate() - { - if (version == null || IsShortVersion() || IsLongVersion()) - { - return; - } - throw new BadKSPVersionException(version); - } - - // Why don't I get operator overloads for free? - // Is there a class I can delcare allegiance to that gives me this? - // Where's my ComparableOperators role? - - public static bool operator <(KSPVersion v1, KSPVersion v2) - { - return v1.CompareTo(v2) < 0; - } - - public static bool operator <=(KSPVersion v1, KSPVersion v2) - { - return v1.CompareTo(v2) <= 0; - } - - public static bool operator >(KSPVersion v1, KSPVersion v2) - { - return v1.CompareTo(v2) > 0; - } - - public static bool operator >=(KSPVersion v1, KSPVersion v2) - { - return v1.CompareTo(v2) >= 0; - } - - public override string ToString() - { - return Version(); - } - - protected bool Equals(KSPVersion other) - { - return string.Equals(version, other.version); - } - - /// - /// Compare the version against a string. This only makes sense for complete - /// versions (eg: '1.0.4'). - /// - public bool Equals(string version_string) - { - return string.Equals(version, version_string); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((KSPVersion) obj); - } - - public override int GetHashCode() - { - return (version != null ? version.GetHashCode() : 0); - } - } - - public class BadKSPVersionException : Exception - { - private readonly string version; - - public BadKSPVersionException(string v) - { - version = v; - } - - public override string ToString() - { - return string.Format("[BadKSPVersionException] {0} is not a valid KSP version", version); - } - } - - public class KSPVersionIncomparableException : Exception - { - private readonly string version1; - private readonly string version2; - private readonly string method; - - public KSPVersionIncomparableException(KSPVersion v1, KSPVersion v2, string m) - { - version1 = v1.Version(); - version2 = v2.Version(); - method = m; - } - - public override string ToString() - { - return string.Format("[KSPVersionIncomparableException] {0} and {1} cannot be compared by {2}", version1, - version2, method); - } - } -} diff --git a/Core/Versioning/KspVersion.cs b/Core/Versioning/KspVersion.cs new file mode 100644 index 0000000000..eb8ed9c87a --- /dev/null +++ b/Core/Versioning/KspVersion.cs @@ -0,0 +1,710 @@ +using System; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; + +namespace CKAN.Versioning +{ + /// + /// Represents the version number of a Kerbal Space Program (KSP) installation. + /// + [JsonConverter(typeof(KspVersionJsonConverter))] + public sealed partial class KspVersion + { + private static readonly Regex Pattern = new Regex( + @"^(?\d+)(?:\.(?\d+)(?:\.(?\d+)(?:\.(?\d+))?)?)?$", + RegexOptions.Compiled + ); + + private const int Undefined = -1; + + public static readonly KspVersion Any = new KspVersion(); + + private readonly int _major; + private readonly int _minor; + private readonly int _patch; + private readonly int _build; + + private readonly string _string; + + /// + /// Gets the value of the major component of the version number for the current + /// object. + /// + public int Major { get { return _major; } } + + /// + /// Gets the value of the minor component of the version number for the current + /// object. + /// + public int Minor { get { return _minor; } } + + /// + /// Gets the value of the patch component of the version number for the current + /// object. + /// + public int Patch { get { return _patch; } } + + /// + /// Gets the value of the build component of the version number for the current + /// object. + /// + public int Build { get { return _build; } } + + /// + /// Gets whether or not the major component of the version number for the current + /// object is defined. + /// + public bool IsMajorDefined { get { return _major != Undefined; } } + + /// + /// Gets whether or not the minor component of the version number for the current + /// object is defined. + /// + public bool IsMinorDefined { get { return _minor != Undefined; } } + + /// + /// Gets whether or not the patch component of the version number for the current + /// object is defined. + /// + public bool IsPatchDefined { get { return _patch != Undefined; } } + + /// + /// Gets whether or not the build component of the version number for the current + /// object is defined. + /// + public bool IsBuildDefined { get { return _build != Undefined; } } + + /// + /// Indicates whether or not all components of the current are defined. + /// + public bool IsFullyDefined + { + get { return IsMajorDefined && IsMinorDefined && IsPatchDefined && IsBuildDefined; } + } + + /// + /// Indicates wheter or not all the components of the current are undefined. + /// + public bool IsAny + { + get { return !IsMajorDefined && !IsMinorDefined && !IsPatchDefined && !IsBuildDefined; } + } + + /// + /// Initialize a new instance of the class with all components unspecified. + /// + public KspVersion() + { + _major = Undefined; + _minor = Undefined; + _patch = Undefined; + _build = Undefined; + + _string = DeriveString(_major, _minor, _patch, _build); + } + + /// + /// Initialize a new instance of the class using the specified major value. + /// + /// The major version number. + public KspVersion(int major) + { + if (major < 0) + throw new ArgumentOutOfRangeException("major"); + + _major = major; + _minor = Undefined; + _patch = Undefined; + _build = Undefined; + + _string = DeriveString(_major, _minor, _patch, _build); + } + + /// + /// Initialize a new instance of the class using the specified major and minor + /// values. + /// + /// The major version number. + /// The minor version number. + public KspVersion(int major, int minor) + { + if (major < 0) + throw new ArgumentOutOfRangeException("major"); + + if (minor < 0) + throw new ArgumentOutOfRangeException("minor"); + + _major = major; + _minor = minor; + _patch = Undefined; + _build = Undefined; + + _string = DeriveString(_major, _minor, _patch, _build); + } + + /// + /// Initialize a new instance of the class using the specified major, minor, and + /// patch values. + /// + /// The major version number. + /// The minor version number. + /// The patch version number. + public KspVersion(int major, int minor, int patch) + { + if (major < 0) + throw new ArgumentOutOfRangeException("major"); + + if (minor < 0) + throw new ArgumentOutOfRangeException("minor"); + + if (patch < 0) + throw new ArgumentOutOfRangeException("patch"); + + _major = major; + _minor = minor; + _patch = patch; + _build = Undefined; + + _string = DeriveString(_major, _minor, _patch, _build); + } + + /// + /// Initialize a new instance of the class using the specified major, minor, patch, + /// and build values. + /// + /// The major version number. + /// The minor version number. + /// The patch version number. + /// The build verison number. + public KspVersion(int major, int minor, int patch, int build) + { + if (major < 0) + throw new ArgumentOutOfRangeException("major"); + + if (minor < 0) + throw new ArgumentOutOfRangeException("minor"); + + if (patch < 0) + throw new ArgumentOutOfRangeException("patch"); + + if (build < 0) + throw new ArgumentOutOfRangeException("build"); + + _major = major; + _minor = minor; + _patch = patch; + _build = build; + + _string = DeriveString(_major, _minor, _patch, _build); + } + + /// + /// Converts the value of the current to its equivalent + /// representation. + /// + /// + /// + /// The representation of the values of the major, minor, patch, and build components of + /// the current object as depicted in the following format. Each component is + /// separated by a period character ('.'). Square brackets ('[' and ']') indicate a component that will not + /// appear in the return value if the component is not defined: + /// + /// + /// [major[.minor[.patch[.build]]]] + /// + /// + /// For example, if you create a object using the constructor KspVersion(1,1), + /// the returned string is "1.1". If you create a using the constructor (1,3,4,2), + /// the returned string is "1.3.4.2". + /// + /// + /// If the current is totally undefined the return value will null. + /// + /// + public override string ToString() + { + return _string; + } + + /// + /// Converts the value of the current to its equivalent + /// . + /// + /// + /// + /// A which specifies a set of versions equivalent to the current + /// . + /// + /// + /// For example, the version "1.0.0.0" would be equivalent to the range ["1.0.0.0", "1.0.0.0"], while the + /// version "1.0" would be equivalent to the range ["1.0.0.0", "1.1.0.0"). Where '[' and ']' represent + /// inclusive bounds and '(' and ')' represent exclusive bounds. + /// + /// + public KspVersionRange ToVersionRange() + { + KspVersionBound lower; + KspVersionBound upper; + + if (IsBuildDefined) + { + lower = new KspVersionBound(this, inclusive: true); + upper = new KspVersionBound(this, inclusive: true); + } + else if (IsPatchDefined) + { + lower = new KspVersionBound(new KspVersion(Major, Minor, Patch, 0), inclusive: true); + upper = new KspVersionBound(new KspVersion(Major, Minor, Patch + 1, 0), inclusive: false); + } + else if (IsMinorDefined) + { + lower = new KspVersionBound(new KspVersion(Major, Minor, 0, 0), inclusive: true); + upper = new KspVersionBound(new KspVersion(Major, Minor + 1, 0, 0), inclusive: false); + } + else if (IsMajorDefined) + { + lower = new KspVersionBound(new KspVersion(Major, 0, 0, 0), inclusive: true); + upper = new KspVersionBound(new KspVersion(Major + 1, 0, 0, 0), inclusive: false); + } + else + { + lower = KspVersionBound.Unbounded; + upper = KspVersionBound.Unbounded; + } + + return new KspVersionRange(lower, upper); + } + + /// + /// Converts the string representation of a version number to an equivalent object. + /// + /// A string that contains a version number to convert. + /// + /// A object that is equivalent to the version number specified in the + /// parameter. + /// + public static KspVersion Parse(string input) + { + if (ReferenceEquals(input, null)) + throw new ArgumentNullException("input"); + + KspVersion result; + if (TryParse(input, out result)) + return result; + else + throw new FormatException(); + } + + /// + /// Tries to convert the string representation of a version number to an equivalent + /// object and returns a value that indicates whether the conversion succeeded. + /// + /// + /// A string that contains a version number to convert. + /// + /// + /// When this method returns true, contains the equivalent of the number that + /// is contained in . When this method returns false, the value is unspecified. + /// + /// + /// true if the parameter was converted successfully; otherwise, false. + /// + public static bool TryParse(string input, out KspVersion result) + { + result = null; + + if (ReferenceEquals(input, null)) + return false; + + var major = Undefined; + var minor = Undefined; + var patch = Undefined; + var build = Undefined; + + var match = Pattern.Match(input.Trim()); + + if (match.Success) + { + var majorGroup = match.Groups["major"]; + var minorGroup = match.Groups["minor"]; + var patchGroup = match.Groups["patch"]; + var buildGroup = match.Groups["build"]; + + if (majorGroup.Success) + if (!int.TryParse(majorGroup.Value, out major)) + return false; + + if (minorGroup.Success) + if (!int.TryParse(minorGroup.Value, out minor)) + return false; + + if (patchGroup.Success) + if (!int.TryParse(patchGroup.Value, out patch)) + return false; + + if (buildGroup.Success) + if (!int.TryParse(buildGroup.Value, out build)) + return false; + + if (minor == Undefined) + result = new KspVersion(major); + else if (patch == Undefined) + result = new KspVersion(major, minor); + else if (build == Undefined) + result = new KspVersion(major, minor, patch); + else + result = new KspVersion(major, minor, patch, build); + + return true; + } + else + { + return false; + } + } + } + + public sealed partial class KspVersion : IEquatable + { + /// + /// Returns a value indicating whether the current object and specified + /// object represent the same value. + /// + /// + /// A object to compare to the current object, or + /// null. + /// + /// + /// true if every component of the current matches the corresponding component + /// of the parameter; otherwise, false. + /// + public bool Equals(KspVersion obj) + { + if (ReferenceEquals(obj, null)) return false; + if (ReferenceEquals(obj, this)) return true; + return _major == obj._major && _minor == obj._minor && _patch == obj._patch && _build == obj._build; + } + + /// + /// Returns a value indicating whether the current object is equal to a specified + /// object. + /// + /// + /// An object to compare with the current object, or null. + /// + /// + /// true if the current object and are both + /// objects and every component of the current object + /// matches the corresponding component of ; otherwise, false. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(obj, null)) return false; + if (ReferenceEquals(obj, this)) return true; + return obj is KspVersion && Equals((KspVersion) obj); + } + + /// + /// Returns a hash code for the current object. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + unchecked + { + var hashCode = _major.GetHashCode(); + hashCode = (hashCode*397) ^ _minor.GetHashCode(); + hashCode = (hashCode*397) ^ _patch.GetHashCode(); + hashCode = (hashCode*397) ^ _build.GetHashCode(); + return hashCode; + } + } + + /// + /// Determines whether two specified objects are equal. + /// + /// The first object. + /// The second object. + /// true if equals ; otherwise, false. + public static bool operator ==(KspVersion v1, KspVersion v2) + { + return Equals(v1, v2); + } + + /// + /// Determines whether two specified objects are not equal. + /// + /// The first object. + /// The second object. + /// + /// true if does not equal ; otherwise, false. + /// + public static bool operator !=(KspVersion v1, KspVersion v2) + { + return !Equals(v1, v2); + } + } + + public sealed partial class KspVersion : IComparable, IComparable + { + /// + /// Compares the current object to a specified object and returns an indication of + /// their relative values. + /// + /// An object to compare, or null. + /// + /// A signed integer that indicates the relative values of the two objects, as shown in the following table. + /// + /// + /// Return value + /// Meaning + /// + /// + /// Less than zero + /// + /// The current object is a version before . + /// + /// + /// + /// Zero + /// + /// The current object is the same version as . + /// + /// + /// + /// Greater than zero + /// + /// + /// The current object is a version subsequent to . + /// + /// + /// + /// + /// + public int CompareTo(object obj) + { + if (ReferenceEquals(obj, null)) + throw new ArgumentNullException("obj"); + + var objKspVersion = obj as KspVersion; + + if (objKspVersion != null) + return CompareTo(objKspVersion); + else + throw new ArgumentException("Object must be of type KspVersion."); + } + + /// + /// Compares the current object to a specified object and returns an indication of + /// their relative values. + /// + /// An object to compare. + /// + /// A signed integer that indicates the relative values of the two objects, as shown in the following table. + /// + /// + /// Return value + /// Meaning + /// + /// + /// Less than zero + /// + /// The current object is a version before . + /// + /// + /// + /// Zero + /// + /// The current object is the same version as . + /// + /// + /// + /// Greater than zero + /// + /// + /// The current object is a version subsequent to . + /// + /// + /// + /// + /// + public int CompareTo(KspVersion other) + { + if (ReferenceEquals(other, null)) + throw new ArgumentNullException("other"); + + if (Equals(this, other)) + return 0; + + var majorCompare = _major.CompareTo(other._major); + + if (majorCompare == 0) + { + var minorCompare = _minor.CompareTo(other._minor); + + if (minorCompare == 0) + { + var patchCompare = _patch.CompareTo(other._patch); + + return patchCompare == 0 ? _build.CompareTo(other._build) : patchCompare; + } + else + { + return minorCompare; + } + } + else + { + return majorCompare; + } + } + + /// + /// Determines whether the first specified object is less than the second specified + /// object. + /// + /// The first object. + /// The second object. + /// + /// true if is less than ; otherwise, flase. + /// + public static bool operator <(KspVersion left, KspVersion right) + { + if (ReferenceEquals(left, null)) + throw new ArgumentNullException("left"); + + if (ReferenceEquals(right, null)) + throw new ArgumentNullException("right"); + + return left.CompareTo(right) < 0; + } + + /// + /// Determines whether the first specified object is greater than the second + /// specified object. + /// + /// The first object. + /// The second object. + /// + /// true if is greater than ; otherwise, flase. + /// + public static bool operator >(KspVersion left, KspVersion right) + { + if (ReferenceEquals(left, null)) + throw new ArgumentNullException("left"); + + if (ReferenceEquals(right, null)) + throw new ArgumentNullException("right"); + + return left.CompareTo(right) > 0; + } + + /// + /// Determines whether the first specified object is less than or equal to the second + /// specified object. + /// + /// The first object. + /// The second object. + /// + /// true if is less than or equal to ; otherwise, flase. + /// + public static bool operator <=(KspVersion left, KspVersion right) + { + if (ReferenceEquals(left, null)) + throw new ArgumentNullException("left"); + + if (ReferenceEquals(right, null)) + throw new ArgumentNullException("right"); + + return left.CompareTo(right) <= 0; + } + + /// + /// Determines whether the first specified object is greater than or equal to the + /// second specified object. + /// + /// The first object. + /// The second object. + /// + /// true if is greater than or equal to ; otherwise, flase. + /// + public static bool operator >=(KspVersion left, KspVersion right) + { + if (ReferenceEquals(left, null)) + throw new ArgumentNullException("left"); + + if (ReferenceEquals(right, null)) + throw new ArgumentNullException("right"); + + return left.CompareTo(right) >= 0; + } + } + + public sealed partial class KspVersion + { + private static string DeriveString(int major, int minor, int patch, int build) + { + var sb = new StringBuilder(); + + if (major != Undefined) + { + sb.Append(major); + } + + if (minor != Undefined) + { + sb.Append("."); + sb.Append(minor); + } + + if (patch != Undefined) + { + sb.Append("."); + sb.Append(patch); + } + + if (build != Undefined) + { + sb.Append("."); + sb.Append(build); + } + + var s = sb.ToString(); + + return s.Equals(string.Empty) ? null : s; + } + } + + public sealed class KspVersionJsonConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteValue(value.ToString()); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var value = reader.Value == null ? null : reader.Value.ToString(); + + switch (value) + { + case null: + return null; + case "any": + return KspVersion.Any; + default: + KspVersion result; + if (KspVersion.TryParse(value, out result)) + return result; + else + throw new JsonException(string.Format("Could not parse KSP version: {0}", value)); + } + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(KspVersion); + } + } +} diff --git a/Core/Versioning/KspVersionBound.cs b/Core/Versioning/KspVersionBound.cs new file mode 100644 index 0000000000..a2516df490 --- /dev/null +++ b/Core/Versioning/KspVersionBound.cs @@ -0,0 +1,68 @@ +using System; + +namespace CKAN.Versioning +{ + public sealed partial class KspVersionBound + { + public static readonly KspVersionBound Unbounded = new KspVersionBound(); + + public KspVersion Value { get; private set; } + public bool Inclusive { get; private set; } + + private readonly string _string; + + public KspVersionBound() + : this(KspVersion.Any, true) { } + + public KspVersionBound(KspVersion value, bool inclusive) + { + if (ReferenceEquals(value, null)) + throw new ArgumentNullException("value"); + + Value = value; + Inclusive = inclusive; + + _string = inclusive ? string.Format("[{0}]", value) : string.Format("({0})", value); + } + + public override string ToString() + { + return _string; + } + } + + public sealed partial class KspVersionBound : IEquatable + { + public bool Equals(KspVersionBound other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(Value, other.Value) && Inclusive == other.Inclusive; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return obj is KspVersionBound && Equals((KspVersionBound) obj); + } + + public override int GetHashCode() + { + unchecked + { + return ((Value != null ? Value.GetHashCode() : 0)*397) ^ Inclusive.GetHashCode(); + } + } + + public static bool operator ==(KspVersionBound left, KspVersionBound right) + { + return Equals(left, right); + } + + public static bool operator !=(KspVersionBound left, KspVersionBound right) + { + return !Equals(left, right); + } + } +} diff --git a/Core/Versioning/KspVersionRange.cs b/Core/Versioning/KspVersionRange.cs new file mode 100644 index 0000000000..e69609d393 --- /dev/null +++ b/Core/Versioning/KspVersionRange.cs @@ -0,0 +1,105 @@ +using System; +using System.Text; + +namespace CKAN.Versioning +{ + public sealed partial class KspVersionRange + { + private readonly string _string; + + public static readonly KspVersionRange Any = + new KspVersionRange(KspVersionBound.Unbounded, KspVersionBound.Unbounded); + + public KspVersionBound Lower { get; private set; } + public KspVersionBound Upper { get; private set; } + + public KspVersionRange(KspVersionBound lower, KspVersionBound upper) + { + if (ReferenceEquals(lower, null)) + throw new ArgumentNullException("lower"); + + if (ReferenceEquals(upper, null)) + throw new ArgumentNullException("upper"); + + Lower = lower; + Upper = upper; + + _string = DeriveString(this); + } + + public override string ToString() + { + return _string; + } + + public bool IsSupersetOf(KspVersionRange other) + { + if (ReferenceEquals(other, null)) + throw new ArgumentNullException("other"); + + var lowerIsOkay = Lower.Value.IsAny + || (Lower.Value < other.Lower.Value) + || (Lower.Value == other.Lower.Value && (Lower.Inclusive || !other.Lower.Inclusive)); + + var upperIsOkay = Upper.Value.IsAny + || (other.Upper.Value < Upper.Value) + || (other.Upper.Value == Upper.Value && (Upper.Inclusive || !other.Upper.Inclusive)); + + return lowerIsOkay && upperIsOkay; + } + + private static string DeriveString(KspVersionRange versionRange) + { + var sb = new StringBuilder(); + + sb.Append(versionRange.Lower.Inclusive ? '[' : '('); + + if (versionRange.Lower.Value != null) + sb.Append(versionRange.Lower.Value); + + sb.Append(','); + + if (versionRange.Upper.Value != null) + sb.Append(versionRange.Upper.Value); + + sb.Append(versionRange.Upper.Inclusive ? ']' : ')'); + + return sb.ToString(); + } + } + + public sealed partial class KspVersionRange : IEquatable + { + public bool Equals(KspVersionRange other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(Lower, other.Lower) && Equals(Upper, other.Upper); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return obj is KspVersionRange && Equals((KspVersionRange) obj); + } + + public override int GetHashCode() + { + unchecked + { + return ((Lower != null ? Lower.GetHashCode() : 0)*397) ^ (Upper != null ? Upper.GetHashCode() : 0); + } + } + + public static bool operator ==(KspVersionRange left, KspVersionRange right) + { + return Equals(left, right); + } + + public static bool operator !=(KspVersionRange left, KspVersionRange right) + { + return !Equals(left, right); + } + } +} diff --git a/Core/Win32Registry.cs b/Core/Win32Registry.cs index f08bd8560b..6355597a39 100644 --- a/Core/Win32Registry.cs +++ b/Core/Win32Registry.cs @@ -10,6 +10,8 @@ public interface IWin32Registry string AutoStartInstance { get; set; } void SetRegistryToInstances(SortedList instances, string auto_start_instance); IEnumerable> GetInstances(); + string GetKSPBuilds(); + void SetKSPBuilds(string buildMap); } public class Win32Registry : IWin32Registry @@ -47,8 +49,6 @@ public void SetRegistryToInstances(SortedList instances, string aut { SetInstanceKeysTo(instance.number, instance.name, instance.path); } - - } public IEnumerable> GetInstances() @@ -56,6 +56,16 @@ public IEnumerable> GetInstances() return Enumerable.Range(0, InstanceCount).Select(GetInstance).ToList(); } + public string GetKSPBuilds() + { + return GetRegistryValue(@"KSPBuilds", (string)null); + } + + public void SetKSPBuilds(string buildMap) + { + SetRegistryValue(@"KSPBuilds", buildMap); + } + private void ConstructKey() { var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\CKAN"); diff --git a/Core/builds.json b/Core/builds.json new file mode 100644 index 0000000000..b30c2ddf78 --- /dev/null +++ b/Core/builds.json @@ -0,0 +1,22 @@ +{ + "builds": { + "464": "0.23.5.464", + "559": "0.24.2.559", + "642": "0.25.0.642", + "705": "0.90.0.705", + "830": "1.0.0.830", + "842": "1.0.2.842", + "861": "1.0.4.861", + "1024": "1.0.5.1024", + "1028": "1.0.5.1028", + "1172": "1.1.0.1172", + "1174": "1.1.0.1174", + "1183": "1.1.0.1183", + "1196": "1.1.0.1196", + "1203": "1.1.0.1203", + "1228": "1.1.0.1228", + "1230": "1.1.0.1230", + "1250": "1.1.1.1250", + "1260": "1.1.2.1260" + } +} diff --git a/GUI/GUIMod.cs b/GUI/GUIMod.cs index 41901b86d1..b5ce33b6c5 100644 --- a/GUI/GUIMod.cs +++ b/GUI/GUIMod.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Windows.Forms; using System.Linq; +using CKAN.Versioning; namespace CKAN { @@ -45,7 +46,7 @@ public string Version get { return IsInstalled ? InstalledVersion : LatestVersion; } } - public GUIMod(CkanModule mod, IRegistryQuerier registry, KSPVersion current_ksp_version) + public GUIMod(CkanModule mod, IRegistryQuerier registry, KspVersion current_ksp_version) { IsCKAN = mod is CkanModule; //Currently anything which could alter these causes a full reload of the modlist diff --git a/GUI/MainModList.cs b/GUI/MainModList.cs index b9fe7c8f12..c753a3ec36 100644 --- a/GUI/MainModList.cs +++ b/GUI/MainModList.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using System.Reflection; using System.Windows.Forms; +using CKAN.Versioning; namespace CKAN { @@ -144,7 +145,7 @@ private void _UpdateModsList(bool repo_updated) { log.Debug("Updating the mod list"); - KSPVersion version = CurrentInstance.Version(); + KspVersion version = CurrentInstance.Version(); IRegistryQuerier registry = RegistryManager.Instance(CurrentInstance).registry; var gui_mods = new HashSet(registry.Available(version) .Select(m => new GUIMod(m, registry, version))); @@ -361,7 +362,7 @@ public string ModDescriptionFilter /// The version of the current KSP install public async Task> ComputeChangeSetFromModList( IRegistryQuerier registry, HashSet changeSet, ModuleInstaller installer, - KSPVersion version) + KspVersion version) { var modules_to_install = new HashSet(); var modules_to_remove = new HashSet(); @@ -600,7 +601,7 @@ private bool IsModInFilter(GUIMod m) public static Dictionary ComputeConflictsFromModList(IRegistryQuerier registry, - IEnumerable change_set, KSPVersion ksp_version) + IEnumerable change_set, KspVersion ksp_version) { var modules_to_install = new HashSet(); var modules_to_remove = new HashSet(); diff --git a/Netkan/Sources/Avc/AvcVersion.cs b/Netkan/Sources/Avc/AvcVersion.cs index 62f4642d22..9acbb8d3e7 100644 --- a/Netkan/Sources/Avc/AvcVersion.cs +++ b/Netkan/Sources/Avc/AvcVersion.cs @@ -1,3 +1,4 @@ +using CKAN.Versioning; using Newtonsoft.Json; namespace CKAN.NetKAN.Sources.Avc @@ -13,12 +14,12 @@ public class AvcVersion public Version version; [JsonConverter(typeof (JsonAvcToKspVersion))] - public KSPVersion ksp_version; + public KspVersion ksp_version; [JsonConverter(typeof (JsonAvcToKspVersion))] - public KSPVersion ksp_version_min; + public KspVersion ksp_version_min; [JsonConverter(typeof (JsonAvcToKspVersion))] - public KSPVersion ksp_version_max; + public KspVersion ksp_version_max; } } diff --git a/Netkan/Sources/Avc/JsonAvcToKspVersion.cs b/Netkan/Sources/Avc/JsonAvcToKspVersion.cs index e4f68f44f3..b7b4178e15 100644 --- a/Netkan/Sources/Avc/JsonAvcToKspVersion.cs +++ b/Netkan/Sources/Avc/JsonAvcToKspVersion.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using CKAN.Versioning; using log4net; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -63,7 +64,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist string version; if (int.TryParse(major, out integer) && integer == AvcWildcard) { - version = null; + return KspVersion.Any; } else if (int.TryParse(minor, out integer) && integer == AvcWildcard) { @@ -79,7 +80,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist } Log.DebugFormat(" extracted version: {0}", version); - var result = new KSPVersion(version); + var result = KspVersion.Parse(version); Log.DebugFormat(" generated result: {0}", result); return result; } diff --git a/Netkan/Sources/Spacedock/SDVersion.cs b/Netkan/Sources/Spacedock/SDVersion.cs index ccbe8d7a57..cdab7f18c9 100644 --- a/Netkan/Sources/Spacedock/SDVersion.cs +++ b/Netkan/Sources/Spacedock/SDVersion.cs @@ -1,5 +1,6 @@ using System; using System.Text.RegularExpressions; +using CKAN.Versioning; using log4net; using Newtonsoft.Json; @@ -13,7 +14,7 @@ public class SDVersion [JsonConverter(typeof(JsonConvertKSPVersion))] [JsonProperty("game_version")] - public KSPVersion KSP_version; + public KspVersion KSP_version; public string changelog; @@ -51,7 +52,7 @@ public override object ReadJson( string raw_version = reader.Value.ToString(); - return new KSPVersion( ExpandVersionIfNeeded(raw_version) ); + return KspVersion.Parse(ExpandVersionIfNeeded(raw_version)); } /// diff --git a/Netkan/Transformers/AvcTransformer.cs b/Netkan/Transformers/AvcTransformer.cs index 4502b5f590..d0f3da555d 100644 --- a/Netkan/Transformers/AvcTransformer.cs +++ b/Netkan/Transformers/AvcTransformer.cs @@ -5,6 +5,7 @@ using CKAN.NetKAN.Model; using CKAN.NetKAN.Services; using CKAN.NetKAN.Sources.Avc; +using CKAN.Versioning; using log4net; using Newtonsoft.Json; @@ -87,8 +88,8 @@ public Metadata Transform(Metadata metadata) var existingKspMinStr = (string)json["ksp_version_min"] ?? (string)json["ksp_version"]; var existingKspMaxStr = (string)json["ksp_version_max"] ?? (string)json["ksp_version"]; - var existingKspMin = existingKspMinStr == null ? null : new KSPVersion(existingKspMinStr); - var existingKspMax = existingKspMaxStr == null ? null : new KSPVersion(existingKspMaxStr); + var existingKspMin = existingKspMinStr == null ? null : KspVersion.Parse(existingKspMinStr); + var existingKspMax = existingKspMaxStr == null ? null : KspVersion.Parse(existingKspMaxStr); // Get the minimum and maximum KSP versions that are in the AVC file. // Use specific KSP version if min/max don't exist. @@ -97,8 +98,8 @@ public Metadata Transform(Metadata metadata) // Now calculate the minimum and maximum KSP versions between both the existing metadata and the // AVC file. - var kspMins = new List(); - var kspMaxes = new List(); + var kspMins = new List(); + var kspMaxes = new List(); if (existingKspMin != null) kspMins.Add(existingKspMin); diff --git a/Tests/Core/KSPManager.cs b/Tests/Core/KSPManager.cs index 0ee3ec463b..66d473ffc6 100644 --- a/Tests/Core/KSPManager.cs +++ b/Tests/Core/KSPManager.cs @@ -176,6 +176,7 @@ public FakeWin32Registry(List> instances, string auto_star } public List> Instances { get; set; } + public string BuildMap { get; set; } public int InstanceCount { @@ -209,5 +210,15 @@ public IEnumerable> GetInstances() { return Instances; } + + public string GetKSPBuilds() + { + return BuildMap; + } + + public void SetKSPBuilds(string buildMap) + { + BuildMap = buildMap; + } } } \ No newline at end of file diff --git a/Tests/Core/Net/Repo.cs b/Tests/Core/Net/Repo.cs index 1edab7ce63..b2185d7ac5 100644 --- a/Tests/Core/Net/Repo.cs +++ b/Tests/Core/Net/Repo.cs @@ -1,4 +1,5 @@ using CKAN; +using CKAN.Versioning; using NUnit.Framework; using Tests.Data; @@ -33,7 +34,7 @@ public void UpdateRegistryTarGz() CKAN.Repo.UpdateRegistry(TestData.TestKANTarGz(), registry, ksp.KSP, new NullUser()); // Test we've got an expected module. - CkanModule far = registry.LatestAvailable("FerramAerospaceResearch", new KSPVersion("0.25.0")); + CkanModule far = registry.LatestAvailable("FerramAerospaceResearch", KspVersion.Parse("0.25.0")); Assert.AreEqual("v0.14.3.2", far.version.ToString()); } @@ -44,7 +45,7 @@ public void UpdateRegistryZip() CKAN.Repo.UpdateRegistry(TestData.TestKANZip(), registry, ksp.KSP, new NullUser()); // Test we've got an expected module. - CkanModule far = registry.LatestAvailable("FerramAerospaceResearch", new KSPVersion("0.25.0")); + CkanModule far = registry.LatestAvailable("FerramAerospaceResearch", KspVersion.Parse("0.25.0")); Assert.AreEqual("v0.14.3.2", far.version.ToString()); } diff --git a/Tests/Core/Registry/Registry.cs b/Tests/Core/Registry/Registry.cs index d7499d388f..68a5e02e83 100644 --- a/Tests/Core/Registry/Registry.cs +++ b/Tests/Core/Registry/Registry.cs @@ -1,5 +1,6 @@ using System.Transactions; using CKAN; +using CKAN.Versioning; using NUnit.Framework; using Tests.Data; @@ -10,8 +11,8 @@ public class Registry { private static readonly CkanModule module = TestData.kOS_014_module(); private static readonly string identifier = module.identifier; - private static readonly KSPVersion v0_24_2 = new KSPVersion("0.24.2"); - private static readonly KSPVersion v0_25_0 = new KSPVersion("0.25.0"); + private static readonly KspVersion v0_24_2 = KspVersion.Parse("0.24.2"); + private static readonly KspVersion v0_25_0 = KspVersion.Parse("0.25.0"); private CKAN.Registry registry; diff --git a/Tests/Core/Relationships/RelationshipResolver.cs b/Tests/Core/Relationships/RelationshipResolver.cs index 3fbac788a8..4db2578198 100644 --- a/Tests/Core/Relationships/RelationshipResolver.cs +++ b/Tests/Core/Relationships/RelationshipResolver.cs @@ -5,6 +5,9 @@ using NUnit.Framework; using Tests.Data; using System.IO; +using CKAN.Versioning; +using Tests.Core.Types; +using RelationshipDescriptor = CKAN.RelationshipDescriptor; using Version = CKAN.Version; namespace Tests.Core.Relationships @@ -694,7 +697,7 @@ public void Constructor_WithRegistryThatHasRequiredModuleRemoved_Throws() { var list = new List(); var mod = generator.GeneratorRandomModule(); - mod.ksp_version = new KSPVersion("0.10"); + mod.ksp_version = KspVersion.Parse("0.10"); list.Add(mod.identifier); registry.AddAvailable(mod); registry.RemoveAvailable(mod); @@ -803,7 +806,7 @@ public void AutodetectedCanSatisfyRelationships() new CKAN.CkanModule[] { mod }, RelationshipResolver.DefaultOpts(), registry, - new KSPVersion("1.0.0") + KspVersion.Parse("1.0.0") ); } } diff --git a/Tests/Core/Types/GameComparator.cs b/Tests/Core/Types/GameComparator.cs index ffbda431a8..aa953e4734 100644 --- a/Tests/Core/Types/GameComparator.cs +++ b/Tests/Core/Types/GameComparator.cs @@ -1,4 +1,5 @@ using System; +using CKAN.Versioning; using NUnit.Framework; using Tests.Data; using log4net; @@ -8,7 +9,7 @@ namespace Tests.Core.Types [TestFixture] public class GameComparator { - static readonly CKAN.KSPVersion gameVersion = new CKAN.KSPVersion("1.0.4"); + static readonly KspVersion gameVersion = KspVersion.Parse("1.0.4"); CKAN.CkanModule gameMod; [SetUp] @@ -28,7 +29,7 @@ public void TotallyCompatible(Type type, bool expected) // Mark the mod as being for 1.0.4 gameMod.ksp_version = gameMod.ksp_version_min = gameMod.ksp_version_max - = new CKAN.KSPVersion("1.0.4"); + = KspVersion.Parse("1.0.4"); // Now test! Assert.AreEqual(expected, comparator.Compatible(gameVersion, gameMod)); @@ -44,7 +45,7 @@ public void GenerallySafeLax(Type type, bool expected) // We're going to tweak compatibly to mark the mod as being for 1.0.3 gameMod.ksp_version = gameMod.ksp_version_min = gameMod.ksp_version_max - = new CKAN.KSPVersion("1.0.3"); + = KspVersion.Parse("1.0.3"); // Now test! Assert.AreEqual(expected, comparator.Compatible(gameVersion, gameMod)); @@ -60,7 +61,7 @@ public void GenerallySafeStrict(Type type, bool expected) // We're going to tweak compatibly to mark the mod as being for 1.0.3 ONLY gameMod.ksp_version = gameMod.ksp_version_min = gameMod.ksp_version_max - = new CKAN.KSPVersion("1.0.3"); + = KspVersion.Parse("1.0.3"); gameMod.ksp_version_strict = true; diff --git a/Tests/Core/Types/KSPVersion.cs b/Tests/Core/Types/KSPVersion.cs deleted file mode 100644 index eeaa4a2b40..0000000000 --- a/Tests/Core/Types/KSPVersion.cs +++ /dev/null @@ -1,116 +0,0 @@ -using NUnit.Framework; - -namespace Tests.Core.Types -{ - [TestFixture] - public class KSPVersion - { - private static void BadTarget() - { - var any = new CKAN.KSPVersion(null); - var vshort = new CKAN.KSPVersion("0.23"); - - // We can't ask if something targets a non-real (short) version. - any.Targets(vshort); - } - - [Test] - public void MinMax() - { - var min = new CKAN.KSPVersion("0.23"); - var max = new CKAN.KSPVersion("0.23"); - - min = min.ToLongMin(); - max = max.ToLongMax(); - - Assert.IsTrue(min.Version() == "0.23.0"); - Assert.IsTrue(max.Version() == "0.23.99"); // Ugh, magic version number. - - Assert.IsTrue(min < max); - Assert.IsTrue(max > min); - } - - [Test] - public void Strings() - { - var any = new CKAN.KSPVersion(null); - var vshort = new CKAN.KSPVersion("0.23"); - var vlong = new CKAN.KSPVersion("0.23.5"); - - Assert.AreEqual(any.ToString(), null); - Assert.AreEqual(vshort.ToString(), "0.23"); - Assert.AreEqual(vlong.ToString(), "0.23.5"); - } - - [Test] - public void Targets() - { - var any = new CKAN.KSPVersion(null); - var vshort = new CKAN.KSPVersion("0.23"); - var vlong = new CKAN.KSPVersion("0.23.5"); - - Assert.IsTrue(any.Targets(vlong)); - Assert.IsTrue(vshort.Targets(vlong)); - Assert.IsTrue(vlong.Targets(vlong)); - - Assert.That(BadTarget, Throws.Exception); - } - - [Test] - public void TargetsString() - { - var any = new CKAN.KSPVersion(null); - var vshort = new CKAN.KSPVersion("0.23"); - var vlong = new CKAN.KSPVersion("0.23.5"); - var slong = "0.23.5"; - - // Same tests as above, but now testing against a string. :) - Assert.IsTrue(any.Targets(slong)); - Assert.IsTrue(vshort.Targets(slong)); - Assert.IsTrue(vlong.Targets(slong)); - - // And lets test some things that should fail. - var too_big = new CKAN.KSPVersion("1.0.4"); - - Assert.IsFalse(vshort.Targets(too_big)); - Assert.IsFalse(vlong.Targets(too_big)); - } - - [Test] - public void Types() - { - var v1 = new CKAN.KSPVersion("any"); - Assert.IsTrue(v1.IsAny()); - Assert.IsFalse(v1.IsNotAny()); - - var v2 = new CKAN.KSPVersion(null); // Same as "any" - Assert.IsTrue(v2.IsAny()); - Assert.IsFalse(v2.IsNotAny()); - - var vshort = new CKAN.KSPVersion("0.25"); - Assert.IsTrue(vshort.IsShortVersion()); - Assert.IsFalse(vshort.IsLongVersion()); - Assert.IsFalse(vshort.IsAny()); - - var vlong = new CKAN.KSPVersion("0.25.2"); - Assert.IsTrue(vlong.IsLongVersion()); - Assert.IsFalse(vlong.IsShortVersion()); - Assert.IsFalse(vlong.IsAny()); - } - - [Test] - public void MissingZeros() - { - var v1 = new CKAN.KSPVersion(".23.5"); - Assert.IsInstanceOf(v1); - Assert.AreEqual("0.23.5", v1.Version()); - - var v0_23_5 = new CKAN.KSPVersion("0.23.5"); - Assert.IsTrue(v1.Targets(v0_23_5)); - - // For when Squad makes what they think is a full release. ;) - var v1_0_0 = new CKAN.KSPVersion("1.0.0"); - Assert.IsTrue(v1_0_0 > v0_23_5); - } - } -} \ No newline at end of file diff --git a/Tests/Core/Versioning/KspVersionBoundTests.cs b/Tests/Core/Versioning/KspVersionBoundTests.cs new file mode 100644 index 0000000000..8dd7a8249f --- /dev/null +++ b/Tests/Core/Versioning/KspVersionBoundTests.cs @@ -0,0 +1,150 @@ +using CKAN.Versioning; +using NUnit.Framework; + +namespace Tests.Core.Versioning +{ + [TestFixture] + public sealed class KspVersionBoundTests + { + private static readonly object[] EqualityCases = + { + new object[] + { + new KspVersionBound(), + new KspVersionBound(), + true + }, + new object[] + { + new KspVersionBound(new KspVersion(1), false), + new KspVersionBound(new KspVersion(1), false), + true + }, + new object[] + { + new KspVersionBound(new KspVersion(1), false), + new KspVersionBound(new KspVersion(1), true), + false + }, + new object[] + { + new KspVersionBound(new KspVersion(1), false), + new KspVersionBound(new KspVersion(1, 2), false), + false + }, + new object[] + { + new KspVersionBound(new KspVersion(), false), + new KspVersionBound(new KspVersion(), true) + } + }; + + [Test] + public void ParameterlessCtorWorksCorrectly() + { + // Act + var result = new KspVersionBound(); + + // Assert + Assert.AreEqual(KspVersion.Any, result.Value); + Assert.IsTrue(result.Inclusive); + } + + [Test] + public void ParameterfulCtorWorksCorrectly() + { + // Act + var result = new KspVersionBound(new KspVersion(1, 2, 3, 4), true); + + // Assert + Assert.AreEqual(new KspVersion(1, 2, 3, 4), result.Value); + Assert.IsTrue(result.Inclusive); + } + + [Test] + public void ParameterfulCtorThrowsOnInvalidInput() + { + // Act + // ReSharper disable once ObjectCreationAsStatement + TestDelegate act = () => new KspVersionBound(null, false); + + // Assert + Assert.That(act, Throws.ArgumentException); + } + + [TestCaseSource("EqualityCases")] + public void EqualityWorksCorrectly(KspVersionBound vb1, KspVersionBound vb2, bool areEqual) + { + // Act + var genericEquals = vb1.Equals(vb2); + var nonGenericEquals = vb1.Equals((object)vb2); + var equalsOperator = vb1 == vb2; + var notEqualsOperator = vb1 != vb2; + var reverseEqualsOperator = vb2 == vb1; + var reverseNotEqualsOperator = vb2 != vb1; + + // Assert + Assert.AreEqual(areEqual, genericEquals); + Assert.AreEqual(areEqual, nonGenericEquals); + Assert.AreEqual(areEqual, equalsOperator); + Assert.AreNotEqual(areEqual, notEqualsOperator); + Assert.AreEqual(areEqual, reverseEqualsOperator); + Assert.AreNotEqual(areEqual, reverseNotEqualsOperator); + } + + [Test] + public void NullEqualityWorksCorrectly() + { + // Act + // ReSharper disable ConditionIsAlwaysTrueOrFalse + var genericEquals = new KspVersionBound().Equals(null); + var nonGenericEquals = new KspVersionBound().Equals((object)null); + var equalsOperatorNullLeft = null == new KspVersionBound(); + var equalsOperatorNullRight = new KspVersionBound() == null; + var notEqualsOperatorNullLeft = null != new KspVersionBound(); + var notEqualsOperatorNullRight = new KspVersionBound() != null; + // ReSharper restore ConditionIsAlwaysTrueOrFalse + + // ASsert + Assert.IsFalse(genericEquals); + Assert.IsFalse(nonGenericEquals); + Assert.IsFalse(equalsOperatorNullLeft); + Assert.IsFalse(equalsOperatorNullRight); + Assert.IsTrue(notEqualsOperatorNullLeft); + Assert.IsTrue(notEqualsOperatorNullRight); + } + + [Test] + public void ReferenecEqualityWorksCorrectly() + { + // Arrange + var sut = new KspVersionBound(); + + // Act + var genericEquals = sut.Equals(sut); + var nonGenericEquals = sut.Equals((object)sut); + + // Assert + Assert.IsTrue(genericEquals); + Assert.IsTrue(nonGenericEquals); + } + + [Test] + public void GetHashCodeDoesNotThrow( + [Random(0, int.MaxValue, 1)]int major, + [Random(0, int.MaxValue, 1)]int minor, + [Random(0, int.MaxValue, 1)]int patch, + [Random(0, int.MaxValue, 1)]int build, + [Values(false, true)]bool inclusive + ) + { + // Act + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + TestDelegate act = + () => new KspVersionBound(new KspVersion(major, minor, patch, build), inclusive).GetHashCode(); + + // Assert + Assert.That(act, Throws.Nothing); + } + } +} diff --git a/Tests/Core/Versioning/KspVersionTests.cs b/Tests/Core/Versioning/KspVersionTests.cs new file mode 100644 index 0000000000..d6b67cc2c7 --- /dev/null +++ b/Tests/Core/Versioning/KspVersionTests.cs @@ -0,0 +1,436 @@ +using System; +using CKAN.Versioning; +using NUnit.Framework; + +#pragma warning disable 219, 414 + +namespace Tests.Core.Versioning +{ + [TestFixture] + public class KspVersionTests + { + private static readonly object[] ParseCases = + { + new object[] { "1", new KspVersion(1) }, + new object[] { "1.2", new KspVersion(1, 2) }, + new object[] { "1.2.3", new KspVersion(1, 2, 3) }, + new object[] { "1.2.3.4", new KspVersion(1, 2, 3, 4) } + }; + + private static readonly object[] ParseFailureCases = + { + new object[] { null }, + new object[] { "" }, + new object[] { "1.2.3.4.5" }, + new object[] { "1.2.3.A" }, + new object[] { "1.2.3.-1" }, + new object[] { "9876543210.1.2.3" }, + new object[] { "1.9876543210.2.3" }, + new object[] { "1.2.9876543210.3" }, + new object[] { "1.2.3.9876543210" } + }; + + private static readonly object[] EqualityCases = + { + new object[] { new KspVersion(), null, false }, + new object[] { new KspVersion(), new KspVersion(), true }, + new object[] { new KspVersion(1), new KspVersion(1), true }, + new object[] { new KspVersion(1, 2), new KspVersion(1, 2), true}, + new object[] { new KspVersion(1, 2, 3), new KspVersion(1, 2, 3), true}, + new object[] { new KspVersion(1, 2, 3, 4), new KspVersion(1, 2, 3, 4), true}, + new object[] { new KspVersion(), new KspVersion(1), false }, + new object[] { new KspVersion(1), new KspVersion(1, 2), false }, + new object[] { new KspVersion(1, 2), new KspVersion(1, 2, 3), false}, + new object[] { new KspVersion(1, 2, 3), new KspVersion(1, 2, 3, 4), false}, + new object[] { new KspVersion(1, 2, 3, 4), new KspVersion(1, 2, 3, 5), false} + }; + + private static readonly object[] CompareToCases = + { + new object[] { new KspVersion(), new KspVersion(), 0 }, + new object[] { new KspVersion(1), new KspVersion(1), 0 }, + new object[] { new KspVersion(1, 2), new KspVersion(1, 2), 0 }, + new object[] { new KspVersion(1, 2, 3), new KspVersion(1, 2, 3), 0 }, + new object[] { new KspVersion(1, 2, 3, 4), new KspVersion(1, 2, 3, 4), 0 }, + new object[] { new KspVersion(), new KspVersion(1), -1 }, + new object[] { new KspVersion(1), new KspVersion(1, 2), -1 }, + new object[] { new KspVersion(1, 2), new KspVersion(1, 2, 3), -1 }, + new object[] { new KspVersion(1, 2, 3), new KspVersion(1, 2, 3, 4), -1 }, + new object[] { new KspVersion(1, 2, 3, 4), new KspVersion(1, 2, 3, 5), -1 }, + new object[] { new KspVersion(1), new KspVersion(), 1 }, + new object[] { new KspVersion(1, 2), new KspVersion(1), 1 }, + new object[] { new KspVersion(1, 2, 3), new KspVersion(1, 2), 1 }, + new object[] { new KspVersion(1, 2, 3, 5), new KspVersion(1, 2, 3, 4), 1} + }; + + private static readonly object[] ToVersionRangeWorksCorrectlyCases = + { + new object[] { new KspVersion(), KspVersionRange.Any }, + new object[] + { + new KspVersion(1), + new KspVersionRange( + new KspVersionBound(new KspVersion(1, 0, 0, 0), inclusive: true), + new KspVersionBound(new KspVersion(2, 0, 0, 0), inclusive: false) + ) + }, + new object[] + { + new KspVersion(1, 2), + new KspVersionRange( + new KspVersionBound(new KspVersion(1, 2, 0, 0), inclusive: true), + new KspVersionBound(new KspVersion(1, 3, 0, 0), inclusive: false) + ) + }, + new object[] + { + new KspVersion(1, 2, 3), + new KspVersionRange( + new KspVersionBound(new KspVersion(1, 2, 3, 0), inclusive: true), + new KspVersionBound(new KspVersion(1, 2, 4, 0), inclusive: false) + ) + }, + new object[] + { + new KspVersion(1, 2, 3, 4), + new KspVersionRange( + new KspVersionBound(new KspVersion(1, 2, 3, 4), inclusive: true), + new KspVersionBound(new KspVersion(1, 2, 3, 4), inclusive: true) + ) + } + }; + + [Test] + public void ParameterlessCtorWorksCorrectly() + { + // Act + var result = new KspVersion(); + + // Assert + Assert.AreEqual(-1, result.Major); + Assert.AreEqual(-1, result.Minor); + Assert.AreEqual(-1, result.Patch); + Assert.AreEqual(-1, result.Build); + + Assert.IsFalse(result.IsMajorDefined); + Assert.IsFalse(result.IsMinorDefined); + Assert.IsFalse(result.IsPatchDefined); + Assert.IsFalse(result.IsBuildDefined); + + Assert.IsFalse(result.IsFullyDefined); + Assert.IsTrue(result.IsAny); + + Assert.AreEqual(null, result.ToString()); + } + + [Test] + public void SingleParameterCtorWorksCorrectly() + { + // Act + var result = new KspVersion(1); + + // Assert + Assert.AreEqual(1, result.Major); + Assert.AreEqual(-1, result.Minor); + Assert.AreEqual(-1, result.Patch); + Assert.AreEqual(-1, result.Build); + + Assert.IsTrue(result.IsMajorDefined); + Assert.IsFalse(result.IsMinorDefined); + Assert.IsFalse(result.IsPatchDefined); + Assert.IsFalse(result.IsBuildDefined); + + Assert.IsFalse(result.IsFullyDefined); + Assert.IsFalse(result.IsAny); + + Assert.AreEqual("1", result.ToString()); + } + + [TestCase(-1)] + public void SingleParameterCtorThrowsOnInvalidParameters(int major) + { + // Act + // ReSharper disable once ObjectCreationAsStatement + TestDelegate act = () => new KspVersion(major); + + // Assert + Assert.That(act, Throws.Exception.InstanceOf()); + } + + [Test] + public void DoubleParameterCtorWorksCorrectly() + { + // Act + var result = new KspVersion(1, 2); + + // Assert + Assert.AreEqual(1, result.Major); + Assert.AreEqual(2, result.Minor); + Assert.AreEqual(-1, result.Patch); + Assert.AreEqual(-1, result.Build); + + Assert.IsTrue(result.IsMajorDefined); + Assert.IsTrue(result.IsMinorDefined); + Assert.IsFalse(result.IsPatchDefined); + Assert.IsFalse(result.IsBuildDefined); + + Assert.IsFalse(result.IsFullyDefined); + Assert.IsFalse(result.IsAny); + + Assert.AreEqual("1.2", result.ToString()); + } + + [TestCase(-1, 0)] + [TestCase(0, -1)] + public void DoubleParameterCtorThrowsOnInvalidParameters(int major, int minor) + { + // Act + // ReSharper disable once ObjectCreationAsStatement + TestDelegate act = () => new KspVersion(major, minor); + + // Assert + Assert.That(act, Throws.Exception.InstanceOf()); + } + + [Test] + public void TripleParameterCtorWorksCorrectly() + { + // Act + var result = new KspVersion(1, 2, 3); + + // Assert + Assert.AreEqual(1, result.Major); + Assert.AreEqual(2, result.Minor); + Assert.AreEqual(3, result.Patch); + Assert.AreEqual(-1, result.Build); + + Assert.IsTrue(result.IsMajorDefined); + Assert.IsTrue(result.IsMinorDefined); + Assert.IsTrue(result.IsPatchDefined); + Assert.IsFalse(result.IsBuildDefined); + + Assert.IsFalse(result.IsFullyDefined); + Assert.IsFalse(result.IsAny); + + Assert.AreEqual("1.2.3", result.ToString()); + } + + [TestCase(-1, 0, 0)] + [TestCase(0, -1, 0)] + [TestCase(0, 0, -1)] + public void TripleParameterCtorThrowsOnInvalidParameters(int major, int minor, int patch) + { + // Act + // ReSharper disable once ObjectCreationAsStatement + TestDelegate act = () => new KspVersion(major, minor, patch); + + // Assert + Assert.That(act, Throws.Exception.InstanceOf()); + } + + [Test] + public void QuadrupleParameterCtorWorksCorrectly() + { + // Act + var result = new KspVersion(1, 2, 3, 4); + + // Assert + Assert.AreEqual(1, result.Major); + Assert.AreEqual(2, result.Minor); + Assert.AreEqual(3, result.Patch); + Assert.AreEqual(4, result.Build); + + Assert.IsTrue(result.IsMajorDefined); + Assert.IsTrue(result.IsMinorDefined); + Assert.IsTrue(result.IsPatchDefined); + Assert.IsTrue(result.IsBuildDefined); + + Assert.IsTrue(result.IsFullyDefined); + Assert.IsFalse(result.IsAny); + + Assert.AreEqual("1.2.3.4", result.ToString()); + } + + [TestCase(-1, 0, 0, 0)] + [TestCase(0, -1, 0, 0)] + [TestCase(0, 0, -1, 0)] + [TestCase(0, 0, 0, -1)] + public void QuadrupleParameterCtorThrowsOnInvalidParameters(int major, int minor, int patch, int build) + { + // Act + // ReSharper disable once ObjectCreationAsStatement + TestDelegate act = () => new KspVersion(major, minor, patch, build); + + // Assert + Assert.That(act, Throws.Exception.InstanceOf()); + } + + [TestCaseSource("ParseCases")] + public void ParseWorksCorrectly(string s, KspVersion version) + { + // Act + var result = KspVersion.Parse(s); + + // Assert + Assert.AreEqual(version, result); + Assert.AreEqual(s, result.ToString()); + } + + [TestCaseSource("ParseFailureCases")] + public void ParseThrowsExceptionOnInvalidParameter(string s) + { + // Act + // ReSharper disable once ObjectCreationAsStatement + TestDelegate act = () => KspVersion.Parse(s); + + // Assert + Assert.That(act, Throws.Exception); + } + + [TestCaseSource("ToVersionRangeWorksCorrectlyCases")] + public void ToVersionRangeWorksCorrectly(KspVersion version, KspVersionRange expectedRange) + { + // Act + var result = version.ToVersionRange(); + + // Assert + Assert.AreEqual(expectedRange, result); + } + + [TestCaseSource("ParseCases")] + public void TryParseWorksCorrectly(string s, KspVersion version) + { + // Act + KspVersion result; + var success = KspVersion.TryParse(s, out result); + + // Assert + Assert.IsTrue(success); + Assert.AreEqual(version, result); + Assert.AreEqual(s, result.ToString()); + } + + [TestCaseSource("ParseFailureCases")] + public void TryParseReturnsFalseOnInvalidParameter(string s) + { + // Act + KspVersion result; + var success = KspVersion.TryParse(s, out result); + + // Assert + Assert.IsFalse(success); + } + + [TestCaseSource("EqualityCases")] + public void EqualityWorksCorrectly(KspVersion a, KspVersion b, bool areEqual) + { + // Act + var genericEquality = a.Equals(b); + var nonGenericEquality = a.Equals((object)b); + var operatorEquality = a == b; + var operatorInequality = a != b; + var genericReferenceEquality = a.Equals(a); + var nonGenericRefereneEquality = a.Equals((object)a); + + // Assert + Assert.AreEqual(areEqual, genericEquality); + Assert.AreEqual(areEqual, nonGenericEquality); + Assert.AreEqual(areEqual, operatorEquality); + Assert.AreNotEqual(areEqual, operatorInequality); + Assert.IsTrue(genericReferenceEquality); + Assert.IsTrue(nonGenericRefereneEquality); + } + + [TestCaseSource("CompareToCases")] + public void CompareToWorksCorrectly(KspVersion v1, KspVersion v2, int comparison) + { + // Act + var genericCompareTo = v1.CompareTo(v2); + var nonGenericCompareTo = v1.CompareTo((object)v2); + var lessThanOperator = v1 < v2; + var lessThanOrEqualOperator = v1 <= v2; + var greaterThanOperator = v1 > v2; + var greaterThanOrEqualOperator = v1 >= v2; + + var reverseGenericCompareTo = v2.CompareTo(v1); + var reverseNonGenericCompareTo = v2.CompareTo((object)v1); + var reverseLessThanOperator = v2 < v1; + var reverseLessThanOrEqualOperator = v2 <= v1; + var reverseGreaterThanOperator = v2 > v1; + var reverseGreaterThanOrEqualOperator = v2 >= v1; + + // Assert + Assert.AreEqual(Math.Sign(comparison), Math.Sign(genericCompareTo)); + Assert.AreEqual(Math.Sign(comparison), Math.Sign(nonGenericCompareTo)); + Assert.AreEqual(comparison < 0, lessThanOperator); + Assert.AreEqual(comparison <= 0, lessThanOrEqualOperator); + Assert.AreEqual(comparison > 0, greaterThanOperator); + Assert.AreEqual(comparison >= 0, greaterThanOrEqualOperator); + Assert.AreEqual(-Math.Sign(comparison), Math.Sign(reverseGenericCompareTo)); + Assert.AreEqual(-Math.Sign(comparison), Math.Sign(reverseNonGenericCompareTo)); + Assert.AreEqual(comparison > 0, reverseLessThanOperator); + Assert.AreEqual(comparison >= 0, reverseLessThanOrEqualOperator); + Assert.AreEqual(comparison < 0, reverseGreaterThanOperator); + Assert.AreEqual(comparison <= 0, reverseGreaterThanOrEqualOperator); + } + + [Test] + public void CompareToThrowsOnNullParameters() + { + // Act + // ReSharper disable ReturnValueOfPureMethodIsNotUsed + // ReSharper disable UnusedVariable + TestDelegate actGenericCompareTo = () => new KspVersion().CompareTo(null); + TestDelegate actNonGenericCompareTo = () => new KspVersion().CompareTo((object)null); + TestDelegate lessThanOperatorNullLeft = () => { var _ = null < new KspVersion(); }; + TestDelegate lessThanOperatorNullRight = () => { var _ = new KspVersion() < null; }; + TestDelegate lessThanOrEqualOperatorNullLeft = () => { var _ = null <= new KspVersion(); }; + TestDelegate lessThanOrEqualOperatorNullRight = () => { var _ = new KspVersion() <= null; }; + TestDelegate greaterThanOperatorNullLeft = () => { var _ = null > new KspVersion(); }; + TestDelegate greaterThanOperatorNullRight = () => { var _ = new KspVersion() > null; }; + TestDelegate greaterThanOrEqualOperatorNullLeft = () => { var _ = null >= new KspVersion(); }; + TestDelegate greaterThanOrEqualOperatorNullRight = () => { var _ = new KspVersion() >= null; }; + // ReSharper restore UnusedVariable + // ReSharper restore ReturnValueOfPureMethodIsNotUsed + + // Assert + Assert.That(actGenericCompareTo, Throws.Exception); + Assert.That(actNonGenericCompareTo, Throws.Exception); + Assert.That(lessThanOperatorNullLeft, Throws.Exception); + Assert.That(lessThanOperatorNullRight, Throws.Exception); + Assert.That(lessThanOrEqualOperatorNullLeft, Throws.Exception); + Assert.That(lessThanOrEqualOperatorNullRight, Throws.Exception); + Assert.That(greaterThanOperatorNullLeft, Throws.Exception); + Assert.That(greaterThanOperatorNullRight, Throws.Exception); + Assert.That(greaterThanOrEqualOperatorNullLeft, Throws.Exception); + Assert.That(greaterThanOrEqualOperatorNullRight, Throws.Exception); + } + + [Test] + public void NonGenericCompareToThrowsOnInvalidType() + { + // Act + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + TestDelegate act = () => new KspVersion().CompareTo(new object()); + + // Assert + Assert.That(act, Throws.ArgumentException); + } + + [Test] + public void GetHashCodeDoesNotThrow( + [Random(0, int.MaxValue, 1)]int major, + [Random(0, int.MaxValue, 1)]int minor, + [Random(0, int.MaxValue, 1)]int patch, + [Random(0, int.MaxValue, 1)]int build + ) + { + // Act + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + TestDelegate act = () => new KspVersion(major, minor, patch, build).GetHashCode(); + + // Assert + Assert.That(act, Throws.Nothing); + } + } +} diff --git a/Tests/Data/TestData.cs b/Tests/Data/TestData.cs index 95c18a726b..c05dd8d28a 100644 --- a/Tests/Data/TestData.cs +++ b/Tests/Data/TestData.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.IO; using CKAN; +using CKAN.Versioning; using Version = CKAN.Version; namespace Tests.Data @@ -470,7 +471,7 @@ public RandomModuleGenerator(Random generator) } public CkanModule GeneratorRandomModule( - KSPVersion ksp_version = null, + KspVersion ksp_version = null, List conflicts = null, List depends = null, List sugests = null, @@ -484,10 +485,10 @@ public CkanModule GeneratorRandomModule( @abstract = Generator.Next().ToString(CultureInfo.InvariantCulture), identifier = identifier??Generator.Next().ToString(CultureInfo.InvariantCulture), spec_version = new Version(1.ToString(CultureInfo.InvariantCulture)), - ksp_version = ksp_version ?? new KSPVersion("0." + Generator.Next()), + ksp_version = ksp_version ?? KspVersion.Parse("0." + Generator.Next()), version = version ?? new Version(Generator.Next().ToString(CultureInfo.InvariantCulture)) }; - mod.ksp_version_max = mod.ksp_version_min = new KSPVersion(null); + mod.ksp_version_max = mod.ksp_version_min = null; mod.conflicts = conflicts; mod.depends = depends; mod.suggests = sugests; diff --git a/Tests/NetKAN/AVC.cs b/Tests/NetKAN/AVC.cs index eda5537b0e..0b4833b443 100644 --- a/Tests/NetKAN/AVC.cs +++ b/Tests/NetKAN/AVC.cs @@ -3,6 +3,7 @@ using CKAN; using CKAN.NetKAN; using CKAN.NetKAN.Sources.Avc; +using CKAN.Versioning; using Newtonsoft.Json; using NUnit.Framework; using Tests.Data; @@ -42,8 +43,8 @@ public void WildcardMajor_OutputsAnyVersion() var converter = new JsonAvcToKspVersion(); string json = @"{""MAJOR"":-1, ""MINOR"":-1, ""PATCH"":-1}"; var reader = new JsonTextReader(new StringReader(json)); - var result = (KSPVersion) converter.ReadJson(reader, null, null, null); - Assert.That(result.IsAny()); + var result = (KspVersion) converter.ReadJson(reader, null, null, null); + Assert.That(!result.IsMajorDefined); } [Test] @@ -52,8 +53,8 @@ public void WildcardMinor_VersionOnlyHasMajor() var converter = new JsonAvcToKspVersion(); string json = @"{""MAJOR"":1, ""MINOR"":-1, ""PATCH"":-1}"; var reader = new JsonTextReader(new StringReader(json)); - var result = (KSPVersion) converter.ReadJson(reader, null, null, null); - Assert.That(result, Is.EqualTo(new KSPVersion("1"))); + var result = (KspVersion) converter.ReadJson(reader, null, null, null); + Assert.That(result, Is.EqualTo(KspVersion.Parse("1"))); } [Test] public void WildcardPatch_VersionOnlyHasMajorMinor() @@ -61,8 +62,8 @@ public void WildcardPatch_VersionOnlyHasMajorMinor() var converter = new JsonAvcToKspVersion(); string json = @"{""MAJOR"":1, ""MINOR"":5, ""PATCH"":-1}"; var reader = new JsonTextReader(new StringReader(json)); - var result = (KSPVersion)converter.ReadJson(reader, null, null, null); - Assert.That(result, Is.EqualTo(new KSPVersion("1.5"))); + var result = (KspVersion)converter.ReadJson(reader, null, null, null); + Assert.That(result, Is.EqualTo(KspVersion.Parse("1.5"))); } } } \ No newline at end of file diff --git a/Tests/NetKAN/Services/ModuleServiceTests.cs b/Tests/NetKAN/Services/ModuleServiceTests.cs index aebe82349f..49524341dc 100644 --- a/Tests/NetKAN/Services/ModuleServiceTests.cs +++ b/Tests/NetKAN/Services/ModuleServiceTests.cs @@ -1,5 +1,6 @@ using CKAN; using CKAN.NetKAN.Services; +using CKAN.Versioning; using Newtonsoft.Json.Linq; using NUnit.Framework; using Tests.Data; @@ -86,13 +87,13 @@ public void GetsInternalAvcCorrectly() Assert.That(result.version, Is.EqualTo(new Version("1.1.0.0")), "ModuleService should get correct version from the internal AVC file." ); - Assert.That(result.ksp_version, Is.EqualTo(new KSPVersion("0.24.2")), + Assert.That(result.ksp_version, Is.EqualTo(KspVersion.Parse("0.24.2")), "ModuleService should get correct ksp_version from the internal AVC file." ); - Assert.That(result.ksp_version_min, Is.EqualTo(new KSPVersion("0.24.0")), + Assert.That(result.ksp_version_min, Is.EqualTo(KspVersion.Parse("0.24.0")), "ModuleService should get correct ksp_version_min from the internal AVC file." ); - Assert.That(result.ksp_version_max, Is.EqualTo(new KSPVersion("0.24.2")), + Assert.That(result.ksp_version_max, Is.EqualTo(KspVersion.Parse("0.24.2")), "ModuleService should get correct ksp_version_max from the internal AVC file." ); } diff --git a/Tests/NetKAN/Transformers/AvcTransformerTests.cs b/Tests/NetKAN/Transformers/AvcTransformerTests.cs index 12c0a5c0b6..ba1feddd56 100644 --- a/Tests/NetKAN/Transformers/AvcTransformerTests.cs +++ b/Tests/NetKAN/Transformers/AvcTransformerTests.cs @@ -3,6 +3,7 @@ using CKAN.NetKAN.Services; using CKAN.NetKAN.Sources.Avc; using CKAN.NetKAN.Transformers; +using CKAN.Versioning; using Moq; using Newtonsoft.Json.Linq; using NUnit.Framework; @@ -19,7 +20,7 @@ public void AddsMissingVersionInfo() var avcVersion = new AvcVersion { version = new Version("1.0.0"), - ksp_version = new KSPVersion("1.0.4") + ksp_version = KspVersion.Parse("1.0.4") }; var mHttp = new Mock(); @@ -55,9 +56,9 @@ public void PreferentiallyAddsRangedKspVersionInfo() // Arrange var avcVersion = new AvcVersion { - ksp_version = new KSPVersion("1.0.4"), - ksp_version_min = new KSPVersion("0.90"), - ksp_version_max = new KSPVersion("1.0.3") + ksp_version = KspVersion.Parse("1.0.4"), + ksp_version_min = KspVersion.Parse("0.90"), + ksp_version_max = KspVersion.Parse("1.0.3") }; var mHttp = new Mock(); @@ -175,13 +176,13 @@ public void CorrectlyCalculatesKspVersionInfo( var avcVersion = new AvcVersion(); if (!string.IsNullOrWhiteSpace(avcKsp)) - avcVersion.ksp_version = new KSPVersion(avcKsp); + avcVersion.ksp_version = KspVersion.Parse(avcKsp); if (!string.IsNullOrWhiteSpace(avcKspMin)) - avcVersion.ksp_version_min = new KSPVersion(avcKspMin); + avcVersion.ksp_version_min = KspVersion.Parse(avcKspMin); if (!string.IsNullOrWhiteSpace(avcKspMax)) - avcVersion.ksp_version_max = new KSPVersion(avcKspMax); + avcVersion.ksp_version_max = KspVersion.Parse(avcKspMax); var mHttp = new Mock(); var mModuleService = new Mock(); diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 3eec213fd1..a24347e39a 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -20,6 +20,7 @@ prompt 4 false + 5 pdbonly @@ -125,12 +126,13 @@ - + + @@ -202,4 +204,4 @@ - + \ No newline at end of file