Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
36ad43e
Initial plan
Copilot Oct 11, 2025
f722681
Add comprehensive IO tests for problematic filename characters
Copilot Oct 11, 2025
035d63e
Add tests for trailing spaces/periods and embedded whitespace scenarios
Copilot Oct 12, 2025
3ba215b
Remove platform-specific attributes from tests valid on all platforms
Copilot Oct 15, 2025
7012a70
Make dash-prefixed filename tests cross-platform and add documentatio…
Copilot Oct 20, 2025
8e479ba
Refactor tests to use centralized test data and clarify Windows trail…
Copilot Oct 20, 2025
c87ce5b
Refactor tests to use shared test data and fix comment about path nor…
Copilot Oct 21, 2025
c49e411
Refactor Delete, Move, and Exists tests to use shared test data
Copilot Oct 21, 2025
c15c7cf
Remove redundant UnixCreateWithControlCharacters test
Copilot Oct 21, 2025
667b416
Remove redundant UnixOnlyFileNames property from TestData.cs
Copilot Oct 21, 2025
f1fd1ae
Add proper test for issue #113120 - directory with trailing space ret…
Copilot Oct 21, 2025
3ca836c
Merge remote-tracking branch 'origin/main' into copilot/add-io-tests-…
Copilot Oct 21, 2025
eacaf94
Merge from main and fix build breaks by removing UsingNewNormalizatio…
Copilot Oct 21, 2025
ea8229f
Address review feedback: simplify file creation and use WindowsTraili…
Copilot Oct 21, 2025
3d54293
Apply code review suggestions: simplify test methods and update docum…
Copilot Oct 21, 2025
d193f6c
Update documentation comment to note need for .NET API docs
Copilot Oct 21, 2025
577e32b
Update src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/…
jkotas Oct 22, 2025
544af73
Update src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/…
jkotas Oct 22, 2025
ddd8ca5
Apply code review suggestions: fix comment formatting and use shared …
Copilot Oct 22, 2025
0e16c2f
Remove WindowsTrailingProblematicFileNamePairs and update Move test t…
Copilot Oct 23, 2025
2cafa5e
Update src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/…
jkotas Oct 23, 2025
f298275
Update src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/…
jkotas Oct 23, 2025
6c4ab55
Add trailing spaces/periods test cases for Unix and remove extra blan…
Copilot Oct 23, 2025
0a6afdc
Update src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/…
jkotas Oct 23, 2025
c520587
Apply code review suggestions: rename test method and format Theory/M…
Copilot Oct 23, 2025
1e109f0
Update src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/…
jkotas Oct 23, 2025
913715e
Fix formatting inconsistency: separate Theory and MemberData attribut…
Copilot Oct 23, 2025
5f8c022
Merge branch 'main' into copilot/add-io-tests-problematic-filenames
jkotas Oct 23, 2025
35e4643
Apply suggestions from code review
jkotas Oct 23, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -192,5 +192,55 @@ public void DirectoryWithTrailingSeparators(string trailing)
string[] files = GetEntries(root + (IsDirectoryInfo ? trailing : ""), "*", SearchOption.AllDirectories);
FSAssert.EqualWhenOrdered(new string[] { rootFile, nestedFile }, files);
}

[Theory]
[MemberData(nameof(TestData.ValidFileNames), MemberType = typeof(TestData))]
public void EnumerateFilesWithProblematicNames(string fileName)
{
DirectoryInfo testDir = Directory.CreateDirectory(GetTestFilePath());
File.Create(Path.Combine(testDir.FullName, fileName)).Dispose();

string[] files = GetEntries(testDir.FullName);
Assert.Single(files);
Assert.Contains(files, f => Path.GetFileName(f) == fileName);
}

[Theory]
[MemberData(nameof(TestData.WindowsTrailingProblematicFileNames), MemberType = typeof(TestData))]
[PlatformSpecific(TestPlatforms.Windows)]
public void WindowsEnumerateFilesWithTrailingSpacePeriod(string fileName)
{
// Files with trailing spaces/periods must be created with \\?\ on Windows
// but enumeration can find them.
DirectoryInfo testDir = Directory.CreateDirectory(GetTestFilePath());
string filePath = Path.Combine(testDir.FullName, fileName);
File.Create(@"\\?\" + filePath).Dispose();

string[] files = GetEntries(testDir.FullName);
Assert.Single(files);
Assert.Contains(files, f => Path.GetFileName(f) == fileName);
}

[Theory]
[MemberData(nameof(TestData.WindowsTrailingProblematicFileNames), MemberType = typeof(TestData))]
[PlatformSpecific(TestPlatforms.Windows)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/113120")]
public void WindowsEnumerateDirectoryWithTrailingSpacePeriod(string dirName)
{
DirectoryInfo parentDir = Directory.CreateDirectory(GetTestFilePath());
string problematicDirPath = Path.Combine(parentDir.FullName, dirName);
Directory.CreateDirectory(@"\\?\" + problematicDirPath);

string normalFileName = "normalfile.txt";
string filePath = Path.Combine(problematicDirPath, normalFileName);
File.Create(filePath).Dispose();

string[] files = GetEntries(problematicDirPath);
Assert.Single(files);

string returnedPath = files[0];
Assert.True(File.Exists(returnedPath),
$"File.Exists should work on path returned by Directory.GetFiles. Path: '{returnedPath}'");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,43 @@ public void DestinationFileIsTruncatedWhenItsLargerThanSourceFile()

Assert.Equal(content, File.ReadAllBytes(destPath));
}

[Theory]
[MemberData(nameof(TestData.ValidFileNames), MemberType = typeof(TestData))]
public void CopyWithProblematicNames(string fileName)
{
DirectoryInfo sourceDir = Directory.CreateDirectory(GetTestFilePath());
DirectoryInfo destDir = Directory.CreateDirectory(GetTestFilePath());
string sourcePath = Path.Combine(sourceDir.FullName, fileName);
string destPath = Path.Combine(destDir.FullName, fileName);

File.Create(sourcePath).Dispose();
Copy(sourcePath, destPath);

Assert.True(File.Exists(sourcePath));
Assert.True(File.Exists(destPath));
}

[Theory]
[MemberData(nameof(TestData.WindowsTrailingProblematicFileNames), MemberType = typeof(TestData))]
[PlatformSpecific(TestPlatforms.Windows)]
public void WindowsCopyWithTrailingSpacePeriod_ViaExtendedSyntax(string fileName)
{
// Windows path normalization strips trailing spaces/periods unless using \\?\ extended syntax.
DirectoryInfo sourceDir = Directory.CreateDirectory(GetTestFilePath());
DirectoryInfo destDir = Directory.CreateDirectory(GetTestFilePath());
string sourcePath = Path.Combine(sourceDir.FullName, fileName);
string destPath = Path.Combine(destDir.FullName, fileName);

// Create source with extended syntax (required for trailing spaces/periods)
File.Create(@"\\?\" + sourcePath).Dispose();

// Copy to destination with extended syntax (required for trailing spaces/periods)
Copy(@"\\?\" + sourcePath, @"\\?\" + destPath);

Assert.True(File.Exists(@"\\?\" + sourcePath));
Assert.True(File.Exists(@"\\?\" + destPath));
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,38 @@ public void WindowsAlternateDataStream_OnExisting(string streamName)
}
}

[Theory]
[MemberData(nameof(TestData.ValidFileNames), MemberType = typeof(TestData))]
public void CreateWithProblematicNames(string fileName)
{
DirectoryInfo testDir = Directory.CreateDirectory(GetTestFilePath());
string filePath = Path.Combine(testDir.FullName, fileName);
using (Create(filePath))
{
Assert.True(File.Exists(filePath));
}
}

[Theory]
[MemberData(nameof(TestData.WindowsTrailingProblematicFileNames), MemberType = typeof(TestData))]
[PlatformSpecific(TestPlatforms.Windows)]
public void WindowsCreateWithTrailingSpacePeriod_ViaExtendedSyntax(string fileName)
{
// Windows path normalization strips trailing spaces/periods unless using \\?\ extended syntax.
DirectoryInfo testDir = Directory.CreateDirectory(GetTestFilePath());
string filePath = Path.Combine(testDir.FullName, fileName);
string extendedPath = @"\\?\" + filePath;

using (Create(extendedPath))
{
Assert.True(File.Exists(extendedPath));
}

// Verify the file can be found via enumeration
string[] files = Directory.GetFiles(testDir.FullName);
Assert.Contains(files, f => Path.GetFileName(f) == fileName);
}

#endregion
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,35 @@ public void WindowsDeleteAlternateDataStream(string streamName)
Assert.True(testFile.Exists);
}

[Theory]
[MemberData(nameof(TestData.ValidFileNames), MemberType = typeof(TestData))]
public void DeleteWithProblematicNames(string fileName)
{
DirectoryInfo testDir = Directory.CreateDirectory(GetTestFilePath());
string filePath = Path.Combine(testDir.FullName, fileName);
File.Create(filePath).Dispose();
Assert.True(File.Exists(filePath));
Delete(filePath);
Assert.False(File.Exists(filePath));
}

[Theory]
[MemberData(nameof(TestData.WindowsTrailingProblematicFileNames), MemberType = typeof(TestData))]
[PlatformSpecific(TestPlatforms.Windows)]
public void WindowsDeleteWithTrailingSpacePeriod_ViaExtendedSyntax(string fileName)
{
// Files with trailing spaces/periods require \\?\ syntax on Windows
DirectoryInfo testDir = Directory.CreateDirectory(GetTestFilePath());
string filePath = Path.Combine(testDir.FullName, fileName);
string extendedPath = @"\\?\" + filePath;

File.Create(extendedPath).Dispose();
Assert.True(File.Exists(extendedPath));

Delete(extendedPath);
Assert.False(File.Exists(extendedPath));
}

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,33 @@ public void DirectoryWithComponentLongerThanMaxComponentAsPath_ReturnsFalse(stri
Assert.False(Exists(component));
}

[Theory]
[MemberData(nameof(TestData.ValidFileNames), MemberType = typeof(TestData))]
public void ExistsWithProblematicNames(string fileName)
{
DirectoryInfo testDir = Directory.CreateDirectory(GetTestFilePath());
string filePath = Path.Combine(testDir.FullName, fileName);
File.Create(filePath).Dispose();
Assert.True(Exists(filePath));
}

[Theory]
[MemberData(nameof(TestData.WindowsTrailingProblematicFileNames), MemberType = typeof(TestData))]
[PlatformSpecific(TestPlatforms.Windows)]
public void WindowsExistsWithTrailingSpacePeriod_ViaExtendedSyntax(string fileName)
{
// Files with trailing spaces/periods require \\?\ syntax on Windows
DirectoryInfo testDir = Directory.CreateDirectory(GetTestFilePath());
string filePath = Path.Combine(testDir.FullName, fileName);
string extendedPath = @"\\?\" + filePath;

File.Create(extendedPath).Dispose();
Assert.True(Exists(extendedPath));

// Without extended syntax, the trailing space/period is trimmed
Assert.False(Exists(filePath));
}

#endregion
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,5 +394,41 @@ public void MoveOntoExistingFileNoOverwrite()
Assert.True(File.Exists(destPath));
Assert.Equal(destContents, File.ReadAllBytes(destPath));
}

[Theory]
[MemberData(nameof(TestData.ValidFileNames), MemberType = typeof(TestData))]
public void MoveWithProblematicNames(string fileName)
{
DirectoryInfo testDir = Directory.CreateDirectory(GetTestFilePath());
string srcPath = Path.Combine(testDir.FullName, fileName);
string destPath = Path.Combine(testDir.FullName, fileName + "_moved");

File.Create(srcPath).Dispose();
Move(srcPath, destPath);

Assert.False(File.Exists(srcPath));
Assert.True(File.Exists(destPath));
}

[Theory]
[MemberData(nameof(TestData.WindowsTrailingProblematicFileNames), MemberType = typeof(TestData))]
[PlatformSpecific(TestPlatforms.Windows)]
public void WindowsMoveWithTrailingSpacePeriod_ViaExtendedSyntax(string fileName)
{
// Windows path normalization strips trailing spaces/periods unless using \\?\ extended syntax.
DirectoryInfo sourceDir = Directory.CreateDirectory(GetTestFilePath());
DirectoryInfo destDir = Directory.CreateDirectory(GetTestFilePath());
string sourcePath = Path.Combine(sourceDir.FullName, fileName);
string destPath = Path.Combine(destDir.FullName, fileName);

// Create source with extended syntax (required for trailing spaces/periods)
File.Create(@"\\?\" + sourcePath).Dispose();

// Move to destination with extended syntax (required for trailing spaces/periods)
Move(@"\\?\" + sourcePath, @"\\?\" + destPath);

Assert.False(File.Exists(@"\\?\" + sourcePath));
Assert.True(File.Exists(@"\\?\" + destPath));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,74 @@ public static TheoryData<char> TrailingCharacters
return data;
}
}

/// <summary>
/// Filenames with problematic but valid characters that work on all platforms.
/// These test scenarios from: https://www.dwheeler.com/essays/fixing-unix-linux-filenames.html
/// </summary>
public static TheoryData<string> ValidFileNames
{
get
{
TheoryData<string> data = new TheoryData<string>
{
// Leading spaces
" leading",
" leading",
" leading",
// Leading dots
".leading",
"..leading",
"...leading",
// Dash-prefixed names
"-",
"--",
"-filename",
"--filename",
// Embedded spaces and periods
"name with spaces",
"name with multiple spaces",
"name.with.periods",
"name with spaces.txt"
};

if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// On Unix, control characters are also valid in filenames
data.Add("file\tname"); // tab
data.Add("file\rname"); // carriage return
data.Add("file\vname"); // vertical tab
data.Add("file\fname"); // form feed
// Trailing spaces and periods are also valid on Unix (but problematic on Windows)
data.Add("trailing ");
data.Add("trailing ");
data.Add("trailing.");
data.Add("trailing..");
data.Add("trailing .");
data.Add("trailing. ");
}

return data;
}
}

/// <summary>
/// Filenames with trailing spaces or periods. On Windows, these require \\?\ prefix for creation
/// but can be enumerated. Direct string-based APIs will have the trailing characters stripped.
/// </summary>
public static TheoryData<string> WindowsTrailingProblematicFileNames
{
get
{
return new TheoryData<string>
{
"trailing ",
"trailing ",
"trailing.",
"trailing..",
"trailing .",
"trailing. "
};
}
}
}
Loading