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
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,9 @@ public class DocExampleCompilationTests

public static IEnumerable<object[]> AllDocExamples()
{
// A missing XML doc file (GenerateDocumentationFile off) throws
// FileNotFoundException from Load, naming the path.
string xmlPath = Path.ChangeExtension(LibraryAssembly.Location, ".xml");
if (!File.Exists(xmlPath))
{
// Missing XML doc: repo-level GenerateDocumentationFile is required.
// Test harness will surface this as "no examples found".
yield break;
}

XDocument doc = XDocument.Load(xmlPath);
int index = 0;
foreach (XElement member in doc.Descendants("member"))
Expand Down Expand Up @@ -148,14 +143,19 @@ private static void Process(int item) { }
.Where(d => d.Severity == DiagnosticSeverity.Error)
.ToList();

if (errors.Count > 0)
{
Assert.Fail(CompileFailureMessage.Format(
// Unconditional so the message builder runs on a pass too (a
// failure-only branch can never reach the 100% test-coverage gate).
Assert.True
(
errors.Count == 0,
CompileFailureMessage.Format
(
$"XML doc example on `{memberName}` (example index {index}) failed to compile.",
errors,
snippet,
"Fix the snippet in the source XML doc, or if it needs an additional using, add it inline in the `<code>` block."));
}
"Fix the snippet in the source XML doc, or if it needs an additional using, add it inline in the `<code>` block."
)
);
}
}

Expand Down
15 changes: 5 additions & 10 deletions tests/Wolfgang.TryPattern.Tests.Unit/PropertyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,13 @@ public Property Try_Run_of_non_throwing_action_always_succeeds()
public Property Try_Run_of_throwing_action_carries_message_round_trip(NonEmptyString message)
{
if (message is null) throw new ArgumentNullException(nameof(message));
// NonEmptyString excludes only the empty string; Result.Failure
// additionally rejects whitespace-only strings and there's a
// latent bug (#273) that lets a whitespace exception message
// escape from Try.Run. Skip that case so the fast-path
// property is well-formed vs the current API.
// NonEmptyString can still be whitespace-only. Since #273 Try.Run
// reports such a message as the exception's type name, because
// Result.Failure rejects whitespace.
string msg = message.Get;
if (string.IsNullOrWhiteSpace(msg))
{
return true.ToProperty();
}
string expected = string.IsNullOrWhiteSpace(msg) ? nameof(InvalidOperationException) : msg;
Result r = Try.Run(() => throw new InvalidOperationException(msg));
return (r.Failed && string.Equals(r.ErrorMessage, msg, StringComparison.Ordinal)).ToProperty();
return (r.Failed && string.Equals(r.ErrorMessage, expected, StringComparison.Ordinal)).ToProperty();
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,9 @@ public class ReadmeExampleCompilationTests

public static IEnumerable<object[]> AllReadmeFences()
{
// LocateReadme returns a non-existent path when nothing is found,
// so ReadAllText throws FileNotFoundException naming it.
string readmePath = LocateReadme();
if (!File.Exists(readmePath))
{
yield break;
}

string content = File.ReadAllText(readmePath);
// Matches ```csharp\n...content...\n``` — the ``` opener/closer
// must be at the start of its own line. `Singleline` lets `.`
Expand Down Expand Up @@ -160,14 +157,19 @@ private static void Process(int item) { }
.Where(d => d.Severity == DiagnosticSeverity.Error)
.ToList();

if (errors.Count > 0)
{
Assert.Fail(CompileFailureMessage.Format(
// Unconditional so the message builder runs on a pass too (a
// failure-only branch can never reach the 100% test-coverage gate).
Assert.True
(
errors.Count == 0,
CompileFailureMessage.Format
(
$"README fence #{index} failed to compile.",
errors,
snippet,
"Fix the fence in README.md, or if it uses framework types the test project doesn't reference (SqlConnection, ASP.NET Core, EF dbContext), add a marker string to ReadmeExampleCompilationTests.SkipMarkers."));
}
"Fix the fence in README.md, or if it uses framework types the test project doesn't reference (SqlConnection, ASP.NET Core, EF dbContext), add a marker string to ReadmeExampleCompilationTests.SkipMarkers."
)
);
}


Expand All @@ -177,16 +179,11 @@ private static string LocateReadme()
// README.md. On CI the test runs from bin/Release/net10.0/,
// so the repo root is a few levels up.
string? dir = AppContext.BaseDirectory;
for (int depth = 0; depth < 10 && dir is not null; depth++)
while (dir is not null && !File.Exists(Path.Combine(dir, "README.md")))
{
string candidate = Path.Combine(dir, "README.md");
if (File.Exists(candidate))
{
return candidate;
}
dir = Path.GetDirectoryName(dir);
}
return "README.md"; // last-ditch, caller checks Exists
return Path.Combine(dir ?? AppContext.BaseDirectory, "README.md");
}
}

Expand Down
20 changes: 17 additions & 3 deletions tests/Wolfgang.TryPattern.Tests.Unit/RunAsyncFuncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -339,11 +339,15 @@ public async Task RunAsync_Func_CancellationToken_int_when_cancellation_is_reque
using var cts = new CancellationTokenSource();
var functionStarted = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);

async Task<int> Function()
// Never completes on its own: the only way out is the token, so
// there is no unreachable `return` after an infinite delay.
var pending = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
using var registration = cts.Token.Register(() => pending.TrySetCanceled(cts.Token));

Task<int> Function()
{
functionStarted.TrySetResult(true);
await Task.Delay(Timeout.Infinite, cts.Token);
return 42;
return pending.Task;
}

var task = Try.RunAsync((Func<Task<int>>)Function, cts.Token);
Expand Down Expand Up @@ -374,6 +378,11 @@ Task<int> Function()
// Act & Assert
await Assert.ThrowsAsync<OperationCanceledException>(() => Try.RunAsync((Func<Task<int>>)Function, cts.Token));
Assert.False(wasInvoked, "function should not be invoked when the token is already canceled");

// Positive control: the probe does flip when the function runs, so
// the assertion above is not vacuous.
await Function();
Assert.True(wasInvoked);
}


Expand All @@ -395,5 +404,10 @@ public async Task RunAsync_Func_nullable_when_token_is_already_canceled_throws_O
// Act & Assert
await Assert.ThrowsAsync<OperationCanceledException>(() => Try.RunAsync((Func<Task<int?>>)Function, cts.Token));
Assert.False(wasInvoked, "function should not be invoked when the token is already canceled");

// Positive control: the probe does flip when the function runs, so
// the assertion above is not vacuous.
await Function();
Assert.True(wasInvoked);
}
}
Loading