Skip to content

Commit

Permalink
feat: add testability for drives
Browse files Browse the repository at this point in the history
  • Loading branch information
FantasyTeddy committed Mar 8, 2024
1 parent 71f534c commit 9030f59
Show file tree
Hide file tree
Showing 9 changed files with 395 additions and 145 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ public interface IMockFileDataAccessor : IFileSystem
/// <returns>The file. <see langword="null"/> if the file does not exist.</returns>
MockFileData GetFile(string path);

/// <summary>
/// Gets a drive.
/// </summary>
/// <param name="name">The name of the drive to get.</param>
/// <returns>The drive. <see langword="null"/> if the drive does not exist.</returns>
MockDriveData GetDrive(string name);

/// <summary>
/// </summary>
void AddFile(string path, MockFileData mockFile);
Expand All @@ -31,6 +38,10 @@ public interface IMockFileDataAccessor : IFileSystem
/// </summary>
void AddDirectory(string path);

/// <summary>
/// </summary>
void AddDrive(string name, MockDriveData mockDrive);

/// <summary>
/// </summary>
void AddFileFromEmbeddedResource(string path, Assembly resourceAssembly, string embeddedResourcePath);
Expand Down Expand Up @@ -74,6 +85,11 @@ public interface IMockFileDataAccessor : IFileSystem
/// </summary>
IEnumerable<string> AllDirectories { get; }

/// <summary>
/// Gets the names of all drives.
/// </summary>
IEnumerable<string> AllDrives { get; }

/// <summary>
/// Gets a helper for string operations.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@

namespace System.IO.Abstractions.TestingHelpers
{
/// <summary>
/// The class represents the associated data of a drive.
/// </summary>
#if FEATURE_SERIALIZABLE
[Serializable]
#endif
public class MockDriveData
{
/// <summary>
/// Initializes a new instance of the <see cref="MockDriveData"/> class.
/// </summary>
public MockDriveData()
{
IsReady = true;
}

/// <summary>
/// Initializes a new instance of the <see cref="MockDriveData"/> class by copying the given <see cref="MockDriveData"/>.
/// </summary>
/// <param name="template">The template instance.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="template"/> is <see langword="null"/>.</exception>
public MockDriveData(MockDriveData template)
{
if (template == null)
{
throw new ArgumentNullException(nameof(template));
}

AvailableFreeSpace = template.AvailableFreeSpace;
DriveFormat = template.DriveFormat;
DriveType = template.DriveType;
IsReady = template.IsReady;
TotalFreeSpace = template.TotalFreeSpace;
TotalSize = template.TotalSize;
VolumeLabel = template.VolumeLabel;
}

/// <summary>
/// Gets or sets the amount of available free space of the <see cref="MockDriveData"/>, in bytes.
/// </summary>
public long AvailableFreeSpace { get; set; }

/// <summary>
/// Gets or sets the name of the file system of the <see cref="MockDriveData"/>, such as NTFS or FAT32.
/// </summary>
public string DriveFormat { get; set; }

/// <summary>
/// Gets or sets the drive type of the <see cref="MockDriveData"/>, such as CD-ROM, removable, network, or fixed.
/// </summary>
public DriveType DriveType { get; set; }

/// <summary>
/// Gets or sets the value that indicates whether the <see cref="MockDriveData"/> is ready.
/// </summary>
public bool IsReady { get; set; }

/// <summary>
/// Gets or sets the total amount of free space available on the <see cref="MockDriveData"/>, in bytes.
/// </summary>
public long TotalFreeSpace { get; set; }

/// <summary>
/// Gets or sets the total size of storage space on the <see cref="MockDriveData"/>, in bytes.
/// </summary>
public long TotalSize { get; set; }

/// <summary>
/// Gets or sets the volume label of the <see cref="MockDriveData"/>.
/// </summary>
public string VolumeLabel { get; set; }
}
}
109 changes: 76 additions & 33 deletions src/TestableIO.System.IO.Abstractions.TestingHelpers/MockDriveInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,51 +7,60 @@
public class MockDriveInfo : DriveInfoBase
{
private readonly IMockFileDataAccessor mockFileDataAccessor;
private readonly string name;

/// <inheritdoc />
public MockDriveInfo(IMockFileDataAccessor mockFileDataAccessor, string name) : base(mockFileDataAccessor?.FileSystem)
{
this.mockFileDataAccessor = mockFileDataAccessor ?? throw new ArgumentNullException(nameof(mockFileDataAccessor));
this.name = mockFileDataAccessor.PathVerifier.NormalizeDriveName(name);
}

if (name == null)
/// <inheritdoc />
public override long AvailableFreeSpace
{
get
{
throw new ArgumentNullException(nameof(name));
var mockDriveData = GetMockDriveData();
return mockDriveData.AvailableFreeSpace;
}
}

const string DRIVE_SEPARATOR = @":\";

if (name.Length == 1
|| (name.Length == 2 && name[1] == ':')
|| (name.Length == 3 && mockFileDataAccessor.StringOperations.EndsWith(name, DRIVE_SEPARATOR)))
/// <inheritdoc />
public override string DriveFormat
{
get
{
name = name[0] + DRIVE_SEPARATOR;
var mockDriveData = GetMockDriveData();
return mockDriveData.DriveFormat;
}
else
{
mockFileDataAccessor.PathVerifier.CheckInvalidPathChars(name);
name = mockFileDataAccessor.Path.GetPathRoot(name);
}

if (string.IsNullOrEmpty(name) || mockFileDataAccessor.StringOperations.StartsWith(name, @"\\"))
{
throw new ArgumentException(
@"Object must be a root directory (""C:\"") or a drive letter (""C"").");
}
/// <inheritdoc />
public override DriveType DriveType
{
get
{
var mockDriveData = GetMockDriveData();
return mockDriveData.DriveType;
}

Name = name;
IsReady = true;
}

/// <inheritdoc />
public new long AvailableFreeSpace { get; set; }
/// <inheritdoc />
public new string DriveFormat { get; set; }
/// <inheritdoc />
public new DriveType DriveType { get; set; }
/// <inheritdoc />
public new bool IsReady { get; protected set; }
public override bool IsReady
{
get
{
var mockDriveData = GetMockDriveData();
return mockDriveData.IsReady;
}
}

/// <inheritdoc />
public override string Name { get; protected set; }
public override string Name
{
get { return name; }
}

/// <inheritdoc />
public override IDirectoryInfo RootDirectory
Expand All @@ -63,16 +72,50 @@ public override IDirectoryInfo RootDirectory
}

/// <inheritdoc />
public override string ToString()
public override long TotalFreeSpace
{
return Name;
get
{
var mockDriveData = GetMockDriveData();
return mockDriveData.TotalFreeSpace;
}
}

/// <inheritdoc />
public new long TotalFreeSpace { get; protected set; }
public override long TotalSize
{
get
{
var mockDriveData = GetMockDriveData();
return mockDriveData.TotalSize;
}
}

/// <inheritdoc />
public new long TotalSize { get; protected set; }
public override string VolumeLabel
{
get
{
var mockDriveData = GetMockDriveData();
return mockDriveData.VolumeLabel;
}
set
{
var mockDriveData = GetMockDriveData();
mockDriveData.VolumeLabel = value;
}
}

/// <inheritdoc />
public override string VolumeLabel { get; set; }
public override string ToString()
{
return Name;
}

private MockDriveData GetMockDriveData()
{
return mockFileDataAccessor.GetDrive(name)
?? throw CommonExceptions.FileNotFound(name);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,8 @@ public IFileSystem FileSystem
/// <inheritdoc />
public IDriveInfo[] GetDrives()
{
var driveLetters = new HashSet<string>(new DriveEqualityComparer(mockFileSystem));
foreach (var path in mockFileSystem.AllPaths)
{
var pathRoot = mockFileSystem.Path.GetPathRoot(path);
driveLetters.Add(pathRoot);
}

var result = new List<DriveInfoBase>();
foreach (string driveLetter in driveLetters)
foreach (string driveLetter in mockFileSystem.AllDrives)
{
try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class MockFileSystem : FileSystemBase, IMockFileDataAccessor
private const string TEMP_DIRECTORY = @"C:\temp";

private readonly IDictionary<string, FileSystemEntry> files;
private readonly IDictionary<string, MockDriveData> drives;
private readonly PathVerifier pathVerifier;
#if FEATURE_SERIALIZABLE
[NonSerialized]
Expand Down Expand Up @@ -58,6 +59,7 @@ public MockFileSystem(IDictionary<string, MockFileData> files, MockFileSystemOpt
StringOperations = new StringOperations(XFS.IsUnixPlatform());
pathVerifier = new PathVerifier(this);
this.files = new Dictionary<string, FileSystemEntry>(StringOperations.Comparer);
drives = new Dictionary<string, MockDriveData>(StringOperations.Comparer);

Path = new MockPath(this, defaultTempDirectory);
File = new MockFile(this);
Expand Down Expand Up @@ -188,6 +190,16 @@ public MockFileData GetFile(string path)
return GetFileWithoutFixingPath(path);
}

/// <inheritdoc />
public MockDriveData GetDrive(string name)
{
name = PathVerifier.NormalizeDriveName(name);
lock (drives)
{
return drives.TryGetValue(name, out var result) ? result : null;
}
}

private void SetEntry(string path, MockFileData mockFile)
{
path = FixPath(path, true).TrimSlashes();
Expand Down Expand Up @@ -322,6 +334,16 @@ public void AddDirectory(string path)
var s = StringOperations.EndsWith(fixedPath, separator) ? fixedPath : fixedPath + separator;
SetEntry(s, new MockDirectoryData());
}

lock (drives)
{
var driveLetter = Path.GetPathRoot(fixedPath);

if (!drives.ContainsKey(driveLetter))
{
drives[driveLetter] = new MockDriveData();
}
}
}

/// <inheritdoc />
Expand Down Expand Up @@ -359,6 +381,16 @@ public void AddFilesFromEmbeddedNamespace(string path, Assembly resourceAssembly
}
}

/// <inheritdoc />
public void AddDrive(string name, MockDriveData mockDrive)
{
name = PathVerifier.NormalizeDriveName(name);
lock (drives)
{
drives[name] = mockDrive;
}
}

/// <inheritdoc />
public void MoveDirectory(string sourcePath, string destPath)
{
Expand Down Expand Up @@ -483,6 +515,18 @@ public IEnumerable<string> AllDirectories
}
}

/// <inheritdoc />
public IEnumerable<string> AllDrives
{
get
{
lock (drives)
{
return drives.Keys.ToArray();
}
}
}

[OnDeserializing]
private void OnDeserializing(StreamingContext c)
{
Expand Down
Loading

0 comments on commit 9030f59

Please sign in to comment.