Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 3 additions & 27 deletions Source/Testably.Abstractions.Testing/Helpers/Execute.LinuxPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,35 +34,25 @@ public override string GetFullPath(string path)
// We would ideally use realpath to do this, but it resolves symlinks and requires that the file actually exist.
string collapsedString = RemoveRelativeSegments(path, GetRootLength(path));

string result = collapsedString.Length == 0
? $"{DirectorySeparatorChar}"
: collapsedString;

if (result.Contains('\0', StringComparison.Ordinal))
if (collapsedString.Contains('\0', StringComparison.Ordinal))
{
throw ExceptionFactory.NullCharacterInPath(nameof(path));
}

return result;
return collapsedString;
}

#if FEATURE_PATH_RELATIVE
/// <inheritdoc cref="IPath.GetFullPath(string, string)" />
public override string GetFullPath(string path, string basePath)
{
path.EnsureValidArgument(_fileSystem, nameof(path));
basePath.EnsureValidArgument(_fileSystem, nameof(basePath));

if (!IsPathFullyQualified(basePath))
{
throw ExceptionFactory.BasePathNotFullyQualified(nameof(basePath));
}

if (IsPathFullyQualified(path))
{
return GetFullPath(path);
}

return GetFullPath(Combine(basePath, path));
}
#endif
Expand Down Expand Up @@ -125,21 +115,7 @@ protected override bool IsPartiallyQualified(string path)
/// </summary>
protected override string NormalizeDirectorySeparators(string path)
{
bool IsAlreadyNormalized()
{
for (int i = 0; i < path.Length - 1; i++)
{
if (IsDirectorySeparator(path[i]) &&
IsDirectorySeparator(path[i + 1]))
{
return false;
}
}

return true;
}

if (IsAlreadyNormalized())
if (string.IsNullOrEmpty(path))
{
return path;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ public ReadOnlySpan<char> TrimEndingDirectorySeparator(ReadOnlySpan<char> path)
/// <inheritdoc cref="IPath.TrimEndingDirectorySeparator(string)" />
public string TrimEndingDirectorySeparator(string path)
{
return EndsInDirectorySeparator(path) && path.Length != GetRootLength(path)
return path.Length != GetRootLength(path) && EndsInDirectorySeparator(path)
? path.Substring(0, path.Length - 1)
: path;
}
Expand Down Expand Up @@ -518,7 +518,7 @@ string NormalizePath(string path)

StringBuilder sb = new();

bool endsWithDirectorySeparator = false;
bool endsWithDirectorySeparator = true;
foreach (string path in paths)
{
if (path == null)
Expand Down Expand Up @@ -721,14 +721,11 @@ protected string RemoveRelativeSegments(string path, int rootLength)
skip--;
}

sb.Append(path.Substring(0, skip));

// Remove "//", "/./", and "/../" from the path by copying each character to the output,
// except the ones we're removing, such that the builder contains the normalized path
// at the end.
if (skip > 0)
{
sb.Append(path.Substring(0, skip));
}

for (int i = skip; i < path.Length; i++)
{
char c = path[i];
Expand Down Expand Up @@ -758,8 +755,7 @@ protected string RemoveRelativeSegments(string path, int rootLength)
path[i + 1] == '.' && path[i + 2] == '.')
{
// Unwind back to the last slash (and if there isn't one, clear out everything).
int s;
for (s = sb.Length - 1; s >= skip; s--)
for (int s = sb.Length - 1; s >= skip; s--)
{
if (IsDirectorySeparator(sb[s]))
{
Expand All @@ -771,11 +767,6 @@ protected string RemoveRelativeSegments(string path, int rootLength)
}
}

if (s < skip)
{
sb.Length = skip;
}

i += 2;
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,6 @@ public override string GetFullPath(string path)
{
path.EnsureValidArgument(_fileSystem, nameof(path));

if (path.Length >= 4
&& path[0] == '\\'
&& (path[1] == '\\' || path[1] == '?')
&& path[2] == '?'
&& path[3] == '\\')
{
return path;
}

string? pathRoot = GetPathRoot(path);
string? directoryRoot = GetPathRoot(_fileSystem.Storage.CurrentDirectory);
string candidate;
Expand All @@ -60,17 +51,15 @@ public override string GetFullPath(string path)
candidate = Combine(_fileSystem.Storage.CurrentDirectory, path);
}

string fullPath =
NormalizeDirectorySeparators(RemoveRelativeSegments(candidate,
GetRootLength(candidate)));
string fullPath = RemoveRelativeSegments(candidate, GetRootLength(candidate));
fullPath = fullPath.TrimEnd('.');

if (fullPath.Contains('\0', StringComparison.Ordinal))
{
throw ExceptionFactory.NullCharacterInPath(nameof(path));
}

if (fullPath.Length > 2 && fullPath[1] == ':' && fullPath[2] != DirectorySeparatorChar)
if (fullPath.Length > 2 && fullPath[2] != DirectorySeparatorChar && fullPath[1] == ':')
{
return fullPath.Substring(0, 2) + DirectorySeparatorChar + fullPath.Substring(2);
}
Expand All @@ -82,19 +71,13 @@ public override string GetFullPath(string path)
/// <inheritdoc cref="IPath.GetFullPath(string, string)" />
public override string GetFullPath(string path, string basePath)
{
path.EnsureValidArgument(_fileSystem, nameof(path));
basePath.EnsureValidArgument(_fileSystem, nameof(basePath));

if (!IsPathFullyQualified(basePath))
{
throw ExceptionFactory.BasePathNotFullyQualified(nameof(basePath));
}

if (IsPathFullyQualified(path))
{
return GetFullPath(path);
}

return GetFullPath(Combine(basePath, path));
}
#endif
Expand Down Expand Up @@ -127,9 +110,8 @@ public override char[] GetInvalidPathChars() =>
return null;
}

return IsPathRooted(path)
? path.Substring(0, GetRootLength(path))
: string.Empty;
return path.Substring(0, GetRootLength(path))
.Replace(AltDirectorySeparatorChar, DirectorySeparatorChar);
}

/// <inheritdoc cref="IPath.GetTempPath()" />
Expand All @@ -150,7 +132,7 @@ public override bool IsPathRooted(string? path)
protected override int GetRootLength(string path)
{
bool IsDeviceUNC(string p)
=> p.Length >= 8
=> p.Length > 7
&& IsDevice(p)
&& IsDirectorySeparator(p[7])
&& p[4] == 'U'
Expand All @@ -161,15 +143,15 @@ bool IsDevice(string p)
=> IsExtended(p)
||
(
p.Length >= 4
p.Length > 3
&& IsDirectorySeparator(p[0])
&& IsDirectorySeparator(p[1])
&& (p[2] == '.' || p[2] == '?')
&& IsDirectorySeparator(p[3])
);

bool IsExtended(string p)
=> p.Length >= 4
=> p.Length > 3
&& p[0] == '\\'
&& (p[1] == '\\' || p[1] == '?')
&& p[2] == '?'
Expand Down
58 changes: 31 additions & 27 deletions Tests/Testably.Abstractions.Tests/FileSystem/Path/CombineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,13 @@ public void Combine_2Paths_Rooted_ShouldReturnLastRootedPath(
}

[SkippableTheory]
[InlineAutoData("/foo/", "/bar/", "/bar/")]
[InlineAutoData("foo/", "/bar", "/bar")]
[InlineAutoData("foo/", "bar", "foo/bar")]
[InlineAutoData("foo", "/bar", "/bar")]
[InlineAutoData("foo", "bar", "foo/bar")]
[InlineAutoData("/foo", "bar/", "/foo/bar/")]
[InlineData("", "", "")]
[InlineData("/foo/", "/bar/", "/bar/")]
[InlineData("foo/", "/bar", "/bar")]
[InlineData("foo/", "bar", "foo/bar")]
[InlineData("foo", "/bar", "/bar")]
[InlineData("foo", "bar", "foo/bar")]
[InlineData("/foo", "bar/", "/foo/bar/")]
public void Combine_2Paths_ShouldReturnExpectedResult(
string path1, string path2, string expectedResult)
{
Expand Down Expand Up @@ -129,13 +130,14 @@ public void Combine_3Paths_Rooted_ShouldReturnLastRootedPath(
}

[SkippableTheory]
[InlineAutoData("/foo/", "/bar/", "/baz/", "/baz/")]
[InlineAutoData("foo/", "/bar/", "/baz", "/baz")]
[InlineAutoData("foo/", "bar", "/baz", "/baz")]
[InlineAutoData("foo", "/bar", "/baz", "/baz")]
[InlineAutoData("foo", "/bar/", "baz", "/bar/baz")]
[InlineAutoData("foo", "bar", "baz", "foo/bar/baz")]
[InlineAutoData("/foo", "bar", "baz/", "/foo/bar/baz/")]
[InlineData("", "", "", "")]
[InlineData("/foo/", "/bar/", "/baz/", "/baz/")]
[InlineData("foo/", "/bar/", "/baz", "/baz")]
[InlineData("foo/", "bar", "/baz", "/baz")]
[InlineData("foo", "/bar", "/baz", "/baz")]
[InlineData("foo", "/bar/", "baz", "/bar/baz")]
[InlineData("foo", "bar", "baz", "foo/bar/baz")]
[InlineData("/foo", "bar", "baz/", "/foo/bar/baz/")]
public void Combine_3Paths_ShouldReturnExpectedResult(
string path1, string path2, string path3, string expectedResult)
{
Expand Down Expand Up @@ -224,13 +226,14 @@ public void Combine_4Paths_Rooted_ShouldReturnLastRootedPath(
}

[SkippableTheory]
[InlineAutoData("/foo/", "/bar/", "/baz/", "/muh/", "/muh/")]
[InlineAutoData("foo/", "/bar/", "/baz/", "/muh", "/muh")]
[InlineAutoData("foo/", "bar", "/baz", "/muh", "/muh")]
[InlineAutoData("foo", "/bar", "/baz", "/muh", "/muh")]
[InlineAutoData("foo", "/bar/", "baz/", "muh", "/bar/baz/muh")]
[InlineAutoData("foo", "bar", "baz", "muh", "foo/bar/baz/muh")]
[InlineAutoData("/foo", "bar", "baz", "muh/", "/foo/bar/baz/muh/")]
[InlineData("", "", "", "", "")]
[InlineData("/foo/", "/bar/", "/baz/", "/muh/", "/muh/")]
[InlineData("foo/", "/bar/", "/baz/", "/muh", "/muh")]
[InlineData("foo/", "bar", "/baz", "/muh", "/muh")]
[InlineData("foo", "/bar", "/baz", "/muh", "/muh")]
[InlineData("foo", "/bar/", "baz/", "muh", "/bar/baz/muh")]
[InlineData("foo", "bar", "baz", "muh", "foo/bar/baz/muh")]
[InlineData("/foo", "bar", "baz", "muh/", "/foo/bar/baz/muh/")]
public void Combine_4Paths_ShouldReturnExpectedResult(
string path1, string path2, string path3, string path4, string expectedResult)
{
Expand Down Expand Up @@ -344,13 +347,14 @@ public void Combine_ParamPaths_Rooted_ShouldReturnLastRootedPath(
}

[SkippableTheory]
[InlineAutoData("/foo/", "/bar/", "/baz/", "/muh/", "/maeh/", "/maeh/")]
[InlineAutoData("foo/", "/bar/", "/baz/", "/muh", "/maeh", "/maeh")]
[InlineAutoData("foo/", "bar", "/baz", "/muh", "/maeh", "/maeh")]
[InlineAutoData("foo", "/bar", "/baz", "/muh", "/maeh", "/maeh")]
[InlineAutoData("foo", "/bar/", "baz/", "muh/", "maeh", "/bar/baz/muh/maeh")]
[InlineAutoData("foo", "bar", "baz", "muh", "maeh", "foo/bar/baz/muh/maeh")]
[InlineAutoData("/foo", "bar", "baz", "muh", "maeh/", "/foo/bar/baz/muh/maeh/")]
[InlineData("", "", "", "", "", "")]
[InlineData("/foo/", "/bar/", "/baz/", "/muh/", "/maeh/", "/maeh/")]
[InlineData("foo/", "/bar/", "/baz/", "/muh", "/maeh", "/maeh")]
[InlineData("foo/", "bar", "/baz", "/muh", "/maeh", "/maeh")]
[InlineData("foo", "/bar", "/baz", "/muh", "/maeh", "/maeh")]
[InlineData("foo", "/bar/", "baz/", "muh/", "maeh", "/bar/baz/muh/maeh")]
[InlineData("foo", "bar", "baz", "muh", "maeh", "foo/bar/baz/muh/maeh")]
[InlineData("/foo", "bar", "baz", "muh", "maeh/", "/foo/bar/baz/muh/maeh/")]
public void Combine_ParamPaths_ShouldReturnExpectedResult(
string path1, string path2, string path3, string path4, string path5, string expectedResult)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ public void GetDirectoryName_ShouldReplaceAltDirectorySeparator(
[SkippableTheory]
[InlineData("foo//bar/file", "foo/bar", TestOS.All)]
[InlineData("foo///bar/file", "foo/bar", TestOS.All)]
[InlineData("//foo//bar/file", "/foo/bar", TestOS.Linux | TestOS.Mac)]
[InlineData(@"foo\\bar/file", "foo/bar", TestOS.Windows)]
[InlineData(@"foo\\\bar/file", "foo/bar", TestOS.Windows)]
public void GetDirectoryName_ShouldNormalizeDirectorySeparators(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@ public void GetFullPath_Dot_ShouldReturnToCurrentDirectory()
}

[SkippableTheory]
[InlineData(@"C:\..", @"C:\", TestOS.Windows)]
[InlineData(@"C:\foo", @"C:\foo", TestOS.Windows)]
[InlineData(@"C:\foo\", @"C:\foo\", TestOS.Windows)]
[InlineData(@"\\?\", @"\\?\", TestOS.Windows)]
[InlineData(@"\\?\foo", @"\\?\foo", TestOS.Windows)]
[InlineData(@"\??\", @"\??\", TestOS.Windows)]
[InlineData(@"\??\BAR", @"\??\BAR", TestOS.Windows)]
[InlineData("/foo", "/foo", TestOS.Linux | TestOS.Mac)]
[InlineData("/foo/", "/foo/", TestOS.Linux | TestOS.Mac)]
public void GetFullPath_EdgeCases_ShouldReturnExpectedValue(string path,
string expected, TestOS operatingSystem)
public void GetFullPath_EdgeCases_ShouldReturnExpectedValue(
string path, string expected, TestOS operatingSystem)
{
Skip.IfNot(Test.RunsOn(operatingSystem));

Expand All @@ -37,6 +40,19 @@ public void GetFullPath_EdgeCases_ShouldReturnExpectedValue(string path,
result.Should().Be(expected);
}

[SkippableTheory]
[InlineData(@"C:\..", @"C:\", TestOS.Windows)]
[InlineData("/..", "/", TestOS.Linux | TestOS.Mac)]
public void GetFullPath_ParentOfRoot_ShouldReturnRoot(string path,
string expected, TestOS operatingSystem)
{
Skip.IfNot(Test.RunsOn(operatingSystem));

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

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

#if FEATURE_PATH_RELATIVE
[SkippableFact]
public void GetFullPath_Relative_NullBasePath_ShouldThrowArgumentNullException()
Expand Down Expand Up @@ -74,7 +90,7 @@ public void GetFullPath_Relative_RelativeBasePath_ShouldThrowArgumentException()
[InlineData("top/../most/file", "foo/bar", "foo/bar/most/file")]
[InlineData("top/../most/../dir/file", "foo", "foo/dir/file")]
[InlineData("top/../../most/file", "foo/bar", "foo/most/file")]
public void GetFullPath_Relative_ShouldNormalizeProvidedPath(string input, string basePath,
public void GetFullPath_Relative_ShouldRemoveRelativeSegments(string input, string basePath,
string expected)
{
string expectedRootedPath = FileTestHelper.RootDrive(Test,
Expand Down Expand Up @@ -140,11 +156,29 @@ public void
result.Should().Be(expectedFullPath);
}

[SkippableTheory]
[InlineData(@"X:\foo/bar", @"X:\foo\bar")]
[InlineData(@"Y:\foo/bar/", @"Y:\foo\bar\")]
public void GetFullPath_ShouldFlipAltDirectorySeparators(string path,
string expected)
{
Skip.IfNot(Test.RunsOnWindows);

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

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

[SkippableTheory]
[InlineData("top/../most/file", "most/file")]
[InlineData("top/../most/../dir/file", "dir/file")]
[InlineData("top/../../most/file", "most/file")]
public void GetFullPath_ShouldNormalizeProvidedPath(string input, string expected)
[InlineData("top/..//../most/file", "most/file")]
[InlineData("top//most//file", "top/most/file")]
[InlineData("top//.//file", "top/file")]
[InlineData("top/most/file/..", "top/most")]
[InlineData("top/..most/file", "top/..most/file")]
public void GetFullPath_ShouldRemoveRelativeSegments(string input, string expected)
{
string expectedRootedPath = FileTestHelper.RootDrive(Test,
expected.Replace('/', FileSystem.Path.DirectorySeparatorChar));
Expand Down
Loading