diff options
Diffstat (limited to 'src/SMAPI.Tests')
-rw-r--r-- | src/SMAPI.Tests/Core/ModResolverTests.cs | 22 | ||||
-rw-r--r-- | src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs | 285 |
2 files changed, 296 insertions, 11 deletions
diff --git a/src/SMAPI.Tests/Core/ModResolverTests.cs b/src/SMAPI.Tests/Core/ModResolverTests.cs index 45b3673b..78056ef7 100644 --- a/src/SMAPI.Tests/Core/ModResolverTests.cs +++ b/src/SMAPI.Tests/Core/ModResolverTests.cs @@ -154,7 +154,7 @@ namespace SMAPI.Tests.Core new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: key => null); // assert - mock.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<string>()), Times.Once, "The validation did not fail the metadata."); + mock.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<ModFailReason>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once, "The validation did not fail the metadata."); } [Test(Description = "Assert that validation fails when the minimum API version is higher than the current SMAPI version.")] @@ -169,7 +169,7 @@ namespace SMAPI.Tests.Core new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: key => null); // assert - mock.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<string>()), Times.Once, "The validation did not fail the metadata."); + mock.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<ModFailReason>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once, "The validation did not fail the metadata."); } [Test(Description = "Assert that validation fails when the manifest references a DLL that does not exist.")] @@ -183,7 +183,7 @@ namespace SMAPI.Tests.Core new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: key => null); // assert - mock.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<string>()), Times.Once, "The validation did not fail the metadata."); + mock.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<ModFailReason>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once, "The validation did not fail the metadata."); } [Test(Description = "Assert that validation fails when multiple mods have the same unique ID.")] @@ -200,8 +200,8 @@ namespace SMAPI.Tests.Core new ModResolver().ValidateManifests(new[] { modA.Object, modB.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: key => null); // assert - modA.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<string>()), Times.Once, "The validation did not fail the first mod with a unique ID."); - modB.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<string>()), Times.Once, "The validation did not fail the second mod with a unique ID."); + modA.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<ModFailReason>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once, "The validation did not fail the first mod with a unique ID."); + modB.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<ModFailReason>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once, "The validation did not fail the second mod with a unique ID."); } [Test(Description = "Assert that validation fails when the manifest references a DLL that does not exist.")] @@ -367,9 +367,9 @@ namespace SMAPI.Tests.Core Assert.AreEqual(5, mods.Length, 0, "Expected to get the same number of mods input."); Assert.AreSame(modA.Object, mods[0], "The load order is incorrect: mod A should be first since it's needed by mod B."); Assert.AreSame(modB.Object, mods[1], "The load order is incorrect: mod B should be second since it needs mod A."); - modC.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<string>()), Times.Once, "Mod C was expected to fail since it's part of a dependency loop."); - modD.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<string>()), Times.Once, "Mod D was expected to fail since it's part of a dependency loop."); - modE.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<string>()), Times.Once, "Mod E was expected to fail since it's part of a dependency loop."); + modC.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<ModFailReason>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once, "Mod C was expected to fail since it's part of a dependency loop."); + modD.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<ModFailReason>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once, "Mod D was expected to fail since it's part of a dependency loop."); + modE.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<ModFailReason>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once, "Mod E was expected to fail since it's part of a dependency loop."); } [Test(Description = "Assert that dependencies are sorted correctly even if some of the mods failed during metadata loading.")] @@ -408,7 +408,7 @@ namespace SMAPI.Tests.Core // assert Assert.AreEqual(2, mods.Length, 0, "Expected to get the same number of mods input."); - modB.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<string>()), Times.Once, "Mod B unexpectedly didn't fail even though it needs a newer version of Mod A."); + modB.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<ModFailReason>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once, "Mod B unexpectedly didn't fail even though it needs a newer version of Mod A."); } [Test(Description = "Assert that dependencies are accepted if they meet the minimum version.")] @@ -525,8 +525,8 @@ namespace SMAPI.Tests.Core if (allowStatusChange) { mod - .Setup(p => p.SetStatus(It.IsAny<ModMetadataStatus>(), It.IsAny<string>())) - .Callback<ModMetadataStatus, string>((status, message) => Console.WriteLine($"<{manifest.UniqueID} changed status: [{status}] {message}")) + .Setup(p => p.SetStatus(It.IsAny<ModMetadataStatus>(), It.IsAny<ModFailReason>(), It.IsAny<string>(), It.IsAny<string>())) + .Callback<ModMetadataStatus, ModFailReason, string, string>((status, failReason, message, errorDetails) => Console.WriteLine($"<{manifest.UniqueID} changed status: [{status}] {message}\n{failReason}\n{errorDetails}")) .Returns(mod.Object); } return mod; diff --git a/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs b/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs new file mode 100644 index 00000000..ea69a9ea --- /dev/null +++ b/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs @@ -0,0 +1,285 @@ +using NUnit.Framework; +using StardewModdingAPI.Toolkit.Utilities; + +namespace SMAPI.Tests.Utilities +{ + /// <summary>Unit tests for <see cref="PathUtilities"/>.</summary> + [TestFixture] + internal class PathUtilitiesTests + { + /********* + ** Sample data + *********/ + /// <summary>Sample paths used in unit tests.</summary> + public static readonly SamplePath[] SamplePaths = { + // Windows absolute path + new SamplePath + { + OriginalPath = @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley", + + Segments = new[] { "C:", "Program Files (x86)", "Steam", "steamapps", "common", "Stardew Valley" }, + SegmentsLimit3 = new [] { "C:", "Program Files (x86)", @"Steam\steamapps\common\Stardew Valley" }, + + NormalizedOnWindows = @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley", + NormalizedOnUnix = @"C:/Program Files (x86)/Steam/steamapps/common/Stardew Valley" + }, + + // Windows absolute path (with trailing slash) + new SamplePath + { + OriginalPath = @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley\", + + Segments = new[] { "C:", "Program Files (x86)", "Steam", "steamapps", "common", "Stardew Valley" }, + SegmentsLimit3 = new [] { "C:", "Program Files (x86)", @"Steam\steamapps\common\Stardew Valley\" }, + + NormalizedOnWindows = @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley\", + NormalizedOnUnix = @"C:/Program Files (x86)/Steam/steamapps/common/Stardew Valley/" + }, + + // Windows relative path + new SamplePath + { + OriginalPath = @"Content\Characters\Dialogue\Abigail", + + Segments = new [] { "Content", "Characters", "Dialogue", "Abigail" }, + SegmentsLimit3 = new [] { "Content", "Characters", @"Dialogue\Abigail" }, + + NormalizedOnWindows = @"Content\Characters\Dialogue\Abigail", + NormalizedOnUnix = @"Content/Characters/Dialogue/Abigail" + }, + + // Windows relative path (with directory climbing) + new SamplePath + { + OriginalPath = @"..\..\Content", + + Segments = new [] { "..", "..", "Content" }, + SegmentsLimit3 = new [] { "..", "..", "Content" }, + + NormalizedOnWindows = @"..\..\Content", + NormalizedOnUnix = @"../../Content" + }, + + // Windows UNC path + new SamplePath + { + OriginalPath = @"\\unc\path", + + Segments = new [] { "unc", "path" }, + SegmentsLimit3 = new [] { "unc", "path" }, + + NormalizedOnWindows = @"\\unc\path", + NormalizedOnUnix = "/unc/path" // there's no good way to normalize this on Unix since UNC paths aren't supported; path normalization is meant for asset names anyway, so this test only ensures it returns some sort of sane value + }, + + // Linux absolute path + new SamplePath + { + OriginalPath = @"/home/.steam/steam/steamapps/common/Stardew Valley", + + Segments = new [] { "home", ".steam", "steam", "steamapps", "common", "Stardew Valley" }, + SegmentsLimit3 = new [] { "home", ".steam", "steam/steamapps/common/Stardew Valley" }, + + NormalizedOnWindows = @"\home\.steam\steam\steamapps\common\Stardew Valley", + NormalizedOnUnix = @"/home/.steam/steam/steamapps/common/Stardew Valley" + }, + + // Linux absolute path (with trailing slash) + new SamplePath + { + OriginalPath = @"/home/.steam/steam/steamapps/common/Stardew Valley/", + + Segments = new [] { "home", ".steam", "steam", "steamapps", "common", "Stardew Valley" }, + SegmentsLimit3 = new [] { "home", ".steam", "steam/steamapps/common/Stardew Valley/" }, + + NormalizedOnWindows = @"\home\.steam\steam\steamapps\common\Stardew Valley\", + NormalizedOnUnix = @"/home/.steam/steam/steamapps/common/Stardew Valley/" + }, + + // Linux absolute path (with ~) + new SamplePath + { + OriginalPath = @"~/.steam/steam/steamapps/common/Stardew Valley", + + Segments = new [] { "~", ".steam", "steam", "steamapps", "common", "Stardew Valley" }, + SegmentsLimit3 = new [] { "~", ".steam", "steam/steamapps/common/Stardew Valley" }, + + NormalizedOnWindows = @"~\.steam\steam\steamapps\common\Stardew Valley", + NormalizedOnUnix = @"~/.steam/steam/steamapps/common/Stardew Valley" + }, + + // Linux relative path + new SamplePath + { + OriginalPath = @"Content/Characters/Dialogue/Abigail", + + Segments = new [] { "Content", "Characters", "Dialogue", "Abigail" }, + SegmentsLimit3 = new [] { "Content", "Characters", "Dialogue/Abigail" }, + + NormalizedOnWindows = @"Content\Characters\Dialogue\Abigail", + NormalizedOnUnix = @"Content/Characters/Dialogue/Abigail" + }, + + // Linux relative path (with directory climbing) + new SamplePath + { + OriginalPath = @"../../Content", + + Segments = new [] { "..", "..", "Content" }, + SegmentsLimit3 = new [] { "..", "..", "Content" }, + + NormalizedOnWindows = @"..\..\Content", + NormalizedOnUnix = @"../../Content" + }, + + // Mixed directory separators + new SamplePath + { + OriginalPath = @"C:\some/mixed\path/separators", + + Segments = new [] { "C:", "some", "mixed", "path", "separators" }, + SegmentsLimit3 = new [] { "C:", "some", @"mixed\path/separators" }, + + NormalizedOnWindows = @"C:\some\mixed\path\separators", + NormalizedOnUnix = @"C:/some/mixed/path/separators" + }, + }; + + + /********* + ** Unit tests + *********/ + /**** + ** GetSegments + ****/ + [Test(Description = "Assert that PathUtilities.GetSegments splits paths correctly.")] + [TestCaseSource(nameof(PathUtilitiesTests.SamplePaths))] + public void GetSegments(SamplePath path) + { + // act + string[] segments = PathUtilities.GetSegments(path.OriginalPath); + + // assert + Assert.AreEqual(path.Segments, segments); + } + + [Test(Description = "Assert that PathUtilities.GetSegments splits paths correctly when given a limit.")] + [TestCaseSource(nameof(PathUtilitiesTests.SamplePaths))] + public void GetSegments_WithLimit(SamplePath path) + { + // act + string[] segments = PathUtilities.GetSegments(path.OriginalPath, 3); + + // assert + Assert.AreEqual(path.SegmentsLimit3, segments); + } + + /**** + ** NormalizePathSeparators + ****/ + [Test(Description = "Assert that PathUtilities.NormalizePathSeparators normalizes paths correctly.")] + [TestCaseSource(nameof(PathUtilitiesTests.SamplePaths))] + public void NormalizePathSeparators(SamplePath path) + { + // act + string normalized = PathUtilities.NormalizePathSeparators(path.OriginalPath); + + // assert +#if SMAPI_FOR_WINDOWS + Assert.AreEqual(path.NormalizedOnWindows, normalized); +#else + Assert.AreEqual(path.NormalizedOnUnix, normalized); +#endif + } + + /**** + ** GetRelativePath + ****/ + [Test(Description = "Assert that PathUtilities.GetRelativePath returns the expected values.")] +#if SMAPI_FOR_WINDOWS + [TestCase( + @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley", + @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley\Mods\Automate", + ExpectedResult = @"Mods\Automate" + )] + [TestCase( + @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley\Mods\Automate", + @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley\Content", + ExpectedResult = @"..\..\Content" + )] + [TestCase( + @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley\Mods\Automate", + @"D:\another-drive", + ExpectedResult = @"D:\another-drive" + )] + [TestCase( + @"\\parent\unc", + @"\\parent\unc\path\to\child", + ExpectedResult = @"path\to\child" + )] + [TestCase( + @"\\parent\unc", + @"\\adjacent\unc", + ExpectedResult = @"\\adjacent\unc" + )] + [TestCase( + @"C:\same\path", + @"C:\same\path", + ExpectedResult = @"." + )] + [TestCase( + @"C:\parent", + @"C:\PARENT\child", + ExpectedResult = @"child" + )] +#else + [TestCase( + @"~/.steam/steam/steamapps/common/Stardew Valley", + @"~/.steam/steam/steamapps/common/Stardew Valley/Mods/Automate", + ExpectedResult = @"Mods/Automate" + )] + [TestCase( + @"~/.steam/steam/steamapps/common/Stardew Valley/Mods/Automate", + @"~/.steam/steam/steamapps/common/Stardew Valley/Content", + ExpectedResult = @"../../Content" + )] + [TestCase( + @"~/.steam/steam/steamapps/common/Stardew Valley/Mods/Automate", + @"/mnt/another-drive", + ExpectedResult = @"/mnt/another-drive" + )] + [TestCase( + @"~/same/path", + @"~/same/path", + ExpectedResult = @"." + )] + [TestCase( + @"~/parent", + @"~/PARENT/child", + ExpectedResult = @"child" // note: incorrect on Linux and sometimes MacOS, but not worth the complexity of detecting whether the filesystem is case-sensitive for SMAPI's purposes + )] +#endif + public string GetRelativePath(string sourceDir, string targetPath) + { + return PathUtilities.GetRelativePath(sourceDir, targetPath); + } + + + /********* + ** Private classes + *********/ + public class SamplePath + { + public string OriginalPath { get; set; } + public string[] Segments { get; set; } + public string[] SegmentsLimit3 { get; set; } + public string NormalizedOnWindows { get; set; } + public string NormalizedOnUnix { get; set; } + + public override string ToString() + { + return this.OriginalPath; + } + } + } +} |