From 35379b2c8a2cb6c8aedab2cf99e92377c8d531e0 Mon Sep 17 00:00:00 2001 From: Mihai Codoban Date: Fri, 28 Aug 2020 14:49:39 -0700 Subject: [PATCH 1/2] Expose MSBuildFileSystemBase public api Deprecate IFileSystem to follow MSBuildFileSystemBase. Ideally we should remove IFileSystem but doing so breaks some old nuget.exe version. --- ref/Microsoft.Build/net/Microsoft.Build.cs | 19 +++++ .../netstandard/Microsoft.Build.cs | 19 +++++ .../FileSystem/MSBuildFileSystemAdapter.cs | 54 +++++++++++++ src/Build/FileSystem/MSBuildFileSystemBase.cs | 79 +++++++++++++++++++ src/Build/Microsoft.Build.csproj | 2 + .../FileSystem/MSBuildTaskHostFileSystem.cs | 30 +++++++ .../FileSystem/CachingFileSystemWrapper.cs | 31 ++++++++ src/Shared/FileSystem/IFileSystem.cs | 45 ++++++----- .../FileSystem/MSBuildOnWindowsFileSystem.cs | 47 ++++++++--- src/Shared/FileSystem/ManagedFileSystem.cs | 55 +++++++++---- src/Shared/FileSystem/WindowsFileSystem.cs | 45 ++++++----- src/Shared/UnitTests/FileMatcher_Tests.cs | 12 +++ 12 files changed, 372 insertions(+), 66 deletions(-) create mode 100644 src/Build/FileSystem/MSBuildFileSystemAdapter.cs create mode 100644 src/Build/FileSystem/MSBuildFileSystemBase.cs diff --git a/ref/Microsoft.Build/net/Microsoft.Build.cs b/ref/Microsoft.Build/net/Microsoft.Build.cs index 835cbaf394f..01ff0723528 100644 --- a/ref/Microsoft.Build/net/Microsoft.Build.cs +++ b/ref/Microsoft.Build/net/Microsoft.Build.cs @@ -1402,6 +1402,25 @@ public enum TargetResultCode : byte Success = (byte)1, } } +namespace Microsoft.Build.FileSystem +{ + public abstract partial class MSBuildFileSystemBase + { + protected MSBuildFileSystemBase() { } + public abstract bool DirectoryExists(string path); + public abstract System.Collections.Generic.IEnumerable EnumerateDirectories(string path, string searchPattern="*", System.IO.SearchOption searchOption=(System.IO.SearchOption)(0)); + public abstract System.Collections.Generic.IEnumerable EnumerateFiles(string path, string searchPattern="*", System.IO.SearchOption searchOption=(System.IO.SearchOption)(0)); + public abstract System.Collections.Generic.IEnumerable EnumerateFileSystemEntries(string path, string searchPattern="*", System.IO.SearchOption searchOption=(System.IO.SearchOption)(0)); + public abstract bool FileExists(string path); + public abstract bool FileOrDirectoryExists(string path); + public abstract System.IO.FileAttributes GetAttributes(string path); + public abstract System.IO.Stream GetFileStream(string path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share); + public abstract System.DateTime GetLastWriteTimeUtc(string path); + public abstract System.IO.TextReader ReadFile(string path); + public abstract byte[] ReadFileAllBytes(string path); + public abstract string ReadFileAllText(string path); + } +} namespace Microsoft.Build.Globbing { public partial class CompositeGlob : Microsoft.Build.Globbing.IMSBuildGlob diff --git a/ref/Microsoft.Build/netstandard/Microsoft.Build.cs b/ref/Microsoft.Build/netstandard/Microsoft.Build.cs index f659dab2bac..4d2c96c734e 100644 --- a/ref/Microsoft.Build/netstandard/Microsoft.Build.cs +++ b/ref/Microsoft.Build/netstandard/Microsoft.Build.cs @@ -1396,6 +1396,25 @@ public enum TargetResultCode : byte Success = (byte)1, } } +namespace Microsoft.Build.FileSystem +{ + public abstract partial class MSBuildFileSystemBase + { + protected MSBuildFileSystemBase() { } + public abstract bool DirectoryExists(string path); + public abstract System.Collections.Generic.IEnumerable EnumerateDirectories(string path, string searchPattern="*", System.IO.SearchOption searchOption=(System.IO.SearchOption)(0)); + public abstract System.Collections.Generic.IEnumerable EnumerateFiles(string path, string searchPattern="*", System.IO.SearchOption searchOption=(System.IO.SearchOption)(0)); + public abstract System.Collections.Generic.IEnumerable EnumerateFileSystemEntries(string path, string searchPattern="*", System.IO.SearchOption searchOption=(System.IO.SearchOption)(0)); + public abstract bool FileExists(string path); + public abstract bool FileOrDirectoryExists(string path); + public abstract System.IO.FileAttributes GetAttributes(string path); + public abstract System.IO.Stream GetFileStream(string path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share); + public abstract System.DateTime GetLastWriteTimeUtc(string path); + public abstract System.IO.TextReader ReadFile(string path); + public abstract byte[] ReadFileAllBytes(string path); + public abstract string ReadFileAllText(string path); + } +} namespace Microsoft.Build.Globbing { public partial class CompositeGlob : Microsoft.Build.Globbing.IMSBuildGlob diff --git a/src/Build/FileSystem/MSBuildFileSystemAdapter.cs b/src/Build/FileSystem/MSBuildFileSystemAdapter.cs new file mode 100644 index 00000000000..4c69284d955 --- /dev/null +++ b/src/Build/FileSystem/MSBuildFileSystemAdapter.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Build.Shared.FileSystem; + +namespace Microsoft.Build.FileSystem +{ + internal class MSBuildFileSystemAdapter : IFileSystem + { + private readonly MSBuildFileSystemBase _msbuildFileSystem; + public MSBuildFileSystemAdapter(MSBuildFileSystemBase msbuildFileSystem) + { + _msbuildFileSystem = msbuildFileSystem; + } + public TextReader ReadFile(string path) => _msbuildFileSystem.ReadFile(path); + + public Stream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share) => _msbuildFileSystem.GetFileStream(path, mode, access, share); + + public string ReadFileAllText(string path) => _msbuildFileSystem.ReadFileAllText(path); + + public byte[] ReadFileAllBytes(string path) => _msbuildFileSystem.ReadFileAllBytes(path); + + public IEnumerable EnumerateFiles(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly) + { + return _msbuildFileSystem.EnumerateFiles(path, searchPattern, searchOption); + } + + public IEnumerable EnumerateDirectories(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly) + { + return _msbuildFileSystem.EnumerateDirectories(path, searchPattern, searchOption); + } + + public IEnumerable EnumerateFileSystemEntries( + string path, + string searchPattern = "*", + SearchOption searchOption = SearchOption.TopDirectoryOnly) + { + return _msbuildFileSystem.EnumerateFileSystemEntries(path, searchPattern, searchOption); + } + + public FileAttributes GetAttributes(string path) => _msbuildFileSystem.GetAttributes(path); + + public DateTime GetLastWriteTimeUtc(string path) => _msbuildFileSystem.GetLastWriteTimeUtc(path); + + public bool DirectoryExists(string path) => _msbuildFileSystem.DirectoryExists(path); + + public bool FileExists(string path) => _msbuildFileSystem.FileExists(path); + + public bool DirectoryEntryExists(string path) => _msbuildFileSystem.FileOrDirectoryExists(path); + } +} diff --git a/src/Build/FileSystem/MSBuildFileSystemBase.cs b/src/Build/FileSystem/MSBuildFileSystemBase.cs new file mode 100644 index 00000000000..5383e717a9b --- /dev/null +++ b/src/Build/FileSystem/MSBuildFileSystemBase.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; + +namespace Microsoft.Build.FileSystem +{ + /// + /// Abstracts away some file system operations. + /// + /// Implementations: + /// - must be thread safe + /// - may cache some or all the calls. + /// + public abstract class MSBuildFileSystemBase + { + /// + /// Use this for var sr = new StreamReader(path) + /// + public abstract TextReader ReadFile(string path); + + /// + /// Use this for new FileStream(path, mode, access, share) + /// + public abstract Stream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share); + + /// + /// Use this for File.ReadAllText(path) + /// + public abstract string ReadFileAllText(string path); + + /// + /// Use this for File.ReadAllBytes(path) + /// + public abstract byte[] ReadFileAllBytes(string path); + + /// + /// Use this for Directory.EnumerateFiles(path, pattern, option) + /// + public abstract IEnumerable EnumerateFiles(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly); + + /// + /// Use this for Directory.EnumerateFolders(path, pattern, option) + /// + public abstract IEnumerable EnumerateDirectories(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly); + + /// + /// Use this for Directory.EnumerateFileSystemEntries(path, pattern, option) + /// + public abstract IEnumerable EnumerateFileSystemEntries(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly); + + /// + /// Use this for File.GetAttributes() + /// + public abstract FileAttributes GetAttributes(string path); + + /// + /// Use this for File.GetLastWriteTimeUtc(path) + /// + public abstract DateTime GetLastWriteTimeUtc(string path); + + /// + /// Use this for Directory.Exists(path) + /// + public abstract bool DirectoryExists(string path); + + /// + /// Use this for File.Exists(path) + /// + public abstract bool FileExists(string path); + + /// + /// Use this for File.Exists(path) || Directory.Exists(path) + /// + public abstract bool FileOrDirectoryExists(string path); + } +} diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 43a84039b2e..524046ab620 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -157,6 +157,7 @@ + @@ -263,6 +264,7 @@ + diff --git a/src/MSBuildTaskHost/FileSystem/MSBuildTaskHostFileSystem.cs b/src/MSBuildTaskHost/FileSystem/MSBuildTaskHostFileSystem.cs index 3b285db56ba..103061df36c 100644 --- a/src/MSBuildTaskHost/FileSystem/MSBuildTaskHostFileSystem.cs +++ b/src/MSBuildTaskHost/FileSystem/MSBuildTaskHostFileSystem.cs @@ -21,6 +21,16 @@ public bool DirectoryEntryExists(string path) return NativeMethodsShared.FileOrDirectoryExists(path); } + public FileAttributes GetAttributes(string path) + { + return File.GetAttributes(path); + } + + public DateTime GetLastWriteTimeUtc(string path) + { + return File.GetLastWriteTimeUtc(path); + } + public bool DirectoryExists(string path) { return NativeMethodsShared.DirectoryExists(path); @@ -31,6 +41,26 @@ public IEnumerable EnumerateDirectories(string path, string searchPatter return Directory.GetDirectories(path, searchPattern, searchOption); } + public TextReader ReadFile(string path) + { + return new StreamReader(path); + } + + public Stream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share) + { + return new FileStream(path, mode, access, share); + } + + public string ReadFileAllText(string path) + { + return File.ReadAllText(path); + } + + public byte[] ReadFileAllBytes(string path) + { + return File.ReadAllBytes(path); + } + public IEnumerable EnumerateFiles(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly) { return Directory.GetFiles(path, searchPattern, searchOption); diff --git a/src/Shared/FileSystem/CachingFileSystemWrapper.cs b/src/Shared/FileSystem/CachingFileSystemWrapper.cs index 26682f6968e..c3b3b141f20 100644 --- a/src/Shared/FileSystem/CachingFileSystemWrapper.cs +++ b/src/Shared/FileSystem/CachingFileSystemWrapper.cs @@ -12,6 +12,7 @@ internal class CachingFileSystemWrapper : IFileSystem { private readonly IFileSystem _fileSystem; private readonly ConcurrentDictionary _existenceCache = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _lastWriteTimeCache = new ConcurrentDictionary(); public CachingFileSystemWrapper(IFileSystem fileSystem) { @@ -23,6 +24,16 @@ public bool DirectoryEntryExists(string path) return CachedExistenceCheck(path, p => _fileSystem.DirectoryEntryExists(p)); } + public FileAttributes GetAttributes(string path) + { + return _fileSystem.GetAttributes(path); + } + + public DateTime GetLastWriteTimeUtc(string path) + { + return _lastWriteTimeCache.GetOrAdd(path, p =>_fileSystem.GetLastWriteTimeUtc(p)); + } + public bool DirectoryExists(string path) { return CachedExistenceCheck(path, p => _fileSystem.DirectoryExists(p)); @@ -38,6 +49,26 @@ public IEnumerable EnumerateDirectories(string path, string searchPatter return _fileSystem.EnumerateDirectories(path, searchPattern, searchOption); } + public TextReader ReadFile(string path) + { + return _fileSystem.ReadFile(path); + } + + public Stream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share) + { + return _fileSystem.GetFileStream(path, mode, access, share); + } + + public string ReadFileAllText(string path) + { + return _fileSystem.ReadFileAllText(path); + } + + public byte[] ReadFileAllBytes(string path) + { + return _fileSystem.ReadFileAllBytes(path); + } + public IEnumerable EnumerateFiles(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly) { return _fileSystem.EnumerateFiles(path, searchPattern, searchOption); diff --git a/src/Shared/FileSystem/IFileSystem.cs b/src/Shared/FileSystem/IFileSystem.cs index 946aae0b286..0ef03e74c65 100644 --- a/src/Shared/FileSystem/IFileSystem.cs +++ b/src/Shared/FileSystem/IFileSystem.cs @@ -1,44 +1,47 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; using System.IO; namespace Microsoft.Build.Shared.FileSystem { - /// - /// Abstracts away some file system operations - /// + + /* + * This is a clone of Microsoft.Build.FileSystem.MSBuildFileSystemBase. + * MSBuildFileSystemBase is the public, reference interface. Changes should be made to MSBuildFileSystemBase and cloned in IFileSystem. + * Any new code should depend on MSBuildFileSystemBase instead of IFileSystem, if possible. + * + * MSBuild uses IFileSystem internally and adapts MSBuildFileSystemBase instances received from the outside to IFileSystem. + * Ideally there should be only one, public interface. However, such an interface would need to be put into the + * Microsoft.Build.Framework assembly, but that assembly cannot take new types because it breaks some old version of Nuget.exe. + * IFileSystem cannot be deleted for the same reason. + */ internal interface IFileSystem { - /// - /// Returns an enumerable collection of file names that match a search pattern in a specified path, and optionally searches subdirectories. - /// + TextReader ReadFile(string path); + + Stream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share); + + string ReadFileAllText(string path); + + byte[] ReadFileAllBytes(string path); + IEnumerable EnumerateFiles(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly); - /// - /// Returns an enumerable collection of directory names that match a search pattern in a specified path, and optionally searches subdirectories. - /// IEnumerable EnumerateDirectories(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly); - /// - /// Returns an enumerable collection of file names and directory names that match a search pattern in a specified path, and optionally searches subdirectories. - /// IEnumerable EnumerateFileSystemEntries(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly); - /// - /// Determines whether the given path refers to an existing directory on disk. - /// + FileAttributes GetAttributes(string path); + + public DateTime GetLastWriteTimeUtc(string path); + bool DirectoryExists(string path); - /// - /// Determines whether the given path refers to an existing file on disk. - /// bool FileExists(string path); - /// - /// Determines whether the given path refers to an existing entry in the directory service. - /// bool DirectoryEntryExists(string path); } } diff --git a/src/Shared/FileSystem/MSBuildOnWindowsFileSystem.cs b/src/Shared/FileSystem/MSBuildOnWindowsFileSystem.cs index 6819117fbf4..de2658e6324 100644 --- a/src/Shared/FileSystem/MSBuildOnWindowsFileSystem.cs +++ b/src/Shared/FileSystem/MSBuildOnWindowsFileSystem.cs @@ -1,55 +1,80 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; using System.IO; +using System.Reflection; namespace Microsoft.Build.Shared.FileSystem { /// - /// Implementation of file system operations directly over the dot net managed layer + /// Implementation of file system operations on windows. Combination of native and managed implementations. + /// TODO Remove this class and replace with WindowsFileSystem. Test perf to ensure no regressions. /// - internal sealed class MSBuildOnWindowsFileSystem : IFileSystem + internal class MSBuildOnWindowsFileSystem : IFileSystem { private static readonly MSBuildOnWindowsFileSystem Instance = new MSBuildOnWindowsFileSystem(); - /// public static MSBuildOnWindowsFileSystem Singleton() => Instance; - private MSBuildOnWindowsFileSystem() - { } + protected MSBuildOnWindowsFileSystem() { } + + public TextReader ReadFile(string path) + { + return ManagedFileSystem.Singleton().ReadFile(path); + } + + public Stream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share) + { + return ManagedFileSystem.Singleton().GetFileStream(path, mode, access, share); + } + + public string ReadFileAllText(string path) + { + return ManagedFileSystem.Singleton().ReadFileAllText(path); + } + + public byte[] ReadFileAllBytes(string path) + { + return ManagedFileSystem.Singleton().ReadFileAllBytes(path); + } - /// public IEnumerable EnumerateFiles(string path, string searchPattern, SearchOption searchOption) { return ManagedFileSystem.Singleton().EnumerateFiles(path, searchPattern, searchOption); } - /// public IEnumerable EnumerateDirectories(string path, string searchPattern, SearchOption searchOption) { return ManagedFileSystem.Singleton().EnumerateDirectories(path, searchPattern, searchOption); } - /// public IEnumerable EnumerateFileSystemEntries(string path, string searchPattern, SearchOption searchOption) { return ManagedFileSystem.Singleton().EnumerateFileSystemEntries(path, searchPattern, searchOption); } - /// + public FileAttributes GetAttributes(string path) + { + return ManagedFileSystem.Singleton().GetAttributes(path); + } + + public DateTime GetLastWriteTimeUtc(string path) + { + return ManagedFileSystem.Singleton().GetLastWriteTimeUtc(path); + } + public bool DirectoryExists(string path) { return WindowsFileSystem.Singleton().DirectoryExists(path); } - /// public bool FileExists(string path) { return WindowsFileSystem.Singleton().FileExists(path); } - /// public bool DirectoryEntryExists(string path) { return WindowsFileSystem.Singleton().DirectoryEntryExists(path); diff --git a/src/Shared/FileSystem/ManagedFileSystem.cs b/src/Shared/FileSystem/ManagedFileSystem.cs index c809822b1df..201a62e7436 100644 --- a/src/Shared/FileSystem/ManagedFileSystem.cs +++ b/src/Shared/FileSystem/ManagedFileSystem.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; using System.IO; @@ -9,48 +10,70 @@ namespace Microsoft.Build.Shared.FileSystem /// /// Implementation of file system operations directly over the dot net managed layer /// - internal sealed class ManagedFileSystem : IFileSystem + internal class ManagedFileSystem : IFileSystem { private static readonly ManagedFileSystem Instance = new ManagedFileSystem(); - /// public static ManagedFileSystem Singleton() => ManagedFileSystem.Instance; - private ManagedFileSystem() - { } + protected ManagedFileSystem() { } - /// - public IEnumerable EnumerateFiles(string path, string searchPattern, SearchOption searchOption) + public TextReader ReadFile(string path) + { + return new StreamReader(path); + } + + public Stream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share) + { + return new FileStream(path, mode, access, share); + } + + public string ReadFileAllText(string path) + { + return File.ReadAllText(path); + } + + public byte[] ReadFileAllBytes(string path) + { + return File.ReadAllBytes(path); + } + + public virtual IEnumerable EnumerateFiles(string path, string searchPattern, SearchOption searchOption) { return Directory.EnumerateFiles(path, searchPattern, searchOption); } - /// - public IEnumerable EnumerateDirectories(string path, string searchPattern, SearchOption searchOption) + public virtual IEnumerable EnumerateDirectories(string path, string searchPattern, SearchOption searchOption) { return Directory.EnumerateDirectories(path, searchPattern, searchOption); } - /// - public IEnumerable EnumerateFileSystemEntries(string path, string searchPattern, SearchOption searchOption) + public virtual IEnumerable EnumerateFileSystemEntries(string path, string searchPattern, SearchOption searchOption) { return Directory.EnumerateFileSystemEntries(path, searchPattern, searchOption); } - /// - public bool DirectoryExists(string path) + public FileAttributes GetAttributes(string path) + { + return File.GetAttributes(path); + } + + public virtual DateTime GetLastWriteTimeUtc(string path) + { + return File.GetLastWriteTimeUtc(path); + } + + public virtual bool DirectoryExists(string path) { return Directory.Exists(path); } - /// - public bool FileExists(string path) + public virtual bool FileExists(string path) { return File.Exists(path); } - /// - public bool DirectoryEntryExists(string path) + public virtual bool DirectoryEntryExists(string path) { return FileExists(path) || DirectoryExists(path); } diff --git a/src/Shared/FileSystem/WindowsFileSystem.cs b/src/Shared/FileSystem/WindowsFileSystem.cs index 2b66c993281..22c578439c7 100644 --- a/src/Shared/FileSystem/WindowsFileSystem.cs +++ b/src/Shared/FileSystem/WindowsFileSystem.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -22,54 +23,62 @@ internal enum FileArtifactType : byte } /// - /// Windows-specific implementation of file system operations using Windows native invocations + /// Windows-specific implementation of file system operations using Windows native invocations. + /// TODO For potential extra perf gains, provide native implementations for all IFileSystem methods and stop inheriting from ManagedFileSystem /// - internal class WindowsFileSystem : IFileSystem + internal class WindowsFileSystem : ManagedFileSystem { private static readonly WindowsFileSystem Instance = new WindowsFileSystem(); - /// - public static WindowsFileSystem Singleton() => WindowsFileSystem.Instance; + public new static WindowsFileSystem Singleton() => WindowsFileSystem.Instance; - private WindowsFileSystem() - { } + private WindowsFileSystem(){ } - /// - public IEnumerable EnumerateFiles(string path, string searchPattern, SearchOption searchOption) + public override IEnumerable EnumerateFiles(string path, string searchPattern, SearchOption searchOption) { return EnumerateFileOrDirectories(path, FileArtifactType.File, searchPattern, searchOption); } - /// - public IEnumerable EnumerateDirectories(string path, string searchPattern, SearchOption searchOption) + public override IEnumerable EnumerateDirectories(string path, string searchPattern, SearchOption searchOption) { return EnumerateFileOrDirectories(path, FileArtifactType.Directory, searchPattern, searchOption); } - /// - public IEnumerable EnumerateFileSystemEntries(string path, string searchPattern, SearchOption searchOption) + public override IEnumerable EnumerateFileSystemEntries(string path, string searchPattern, SearchOption searchOption) { return EnumerateFileOrDirectories(path, FileArtifactType.FileOrDirectory, searchPattern, searchOption); } - /// - public bool DirectoryExists(string path) + public override bool DirectoryExists(string path) { return NativeMethodsShared.DirectoryExistsWindows(path); } - /// - public bool FileExists(string path) + public override bool FileExists(string path) { return NativeMethodsShared.FileExistsWindows(path); } - /// - public bool DirectoryEntryExists(string path) + public override bool DirectoryEntryExists(string path) { return NativeMethodsShared.FileOrDirectoryExistsWindows(path); } + public override DateTime GetLastWriteTimeUtc(string path) + { + var fileLastWriteTime = NativeMethodsShared.GetLastWriteFileUtcTime(path); + + if (fileLastWriteTime != DateTime.MinValue) + { + return fileLastWriteTime; + } + else + { + NativeMethodsShared.GetLastWriteDirectoryUtcTime(path, out var directoryLastWriteTime); + return directoryLastWriteTime; + } + } + private static IEnumerable EnumerateFileOrDirectories( string directoryPath, FileArtifactType fileArtifactType, diff --git a/src/Shared/UnitTests/FileMatcher_Tests.cs b/src/Shared/UnitTests/FileMatcher_Tests.cs index 293b4dbcec3..b0c2b567f4c 100644 --- a/src/Shared/UnitTests/FileMatcher_Tests.cs +++ b/src/Shared/UnitTests/FileMatcher_Tests.cs @@ -2545,6 +2545,14 @@ public FileSystemAdapter(MockFileSystem mockFileSystem) _mockFileSystem = mockFileSystem; } + public TextReader ReadFile(string path) => throw new NotImplementedException(); + + public Stream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share) => throw new NotImplementedException(); + + public string ReadFileAllText(string path) => throw new NotImplementedException(); + + public byte[] ReadFileAllBytes(string path) => throw new NotImplementedException(); + public IEnumerable EnumerateFiles(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly) { return FileSystems.Default.EnumerateFiles(path, searchPattern, searchOption); @@ -2560,6 +2568,10 @@ public IEnumerable EnumerateFileSystemEntries(string path, string search return FileSystems.Default.EnumerateFileSystemEntries(path, searchPattern, searchOption); } + public FileAttributes GetAttributes(string path) => throw new NotImplementedException(); + + public DateTime GetLastWriteTimeUtc(string path) => throw new NotImplementedException(); + public bool DirectoryExists(string path) { return _mockFileSystem.DirectoryExists(path); From fd3f6438623bd89e628a1647f32e1355e37881c4 Mon Sep 17 00:00:00 2001 From: Mihai Codoban Date: Fri, 28 Aug 2020 14:52:56 -0700 Subject: [PATCH 2/2] EvaluationContext can be passed in a user file system --- ref/Microsoft.Build/net/Microsoft.Build.cs | 1 + .../netstandard/Microsoft.Build.cs | 1 + .../ProjectEvaluationContext_Tests.cs | 45 ++++++ .../Evaluation/Context/EvaluationContext.cs | 45 +++++- src/Build/Resources/Strings.resx | 5 +- src/Build/Resources/xlf/Strings.cs.xlf | 5 + src/Build/Resources/xlf/Strings.de.xlf | 5 + src/Build/Resources/xlf/Strings.en.xlf | 5 + src/Build/Resources/xlf/Strings.es.xlf | 5 + src/Build/Resources/xlf/Strings.fr.xlf | 5 + src/Build/Resources/xlf/Strings.it.xlf | 5 + src/Build/Resources/xlf/Strings.ja.xlf | 5 + src/Build/Resources/xlf/Strings.ko.xlf | 5 + src/Build/Resources/xlf/Strings.pl.xlf | 5 + src/Build/Resources/xlf/Strings.pt-BR.xlf | 5 + src/Build/Resources/xlf/Strings.ru.xlf | 5 + src/Build/Resources/xlf/Strings.tr.xlf | 5 + src/Build/Resources/xlf/Strings.zh-Hans.xlf | 5 + src/Build/Resources/xlf/Strings.zh-Hant.xlf | 5 + src/Shared/UnitTests/ObjectModelHelpers.cs | 131 ++++++++++++++++++ 20 files changed, 292 insertions(+), 6 deletions(-) diff --git a/ref/Microsoft.Build/net/Microsoft.Build.cs b/ref/Microsoft.Build/net/Microsoft.Build.cs index 01ff0723528..8daf09c374a 100644 --- a/ref/Microsoft.Build/net/Microsoft.Build.cs +++ b/ref/Microsoft.Build/net/Microsoft.Build.cs @@ -869,6 +869,7 @@ public partial class EvaluationContext { internal EvaluationContext() { } public static Microsoft.Build.Evaluation.Context.EvaluationContext Create(Microsoft.Build.Evaluation.Context.EvaluationContext.SharingPolicy policy) { throw null; } + public static Microsoft.Build.Evaluation.Context.EvaluationContext Create(Microsoft.Build.Evaluation.Context.EvaluationContext.SharingPolicy policy, Microsoft.Build.FileSystem.MSBuildFileSystemBase fileSystem) { throw null; } public enum SharingPolicy { Isolated = 1, diff --git a/ref/Microsoft.Build/netstandard/Microsoft.Build.cs b/ref/Microsoft.Build/netstandard/Microsoft.Build.cs index 4d2c96c734e..23825be7aba 100644 --- a/ref/Microsoft.Build/netstandard/Microsoft.Build.cs +++ b/ref/Microsoft.Build/netstandard/Microsoft.Build.cs @@ -869,6 +869,7 @@ public partial class EvaluationContext { internal EvaluationContext() { } public static Microsoft.Build.Evaluation.Context.EvaluationContext Create(Microsoft.Build.Evaluation.Context.EvaluationContext.SharingPolicy policy) { throw null; } + public static Microsoft.Build.Evaluation.Context.EvaluationContext Create(Microsoft.Build.Evaluation.Context.EvaluationContext.SharingPolicy policy, Microsoft.Build.FileSystem.MSBuildFileSystemBase fileSystem) { throw null; } public enum SharingPolicy { Isolated = 1, diff --git a/src/Build.UnitTests/Definition/ProjectEvaluationContext_Tests.cs b/src/Build.UnitTests/Definition/ProjectEvaluationContext_Tests.cs index 9271584b5ff..b9329dad3ba 100644 --- a/src/Build.UnitTests/Definition/ProjectEvaluationContext_Tests.cs +++ b/src/Build.UnitTests/Definition/ProjectEvaluationContext_Tests.cs @@ -86,6 +86,51 @@ public void SharedContextShouldGetReusedWhereasIsolatedContextShouldNot(Evaluati } } + [Fact] + public void PassedInFileSystemShouldBeReusedInSharedContext() + { + var projectFiles = new[] + { + _env.CreateFile("1.proj", @" ".Cleanup()).Path, + _env.CreateFile("2.proj", @" ".Cleanup()).Path + }; + + var projectCollection = _env.CreateProjectCollection().Collection; + var fileSystem = new Helpers.LoggingFileSystem(); + var evaluationContext = EvaluationContext.Create(EvaluationContext.SharingPolicy.Shared, fileSystem); + + foreach (var projectFile in projectFiles) + { + Project.FromFile( + projectFile, + new ProjectOptions + { + ProjectCollection = projectCollection, + EvaluationContext = evaluationContext + } + ); + } + + fileSystem.ExistenceChecks.OrderBy(kvp => kvp.Key) + .ShouldBe( + new Dictionary + { + {Path.Combine(_env.DefaultTestDirectory.Path, "1.file"), 1}, + {Path.Combine(_env.DefaultTestDirectory.Path, "2.file"), 1} + }.OrderBy(kvp => kvp.Key)); + + fileSystem.DirectoryEntryExistsCalls.ShouldBe(2); + } + + [Fact] + public void IsolatedContextShouldNotSupportBeingPassedAFileSystem() + { + _env.DoNotLaunchDebugger(); + + var fileSystem = new Helpers.LoggingFileSystem(); + Should.Throw(() => EvaluationContext.Create(EvaluationContext.SharingPolicy.Isolated, fileSystem)); + } + [Theory] [InlineData(EvaluationContext.SharingPolicy.Shared)] [InlineData(EvaluationContext.SharingPolicy.Isolated)] diff --git a/src/Build/Evaluation/Context/EvaluationContext.cs b/src/Build/Evaluation/Context/EvaluationContext.cs index 07a36f6d034..633dd5404da 100644 --- a/src/Build/Evaluation/Context/EvaluationContext.cs +++ b/src/Build/Evaluation/Context/EvaluationContext.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Threading; using Microsoft.Build.BackEnd.SdkResolution; +using Microsoft.Build.FileSystem; using Microsoft.Build.Internal; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; @@ -17,13 +18,20 @@ namespace Microsoft.Build.Evaluation.Context /// evaluations). /// The caller should throw away the context when the environment changes (IO, environment variables, SDK resolution /// inputs, etc). - /// This class and it's closure needs to be thread safe since API users can do evaluations in parallel. + /// This class and its closure needs to be thread safe since API users can do evaluations in parallel. /// public class EvaluationContext { public enum SharingPolicy { + /// + /// Instructs the to reuse state between the different project evaluations that use it. + /// Shared, + + /// + /// Instructs the not to reuse state between the different project evaluations that use it. + /// Isolated } @@ -40,15 +48,21 @@ public enum SharingPolicy /// /// Key to file entry list. Example usages: cache glob expansion and intermediary directory expansions during glob expansion. /// - internal ConcurrentDictionary> FileEntryExpansionCache { get; } + private ConcurrentDictionary> FileEntryExpansionCache { get; } - internal EvaluationContext(SharingPolicy policy) + private EvaluationContext(SharingPolicy policy, IFileSystem fileSystem) { + // Unsupported case: isolated context with non null file system. + // Isolated means caches aren't reused, but the given file system might cache. + ErrorUtilities.VerifyThrowArgument( + policy == SharingPolicy.Shared || fileSystem == null, + "IsolatedContextDoesNotSupportFileSystem"); + Policy = policy; SdkResolverService = new CachingSdkResolverService(); FileEntryExpansionCache = new ConcurrentDictionary>(); - FileSystem = new CachingFileSystemWrapper(FileSystems.Default); + FileSystem = fileSystem ?? new CachingFileSystemWrapper(FileSystems.Default); EngineFileUtilities = new EngineFileUtilities(new FileMatcher(FileSystem, FileEntryExpansionCache)); } @@ -57,7 +71,28 @@ internal EvaluationContext(SharingPolicy policy) /// public static EvaluationContext Create(SharingPolicy policy) { - var context = new EvaluationContext(policy); + + // ReSharper disable once IntroduceOptionalParameters.Global + // do not remove this method to avoid breaking binary compatibility + return Create(policy, fileSystem: null); + } + + /// + /// Factory for + /// + /// The to use. + /// The to use. + /// This parameter is compatible only with . + /// The method throws if a file system is used with . + /// The reasoning is that means not reusing any caches between evaluations, + /// and the passed in might cache state. + /// + public static EvaluationContext Create(SharingPolicy policy, MSBuildFileSystemBase fileSystem) + { + var context = new EvaluationContext( + policy, + fileSystem == null ? null : new MSBuildFileSystemAdapter(fileSystem)); + TestOnlyHookOnCreate?.Invoke(context); return context; diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index 113593b7d91..c2708c387c5 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -1831,4 +1831,7 @@ Utilization: {0} Average Utilization: {1:###.0} "Static graph loaded in {0} seconds: {1} nodes, {2} edges" - \ No newline at end of file + + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + + diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf index 370654db138..ee4d13472fd 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -100,6 +100,11 @@ Řetězec verze nemá správný formát. + + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + + Logging verbosity is set to: {0}. Podrobnost protokolování je nastavená na: {0}. diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index 16260f7b5f5..44b17444d81 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -100,6 +100,11 @@ Die Versionszeichenfolge liegt nicht im richtigen Format vor. + + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + + Logging verbosity is set to: {0}. Die Ausführlichkeit der Protokollierung ist auf "{0}" festgelegt. diff --git a/src/Build/Resources/xlf/Strings.en.xlf b/src/Build/Resources/xlf/Strings.en.xlf index aa60e39687a..6f48da26a49 100644 --- a/src/Build/Resources/xlf/Strings.en.xlf +++ b/src/Build/Resources/xlf/Strings.en.xlf @@ -100,6 +100,11 @@ Version string was not in a correct format. + + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + + Logging verbosity is set to: {0}. Logging verbosity is set to: {0}. diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index 0662b364837..ff6b7df796d 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -100,6 +100,11 @@ La cadena de versión no tenía el formato correcto. + + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + + Logging verbosity is set to: {0}. El nivel de detalle de registro está establecido en {0}. diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index 0c7c314d134..531f580e15a 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -100,6 +100,11 @@ La chaîne de version n'était pas au format approprié. + + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + + Logging verbosity is set to: {0}. La verbosité de la journalisation a la valeur {0}. diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index bea41fb0d0e..1a694c9d92c 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -100,6 +100,11 @@ Il formato della stringa di versione non è corretto. + + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + + Logging verbosity is set to: {0}. Il livello di dettaglio della registrazione è impostato su: {0}. diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index bdb2273039f..9ac0fcd13c4 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -100,6 +100,11 @@ バージョン文字列の形式が正しくありません。 + + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + + Logging verbosity is set to: {0}. ログの詳細度は次のように設定されています: {0}。 diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index 1abe7974cd6..6b4bb9305d7 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -100,6 +100,11 @@ 버전 문자열의 형식이 잘못되었습니다. + + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + + Logging verbosity is set to: {0}. 로깅의 세부 정보 표시가 {0}(으)로 설정되었습니다. diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index 5890ec6a05e..afb3ebf2e36 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -100,6 +100,11 @@ Nieprawidłowy format ciągu wersji. + + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + + Logging verbosity is set to: {0}. Szczegółowość rejestrowania została ustawiona na: {0}. diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index 12ef60ca544..0af53bc16a2 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -100,6 +100,11 @@ A cadeia de caracteres de versão não estava em um formato correto. + + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + + Logging verbosity is set to: {0}. O detalhamento do log está definido como: {0}. diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index 62e4d151553..1fd04905eb0 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -100,6 +100,11 @@ Строка версии имела неверный формат. + + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + + Logging verbosity is set to: {0}. Уровень детализации журнала: {0}. diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index bda67583870..d65c72420e7 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -100,6 +100,11 @@ Sürüm dizesi doğru biçimde değildi. + + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + + Logging verbosity is set to: {0}. Günlük kaydı ayrıntı düzeyi {0} olarak ayarlandı. diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index 2c1cb8e965d..be7e4e06f42 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -100,6 +100,11 @@ 版本字符串的格式不正确。 + + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + + Logging verbosity is set to: {0}. 日志记录详细程度设置为: {0}。 diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index 43d0c4ce594..eb53e039bba 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -100,6 +100,11 @@ 版本字串格式不正確。 + + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + "EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system." + + Logging verbosity is set to: {0}. 記錄詳細程度設定為: {0}。 diff --git a/src/Shared/UnitTests/ObjectModelHelpers.cs b/src/Shared/UnitTests/ObjectModelHelpers.cs index 9840a2534bf..63300d1090b 100644 --- a/src/Shared/UnitTests/ObjectModelHelpers.cs +++ b/src/Shared/UnitTests/ObjectModelHelpers.cs @@ -3,16 +3,19 @@ using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; +using System.Threading; using System.Xml; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; +using Microsoft.Build.FileSystem; using Microsoft.Build.Framework; using Microsoft.Build.Graph; using Microsoft.Build.Logging; @@ -1936,5 +1939,133 @@ public void Dispose() _buildManager.Dispose(); } } + + internal class LoggingFileSystem : MSBuildFileSystemBase + { + private readonly IFileSystem _wrappingFileSystem; + private int _fileSystemCalls; + + public int FileSystemCalls => _fileSystemCalls; + + public ConcurrentDictionary ExistenceChecks { get; } = new ConcurrentDictionary(); + + public LoggingFileSystem(IFileSystem wrappingFileSystem = null) + { + _wrappingFileSystem = wrappingFileSystem ?? FileSystems.Default; + } + + public override TextReader ReadFile(string path) + { + IncrementCalls(ref _fileSystemCalls); + + return _wrappingFileSystem.ReadFile(path); + } + + public override Stream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share) + { + IncrementCalls(ref _fileSystemCalls); + + return _wrappingFileSystem.GetFileStream(path, mode, access, share); + } + + public override string ReadFileAllText(string path) + { + IncrementCalls(ref _fileSystemCalls); + + return _wrappingFileSystem.ReadFileAllText(path); + } + + public override byte[] ReadFileAllBytes(string path) + { + IncrementCalls(ref _fileSystemCalls); + + return _wrappingFileSystem.ReadFileAllBytes(path); + } + + public override IEnumerable EnumerateFiles( + string path, + string searchPattern = "*", + SearchOption searchOption = SearchOption.TopDirectoryOnly + ) + { + IncrementCalls(ref _fileSystemCalls); + + return _wrappingFileSystem.EnumerateFiles(path, searchPattern, searchOption); + } + + public override IEnumerable EnumerateDirectories( + string path, + string searchPattern = "*", + SearchOption searchOption = SearchOption.TopDirectoryOnly + ) + { + IncrementCalls(ref _fileSystemCalls); + + return _wrappingFileSystem.EnumerateDirectories(path, searchPattern, searchOption); + } + + public override IEnumerable EnumerateFileSystemEntries( + string path, + string searchPattern = "*", + SearchOption searchOption = SearchOption.TopDirectoryOnly + ) + { + IncrementCalls(ref _fileSystemCalls); + + return _wrappingFileSystem.EnumerateFileSystemEntries(path, searchPattern, searchOption); + } + + public override FileAttributes GetAttributes(string path) + { + IncrementCalls(ref _fileSystemCalls); + + return _wrappingFileSystem.GetAttributes(path); + } + + public override DateTime GetLastWriteTimeUtc(string path) + { + IncrementCalls(ref _fileSystemCalls); + + return _wrappingFileSystem.GetLastWriteTimeUtc(path); + } + + public override bool DirectoryExists(string path) + { + IncrementCalls(ref _fileSystemCalls); + IncrementExistenceChecks(path); + + return _wrappingFileSystem.DirectoryExists(path); + } + + public override bool FileExists(string path) + { + IncrementCalls(ref _fileSystemCalls); + IncrementExistenceChecks(path); + + return _wrappingFileSystem.FileExists(path); + } + + private int _directoryEntryExistsCalls; + public int DirectoryEntryExistsCalls => _directoryEntryExistsCalls; + + public override bool FileOrDirectoryExists(string path) + { + IncrementCalls(ref _fileSystemCalls); + IncrementCalls(ref _directoryEntryExistsCalls); + IncrementExistenceChecks(path); + + return _wrappingFileSystem.DirectoryEntryExists(path); + } + + private void IncrementCalls(ref int incremented) + { + Interlocked.Increment(ref incremented); + } + + private void IncrementExistenceChecks(string path) + { + ExistenceChecks.AddOrUpdate(path, p => 1, (p, c) => c + 1); + } + } } }