Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/naming.md
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,7 @@ public static string NameWithParent(this Type type)
return type.Name;
}
```
<sup><a href='/src/Verify/Extensions.cs#L114-L126' title='Snippet source file'>snippet source</a> | <a href='#snippet-NameWithParent' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify/Extensions.cs#L123-L135' title='Snippet source file'>snippet source</a> | <a href='#snippet-NameWithParent' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Any path calculated in `DerivePathInfo` should be fully qualified to remove the inconsistency of the current directory.
Expand Down
36 changes: 18 additions & 18 deletions docs/temp-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public void Usage()
// file automatically deleted here
}
```
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L38-L50' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFile' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L87-L99' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFile' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand Down Expand Up @@ -62,7 +62,7 @@ public void PathProperty()
Assert.True(Path.IsPathRooted(path));
}
```
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L52-L62' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFilePathProperty' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L101-L111' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFilePathProperty' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand Down Expand Up @@ -91,7 +91,7 @@ public void StringConversion()
Trace.WriteLine(content);
}
```
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L344-L359' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileStringConversion' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L393-L408' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileStringConversion' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand All @@ -114,7 +114,7 @@ public void FileInfoConversion()
Trace.WriteLine(directoryName);
}
```
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L361-L375' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileFileInfoConversion' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L410-L424' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileFileInfoConversion' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand All @@ -135,7 +135,7 @@ public void InfoProperty()
Trace.WriteLine(directoryName);
}
```
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L377-L389' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileInfoProperty' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L426-L438' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileInfoProperty' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand All @@ -151,7 +151,7 @@ public void RootDirectory() =>
// Accessing the root directory for all TempDirectory instances
Trace.WriteLine(TempFile.RootDirectory);
```
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L114-L121' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileRootDirectory' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L163-L170' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileRootDirectory' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand Down Expand Up @@ -186,7 +186,7 @@ public async Task VerifyFileInstance()
await VerifyFile(file);
}
```
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L26-L36' title='Snippet source file'>snippet source</a> | <a href='#snippet-VerifyTempFile' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L75-L85' title='Snippet source file'>snippet source</a> | <a href='#snippet-VerifyTempFile' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand All @@ -207,7 +207,7 @@ public async Task Scrubbing()
});
}
```
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L96-L110' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileScrubbing' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L145-L159' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileScrubbing' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

Result:
Expand Down Expand Up @@ -237,7 +237,7 @@ File.WriteAllText(temp, "content");

// file automatically deleted here
```
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L620-L628' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileCreate' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L669-L677' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileCreate' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand All @@ -252,7 +252,7 @@ using var temp = TempFile.Create(".txt");

File.WriteAllText(temp, "content");
```
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L635-L641' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileCreateWithExtension' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L684-L690' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileCreateWithExtension' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand All @@ -267,7 +267,7 @@ using var temp = TempFile.Create(".txt", Encoding.UTF8);

File.Exists(temp.Path);
```
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L649-L655' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileCreateWithEncoding' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L698-L704' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileCreateWithEncoding' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand All @@ -283,7 +283,7 @@ using var temp = await TempFile.CreateText("Hello, World!");
var content = await File.ReadAllTextAsync(temp);
Assert.Equal("Hello, World!", content);
```
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L661-L668' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileCreateText' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L710-L717' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileCreateText' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand All @@ -304,7 +304,7 @@ using var temp = await TempFile.CreateText(json, ".json");
var content = await File.ReadAllTextAsync(temp);
Assert.Equal(json, content);
```
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L674-L688' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileCreateTextWithExtension' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L723-L737' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileCreateTextWithExtension' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand All @@ -323,7 +323,7 @@ using var temp = await TempFile.CreateText(
var content = await File.ReadAllTextAsync(temp, Encoding.UTF8);
Assert.Equal("Content with special chars: äöü", content);
```
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L694-L704' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileCreateTextWithEncoding' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L743-L753' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileCreateTextWithEncoding' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand All @@ -341,7 +341,7 @@ using var temp = await TempFile.CreateBinary(data);
var readData = await File.ReadAllBytesAsync(temp);
Assert.Equal(data, readData);
```
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L712-L721' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileCreateBinary' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L761-L770' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileCreateBinary' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand All @@ -353,7 +353,7 @@ Assert.Equal(data, readData);
byte[] data = [0x01, 0x02, 0x03, 0x04];
using var temp = await TempFile.CreateBinary(data, ".bin");
```
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L727-L732' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileCreateBinaryWithExtension' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L776-L781' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileCreateBinaryWithExtension' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand Down Expand Up @@ -384,7 +384,7 @@ public void NoUsing()
Debug.WriteLine(temp);
}
```
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L79-L92' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileNoUsing' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L128-L141' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileNoUsing' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

The file can then be manually inspected.
Expand All @@ -408,7 +408,7 @@ public void OpenExplorerAndDebug()
temp.OpenExplorerAndDebug();
}
```
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L64-L77' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileOpenExplorerAndDebug' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Verify.Tests/TempFileTests.cs#L113-L126' title='Snippet source file'>snippet source</a> | <a href='#snippet-TempFileOpenExplorerAndDebug' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

This method is designed to help debug tests by enabling the inspection of the contents of the temporary file while the test is paused. It performs two actions:
Expand Down
58 changes: 58 additions & 0 deletions src/Verify.Tests/CopyConstructorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
public class CopyConstructorTests
{
[Fact]
public void PreservesThrowException()
{
var settings = new VerifySettings();
settings.AutoVerify(throwException: true);

var copy = new VerifySettings(settings);

Assert.True(copy.throwException);
}

[Fact]
public void ClonesAppendedFiles()
{
var settings = new VerifySettings();
settings.AppendContentAsFile("base");

var copy = new VerifySettings(settings);
copy.AppendContentAsFile("extra");

// The base settings must not see the file appended to the copy.
Assert.Single(settings.appendedFiles!);
Assert.Equal(2, copy.appendedFiles!.Count);
}

[Fact]
public void ClonesExtensionStreamComparers()
{
var settings = new VerifySettings();
StreamCompare compare = (_, _, _) => Task.FromResult(CompareResult.Equal);
// Extensions unique to this test so the global comparer registry can't
// satisfy the lookup below.
settings.UseStreamComparer(compare, "copyctorext1");

var copy = new VerifySettings(settings);
copy.UseStreamComparer(compare, "copyctorext2");

// The comparer added to the copy must not leak into the base settings.
Assert.False(settings.TryFindStreamComparer("copyctorext2", out _));
Assert.True(copy.TryFindStreamComparer("copyctorext2", out _));
}

[Fact]
public void ClonesExtensionMappedScrubbers()
{
var settings = new VerifySettings();
settings.AddScrubber("txt", _ => { });

var copy = new VerifySettings(settings);
copy.AddScrubber("txt", _ => { });

// The inner list is shared unless deep-copied.
Assert.Single(settings.ExtensionMappedInstanceScrubbers!["txt"]);
Assert.Equal(2, copy.ExtensionMappedInstanceScrubbers!["txt"].Count);
}
}
40 changes: 40 additions & 0 deletions src/Verify.Tests/IoHelpersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,44 @@ public void ResolveDirectoryNameFromSourceFileTests(string sourceFile, string ex

Assert.Equal(expectedDirectory, IoHelpers.ResolveDirectoryFromSourceFile(sourceFile));
}

[Fact]
public async Task WriteStreamHandlesExclusiveFileStream()
{
using var source = new TempFile(".bin");
await File.WriteAllBytesAsync(source, [1, 2, 3, 4, 5, 6, 7, 8]);
var destination = Path.Combine(Path.GetTempPath(), $"WriteStream{Guid.NewGuid():N}.bin");
try
{
// Writable + FileShare.None means File.Copy cannot re-open the source
// by path on Windows; WriteStream must fall back to the handle.
using var stream = new FileStream(source, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
await IoHelpers.WriteStream(destination, stream);

Assert.True(File.Exists(destination));
Assert.Equal(8, new FileInfo(destination).Length);
}
finally
{
File.Delete(destination);
}
}

[Fact]
public void ExtensionForExtensionlessFileStream()
{
var directory = Path.Combine(Path.GetTempPath(), $"ExtTest{Guid.NewGuid():N}");
Directory.CreateDirectory(directory);
var path = Path.Combine(directory, "Dockerfile");
File.WriteAllText(path, "content");
try
{
using var stream = File.OpenRead(path);
Assert.Equal("noextension", stream.Extension());
}
finally
{
Directory.Delete(directory, true);
}
}
}
22 changes: 22 additions & 0 deletions src/Verify.Tests/TempDirectoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,28 @@ public void IgnoreLockedFiles_DefaultIsFalse()
}
}

[Fact]
public void CleanupDoesNotThrowForUndeletableDirectory()
{
var directoryPath = Path.Combine(TempDirectory.RootDirectory, Path.GetRandomFileName());
Directory.CreateDirectory(directoryPath);
var filePath = Path.Combine(directoryPath, "locked.txt");
File.WriteAllText(filePath, "content");
PreventDeletion(filePath, directoryPath);
Directory.SetLastWriteTime(directoryPath, DateTime.Now.AddDays(-2));
try
{
// An aged-out orphan that cannot be deleted must not fail the whole
// module-initializer cleanup.
TempDirectory.Cleanup();
}
finally
{
AllowDeletion(filePath, directoryPath);
Directory.Delete(directoryPath, true);
}
}

// On Windows, ReadOnly on a file prevents deletion.
// On Unix, file deletion is controlled by the parent directory's write permission.
static void PreventDeletion(string filePath, string directoryPath)
Expand Down
49 changes: 49 additions & 0 deletions src/Verify.Tests/TempFileTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,55 @@ public void Constructor_WithExtension_AppliesExtension()
Assert.EndsWith(".txt", temp.Path);
}

[Fact]
public void CleanupDoesNotThrowForUndeletableFile()
{
var filePath = Path.Combine(TempFile.RootDirectory, $"undeletable{Guid.NewGuid():N}.txt");
File.WriteAllText(filePath, "content");
// Age the file out before marking it read-only: on .NET Framework the
// timestamp cannot be set on a read-only file.
File.SetLastWriteTime(filePath, DateTime.Now.AddDays(-2));
// On Windows a read-only file cannot be deleted; on Unix deletion is
// governed by the directory, so the file is deletable there and this
// test is a no-op guard.
var readOnly = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
if (readOnly)
{
File.SetAttributes(filePath, FileAttributes.ReadOnly);
}

try
{
TempFile.Cleanup();
}
finally
{
if (readOnly)
{
File.SetAttributes(filePath, FileAttributes.Normal);
}

if (File.Exists(filePath))
{
File.Delete(filePath);
}
}
}

[Fact]
public async Task ConcurrentInstancesDoNotCorruptState()
{
// Establish the shared async-local path list, then hammer it from many
// child contexts that flow the same list in.
using var parent = new TempFile();
var tasks = Enumerable.Range(0, 200)
.Select(_ => Task.Run(() =>
{
using var temp = new TempFile();
}));
await Task.WhenAll(tasks);
}

#region VerifyTempFile

[Fact]
Expand Down
13 changes: 11 additions & 2 deletions src/Verify/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,17 @@ static class Extensions
public static bool IsNumeric(this Type type) =>
numericTypes.Contains(Nullable.GetUnderlyingType(type) ?? type);

public static string Extension(this FileStream file) =>
Path.GetExtension(file.Name)[1..];
public static string Extension(this FileStream file)
{
var extension = Path.GetExtension(file.Name);
if (extension.Length == 0)
{
// Matches the convention used for extension-less directory entries.
return "noextension";
}

return extension[1..];
}

public static Version MajorMinor(this Version version) =>
new(version.Major, version.Minor);
Expand Down
Loading
Loading