diff --git a/src/Verify.Tests/Converters/InstanceFileAppenderTests.AppendFileSettingsReuse_first#sample.verified.png b/src/Verify.Tests/Converters/InstanceFileAppenderTests.AppendFileSettingsReuse_first#sample.verified.png
new file mode 100644
index 0000000000..b54820aa60
Binary files /dev/null and b/src/Verify.Tests/Converters/InstanceFileAppenderTests.AppendFileSettingsReuse_first#sample.verified.png differ
diff --git a/src/Verify.Tests/Converters/InstanceFileAppenderTests.AppendFileSettingsReuse_first.verified.txt b/src/Verify.Tests/Converters/InstanceFileAppenderTests.AppendFileSettingsReuse_first.verified.txt
new file mode 100644
index 0000000000..ca25d6d7b1
--- /dev/null
+++ b/src/Verify.Tests/Converters/InstanceFileAppenderTests.AppendFileSettingsReuse_first.verified.txt
@@ -0,0 +1 @@
+First
\ No newline at end of file
diff --git a/src/Verify.Tests/Converters/InstanceFileAppenderTests.AppendFileSettingsReuse_second#sample.verified.png b/src/Verify.Tests/Converters/InstanceFileAppenderTests.AppendFileSettingsReuse_second#sample.verified.png
new file mode 100644
index 0000000000..b54820aa60
Binary files /dev/null and b/src/Verify.Tests/Converters/InstanceFileAppenderTests.AppendFileSettingsReuse_second#sample.verified.png differ
diff --git a/src/Verify.Tests/Converters/InstanceFileAppenderTests.AppendFileSettingsReuse_second.verified.txt b/src/Verify.Tests/Converters/InstanceFileAppenderTests.AppendFileSettingsReuse_second.verified.txt
new file mode 100644
index 0000000000..ad4facd1db
--- /dev/null
+++ b/src/Verify.Tests/Converters/InstanceFileAppenderTests.AppendFileSettingsReuse_second.verified.txt
@@ -0,0 +1 @@
+Second
\ No newline at end of file
diff --git a/src/Verify.Tests/Converters/InstanceFileAppenderTests.BinaryBytesSettingsReuse_first#appendedBytes.verified.bin b/src/Verify.Tests/Converters/InstanceFileAppenderTests.BinaryBytesSettingsReuse_first#appendedBytes.verified.bin
new file mode 100644
index 0000000000..aed2973e4b
--- /dev/null
+++ b/src/Verify.Tests/Converters/InstanceFileAppenderTests.BinaryBytesSettingsReuse_first#appendedBytes.verified.bin
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/Verify.Tests/Converters/InstanceFileAppenderTests.BinaryBytesSettingsReuse_first.verified.txt b/src/Verify.Tests/Converters/InstanceFileAppenderTests.BinaryBytesSettingsReuse_first.verified.txt
new file mode 100644
index 0000000000..ca25d6d7b1
--- /dev/null
+++ b/src/Verify.Tests/Converters/InstanceFileAppenderTests.BinaryBytesSettingsReuse_first.verified.txt
@@ -0,0 +1 @@
+First
\ No newline at end of file
diff --git a/src/Verify.Tests/Converters/InstanceFileAppenderTests.BinaryBytesSettingsReuse_second#appendedBytes.verified.bin b/src/Verify.Tests/Converters/InstanceFileAppenderTests.BinaryBytesSettingsReuse_second#appendedBytes.verified.bin
new file mode 100644
index 0000000000..aed2973e4b
--- /dev/null
+++ b/src/Verify.Tests/Converters/InstanceFileAppenderTests.BinaryBytesSettingsReuse_second#appendedBytes.verified.bin
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/Verify.Tests/Converters/InstanceFileAppenderTests.BinaryBytesSettingsReuse_second.verified.txt b/src/Verify.Tests/Converters/InstanceFileAppenderTests.BinaryBytesSettingsReuse_second.verified.txt
new file mode 100644
index 0000000000..ad4facd1db
--- /dev/null
+++ b/src/Verify.Tests/Converters/InstanceFileAppenderTests.BinaryBytesSettingsReuse_second.verified.txt
@@ -0,0 +1 @@
+Second
\ No newline at end of file
diff --git a/src/Verify.Tests/Converters/InstanceFileAppenderTests.cs b/src/Verify.Tests/Converters/InstanceFileAppenderTests.cs
index 92aaf8ffd6..7d71760a6c 100644
--- a/src/Verify.Tests/Converters/InstanceFileAppenderTests.cs
+++ b/src/Verify.Tests/Converters/InstanceFileAppenderTests.cs
@@ -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");
+ }
}
\ No newline at end of file
diff --git a/src/Verify/Guards.cs b/src/Verify/Guards.cs
index d9a286e724..4369d7786d 100644
--- a/src/Verify/Guards.cs
+++ b/src/Verify/Guards.cs
@@ -28,6 +28,19 @@ public static void BadParametersText(string value, [CallerArgumentExpression(nam
}
}
+ ///
+ /// 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.
+ ///
+ 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(['/', '\\', ':']))
diff --git a/src/Verify/Splitters/Settings_FileAppender.cs b/src/Verify/Splitters/Settings_FileAppender.cs
index e4bf1581d5..e2a28e491d 100644
--- a/src/Verify/Splitters/Settings_FileAppender.cs
+++ b/src/Verify/Splitters/Settings_FileAppender.cs
@@ -20,9 +20,9 @@ internal static IEnumerable 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();
}
}
}
@@ -37,18 +37,23 @@ public static void RegisterFileAppender(FileAppender appender)
public partial class VerifySettings
{
- internal List? appendedFiles;
+ ///
+ /// 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.
+ ///
+ internal List>? 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)
@@ -56,16 +61,26 @@ public void AppendContentAsFile(byte[] content, string extension = "txt", string
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);
@@ -73,6 +88,12 @@ public void AppendFile(FileInfo file, string? name = null) =>
public void AppendFile(FileStream stream, string? name = null) =>
AppendFile(stream, stream.Extension(), name ?? Path.GetFileNameWithoutExtension(stream.Name));
+ ///
+ /// 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 or
+ /// where that matters.
+ ///
public void AppendFile(Stream stream, string extension = "txt", string? name = null)
{
stream.MoveToStart();
@@ -80,11 +101,28 @@ public void AppendFile(Stream stream, string extension = "txt", string? name = n
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 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));
}
}
}