Skip to content

Commit

Permalink
feat: implement additional methods for simulated Path (#566)
Browse files Browse the repository at this point in the history
Implement the following methods for the simulated `Path`:
- `ChangeExtension`
- `EndsInDirectorySeparator`
- `GetExtension`
- `GetFileName`
- `GetFileNameWithoutExtension`
  • Loading branch information
vbreuss authored Apr 19, 2024
1 parent 02cc9bb commit a43d1f0
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,11 @@ public override string GetTempPath()
/// <inheritdoc cref="IPath.IsPathRooted(string)" />
public override bool IsPathRooted(string? path)
=> path?.Length > 0 && path[0] == '/';

/// <summary>
/// https://github.com/dotnet/runtime/blob/v8.0.4/src/libraries/Common/src/System/IO/PathInternal.Unix.cs#L27
/// </summary>
protected override bool IsDirectorySeparator(char c)
=> c == DirectorySeparatorChar;
}
}
103 changes: 96 additions & 7 deletions Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics.CodeAnalysis;
#if FEATURE_FILESYSTEM_NET7
using Testably.Abstractions.Testing.Storage;
Expand Down Expand Up @@ -30,7 +30,33 @@ private abstract class SimulatedPath(MockFileSystem fileSystem) : IPath
/// <inheritdoc cref="IPath.ChangeExtension(string, string)" />
[return: NotNullIfNotNull("path")]
public string? ChangeExtension(string? path, string? extension)
=> System.IO.Path.ChangeExtension(path, extension);
{
if (path == null)
{
return null;
}

if (path == string.Empty)
{
return string.Empty;
}

if (extension == null)
{
extension = "";
}
else if (!extension.StartsWith('.'))
{
extension = "." + extension;
}

if (!TryGetExtensionIndex(path, out int? dotIndex))
{
return path + extension;
}

return path.Substring(0, dotIndex.Value) + extension;
}

/// <inheritdoc cref="IPath.Combine(string, string)" />
public string Combine(string path1, string path2)
Expand Down Expand Up @@ -108,7 +134,7 @@ public bool EndsInDirectorySeparator(ReadOnlySpan<char> path)
#if FEATURE_PATH_ADVANCED
/// <inheritdoc cref="IPath.EndsInDirectorySeparator(string)" />
public bool EndsInDirectorySeparator(string path)
=> System.IO.Path.EndsInDirectorySeparator(path);
=> !string.IsNullOrEmpty(path) && IsDirectorySeparator(path[path.Length - 1]);
#endif

#if FEATURE_FILESYSTEM_NET7
Expand Down Expand Up @@ -144,7 +170,21 @@ public ReadOnlySpan<char> GetExtension(ReadOnlySpan<char> path)
/// <inheritdoc cref="IPath.GetExtension(string)" />
[return: NotNullIfNotNull("path")]
public string? GetExtension(string? path)
=> System.IO.Path.GetExtension(path);
{
if (path == null)
{
return null;
}

if (TryGetExtensionIndex(path, out int? dotIndex))
{
return dotIndex != path.Length - 1
? path.Substring(dotIndex.Value)
: string.Empty;
}

return string.Empty;
}

#if FEATURE_SPAN
/// <inheritdoc cref="IPath.GetFileName(ReadOnlySpan{char})" />
Expand All @@ -155,7 +195,22 @@ public ReadOnlySpan<char> GetFileName(ReadOnlySpan<char> path)
/// <inheritdoc cref="IPath.GetFileName(string)" />
[return: NotNullIfNotNull("path")]
public string? GetFileName(string? path)
=> System.IO.Path.GetFileName(path);
{
if (path == null)
{
return null;
}

for (int i = path.Length - 1; i >= 0; i--)
{
if (IsDirectorySeparator(path[i]))
{
return path.Substring(i + 1);
}
}

return path;
}

#if FEATURE_SPAN
/// <inheritdoc cref="IPath.GetFileNameWithoutExtension(ReadOnlySpan{char})" />
Expand All @@ -166,7 +221,16 @@ public ReadOnlySpan<char> GetFileNameWithoutExtension(ReadOnlySpan<char> path)
/// <inheritdoc cref="IPath.GetFileNameWithoutExtension(string)" />
[return: NotNullIfNotNull("path")]
public string? GetFileNameWithoutExtension(string? path)
=> System.IO.Path.GetFileNameWithoutExtension(path);
{
if (path == null)
{
return null;
}

string fileName = GetFileName(path);
int lastPeriod = fileName.LastIndexOf('.');
return lastPeriod < 0 ? fileName : fileName.Substring(0, lastPeriod);
}

/// <inheritdoc cref="IPath.GetFullPath(string)" />
public string GetFullPath(string path)
Expand Down Expand Up @@ -295,7 +359,8 @@ public string Join(ReadOnlySpan<char> path1,
ReadOnlySpan<char> path2,
ReadOnlySpan<char> path3,
ReadOnlySpan<char> path4)
=> JoinInternal([path1.ToString(), path2.ToString(), path3.ToString(), path4.ToString()]);
=> JoinInternal(
[path1.ToString(), path2.ToString(), path3.ToString(), path4.ToString()]);
#endif

#if FEATURE_PATH_ADVANCED
Expand Down Expand Up @@ -409,9 +474,33 @@ public bool TryJoin(ReadOnlySpan<char> path1,
private static string CombineInternal(string[] paths)
=> System.IO.Path.Combine(paths);

protected abstract bool IsDirectorySeparator(char c);

#if FEATURE_PATH_ADVANCED
private static string JoinInternal(string?[] paths)
=> System.IO.Path.Join(paths);
#endif

private bool TryGetExtensionIndex(string path, [NotNullWhen(true)] out int? dotIndex)
{
for (int i = path.Length - 1; i >= 0; i--)
{
char ch = path[i];

if (ch == '.')
{
dotIndex = i;
return true;
}

if (IsDirectorySeparator(ch))
{
break;
}
}

dotIndex = null;
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,10 @@ public override bool IsPathRooted(string? path)
}

/// <summary>
/// True if the given character is a directory separator.
/// https://github.com/dotnet/runtime/blob/v8.0.4/src/libraries/Common/src/System/IO/PathInternal.Windows.cs#L280
/// </summary>
/// <remarks>https://github.com/dotnet/runtime/blob/v8.0.3/src/libraries/Common/src/System/IO/PathInternal.Windows.cs#L280</remarks>
private static bool IsDirectorySeparator(char c)
=> c == '\\' || c == '/';
protected override bool IsDirectorySeparator(char c)
=> c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;

/// <summary>
/// Returns true if the given character is a valid drive letter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,19 @@ public override void SkipIfLongRunningTestsShouldBeSkipped()

private bool IncludeSimulatedTests(ClassModel @class)
{
string[] supportedPathTests = ["Tests", "GetRandomFileNameTests", "GetPathRootTests", "GetTempPathTests", "IsPathRootedTests"];
string[] supportedPathTests =
[
"ChangeExtensionTests",
"EndsInDirectorySeparatorTests",
"GetExtensionTests",
"GetFileNameTests",
"GetFileNameWithoutExtensionTests",
"GetPathRootTests",
"GetRandomFileNameTests",
"GetTempPathTests",
"IsPathRootedTests",
"Tests"
];
return @class.Namespace
.StartsWith("Testably.Abstractions.Tests.FileSystem.Path", StringComparison.Ordinal)
&& supportedPathTests.Contains(@class.Name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,27 @@ public void ChangeExtension_WithDotOnlyInDirectory_ShouldAppendExtensionToPath(
string directory, string fileName, string extension)
{
directory = directory + "." + "with-dot";
string path = FileSystem.Path.Combine(directory, fileName);
string path = $"{directory}{FileSystem.Path.DirectorySeparatorChar}{fileName}";
string expectedResult = path + "." + extension;

string result = FileSystem.Path.ChangeExtension(path, extension);

result.Should().Be(expectedResult);
}

[SkippableTheory]
[AutoData]
public void ChangeExtension_WithFileStartingWithDot_ShouldAppendExtensionToPath(
string fileName, string extension)
{
string path = $".{fileName}";
string expectedResult = $".{extension}";

string result = FileSystem.Path.ChangeExtension(path, extension);

result.Should().Be(expectedResult);
}

[SkippableTheory]
[AutoData]
public void ChangeExtension_WithLeadingDotInExtension_ShouldNotIncludeTwoDots(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ public void
result.Should().BeFalse();
}

[SkippableTheory]
[AutoData]
public void
EndsInDirectorySeparator_Span_WithTrailingAltDirectorySeparator_ShouldReturnTrue(
string path)
{
path += FileSystem.Path.AltDirectorySeparatorChar;

bool result = FileSystem.Path.EndsInDirectorySeparator(path.AsSpan());

result.Should().BeTrue();
}

[SkippableTheory]
[AutoData]
public void
Expand Down Expand Up @@ -71,6 +84,18 @@ public void
result.Should().BeFalse();
}

[SkippableTheory]
[AutoData]
public void EndsInDirectorySeparator_WithTrailingAltDirectorySeparator_ShouldReturnTrue(
string path)
{
path += FileSystem.Path.AltDirectorySeparatorChar;

bool result = FileSystem.Path.EndsInDirectorySeparator(path);

result.Should().BeTrue();
}

[SkippableTheory]
[AutoData]
public void EndsInDirectorySeparator_WithTrailingDirectorySeparator_ShouldReturnTrue(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ public abstract partial class GetExtensionTests<TFileSystem>
: FileSystemTestBase<TFileSystem>
where TFileSystem : IFileSystem
{
[SkippableFact]
public void GetExtension_Empty_ShouldReturnEmpty()
{
string? result = FileSystem.Path.GetExtension(string.Empty);

result.Should().BeEmpty();
}

[SkippableFact]
public void GetExtension_Null_ShouldReturnNull()
{
Expand All @@ -15,7 +23,7 @@ public void GetExtension_Null_ShouldReturnNull()

[SkippableTheory]
[AutoData]
public void GetExtension_ShouldReturnExtension(
public void GetExtension_ShouldReturnExtensionWithLeadingDot(
string directory, string filename, string extension)
{
string path = directory + FileSystem.Path.DirectorySeparatorChar + filename +
Expand All @@ -29,7 +37,7 @@ public void GetExtension_ShouldReturnExtension(
#if FEATURE_SPAN
[SkippableTheory]
[AutoData]
public void GetExtension_Span_ShouldReturnExtension(
public void GetExtension_Span_ShouldReturnExtensionWithLeadingDot(
string directory, string filename, string extension)
{
string path = directory + FileSystem.Path.DirectorySeparatorChar + filename +
Expand All @@ -52,4 +60,16 @@ public void GetExtension_TrailingDot_ShouldReturnEmptyString(

result.Should().Be("");
}

[SkippableTheory]
[AutoData]
public void GetExtension_StartingDot_ShouldReturnCompleteFileName(
string directory, string filename)
{
string path = directory + FileSystem.Path.DirectorySeparatorChar + "." + filename;

string result = FileSystem.Path.GetExtension(path);

result.Should().Be("." + filename);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ public abstract partial class GetFileNameTests<TFileSystem>
: FileSystemTestBase<TFileSystem>
where TFileSystem : IFileSystem
{
[SkippableFact]
public void GetFileName_EmptyString_ShouldReturnEmptyString()
{
string result = FileSystem.Path.GetFileName(string.Empty);

result.Should().Be(string.Empty);
}

[SkippableFact]
public void GetFileName_Null_ShouldReturnNull()
{
Expand All @@ -15,7 +23,7 @@ public void GetFileName_Null_ShouldReturnNull()

[SkippableTheory]
[AutoData]
public void GetFileName_ShouldReturnDirectory(string directory, string filename,
public void GetFileName_ShouldReturnFilename(string directory, string filename,
string extension)
{
string path = directory + FileSystem.Path.DirectorySeparatorChar + filename +
Expand All @@ -41,4 +49,28 @@ public void GetFileName_Span_ShouldReturnDirectory(
result.ToString().Should().Be(filename + "." + extension);
}
#endif

[SkippableTheory]
[InlineData("foo/", "", TestOS.All)]
[InlineData("bar\\", "", TestOS.Windows)]
[InlineData("/foo", "foo", TestOS.All)]
[InlineData("\\bar", "bar", TestOS.Windows)]
public void GetFileName_SpecialCases_ShouldReturnExpectedResult(
string? path, string? expected, TestOS operatingSystem)
{
Skip.IfNot(Test.RunsOn(operatingSystem));

string? result = FileSystem.Path.GetFileName(path);

result.Should().Be(expected);
}

[SkippableTheory]
[AutoData]
public void GetFileName_WithoutDirectory_ShouldReturnFilename(string filename)
{
string result = FileSystem.Path.GetFileName(filename);

result.Should().Be(filename);
}
}
Loading

0 comments on commit a43d1f0

Please sign in to comment.