diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileData.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileData.cs
index 7fcf94514..a7cfebb65 100644
--- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileData.cs
+++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileData.cs
@@ -119,6 +119,11 @@ public MockFileData(MockFileData template)
///
public byte[] Contents { get; set; }
+ ///
+ /// Gets or sets the file version info of the
+ ///
+ public IFileVersionInfo FileVersionInfo { get; set; }
+
///
/// Gets or sets the string contents of the .
///
diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileInfo.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileInfo.cs
index 5356260f1..f9dd951f5 100644
--- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileInfo.cs
+++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileInfo.cs
@@ -259,7 +259,7 @@ public override void Encrypt()
var mockFileData = GetMockFileDataForWrite();
mockFileData.Attributes |= FileAttributes.Encrypted;
}
-
+
///
public override void MoveTo(string destFileName)
{
@@ -323,7 +323,7 @@ public override IFileInfo Replace(string destinationFileName, string destination
mockFile.Replace(path, destinationFileName, destinationBackupFileName, ignoreMetadataErrors);
return mockFileSystem.FileInfo.New(destinationFileName);
}
-
+
///
public override IDirectoryInfo Directory
{
diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileSystem.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileSystem.cs
index 4199c5622..572461551 100644
--- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileSystem.cs
+++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileSystem.cs
@@ -65,6 +65,7 @@ public MockFileSystem(IDictionary files, MockFileSystemOpt
File = new MockFile(this);
Directory = new MockDirectory(this, currentDirectory);
FileInfo = new MockFileInfoFactory(this);
+ FileVersionInfo = new MockFileVersionInfoFactory(this);
FileStream = new MockFileStreamFactory(this);
DirectoryInfo = new MockDirectoryInfoFactory(this);
DriveInfo = new MockDriveInfoFactory(this);
@@ -98,6 +99,8 @@ public MockFileSystem(IDictionary files, MockFileSystemOpt
///
public override IFileInfoFactory FileInfo { get; }
///
+ public override IFileVersionInfoFactory FileVersionInfo { get; }
+ ///
public override IFileStreamFactory FileStream { get; }
///
public override IPath Path { get; }
@@ -252,6 +255,8 @@ public void AddFile(string path, MockFileData mockFile)
AddDirectory(directoryPath);
}
+ mockFile.FileVersionInfo ??= new MockFileVersionInfo(fixedPath);
+
SetEntry(fixedPath, mockFile);
}
@@ -568,7 +573,7 @@ private bool FileIsReadOnly(string path)
}
#if FEATURE_SERIALIZABLE
- [Serializable]
+ [Serializable]
#endif
private class FileSystemEntry
{
diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileVersionInfo.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileVersionInfo.cs
new file mode 100644
index 000000000..1eaac4f59
--- /dev/null
+++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileVersionInfo.cs
@@ -0,0 +1,174 @@
+using System.Diagnostics;
+using System.Text;
+
+namespace System.IO.Abstractions.TestingHelpers
+{
+ ///
+#if FEATURE_SERIALIZABLE
+ [Serializable]
+#endif
+ public class MockFileVersionInfo : FileVersionInfoBase
+ {
+ ///
+ public MockFileVersionInfo(
+ string fileName,
+ string fileVersion = null,
+ string productVersion = null,
+ string fileDescription = null,
+ string productName = null,
+ string companyName = null,
+ string comments = null,
+ string internalName = null,
+ bool isDebug = false,
+ bool isPatched = false,
+ bool isPrivateBuild = false,
+ bool isPreRelease = false,
+ bool isSpecialBuild = false,
+ string language = null,
+ string legalCopyright = null,
+ string legalTrademarks = null,
+ string originalFilename = null,
+ string privateBuild = null,
+ string specialBuild = null)
+ {
+ FileName = fileName;
+ FileVersion = fileVersion;
+ ProductVersion = productVersion;
+ FileDescription = fileDescription;
+ ProductName = productName;
+ CompanyName = companyName;
+ Comments = comments;
+ InternalName = internalName;
+ IsDebug = isDebug;
+ IsPatched = isPatched;
+ IsPrivateBuild = isPrivateBuild;
+ IsPreRelease = isPreRelease;
+ IsSpecialBuild = isSpecialBuild;
+ Language = language;
+ LegalCopyright = legalCopyright;
+ LegalTrademarks = legalTrademarks;
+ OriginalFilename = originalFilename;
+ PrivateBuild = privateBuild;
+ SpecialBuild = specialBuild;
+
+ if (Version.TryParse(fileVersion, out Version version))
+ {
+ FileMajorPart = version.Major;
+ FileMinorPart = version.Minor;
+ FileBuildPart = version.Build;
+ FilePrivatePart = version.Revision;
+ }
+
+ var parsedProductVersion = ProductVersionParser.Parse(productVersion);
+
+ ProductMajorPart = parsedProductVersion.Major;
+ ProductMinorPart = parsedProductVersion.Minor;
+ ProductBuildPart = parsedProductVersion.Build;
+ ProductPrivatePart = parsedProductVersion.PrivatePart;
+ }
+
+ ///
+ public override string FileName { get; }
+
+ ///
+ public override string FileVersion { get; }
+
+ ///
+ public override string ProductVersion { get; }
+
+ ///
+ public override string FileDescription { get; }
+
+ ///
+ public override string ProductName { get; }
+
+ ///
+ public override string CompanyName { get; }
+
+ ///
+ public override string Comments { get; }
+
+ ///
+ public override string InternalName { get; }
+
+ ///
+ public override bool IsDebug { get; }
+
+ ///
+ public override bool IsPatched { get; }
+
+ ///
+ public override bool IsPrivateBuild { get; }
+
+ ///
+ public override bool IsPreRelease { get; }
+
+ ///
+ public override bool IsSpecialBuild { get; }
+
+ ///
+ public override string Language { get; }
+
+ ///
+ public override string LegalCopyright { get; }
+
+ ///
+ public override string LegalTrademarks { get; }
+
+ ///
+ public override string OriginalFilename { get; }
+
+ ///
+ public override string PrivateBuild { get; }
+
+ ///
+ public override string SpecialBuild { get; }
+
+ ///
+ public override int FileMajorPart { get; }
+
+ ///
+ public override int FileMinorPart { get; }
+
+ ///
+ public override int FileBuildPart { get; }
+
+ ///
+ public override int FilePrivatePart { get; }
+
+ ///
+ public override int ProductMajorPart { get; }
+
+ ///
+ public override int ProductMinorPart { get; }
+
+ ///
+ public override int ProductBuildPart { get; }
+
+ ///
+ public override int ProductPrivatePart { get; }
+
+ ///
+ public override string ToString()
+ {
+ // An initial capacity of 512 was chosen because it is large enough to cover
+ // the size of the static strings with enough capacity left over to cover
+ // average length property values.
+ var sb = new StringBuilder(512);
+ sb.Append("File: ").AppendLine(FileName);
+ sb.Append("InternalName: ").AppendLine(InternalName);
+ sb.Append("OriginalFilename: ").AppendLine(OriginalFilename);
+ sb.Append("FileVersion: ").AppendLine(FileVersion);
+ sb.Append("FileDescription: ").AppendLine(FileDescription);
+ sb.Append("Product: ").AppendLine(ProductName);
+ sb.Append("ProductVersion: ").AppendLine(ProductVersion);
+ sb.Append("Debug: ").AppendLine(IsDebug.ToString());
+ sb.Append("Patched: ").AppendLine(IsPatched.ToString());
+ sb.Append("PreRelease: ").AppendLine(IsPreRelease.ToString());
+ sb.Append("PrivateBuild: ").AppendLine(IsPrivateBuild.ToString());
+ sb.Append("SpecialBuild: ").AppendLine(IsSpecialBuild.ToString());
+ sb.Append("Language: ").AppendLine(Language);
+ return sb.ToString();
+ }
+ }
+}
diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileVersionInfoFactory.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileVersionInfoFactory.cs
new file mode 100644
index 000000000..76278f0dc
--- /dev/null
+++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileVersionInfoFactory.cs
@@ -0,0 +1,33 @@
+namespace System.IO.Abstractions.TestingHelpers
+{
+ ///
+#if FEATURE_SERIALIZABLE
+ [Serializable]
+#endif
+ public class MockFileVersionInfoFactory : IFileVersionInfoFactory
+ {
+ private readonly IMockFileDataAccessor mockFileSystem;
+
+ ///
+ public MockFileVersionInfoFactory(IMockFileDataAccessor mockFileSystem)
+ {
+ this.mockFileSystem = mockFileSystem ?? throw new ArgumentNullException(nameof(mockFileSystem));
+ }
+
+ ///
+ public IFileSystem FileSystem => mockFileSystem;
+
+ ///
+ public IFileVersionInfo GetVersionInfo(string fileName)
+ {
+ MockFileData mockFileData = mockFileSystem.GetFile(fileName);
+
+ if (mockFileData != null)
+ {
+ return mockFileData.FileVersionInfo;
+ }
+
+ throw CommonExceptions.FileNotFound(fileName);
+ }
+ }
+}
diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/ProductVersionParser.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/ProductVersionParser.cs
new file mode 100644
index 000000000..011de195c
--- /dev/null
+++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/ProductVersionParser.cs
@@ -0,0 +1,99 @@
+using System.Reflection;
+using System.Text.RegularExpressions;
+
+namespace System.IO.Abstractions.TestingHelpers
+{
+ ///
+ /// Provides functionality to parse a product version string into its major, minor, build, and private parts.
+ ///
+ internal static class ProductVersionParser
+ {
+ ///
+ /// Parses a product version string and extracts the numeric values for the major, minor, build, and private parts,
+ /// mimicking the behavior of the attribute.
+ ///
+ /// The product version string to parse.
+ ///
+ /// A object containing the parsed major, minor, build, and private parts.
+ /// If the input is invalid, returns a with all parts set to 0.
+ ///
+ ///
+ /// The method splits the input string into segments separated by dots ('.') and attempts to extract
+ /// the leading numeric value from each segment. A maximum of 4 segments are processed; if more than
+ /// 4 segments are present, all segments are ignored. Additionally, if a segment does not contain
+ /// a valid numeric part at its start or it contains more than just a number, the rest of the segments are ignored.
+ ///
+ public static ProductVersion Parse(string productVersion)
+ {
+ if (string.IsNullOrWhiteSpace(productVersion))
+ {
+ return new();
+ }
+
+ var segments = productVersion.Split('.');
+ if (segments.Length > 4)
+ {
+ // if more than 4 segments are present, all segments are ignored
+ return new();
+ }
+
+ var regex = new Regex(@"^\d+");
+
+ int[] parts = new int[4];
+
+ for (int i = 0; i < segments.Length; i++)
+ {
+ var match = regex.Match(segments[i]);
+ if (match.Success && int.TryParse(match.Value, out int number))
+ {
+ parts[i] = number;
+
+ if (match.Value != segments[i])
+ {
+ // when a segment contains more than a number, the rest of the segments are ignored
+ break;
+ }
+ }
+ else
+ {
+ // when a segment is not valid, the rest of the segments are ignored
+ break;
+ }
+ }
+
+ return new()
+ {
+ Major = parts[0],
+ Minor = parts[1],
+ Build = parts[2],
+ PrivatePart = parts[3]
+ };
+ }
+
+ ///
+ /// Represents a product version with numeric parts for major, minor, build, and private versions.
+ ///
+ public class ProductVersion
+ {
+ ///
+ /// Gets the major part of the version number
+ ///
+ public int Major { get; init; }
+
+ ///
+ /// Gets the minor part of the version number
+ ///
+ public int Minor { get; init; }
+
+ ///
+ /// Gets the build part of the version number
+ ///
+ public int Build { get; init; }
+
+ ///
+ /// Gets the private part of the version number
+ ///
+ public int PrivatePart { get; init; }
+ }
+ }
+}
diff --git a/src/TestableIO.System.IO.Abstractions.Wrappers/FileInfoBase.cs b/src/TestableIO.System.IO.Abstractions.Wrappers/FileInfoBase.cs
index 1c8875fb0..ca4df775b 100644
--- a/src/TestableIO.System.IO.Abstractions.Wrappers/FileInfoBase.cs
+++ b/src/TestableIO.System.IO.Abstractions.Wrappers/FileInfoBase.cs
@@ -36,7 +36,7 @@ internal FileInfoBase() { }
///
public abstract void Encrypt();
-
+
///
public abstract void MoveTo(string destFileName);
@@ -73,7 +73,7 @@ internal FileInfoBase() { }
///
public abstract IFileInfo Replace(string destinationFileName, string destinationBackupFileName, bool ignoreMetadataErrors);
-
+
///
public abstract IDirectoryInfo Directory { get; }
diff --git a/src/TestableIO.System.IO.Abstractions.Wrappers/FileSystem.cs b/src/TestableIO.System.IO.Abstractions.Wrappers/FileSystem.cs
index 80e7ae69e..1e9cfca2b 100644
--- a/src/TestableIO.System.IO.Abstractions.Wrappers/FileSystem.cs
+++ b/src/TestableIO.System.IO.Abstractions.Wrappers/FileSystem.cs
@@ -12,6 +12,7 @@ public FileSystem()
DriveInfo = new DriveInfoFactory(this);
DirectoryInfo = new DirectoryInfoFactory(this);
FileInfo = new FileInfoFactory(this);
+ FileVersionInfo = new FileVersionInfoFactory(this);
Path = new PathWrapper(this);
File = new FileWrapper(this);
Directory = new DirectoryWrapper(this);
@@ -28,6 +29,9 @@ public FileSystem()
///
public override IFileInfoFactory FileInfo { get; }
+ ///
+ public override IFileVersionInfoFactory FileVersionInfo { get; }
+
///
public override IFileStreamFactory FileStream { get; }
diff --git a/src/TestableIO.System.IO.Abstractions.Wrappers/FileSystemBase.cs b/src/TestableIO.System.IO.Abstractions.Wrappers/FileSystemBase.cs
index 6fb508161..b851d9eaa 100644
--- a/src/TestableIO.System.IO.Abstractions.Wrappers/FileSystemBase.cs
+++ b/src/TestableIO.System.IO.Abstractions.Wrappers/FileSystemBase.cs
@@ -15,6 +15,9 @@ public abstract class FileSystemBase : IFileSystem
///
public abstract IFileInfoFactory FileInfo { get; }
+ ///
+ public abstract IFileVersionInfoFactory FileVersionInfo { get; }
+
///
public abstract IFileStreamFactory FileStream { get; }
diff --git a/src/TestableIO.System.IO.Abstractions.Wrappers/FileVersionInfoBase.cs b/src/TestableIO.System.IO.Abstractions.Wrappers/FileVersionInfoBase.cs
new file mode 100644
index 000000000..d8dd87683
--- /dev/null
+++ b/src/TestableIO.System.IO.Abstractions.Wrappers/FileVersionInfoBase.cs
@@ -0,0 +1,106 @@
+using System.Diagnostics;
+
+namespace System.IO.Abstractions
+{
+ ///
+#if FEATURE_SERIALIZABLE
+ [Serializable]
+#endif
+ public abstract class FileVersionInfoBase : IFileVersionInfo
+ {
+ ///
+ public abstract string Comments { get; }
+
+ ///
+ public abstract string CompanyName { get; }
+
+ ///
+ public abstract int FileBuildPart { get; }
+
+ ///
+ public abstract string FileDescription { get; }
+
+ ///
+ public abstract int FileMajorPart { get; }
+
+ ///
+ public abstract int FileMinorPart { get; }
+
+ ///
+ public abstract string FileName { get; }
+
+ ///
+ public abstract int FilePrivatePart { get; }
+
+ ///
+ public abstract string FileVersion { get; }
+
+ ///
+ public abstract string InternalName { get; }
+
+ ///
+ public abstract bool IsDebug { get; }
+
+ ///
+ public abstract bool IsPatched { get; }
+
+ ///
+ public abstract bool IsPrivateBuild { get; }
+
+ ///
+ public abstract bool IsPreRelease { get; }
+
+ ///
+ public abstract bool IsSpecialBuild { get; }
+
+ ///
+ public abstract string Language { get; }
+
+ ///
+ public abstract string LegalCopyright { get; }
+
+ ///
+ public abstract string LegalTrademarks { get; }
+
+ ///
+ public abstract string OriginalFilename { get; }
+
+ ///
+ public abstract string PrivateBuild { get; }
+
+ ///
+ public abstract int ProductBuildPart { get; }
+
+ ///
+ public abstract int ProductMajorPart { get; }
+
+ ///
+ public abstract int ProductMinorPart { get; }
+
+ ///
+ public abstract string ProductName { get; }
+
+ ///
+ public abstract int ProductPrivatePart { get; }
+
+ ///
+ public abstract string ProductVersion { get; }
+
+ ///
+ public abstract string SpecialBuild { get; }
+
+ ///
+ public static implicit operator FileVersionInfoBase(FileVersionInfo fileVersionInfo)
+ {
+ if (fileVersionInfo == null)
+ {
+ return null;
+ }
+
+ return new FileVersionInfoWrapper(fileVersionInfo);
+ }
+
+ ///
+ public new abstract string ToString();
+ }
+}
diff --git a/src/TestableIO.System.IO.Abstractions.Wrappers/FileVersionInfoFactory.cs b/src/TestableIO.System.IO.Abstractions.Wrappers/FileVersionInfoFactory.cs
new file mode 100644
index 000000000..f3594a28f
--- /dev/null
+++ b/src/TestableIO.System.IO.Abstractions.Wrappers/FileVersionInfoFactory.cs
@@ -0,0 +1,27 @@
+namespace System.IO.Abstractions
+{
+#if FEATURE_SERIALIZABLE
+ [Serializable]
+#endif
+ internal class FileVersionInfoFactory : IFileVersionInfoFactory
+ {
+ private readonly IFileSystem fileSystem;
+
+ ///
+ public FileVersionInfoFactory(IFileSystem fileSystem)
+ {
+ this.fileSystem = fileSystem;
+ }
+
+ ///
+ public IFileSystem FileSystem => fileSystem;
+
+ ///
+ public IFileVersionInfo GetVersionInfo(string fileName)
+ {
+ Diagnostics.FileVersionInfo fileVersionInfo = Diagnostics.FileVersionInfo.GetVersionInfo(fileName);
+
+ return new FileVersionInfoWrapper(fileVersionInfo);
+ }
+ }
+}
diff --git a/src/TestableIO.System.IO.Abstractions.Wrappers/FileVersionInfoWrapper.cs b/src/TestableIO.System.IO.Abstractions.Wrappers/FileVersionInfoWrapper.cs
new file mode 100644
index 000000000..999864475
--- /dev/null
+++ b/src/TestableIO.System.IO.Abstractions.Wrappers/FileVersionInfoWrapper.cs
@@ -0,0 +1,187 @@
+using System.Diagnostics;
+
+namespace System.IO.Abstractions
+{
+ ///
+#if FEATURE_SERIALIZABLE
+ [Serializable]
+#endif
+ public class FileVersionInfoWrapper : FileVersionInfoBase
+ {
+ private readonly FileVersionInfo instance;
+
+ ///
+ public FileVersionInfoWrapper(FileVersionInfo fileVersionInfo)
+ {
+ instance = fileVersionInfo;
+ }
+
+ ///
+ public override string Comments
+ {
+ get { return instance.Comments; }
+ }
+
+ ///
+ public override string CompanyName
+ {
+ get { return instance.CompanyName; }
+ }
+
+ ///
+ public override int FileBuildPart
+ {
+ get { return instance.FileBuildPart; }
+ }
+
+ ///
+ public override string FileDescription
+ {
+ get { return instance.FileDescription; }
+ }
+
+ ///
+ public override int FileMajorPart
+ {
+ get { return instance.FileMajorPart; }
+ }
+
+ ///
+ public override int FileMinorPart
+ {
+ get { return instance.FileMinorPart; }
+ }
+
+ ///
+ public override string FileName
+ {
+ get { return instance.FileName; }
+ }
+
+ ///
+ public override int FilePrivatePart
+ {
+ get { return instance.FilePrivatePart; }
+ }
+
+ ///
+ public override string FileVersion
+ {
+ get { return instance.FileVersion; }
+ }
+
+ ///
+ public override string InternalName
+ {
+ get { return instance.InternalName; }
+ }
+
+ ///
+ public override bool IsDebug
+ {
+ get { return instance.IsDebug; }
+ }
+
+ ///
+ public override bool IsPatched
+ {
+ get { return instance.IsPatched; }
+ }
+
+ ///
+ public override bool IsPrivateBuild
+ {
+ get { return instance.IsPrivateBuild; }
+ }
+
+ ///
+ public override bool IsPreRelease
+ {
+ get { return instance.IsPreRelease; }
+ }
+
+ ///
+ public override bool IsSpecialBuild
+ {
+ get { return instance.IsSpecialBuild; }
+ }
+
+ ///
+ public override string Language
+ {
+ get { return instance.Language; }
+ }
+
+ ///
+ public override string LegalCopyright
+ {
+ get { return instance.LegalCopyright; }
+ }
+
+ ///
+ public override string LegalTrademarks
+ {
+ get { return instance.LegalTrademarks; }
+ }
+
+ ///
+ public override string OriginalFilename
+ {
+ get { return instance.OriginalFilename; }
+ }
+
+ ///
+ public override string PrivateBuild
+ {
+ get { return instance.PrivateBuild; }
+ }
+
+ ///
+ public override int ProductBuildPart
+ {
+ get { return instance.ProductBuildPart; }
+ }
+
+ ///
+ public override int ProductMajorPart
+ {
+ get { return instance.ProductMajorPart; }
+ }
+
+ ///
+ public override int ProductMinorPart
+ {
+ get { return instance.ProductMinorPart; }
+ }
+
+ ///
+ public override string ProductName
+ {
+ get { return instance.ProductName; }
+ }
+
+ ///
+ public override int ProductPrivatePart
+ {
+ get { return instance.ProductPrivatePart; }
+ }
+
+ ///
+ public override string ProductVersion
+ {
+ get { return instance.ProductVersion; }
+ }
+
+ ///
+ public override string SpecialBuild
+ {
+ get { return instance.SpecialBuild; }
+ }
+
+ ///
+ public override string ToString()
+ {
+ return instance.ToString();
+ }
+ }
+}
diff --git a/src/TestableIO.System.IO.Abstractions/IFileSystem.cs b/src/TestableIO.System.IO.Abstractions/IFileSystem.cs
index d6f27d526..bc56d6246 100644
--- a/src/TestableIO.System.IO.Abstractions/IFileSystem.cs
+++ b/src/TestableIO.System.IO.Abstractions/IFileSystem.cs
@@ -30,6 +30,11 @@ public interface IFileSystem
///
IFileInfoFactory FileInfo { get; }
+ ///
+ /// A factory for the creation of wrappers for .
+ ///
+ IFileVersionInfoFactory FileVersionInfo { get; }
+
///
/// A factory for the creation of wrappers for .
///
diff --git a/src/TestableIO.System.IO.Abstractions/IFileVersionInfo.cs b/src/TestableIO.System.IO.Abstractions/IFileVersionInfo.cs
new file mode 100644
index 000000000..d7840f2f4
--- /dev/null
+++ b/src/TestableIO.System.IO.Abstractions/IFileVersionInfo.cs
@@ -0,0 +1,89 @@
+using System.Diagnostics;
+
+namespace System.IO.Abstractions
+{
+ ///
+ public interface IFileVersionInfo
+ {
+ ///
+ string? Comments { get; }
+
+ ///
+ string? CompanyName { get; }
+
+ ///
+ int FileBuildPart { get; }
+
+ ///
+ string? FileDescription { get; }
+
+ ///
+ int FileMajorPart { get; }
+
+ ///
+ int FileMinorPart { get; }
+
+ ///
+ string FileName { get; }
+
+ ///
+ int FilePrivatePart { get; }
+
+ ///
+ string? FileVersion { get; }
+
+ ///
+ string? InternalName { get; }
+
+ ///
+ bool IsDebug { get; }
+
+ ///
+ bool IsPatched { get; }
+
+ ///
+ bool IsPrivateBuild { get; }
+
+ ///
+ bool IsPreRelease { get; }
+
+ ///
+ bool IsSpecialBuild { get; }
+
+ ///
+ string? Language { get; }
+
+ ///
+ string? LegalCopyright { get; }
+
+ ///
+ string? LegalTrademarks { get; }
+
+ ///
+ string? OriginalFilename { get; }
+
+ ///
+ string? PrivateBuild { get; }
+
+ ///
+ int ProductBuildPart { get; }
+
+ ///
+ int ProductMajorPart { get; }
+
+ ///
+ int ProductMinorPart { get; }
+
+ ///
+ string? ProductName { get; }
+
+ ///
+ int ProductPrivatePart { get; }
+
+ ///
+ string? ProductVersion { get; }
+
+ ///
+ string? SpecialBuild { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/TestableIO.System.IO.Abstractions/IFileVersionInfoFactory.cs b/src/TestableIO.System.IO.Abstractions/IFileVersionInfoFactory.cs
new file mode 100644
index 000000000..a1d9208eb
--- /dev/null
+++ b/src/TestableIO.System.IO.Abstractions/IFileVersionInfoFactory.cs
@@ -0,0 +1,13 @@
+using System.Diagnostics;
+
+namespace System.IO.Abstractions
+{
+ ///
+ /// A factory for the creation of wrappers for in a .
+ ///
+ public interface IFileVersionInfoFactory : IFileSystemEntity
+ {
+ ///
+ IFileVersionInfo GetVersionInfo(string fileName);
+ }
+}
diff --git a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileSystemTests.cs b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileSystemTests.cs
index 4b681421c..1f88311c1 100644
--- a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileSystemTests.cs
+++ b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileSystemTests.cs
@@ -286,6 +286,21 @@ public void MockFileSystem_AddFile_ShouldMatchCapitalization_PartialMatch_Furthe
Assert.That(fileSystem.AllDirectories.ToList(), Does.Contain(XFS.Path(@"C:\LOUD\SUBLOUD\new\SUBDirectory")));
}
+ [Test]
+ public void MockFileSystem_AddFile_InitializesMockFileDataFileVersionInfoIfNull()
+ {
+ // Arrange
+ var fileSystem = new MockFileSystem();
+
+ // Act
+ fileSystem.AddFile(XFS.Path(@"C:\file.txt"), string.Empty);
+
+ // Assert
+ IFileVersionInfo fileVersionInfo = fileSystem.FileVersionInfo.GetVersionInfo(XFS.Path(@"C:\file.txt"));
+ Assert.That(fileVersionInfo, Is.Not.Null);
+ Assert.That(fileVersionInfo.FileName, Is.EqualTo(XFS.Path(@"C:\file.txt")));
+ }
+
[Test]
public void MockFileSystem_AddFileFromEmbeddedResource_ShouldAddTheFile()
{
@@ -435,7 +450,7 @@ public void MockFileSystem_Constructor_ThrowsForNonRootedCurrentDirectory()
);
Assert.That(ae.ParamName, Is.EqualTo("currentDirectory"));
}
-
+
[Test]
[WindowsOnly(WindowsSpecifics.Drives)]
public void MockFileSystem_Constructor_ShouldSupportDifferentRootDrives()
@@ -450,7 +465,7 @@ public void MockFileSystem_Constructor_ShouldSupportDifferentRootDrives()
var cExists = fileSystem.Directory.Exists(@"c:\");
var zExists = fileSystem.Directory.Exists(@"z:\");
var dExists = fileSystem.Directory.Exists(@"d:\");
-
+
Assert.That(fileSystem, Is.Not.Null);
Assert.That(cExists, Is.True);
Assert.That(zExists, Is.True);
@@ -484,9 +499,9 @@ public void MockFileSystem_Constructor_ShouldNotDuplicateDrives()
[@"d:\foo\bar\"] = new MockDirectoryData(),
[@"d:\"] = new MockDirectoryData()
});
-
+
var drivesInfo = fileSystem.DriveInfo.GetDrives();
-
+
Assert.That(drivesInfo.Where(d => string.Equals(d.Name, @"D:\", StringComparison.InvariantCultureIgnoreCase)), Has.Exactly(1).Items);
}
diff --git a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileVersionInfoFactoryTests.cs b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileVersionInfoFactoryTests.cs
new file mode 100644
index 000000000..9b10ac3a8
--- /dev/null
+++ b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileVersionInfoFactoryTests.cs
@@ -0,0 +1,43 @@
+using NUnit.Framework;
+using System.Collections.Generic;
+
+namespace System.IO.Abstractions.TestingHelpers.Tests
+{
+ [TestFixture]
+ public class MockFileVersionInfoFactoryTests
+ {
+ [Test]
+ public void MockFileVersionInfoFactory_GetVersionInfo_ShouldReturnTheFileVersionInfoOfTheMockFileData()
+ {
+ // Arrange
+ var fileVersionInfo = new MockFileVersionInfo(@"c:\a.txt");
+ var fileSystem = new MockFileSystem(new Dictionary
+ {
+ { @"c:\a.txt", new MockFileData("Demo text content") { FileVersionInfo = fileVersionInfo } }
+ });
+
+ // Act
+ var result = fileSystem.FileVersionInfo.GetVersionInfo(@"c:\a.txt");
+
+ // Assert
+ Assert.That(result, Is.EqualTo(fileVersionInfo));
+ }
+
+ [Test]
+ public void MockFileVersionInfoFactory_GetVersionInfo_ShouldThrowFileNotFoundExceptionIfFileDoesNotExist()
+ {
+ // Arrange
+ var fileSystem = new MockFileSystem(new Dictionary
+ {
+ { @"c:\a.txt", new MockFileData("Demo text content") },
+ { @"c:\a\b\c.txt", new MockFileData("Demo text content") },
+ });
+
+ // Act
+ TestDelegate code = () => fileSystem.FileVersionInfo.GetVersionInfo(@"c:\foo.txt");
+
+ // Assert
+ Assert.Throws(code);
+ }
+ }
+}
diff --git a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileVersionInfoTests.cs b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileVersionInfoTests.cs
new file mode 100644
index 000000000..307c7c1cc
--- /dev/null
+++ b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileVersionInfoTests.cs
@@ -0,0 +1,86 @@
+using NUnit.Framework;
+
+namespace System.IO.Abstractions.TestingHelpers.Tests
+{
+ [TestFixture]
+ public class MockFileVersionInfoTests
+ {
+ [Test]
+ public void MockFileVersionInfo_ToString_ShouldReturnTheDefaultFormat()
+ {
+ // Arrange
+ var mockFileVersionInfo = new MockFileVersionInfo(
+ fileName: @"c:\b.txt",
+ fileVersion: "1.0.0.0",
+ productVersion: "1.0.0.0",
+ fileDescription: "b",
+ productName: "b",
+ companyName: null,
+ comments: null,
+ internalName: "b.txt",
+ isDebug: true,
+ isPatched: true,
+ isPrivateBuild: true,
+ isPreRelease: true,
+ isSpecialBuild: true,
+ language: "English",
+ legalCopyright: null,
+ legalTrademarks: null,
+ originalFilename: "b.txt",
+ privateBuild: null,
+ specialBuild: null);
+
+ string expected = @"File: c:\b.txt
+InternalName: b.txt
+OriginalFilename: b.txt
+FileVersion: 1.0.0.0
+FileDescription: b
+Product: b
+ProductVersion: 1.0.0.0
+Debug: True
+Patched: True
+PreRelease: True
+PrivateBuild: True
+SpecialBuild: True
+Language: English
+";
+
+ // Act & Assert
+ Assert.That(mockFileVersionInfo.ToString(), Is.EqualTo(expected));
+ }
+
+ [Test]
+ public void MockFileVersionInfo_Constructor_ShouldSetFileAndProductVersionNumbersIfFileAndProductVersionAreNotNull()
+ {
+ // Arrange
+ var mockFileVersionInfo = new MockFileVersionInfo(@"c:\file.txt", fileVersion: "1.2.3.4", productVersion: "5.6.7.8");
+
+ // Assert
+ Assert.That(mockFileVersionInfo.FileMajorPart, Is.EqualTo(1));
+ Assert.That(mockFileVersionInfo.FileMinorPart, Is.EqualTo(2));
+ Assert.That(mockFileVersionInfo.FileBuildPart, Is.EqualTo(3));
+ Assert.That(mockFileVersionInfo.FilePrivatePart, Is.EqualTo(4));
+ Assert.That(mockFileVersionInfo.ProductMajorPart, Is.EqualTo(5));
+ Assert.That(mockFileVersionInfo.ProductMinorPart, Is.EqualTo(6));
+ Assert.That(mockFileVersionInfo.ProductBuildPart, Is.EqualTo(7));
+ Assert.That(mockFileVersionInfo.ProductPrivatePart, Is.EqualTo(8));
+ }
+
+ [Test]
+ public void MockFileVersionInfo_Constructor_ShouldNotSetFileAndProductVersionNumbersIfFileAndProductVersionAreNull()
+ {
+ // Act
+ var mockFileVersionInfo = new MockFileVersionInfo(@"c:\a.txt");
+
+ // Assert
+ Assert.That(mockFileVersionInfo.FileMajorPart, Is.EqualTo(0));
+ Assert.That(mockFileVersionInfo.FileMinorPart, Is.EqualTo(0));
+ Assert.That(mockFileVersionInfo.FileBuildPart, Is.EqualTo(0));
+ Assert.That(mockFileVersionInfo.FilePrivatePart, Is.EqualTo(0));
+ Assert.That(mockFileVersionInfo.ProductMajorPart, Is.EqualTo(0));
+ Assert.That(mockFileVersionInfo.ProductMinorPart, Is.EqualTo(0));
+ Assert.That(mockFileVersionInfo.ProductBuildPart, Is.EqualTo(0));
+ Assert.That(mockFileVersionInfo.ProductPrivatePart, Is.EqualTo(0));
+ }
+ }
+}
diff --git a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/ProductVersionParserTests.cs b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/ProductVersionParserTests.cs
new file mode 100644
index 000000000..980c8be5d
--- /dev/null
+++ b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/ProductVersionParserTests.cs
@@ -0,0 +1,107 @@
+using NUnit.Framework;
+
+namespace System.IO.Abstractions.TestingHelpers.Tests
+{
+ [TestFixture]
+ public class ProductVersionParserTests
+ {
+ [Test]
+ public void ProductVersionParser_Parse_ShouldIgnoreTheSegmentsWhenThereAreMoreThanFiveOfThem()
+ {
+ // Arrange
+ string productVersion = "1.2.3.4.5";
+
+ // Act
+ var parsedProductVersion = ProductVersionParser.Parse(productVersion);
+
+ // Assert
+ Assert.Multiple(() =>
+ {
+ Assert.That(parsedProductVersion.Major, Is.Zero);
+ Assert.That(parsedProductVersion.Minor, Is.Zero);
+ Assert.That(parsedProductVersion.Build, Is.Zero);
+ Assert.That(parsedProductVersion.PrivatePart, Is.Zero);
+ });
+ }
+
+ [Test]
+ [TestCase("test.2.3.4", 0, 0, 0, 0)]
+ [TestCase("1.test.3.4", 1, 0, 0, 0)]
+ [TestCase("1.2.test.4", 1, 2, 0, 0)]
+ [TestCase("1.2.3.test", 1, 2, 3, 0)]
+ public void ProductVersionParser_Parse_ShouldSkipTheRestOfTheSegmentsWhenOneIsNotValidNumber(
+ string productVersion,
+ int expectedMajor,
+ int expectedMinor,
+ int expectedBuild,
+ int expectedRevision)
+ {
+ // Act
+ var parsedProductVersion = ProductVersionParser.Parse(productVersion);
+
+ // Assert
+ Assert.Multiple(() =>
+ {
+ Assert.That(parsedProductVersion.Major, Is.EqualTo(expectedMajor));
+ Assert.That(parsedProductVersion.Minor, Is.EqualTo(expectedMinor));
+ Assert.That(parsedProductVersion.Build, Is.EqualTo(expectedBuild));
+ Assert.That(parsedProductVersion.PrivatePart, Is.EqualTo(expectedRevision));
+ });
+ }
+
+ [Test]
+ [TestCase("1-test.2.3.4", 1, 0, 0, 0)]
+ [TestCase("1-test5.2.3.4", 1, 0, 0, 0)]
+ [TestCase("1.2-test.3.4", 1, 2, 0, 0)]
+ [TestCase("1.2-test5.3.4", 1, 2, 0, 0)]
+ [TestCase("1.2.3-test.4", 1, 2, 3, 0)]
+ [TestCase("1.2.3-test5.4", 1, 2, 3, 0)]
+ [TestCase("1.2.3.4-test", 1, 2, 3, 4)]
+ [TestCase("1.2.3.4-test5", 1, 2, 3, 4)]
+ public void ProductVersionParser_Parse_ShouldSkipTheRestOfTheSegmentsWhenOneContainsMoreThanJustOneNumber(
+ string productVersion,
+ int expectedMajor,
+ int expectedMinor,
+ int expectedBuild,
+ int expectedRevision)
+ {
+ // Act
+ var parsedProductVersion = ProductVersionParser.Parse(productVersion);
+
+ // Assert
+ Assert.Multiple(() =>
+ {
+ Assert.That(parsedProductVersion.Major, Is.EqualTo(expectedMajor));
+ Assert.That(parsedProductVersion.Minor, Is.EqualTo(expectedMinor));
+ Assert.That(parsedProductVersion.Build, Is.EqualTo(expectedBuild));
+ Assert.That(parsedProductVersion.PrivatePart, Is.EqualTo(expectedRevision));
+ });
+ }
+
+ [Test]
+ [TestCase("", 0, 0, 0, 0)]
+ [TestCase("1", 1, 0, 0, 0)]
+ [TestCase("1.2", 1, 2, 0, 0)]
+ [TestCase("1.2.3", 1, 2, 3, 0)]
+ [TestCase("1.2.3.4", 1, 2, 3, 4)]
+ public void ProductVersionParser_Parse_ShouldParseEachProvidedSegment(
+ string productVersion,
+ int expectedMajor,
+ int expectedMinor,
+ int expectedBuild,
+ int expectedRevision)
+ {
+ // Act
+ var parsedProductVersion = ProductVersionParser.Parse(productVersion);
+
+ // Assert
+ Assert.Multiple(() =>
+ {
+ Assert.That(parsedProductVersion.Major, Is.EqualTo(expectedMajor));
+ Assert.That(parsedProductVersion.Minor, Is.EqualTo(expectedMinor));
+ Assert.That(parsedProductVersion.Build, Is.EqualTo(expectedBuild));
+ Assert.That(parsedProductVersion.PrivatePart, Is.EqualTo(expectedRevision));
+ });
+ }
+ }
+}
diff --git a/tests/TestableIO.System.IO.Abstractions.Wrappers.Tests/ApiParityTests.cs b/tests/TestableIO.System.IO.Abstractions.Wrappers.Tests/ApiParityTests.cs
index 706b06a2c..ec46effa8 100644
--- a/tests/TestableIO.System.IO.Abstractions.Wrappers.Tests/ApiParityTests.cs
+++ b/tests/TestableIO.System.IO.Abstractions.Wrappers.Tests/ApiParityTests.cs
@@ -24,6 +24,13 @@ public void FileInfo() =>
typeof(System.IO.Abstractions.FileInfoBase)
);
+ [Test]
+ public void FileVersionInfo() =>
+ AssertParity(
+ typeof(System.Diagnostics.FileVersionInfo),
+ typeof(System.IO.Abstractions.FileVersionInfoBase)
+ );
+
[Test]
public void Directory() =>
AssertParity(
@@ -72,7 +79,8 @@ static IEnumerable GetMembers(Type type) => type
.Select(x => x.Replace("System.IO.FileInfo", "System.IO.Abstractions.IFileInfo"))
.Select(x => x.Replace("System.IO.DirectoryInfo", "System.IO.Abstractions.IDirectoryInfo"))
.Select(x => x.Replace("System.IO.DriveInfo", "System.IO.Abstractions.IDriveInfo"))
- .Select(x => x.Replace("System.IO.WaitForChangedResult", "System.IO.Abstractions.IWaitForChangedResult"));
+ .Select(x => x.Replace("System.IO.WaitForChangedResult", "System.IO.Abstractions.IWaitForChangedResult"))
+ .Where(x => x != "System.Diagnostics.FileVersionInfo GetVersionInfo(System.String)");
var abstractionMembers = GetMembers(abstractionType)
.Where(x => !x.Contains("op_Implicit"))
.Where(x => x != "System.IO.Abstractions.IFileSystem get_FileSystem()")
diff --git a/tests/TestableIO.System.IO.Abstractions.Wrappers.Tests/FileVersionInfoBaseConversionTests.cs b/tests/TestableIO.System.IO.Abstractions.Wrappers.Tests/FileVersionInfoBaseConversionTests.cs
new file mode 100644
index 000000000..b53a64e9e
--- /dev/null
+++ b/tests/TestableIO.System.IO.Abstractions.Wrappers.Tests/FileVersionInfoBaseConversionTests.cs
@@ -0,0 +1,27 @@
+using NUnit.Framework;
+using System.Diagnostics;
+
+namespace System.IO.Abstractions.Tests
+{
+ ///
+ /// Unit tests for the conversion operators of the class.
+ ///
+ public class FileVersionInfoBaseConversionTests
+ {
+ ///
+ /// Tests that a null is correctly converted to a null without exception.
+ ///
+ [Test]
+ public void FileVersionInfoBase_FromFileVersionInfo_ShouldReturnNullIfFileVersionInfoIsNull()
+ {
+ // Arrange
+ FileVersionInfo fileVersionInfo = null;
+
+ // Act
+ FileVersionInfoBase actual = fileVersionInfo;
+
+ // Assert
+ Assert.That(actual, Is.Null);
+ }
+ }
+}
diff --git a/tests/TestableIO.System.IO.Abstractions.Wrappers.Tests/__snapshots__/ApiParityTests.FileVersionInfo_.NET 6.0.snap b/tests/TestableIO.System.IO.Abstractions.Wrappers.Tests/__snapshots__/ApiParityTests.FileVersionInfo_.NET 6.0.snap
new file mode 100644
index 000000000..7cb3b9523
--- /dev/null
+++ b/tests/TestableIO.System.IO.Abstractions.Wrappers.Tests/__snapshots__/ApiParityTests.FileVersionInfo_.NET 6.0.snap
@@ -0,0 +1,4 @@
+{
+ "ExtraMembers": [],
+ "MissingMembers": []
+}
diff --git a/tests/TestableIO.System.IO.Abstractions.Wrappers.Tests/__snapshots__/ApiParityTests.FileVersionInfo_.NET 7.0.snap b/tests/TestableIO.System.IO.Abstractions.Wrappers.Tests/__snapshots__/ApiParityTests.FileVersionInfo_.NET 7.0.snap
new file mode 100644
index 000000000..7cb3b9523
--- /dev/null
+++ b/tests/TestableIO.System.IO.Abstractions.Wrappers.Tests/__snapshots__/ApiParityTests.FileVersionInfo_.NET 7.0.snap
@@ -0,0 +1,4 @@
+{
+ "ExtraMembers": [],
+ "MissingMembers": []
+}
diff --git a/tests/TestableIO.System.IO.Abstractions.Wrappers.Tests/__snapshots__/ApiParityTests.FileVersionInfo_.NET 8.0.snap b/tests/TestableIO.System.IO.Abstractions.Wrappers.Tests/__snapshots__/ApiParityTests.FileVersionInfo_.NET 8.0.snap
new file mode 100644
index 000000000..7cb3b9523
--- /dev/null
+++ b/tests/TestableIO.System.IO.Abstractions.Wrappers.Tests/__snapshots__/ApiParityTests.FileVersionInfo_.NET 8.0.snap
@@ -0,0 +1,4 @@
+{
+ "ExtraMembers": [],
+ "MissingMembers": []
+}
diff --git a/tests/TestableIO.System.IO.Abstractions.Wrappers.Tests/__snapshots__/ApiParityTests.FileVersionInfo_.NET Framework 4.6.2.snap b/tests/TestableIO.System.IO.Abstractions.Wrappers.Tests/__snapshots__/ApiParityTests.FileVersionInfo_.NET Framework 4.6.2.snap
new file mode 100644
index 000000000..7cb3b9523
--- /dev/null
+++ b/tests/TestableIO.System.IO.Abstractions.Wrappers.Tests/__snapshots__/ApiParityTests.FileVersionInfo_.NET Framework 4.6.2.snap
@@ -0,0 +1,4 @@
+{
+ "ExtraMembers": [],
+ "MissingMembers": []
+}
diff --git a/version.json b/version.json
index 081fbf157..8e4c8f245 100644
--- a/version.json
+++ b/version.json
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
- "version": "21.1",
+ "version": "21.2",
"assemblyVersion": {
"precision": "major"
},