Skip to content

Commit

Permalink
ForNeVeR#20 Improve case-sensitive path comparison
Browse files Browse the repository at this point in the history
  • Loading branch information
Kataane committed Jul 20, 2024
1 parent c128df2 commit 21e6c9c
Show file tree
Hide file tree
Showing 10 changed files with 335 additions and 19 deletions.
99 changes: 99 additions & 0 deletions TruePath.Tests/AbsolutePathEqualsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
namespace TruePath.Tests;

public partial class AbsolutePathTests
{
[Fact]
public void EqualsUseStrictStringPathComparer_SamePaths_True()
{
// Arrange
var currentDirectory = Environment.CurrentDirectory;
var nonCanonicalPath = currentDirectory;

var path1 = new AbsolutePath(currentDirectory);
var path2 = new AbsolutePath(nonCanonicalPath);

// Act
var equals = path1.Equals(path2, StrictStringPathComparer.Comparer);

// Assert
Assert.True(equals);
}

[Fact]
public void EqualsUseStrictStringPathComparer_NotSamePaths_False()
{
// Arrange
var currentDirectory = Environment.CurrentDirectory;
var nonCanonicalPath = new string(currentDirectory.MakeNonCanonicalPath().ToArray());

var path1 = new AbsolutePath(currentDirectory);
var path2 = new AbsolutePath(nonCanonicalPath);

// Act
var equals = path1.Equals(path2, StrictStringPathComparer.Comparer);

// Assert
Assert.False(equals);
}

[Fact]
public void OnLinux_EqualsDefault_CaseSensitive_False()
{
// Arrange
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return;
}

var currentDirectory = Environment.CurrentDirectory;
var nonCanonicalPath = new string(currentDirectory.MakeNonCanonicalPath().ToArray());

var path1 = new AbsolutePath(currentDirectory);
var path2 = new AbsolutePath(nonCanonicalPath);

// Act
var equals = path1.Equals(path2);

// Assert
Assert.False(equals);
}

[Fact]
public void EqualsNull_False()
{
// Arrange
var currentDirectory = Environment.CurrentDirectory;
var nonCanonicalPath = currentDirectory;

var path1 = new AbsolutePath(currentDirectory);
var path2 = new AbsolutePath(nonCanonicalPath);

// Act
var equals = path1.Equals(path2, null);

// Assert
Assert.False(equals);
}

[Fact]
public void OnWindowsOrOsx_EqualsDefault_CaseInsensitive_True()
{
// Arrange
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return;
}

var currentDirectory = Environment.CurrentDirectory;
var nonCanonicalPath = new string(currentDirectory.MakeNonCanonicalPath().ToArray());

var path1 = new AbsolutePath(currentDirectory);
var path2 = new AbsolutePath(nonCanonicalPath);

// Act
var equals = path1.Equals(path2);

// Assert
Assert.True(equals);
}
}
2 changes: 1 addition & 1 deletion TruePath.Tests/AbsolutePathTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace TruePath.Tests;

public class AbsolutePathTests
public partial class AbsolutePathTests
{
[Theory]
[InlineData("/home/user", "/home/user/documents")]
Expand Down
16 changes: 1 addition & 15 deletions TruePath.Tests/DiskUtilsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public void DiskUtils_OnWindows_PassNonCanonicalPath_ReturnCanonicalPath()
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return;

var expected = Environment.CurrentDirectory;
var nonCanonicalPath = new string(MakeNonCanonicalPath(expected).ToArray());
var nonCanonicalPath = new string(expected.MakeNonCanonicalPath().ToArray());

// Act
var actual = DiskUtils.GetRealPath(nonCanonicalPath);
Expand Down Expand Up @@ -76,18 +76,4 @@ private static string Back(List<string> folders, int stepsBack, string delimiter

return string.Join(delimiter, finalFolders);
}

private static IEnumerable<char> MakeNonCanonicalPath(string path)
{
foreach (var @char in path)
{
if (char.IsLetter(@char) && Random.Shared.NextSingle() >= 0.5)
{
yield return char.ToUpper(@char);
continue;
}

yield return @char;
}
}
}
99 changes: 99 additions & 0 deletions TruePath.Tests/LocalPathEqualsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
namespace TruePath.Tests;

public partial class LocalPathTests
{
[Fact]
public void EqualsUseStrictStringPathComparer_SamePaths_True()
{
// Arrange
var currentDirectory = Environment.CurrentDirectory;
var nonCanonicalPath = currentDirectory;

var path1 = new LocalPath(currentDirectory);
var path2 = new LocalPath(nonCanonicalPath);

// Act
var equals = path1.Equals(path2, StrictStringPathComparer.Comparer);

// Assert
Assert.True(equals);
}

[Fact]
public void EqualsUseStrictStringPathComparer_NotSamePaths_False()
{
// Arrange
var currentDirectory = Environment.CurrentDirectory;
var nonCanonicalPath = new string(currentDirectory.MakeNonCanonicalPath().ToArray());

var path1 = new LocalPath(currentDirectory);
var path2 = new LocalPath(nonCanonicalPath);

// Act
var equals = path1.Equals(path2, StrictStringPathComparer.Comparer);

// Assert
Assert.False(equals);
}

[Fact]
public void OnLinux_EqualsDefault_CaseSensitive_False()
{
// Arrange
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return;
}

var currentDirectory = Environment.CurrentDirectory;
var nonCanonicalPath = new string(currentDirectory.MakeNonCanonicalPath().ToArray());

var path1 = new LocalPath(currentDirectory);
var path2 = new LocalPath(nonCanonicalPath);

// Act
var equals = path1.Equals(path2);

// Assert
Assert.False(equals);
}

[Fact]
public void EqualsNull_False()
{
// Arrange
var currentDirectory = Environment.CurrentDirectory;
var nonCanonicalPath = currentDirectory;

var path1 = new LocalPath(currentDirectory);
var path2 = new LocalPath(nonCanonicalPath);

// Act
var equals = path1.Equals(path2, null);

// Assert
Assert.False(equals);
}

[Fact]
public void OnWindowsOrOsx_EqualsDefault_CaseInsensitive_True()
{
// Arrange
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return;
}

var currentDirectory = Environment.CurrentDirectory;
var nonCanonicalPath = new string(currentDirectory.MakeNonCanonicalPath().ToArray());

var path1 = new LocalPath(currentDirectory);
var path2 = new LocalPath(nonCanonicalPath);

// Act
var equals = path1.Equals(path2);

// Assert
Assert.True(equals);
}
}
2 changes: 1 addition & 1 deletion TruePath.Tests/LocalPathTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace TruePath.Tests;

public class LocalPathTests
public partial class LocalPathTests
{
[Theory]
[InlineData("user", "user/documents")]
Expand Down
18 changes: 18 additions & 0 deletions TruePath.Tests/Utils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace TruePath.Tests;

public static class Utils
{
internal static IEnumerable<char> MakeNonCanonicalPath(this string path)
{
foreach (var @char in path)
{
if (char.IsLetter(@char) && Random.Shared.NextSingle() >= 0.5)
{
yield return char.ToUpper(@char);
continue;
}

yield return @char;
}
}
}
24 changes: 23 additions & 1 deletion TruePath/AbsolutePath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,29 @@ public bool IsPrefixOf(AbsolutePath other)
/// <remarks>Note that currently this comparison is case-sensitive.</remarks>
public bool Equals(AbsolutePath other)
{
return Underlying.Equals(other.Underlying);
var comparer = PlatformDefaultPathComparer.Comparer;
return comparer.Compare(Underlying.Value, other.Underlying.Value) == 0;
}

/// <summary>
/// Determines whether the specified <see cref="AbsolutePath"/> is equal to the current <see cref="AbsolutePath"/> using the specified string comparer.
/// </summary>
/// <param name="other">The <see cref="AbsolutePath"/> to compare with the current <see cref="AbsolutePath"/>.</param>
/// <param name="comparer">The comparer to use for comparing the paths.</param>
/// <returns>
/// <see langword="true"/> if the specified <see cref="AbsolutePath"/> is equal to the current <see cref="AbsolutePath"/> using the specified string comparer; otherwise, <see langword="false"/>.
/// </returns>
/// <remarks>
/// If the comparer is null, this method returns <see langword="false"/>.
/// </remarks>
public bool Equals(AbsolutePath other, IComparer<string>? comparer)
{
if (comparer is null)
{
return false;
}

return comparer.Compare(Value, other.Value) == 0;
}

/// <inheritdoc cref="Equals(AbsolutePath)"/>
Expand Down
24 changes: 23 additions & 1 deletion TruePath/LocalPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,29 @@ public readonly struct LocalPath(string value) : IEquatable<LocalPath>, IPath, I
/// <remarks>Note that currently this comparison is case-sensitive.</remarks>
public bool Equals(LocalPath other)
{
return Value == other.Value;
var comparer = PlatformDefaultPathComparer.Comparer;
return comparer.Compare(Value, other.Value) == 0;
}

/// <summary>
/// Determines whether the specified <see cref="LocalPath"/> is equal to the current <see cref="LocalPath"/> using the specified string comparer.
/// </summary>
/// <param name="other">The <see cref="LocalPath"/> to compare with the current <see cref="LocalPath"/>.</param>
/// <param name="comparer">The comparer to use for comparing the paths.</param>
/// <returns>
/// <see langword="true"/> if the specified <see cref="LocalPath"/> is equal to the current <see cref="LocalPath"/> using the specified string comparer; otherwise, <see langword="false"/>.
/// </returns>
/// <remarks>
/// If the comparer is null, this method returns <see langword="false"/>.
/// </remarks>
public bool Equals(LocalPath other, IComparer<string>? comparer)
{
if (comparer is null)
{
return false;
}

return comparer.Compare(Value, other.Value) == 0;
}

/// <inheritdoc cref="Equals(LocalPath)"/>
Expand Down
45 changes: 45 additions & 0 deletions TruePath/PlatformDefaultPathComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Runtime.InteropServices;

namespace TruePath;

/// <summary>
/// Provides a platform-specific string comparer for comparing file paths.
/// </summary>
public class PlatformDefaultPathComparer : IComparer<string>
{
/// <summary>
/// Gets the singleton instance of the <see cref="PlatformDefaultPathComparer"/> class.
/// </summary>
public static readonly PlatformDefaultPathComparer Comparer = new();

private readonly StringComparer comparisonType;

/// <summary>
/// Initializes a new instance of the <see cref="PlatformDefaultPathComparer"/> class.
/// </summary>
public PlatformDefaultPathComparer()
{
// Определяем тип сравнения в зависимости от платформы
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
comparisonType = StringComparer.OrdinalIgnoreCase;
}
else
{
comparisonType = StringComparer.Ordinal;
}
}

/// <summary>
/// Compares two strings and returns an integer that indicates their relative position in the sort order.
/// </summary>
/// <param name="x">The first string to compare.</param>
/// <param name="y">The second string to compare.</param>
/// <returns>
/// A value less than zero if <paramref name="x"/> is less than <paramref name="y"/>; zero if <paramref name="x"/> equals <paramref name="y"/>; a value greater than zero if <paramref name="x"/> is greater than <paramref name="y"/>.
/// </returns>
public int Compare(string? x, string? y)
{
return comparisonType.Compare(x, y);
}
}
25 changes: 25 additions & 0 deletions TruePath/StrictStringPathComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace TruePath;

/// <summary>
/// Provides a strict string comparer for comparing file paths using ordinal comparison.
/// </summary>
public class StrictStringPathComparer : IComparer<string>
{
/// <summary>
/// Gets the singleton instance of the <see cref="StrictStringPathComparer"/> class.
/// </summary>
public static readonly StrictStringPathComparer Comparer = new();

/// <summary>
/// Compares two strings and returns an integer that indicates their relative position in the sort order using ordinal comparison.
/// </summary>
/// <param name="x">The first string to compare.</param>
/// <param name="y">The second string to compare.</param>
/// <returns>
/// A value less than zero if <paramref name="x"/> is less than <paramref name="y"/>; zero if <paramref name="x"/> equals <paramref name="y"/>; a value greater than zero if <paramref name="x"/> is greater than <paramref name="y"/>.
/// </returns>
public int Compare(string? x, string? y)
{
return StringComparer.Ordinal.Compare(x, y);
}
}

0 comments on commit 21e6c9c

Please sign in to comment.