Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 8dbc04c

Browse files
committed
Fill out FileSystemInfo state when enumerating (#20716)
* Fill out FileSystemInfo state when enumerating On Windows FileSystemInfos are filled in with state as we have all of the state from FindFirstFileEx. * Add more tests Refactor Time tests and add cases for refresh state. Fix positive attribute assertion. * Missed one of the cases * Tweaking the time tolerance didn't succeed. Putting it back to -3 seconds. * Refactor tests Gets coverage consistent Fixes test bug in DirectoryInfo where it was creating files for invalid attributes * Address feedback
1 parent dbd63bb commit 8dbc04c

18 files changed

+595
-723
lines changed

src/System.IO.FileSystem/src/System/IO/UnixFileSystem.cs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -448,14 +448,27 @@ public override IEnumerable<FileSystemInfo> EnumerateFileSystemInfos(string full
448448
{
449449
case SearchTarget.Files:
450450
return new FileSystemEnumerable<FileInfo>(fullPath, searchPattern, searchOption, searchTarget, (path, isDir) =>
451-
new FileInfo(path, null));
451+
{
452+
var info = new FileInfo(path, null);
453+
info.Refresh();
454+
return info;
455+
});
452456
case SearchTarget.Directories:
453457
return new FileSystemEnumerable<DirectoryInfo>(fullPath, searchPattern, searchOption, searchTarget, (path, isDir) =>
454-
new DirectoryInfo(path, null));
458+
{
459+
var info = new DirectoryInfo(path, null);
460+
info.Refresh();
461+
return info;
462+
});
455463
default:
456-
return new FileSystemEnumerable<FileSystemInfo>(fullPath, searchPattern, searchOption, searchTarget, (path, isDir) => isDir ?
457-
(FileSystemInfo)new DirectoryInfo(path, null) :
458-
(FileSystemInfo)new FileInfo(path, null));
464+
return new FileSystemEnumerable<FileSystemInfo>(fullPath, searchPattern, searchOption, searchTarget, (path, isDir) =>
465+
{
466+
var info = isDir ?
467+
(FileSystemInfo)new DirectoryInfo(path, null) :
468+
(FileSystemInfo)new FileInfo(path, null);
469+
info.Refresh();
470+
return info;
471+
});
459472
}
460473
}
461474

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Xunit;
6+
7+
namespace System.IO.Tests
8+
{
9+
// Tests that are valid for File, FileInfo, and DirectoryInfo
10+
public abstract class AllGetSetAttributes : BaseGetSetAttributes
11+
{
12+
[Fact]
13+
public void NullParameters()
14+
{
15+
Assert.Throws<ArgumentNullException>(() => GetAttributes(null));
16+
Assert.Throws<ArgumentNullException>(() => SetAttributes(null, FileAttributes.Normal));
17+
}
18+
19+
[Fact]
20+
public void InvalidParameters()
21+
{
22+
Assert.Throws<ArgumentException>(() => GetAttributes(string.Empty));
23+
Assert.Throws<ArgumentException>(() => SetAttributes(string.Empty, FileAttributes.Normal));
24+
}
25+
26+
[Theory, MemberData(nameof(TrailingCharacters))]
27+
public void SetAttributes_MissingFile(char trailingChar)
28+
{
29+
Assert.Throws<FileNotFoundException>(() => SetAttributes(GetTestFilePath() + trailingChar, FileAttributes.ReadOnly));
30+
}
31+
32+
[Theory, MemberData(nameof(TrailingCharacters))]
33+
public void SetAttributes_MissingDirectory(char trailingChar)
34+
{
35+
Assert.Throws<DirectoryNotFoundException>(() => SetAttributes(Path.Combine(GetTestFilePath(), "file" + trailingChar), FileAttributes.ReadOnly));
36+
}
37+
38+
39+
[ConditionalFact(nameof(CanCreateSymbolicLinks))]
40+
public void SymLinksAreReparsePoints()
41+
{
42+
string path = CreateItem();
43+
string linkPath = GetTestFilePath();
44+
45+
Assert.True(MountHelper.CreateSymbolicLink(linkPath, path, isDirectory: IsDirectory));
46+
47+
Assert.NotEqual(FileAttributes.ReparsePoint, FileAttributes.ReparsePoint & GetAttributes(path));
48+
Assert.Equal(FileAttributes.ReparsePoint, FileAttributes.ReparsePoint & GetAttributes(linkPath));
49+
}
50+
51+
[ConditionalFact(nameof(CanCreateSymbolicLinks))]
52+
public void SymLinksReflectSymLinkAttributes()
53+
{
54+
string path = CreateItem();
55+
string linkPath = GetTestFilePath();
56+
57+
Assert.True(MountHelper.CreateSymbolicLink(linkPath, path, isDirectory: IsDirectory));
58+
59+
SetAttributes(path, FileAttributes.ReadOnly);
60+
try
61+
{
62+
Assert.Equal(FileAttributes.ReadOnly, FileAttributes.ReadOnly & GetAttributes(path));
63+
Assert.NotEqual(FileAttributes.ReadOnly, FileAttributes.ReadOnly & GetAttributes(linkPath));
64+
}
65+
finally
66+
{
67+
SetAttributes(path, GetAttributes(path) & ~FileAttributes.ReadOnly);
68+
}
69+
}
70+
}
71+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Runtime.CompilerServices;
6+
7+
namespace System.IO.Tests
8+
{
9+
public abstract class BaseGetSetAttributes : FileSystemTest
10+
{
11+
protected abstract FileAttributes GetAttributes(string path);
12+
protected abstract void SetAttributes(string path, FileAttributes attributes);
13+
14+
/// <summary>
15+
/// Create an appropriate filesystem object at the given path.
16+
/// </summary>
17+
protected virtual string CreateItem(string path = null, [CallerMemberName] string memberName = null, [CallerLineNumber] int lineNumber = 0)
18+
{
19+
path = path ?? GetTestFilePath(null, memberName, lineNumber);
20+
File.Create(path).Dispose();
21+
return path;
22+
}
23+
24+
protected virtual void DeleteItem(string path) => File.Delete(path);
25+
26+
protected virtual bool IsDirectory => false;
27+
}
28+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections.Generic;
6+
using Xunit;
7+
8+
namespace System.IO.Tests
9+
{
10+
public abstract class BaseGetSetTimes<T> : FileSystemTest
11+
{
12+
public delegate void SetTime(T item, DateTime time);
13+
public delegate DateTime GetTime(T item);
14+
15+
public abstract T GetExistingItem();
16+
public abstract T GetMissingItem();
17+
18+
public abstract IEnumerable<TimeFunction> TimeFunctions(bool requiresRoundtripping = false);
19+
20+
public class TimeFunction : Tuple<SetTime, GetTime, DateTimeKind>
21+
{
22+
public TimeFunction(SetTime setter, GetTime getter, DateTimeKind kind)
23+
: base(item1: setter, item2: getter, item3: kind)
24+
{
25+
}
26+
27+
public static TimeFunction Create(SetTime setter, GetTime getter, DateTimeKind kind)
28+
=> new TimeFunction(setter, getter, kind);
29+
30+
public SetTime Setter => Item1;
31+
public GetTime Getter => Item2;
32+
public DateTimeKind Kind => Item3;
33+
}
34+
35+
[Fact]
36+
public void SettingUpdatesProperties()
37+
{
38+
T item = GetExistingItem();
39+
40+
Assert.All(TimeFunctions(requiresRoundtripping: true), (function) =>
41+
{
42+
DateTime dt = new DateTime(2014, 12, 1, 12, 0, 0, function.Kind);
43+
function.Setter(item, dt);
44+
DateTime result = function.Getter(item);
45+
Assert.Equal(dt, result);
46+
Assert.Equal(dt.ToLocalTime(), result.ToLocalTime());
47+
48+
// File and Directory UTC APIs treat a DateTimeKind.Unspecified as UTC whereas
49+
// ToUniversalTime treats it as local.
50+
if (function.Kind == DateTimeKind.Unspecified)
51+
{
52+
Assert.Equal(dt, result.ToUniversalTime());
53+
}
54+
else
55+
{
56+
Assert.Equal(dt.ToUniversalTime(), result.ToUniversalTime());
57+
}
58+
});
59+
}
60+
61+
[Fact]
62+
public void CanGetAllTimesAfterCreation()
63+
{
64+
DateTime beforeTime = DateTime.UtcNow.AddSeconds(-3);
65+
T item = GetExistingItem();
66+
DateTime afterTime = DateTime.UtcNow.AddSeconds(3);
67+
ValidateSetTimes(item, beforeTime, afterTime);
68+
}
69+
70+
protected void ValidateSetTimes(T item, DateTime beforeTime, DateTime afterTime)
71+
{
72+
Assert.All(TimeFunctions(), (function) =>
73+
{
74+
// We want to test all possible DateTimeKind conversions to ensure they function as expected
75+
if (function.Kind == DateTimeKind.Local)
76+
Assert.InRange(function.Getter(item).Ticks, beforeTime.ToLocalTime().Ticks, afterTime.ToLocalTime().Ticks);
77+
else
78+
Assert.InRange(function.Getter(item).Ticks, beforeTime.Ticks, afterTime.Ticks);
79+
Assert.InRange(function.Getter(item).ToLocalTime().Ticks, beforeTime.ToLocalTime().Ticks, afterTime.ToLocalTime().Ticks);
80+
Assert.InRange(function.Getter(item).ToUniversalTime().Ticks, beforeTime.Ticks, afterTime.Ticks);
81+
});
82+
}
83+
84+
public void DoesntExist_ReturnsDefaultValues()
85+
{
86+
T item = GetMissingItem();
87+
88+
Assert.All(TimeFunctions(), (function) =>
89+
{
90+
Assert.Equal(
91+
function.Kind == DateTimeKind.Local
92+
? DateTime.FromFileTime(0).Ticks
93+
: DateTime.FromFileTimeUtc(0).Ticks,
94+
function.Getter(item).Ticks);
95+
});
96+
}
97+
}
98+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Xunit;
6+
7+
namespace System.IO.Tests
8+
{
9+
// Tests that are valid for File and FileInfo
10+
public abstract class FileGetSetAttributes : BaseGetSetAttributes
11+
{
12+
[Theory]
13+
[InlineData(FileAttributes.ReadOnly)]
14+
[InlineData(FileAttributes.Normal)]
15+
[PlatformSpecific(TestPlatforms.AnyUnix)] // Unix valid file attributes
16+
public void UnixAttributeSetting(FileAttributes attr)
17+
{
18+
string path = CreateItem();
19+
SetAttributes(path, attr);
20+
Assert.Equal(attr, GetAttributes(path));
21+
SetAttributes(path, 0);
22+
}
23+
24+
[Theory]
25+
[InlineData(FileAttributes.ReadOnly)]
26+
[InlineData(FileAttributes.Hidden)]
27+
[InlineData(FileAttributes.System)]
28+
[InlineData(FileAttributes.Archive)]
29+
[InlineData(FileAttributes.Normal)]
30+
[InlineData(FileAttributes.Temporary)]
31+
[InlineData(FileAttributes.ReadOnly | FileAttributes.Hidden)]
32+
[PlatformSpecific(TestPlatforms.Windows)] // Valid Windows file attribute
33+
public void WindowsAttributeSetting(FileAttributes attr)
34+
{
35+
string path = CreateItem();
36+
SetAttributes(path, attr);
37+
Assert.Equal(attr, GetAttributes(path));
38+
SetAttributes(path, 0);
39+
}
40+
41+
[Theory]
42+
[InlineData(FileAttributes.Temporary)]
43+
[InlineData(FileAttributes.Encrypted)]
44+
[InlineData(FileAttributes.SparseFile)]
45+
[InlineData(FileAttributes.ReparsePoint)]
46+
[InlineData(FileAttributes.Compressed)]
47+
[PlatformSpecific(TestPlatforms.AnyUnix)] // Unix invalid file attributes
48+
public void UnixInvalidAttributes(FileAttributes attr)
49+
{
50+
string path = CreateItem();
51+
SetAttributes(path, attr);
52+
Assert.Equal(FileAttributes.Normal, GetAttributes(path));
53+
}
54+
55+
[Theory]
56+
[InlineData(FileAttributes.Normal)]
57+
[InlineData(FileAttributes.Encrypted)]
58+
[InlineData(FileAttributes.SparseFile)]
59+
[InlineData(FileAttributes.ReparsePoint)]
60+
[InlineData(FileAttributes.Compressed)]
61+
[PlatformSpecific(TestPlatforms.Windows)] // Invalid Windows file attributes
62+
public void WindowsInvalidAttributes(FileAttributes attr)
63+
{
64+
string path = CreateItem();
65+
SetAttributes(path, attr);
66+
Assert.Equal(FileAttributes.Normal, GetAttributes(path));
67+
}
68+
}
69+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Xunit;
6+
using System.Linq;
7+
8+
namespace System.IO.Tests
9+
{
10+
// Tests that are valid for FileInfo and DirectoryInfo
11+
public abstract class InfoGetSetAttributes<T> : AllGetSetAttributes where T : FileSystemInfo
12+
{
13+
protected abstract T CreateInfo(string path);
14+
15+
// In NetFX we ignore "not found" errors, which leaves the attributes
16+
// state as invalid (0xFFFFFFFF), which makes all flags true.
17+
18+
[Theory, MemberData(nameof(TrailingCharacters))]
19+
public void GetAttributes_MissingFile(char trailingChar)
20+
{
21+
Assert.Equal((FileAttributes)(-1), GetAttributes(GetTestFilePath() + trailingChar));
22+
}
23+
24+
[Theory, MemberData(nameof(TrailingCharacters))]
25+
public void GetAttributes_MissingDirectory(char trailingChar)
26+
{
27+
Assert.Equal((FileAttributes)(-1), GetAttributes(Path.Combine(GetTestFilePath(), "file" + trailingChar)));
28+
}
29+
30+
[Theory, MemberData(nameof(TrailingCharacters))]
31+
public void GetAttributes_CreateAfter(char trailingChar)
32+
{
33+
string path = GetTestFilePath();
34+
T info = CreateInfo(trailingChar == 'a' ? path : path + trailingChar);
35+
CreateItem(path);
36+
37+
// The actual value will vary depending on the OS and what is running.
38+
// Archive, NotContentIndexed, etc. might be set.
39+
Assert.NotEqual((FileAttributes)(-1), info.Attributes);
40+
}
41+
42+
[Theory, MemberData(nameof(TrailingCharacters))]
43+
public void GetAttributes_DeleteAfter(char trailingChar)
44+
{
45+
string path = CreateItem();
46+
T info = CreateInfo(trailingChar == 'a' ? path : path + trailingChar);
47+
DeleteItem(path);
48+
Assert.Equal((FileAttributes)(-1), info.Attributes);
49+
}
50+
51+
public void GetAttributes_DeleteAfterEnumerate()
52+
{
53+
// When enumerating we populate the state as we already have it.
54+
string path = CreateItem();
55+
FileSystemInfo info = new DirectoryInfo(TestDirectory).EnumerateFileSystemInfos().First();
56+
DeleteItem(path);
57+
58+
// The actual value will vary depending on the OS and what is running.
59+
// Archive, NotContentIndexed, etc. might be set.
60+
Assert.NotEqual((FileAttributes)(-1), info.Attributes);
61+
info.Refresh();
62+
Assert.Equal((FileAttributes)(-1), info.Attributes);
63+
}
64+
}
65+
}

0 commit comments

Comments
 (0)