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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
First
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Second
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
First
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Second
27 changes: 27 additions & 0 deletions src/Verify.Tests/Converters/InstanceFileAppenderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,31 @@ public Task TextBytesFluent() =>
public Task TextStreamFluent() =>
Verify("Foo")
.AppendFile(new MemoryStream("appendedFile"u8.ToArray()));

// The engine disposes the stream of every target it writes, so an appended binary file
// held as a live stream was dead after the first verification. Both of these are backed
// by something re-readable, so reusing the settings has to work.
[Fact]
public async Task BinaryBytesSettingsReuse()
{
var reused = new VerifySettings();
reused.AppendContentAsFile(new byte[] {1, 2, 3}, "bin", "appendedBytes");

await Verify("First", reused)
.UseMethodName("BinaryBytesSettingsReuse_first");
await Verify("Second", reused)
.UseMethodName("BinaryBytesSettingsReuse_second");
}

[Fact]
public async Task AppendFileSettingsReuse()
{
var reused = new VerifySettings();
reused.AppendFile("sample.png");

await Verify("First", reused)
.UseMethodName("AppendFileSettingsReuse_first");
await Verify("Second", reused)
.UseMethodName("AppendFileSettingsReuse_second");
}
}
13 changes: 13 additions & 0 deletions src/Verify/Guards.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ public static void BadParametersText(string value, [CallerArgumentExpression(nam
}
}

/// <summary>
/// Kept as a fail fast for APIs that record a path and read it later, so a missing
/// file is reported where the caller passed it rather than at verification time.
/// </summary>
public static void FileExists(string path, [CallerArgumentExpression(nameof(path))] string argumentName = "")
{
Ensure.NotNullOrEmpty(path, argumentName);
if (!File.Exists(path))
{
throw new FileNotFoundException($"File not found. Path: {path}", path);
}
}

static char[] invalidPathChars = Path
.GetInvalidPathChars()
.Concat(invalidFileChars.Except(['/', '\\', ':']))
Expand Down
60 changes: 49 additions & 11 deletions src/Verify/Splitters/Settings_FileAppender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ internal static IEnumerable<Target> GetFileAppenders(VerifySettings settings)

if (settings.appendedFiles != null)
{
foreach (var target in settings.appendedFiles)
foreach (var buildTarget in settings.appendedFiles)
{
yield return target;
yield return buildTarget();
}
}
}
Expand All @@ -37,54 +37,92 @@ public static void RegisterFileAppender(FileAppender appender)

public partial class VerifySettings
{
internal List<Target>? appendedFiles;
/// <summary>
/// Built once per verification rather than held as Targets. The engine disposes the
/// stream of every target it writes, so a stored stream is dead after the first
/// verification, and settings are reused: SettingsTask copies them per Verify call.
/// </summary>
internal List<Func<Target>>? appendedFiles;

public void AppendContentAsFile(string content, string extension = "txt", string? name = null)
{
appendedFiles ??= [];
appendedFiles.Add(new(extension, content, name));
appendedFiles.Add(() => new(extension, content, name));
}

public void AppendContentAsFile(StringBuilder content, string extension = "txt", string? name = null)
{
appendedFiles ??= [];
appendedFiles.Add(new(extension, content, name));
appendedFiles.Add(() => new(extension, content, name));
}

public void AppendContentAsFile(byte[] content, string extension = "txt", string? name = null)
{
appendedFiles ??= [];
if (FileExtensions.IsTextExtension(extension))
{
appendedFiles.Add(new(extension, Encoding.UTF8.GetString(content), name));
var text = Encoding.UTF8.GetString(content);
appendedFiles.Add(() => new(extension, text, name));
}
else
{
appendedFiles.Add(new(extension, new MemoryStream(content), name));
// A fresh stream per verification: the bytes stay re-readable, so reusing the
// settings for a second Verify works.
appendedFiles.Add(() => new(extension, new MemoryStream(content), name));
}
}

public void AppendFile(string file, string? name = null) =>
AppendFile(IoHelpers.OpenRead(file), name);
public void AppendFile(string file, string? name = null)
{
// Opened per verification rather than held open from here, for the same reason,
// and so the handle is not held for the lifetime of the settings.
Guards.FileExists(file);
var extension = Path.GetExtension(file);
extension = extension.Length == 0 ? "noextension" : extension[1..];
AppendFile(() => IoHelpers.OpenRead(file), extension, name ?? Path.GetFileNameWithoutExtension(file));
}

public void AppendFile(FileInfo file, string? name = null) =>
AppendFile(file.FullName, name);

public void AppendFile(FileStream stream, string? name = null) =>
AppendFile(stream, stream.Extension(), name ?? Path.GetFileNameWithoutExtension(stream.Name));

/// <remarks>
/// The stream is owned by the caller and can only be read once, so unlike the other
/// overloads this one cannot be replayed for a second verification with the same
/// settings. Use <see cref="AppendFile(string,string?)" /> or
/// <see cref="AppendContentAsFile(byte[],string,string?)" /> where that matters.
/// </remarks>
public void AppendFile(Stream stream, string extension = "txt", string? name = null)
{
stream.MoveToStart();
appendedFiles ??= [];
if (FileExtensions.IsTextExtension(extension))
{
using var reader = new StreamReader(stream, Encoding.UTF8);
appendedFiles.Add(new(extension, reader.ReadToEnd(), name));
var text = reader.ReadToEnd();
appendedFiles.Add(() => new(extension, text, name));
}
else
{
appendedFiles.Add(() => new(extension, stream, name));
}
}

void AppendFile(Func<Stream> openStream, string extension, string? name)
{
appendedFiles ??= [];
if (FileExtensions.IsTextExtension(extension))
{
using var stream = openStream();
using var reader = new StreamReader(stream, Encoding.UTF8);
var text = reader.ReadToEnd();
appendedFiles.Add(() => new(extension, text, name));
}
else
{
appendedFiles.Add(new(extension, stream, name));
appendedFiles.Add(() => new(extension, openStream(), name));
}
}
}
Expand Down
Loading