diff options
author | Marek Safar <marek.safar@gmail.com> | 2018-04-02 00:54:57 +0300 |
---|---|---|
committer | Marek Safar <marek.safar@gmail.com> | 2018-04-02 00:54:57 +0300 |
commit | f4951179bb053a9264f063c824720d6237ae55f1 (patch) | |
tree | f7beda10c1e1c9f8a8f4c21a000993598ea0502c /src/System.IO.FileSystem/tests | |
parent | e89c5c9646d29d74a069f8f9630e35858b6bb076 (diff) | |
parent | 79b3c40e4322fd1778ad075214c90af93e5d2adf (diff) |
Merge remote-tracking branch 'upstream/release/2.1' into 2.1-merge
Diffstat (limited to 'src/System.IO.FileSystem/tests')
48 files changed, 2587 insertions, 220 deletions
diff --git a/src/System.IO.FileSystem/tests/Base/BaseGetSetTimes.cs b/src/System.IO.FileSystem/tests/Base/BaseGetSetTimes.cs index f650ae07f3..865cde2ad8 100644 --- a/src/System.IO.FileSystem/tests/Base/BaseGetSetTimes.cs +++ b/src/System.IO.FileSystem/tests/Base/BaseGetSetTimes.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Threading; using Xunit; namespace System.IO.Tests @@ -15,6 +16,8 @@ namespace System.IO.Tests public abstract T GetExistingItem(); public abstract T GetMissingItem(); + public abstract string GetItemPath(T item); + public abstract IEnumerable<TimeFunction> TimeFunctions(bool requiresRoundtripping = false); public class TimeFunction : Tuple<SetTime, GetTime, DateTimeKind> @@ -68,19 +71,35 @@ namespace System.IO.Tests } [Fact] - [ActiveIssue(26349, TestPlatforms.AnyUnix)] - public void TimesIncludeMillisecondPart() + [PlatformSpecific(TestPlatforms.Linux)] // Windows tested below, and OSX does not currently support millisec granularity + public void TimesIncludeMillisecondPart_Linux() { T item = GetExistingItem(); + + string driveFormat = new DriveInfo(GetItemPath(item)).DriveFormat; + Assert.All(TimeFunctions(), (function) => { var msec = 0; - for (int i = 0; i < 3; i++) + for (int i = 0; i < 5; i++) { - msec = function.Getter(item).Millisecond; + DateTime time = function.Getter(item); + msec = time.Millisecond; + if (msec != 0) break; + // This case should only happen 1/1000 times, unless the OS/Filesystem does + // not support millisecond granularity. + + // If it's 1/1000, or low granularity, this may help: + Thread.Sleep(1234); + + // If it's the OS/Filesystem often returns 0 for the millisecond part, this may + // help prove it. This should only be written 1/1000 runs, unless the test is going to + // fail. + Console.WriteLine($"## TimesIncludeMillisecondPart got a file time of {time.ToString("o")} on {driveFormat}"); + item = GetExistingItem(); // try a new file/directory } @@ -88,6 +107,49 @@ namespace System.IO.Tests }); } + + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] // Breaking out Windows as it passes no problem there + public void TimesIncludeMillisecondPart_Windows() + { + T item = GetExistingItem(); + Assert.All(TimeFunctions(), (function) => + { + var msec = 0; + for (int i = 0; i < 5; i++) + { + DateTime time = function.Getter(item); + msec = time.Millisecond; + if (msec != 0) + break; + + // This case should only happen 1/1000 times, unless the OS/Filesystem does + // not support millisecond granularity. + + // If it's 1/1000, or low granularity, this may help: + Thread.Sleep(1234); + + item = GetExistingItem(); // try a new file/directory + } + + Assert.NotEqual(0, msec); + }); + } + + [Fact] + // OSX does not currently support millisec granularity: use this test as a canary to flag + // if this ever changes so we can enable the actual test + [PlatformSpecific(TestPlatforms.OSX)] + public void TimesIncludeMillisecondPart_OSX() + { + T item = GetExistingItem(); + Assert.All(TimeFunctions(), (function) => + { + DateTime time = function.Getter(item); + Assert.Equal(0, time.Millisecond); + }); + } + protected void ValidateSetTimes(T item, DateTime beforeTime, DateTime afterTime) { Assert.All(TimeFunctions(), (function) => diff --git a/src/System.IO.FileSystem/tests/Base/FileGetSetAttributes.cs b/src/System.IO.FileSystem/tests/Base/FileGetSetAttributes.cs index bdb4cee984..59d784fbe9 100644 --- a/src/System.IO.FileSystem/tests/Base/FileGetSetAttributes.cs +++ b/src/System.IO.FileSystem/tests/Base/FileGetSetAttributes.cs @@ -12,12 +12,12 @@ namespace System.IO.Tests [Theory] [InlineData(FileAttributes.ReadOnly)] [InlineData(FileAttributes.Normal)] - [PlatformSpecific(TestPlatforms.AnyUnix)] // Unix valid file attributes - public void UnixAttributeSetting(FileAttributes attr) + [PlatformSpecific(TestPlatforms.AnyUnix)] + public void SettingAttributes_Unix(FileAttributes attributes) { string path = CreateItem(); - SetAttributes(path, attr); - Assert.Equal(attr, GetAttributes(path)); + SetAttributes(path, attributes); + Assert.Equal(attributes, GetAttributes(path)); SetAttributes(path, 0); } @@ -29,12 +29,12 @@ namespace System.IO.Tests [InlineData(FileAttributes.Normal)] [InlineData(FileAttributes.Temporary)] [InlineData(FileAttributes.ReadOnly | FileAttributes.Hidden)] - [PlatformSpecific(TestPlatforms.Windows)] // Valid Windows file attribute - public void WindowsAttributeSetting(FileAttributes attr) + [PlatformSpecific(TestPlatforms.Windows)] + public void SettingAttributes_Windows(FileAttributes attributes) { string path = CreateItem(); - SetAttributes(path, attr); - Assert.Equal(attr, GetAttributes(path)); + SetAttributes(path, attributes); + Assert.Equal(attributes, GetAttributes(path)); SetAttributes(path, 0); } @@ -44,11 +44,11 @@ namespace System.IO.Tests [InlineData(FileAttributes.SparseFile)] [InlineData(FileAttributes.ReparsePoint)] [InlineData(FileAttributes.Compressed)] - [PlatformSpecific(TestPlatforms.AnyUnix)] // Unix invalid file attributes - public void UnixInvalidAttributes(FileAttributes attr) + [PlatformSpecific(TestPlatforms.AnyUnix)] + public void SettingInvalidAttributes_Unix(FileAttributes attributes) { string path = CreateItem(); - SetAttributes(path, attr); + SetAttributes(path, attributes); Assert.Equal(FileAttributes.Normal, GetAttributes(path)); } @@ -58,11 +58,36 @@ namespace System.IO.Tests [InlineData(FileAttributes.SparseFile)] [InlineData(FileAttributes.ReparsePoint)] [InlineData(FileAttributes.Compressed)] - [PlatformSpecific(TestPlatforms.Windows)] // Invalid Windows file attributes - public void WindowsInvalidAttributes(FileAttributes attr) + [PlatformSpecific(TestPlatforms.Windows)] + public void SettingInvalidAttributes_Windows(FileAttributes attributes) { string path = CreateItem(); - SetAttributes(path, attr); + SetAttributes(path, attributes); + Assert.Equal(FileAttributes.Normal, GetAttributes(path)); + } + + [Theory, + InlineData(":bar"), + InlineData(":bar:$DATA")] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void GettingAndSettingAttributes_AlternateDataStream_Windows(string streamName) + { + string path = CreateItem(); + streamName = path + streamName; + File.Create(streamName); + + FileAttributes attributes = GetAttributes(streamName); + Assert.NotEqual((FileAttributes)0, attributes); + Assert.NotEqual((FileAttributes)(-1), attributes); + + // Attributes are shared for the file and all streams + SetAttributes(streamName, FileAttributes.Hidden); + Assert.Equal(FileAttributes.Hidden, GetAttributes(streamName)); + Assert.Equal(FileAttributes.Hidden, GetAttributes(path)); + + SetAttributes(path, FileAttributes.Normal); + Assert.Equal(FileAttributes.Normal, GetAttributes(streamName)); Assert.Equal(FileAttributes.Normal, GetAttributes(path)); } } diff --git a/src/System.IO.FileSystem/tests/Base/StaticGetSetTimes.cs b/src/System.IO.FileSystem/tests/Base/StaticGetSetTimes.cs index 6444ff203f..12ee8997fd 100644 --- a/src/System.IO.FileSystem/tests/Base/StaticGetSetTimes.cs +++ b/src/System.IO.FileSystem/tests/Base/StaticGetSetTimes.cs @@ -10,6 +10,8 @@ namespace System.IO.Tests { public override string GetMissingItem() => GetTestFilePath(); + public override string GetItemPath(string item) => item; + [Fact] public void NullPath_ThrowsArgumentNullException() { diff --git a/src/System.IO.FileSystem/tests/Directory/CreateDirectory.cs b/src/System.IO.FileSystem/tests/Directory/CreateDirectory.cs index bfd60bb977..021a05b949 100644 --- a/src/System.IO.FileSystem/tests/Directory/CreateDirectory.cs +++ b/src/System.IO.FileSystem/tests/Directory/CreateDirectory.cs @@ -34,14 +34,20 @@ namespace System.IO.Tests } [Theory, MemberData(nameof(PathsWithInvalidCharacters))] - public void PathWithInvalidCharactersAsPath_ThrowsArgumentException(string invalidPath) + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] + public void PathWithInvalidCharactersAsPath_Desktop(string invalidPath) { - if (invalidPath.Equals(@"\\?\") && !PathFeatures.IsUsingLegacyPathNormalization()) - AssertExtensions.ThrowsAny<IOException, UnauthorizedAccessException>(() => Create(invalidPath)); - else if (invalidPath.Contains(@"\\?\") && !PathFeatures.IsUsingLegacyPathNormalization()) - Assert.Throws<DirectoryNotFoundException>(() => Create(invalidPath)); + Assert.Throws<ArgumentException>(() => Create(invalidPath)); + } + + [Theory, MemberData(nameof(PathsWithInvalidCharacters))] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void PathWithInvalidCharactersAsPath_Core(string invalidPath) + { + if (invalidPath.Contains('\0')) + Assert.Throws<ArgumentException>("path", () => Create(invalidPath)); else - Assert.Throws<ArgumentException>(() => Create(invalidPath)); + Assert.Throws<IOException>(() => Create(invalidPath)); } [Fact] @@ -203,11 +209,28 @@ namespace System.IO.Tests #region PlatformSpecific [Theory, MemberData(nameof(PathsWithInvalidColons))] - [PlatformSpecific(TestPlatforms.Windows)] // invalid colons throws ArgumentException - [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Versions of netfx older than 4.6.2 throw an ArgumentException instead of NotSupportedException. Until all of our machines run netfx against the actual latest version, these will fail.")] - public void PathWithInvalidColons_ThrowsNotSupportedException(string invalidPath) + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] + public void PathWithInvalidColons_ThrowsNotSupportedException_Desktop(string invalidPath) { - Assert.Throws<NotSupportedException>(() => Create(invalidPath)); + if (PathFeatures.IsUsingLegacyPathNormalization()) + { + Assert.Throws<ArgumentException>(() => Create(invalidPath)); + } + else + { + Assert.Throws<NotSupportedException>(() => Create(invalidPath)); + } + } + + [Theory, MemberData(nameof(PathsWithInvalidColons))] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void PathsWithInvalidColons_ThrowIOException_Core(string invalidPath) + { + // You can't actually create a directory with a colon in it. It was a preemptive + // check, now we let the OS give us failures on usage. + Assert.ThrowsAny<IOException>(() => Create(invalidPath)); } [ConditionalFact(nameof(AreAllLongPathsAvailable))] @@ -308,14 +331,33 @@ namespace System.IO.Tests } [Theory, - MemberData(nameof(WhiteSpace))] - [PlatformSpecific(TestPlatforms.Windows)] // whitespace as path throws ArgumentException on Windows - public void WindowsWhiteSpaceAsPath_ThrowsArgumentException(string path) + MemberData(nameof(SimpleWhiteSpace))] + [PlatformSpecific(TestPlatforms.Windows)] + public void WindowsSimpleWhiteSpaceAsPath_ThrowsArgumentException(string path) + { + Assert.Throws<ArgumentException>(() => Create(path)); + } + + [Theory, + MemberData(nameof(ControlWhiteSpace))] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] + public void WindowsControlWhiteSpaceAsPath_ThrowsArgumentException_Desktop(string path) { Assert.Throws<ArgumentException>(() => Create(path)); } [Theory, + MemberData(nameof(ControlWhiteSpace))] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void WindowsWhiteSpaceAsPath_ThrowsIOException_Core(string path) + { + Assert.Throws<IOException>(() => Create(path)); + } + + + [Theory, MemberData(nameof(WhiteSpace))] [PlatformSpecific(TestPlatforms.AnyUnix)] // whitespace as path allowed public void UnixWhiteSpaceAsPath_Allowed(string path) @@ -397,14 +439,24 @@ namespace System.IO.Tests } [Theory, - MemberData(nameof(PathsWithAlternativeDataStreams))] + MemberData(nameof(PathsWithColons))] [PlatformSpecific(TestPlatforms.Windows)] // alternate data streams - public void PathWithAlternateDataStreams_ThrowsNotSupportedException(string path) + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] + public void PathWithColons_ThrowsNotSupportedException_Desktop(string path) { Assert.Throws<NotSupportedException>(() => Create(path)); } [Theory, + MemberData(nameof(PathsWithColons))] + [PlatformSpecific(TestPlatforms.Windows)] // alternate data streams + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void PathWithColons_ThrowsIOException_Core(string path) + { + Assert.ThrowsAny<IOException>(() => Create(Path.Combine(TestDirectory, path))); + } + + [Theory, MemberData(nameof(PathsWithReservedDeviceNames))] [PlatformSpecific(TestPlatforms.Windows)] // device name prefixes public void PathWithReservedDeviceNameAsPath_ThrowsDirectoryNotFoundException(string path) @@ -424,20 +476,39 @@ namespace System.IO.Tests [Theory, MemberData(nameof(UncPathsWithoutShareName))] - [PlatformSpecific(TestPlatforms.Windows)] // UNC shares - public void UncPathWithoutShareNameAsPath_ThrowsArgumentException(string path) + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] + public void UncPathWithoutShareNameAsPath_ThrowsArgumentException_Desktop(string path) { Assert.Throws<ArgumentException>(() => Create(path)); } + [Theory, + MemberData(nameof(UncPathsWithoutShareName))] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void UncPathWithoutShareNameAsPath_ThrowsIOException_Core(string path) + { + Assert.ThrowsAny<IOException>(() => Create(path)); + } + [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // UNC shares - public void UNCPathWithOnlySlashes() + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] + public void UNCPathWithOnlySlashes_Desktop() { Assert.Throws<ArgumentException>(() => Create("//")); } [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void UNCPathWithOnlySlashes_Core() + { + Assert.ThrowsAny<IOException>(() => Create("//")); + } + + [Fact] [PlatformSpecific(TestPlatforms.Windows)] // drive labels [ActiveIssue(20117, TargetFrameworkMonikers.Uap)] public void CDriveCase() diff --git a/src/System.IO.FileSystem/tests/Directory/Delete.cs b/src/System.IO.FileSystem/tests/Directory/Delete.cs index 98ab80c3ef..52f5b9dfad 100644 --- a/src/System.IO.FileSystem/tests/Directory/Delete.cs +++ b/src/System.IO.FileSystem/tests/Directory/Delete.cs @@ -208,7 +208,7 @@ namespace System.IO.Tests [Trait(XunitConstants.Category, XunitConstants.RequiresElevation)] public void Unix_NotFoundDirectory_ReadOnlyVolume() { - if (PlatformDetection.IsRedHatFamily6) + if (PlatformDetection.IsRedHatFamily6 || PlatformDetection.IsAlpine) return; // [ActiveIssue(https://github.com/dotnet/corefx/issues/21920)] ReadOnly_FileSystemHelper(readOnlyDirectory => diff --git a/src/System.IO.FileSystem/tests/Directory/EnumerableTests.cs b/src/System.IO.FileSystem/tests/Directory/EnumerableTests.cs index e9c6028c56..7d604b6f1f 100644 --- a/src/System.IO.FileSystem/tests/Directory/EnumerableTests.cs +++ b/src/System.IO.FileSystem/tests/Directory/EnumerableTests.cs @@ -32,6 +32,17 @@ namespace System.IO.Tests } } + [Fact] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void EnumerateDirectories_NonBreakingSpace() + { + DirectoryInfo rootDirectory = Directory.CreateDirectory(GetTestFilePath()); + DirectoryInfo subDirectory1 = rootDirectory.CreateSubdirectory("\u00A0"); + DirectoryInfo subDirectory2 = subDirectory1.CreateSubdirectory(GetTestFileName()); + + FSAssert.EqualWhenOrdered(new string[] { subDirectory1.FullName, subDirectory2.FullName }, Directory.EnumerateDirectories(rootDirectory.FullName, string.Empty, SearchOption.AllDirectories)); + } + class ThreadSafeRepro { volatile IEnumerator<string> _enumerator; diff --git a/src/System.IO.FileSystem/tests/Directory/GetFileSystemEntries_str.cs b/src/System.IO.FileSystem/tests/Directory/GetFileSystemEntries_str.cs index 8a4175705a..01402a222b 100644 --- a/src/System.IO.FileSystem/tests/Directory/GetFileSystemEntries_str.cs +++ b/src/System.IO.FileSystem/tests/Directory/GetFileSystemEntries_str.cs @@ -11,7 +11,7 @@ namespace System.IO.Tests { #region Utilities - public static TheoryData WindowsInvalidUnixValid = new TheoryData<string> { " ", " ", "\n", ">", "<", "\t" }; + protected virtual bool TestFiles { get { return true; } } // True if the virtual GetEntries mmethod returns files protected virtual bool TestDirectories { get { return true; } } // True if the virtual GetEntries mmethod returns Directories @@ -175,42 +175,125 @@ namespace System.IO.Tests } } + [Fact] + public void HiddenFilesAreReturned() + { + // Note that APIs that take EnumerationOptions do NOT find hidden files by default + + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, GetTestFileName())); + + // Put a period in front to make it hidden on Unix + FileInfo fileTwo = new FileInfo(Path.Combine(testDirectory.FullName, "." + GetTestFileName())); + fileOne.Create().Dispose(); + fileTwo.Create().Dispose(); + if (PlatformDetection.IsWindows) + fileTwo.Attributes = fileTwo.Attributes | FileAttributes.Hidden; + + if (TestFiles) + { + FSAssert.EqualWhenOrdered(new string[] { fileOne.FullName, fileTwo.FullName }, GetEntries(testDirectory.FullName)); + } + else + { + Assert.Empty(GetEntries(testDirectory.FullName)); + } + } + #endregion #region PlatformSpecific [Fact] - public void InvalidPath() + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] + public void InvalidPath_Desktop() { foreach (char invalid in Path.GetInvalidFileNameChars()) { - if (invalid == '/' || invalid == '\\') - { - Assert.Throws<DirectoryNotFoundException>(() => GetEntries(Path.Combine(TestDirectory, string.Format("te{0}st", invalid.ToString())))); - } - else if (invalid == ':') + string badPath = string.Format($"{TestDirectory}{Path.DirectorySeparatorChar}te{invalid}st"); + switch (invalid) { - if (FileSystemDebugInfo.IsCurrentDriveNTFS()) - Assert.Throws<NotSupportedException>(() => GetEntries(Path.Combine(TestDirectory, string.Format("te{0}st", invalid.ToString())))); + case '/': + case '\\': + Assert.Throws<DirectoryNotFoundException>(() => GetEntries(badPath)); + break; + case ':': + Assert.Throws<NotSupportedException>(() => GetEntries(badPath)); + break; + case '\0': + Assert.Throws<ArgumentException>(() => GetEntries(badPath)); + break; + default: + Assert.Throws<ArgumentException>(() => GetEntries(badPath)); + break; } - else + } + } + + [Fact] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void InvalidPath_Core() + { + foreach (char invalid in Path.GetInvalidFileNameChars()) + { + string badPath = string.Format($"{TestDirectory}{Path.DirectorySeparatorChar}te{invalid}st"); + switch (invalid) { - Assert.Throws<ArgumentException>(() => GetEntries(Path.Combine(TestDirectory, string.Format("te{0}st", invalid.ToString())))); + case '/': + case '\\': + case ':': + Assert.Throws<DirectoryNotFoundException>(() => GetEntries(badPath)); + break; + case '\0': + Assert.Throws<ArgumentException>(() => GetEntries(badPath)); + break; + default: + Assert.Throws<IOException>(() => GetEntries(badPath)); + break; } } } [Theory, - MemberData(nameof(WindowsInvalidUnixValid))] - [PlatformSpecific(TestPlatforms.Windows)] // Windows-only Invalid chars in path - public void WindowsInvalidCharsPath(string invalid) + InlineData(" "), + InlineData(" ")] + [PlatformSpecific(TestPlatforms.Windows)] + public void WindowsWhitespaceOnlyPath(string invalid) + { + Assert.Throws<ArgumentException>(() => GetEntries(invalid)); + } + + [Theory, + InlineData("\n"), + InlineData(">"), + InlineData("<"), + InlineData("\t")] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] + public void WindowsInvalidCharsPath_Desktop(string invalid) { - Assert.Throws<ArgumentException>(() => GetEntries(invalid)); } [Theory, - MemberData(nameof(WindowsInvalidUnixValid))] + InlineData("\n"), + InlineData(">"), + InlineData("<"), + InlineData("\t")] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void WindowsInvalidCharsPath_Core(string invalid) + { + Assert.Throws<IOException>(() => GetEntries(invalid)); + } + + [Theory, + InlineData(" "), + InlineData(" "), + InlineData("\n"), + InlineData(">"), + InlineData("<"), + InlineData("\t")] [PlatformSpecific(TestPlatforms.AnyUnix)] // Unix-only valid chars in file path public void UnixValidCharsFilePath(string valid) { @@ -226,7 +309,12 @@ namespace System.IO.Tests } [Theory, - MemberData(nameof(WindowsInvalidUnixValid))] + InlineData(" "), + InlineData(" "), + InlineData("\n"), + InlineData(">"), + InlineData("<"), + InlineData("\t")] [PlatformSpecific(TestPlatforms.AnyUnix)] // Windows-only invalid chars in directory path public void UnixValidCharsDirectoryPath(string valid) { diff --git a/src/System.IO.FileSystem/tests/Directory/GetFileSystemEntries_str_str.cs b/src/System.IO.FileSystem/tests/Directory/GetFileSystemEntries_str_str.cs index a352bf6529..e96881bc46 100644 --- a/src/System.IO.FileSystem/tests/Directory/GetFileSystemEntries_str_str.cs +++ b/src/System.IO.FileSystem/tests/Directory/GetFileSystemEntries_str_str.cs @@ -985,7 +985,12 @@ namespace System.IO.Tests } [Theory, - MemberData(nameof(WindowsInvalidUnixValid))] + InlineData(" "), + InlineData(" "), + InlineData("\n"), + InlineData(">"), + InlineData("<"), + InlineData("\t")] [PlatformSpecific(TestPlatforms.AnyUnix)] // Unix-valid chars in file search patterns public void UnixSearchPatternFileValidChar(string valid) { @@ -999,7 +1004,12 @@ namespace System.IO.Tests } [Theory, - MemberData(nameof(WindowsInvalidUnixValid))] + InlineData(" "), + InlineData(" "), + InlineData("\n"), + InlineData(">"), + InlineData("<"), + InlineData("\t")] [PlatformSpecific(TestPlatforms.AnyUnix)] // Unix-valid chars in directory search patterns public void UnixSearchPatternDirectoryValidChar(string valid) { diff --git a/src/System.IO.FileSystem/tests/Directory/Move.cs b/src/System.IO.FileSystem/tests/Directory/Move.cs index 8c21954c03..1da296c1c3 100644 --- a/src/System.IO.FileSystem/tests/Directory/Move.cs +++ b/src/System.IO.FileSystem/tests/Directory/Move.cs @@ -242,8 +242,9 @@ namespace System.IO.Tests } [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Wild characters in path, wild chars are normal chars on Unix - public void WindowsWildCharacterPath() + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] + public void WindowsWildCharacterPath_Desktop() { Assert.Throws<ArgumentException>(() => Move("*", GetTestFilePath())); Assert.Throws<ArgumentException>(() => Move(TestDirectory, "*")); @@ -252,6 +253,17 @@ namespace System.IO.Tests } [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void WindowsWildCharacterPath_Core() + { + Assert.ThrowsAny<IOException>(() => Move(Path.Combine(TestDirectory, "*"), GetTestFilePath())); + Assert.ThrowsAny<IOException>(() => Move(TestDirectory, Path.Combine(TestDirectory, "*"))); + Assert.ThrowsAny<IOException>(() => Move(TestDirectory, Path.Combine(TestDirectory, "Test*t"))); + Assert.ThrowsAny<IOException>(() => Move(TestDirectory, Path.Combine(TestDirectory, "*Test"))); + } + + [Fact] [PlatformSpecific(TestPlatforms.AnyUnix)] // Wild characters in path are allowed public void UnixWildCharacterPath() { @@ -278,17 +290,13 @@ namespace System.IO.Tests } [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Whitespace path causes ArgumentException - public void WindowsWhitespacePath() + [PlatformSpecific(TestPlatforms.Windows)] + public void WindowsEmptyPath() { DirectoryInfo testDir = Directory.CreateDirectory(GetTestFilePath()); Assert.Throws<ArgumentException>(() => Move(testDir.FullName, " ")); - Assert.Throws<ArgumentException>(() => Move(testDir.FullName, "\n")); Assert.Throws<ArgumentException>(() => Move(testDir.FullName, "")); - Assert.Throws<ArgumentException>(() => Move(testDir.FullName, ">")); - Assert.Throws<ArgumentException>(() => Move(testDir.FullName, "<")); Assert.Throws<ArgumentException>(() => Move(testDir.FullName, "\0")); - Assert.Throws<ArgumentException>(() => Move(testDir.FullName, "\t")); } [Fact] diff --git a/src/System.IO.FileSystem/tests/DirectoryInfo/CreateSubdirectory.cs b/src/System.IO.FileSystem/tests/DirectoryInfo/CreateSubdirectory.cs index 6122cb1f5c..75af106c41 100644 --- a/src/System.IO.FileSystem/tests/DirectoryInfo/CreateSubdirectory.cs +++ b/src/System.IO.FileSystem/tests/DirectoryInfo/CreateSubdirectory.cs @@ -140,27 +140,40 @@ namespace System.IO.Tests [Theory, MemberData(nameof(ControlWhiteSpace))] - [PlatformSpecific(TestPlatforms.Windows)] // Control whitespace in path throws ArgumentException - public void WindowsControlWhiteSpace(string component) + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] + public void WindowsControlWhiteSpace_Desktop(string component) + { + Assert.Throws<ArgumentException>(() => new DirectoryInfo(TestDirectory).CreateSubdirectory(component)); + } + + [Theory, + MemberData(nameof(ControlWhiteSpace))] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void WindowsControlWhiteSpace_Core(string component) + { + Assert.Throws<IOException>(() => new DirectoryInfo(TestDirectory).CreateSubdirectory(component)); + } + + [Theory, + MemberData(nameof(SimpleWhiteSpace))] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + [PlatformSpecific(TestPlatforms.Windows)] + public void WindowsSimpleWhiteSpaceThrowsException(string component) { - // CreateSubdirectory will throw when passed a path with control whitespace e.g. "\t" - string path = IOServices.RemoveTrailingSlash(GetTestFileName()); Assert.Throws<ArgumentException>(() => new DirectoryInfo(TestDirectory).CreateSubdirectory(component)); } [Theory, MemberData(nameof(SimpleWhiteSpace))] - [PlatformSpecific(TestPlatforms.Windows)] // Simple whitespace is trimmed in path + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] // Simple whitespace is trimmed in path public void WindowsSimpleWhiteSpace(string component) { - // CreateSubdirectory trims all simple whitespace, returning us the parent directory - // that called CreateSubdirectory - string path = IOServices.RemoveTrailingSlash(GetTestFileName()); DirectoryInfo result = new DirectoryInfo(TestDirectory).CreateSubdirectory(component); Assert.True(Directory.Exists(result.FullName)); Assert.Equal(TestDirectory, IOServices.RemoveTrailingSlash(result.FullName)); - } [Theory, @@ -170,7 +183,6 @@ namespace System.IO.Tests { new DirectoryInfo(TestDirectory).CreateSubdirectory(path); Assert.True(Directory.Exists(Path.Combine(TestDirectory, path))); - } [Theory, @@ -207,6 +219,16 @@ namespace System.IO.Tests Assert.Throws<ArgumentException>(() => testDir.CreateSubdirectory("//")); } + [Fact] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void ParentDirectoryNameAsPrefixShouldThrow() + { + string randomName = GetTestFileName(); + DirectoryInfo di = Directory.CreateDirectory(Path.Combine(TestDirectory, randomName)); + + Assert.Throws<ArgumentException>(() => di.CreateSubdirectory(Path.Combine("..", randomName + "abc", GetTestFileName()))); + } + #endregion } } diff --git a/src/System.IO.FileSystem/tests/DirectoryInfo/Exists.cs b/src/System.IO.FileSystem/tests/DirectoryInfo/Exists.cs index d54f799cf0..be9e7e2641 100644 --- a/src/System.IO.FileSystem/tests/DirectoryInfo/Exists.cs +++ b/src/System.IO.FileSystem/tests/DirectoryInfo/Exists.cs @@ -38,6 +38,12 @@ namespace System.IO.Tests } [Fact] + public void Root() + { + Assert.True(new DirectoryInfo(Path.GetPathRoot(Directory.GetCurrentDirectory())).Exists); + } + + [Fact] public void DotPath() { Assert.True(new DirectoryInfo(Path.Combine(TestDirectory, ".")).Exists); diff --git a/src/System.IO.FileSystem/tests/DirectoryInfo/GetSetTimes.cs b/src/System.IO.FileSystem/tests/DirectoryInfo/GetSetTimes.cs index 927c770d61..779be70df6 100644 --- a/src/System.IO.FileSystem/tests/DirectoryInfo/GetSetTimes.cs +++ b/src/System.IO.FileSystem/tests/DirectoryInfo/GetSetTimes.cs @@ -12,6 +12,8 @@ namespace System.IO.Tests public override DirectoryInfo GetMissingItem() => new DirectoryInfo(GetTestFilePath()); + public override string GetItemPath(DirectoryInfo item) => item.FullName; + public override void InvokeCreate(DirectoryInfo item) => item.Create(); public override IEnumerable<TimeFunction> TimeFunctions(bool requiresRoundtripping = false) diff --git a/src/System.IO.FileSystem/tests/DirectoryInfo/ToString.cs b/src/System.IO.FileSystem/tests/DirectoryInfo/ToString.cs index 732c71ee62..de2ea39621 100644 --- a/src/System.IO.FileSystem/tests/DirectoryInfo/ToString.cs +++ b/src/System.IO.FileSystem/tests/DirectoryInfo/ToString.cs @@ -38,12 +38,28 @@ namespace System.IO.Tests } [Fact] + [SkipOnTargetFramework(TargetFrameworkMonikers.Netcoreapp)] [PlatformSpecific(TestPlatforms.Windows)] // Drive letter only - public void DriveOnlyReturnsPeriod_Windows() + public void DriveOnlyReturnsPeriod_Windows_Desktop() { string path = @"C:"; var info = new DirectoryInfo(path); Assert.Equal(".", info.ToString()); } + + [Fact] + [SkipOnTargetFramework(~TargetFrameworkMonikers.Netcoreapp)] + [PlatformSpecific(TestPlatforms.Windows)] // Drive letter only + public void DriveOnlyReturnsPeriod_Windows_Core() + { + // This was likely a limited trust hack that was strangely implemented. + // Getting the current directory for a specified drive relative path + // doesn't make a lot of sense. There is no reason to hide original paths + // when in full trust. + string path = @"C:"; + var info = new DirectoryInfo(path); + Assert.Equal("C:", info.ToString()); + } + } } diff --git a/src/System.IO.FileSystem/tests/Enumeration/AttributeTests.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/AttributeTests.netcoreapp.cs new file mode 100644 index 0000000000..2ba62dfb42 --- /dev/null +++ b/src/System.IO.FileSystem/tests/Enumeration/AttributeTests.netcoreapp.cs @@ -0,0 +1,141 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.IO.Enumeration; +using Xunit; + +namespace System.IO.Tests.Enumeration +{ + public class AttributeTests : FileSystemTest + { + private class DefaultFileAttributes : FileSystemEnumerator<string> + { + public DefaultFileAttributes(string directory, EnumerationOptions options) + : base(directory, options) + { + } + + protected override bool ContinueOnError(int error) + { + Assert.False(true, $"Should not have errored {error}"); + return false; + } + + protected override bool ShouldIncludeEntry(ref FileSystemEntry entry) + => !entry.IsDirectory; + + protected override string TransformEntry(ref FileSystemEntry entry) + { + string path = entry.ToFullPath(); + File.Delete(path); + + // Attributes require a stat call on Unix- ensure that we have the right attributes + // even if the returned file is deleted. + Assert.Equal(FileAttributes.Normal, entry.Attributes); + Assert.Equal(path, entry.ToFullPath()); + return new string(entry.FileName); + } + } + + [Fact] + public void FileAttributesAreExpected() + { + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, GetTestFileName())); + + fileOne.Create().Dispose(); + + if (PlatformDetection.IsWindows) + { + // Archive should always be set on a new file. Clear it and other expected flags to + // see that we get "Normal" as the default when enumerating. + + Assert.True((fileOne.Attributes & FileAttributes.Archive) != 0); + fileOne.Attributes &= ~(FileAttributes.Archive | FileAttributes.NotContentIndexed); + } + + using (var enumerator = new DefaultFileAttributes(testDirectory.FullName, new EnumerationOptions())) + { + Assert.True(enumerator.MoveNext()); + Assert.Equal(fileOne.Name, enumerator.Current); + Assert.False(enumerator.MoveNext()); + } + } + + private class DefaultDirectoryAttributes : FileSystemEnumerator<string> + { + public DefaultDirectoryAttributes(string directory, EnumerationOptions options) + : base(directory, options) + { + } + + protected override bool ShouldIncludeEntry(ref FileSystemEntry entry) + => entry.IsDirectory; + + protected override bool ContinueOnError(int error) + { + Assert.False(true, $"Should not have errored {error}"); + return false; + } + + protected override string TransformEntry(ref FileSystemEntry entry) + { + string path = entry.ToFullPath(); + Directory.Delete(path); + + // Attributes require a stat call on Unix- ensure that we have the right attributes + // even if the returned directory is deleted. + Assert.Equal(FileAttributes.Directory, entry.Attributes); + Assert.Equal(path, entry.ToFullPath()); + return new string(entry.FileName); + } + } + + [Fact] + public void DirectoryAttributesAreExpected() + { + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + DirectoryInfo subDirectory = Directory.CreateDirectory(Path.Combine(testDirectory.FullName, GetTestFileName())); + + if (PlatformDetection.IsWindows) + { + // Clear possible extra flags to see that we get Directory + subDirectory.Attributes &= ~FileAttributes.NotContentIndexed; + } + + using (var enumerator = new DefaultDirectoryAttributes(testDirectory.FullName, new EnumerationOptions())) + { + Assert.True(enumerator.MoveNext()); + Assert.Equal(subDirectory.Name, enumerator.Current); + Assert.False(enumerator.MoveNext()); + } + } + + [Fact] + public void IsHiddenAttribute() + { + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, GetTestFileName())); + + // Put a period in front to make it hidden on Unix + FileInfo fileTwo = new FileInfo(Path.Combine(testDirectory.FullName, "." + GetTestFileName())); + + fileOne.Create().Dispose(); + fileTwo.Create().Dispose(); + if (PlatformDetection.IsWindows) + fileTwo.Attributes = fileTwo.Attributes | FileAttributes.Hidden; + + IEnumerable<string> enumerable = new FileSystemEnumerable<string>( + testDirectory.FullName, + (ref FileSystemEntry entry) => entry.ToFullPath(), + new EnumerationOptions() { AttributesToSkip = 0 }) + { + ShouldIncludePredicate = (ref FileSystemEntry entry) => entry.IsHidden + }; + + Assert.Equal(new string[] { fileTwo.FullName }, enumerable); + } + } +} diff --git a/src/System.IO.FileSystem/tests/Enumeration/ConstructionTests.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/ConstructionTests.netcoreapp.cs new file mode 100644 index 0000000000..4f453200ee --- /dev/null +++ b/src/System.IO.FileSystem/tests/Enumeration/ConstructionTests.netcoreapp.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO.Enumeration; +using Xunit; + +namespace System.IO.Tests.Enumeration +{ + public class ConstructionTests : FileSystemTest + { + [Fact] + public void Enumerable_NullTransformThrows() + { + AssertExtensions.Throws<ArgumentNullException>("transform", + () => new FileSystemEnumerable<string>(TestDirectory, transform: null)); + } + + [Fact] + public void Enumerable_NullDirectoryThrows() + { + AssertExtensions.Throws<ArgumentNullException>("directory", + () => new FileSystemEnumerable<string>(null, null)); + } + + private class TestEnumerator : FileSystemEnumerator<string> + { + public TestEnumerator(string directory, EnumerationOptions options) + : base(directory, options) + { + } + + protected override string TransformEntry(ref FileSystemEntry entry) + { + throw new NotImplementedException(); + } + } + + [Fact] + public void Enumerator_NullDirectoryThrows() + { + AssertExtensions.Throws<ArgumentNullException>("directory", + () => new TestEnumerator(null, null)); + } + } +} diff --git a/src/System.IO.FileSystem/tests/Enumeration/ErrorHandlingTests.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/ErrorHandlingTests.netcoreapp.cs new file mode 100644 index 0000000000..1414156cf3 --- /dev/null +++ b/src/System.IO.FileSystem/tests/Enumeration/ErrorHandlingTests.netcoreapp.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO.Enumeration; +using Xunit; + +namespace System.IO.Tests +{ + public class ErrorHandlingTests : FileSystemTest + { + private class IgnoreErrors : FileSystemEnumerator<string> + { + public IgnoreErrors(string directory) + : base(directory) + { } + + public int ErrorCount { get; private set; } + public string DirectoryFinished { get; private set; } + + protected override string TransformEntry(ref FileSystemEntry entry) + => entry.FileName.ToString(); + + protected override bool ContinueOnError(int error) + { + ErrorCount++; + return true; + } + + protected override void OnDirectoryFinished(ReadOnlySpan<char> directory) + => DirectoryFinished = directory.ToString(); + } + + [Fact] + public void OpenErrorDoesNotHappenAgainOnMoveNext() + { + // What we're checking for here is that we don't try to enumerate when we + // couldn't even open the root directory (e.g. open the handle again, try + // to get data, etc.) + using (IgnoreErrors ie = new IgnoreErrors(Path.GetRandomFileName())) + { + Assert.Equal(1, ie.ErrorCount); + Assert.False(ie.MoveNext()); + Assert.Equal(1, ie.ErrorCount); + + // Since we didn't start, the directory shouldn't finish. + Assert.Null(ie.DirectoryFinished); + } + } + + [Fact] + public void DeleteDirectoryAfterOpening() + { + // We shouldn't prevent the directory from being deleted, even though we've + // opened (and are holding) the handle. On Windows this means we've opened + // the handle with file share of delete. + DirectoryInfo info = Directory.CreateDirectory(GetTestFilePath()); + using (IgnoreErrors ie = new IgnoreErrors(info.FullName)) + { + Assert.Equal(0, ie.ErrorCount); + Directory.Delete(info.FullName); + Assert.False(ie.MoveNext()); + + // This doesn't cause an error as the directory is still valid until the + // the enumerator is closed (as we have an open handle) + Assert.Equal(0, ie.ErrorCount); + Assert.Equal(info.FullName, ie.DirectoryFinished); + } + } + } +} diff --git a/src/System.IO.FileSystem/tests/Enumeration/ExampleTests.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/ExampleTests.netcoreapp.cs new file mode 100644 index 0000000000..9db4f452d1 --- /dev/null +++ b/src/System.IO.FileSystem/tests/Enumeration/ExampleTests.netcoreapp.cs @@ -0,0 +1,141 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.IO.Enumeration; +using System.Linq; +using Xunit; + +namespace System.IO.Tests.Enumeration +{ + // For tests that cover examples from documentation, blog posts, etc. While these overlap with + // existing tests, having explicit coverage here is extra insurance we are covering the + // examples we've given out publicly. + public class ExampleTests : FileSystemTest + { + [Fact] + public void GetFileNamesEnumerable() + { + // https://blogs.msdn.microsoft.com/jeremykuhne/2018/03/09/custom-directory-enumeration-in-net-core-2-1/ + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + File.Create(Path.Join(testDirectory.FullName, "one")).Dispose(); + File.Create(Path.Join(testDirectory.FullName, "two")).Dispose(); + Directory.CreateDirectory(Path.Join(testDirectory.FullName, "three")); + + IEnumerable<string> fileNames = + new FileSystemEnumerable<string>( + testDirectory.FullName, + (ref FileSystemEntry entry) => entry.FileName.ToString()) + { + ShouldIncludePredicate = (ref FileSystemEntry entry) => !entry.IsDirectory + }; + + FSAssert.EqualWhenOrdered(new string[] { "one", "two" }, fileNames); + } + + private static IEnumerable<FileInfo> GetFilesWithExtensions(string directory, + bool recursive, params string[] extensions) + { + return new FileSystemEnumerable<FileInfo>( + directory, + (ref FileSystemEntry entry) => (FileInfo)entry.ToFileSystemInfo(), + new EnumerationOptions() { RecurseSubdirectories = recursive }) + { + ShouldIncludePredicate = (ref FileSystemEntry entry) => + { + if (entry.IsDirectory) + return false; + foreach (string extension in extensions) + { + if (Path.GetExtension(entry.FileName).SequenceEqual(extension)) + return true; + } + return false; + } + }; + } + + [Fact] + public void TestGetFilesWithExtensions() + { + // https://blogs.msdn.microsoft.com/jeremykuhne/2018/03/09/custom-directory-enumeration-in-net-core-2-1/ + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + File.Create(Path.Join(testDirectory.FullName, "file.one")).Dispose(); + File.Create(Path.Join(testDirectory.FullName, "file.two")).Dispose(); + File.Create(Path.Join(testDirectory.FullName, "file.three")).Dispose(); + DirectoryInfo subDirectory = testDirectory.CreateSubdirectory("three.one"); + File.Create(Path.Join(subDirectory.FullName, "subfile.one")).Dispose(); + + FSAssert.EqualWhenOrdered( + new string[] { "file.one", "file.three" }, + GetFilesWithExtensions(testDirectory.FullName, false, ".one", ".three").Select(f => f.Name)); + + FSAssert.EqualWhenOrdered( + new string[] { "file.one", "file.three", "subfile.one" }, + GetFilesWithExtensions(testDirectory.FullName, true, ".one", ".three").Select(f => f.Name)); + } + + private static int CountFiles(string directory, bool recursive) + { + return (new FileSystemEnumerable<int>( + directory, + (ref FileSystemEntry entry) => 1, + new EnumerationOptions() { RecurseSubdirectories = recursive }) + { + ShouldIncludePredicate = (ref FileSystemEntry entry) => !entry.IsDirectory + }).Count(); + } + + [Fact] + public void TestCountFiles() + { + // https://blogs.msdn.microsoft.com/jeremykuhne/2018/03/09/custom-directory-enumeration-in-net-core-2-1/ + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + File.Create(Path.Join(testDirectory.FullName, "file.one")).Dispose(); + File.Create(Path.Join(testDirectory.FullName, "file.two")).Dispose(); + File.Create(Path.Join(testDirectory.FullName, "file.three")).Dispose(); + DirectoryInfo subDirectory = testDirectory.CreateSubdirectory("three.one"); + File.Create(Path.Join(subDirectory.FullName, "subfile.one")).Dispose(); + + Assert.Equal(3, CountFiles(testDirectory.FullName, false)); + + Assert.Equal(4, CountFiles(testDirectory.FullName, true)); + } + + private static long CountFileBytes(string directory, bool recursive) + { + return (new FileSystemEnumerable<long>( + directory, + (ref FileSystemEntry entry) => entry.Length, + new EnumerationOptions() { RecurseSubdirectories = recursive }) + { + ShouldIncludePredicate = (ref FileSystemEntry entry) => !entry.IsDirectory + }).Sum(); + } + + [Fact] + public void TestCountFileBytes() + { + // https://blogs.msdn.microsoft.com/jeremykuhne/2018/03/09/custom-directory-enumeration-in-net-core-2-1/ + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + FileInfo firstFile = new FileInfo(Path.Join(testDirectory.FullName, "file.one")); + using (var writer = firstFile.CreateText()) + { + for (int i = 0; i < 100; i++) + writer.WriteLine("The quick brown fox jumped over the lazy dog."); + } + + firstFile.CopyTo(Path.Join(testDirectory.FullName, "file.two")); + firstFile.CopyTo(Path.Join(testDirectory.FullName, "file.three")); + DirectoryInfo subDirectory = testDirectory.CreateSubdirectory("three.one"); + firstFile.CopyTo(Path.Join(subDirectory.FullName, "subfile.one")); + + firstFile.Refresh(); + Assert.True(firstFile.Length > 0, "The file we wrote should have a length."); + Assert.Equal(firstFile.Length * 3, CountFileBytes(testDirectory.FullName, false)); + + Assert.Equal(firstFile.Length * 4, CountFileBytes(testDirectory.FullName, true)); + } + } +} diff --git a/src/System.IO.FileSystem/tests/Enumeration/FileSystemNameTests.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/FileSystemNameTests.netcoreapp.cs new file mode 100644 index 0000000000..a199d2c298 --- /dev/null +++ b/src/System.IO.FileSystem/tests/Enumeration/FileSystemNameTests.netcoreapp.cs @@ -0,0 +1,154 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO.Enumeration; +using Xunit; + +namespace System.IO.Tests +{ + public class FileSystemNameTests + { + [Theory, + MemberData(nameof(SimpleMatchData)), + MemberData(nameof(EscapedSimpleMatchData)), + MemberData(nameof(Win32MatchData)), + MemberData(nameof(EscapedWin32MatchData))] + public static void Win32Match(string expression, string name, bool ignoreCase, bool expected) + { + Assert.Equal(expected, FileSystemName.MatchesWin32Expression(expression, name.AsSpan(), ignoreCase)); + } + + [Theory, + MemberData(nameof(SimpleMatchData)), + MemberData(nameof(EscapedSimpleMatchData))] + public static void SimpleMatch(string expression, string name, bool ignoreCase, bool expected) + { + Assert.Equal(expected, FileSystemName.MatchesSimpleExpression(expression, name.AsSpan(), ignoreCase)); + } + + public static TheoryData<string, string, bool, bool> EscapedSimpleMatchData => new TheoryData<string, string, bool, bool> + { + // Trailing escape matches as it is considered "invisible" + { "\\", "\\", false, true }, + { "\\", "\\", true, true }, + { "\\\\", "\\", false, true }, + { "\\\\", "\\", true, true }, + + { "\\*", "a", false, false }, + { "\\*", "a", true, false }, + { "\\*", "*", false, true }, + { "\\*", "*", true, true }, + { "*\\*", "***", false, true }, + { "*\\*", "***", true, true }, + { "*\\*", "ABC*", false, true }, + { "*\\*", "ABC*", true, true }, + { "*\\*", "***A", false, false }, + { "*\\*", "***A", true, false }, + { "*\\*", "ABC*A", false, false }, + { "*\\*", "ABC*A", true, false }, + }; + + public static TheoryData<string, string, bool, bool> EscapedWin32MatchData => new TheoryData<string, string, bool, bool> + { + { "\\\"", "a", false, false }, + { "\\\"", "a", true, false }, + { "\\\"", "\"", false, true }, + { "\\\"", "\"", true, true }, + }; + + public static TheoryData<string, string, bool, bool> SimpleMatchData => new TheoryData<string, string, bool, bool> + { + { null, "", false, false }, + { null, "", true, false }, + { "*", "", false, false }, + { "*", "", true, false }, + { "*", "ab", false, true }, + { "*", "AB", true, true }, + { "*foo", "foo", false, true }, + { "*foo", "foo", true, true }, + { "*foo", "FOO", false, false }, + { "*foo", "FOO", true, true }, + { "*foo", "nofoo", true, true }, + { "*foo", "NoFOO", true, true }, + { "*foo", "noFOO", false, false }, + { @"*", @"foo.txt", true, true }, + { @".", @"foo.txt", true, false }, + { @".", @"footxt", true, false }, + { @"*.*", @"foo.txt", true, true }, + { @"*.*", @"foo.", true, true }, + { @"*.*", @".foo", true, true }, + { @"*.*", @"footxt", true, false }, + }; + + public static TheoryData<string, string, bool, bool> Win32MatchData => new TheoryData<string, string, bool, bool> + { + { "<\"*", @"footxt", true, true }, // DOS equivalent of *.* + { "<\"*", @"foo.txt", true, true }, // DOS equivalent of *.* + { "<\"*", @".foo", true, true }, // DOS equivalent of *.* + { "<\"*", @"foo.", true, true }, // DOS equivalent of *.* + { ">\">", @"a.b", true, true }, // DOS equivalent of ?.? + { ">\">", @"a.", true, true }, // DOS equivalent of ?.? + { ">\">", @"a", true, true }, // DOS equivalent of ?.? + { ">\">", @"ab", true, false }, // DOS equivalent of ?.? + { ">\">", @"a.bc", true, false }, // DOS equivalent of ?.? + { ">\">", @"ab.c", true, false }, // DOS equivalent of ?.? + { ">>\">>", @"a.b", true, true }, // DOS equivalent of ??.?? + { ">>\"\">>", @"a.b", true, false }, // Not possible to do from DOS ??""?? + { ">>\">>", @"a.bc", true, true }, // DOS equivalent of ??.?? + { ">>\">>", @"ab.ba", true, true }, // DOS equivalent of ??.?? + { ">>\">>", @"ab.", true, true }, // DOS equivalent of ??.?? + { ">>\"\"\">>", @"ab.", true, true }, // Not possible to do from DOS ??"""?? + { ">>b\">>", @"ab.ba", true, false }, // DOS equivalent of ??b.?? + { "a>>\">>", @"ab.ba", true, true }, // DOS equivalent of a??.?? + { ">>\">>a", @"ab.ba", true, false }, // DOS equivalent of ??.??a + { ">>\"b>>", @"ab.ba", true, true }, // DOS equivalent of ??.b?? + { ">>\"b>>", @"ab.b", true, true }, // DOS equivalent of ??.b?? + { ">>b.>>", @"ab.ba", true, false }, + { "a>>.>>", @"ab.ba", true, true }, + { ">>.>>a", @"ab.ba", true, false }, + { ">>.b>>", @"ab.ba", true, true }, + { ">>.b>>", @"ab.b", true, true }, + { ">>\">>\">>", @"ab.ba", true, true }, // DOS equivalent of ??.??.?? (The last " is an optional period) + { ">>\">>\">>", @"abba", true, false }, // DOS equivalent of ??.??.?? (The first " isn't, so this doesn't match) + { ">>\"ab\"ba", @"ab.ba", true, false }, // DOS equivalent of ??.ab.ba + { "ab\"ba\">>", @"ab.ba", true, true }, // DOS equivalent of ab.ba.?? + { "ab\">>\"ba", @"ab.ba", true, false }, // DOS equivalent of ab.??.ba + { ">>\">>\">>>", @"ab.ba.cab", true, true }, // DOS equivalent of ??.??.??? + { "a>>\"b>>\"c>>>", @"ab.ba.cab", true, true }, // DOS equivalent of a??.b??.c??? + { @"<", @"a", true, true }, // DOS equivalent of *. + { @"<", @"a.", true, true }, // DOS equivalent of *. + { @"<", @"a. ", true, false }, // DOS equivalent of *. + { @"<", @"a.b", true, false }, // DOS equivalent of *. + { @"foo<", @"foo.", true, true }, // DOS equivalent of foo*. + { @"foo<", @"foo. ", true, false }, // DOS equivalent of foo*. + { @"<<", @"a.b", true, true }, + { @"<<", @"a.b.c", true, true }, + { "<\"", @"a.b.c", true, false }, + { @"<.", @"a", true, false }, + { @"<.", @"a.", true, true }, + { @"<.", @"a.b", true, false }, + }; + + [Theory, + InlineData("", "*"), + InlineData("*.*", "*"), + InlineData("*", "*"), + InlineData(".", "."), + InlineData("?", ">"), + InlineData("*.", "<"), + InlineData("?.?", ">\">"), + InlineData("foo*.", "foo<")] + public void TranslateExpression(string expression, string expected) + { + Assert.Equal(expected, FileSystemName.TranslateWin32Expression(expression)); + } + + [Fact] + public void TranslateVeryLongExpression() + { + string longString = new string('a', 10_000_000); + Assert.Equal(longString, FileSystemName.TranslateWin32Expression(longString)); + } + } +} diff --git a/src/System.IO.FileSystem/tests/Enumeration/IncludePredicateTests.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/IncludePredicateTests.netcoreapp.cs new file mode 100644 index 0000000000..0a6737e064 --- /dev/null +++ b/src/System.IO.FileSystem/tests/Enumeration/IncludePredicateTests.netcoreapp.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.IO.Enumeration; +using System.Linq; +using Xunit; + +namespace System.IO.Tests.Enumeration +{ + public abstract class IncludePredicateTests : FileSystemTest + { + public static IEnumerable<string> GetFileFullPathsWithExtension(string directory, + bool recursive, params string[] extensions) + { + return new FileSystemEnumerable<string>( + directory, + (ref FileSystemEntry entry) => entry.ToFullPath(), + new EnumerationOptions() { RecurseSubdirectories = recursive }) + { + ShouldIncludePredicate = (ref FileSystemEntry entry) => + { + if (entry.IsDirectory) return false; + foreach (string extension in extensions) + { + if (Path.GetExtension(entry.FileName).EndsWith(extension)) + return true; + } + return false; + } + }; + } + + [Fact] + public void CustomExtensionMatch() + { + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + DirectoryInfo testSubdirectory = Directory.CreateDirectory(Path.Combine(testDirectory.FullName, "Subdirectory")); + FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, "fileone.htm")); + FileInfo fileTwo = new FileInfo(Path.Combine(testDirectory.FullName, "filetwo.html")); + FileInfo fileThree = new FileInfo(Path.Combine(testSubdirectory.FullName, "filethree.doc")); + FileInfo fileFour = new FileInfo(Path.Combine(testSubdirectory.FullName, "filefour.docx")); + + fileOne.Create().Dispose(); + fileTwo.Create().Dispose(); + fileThree.Create().Dispose(); + fileFour.Create().Dispose(); + + string[] paths = GetFileFullPathsWithExtension(testDirectory.FullName, true, ".htm", ".doc").ToArray(); + + FSAssert.EqualWhenOrdered(new string[] { fileOne.FullName, fileThree.FullName }, paths); + } + } +} diff --git a/src/System.IO.FileSystem/tests/Enumeration/MatchCasingTests.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/MatchCasingTests.netcoreapp.cs new file mode 100644 index 0000000000..00a74055ab --- /dev/null +++ b/src/System.IO.FileSystem/tests/Enumeration/MatchCasingTests.netcoreapp.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO.Enumeration; +using System.Linq; +using Xunit; + +namespace System.IO.Tests.Enumeration +{ + public abstract class MatchCasingTests : FileSystemTest + { + protected abstract string[] GetPaths(string directory, string pattern, EnumerationOptions options); + + [Fact] + public void MatchCase() + { + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + DirectoryInfo testSubdirectory = Directory.CreateDirectory(Path.Combine(testDirectory.FullName, "Subdirectory")); + FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, "FileOne")); + FileInfo fileTwo = new FileInfo(Path.Combine(testDirectory.FullName, "FileTwo")); + FileInfo fileThree = new FileInfo(Path.Combine(testSubdirectory.FullName, "FileThree")); + FileInfo fileFour = new FileInfo(Path.Combine(testSubdirectory.FullName, "FileFour")); + + fileOne.Create().Dispose(); + fileTwo.Create().Dispose(); + fileThree.Create().Dispose(); + fileFour.Create().Dispose(); + + string[] paths = GetPaths(testDirectory.FullName, "file*", new EnumerationOptions { MatchCasing = MatchCasing.CaseSensitive, RecurseSubdirectories = true }); + + Assert.Empty(paths); + + paths = GetPaths(testDirectory.FullName, "file*", new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive, RecurseSubdirectories = true }); + FSAssert.EqualWhenOrdered(new string[] { fileOne.FullName, fileTwo.FullName, fileThree.FullName, fileFour.FullName }, paths); + + paths = GetPaths(testDirectory.FullName, "FileT*", new EnumerationOptions { MatchCasing = MatchCasing.CaseSensitive, RecurseSubdirectories = true }); + FSAssert.EqualWhenOrdered(new string[] { fileTwo.FullName, fileThree.FullName }, paths); + + paths = GetPaths(testDirectory.FullName, "File???", new EnumerationOptions { MatchCasing = MatchCasing.CaseSensitive, RecurseSubdirectories = true }); + FSAssert.EqualWhenOrdered(new string[] { fileOne.FullName, fileTwo.FullName }, paths); + } + } + + public class MatchCasingTests_Directory_GetFiles : MatchCasingTests + { + protected override string[] GetPaths(string directory, string pattern, EnumerationOptions options) + { + return Directory.GetFiles(directory, pattern, options); + } + } + + public class MatchCasingTests_DirectoryInfo_GetFiles : MatchCasingTests + { + protected override string[] GetPaths(string directory, string pattern, EnumerationOptions options) + { + return new DirectoryInfo(directory).GetFiles(pattern, options).Select(i => i.FullName).ToArray(); + } + } +} diff --git a/src/System.IO.FileSystem/tests/Enumeration/MatchTypesTests.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/MatchTypesTests.netcoreapp.cs new file mode 100644 index 0000000000..49b8e1b9e0 --- /dev/null +++ b/src/System.IO.FileSystem/tests/Enumeration/MatchTypesTests.netcoreapp.cs @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using Xunit; + +namespace System.IO.Tests.Enumeration +{ + public abstract class MatchTypesTests : FileSystemTest + { + protected abstract string[] GetPaths(string directory, string pattern, EnumerationOptions options); + + [Fact] + public void QuestionMarkBehavior() + { + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, "a.one")); + FileInfo fileTwo = new FileInfo(Path.Combine(testDirectory.FullName, "ab.two")); + FileInfo fileThree = new FileInfo(Path.Combine(testDirectory.FullName, "abc.three")); + + fileOne.Create().Dispose(); + fileTwo.Create().Dispose(); + fileThree.Create().Dispose(); + + // Question marks collapse to periods in Win32 + string[] paths = GetPaths(testDirectory.FullName, "a??.*", new EnumerationOptions { MatchType = MatchType.Win32 }); + FSAssert.EqualWhenOrdered(new string[] { fileOne.FullName, fileTwo.FullName, fileThree.FullName }, paths); + + paths = GetPaths(testDirectory.FullName, "*.?????", new EnumerationOptions { MatchType = MatchType.Win32 }); + FSAssert.EqualWhenOrdered(new string[] { fileOne.FullName, fileTwo.FullName, fileThree.FullName }, paths); + + // Simple, one question mark is one character + paths = GetPaths(testDirectory.FullName, "a??.*", new EnumerationOptions { MatchType = MatchType.Simple }); + FSAssert.EqualWhenOrdered(new string[] { fileThree.FullName }, paths); + + paths = GetPaths(testDirectory.FullName, "*.?????", new EnumerationOptions { MatchType = MatchType.Simple }); + FSAssert.EqualWhenOrdered(new string[] { fileThree.FullName }, paths); + } + + [Fact] + public void StarDotBehavior() + { + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, "one")); + FileInfo fileTwo = new FileInfo(Path.Combine(testDirectory.FullName, "one.two")); + string fileThree = Path.Combine(testDirectory.FullName, "three."); + + fileOne.Create().Dispose(); + fileTwo.Create().Dispose(); + + // Need extended device syntax to create a name with a trailing dot. + File.Create(PlatformDetection.IsWindows ? @"\\?\" + fileThree : fileThree).Dispose(); + + // *. means any file without an extension + string[] paths = GetPaths(testDirectory.FullName, "*.", new EnumerationOptions { MatchType = MatchType.Win32 }); + FSAssert.EqualWhenOrdered(new string[] { fileOne.FullName, fileThree }, paths); + + // Simple, anything with a trailing period + paths = GetPaths(testDirectory.FullName, "*.", new EnumerationOptions { MatchType = MatchType.Simple }); + FSAssert.EqualWhenOrdered(new string[] { fileThree }, paths); + } + } + + public class MatchTypesTests_Directory_GetFiles : MatchTypesTests + { + protected override string[] GetPaths(string directory, string pattern, EnumerationOptions options) + { + return Directory.GetFiles(directory, pattern, options); + } + } + + public class MatchTypesTests_DirectoryInfo_GetFiles : MatchTypesTests + { + protected override string[] GetPaths(string directory, string pattern, EnumerationOptions options) + { + return new DirectoryInfo(directory).GetFiles(pattern, options).Select(i => i.FullName).ToArray(); + } + } +} diff --git a/src/System.IO.FileSystem/tests/Enumeration/PatternTransformTests.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/PatternTransformTests.netcoreapp.cs new file mode 100644 index 0000000000..9cb33e5853 --- /dev/null +++ b/src/System.IO.FileSystem/tests/Enumeration/PatternTransformTests.netcoreapp.cs @@ -0,0 +1,126 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using Xunit; + +namespace System.IO.Tests.Enumeration +{ + public class PatternTransformTests_Directory : FileSystemTest + { + protected virtual string[] GetFiles(string directory, string pattern) + { + return Directory.GetFiles(directory, pattern); + } + + protected virtual string[] GetFiles(string directory, string pattern, EnumerationOptions options) + { + return Directory.GetFiles(directory, pattern, options); + } + + [Theory, + InlineData("."), + InlineData("*.*")] + public void GetFiles_WildcardPatternIsTranslated(string pattern) + { + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, "File.One")); + FileInfo fileTwo = new FileInfo(Path.Combine(testDirectory.FullName, "FileTwo")); + fileOne.Create().Dispose(); + fileTwo.Create().Dispose(); + string[] results = GetFiles(testDirectory.FullName, pattern); + FSAssert.EqualWhenOrdered(new string[] { fileOne.FullName, fileTwo.FullName }, results); + + results = GetFiles(testDirectory.FullName, pattern, new EnumerationOptions { MatchType = MatchType.Win32 }); + FSAssert.EqualWhenOrdered(new string[] { fileOne.FullName, fileTwo.FullName }, results); + } + + [Fact] + public void GetFiles_WildcardPatternIsNotTranslated() + { + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, "File.One")); + FileInfo fileTwo = new FileInfo(Path.Combine(testDirectory.FullName, "FileTwo")); + fileOne.Create().Dispose(); + fileTwo.Create().Dispose(); + string[] results = GetFiles(testDirectory.FullName, ".", new EnumerationOptions()); + Assert.Empty(results); + + results = GetFiles(testDirectory.FullName, "*.*", new EnumerationOptions()); + Assert.Equal(new string[] { fileOne.FullName }, results); + } + + [Fact] + public void GetFiles_EmptyPattern() + { + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, "File.One")); + FileInfo fileTwo = new FileInfo(Path.Combine(testDirectory.FullName, "FileTwo")); + fileOne.Create().Dispose(); + fileTwo.Create().Dispose(); + + // We allow for expression to be "foo\" which would translate to "foo\*". + string[] results = GetFiles(testDirectory.Parent.FullName, testDirectory.Name + Path.DirectorySeparatorChar); + FSAssert.EqualWhenOrdered(new string[] { fileOne.FullName, fileTwo.FullName }, results); + + results = GetFiles(testDirectory.Parent.FullName, testDirectory.Name + Path.AltDirectorySeparatorChar); + FSAssert.EqualWhenOrdered(new string[] { fileOne.FullName, fileTwo.FullName }, results); + + results = GetFiles(testDirectory.FullName, string.Empty); + FSAssert.EqualWhenOrdered(new string[] { fileOne.FullName, fileTwo.FullName }, results); + } + + [Fact] + [PlatformSpecific(TestPlatforms.AnyUnix)] + public void GetFiles_EmptyPattern_Unix() + { + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, "File\\One")); + FileInfo fileTwo = new FileInfo(Path.Combine(testDirectory.FullName, "FileTwo")); + fileOne.Create().Dispose(); + fileTwo.Create().Dispose(); + + // We allow for expression to be "foo\" which would translate to "foo\*". On Unix we should not be + // considering the backslash as a directory separator. + string[] results = GetFiles(testDirectory.FullName, "File\\One"); + Assert.Equal(new string[] { fileOne.FullName }, results); + } + + [Fact] + [PlatformSpecific(TestPlatforms.AnyUnix)] + public void GetFiles_ExtendedDosWildcards_Unix() + { + // The extended wildcards ('"', '<', and '>') should not be considered on Unix, even when doing DOS style matching. + // Getting these behaviors requires using the FileSystemEnumerable/Enumerator directly. + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, "File\"One")); + FileInfo fileTwo = new FileInfo(Path.Combine(testDirectory.FullName, "File<Two")); + FileInfo fileThree = new FileInfo(Path.Combine(testDirectory.FullName, "File>Three")); + fileOne.Create().Dispose(); + fileTwo.Create().Dispose(); + fileThree.Create().Dispose(); + + string[] results = GetFiles(testDirectory.FullName, "*\"*"); + Assert.Equal(new string[] { fileOne.FullName }, results); + results = GetFiles(testDirectory.FullName, "*<*"); + Assert.Equal(new string[] { fileTwo.FullName }, results); + results = GetFiles(testDirectory.FullName, "*>*"); + Assert.Equal(new string[] { fileThree.FullName }, results); + } + } + + public class PatternTransformTests_DirectoryInfo : PatternTransformTests_Directory + { + + protected override string[] GetFiles(string directory, string pattern) + { + return new DirectoryInfo(directory).GetFiles(pattern).Select(i => i.FullName).ToArray(); + } + + protected override string[] GetFiles(string directory, string pattern, EnumerationOptions options) + { + return new DirectoryInfo(directory).GetFiles(pattern, options).Select(i => i.FullName).ToArray(); + } + } +} diff --git a/src/System.IO.FileSystem/tests/Enumeration/RootTests.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/RootTests.netcoreapp.cs new file mode 100644 index 0000000000..91b1376ede --- /dev/null +++ b/src/System.IO.FileSystem/tests/Enumeration/RootTests.netcoreapp.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO.Enumeration; +using Xunit; + +namespace System.IO.Tests.Enumeration +{ + public class RootTests + { + private class DirectoryRecursed : FileSystemEnumerator<string> + { + public string LastDirectory { get; private set; } + + public DirectoryRecursed(string directory, EnumerationOptions options) + : base(directory, options) + { + } + + protected override bool ShouldIncludeEntry(ref FileSystemEntry entry) + => !entry.IsDirectory; + + protected override string TransformEntry(ref FileSystemEntry entry) + => entry.ToFullPath(); + + protected override bool ShouldRecurseIntoEntry(ref FileSystemEntry entry) + { + LastDirectory = new string(entry.Directory); + return false; + } + } + + [Fact] + public void CanRecurseFromRoot() + { + string root = Path.GetPathRoot(Path.GetTempPath()); + + using (var recursed = new DirectoryRecursed(root, new EnumerationOptions { AttributesToSkip = FileAttributes.System, RecurseSubdirectories = true })) + { + while (recursed.MoveNext()) + { + if (recursed.LastDirectory != null) + { + Assert.Equal(root, recursed.LastDirectory); + return; + } + + // Should start with the root and shouldn't have a separator after the root + Assert.StartsWith(root, recursed.Current); + Assert.True(recursed.Current.LastIndexOf(Path.DirectorySeparatorChar) < root.Length, + $"should have no separators pasth the root '{root}' in in '{recursed.Current}'"); + } + + Assert.NotNull(recursed.LastDirectory); + } + } + } +} diff --git a/src/System.IO.FileSystem/tests/Enumeration/SkipAttributeTests.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/SkipAttributeTests.netcoreapp.cs new file mode 100644 index 0000000000..39e4d84324 --- /dev/null +++ b/src/System.IO.FileSystem/tests/Enumeration/SkipAttributeTests.netcoreapp.cs @@ -0,0 +1,107 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO.Enumeration; +using System.Linq; +using Xunit; + +namespace System.IO.Tests.Enumeration +{ + public class SkipAttributeTests : FileSystemTest + { + protected virtual string[] GetPaths(string directory, EnumerationOptions options) + { + return new FileSystemEnumerable<string>( + directory, + (ref FileSystemEntry entry) => entry.ToFullPath(), + options) + { + ShouldIncludePredicate = (ref FileSystemEntry entry) => { return !entry.IsDirectory; } + }.ToArray(); + } + + [Fact] + public void SkippingHiddenFiles() + { + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + DirectoryInfo testSubdirectory = Directory.CreateDirectory(Path.Combine(testDirectory.FullName, GetTestFileName())); + FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, GetTestFileName())); + + // Put a period in front to make it hidden on Unix + FileInfo fileTwo = new FileInfo(Path.Combine(testDirectory.FullName, "." + GetTestFileName())); + FileInfo fileThree = new FileInfo(Path.Combine(testSubdirectory.FullName, GetTestFileName())); + FileInfo fileFour = new FileInfo(Path.Combine(testSubdirectory.FullName, "." + GetTestFileName())); + + fileOne.Create().Dispose(); + fileTwo.Create().Dispose(); + if (PlatformDetection.IsWindows) + fileTwo.Attributes = fileTwo.Attributes | FileAttributes.Hidden; + fileThree.Create().Dispose(); + fileFour.Create().Dispose(); + if (PlatformDetection.IsWindows) + fileFour.Attributes = fileTwo.Attributes | FileAttributes.Hidden; + + // Default EnumerationOptions is to skip hidden + string[] paths = GetPaths(testDirectory.FullName, new EnumerationOptions()); + Assert.Equal(new string[] { fileOne.FullName }, paths); + + paths = GetPaths(testDirectory.FullName, new EnumerationOptions { AttributesToSkip = 0 }); + FSAssert.EqualWhenOrdered(new string[] { fileOne.FullName, fileTwo.FullName }, paths); + + paths = GetPaths(testDirectory.FullName, new EnumerationOptions { RecurseSubdirectories = true }); + Assert.Equal(new string[] { fileOne.FullName, fileThree.FullName }, paths); + + if (PlatformDetection.IsWindows) + { + // Shouldn't recurse into the subdirectory now that it is hidden + testSubdirectory.Attributes = testSubdirectory.Attributes | FileAttributes.Hidden; + } + else + { + Directory.Move(testSubdirectory.FullName, Path.Combine(testDirectory.FullName, "." + testSubdirectory.Name)); + } + + paths = GetPaths(testDirectory.FullName, new EnumerationOptions { RecurseSubdirectories = true }); + Assert.Equal(new string[] { fileOne.FullName }, paths); + } + + [Fact] + public void SkipComesFirst() + { + // If skip comes first we shouldn't find ourselves recursing. + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + DirectoryInfo testSubdirectory = Directory.CreateDirectory(Path.Combine(testDirectory.FullName, GetTestFileName())); + + FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, GetTestFileName())); + FileInfo fileTwo = new FileInfo(Path.Combine(testDirectory.FullName, GetTestFileName())); + + FileInfo fileThree = new FileInfo(Path.Combine(testSubdirectory.FullName, GetTestFileName())); + FileInfo fileFour = new FileInfo(Path.Combine(testSubdirectory.FullName, GetTestFileName())); + + fileOne.Create().Dispose(); + fileTwo.Create().Dispose(); + fileThree.Create().Dispose(); + fileFour.Create().Dispose(); + + string[] paths = GetPaths(testDirectory.FullName, new EnumerationOptions { AttributesToSkip = FileAttributes.Directory, RecurseSubdirectories = true }); + FSAssert.EqualWhenOrdered(new string[] { fileOne.FullName, fileTwo.FullName }, paths); + } + } + + public class SkipAttributeTests_Directory_GetFiles : SkipAttributeTests + { + protected override string[] GetPaths(string directory, EnumerationOptions options) + { + return Directory.GetFiles(directory, "*", options); + } + } + + public class SkipAttributeTests_DirectoryInfo_GetFiles : SkipAttributeTests + { + protected override string[] GetPaths(string directory, EnumerationOptions options) + { + return new DirectoryInfo(directory).GetFiles("*", options).Select(i => i.FullName).ToArray(); + } + } +} diff --git a/src/System.IO.FileSystem/tests/Enumeration/SpecialDirectoryTests.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/SpecialDirectoryTests.netcoreapp.cs new file mode 100644 index 0000000000..5043190cff --- /dev/null +++ b/src/System.IO.FileSystem/tests/Enumeration/SpecialDirectoryTests.netcoreapp.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.IO.Enumeration; +using System.Linq; +using Xunit; + +namespace System.IO.Tests.Enumeration +{ + + public class SpecialDirectoryTests : FileSystemTest + { + private class DirectoryRecursed : FileSystemEnumerator<string> + { + public int ShouldRecurseCalls { get; private set; } + + public DirectoryRecursed(string directory, EnumerationOptions options) + : base(directory, options) + { + } + + protected override string TransformEntry(ref FileSystemEntry entry) + =>new string(entry.FileName); + + protected override bool ShouldRecurseIntoEntry(ref FileSystemEntry entry) + { + ShouldRecurseCalls++; + return base.ShouldRecurseIntoEntry(ref entry); + } + } + + [Fact] + public void SpecialDirectoriesAreNotUpForRecursion() + { + using (var recursed = new DirectoryRecursed(TestDirectory, new EnumerationOptions { ReturnSpecialDirectories = true, RecurseSubdirectories = true, AttributesToSkip = 0 })) + { + List<string> results = new List<string>(); + while (recursed.MoveNext()) + results.Add(recursed.Current); + + Assert.Equal(0, recursed.ShouldRecurseCalls); + Assert.Contains("..", results); + } + } + } + + public class SpecialDirectoryTests_Enumerable : FileSystemTest + { + protected virtual string[] GetNames(string directory, EnumerationOptions options) + { + return new FileSystemEnumerable<string>( + directory, + (ref FileSystemEntry entry) => new string(entry.FileName), + options).ToArray(); + } + + [Fact] + public void SkippingHiddenFiles() + { + // Files that begin with periods are considered hidden on Unix + string[] paths = GetNames(TestDirectory, new EnumerationOptions { ReturnSpecialDirectories = true, AttributesToSkip = 0 }); + Assert.Contains(".", paths); + Assert.Contains("..", paths); + } + } + + public class SpecialDirectoryTests_DirectoryInfo_GetDirectories : SpecialDirectoryTests_Enumerable + { + protected override string[] GetNames(string directory, EnumerationOptions options) + { + return new DirectoryInfo(directory).GetDirectories("*", options).Select(i => i.Name).ToArray(); + } + } +} diff --git a/src/System.IO.FileSystem/tests/Enumeration/TrimmedPaths.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/TrimmedPaths.netcoreapp.cs new file mode 100644 index 0000000000..ed73117634 --- /dev/null +++ b/src/System.IO.FileSystem/tests/Enumeration/TrimmedPaths.netcoreapp.cs @@ -0,0 +1,284 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using Xunit; + +namespace System.IO.Tests.Enumeration +{ + public class TrimmedPaths : FileSystemTest + { + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void TrimmedPathsAreFound_Windows() + { + // Trailing spaces and periods are eaten when normalizing in Windows, making them impossible + // to access without using the \\?\ device syntax. We should, however, be able to find them + // and retain the filename in the info classes and string results. + + DirectoryInfo directory = Directory.CreateDirectory(GetTestFilePath()); + File.Create(@"\\?\" + Path.Combine(directory.FullName, "Trailing space ")).Dispose(); + File.Create(@"\\?\" + Path.Combine(directory.FullName, "Trailing period.")).Dispose(); + + FileInfo[] files = directory.GetFiles(); + Assert.Equal(2, files.Count()); + FSAssert.EqualWhenOrdered(new string[] { "Trailing space ", "Trailing period." }, files.Select(f => f.Name)); + + var paths = Directory.GetFiles(directory.FullName); + Assert.Equal(2, paths.Count()); + FSAssert.EqualWhenOrdered(new string[] { "Trailing space ", "Trailing period." }, paths.Select(p => Path.GetFileName(p))); + } + + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void TrimmedPathsDeletion_Windows() + { + // Trailing spaces and periods are eaten when normalizing in Windows, making them impossible + // to access without using the \\?\ device syntax. We should, however, be able to delete them + // from the info class. + + DirectoryInfo directory = Directory.CreateDirectory(GetTestFilePath()); + File.Create(@"\\?\" + Path.Combine(directory.FullName, "Trailing space ")).Dispose(); + File.Create(@"\\?\" + Path.Combine(directory.FullName, "Trailing period.")).Dispose(); + + // With just a path name, the trailing space/period will get eaten, so we + // can't delete without prepending- they won't "exist". + var paths = Directory.GetFiles(directory.FullName); + Assert.All(paths, p => Assert.False(File.Exists(p))); + + FileInfo[] files = directory.GetFiles(); + Assert.Equal(2, files.Count()); + Assert.All(files, f => Assert.True(f.Exists)); + foreach (FileInfo f in files) + f.Refresh(); + Assert.All(files, f => Assert.True(f.Exists)); + foreach (FileInfo f in files) + { + f.Delete(); + f.Refresh(); + } + Assert.All(files, f => Assert.False(f.Exists)); + + foreach (FileInfo f in files) + { + f.Create().Dispose(); + f.Refresh(); + } + Assert.All(files, f => Assert.True(f.Exists)); + } + + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void TrimmedPathsOpen_Windows() + { + // Trailing spaces and periods are eaten when normalizing in Windows, making them impossible + // to access without using the \\?\ device syntax. We should, however, be able to open them + // from the info class when enumerating. + + DirectoryInfo directory = Directory.CreateDirectory(GetTestFilePath()); + string fileOne = Path.Join(directory.FullName, "Trailing space "); + string fileTwo = Path.Join(directory.FullName, "Trailing period."); + File.Create(@"\\?\" + fileOne).Dispose(); + File.Create(@"\\?\" + fileTwo).Dispose(); + + FileInfo[] files = directory.GetFiles(); + Assert.Equal(2, files.Length); + foreach (FileInfo fi in directory.GetFiles()) + { + // Shouldn't throw hitting any of the Open overloads + using (FileStream stream = fi.Open(FileMode.Open)) + { } + using (FileStream stream = fi.Open(FileMode.Open, FileAccess.Read)) + { } + using (FileStream stream = fi.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { } + using (FileStream stream = fi.OpenRead()) + { } + using (FileStream stream = fi.OpenWrite()) + { } + } + } + + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void TrimmedPathsText_Windows() + { + // Trailing spaces and periods are eaten when normalizing in Windows, making them impossible + // to access without using the \\?\ device syntax. We should, however, be able to open readers + // and writers from the the info class when enumerating. + + DirectoryInfo directory = Directory.CreateDirectory(GetTestFilePath()); + string fileOne = Path.Join(directory.FullName, "Trailing space "); + string fileTwo = Path.Join(directory.FullName, "Trailing period."); + File.WriteAllText(@"\\?\" + fileOne, "space"); + File.WriteAllText(@"\\?\" + fileTwo, "period"); + + FileInfo[] files = directory.GetFiles(); + Assert.Equal(2, files.Length); + foreach (FileInfo fi in directory.GetFiles()) + { + using (StreamReader reader = fi.OpenText()) + { + string content = reader.ReadToEnd(); + if (fi.FullName.EndsWith(fileOne)) + { + Assert.Equal("space", content); + } + else if (fi.FullName.EndsWith(fileTwo)) + { + Assert.Equal("period", content); + } + else + { + Assert.False(true, $"Unexpected name '{fi.FullName}'"); + } + } + + using (StreamWriter writer = fi.CreateText()) + { + writer.Write("foo"); + } + + using (StreamReader reader = fi.OpenText()) + { + Assert.Equal("foo", reader.ReadToEnd()); + } + + using (StreamWriter writer = fi.AppendText()) + { + writer.Write("bar"); + } + + using (StreamReader reader = fi.OpenText()) + { + Assert.Equal("foobar", reader.ReadToEnd()); + } + } + } + + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void TrimmedPathsCopyTo_Windows() + { + // Trailing spaces and periods are eaten when normalizing in Windows, making them impossible + // to access without using the \\?\ device syntax. We should, however, be able to copy them + // without the special syntax from the info class when enumerating. + + DirectoryInfo directory = Directory.CreateDirectory(GetTestFilePath()); + string fileOne = Path.Join(directory.FullName, "Trailing space "); + string fileTwo = Path.Join(directory.FullName, "Trailing period."); + File.Create(@"\\?\" + fileOne).Dispose(); + File.Create(@"\\?\" + fileTwo).Dispose(); + + FileInfo[] files = directory.GetFiles(); + Assert.Equal(2, files.Length); + foreach (FileInfo fi in directory.GetFiles()) + { + FileInfo newInfo = fi.CopyTo(Path.Join(directory.FullName, GetTestFileName())); + Assert.True(newInfo.Exists); + FileInfo newerInfo = fi.CopyTo(Path.Join(directory.FullName, GetTestFileName()), overwrite: true); + Assert.True(newerInfo.Exists); + } + + Assert.Equal(6, directory.GetFiles().Length); + } + + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void TrimmedPathsReplace_Windows() + { + // Trailing spaces and periods are eaten when normalizing in Windows, making them impossible + // to access without using the \\?\ device syntax. We should, however, be able to replace them + // from the info class when enumerating. + + DirectoryInfo directory = Directory.CreateDirectory(GetTestFilePath()); + string fileOne = Path.Join(directory.FullName, "Trailing space "); + string fileTwo = Path.Join(directory.FullName, "Trailing period."); + File.WriteAllText(@"\\?\" + fileOne, "space"); + File.WriteAllText(@"\\?\" + fileTwo, "period"); + + FileInfo[] files = directory.GetFiles(); + Assert.Equal(2, files.Length); + + FileInfo destination = new FileInfo(Path.Join(directory.FullName, GetTestFileName())); + destination.Create().Dispose(); + + foreach (FileInfo fi in files) + { + fi.Replace(destination.FullName, null); + using (StreamReader reader = destination.OpenText()) + { + string content = reader.ReadToEnd(); + if (fi.FullName.EndsWith(fileOne)) + { + Assert.Equal("space", content); + } + else if (fi.FullName.EndsWith(fileTwo)) + { + Assert.Equal("period", content); + } + else + { + Assert.False(true, $"Unexpected name '{fi.FullName}'"); + } + } + } + } + + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void TrimmedPathsMoveTo_Windows() + { + // Trailing spaces and periods are eaten when normalizing in Windows, making them impossible + // to access without using the \\?\ device syntax. We should, however, be able to move them + // without the special syntax from the info class when enumerating. + + DirectoryInfo directory = Directory.CreateDirectory(GetTestFilePath()); + DirectoryInfo spaceDirectory = Directory.CreateDirectory(Path.Join(@"\\?\", directory.FullName, "Trailing space ")); + DirectoryInfo periodDirectory = Directory.CreateDirectory(Path.Join(@"\\?\", directory.FullName, "Trailing period.")); + string spaceFile = Path.Join(spaceDirectory.FullName, "space"); + string periodFile = Path.Join(periodDirectory.FullName, "period"); + File.Create(spaceFile).Dispose(); + File.Create(periodFile).Dispose(); + + DirectoryInfo[] directories = directory.GetDirectories(); + Assert.Equal(2, directories.Length); + foreach (DirectoryInfo di in directories) + { + if (di.Name == "Trailing space ") + { + di.MoveTo(Path.Join(directory.FullName, "WasSpace")); + } + else if (di.Name == "Trailing period.") + { + di.MoveTo(Path.Join(directory.FullName, "WasPeriod")); + } + else + { + Assert.False(true, $"Found unexpected name '{di.Name}'"); + } + } + + directories = directory.GetDirectories(); + Assert.Equal(2, directories.Length); + foreach (DirectoryInfo di in directories) + { + if (di.Name == "WasSpace") + { + FileInfo fi = di.GetFiles().Single(); + Assert.Equal("space", fi.Name); + } + else if (di.Name == "WasPeriod") + { + FileInfo fi = di.GetFiles().Single(); + Assert.Equal("period", fi.Name); + } + else + { + Assert.False(true, $"Found unexpected name '{di.Name}'"); + } + } + } + } +} diff --git a/src/System.IO.FileSystem/tests/FSAssert.cs b/src/System.IO.FileSystem/tests/FSAssert.cs index 9fbefaa874..10125d8537 100644 --- a/src/System.IO.FileSystem/tests/FSAssert.cs +++ b/src/System.IO.FileSystem/tests/FSAssert.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Xunit; @@ -62,5 +64,10 @@ namespace System.IO.Tests Assert.NotNull(tce); Assert.Equal(ct, tce.CancellationToken); } + + public static void EqualWhenOrdered<T>(IEnumerable<T> expected, IEnumerable<T> actual) + { + Assert.Equal(expected.OrderBy(e => e).Select(o => o), actual.OrderBy(a => a).Select(o => o)); + } } } diff --git a/src/System.IO.FileSystem/tests/File/Copy.cs b/src/System.IO.FileSystem/tests/File/Copy.cs index 036dedae6a..1caca2aae1 100644 --- a/src/System.IO.FileSystem/tests/File/Copy.cs +++ b/src/System.IO.FileSystem/tests/File/Copy.cs @@ -4,23 +4,17 @@ using System.Collections.Generic; using System.Linq; -using System.Runtime.InteropServices; using Xunit; namespace System.IO.Tests { public partial class File_Copy_str_str : FileSystemTest { - #region Utilities - - public static TheoryData WindowsInvalidUnixValid = new TheoryData<string> { " ", " ", "\n", ">", "<", "\t" }; public virtual void Copy(string source, string dest) { File.Copy(source, dest); } - #endregion - #region UniversalTests [Fact] @@ -160,10 +154,11 @@ namespace System.IO.Tests #region PlatformSpecific - [Theory, - MemberData(nameof(WindowsInvalidUnixValid))] - [PlatformSpecific(TestPlatforms.Windows)] // Whitespace path throws ArgumentException - public void WindowsWhitespacePath(string invalid) + [Theory, + InlineData(" "), + InlineData(" ")] + [PlatformSpecific(TestPlatforms.Windows)] + public void WindowsAllSpacePath(string invalid) { string testFile = GetTestFilePath(); File.Create(testFile).Dispose(); @@ -173,24 +168,92 @@ namespace System.IO.Tests } [Theory, - MemberData(nameof(WindowsInvalidUnixValid))] - [PlatformSpecific(TestPlatforms.AnyUnix)] // Whitespace path allowed - public void UnixWhitespacePath(string valid) + InlineData("\n"), + InlineData(">"), + InlineData("<"), + InlineData("\t")] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] + public void WindowsInvalidCharsPath_Desktop(string invalid) { string testFile = GetTestFilePath(); File.Create(testFile).Dispose(); + Assert.Throws<ArgumentException>(() => Copy(testFile, invalid)); + Assert.Throws<ArgumentException>(() => Copy(invalid, testFile)); + } + + [Theory, + InlineData("\n"), + InlineData(">"), + InlineData("<"), + InlineData("\t")] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void WindowsInvalidCharsPath_Core(string invalid) + { + string testFile = GetTestFilePath(); + File.Create(testFile).Dispose(); + + Assert.Throws<IOException>(() => Copy(testFile, invalid)); + Assert.Throws<IOException>(() => Copy(invalid, testFile)); + } + + [Theory, + InlineData(" "), + InlineData(" "), + InlineData("\n"), + InlineData(">"), + InlineData("<"), + InlineData("\t")] + [PlatformSpecific(TestPlatforms.AnyUnix)] + public void UnixInvalidWindowsPaths(string valid) + { + // Unix allows whitespaces paths that aren't valid on Windows + string testFile = GetTestFilePath(); + File.Create(testFile).Dispose(); + Copy(testFile, Path.Combine(TestDirectory, valid)); Assert.True(File.Exists(testFile)); Assert.True(File.Exists(Path.Combine(TestDirectory, valid))); } + + [Theory, + InlineData("", ":bar"), + InlineData("", ":bar:$DATA"), + InlineData("::$DATA", ":bar"), + InlineData("::$DATA", ":bar:$DATA")] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void WindowsAlternateDataStream(string defaultStream, string alternateStream) + { + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + string testFile = Path.Combine(testDirectory.FullName, GetTestFileName()); + string testFileDefaultStream = testFile + defaultStream; + string testFileAlternateStream = testFile + alternateStream; + + // Copy the default stream into an alternate stream + File.WriteAllText(testFileDefaultStream, "Foo"); + Copy(testFileDefaultStream, testFileAlternateStream); + Assert.Equal(testFile, testDirectory.GetFiles().Single().FullName); + Assert.Equal("Foo", File.ReadAllText(testFileDefaultStream)); + Assert.Equal("Foo", File.ReadAllText(testFileAlternateStream)); + + // Copy another file over the alternate stream + string testFile2 = Path.Combine(testDirectory.FullName, GetTestFileName()); + string testFile2DefaultStream = testFile2 + defaultStream; + File.WriteAllText(testFile2DefaultStream, "Bar"); + Assert.Throws<IOException>(() => Copy(testFile2DefaultStream, testFileAlternateStream)); + + // This always throws as you can't copy an alternate stream out (oddly) + Assert.Throws<IOException>(() => Copy(testFileAlternateStream, testFile2)); + Assert.Throws<IOException>(() => Copy(testFileAlternateStream, testFile2 + alternateStream)); + } #endregion } public class File_Copy_str_str_b : File_Copy_str_str { - #region Utilities - public override void Copy(string source, string dest) { File.Copy(source, dest, false); @@ -201,10 +264,6 @@ namespace System.IO.Tests File.Copy(source, dest, overwrite); } - #endregion - - #region UniversalTests - [Fact] public void OverwriteTrue() { @@ -257,6 +316,38 @@ namespace System.IO.Tests } } - #endregion + [Theory, + InlineData("", ":bar"), + InlineData("", ":bar:$DATA"), + InlineData("::$DATA", ":bar"), + InlineData("::$DATA", ":bar:$DATA")] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void WindowsAlternateDataStreamOverwrite(string defaultStream, string alternateStream) + { + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + string testFile = Path.Combine(testDirectory.FullName, GetTestFileName()); + string testFileDefaultStream = testFile + defaultStream; + string testFileAlternateStream = testFile + alternateStream; + + // Copy the default stream into an alternate stream + File.WriteAllText(testFileDefaultStream, "Foo"); + Copy(testFileDefaultStream, testFileAlternateStream); + Assert.Equal(testFile, testDirectory.GetFiles().Single().FullName); + Assert.Equal("Foo", File.ReadAllText(testFileDefaultStream)); + Assert.Equal("Foo", File.ReadAllText(testFileAlternateStream)); + + // Copy another file over the alternate stream + string testFile2 = Path.Combine(testDirectory.FullName, GetTestFileName()); + string testFile2DefaultStream = testFile2 + defaultStream; + File.WriteAllText(testFile2DefaultStream, "Bar"); + Copy(testFile2DefaultStream, testFileAlternateStream, overwrite: true); + Assert.Equal("Foo", File.ReadAllText(testFileDefaultStream)); + Assert.Equal("Bar", File.ReadAllText(testFileAlternateStream)); + + // This always throws as you can't copy an alternate stream out (oddly) + Assert.Throws<IOException>(() => Copy(testFileAlternateStream, testFile2, overwrite: true)); + Assert.Throws<IOException>(() => Copy(testFileAlternateStream, testFile2 + alternateStream, overwrite: true)); + } } } diff --git a/src/System.IO.FileSystem/tests/File/Create.cs b/src/System.IO.FileSystem/tests/File/Create.cs index d53ac85512..c910e735b3 100644 --- a/src/System.IO.FileSystem/tests/File/Create.cs +++ b/src/System.IO.FileSystem/tests/File/Create.cs @@ -8,15 +8,11 @@ namespace System.IO.Tests { public class File_Create_str : FileSystemTest { - #region Utilities - public virtual FileStream Create(string path) { return File.Create(path); } - #endregion - #region UniversalTests [Fact] @@ -201,8 +197,9 @@ namespace System.IO.Tests } [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Invalid file name with wildcard characters on Windows - public void WindowsWildCharacterPath() + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] + public void WindowsWildCharacterPath_Desktop() { DirectoryInfo testDir = Directory.CreateDirectory(GetTestFilePath()); Assert.Throws<ArgumentException>(() => Create(Path.Combine(testDir.FullName, "dls;d", "442349-0", "v443094(*)(+*$#$*", new string(Path.DirectorySeparatorChar, 3)))); @@ -211,20 +208,53 @@ namespace System.IO.Tests Assert.Throws<ArgumentException>(() => Create(Path.Combine(testDir.FullName, "*Tes*t"))); } + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void WindowsWildCharacterPath_Core() + { + DirectoryInfo testDir = Directory.CreateDirectory(GetTestFilePath()); + Assert.ThrowsAny<IOException>(() => Create(Path.Combine(testDir.FullName, "dls;d", "442349-0", "v443094(*)(+*$#$*", new string(Path.DirectorySeparatorChar, 3)))); + Assert.ThrowsAny<IOException>(() => Create(Path.Combine(testDir.FullName, "*"))); + Assert.ThrowsAny<IOException>(() => Create(Path.Combine(testDir.FullName, "Test*t"))); + Assert.ThrowsAny<IOException>(() => Create(Path.Combine(testDir.FullName, "*Tes*t"))); + } + [Theory, InlineData(" "), - InlineData(" "), + InlineData(""), + InlineData("\0"), + InlineData(" ")] + [PlatformSpecific(TestPlatforms.Windows)] + public void WindowsEmptyPath(string path) + { + Assert.Throws<ArgumentException>(() => Create(path)); + } + + [Theory, InlineData("\n"), InlineData(">"), InlineData("<"), - InlineData("\0"), InlineData("\t")] - [PlatformSpecific(TestPlatforms.Windows)] // Invalid file name with whitespace on Windows - public void WindowsWhitespacePath(string path) + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] + public void WindowsInvalidPath_Desktop(string path) { Assert.Throws<ArgumentException>(() => Create(path)); } + [Theory, + InlineData("\n"), + InlineData(">"), + InlineData("<"), + InlineData("\t")] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void WindowsInvalidPath_Core(string path) + { + Assert.ThrowsAny<IOException>(() => Create(Path.Combine(TestDirectory, path))); + } + [Fact] [PlatformSpecific(TestPlatforms.AnyUnix)] public void CreateNullThrows_Unix() @@ -249,6 +279,49 @@ namespace System.IO.Tests } } + [Theory, + InlineData(":bar"), + InlineData(":bar:$DATA"), + InlineData("::$DATA")] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void WindowsAlternateDataStream(string streamName) + { + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + streamName = Path.Combine(testDirectory.FullName, GetTestFileName()) + streamName; + using (Create(streamName)) + { + Assert.True(File.Exists(streamName)); + } + } + + [Theory, + InlineData(":bar"), + InlineData(":bar:$DATA")] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void WindowsAlternateDataStream_OnExisting(string streamName) + { + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + + // On closed file + string fileName = Path.Combine(testDirectory.FullName, GetTestFileName()); + Create(fileName).Dispose(); + streamName = fileName + streamName; + using (Create(streamName)) + { + Assert.True(File.Exists(streamName)); + } + + // On open file + fileName = Path.Combine(testDirectory.FullName, GetTestFileName()); + using (Create(fileName)) + using (Create(streamName)) + { + Assert.True(File.Exists(streamName)); + } + } + #endregion } diff --git a/src/System.IO.FileSystem/tests/File/Delete.cs b/src/System.IO.FileSystem/tests/File/Delete.cs index 398833f102..7cd005f944 100644 --- a/src/System.IO.FileSystem/tests/File/Delete.cs +++ b/src/System.IO.FileSystem/tests/File/Delete.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Diagnostics; using Xunit; using Xunit.NetCore.Extensions; @@ -10,8 +9,6 @@ namespace System.IO.Tests { public class File_Delete : FileSystemTest { - #region Utilities - public virtual void Delete(string path) { File.Delete(path); @@ -24,8 +21,6 @@ namespace System.IO.Tests return ret; } - #endregion - #region UniversalTests [Fact] @@ -129,7 +124,7 @@ namespace System.IO.Tests [Trait(XunitConstants.Category, XunitConstants.RequiresElevation)] public void Unix_NonExistentPath_ReadOnlyVolume() { - if (PlatformDetection.IsRedHatFamily6) + if (PlatformDetection.IsRedHatFamily6 || PlatformDetection.IsAlpine) return; // [ActiveIssue(https://github.com/dotnet/corefx/issues/21920)] ReadOnly_FileSystemHelper(readOnlyDirectory => @@ -199,6 +194,24 @@ namespace System.IO.Tests Assert.False(testFile.Exists); } + [Theory, + InlineData(":bar"), + InlineData(":bar:$DATA")] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void WindowsDeleteAlternateDataStream(string streamName) + { + FileInfo testFile = Create(GetTestFilePath()); + testFile.Create().Dispose(); + streamName = testFile.FullName + streamName; + File.Create(streamName).Dispose(); + Assert.True(File.Exists(streamName)); + Delete(streamName); + Assert.False(File.Exists(streamName)); + testFile.Refresh(); + Assert.True(testFile.Exists); + } + #endregion } } diff --git a/src/System.IO.FileSystem/tests/File/Exists.cs b/src/System.IO.FileSystem/tests/File/Exists.cs index e1be14a175..24eec6c5cb 100644 --- a/src/System.IO.FileSystem/tests/File/Exists.cs +++ b/src/System.IO.FileSystem/tests/File/Exists.cs @@ -231,7 +231,7 @@ namespace System.IO.Tests [Theory, - MemberData(nameof(PathsWithAlternativeDataStreams))] + MemberData(nameof(PathsWithColons))] [PlatformSpecific(TestPlatforms.Windows)] // alternate data stream public void PathWithAlternateDataStreams_ReturnsFalse(string component) { diff --git a/src/System.IO.FileSystem/tests/File/GetSetAttributes.cs b/src/System.IO.FileSystem/tests/File/GetSetAttributes.cs index d290ce7971..7a115ad80b 100644 --- a/src/System.IO.FileSystem/tests/File/GetSetAttributes.cs +++ b/src/System.IO.FileSystem/tests/File/GetSetAttributes.cs @@ -18,6 +18,20 @@ namespace System.IO.Tests Assert.Throws<FileNotFoundException>(() => GetAttributes(GetTestFilePath() + trailingChar)); } + // Getting only throws for File, not FileInfo + [Theory, + InlineData(":bar"), + InlineData(":bar:$DATA")] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void GetAttributes_MissingAlternateDataStream_Windows(string streamName) + { + string path = CreateItem(); + streamName = path + streamName; + + Assert.Throws<FileNotFoundException>(() => GetAttributes(streamName)); + } + [Theory, MemberData(nameof(TrailingCharacters))] public void GetAttributes_MissingDirectory(char trailingChar) { diff --git a/src/System.IO.FileSystem/tests/File/Move.cs b/src/System.IO.FileSystem/tests/File/Move.cs index bc1199e122..304094a999 100644 --- a/src/System.IO.FileSystem/tests/File/Move.cs +++ b/src/System.IO.FileSystem/tests/File/Move.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Xunit; +using System.Linq; namespace System.IO.Tests { @@ -44,17 +45,26 @@ namespace System.IO.Tests } [Theory, MemberData(nameof(PathsWithInvalidCharacters))] - public void PathWithIllegalCharacters(string invalidPath) + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] + public void PathWithIllegalCharacters_Desktop(string invalidPath) { FileInfo testFile = new FileInfo(GetTestFilePath()); testFile.Create().Dispose(); - // Under legacy normalization we kick \\?\ paths back as invalid with ArgumentException - // New style we don't prevalidate \\?\ at all - if (invalidPath.Contains(@"\\?\") && !PathFeatures.IsUsingLegacyPathNormalization()) - Assert.Throws<IOException>(() => Move(testFile.FullName, invalidPath)); - else + Assert.Throws<ArgumentException>(() => Move(testFile.FullName, invalidPath)); + } + + [Theory, MemberData(nameof(PathsWithInvalidCharacters))] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void PathWithIllegalCharacters_Core(string invalidPath) + { + FileInfo testFile = new FileInfo(GetTestFilePath()); + testFile.Create().Dispose(); + + if (invalidPath.Contains('\0'.ToString())) Assert.Throws<ArgumentException>(() => Move(testFile.FullName, invalidPath)); + else + Assert.ThrowsAny<IOException>(() => Move(testFile.FullName, invalidPath)); } [Fact] @@ -225,17 +235,35 @@ namespace System.IO.Tests [Theory, MemberData(nameof(PathsWithInvalidColons))] [PlatformSpecific(TestPlatforms.Windows)] - [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Versions of netfx older than 4.6.2 throw an ArgumentException instead of NotSupportedException. Until all of our machines run netfx against the actual latest version, these will fail.")] - public void WindowsPathWithIllegalColons(string invalidPath) + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] + public void WindowsPathWithIllegalColons_Desktop(string invalidPath) + { + FileInfo testFile = new FileInfo(GetTestFilePath()); + testFile.Create().Dispose(); + if (PathFeatures.IsUsingLegacyPathNormalization()) + { + Assert.Throws<ArgumentException>(() => Move(testFile.FullName, invalidPath)); + } + else + { + Assert.Throws<NotSupportedException>(() => Move(testFile.FullName, invalidPath)); + } + } + + [Theory, MemberData(nameof(PathsWithInvalidColons))] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void WindowsPathWithIllegalColons_Core(string invalidPath) { FileInfo testFile = new FileInfo(GetTestFilePath()); testFile.Create().Dispose(); - Assert.Throws<NotSupportedException>(() => Move(testFile.FullName, invalidPath)); + Assert.ThrowsAny<IOException>(() => Move(testFile.FullName, testFile.DirectoryName + Path.DirectorySeparatorChar + invalidPath)); } [Fact] - [PlatformSpecific(TestPlatforms.Windows)] // Wild characters in path throw ArgumentException - public void WindowsWildCharacterPath() + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] + public void WindowsWildCharacterPath_Desktop() { Assert.Throws<ArgumentException>(() => Move("*", GetTestFilePath())); Assert.Throws<ArgumentException>(() => Move(GetTestFilePath(), "*")); @@ -244,6 +272,18 @@ namespace System.IO.Tests } [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void WindowsWildCharacterPath_Core() + { + Assert.Throws<FileNotFoundException>(() => Move(Path.Combine(TestDirectory, "*"), GetTestFilePath())); + Assert.Throws<FileNotFoundException>(() => Move(GetTestFilePath(), Path.Combine(TestDirectory, "*"))); + Assert.Throws<FileNotFoundException>(() => Move(GetTestFilePath(), Path.Combine(TestDirectory, "Test*t"))); + Assert.Throws<FileNotFoundException>(() => Move(GetTestFilePath(), Path.Combine(TestDirectory, "*Test"))); + } + + + [Fact] [PlatformSpecific(TestPlatforms.AnyUnix)] // Wild characters in path are allowed public void UnixWildCharacterPath() { @@ -268,9 +308,29 @@ namespace System.IO.Tests } [Theory, - MemberData(nameof(WhiteSpace))] - [PlatformSpecific(TestPlatforms.Windows)] // Whitespace in path throws ArgumentException - public void WindowsWhitespacePath(string whitespace) + MemberData(nameof(ControlWhiteSpace))] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] + public void WindowsControlPath_Desktop(string whitespace) + { + FileInfo testFile = new FileInfo(GetTestFilePath()); + Assert.Throws<ArgumentException>(() => Move(testFile.FullName, whitespace)); + } + + [Theory, + MemberData(nameof(ControlWhiteSpace))] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void WindowsControlPath_Core(string whitespace) + { + FileInfo testFile = new FileInfo(GetTestFilePath()); + Assert.ThrowsAny<IOException>(() => Move(testFile.FullName, Path.Combine(TestDirectory, whitespace))); + } + + [Theory, + MemberData(nameof(SimpleWhiteSpace))] + [PlatformSpecific(TestPlatforms.Windows)] + public void WindowsSimpleWhitespacePath(string whitespace) { FileInfo testFile = new FileInfo(GetTestFilePath()); Assert.Throws<ArgumentException>(() => Move(testFile.FullName, whitespace)); @@ -289,6 +349,29 @@ namespace System.IO.Tests } + [Theory, + InlineData("", ":bar"), + InlineData("", ":bar:$DATA"), + InlineData("::$DATA", ":bar"), + InlineData("::$DATA", ":bar:$DATA")] + [PlatformSpecific(TestPlatforms.Windows)] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void WindowsAlternateDataStreamMove(string defaultStream, string alternateStream) + { + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + string testFile = Path.Combine(testDirectory.FullName, GetTestFileName()); + string testFileDefaultStream = testFile + defaultStream; + string testFileAlternateStream = testFile + alternateStream; + + // Cannot move into an alternate stream + File.WriteAllText(testFileDefaultStream, "Foo"); + Assert.Throws<IOException>(() => Move(testFileDefaultStream, testFileAlternateStream)); + + // Cannot move out of an alternate stream + File.WriteAllText(testFileAlternateStream, "Bar"); + string testFile2 = Path.Combine(testDirectory.FullName, GetTestFileName()); + Assert.Throws<IOException>(() => Move(testFileAlternateStream, testFile2)); + } #endregion } } diff --git a/src/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs b/src/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs index 7764cdfe5b..b92e5201bd 100644 --- a/src/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs +++ b/src/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; using System.Text; +using System.Threading.Tasks; using Xunit; namespace System.IO.Tests @@ -120,5 +121,57 @@ namespace System.IO.Tests File.SetAttributes(path, FileAttributes.Normal); } } + + [Fact] + public void EmptyFile_ReturnsEmptyArray() + { + string path = GetTestFilePath(); + File.Create(path).Dispose(); + Assert.Equal(0, File.ReadAllBytes(path).Length); + } + + [Theory] + [PlatformSpecific(TestPlatforms.Linux)] + [InlineData("/proc/cmdline")] + [InlineData("/proc/version")] + [InlineData("/proc/filesystems")] + public void ProcFs_EqualsReadAllText(string path) + { + byte[] bytes = null; + string text = null; + + const int NumTries = 3; // some of these could theoretically change between reads, so allow retries just in case + for (int i = 1; i <= NumTries; i++) + { + try + { + bytes = File.ReadAllBytes(path); + text = File.ReadAllText(path); + Assert.Equal(text, Encoding.UTF8.GetString(bytes)); + } + catch when (i < NumTries) { } + } + } + + [Theory] + [PlatformSpecific(TestPlatforms.Linux)] + public void ReadAllBytes_ProcFs_Uptime_ContainsTwoNumbers() + { + string text = Encoding.UTF8.GetString(File.ReadAllBytes("/proc/uptime")); + string[] parts = text.Split(new [] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + Assert.Equal(2, parts.Length); + Assert.True(double.TryParse(parts[0].Trim(), out _)); + Assert.True(double.TryParse(parts[1].Trim(), out _)); + } + + [Theory] + [PlatformSpecific(TestPlatforms.Linux)] + [InlineData("/proc/meminfo")] + [InlineData("/proc/stat")] + [InlineData("/proc/cpuinfo")] + public void ProcFs_NotEmpty(string path) + { + Assert.InRange(File.ReadAllBytes(path).Length, 1, int.MaxValue); + } } } diff --git a/src/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs b/src/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs index 47b3d71ccc..fa2a8faa7f 100644 --- a/src/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs +++ b/src/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs @@ -134,5 +134,57 @@ namespace System.IO.Tests File.SetAttributes(path, FileAttributes.Normal); } } + + [Fact] + public async Task EmptyFile_ReturnsEmptyArray() + { + string path = GetTestFilePath(); + File.Create(path).Dispose(); + Assert.Equal(0, (await File.ReadAllBytesAsync(path)).Length); + } + + [Theory] + [PlatformSpecific(TestPlatforms.Linux)] + [InlineData("/proc/cmdline")] + [InlineData("/proc/version")] + [InlineData("/proc/filesystems")] + public async Task ProcFs_EqualsReadAllText(string path) + { + byte[] bytes = null; + string text = null; + + const int NumTries = 3; // some of these could theoretically change between reads, so allow retries just in case + for (int i = 1; i <= NumTries; i++) + { + try + { + bytes = await File.ReadAllBytesAsync(path); + text = await File.ReadAllTextAsync(path); + Assert.Equal(text, Encoding.UTF8.GetString(bytes)); + } + catch when (i < NumTries) { } + } + } + + [Theory] + [PlatformSpecific(TestPlatforms.Linux)] + public async Task ReadAllBytes_ProcFs_Uptime_ContainsTwoNumbers() + { + string text = Encoding.UTF8.GetString(await File.ReadAllBytesAsync("/proc/uptime")); + string[] parts = text.Split(' ', StringSplitOptions.RemoveEmptyEntries); + Assert.Equal(2, parts.Length); + Assert.True(double.TryParse(parts[0].Trim(), out _)); + Assert.True(double.TryParse(parts[1].Trim(), out _)); + } + + [Theory] + [PlatformSpecific(TestPlatforms.Linux)] + [InlineData("/proc/meminfo")] + [InlineData("/proc/stat")] + [InlineData("/proc/cpuinfo")] + public async Task ProcFs_NotEmpty(string path) + { + Assert.InRange((await File.ReadAllBytesAsync(path)).Length, 1, int.MaxValue); + } } } diff --git a/src/System.IO.FileSystem/tests/FileInfo/GetSetTimes.cs b/src/System.IO.FileSystem/tests/FileInfo/GetSetTimes.cs index ed38b89719..d88fa6ea00 100644 --- a/src/System.IO.FileSystem/tests/FileInfo/GetSetTimes.cs +++ b/src/System.IO.FileSystem/tests/FileInfo/GetSetTimes.cs @@ -19,6 +19,8 @@ namespace System.IO.Tests public override FileInfo GetMissingItem() => new FileInfo(GetTestFilePath()); + public override string GetItemPath(FileInfo item) => item.FullName; + public override void InvokeCreate(FileInfo item) => item.Create(); public override IEnumerable<TimeFunction> TimeFunctions(bool requiresRoundtripping = false) diff --git a/src/System.IO.FileSystem/tests/FileInfo/Open.cs b/src/System.IO.FileSystem/tests/FileInfo/Open.cs index c5df42fd5e..b9b35f51c2 100644 --- a/src/System.IO.FileSystem/tests/FileInfo/Open.cs +++ b/src/System.IO.FileSystem/tests/FileInfo/Open.cs @@ -13,22 +13,22 @@ namespace System.IO.Tests return new FileInfo(path).Open(mode); } - [Fact] + [Theory, MemberData(nameof(StreamSpecifiers))] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "FileInfo.Open(string, filemode) on netfx always uses FileAccess.ReadWrite instead of choosing a FileAccess based on the FileMode. This bug was fixed in netcoreapp.")] - public override void FileModeAppend() + public override void FileModeAppend(string streamSpecifier) { - using (FileStream fs = CreateFileStream(GetTestFilePath(), FileMode.Append)) + using (FileStream fs = CreateFileStream(GetTestFilePath() + streamSpecifier, FileMode.Append)) { Assert.Equal(false, fs.CanRead); Assert.Equal(true, fs.CanWrite); } } - [Fact] + [Theory, MemberData(nameof(StreamSpecifiers))] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "FileInfo.Open(string, filemode) on netfx always uses FileAccess.ReadWrite instead of choosing a FileAccess based on the FileMode. This bug was fixed in netcoreapp.")] - public override void FileModeAppendExisting() + public override void FileModeAppendExisting(string streamSpecifier) { - string fileName = GetTestFilePath(); + string fileName = GetTestFilePath() + streamSpecifier; using (FileStream fs = CreateFileStream(fileName, FileMode.Create)) { fs.WriteByte(0); diff --git a/src/System.IO.FileSystem/tests/FileStream/Dispose.cs b/src/System.IO.FileSystem/tests/FileStream/Dispose.cs index 026a82b910..edee857a62 100644 --- a/src/System.IO.FileSystem/tests/FileStream/Dispose.cs +++ b/src/System.IO.FileSystem/tests/FileStream/Dispose.cs @@ -4,6 +4,7 @@ using Microsoft.Win32.SafeHandles; using System; +using System.Diagnostics; using System.IO; using Xunit; @@ -45,6 +46,11 @@ namespace System.IO.Tests : base(path, mode) { } + public MyFileStream(SafeFileHandle handle, FileAccess access, Action<bool> disposeMethod) : base(handle, access) + { + DisposeMethod = disposeMethod; + } + public Action<bool> DisposeMethod { get; set; } protected override void Dispose(bool disposing) @@ -58,6 +64,92 @@ namespace System.IO.Tests } } + + [Fact] + public void Dispose_CallsVirtualDisposeTrueArg_ThrowsDuringFlushWriteBuffer_DisposeThrows() + { + RemoteInvoke(() => + { + string fileName = GetTestFilePath(); + using (FileStream fscreate = new FileStream(fileName, FileMode.Create)) + { + fscreate.WriteByte(0); + } + bool writeDisposeInvoked = false; + Action<bool> writeDisposeMethod = _ => writeDisposeInvoked = true; + using (var fsread = new FileStream(fileName, FileMode.Open, FileAccess.Read)) + { + Action act = () => // separate method to avoid JIT lifetime-extension issues + { + using (var fswrite = new MyFileStream(fsread.SafeFileHandle, FileAccess.Write, writeDisposeMethod)) + { + fswrite.WriteByte(0); + + // Normal dispose should call Dispose(true). Throws due to FS trying to flush write buffer + Assert.Throws<UnauthorizedAccessException>(() => fswrite.Dispose()); + Assert.True(writeDisposeInvoked, "Expected Dispose(true) to be called from Dispose()"); + writeDisposeInvoked = false; + + // Only throws on first Dispose call + fswrite.Dispose(); + Assert.True(writeDisposeInvoked, "Expected Dispose(true) to be called from Dispose()"); + writeDisposeInvoked = false; + } + Assert.True(writeDisposeInvoked, "Expected Dispose(true) to be called from Dispose() again"); + writeDisposeInvoked = false; + }; + act(); + + for (int i = 0; i < 2; i++) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + Assert.False(writeDisposeInvoked, "Expected finalizer to have been suppressed"); + } + return SuccessExitCode; + }).Dispose(); + } + + [Fact] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Missing fix for https://github.com/dotnet/coreclr/pull/16250")] + public void NoDispose_CallsVirtualDisposeFalseArg_ThrowsDuringFlushWriteBuffer_FinalizerWontThrow() + { + RemoteInvoke(() => + { + string fileName = GetTestFilePath(); + using (FileStream fscreate = new FileStream(fileName, FileMode.Create)) + { + fscreate.WriteByte(0); + } + bool writeDisposeInvoked = false; + Action<bool> writeDisposeMethod = (disposing) => + { + writeDisposeInvoked = true; + Assert.False(disposing, "Expected false arg to Dispose(bool)"); + }; + using (var fsread = new FileStream(fileName, FileMode.Open, FileAccess.Read)) + { + Action act = () => // separate method to avoid JIT lifetime-extension issues + { + var fswrite = new MyFileStream(fsread.SafeFileHandle, FileAccess.Write, writeDisposeMethod); + fswrite.WriteByte(0); + }; + act(); + + // Dispose is not getting called here. + // instead, make sure finalizer gets called and doesnt throw exception + for (int i = 0; i < 2; i++) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + Assert.True(writeDisposeInvoked, "Expected finalizer to be invoked but not throw exception"); + } + return SuccessExitCode; + }).Dispose(); + } + [Fact] public void Dispose_CallsVirtualDispose_TrueArg() { diff --git a/src/System.IO.FileSystem/tests/FileStream/Name.cs b/src/System.IO.FileSystem/tests/FileStream/Name.cs index 88fe39c402..d4dde6ed22 100644 --- a/src/System.IO.FileSystem/tests/FileStream/Name.cs +++ b/src/System.IO.FileSystem/tests/FileStream/Name.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Globalization; using System.IO; using Xunit; @@ -39,11 +40,16 @@ namespace System.IO.Tests [Fact] public void NameReturnsUnknownForHandle() { - using (FileStream fs = new FileStream(GetTestFilePath(), FileMode.Create, FileAccess.ReadWrite)) - using (FileStream fsh = new FileStream(fs.SafeFileHandle, FileAccess.ReadWrite)) + RemoteInvoke(() => { - Assert.Equal("[Unknown]", fsh.Name); - } + CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; + + using (FileStream fs = new FileStream(GetTestFilePath(), FileMode.Create, FileAccess.ReadWrite)) + using (FileStream fsh = new FileStream(fs.SafeFileHandle, FileAccess.ReadWrite)) + { + Assert.Equal("[Unknown]", fsh.Name); + } + }).Dispose(); } } } diff --git a/src/System.IO.FileSystem/tests/FileStream/ReadWriteSpan.netcoreapp.cs b/src/System.IO.FileSystem/tests/FileStream/ReadWriteSpan.netcoreapp.cs index 272b19769e..0b1898d1f5 100644 --- a/src/System.IO.FileSystem/tests/FileStream/ReadWriteSpan.netcoreapp.cs +++ b/src/System.IO.FileSystem/tests/FileStream/ReadWriteSpan.netcoreapp.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Buffers; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -188,6 +189,20 @@ namespace System.IO.Tests } [Fact] + public async Task NonEmptyFile_CustomOwnedMemory_ReadAsync_GetsExpectedData() + { + string fileName = GetTestFilePath(); + File.WriteAllBytes(fileName, TestBuffer); + + using (var fs = CreateFileStream(fileName, FileMode.Open)) + using (var buffer = new NativeOwnedMemory(TestBuffer.Length)) + { + Assert.Equal(TestBuffer.Length, await fs.ReadAsync(buffer.Memory)); + Assert.Equal<byte>(TestBuffer, buffer.Memory.ToArray()); + } + } + + [Fact] public void ReadOnly_WriteAsync_Throws() { string fileName = GetTestFilePath(); @@ -238,24 +253,49 @@ namespace System.IO.Tests Assert.Equal(TestBuffer, buffer); } } + + [Fact] + public async Task NonEmptyWriteAsync_CustomOwnedMemory_WritesExpectedData() + { + using (var mem = new NativeOwnedMemory(TestBuffer.Length)) + using (var fs = CreateFileStream(GetTestFilePath(), FileMode.Create)) + { + new Memory<byte>(TestBuffer).CopyTo(mem.Memory); + + await fs.WriteAsync(mem.Memory); + Assert.Equal(TestBuffer.Length, fs.Length); + Assert.Equal(TestBuffer.Length, fs.Position); + + fs.Position = 0; + var buffer = new byte[TestBuffer.Length]; + Assert.Equal(TestBuffer.Length, await fs.ReadAsync(new Memory<byte>(buffer))); + Assert.Equal(TestBuffer, buffer); + } + } } public class Sync_FileStream_ReadWrite_Span : FileStream_ReadWrite_Span { protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access) => - new FileStream(path, mode, access, FileShare.None, 0x1000, FileOptions.None); + new FileStream(path, mode, access, FileShare.None, bufferSize: 0x1000, FileOptions.None); } public class Async_FileStream_ReadWrite_Span : FileStream_ReadWrite_Span { protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access) => - new FileStream(path, mode, access, FileShare.None, 0x1000, FileOptions.Asynchronous); + new FileStream(path, mode, access, FileShare.None, bufferSize: 0x1000, FileOptions.Asynchronous); + } + + public class Async_NoBuffer_FileStream_ReadWrite_Span : FileStream_ReadWrite_Span + { + protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access) => + new FileStream(path, mode, access, FileShare.None, bufferSize: 1, FileOptions.Asynchronous); } public sealed class Sync_DerivedFileStream_ReadWrite_Span : Sync_FileStream_ReadWrite_Span { protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access) => - new DerivedFileStream(path, mode, access, FileShare.None, 0x1000, FileOptions.None); + new DerivedFileStream(path, mode, access, FileShare.None, bufferSize: 0x1000, FileOptions.None); [Fact] public void CallSpanReadWriteOnDerivedFileStream_ArrayMethodsUsed() @@ -299,7 +339,7 @@ namespace System.IO.Tests public sealed class Async_DerivedFileStream_ReadWrite_Span : Async_FileStream_ReadWrite_Span { protected override FileStream CreateFileStream(string path, FileMode mode, FileAccess access) => - new DerivedFileStream(path, mode, access, FileShare.None, 0x1000, FileOptions.Asynchronous); + new DerivedFileStream(path, mode, access, FileShare.None, bufferSize: 0x1000, FileOptions.Asynchronous); } internal sealed class DerivedFileStream : FileStream diff --git a/src/System.IO.FileSystem/tests/FileStream/WriteAsync.cs b/src/System.IO.FileSystem/tests/FileStream/WriteAsync.cs index fa1f45095d..a80eb79fbb 100644 --- a/src/System.IO.FileSystem/tests/FileStream/WriteAsync.cs +++ b/src/System.IO.FileSystem/tests/FileStream/WriteAsync.cs @@ -388,35 +388,30 @@ namespace System.IO.Tests string writeFileName = GetTestFilePath(); do { - // Create a new token that expires between 100-1000ms - CancellationTokenSource tokenSource = new CancellationTokenSource(); - tokenSource.CancelAfter(rand.Next(100, 1000)); + + int totalBytesWritten = 0; using (var stream = new FileStream(writeFileName, FileMode.Create, FileAccess.Write)) { do { - try + // 20%: random write size + int bytesToWrite = (rand.NextDouble() < 0.2 ? rand.Next(16, MaximumWriteSize) : NormalWriteSize); + + if (rand.NextDouble() < 0.1) { - // 20%: random write size - int bytesToWrite = (rand.NextDouble() < 0.2 ? rand.Next(16, MaximumWriteSize) : NormalWriteSize); - - if (rand.NextDouble() < 0.1) - { - // 10%: Sync write - stream.Write(dataToWrite, 0, bytesToWrite); - } - else - { - // 90%: Async write - await WriteAsync(stream, dataToWrite, 0, bytesToWrite, tokenSource.Token); - } + // 10%: Sync write + stream.Write(dataToWrite, 0, bytesToWrite); } - catch (TaskCanceledException) + else { - Assert.True(tokenSource.Token.IsCancellationRequested, "Received cancellation exception before token expired"); + // 90%: Async write + await WriteAsync(stream, dataToWrite, 0, bytesToWrite); } - } while (!tokenSource.Token.IsCancellationRequested); + + totalBytesWritten += bytesToWrite; + // Cap written bytes at 10 million to avoid writing too much to disk + } while (totalBytesWritten < 10_000_000); } } while (DateTime.UtcNow - testStartTime <= testRunTime); } diff --git a/src/System.IO.FileSystem/tests/FileStream/ctor_str_fm.cs b/src/System.IO.FileSystem/tests/FileStream/ctor_str_fm.cs index 2f9655ac5e..45791fe767 100644 --- a/src/System.IO.FileSystem/tests/FileStream/ctor_str_fm.cs +++ b/src/System.IO.FileSystem/tests/FileStream/ctor_str_fm.cs @@ -51,17 +51,39 @@ namespace System.IO.Tests Assert.Throws<DirectoryNotFoundException>(() => CreateFileStream(path, FileMode.Open)); } - [Fact] - public void FileModeCreate() + + public static TheoryData<string> StreamSpecifiers { - using (CreateFileStream(GetTestFilePath(), FileMode.Create)) - { } + get + { + TheoryData<string> data = new TheoryData<string>(); + data.Add(""); + + if (PlatformDetection.IsWindows && PlatformDetection.IsNetCore) + { + data.Add("::$DATA"); // Same as default stream (e.g. main file) + data.Add(":bar"); // $DATA isn't necessary + data.Add(":bar:$DATA"); // $DATA can be explicitly specified + } + + return data; + } } - [Fact] - public void FileModeCreateExisting() + [Theory, MemberData(nameof(StreamSpecifiers))] + public void FileModeCreate(string streamSpecifier) { - string fileName = GetTestFilePath(); + string fileName = GetTestFilePath() + streamSpecifier; + using (CreateFileStream(fileName, FileMode.Create)) + { + Assert.True(File.Exists(fileName)); + } + } + + [Theory, MemberData(nameof(StreamSpecifiers))] + public void FileModeCreateExisting(string streamSpecifier) + { + string fileName = GetTestFilePath() + streamSpecifier; using (FileStream fs = CreateFileStream(fileName, FileMode.Create)) { fs.WriteByte(0); @@ -77,17 +99,20 @@ namespace System.IO.Tests } } - [Fact] - public void FileModeCreateNew() + [Theory, MemberData(nameof(StreamSpecifiers))] + public void FileModeCreateNew(string streamSpecifier) { - using (CreateFileStream(GetTestFilePath(), FileMode.CreateNew)) - { } + string fileName = GetTestFilePath() + streamSpecifier; + using (CreateFileStream(fileName, FileMode.CreateNew)) + { + Assert.True(File.Exists(fileName)); + } } - [Fact] - public void FileModeCreateNewExistingThrows() + [Theory, MemberData(nameof(StreamSpecifiers))] + public void FileModeCreateNewExistingThrows(string streamSpecifier) { - string fileName = GetTestFilePath(); + string fileName = GetTestFilePath() + streamSpecifier; using (FileStream fs = CreateFileStream(fileName, FileMode.CreateNew)) { fs.WriteByte(0); @@ -98,18 +123,18 @@ namespace System.IO.Tests Assert.Throws<IOException>(() => CreateFileStream(fileName, FileMode.CreateNew)); } - [Fact] - public void FileModeOpenThrows() + [Theory, MemberData(nameof(StreamSpecifiers))] + public void FileModeOpenThrows(string streamSpecifier) { - string fileName = GetTestFilePath(); + string fileName = GetTestFilePath() + streamSpecifier; FileNotFoundException fnfe = Assert.Throws<FileNotFoundException>(() => CreateFileStream(fileName, FileMode.Open)); Assert.Equal(fileName, fnfe.FileName); } - [Fact] - public void FileModeOpenExisting() + [Theory, MemberData(nameof(StreamSpecifiers))] + public void FileModeOpenExisting(string streamSpecifier) { - string fileName = GetTestFilePath(); + string fileName = GetTestFilePath() + streamSpecifier; using (FileStream fs = CreateFileStream(fileName, FileMode.Create)) { fs.WriteByte(0); @@ -125,17 +150,20 @@ namespace System.IO.Tests } } - [Fact] - public void FileModeOpenOrCreate() + [Theory, MemberData(nameof(StreamSpecifiers))] + public void FileModeOpenOrCreate(string streamSpecifier) { - using (CreateFileStream(GetTestFilePath(), FileMode.OpenOrCreate)) - {} + string fileName = GetTestFilePath() + streamSpecifier; + using (CreateFileStream(fileName, FileMode.OpenOrCreate)) + { + Assert.True(File.Exists(fileName)); + } } - [Fact] - public void FileModeOpenOrCreateExisting() + [Theory, MemberData(nameof(StreamSpecifiers))] + public void FileModeOpenOrCreateExisting(string streamSpecifier) { - string fileName = GetTestFilePath(); + string fileName = GetTestFilePath() + streamSpecifier; using (FileStream fs = CreateFileStream(fileName, FileMode.Create)) { fs.WriteByte(0); @@ -151,18 +179,18 @@ namespace System.IO.Tests } } - [Fact] - public void FileModeTruncateThrows() + [Theory, MemberData(nameof(StreamSpecifiers))] + public void FileModeTruncateThrows(string streamSpecifier) { - string fileName = GetTestFilePath(); + string fileName = GetTestFilePath() + streamSpecifier; FileNotFoundException fnfe = Assert.Throws<FileNotFoundException>(() => CreateFileStream(fileName, FileMode.Truncate)); Assert.Equal(fileName, fnfe.FileName); } - [Fact] - public void FileModeTruncateExisting() + [Theory, MemberData(nameof(StreamSpecifiers))] + public void FileModeTruncateExisting(string streamSpecifier) { - string fileName = GetTestFilePath(); + string fileName = GetTestFilePath() + streamSpecifier; using (FileStream fs = CreateFileStream(fileName, FileMode.Create)) { fs.WriteByte(0); @@ -178,20 +206,20 @@ namespace System.IO.Tests } } - [Fact] - public virtual void FileModeAppend() + [Theory, MemberData(nameof(StreamSpecifiers))] + public virtual void FileModeAppend(string streamSpecifier) { - using (FileStream fs = CreateFileStream(GetTestFilePath(), FileMode.Append)) + using (FileStream fs = CreateFileStream(GetTestFilePath() + streamSpecifier, FileMode.Append)) { Assert.Equal(false, fs.CanRead); Assert.Equal(true, fs.CanWrite); } } - [Fact] - public virtual void FileModeAppendExisting() + [Theory, MemberData(nameof(StreamSpecifiers))] + public virtual void FileModeAppendExisting(string streamSpecifier) { - string fileName = GetTestFilePath(); + string fileName = GetTestFilePath() + streamSpecifier; using (FileStream fs = CreateFileStream(fileName, FileMode.Create)) { fs.WriteByte(0); diff --git a/src/System.IO.FileSystem/tests/FileSystemTest.cs b/src/System.IO.FileSystem/tests/FileSystemTest.cs index 4e593f27b8..6f020a00f6 100644 --- a/src/System.IO.FileSystem/tests/FileSystemTest.cs +++ b/src/System.IO.FileSystem/tests/FileSystemTest.cs @@ -28,7 +28,7 @@ namespace System.IO.Tests public static TheoryData WhiteSpace = IOInputs.GetWhiteSpace().ToTheoryData(); public static TheoryData UncPathsWithoutShareName = IOInputs.GetUncPathsWithoutShareName().ToTheoryData(); public static TheoryData PathsWithReservedDeviceNames = IOInputs.GetPathsWithReservedDeviceNames().ToTheoryData(); - public static TheoryData PathsWithAlternativeDataStreams = IOInputs.GetPathsWithAlternativeDataStreams().ToTheoryData(); + public static TheoryData PathsWithColons = IOInputs.GetPathsWithColons().ToTheoryData(); public static TheoryData PathsWithComponentLongerThanMaxComponent = IOInputs.GetPathsWithComponentLongerThanMaxComponent().ToTheoryData(); public static TheoryData ControlWhiteSpace = IOInputs.GetControlWhiteSpace().ToTheoryData(); public static TheoryData NonControlWhiteSpace = IOInputs.GetNonControlWhiteSpace().ToTheoryData(); diff --git a/src/System.IO.FileSystem/tests/Performance/Perf.Directory.cs b/src/System.IO.FileSystem/tests/Performance/Perf.Directory.cs index 508275dc12..7a70751425 100644 --- a/src/System.IO.FileSystem/tests/Performance/Perf.Directory.cs +++ b/src/System.IO.FileSystem/tests/Performance/Perf.Directory.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Text; using Microsoft.Xunit.Performance; +using Xunit; namespace System.IO.Tests { @@ -55,5 +57,89 @@ namespace System.IO.Tests // Teardown Directory.Delete(testFile); } + + public string GetTestDeepFilePath(int depth) + { + string directory = Path.DirectorySeparatorChar + "a"; + StringBuilder sb = new StringBuilder(depth * 2); + for (int i = 0; i < depth; i++) + { + sb.Append(directory); + } + + return sb.ToString(); + } + + public static TheoryData<int, int> RecursiveDepthData + { + get + { + var data = new TheoryData<int, int>(); + data.Add(10, 100); + + // Length of the path can be 260 characters on netfx. + if (AreAllLongPathsAvailable) + { + data.Add(100, 10); + // Most Unix distributions have a maximum path length of 1024 characters (1024 UTF-8 bytes). + if (PlatformDetection.IsWindows) + data.Add(1000, 1); + } + + return data; + } + } + + [Benchmark] + [MemberData(nameof(RecursiveDepthData))] + [OuterLoop("Takes a lot of time to finish")] + public void RecursiveCreateDirectoryTest(int depth, int times) + { + // Setup + string rootDirectory = GetTestFilePath(); + string path = GetTestDeepFilePath(depth); + + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < times; i++) + { + Directory.CreateDirectory(rootDirectory + Path.DirectorySeparatorChar + i + path); + } + } + // TearDown For each iteration + Directory.Delete(rootDirectory, recursive: true); + } + } + + [Benchmark] + [MemberData(nameof(RecursiveDepthData))] + [OuterLoop("Takes a lot of time to finish")] + public void RecursiveDeleteDirectoryTest(int depth, int times) + { + // Setup + string rootDirectory = GetTestFilePath(); + string path = GetTestDeepFilePath(depth); + + foreach (var iteration in Benchmark.Iterations) + { + // Setup For each Iteration + for (int i = 0; i < times; i++) + { + Directory.CreateDirectory(rootDirectory + Path.DirectorySeparatorChar + i + path); + } + + using (iteration.StartMeasurement()) + { + for (int i = 0; i < times; i++) + { + Directory.Delete(rootDirectory + Path.DirectorySeparatorChar + i, recursive: true); + } + } + } + // TearDown + Directory.Delete(rootDirectory, recursive: true); + } } } diff --git a/src/System.IO.FileSystem/tests/Performance/System.IO.FileSystem.Performance.Tests.csproj b/src/System.IO.FileSystem/tests/Performance/System.IO.FileSystem.Performance.Tests.csproj index afcd6a3da8..4d758dda6f 100644 --- a/src/System.IO.FileSystem/tests/Performance/System.IO.FileSystem.Performance.Tests.csproj +++ b/src/System.IO.FileSystem/tests/Performance/System.IO.FileSystem.Performance.Tests.csproj @@ -37,5 +37,8 @@ <Name>PerfRunner</Name> </ProjectReference> </ItemGroup> + <ItemGroup> + <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> + </ItemGroup> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> </Project>
\ No newline at end of file diff --git a/src/System.IO.FileSystem/tests/PortedCommon/IOInputs.cs b/src/System.IO.FileSystem/tests/PortedCommon/IOInputs.cs index 10fbbd8138..68dfcd7fa1 100644 --- a/src/System.IO.FileSystem/tests/PortedCommon/IOInputs.cs +++ b/src/System.IO.FileSystem/tests/PortedCommon/IOInputs.cs @@ -152,7 +152,7 @@ internal static class IOInputs } } - public static IEnumerable<string> GetPathsWithAlternativeDataStreams() + public static IEnumerable<string> GetPathsWithColons() { yield return @"AA:"; yield return @"AAA:"; diff --git a/src/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj b/src/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj index fb99199ad1..7597996101 100644 --- a/src/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj +++ b/src/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj @@ -54,6 +54,19 @@ <Compile Include="File\ReadWriteAllBytesAsync.cs" /> <Compile Include="File\ReadWriteAllTextAsync.cs" /> <Compile Include="FileStream\ReadWriteSpan.netcoreapp.cs" /> + <Compile Include="Enumeration\ConstructionTests.netcoreapp.cs" /> + <Compile Include="Enumeration\SpecialDirectoryTests.netcoreapp.cs" /> + <Compile Include="Enumeration\SkipAttributeTests.netcoreapp.cs" /> + <Compile Include="Enumeration\FileSystemNameTests.netcoreapp.cs" /> + <Compile Include="Enumeration\MatchCasingTests.netcoreapp.cs" /> + <Compile Include="Enumeration\TrimmedPaths.netcoreapp.cs" /> + <Compile Include="Enumeration\ErrorHandlingTests.netcoreapp.cs" /> + <Compile Include="Enumeration\IncludePredicateTests.netcoreapp.cs" /> + <Compile Include="Enumeration\PatternTransformTests.netcoreapp.cs" /> + <Compile Include="Enumeration\RootTests.netcoreapp.cs" /> + <Compile Include="Enumeration\AttributeTests.netcoreapp.cs" /> + <Compile Include="Enumeration\MatchTypesTests.netcoreapp.cs" /> + <Compile Include="Enumeration\ExampleTests.netcoreapp.cs" /> </ItemGroup> <ItemGroup> <!-- Rewritten --> @@ -164,6 +177,9 @@ <Compile Include="FileInfo\AppendText.cs" /> <Compile Include="FileInfo\CopyTo.cs" /> <!-- Helpers --> + <Compile Include="$(CommonTestPath)\System\Buffers\NativeOwnedMemory.cs"> + <Link>Common\System\Buffers\NativeOwnedMemory.cs</Link> + </Compile> <Compile Include="$(CommonTestPath)\System\IO\TempFile.cs"> <Link>Common\System\IO\TempFile.cs</Link> </Compile> @@ -182,5 +198,8 @@ <ItemGroup> <EmbeddedResource Include="Resources\$(AssemblyName).rd.xml" /> </ItemGroup> + <ItemGroup> + <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> + </ItemGroup> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> </Project>
\ No newline at end of file diff --git a/src/System.IO.FileSystem/tests/TestData.cs b/src/System.IO.FileSystem/tests/TestData.cs index 3ca4af5160..69b4373bd9 100644 --- a/src/System.IO.FileSystem/tests/TestData.cs +++ b/src/System.IO.FileSystem/tests/TestData.cs @@ -57,25 +57,12 @@ internal static class TestData { get { - TheoryData<string> data = new TheoryData<string>(); - - // NOTE: That I/O treats "file"/http" specially and throws ArgumentException. - // Otherwise, it treats all other urls as alternative data streams - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) // alternate data streams, drive labels, etc. - { - data.Add("\0"); - data.Add("middle\0path"); - data.Add("trailing\0"); - data.Add(@"\\?\"); - data.Add(@"\\?\UNC\"); - data.Add(@"\\?\UNC\LOCALHOST"); - } - else + TheoryData<string> data = new TheoryData<string> { - data.Add("\0"); - data.Add("middle\0path"); - data.Add("trailing\0"); - } + "\0", + "middle\0path", + "trailing\0" + }; foreach (char c in s_invalidFileNameChars) { |