Skip to content

Commit bb4df6c

Browse files
authored
feat: add drive info expectations (#146)
This pull request adds comprehensive support for drive-level assertions to the testing library, both in documentation and implementation. It introduces new extension methods for asserting properties of `IDriveInfo` objects, such as drive existence, readiness, size, format, and more. The documentation is updated to demonstrate these new capabilities, and the codebase now includes several new files implementing these assertions. **Drive assertion support:** * Added `HasDrive` and `DoesNotHaveDrive` extension methods to `IFileSystem` for asserting the presence or absence of drives by name, with case-insensitive matching and `.Which` chaining to access `IDriveInfo` assertions. * Introduced a new partial class `DriveInfoExtensions` as the container for all drive-related assertion extensions. **Drive property assertions:** * Added extension methods to `IThat<IDriveInfo>` for asserting drive properties: - `IsReady` and `IsNotReady` to check drive readiness - `HasTotalSize`, `HasTotalFreeSpace`, and `HasAvailableFreeSpace` for size-related assertions - `HasDriveFormat`, `HasDriveType`, `HasName`, and `HasVolumeLabel` for format, type, name, and label assertions **Documentation updates:** * Expanded the `README.md` with a new "Drives" section, including usage examples for the new drive assertions and `.Which` chaining. * Updated documentation for `.Which` to include `HasDrive` in addition to `HasFile` and `HasDirectory`. These changes significantly enhance the ability to write expressive and comprehensive tests involving file system drives.
1 parent 0669d8a commit bb4df6c

29 files changed

Lines changed: 1631 additions & 3 deletions

README.md

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,42 @@ fileSystem.File.WriteAllText("foo/bar/my-file.txt", "some content");
7979
await That(fileSystem).HasDirectory("foo/bar").WithFiles(f => f.All().ComplyWith(x => x.HasContent("SOME CONTENT").IgnoringCase()));
8080
```
8181

82+
### Drives
83+
84+
You can verify that a drive is registered on the file system. Drives are
85+
matched by name (case-insensitive) against `IFileSystem.DriveInfo.GetDrives()`;
86+
UNC drives (which do not appear in `GetDrives()`) are not supported by
87+
`HasDrive`:
88+
89+
```csharp
90+
MockFileSystem fileSystem = new(o => o.SimulatingOperatingSystem(SimulationMode.Windows));
91+
fileSystem.WithDrive("D:", d => d.SetTotalSize(2048));
92+
93+
await That(fileSystem).HasDrive("D:\\");
94+
await That(fileSystem).DoesNotHaveDrive("Z:\\");
95+
```
96+
97+
`HasDrive` exposes a `.Which` property returning `IThat<IDriveInfo>`, so all
98+
`IDriveInfo` assertions can be chained directly:
99+
100+
```csharp
101+
await That(fileSystem).HasDrive("D:\\")
102+
.Which.HasTotalSize(2048).And.IsReady();
103+
```
104+
105+
You can also assert directly on `IDriveInfo` instances:
106+
107+
```csharp
108+
IDriveInfo driveInfo = fileSystem.DriveInfo.New("D:");
109+
110+
await That(driveInfo).HasAvailableFreeSpace(2048);
111+
await That(driveInfo).HasTotalSize(2048).And.HasTotalFreeSpace(2048);
112+
await That(driveInfo).HasDriveFormat("NTFS");
113+
await That(driveInfo).HasDriveType(System.IO.DriveType.Fixed);
114+
await That(driveInfo).HasName(driveInfo.Name).And.HasVolumeLabel(driveInfo.VolumeLabel);
115+
await That(driveInfo).IsReady();
116+
```
117+
82118
### IFileInfo / IDirectoryInfo as subjects
83119

84120
You can also assert directly on `IFileInfo` and `IDirectoryInfo` instances:
@@ -97,13 +133,14 @@ await That(dirInfo).HasDirectory("bar").Which.HasFile("my-file.txt");
97133

98134
### Bridging from the file-system chain via `.Which`
99135

100-
`HasFile` and `HasDirectory` expose a `.Which` property that returns the
101-
`IThat<IFileInfo>` / `IThat<IDirectoryInfo>` for the resolved entry, so the
102-
same assertions light up in both places:
136+
`HasFile`, `HasDirectory` and `HasDrive` expose a `.Which` property that returns
137+
the `IThat<IFileInfo>` / `IThat<IDirectoryInfo>` / `IThat<IDriveInfo>` for the
138+
resolved entry, so the same assertions light up in both places:
103139

104140
```csharp
105141
await That(fileSystem).HasFile("my-file.txt").Which.HasLength(12).And.HasContent("some content");
106142
await That(fileSystem).HasDirectory("logs").Which.IsEmpty();
143+
await That(fileSystem).HasDrive("D:\\").Which.IsReady().And.HasDriveFormat("NTFS");
107144
```
108145

109146
### IFileVersionInfo
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using System.IO.Abstractions;
2+
using System.Text;
3+
using aweXpect.Core;
4+
using aweXpect.Core.Constraints;
5+
using aweXpect.Results;
6+
using aweXpect.Testably.Helpers;
7+
8+
namespace aweXpect.Testably;
9+
10+
public static partial class DriveInfoExtensions
11+
{
12+
/// <summary>
13+
/// Verifies that the <see cref="IDriveInfo" /> has the <paramref name="expected" /> available free space in bytes.
14+
/// </summary>
15+
public static AndOrResult<IDriveInfo, IThat<IDriveInfo>> HasAvailableFreeSpace(this IThat<IDriveInfo> source,
16+
long expected)
17+
=> new(
18+
source.Get().ExpectationBuilder.AddConstraint((it, grammars)
19+
=> new HasAvailableFreeSpaceConstraint(it, grammars, expected)),
20+
source);
21+
22+
private sealed class HasAvailableFreeSpaceConstraint(string it, ExpectationGrammars grammars, long expected)
23+
: ConstraintResult.WithValue<IDriveInfo>(grammars),
24+
IValueConstraint<IDriveInfo>
25+
{
26+
private long _actualAvailableFreeSpace;
27+
28+
public ConstraintResult IsMetBy(IDriveInfo actual)
29+
{
30+
Actual = actual;
31+
if (actual is null)
32+
{
33+
Outcome = Outcome.Failure;
34+
return this;
35+
}
36+
37+
_actualAvailableFreeSpace = actual.AvailableFreeSpace;
38+
Outcome = _actualAvailableFreeSpace == expected ? Outcome.Success : Outcome.Failure;
39+
return this;
40+
}
41+
42+
protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null)
43+
=> stringBuilder.Append("has available free space ").Append(expected);
44+
45+
protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null)
46+
{
47+
if (Actual is null)
48+
{
49+
stringBuilder.Append(it).Append(" was <null>");
50+
}
51+
else
52+
{
53+
stringBuilder.Append(it).Append(" was ").Append(_actualAvailableFreeSpace);
54+
}
55+
}
56+
57+
protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null)
58+
=> stringBuilder.Append("does not have available free space ").Append(expected);
59+
60+
protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null)
61+
{
62+
if (Actual is null)
63+
{
64+
stringBuilder.Append(it).Append(" was <null>");
65+
}
66+
else
67+
{
68+
stringBuilder.Append(it).Append(" did");
69+
}
70+
}
71+
}
72+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System.IO.Abstractions;
2+
using System.Text;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using aweXpect.Core;
6+
using aweXpect.Core.Constraints;
7+
using aweXpect.Options;
8+
using aweXpect.Results;
9+
using aweXpect.Testably.Helpers;
10+
11+
namespace aweXpect.Testably;
12+
13+
public static partial class DriveInfoExtensions
14+
{
15+
/// <summary>
16+
/// Verifies that the <see cref="IDriveInfo" /> has the <paramref name="expected" /> drive format.
17+
/// </summary>
18+
public static StringEqualityTypeResult<IDriveInfo, IThat<IDriveInfo>> HasDriveFormat(this IThat<IDriveInfo> source,
19+
string expected)
20+
{
21+
StringEqualityOptions options = new();
22+
return new StringEqualityTypeResult<IDriveInfo, IThat<IDriveInfo>>(
23+
source.Get().ExpectationBuilder.AddConstraint((it, grammars)
24+
=> new HasDriveFormatConstraint(it, grammars, options, expected)),
25+
source,
26+
options);
27+
}
28+
29+
private sealed class HasDriveFormatConstraint(
30+
string it,
31+
ExpectationGrammars grammars,
32+
StringEqualityOptions options,
33+
string expected)
34+
: ConstraintResult.WithValue<IDriveInfo>(grammars),
35+
IAsyncConstraint<IDriveInfo>
36+
{
37+
private string? _actualDriveFormat;
38+
39+
public async Task<ConstraintResult> IsMetBy(IDriveInfo actual, CancellationToken cancellationToken)
40+
{
41+
Actual = actual;
42+
if (actual is null)
43+
{
44+
Outcome = Outcome.Failure;
45+
return this;
46+
}
47+
48+
_actualDriveFormat = actual.DriveFormat;
49+
Outcome = await options.AreConsideredEqual(_actualDriveFormat, expected) ? Outcome.Success : Outcome.Failure;
50+
return this;
51+
}
52+
53+
protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null)
54+
=> stringBuilder.Append("has drive format ").Append(options.GetExpectation(expected, Grammars));
55+
56+
protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null)
57+
{
58+
if (Actual is null)
59+
{
60+
stringBuilder.Append(it).Append(" was <null>");
61+
}
62+
else
63+
{
64+
stringBuilder.Append(options.GetExtendedFailure(it, Grammars, _actualDriveFormat, expected));
65+
}
66+
}
67+
68+
protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null)
69+
=> stringBuilder.Append("does not have drive format ").Append(options.GetExpectation(expected, Grammars));
70+
71+
protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null)
72+
{
73+
if (Actual is null)
74+
{
75+
stringBuilder.Append(it).Append(" was <null>");
76+
}
77+
else
78+
{
79+
stringBuilder.Append(it).Append(" did");
80+
}
81+
}
82+
}
83+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System.IO;
2+
using System.IO.Abstractions;
3+
using System.Text;
4+
using aweXpect.Core;
5+
using aweXpect.Core.Constraints;
6+
using aweXpect.Results;
7+
using aweXpect.Testably.Helpers;
8+
9+
namespace aweXpect.Testably;
10+
11+
public static partial class DriveInfoExtensions
12+
{
13+
/// <summary>
14+
/// Verifies that the <see cref="IDriveInfo" /> has the <paramref name="expected" /> <see cref="DriveType" />.
15+
/// </summary>
16+
public static AndOrResult<IDriveInfo, IThat<IDriveInfo>> HasDriveType(this IThat<IDriveInfo> source,
17+
DriveType expected)
18+
=> new(
19+
source.Get().ExpectationBuilder.AddConstraint((it, grammars)
20+
=> new HasDriveTypeConstraint(it, grammars, expected)),
21+
source);
22+
23+
private sealed class HasDriveTypeConstraint(string it, ExpectationGrammars grammars, DriveType expected)
24+
: ConstraintResult.WithValue<IDriveInfo>(grammars),
25+
IValueConstraint<IDriveInfo>
26+
{
27+
private DriveType _actualDriveType;
28+
29+
public ConstraintResult IsMetBy(IDriveInfo actual)
30+
{
31+
Actual = actual;
32+
if (actual is null)
33+
{
34+
Outcome = Outcome.Failure;
35+
return this;
36+
}
37+
38+
_actualDriveType = actual.DriveType;
39+
Outcome = _actualDriveType == expected ? Outcome.Success : Outcome.Failure;
40+
return this;
41+
}
42+
43+
protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null)
44+
=> stringBuilder.Append("has drive type ").Append(expected);
45+
46+
protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null)
47+
{
48+
if (Actual is null)
49+
{
50+
stringBuilder.Append(it).Append(" was <null>");
51+
}
52+
else
53+
{
54+
stringBuilder.Append(it).Append(" was ").Append(_actualDriveType);
55+
}
56+
}
57+
58+
protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null)
59+
=> stringBuilder.Append("does not have drive type ").Append(expected);
60+
61+
protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null)
62+
{
63+
if (Actual is null)
64+
{
65+
stringBuilder.Append(it).Append(" was <null>");
66+
}
67+
else
68+
{
69+
stringBuilder.Append(it).Append(" did");
70+
}
71+
}
72+
}
73+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System.IO.Abstractions;
2+
using System.Text;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using aweXpect.Core;
6+
using aweXpect.Core.Constraints;
7+
using aweXpect.Options;
8+
using aweXpect.Results;
9+
using aweXpect.Testably.Helpers;
10+
11+
namespace aweXpect.Testably;
12+
13+
public static partial class DriveInfoExtensions
14+
{
15+
/// <summary>
16+
/// Verifies that the <see cref="IDriveInfo" /> has the <paramref name="expected" /> name.
17+
/// </summary>
18+
public static StringEqualityTypeResult<IDriveInfo, IThat<IDriveInfo>> HasName(this IThat<IDriveInfo> source,
19+
string expected)
20+
{
21+
StringEqualityOptions options = new();
22+
return new StringEqualityTypeResult<IDriveInfo, IThat<IDriveInfo>>(
23+
source.Get().ExpectationBuilder.AddConstraint((it, grammars)
24+
=> new HasNameConstraint(it, grammars, options, expected)),
25+
source,
26+
options);
27+
}
28+
29+
private sealed class HasNameConstraint(
30+
string it,
31+
ExpectationGrammars grammars,
32+
StringEqualityOptions options,
33+
string expected)
34+
: ConstraintResult.WithValue<IDriveInfo>(grammars),
35+
IAsyncConstraint<IDriveInfo>
36+
{
37+
private string? _actualName;
38+
39+
public async Task<ConstraintResult> IsMetBy(IDriveInfo actual, CancellationToken cancellationToken)
40+
{
41+
Actual = actual;
42+
if (actual is null)
43+
{
44+
Outcome = Outcome.Failure;
45+
return this;
46+
}
47+
48+
_actualName = actual.Name;
49+
Outcome = await options.AreConsideredEqual(_actualName, expected) ? Outcome.Success : Outcome.Failure;
50+
return this;
51+
}
52+
53+
protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null)
54+
=> stringBuilder.Append("has name ").Append(options.GetExpectation(expected, Grammars));
55+
56+
protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null)
57+
{
58+
if (Actual is null)
59+
{
60+
stringBuilder.Append(it).Append(" was <null>");
61+
}
62+
else
63+
{
64+
stringBuilder.Append(options.GetExtendedFailure(it, Grammars, _actualName, expected));
65+
}
66+
}
67+
68+
protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null)
69+
=> stringBuilder.Append("does not have name ").Append(options.GetExpectation(expected, Grammars));
70+
71+
protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null)
72+
{
73+
if (Actual is null)
74+
{
75+
stringBuilder.Append(it).Append(" was <null>");
76+
}
77+
else
78+
{
79+
stringBuilder.Append(it).Append(" did");
80+
}
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)