@@ -0,0 +1,173 @@
|
||||
namespace Raliway.Tests.ReflectionHelper;
|
||||
|
||||
// --- Test types ---
|
||||
|
||||
file sealed record EquatableRecord(string Name, int Value);
|
||||
|
||||
file sealed class NonEquatableClass
|
||||
{
|
||||
public string Name { get; }
|
||||
public NonEquatableClass(string name) => Name = name;
|
||||
public override bool Equals(object? obj) => obj is NonEquatableClass other && Name == other.Name;
|
||||
public override int GetHashCode() => Name.GetHashCode();
|
||||
}
|
||||
|
||||
file readonly struct EquatableStruct : IEquatable<EquatableStruct>
|
||||
{
|
||||
public int Value { get; }
|
||||
public EquatableStruct(int value) => Value = value;
|
||||
public bool Equals(EquatableStruct other) => Value == other.Value;
|
||||
public override bool Equals(object? obj) => obj is EquatableStruct s && Equals(s);
|
||||
public override int GetHashCode() => Value;
|
||||
}
|
||||
|
||||
// --- IsEqual tests ---
|
||||
|
||||
public class IsEqual_Tests
|
||||
{
|
||||
public class ValueType_IEquatable
|
||||
{
|
||||
[Fact] public void Equal_values() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual(42, 42).ShouldBeTrue();
|
||||
|
||||
[Fact] public void Different_values() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual(42, 43).ShouldBeFalse();
|
||||
|
||||
[Fact] public void Null_args() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual(default(int), default(int)).ShouldBeTrue();
|
||||
}
|
||||
|
||||
public class NullableValueType_IEquatable
|
||||
{
|
||||
[Fact] public void Both_have_same_value() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual<int?>(42, 42).ShouldBeTrue();
|
||||
|
||||
[Fact] public void Different_values() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual<int?>(42, 43).ShouldBeFalse();
|
||||
|
||||
[Fact] public void Both_null() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual<int?>(null, null).ShouldBeTrue();
|
||||
|
||||
[Fact] public void One_null_one_value() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual<int?>(42, null).ShouldBeFalse();
|
||||
|
||||
[Fact] public void Null_and_value_reversed() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual<int?>(null, 42).ShouldBeFalse();
|
||||
}
|
||||
|
||||
public class ReferenceType_IEquatable_String
|
||||
{
|
||||
[Fact] public void Equal_strings() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual("hello", "hello").ShouldBeTrue();
|
||||
|
||||
[Fact] public void Different_strings() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual("hello", "world").ShouldBeFalse();
|
||||
|
||||
[Fact] public void Both_null() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual<string>(null, null).ShouldBeTrue();
|
||||
|
||||
[Fact] public void Left_null_right_value() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual<string>(null, "hello").ShouldBeFalse();
|
||||
|
||||
[Fact] public void Left_value_right_null() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual("hello", null).ShouldBeFalse();
|
||||
}
|
||||
|
||||
public class ReferenceType_IEquatable_Error
|
||||
{
|
||||
[Fact] public void Equal_errors() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual(
|
||||
Error.New("type", "msg"),
|
||||
Error.New("type", "msg")
|
||||
).ShouldBeTrue();
|
||||
|
||||
[Fact] public void Different_errors() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual(
|
||||
Error.New("type_a", "msg"),
|
||||
Error.New("type_b", "msg")
|
||||
).ShouldBeFalse();
|
||||
|
||||
[Fact] public void Both_null() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual<Error>(null, null).ShouldBeTrue();
|
||||
|
||||
[Fact] public void One_null() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual(Error.New("type", "msg"), null).ShouldBeFalse();
|
||||
}
|
||||
|
||||
public class RecordType_IEquatable
|
||||
{
|
||||
[Fact] public void Equal_records() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual(
|
||||
new EquatableRecord("a", 1),
|
||||
new EquatableRecord("a", 1)
|
||||
).ShouldBeTrue();
|
||||
|
||||
[Fact] public void Different_records() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual(
|
||||
new EquatableRecord("a", 1),
|
||||
new EquatableRecord("b", 2)
|
||||
).ShouldBeFalse();
|
||||
|
||||
[Fact] public void Both_null() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual<EquatableRecord>(null, null).ShouldBeTrue();
|
||||
}
|
||||
|
||||
public class NonIEquatable_Fallback
|
||||
{
|
||||
[Fact] public void Equal_by_Equals_override() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual(
|
||||
new NonEquatableClass("a"),
|
||||
new NonEquatableClass("a")
|
||||
).ShouldBeTrue();
|
||||
|
||||
[Fact] public void Not_equal() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual(
|
||||
new NonEquatableClass("a"),
|
||||
new NonEquatableClass("b")
|
||||
).ShouldBeFalse();
|
||||
|
||||
[Fact] public void Both_null() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual<NonEquatableClass>(null, null).ShouldBeTrue();
|
||||
|
||||
[Fact] public void Left_null() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual<NonEquatableClass>(null, new NonEquatableClass("a"))
|
||||
.ShouldBeFalse();
|
||||
}
|
||||
|
||||
public class CustomEquatableStruct
|
||||
{
|
||||
[Fact] public void Equal() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual(
|
||||
new EquatableStruct(1),
|
||||
new EquatableStruct(1)
|
||||
).ShouldBeTrue();
|
||||
|
||||
[Fact] public void Not_equal() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual(
|
||||
new EquatableStruct(1),
|
||||
new EquatableStruct(2)
|
||||
).ShouldBeFalse();
|
||||
}
|
||||
|
||||
public class CustomNullableEquatableStruct
|
||||
{
|
||||
[Fact] public void Both_have_same_value() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual<EquatableStruct?>(
|
||||
new EquatableStruct(1),
|
||||
new EquatableStruct(1)
|
||||
).ShouldBeTrue();
|
||||
|
||||
[Fact] public void Different_values() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual<EquatableStruct?>(
|
||||
new EquatableStruct(1),
|
||||
new EquatableStruct(2)
|
||||
).ShouldBeFalse();
|
||||
|
||||
[Fact] public void Both_null() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual<EquatableStruct?>(null, null).ShouldBeTrue();
|
||||
|
||||
[Fact] public void One_null() =>
|
||||
Just.Railway.ReflectionHelper.IsEqual<EquatableStruct?>(new EquatableStruct(1), null)
|
||||
.ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
namespace Raliway.Tests.Results;
|
||||
|
||||
public class Bugs
|
||||
{
|
||||
#region BUG-1: Result.Equals asymmetry
|
||||
|
||||
[Fact]
|
||||
public void Result_Success_ShouldNotEqual_Default()
|
||||
{
|
||||
// default(Result) has State=Bottom — it's uninitialized.
|
||||
// Equals should throw because one operand is Bottom.
|
||||
var success = Result.Success();
|
||||
Result bottom = default;
|
||||
|
||||
// Should throw ResultNotInitializedException, NOT return true
|
||||
Should.Throw<ResultNotInitializedException>(() => success.Equals(bottom));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Result_Default_ShouldNotEqual_Success()
|
||||
{
|
||||
Result bottom = default;
|
||||
var success = Result.Success();
|
||||
|
||||
Should.Throw<ResultNotInitializedException>(() => bottom.Equals(success));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Result_Success_EqualsOperator_WithDefault()
|
||||
{
|
||||
var success = Result.Success();
|
||||
Result bottom = default;
|
||||
|
||||
Should.Throw<ResultNotInitializedException>(() => success == bottom);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Result_Error_Equals_WithBottom_ShouldThrow()
|
||||
{
|
||||
var error = Result.Failure("test");
|
||||
Result bottom = default;
|
||||
|
||||
Should.Throw<ResultNotInitializedException>(() => error.Equals(bottom));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Result_Bottom_Equals_WithSuccess_ShouldThrow()
|
||||
{
|
||||
Result bottom = default;
|
||||
var success = Result.Success();
|
||||
|
||||
Should.Throw<ResultNotInitializedException>(() => bottom.Equals(success));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BUG-2: Result<T>.Equals asymmetry
|
||||
|
||||
[Fact]
|
||||
public void ResultOfT_Success_ShouldNotEqual_Default()
|
||||
{
|
||||
var success = Result.Success(42);
|
||||
Result<int> bottom = default;
|
||||
|
||||
Should.Throw<ResultNotInitializedException>(() => success.Equals(bottom));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResultOfT_Error_ShouldNotEqual_Default()
|
||||
{
|
||||
var error = Result.Failure<int>("test");
|
||||
Result<int> bottom = default;
|
||||
|
||||
Should.Throw<ResultNotInitializedException>(() => error.Equals(bottom));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResultOfT_Default_ShouldNotEqual_Success()
|
||||
{
|
||||
Result<int> bottom = default;
|
||||
var success = Result.Success(42);
|
||||
|
||||
Should.Throw<ResultNotInitializedException>(() => bottom.Equals(success));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BUG-3: Task.WhenAny Merge — faulted task
|
||||
|
||||
[Fact]
|
||||
public async Task Merge_Tasks_WhenOneFaults_ShouldReturnError_NotThrow()
|
||||
{
|
||||
var tasks = new Task<Result>[]
|
||||
{
|
||||
Task.FromResult(Result.Success()),
|
||||
Task.Run(() =>
|
||||
{
|
||||
throw new InvalidOperationException("boom");
|
||||
#pragma warning disable CS0162 // Unreachable code
|
||||
return Result.Success();
|
||||
#pragma warning restore CS0162
|
||||
}),
|
||||
};
|
||||
|
||||
// Should not throw AggregateException escaping the Result error channel
|
||||
var result = await tasks.Merge();
|
||||
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Merge_Tasks_WhenOneIsCanceled_ShouldReturnError_NotThrow()
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
var canceledTask = Task.FromCanceled<Result>(cts.Token);
|
||||
|
||||
var tasks = new Task<Result>[]
|
||||
{
|
||||
Task.FromResult(Result.Success()),
|
||||
canceledTask,
|
||||
};
|
||||
|
||||
// Should not throw — canceled tasks have null Exception
|
||||
var result = await tasks.Merge();
|
||||
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BUG-4: Task.WhenAny Merge<T> — canceled task + O(n²)
|
||||
|
||||
[Fact]
|
||||
public async Task MergeOfT_Tasks_WhenOneIsCanceled_ShouldReturnError_NotThrow()
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
var canceledTask = Task.FromCanceled<Result<int>>(cts.Token);
|
||||
|
||||
var tasks = new Task<Result<int>>[]
|
||||
{
|
||||
Task.FromResult(Result.Success(1)),
|
||||
canceledTask,
|
||||
};
|
||||
|
||||
// Should not crash on canceled task (task.Exception is null → null ref in implicit operator)
|
||||
var result = await tasks.Merge();
|
||||
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MergeOfT_Tasks_WhenOneFaults_ShouldReturnError_NotThrow()
|
||||
{
|
||||
var tasks = new Task<Result<int>>[]
|
||||
{
|
||||
Task.FromResult(Result.Success(1)),
|
||||
Task.Run(() =>
|
||||
{
|
||||
throw new InvalidOperationException("boom");
|
||||
#pragma warning disable CS0162 // Unreachable code
|
||||
return Result.Success(2);
|
||||
#pragma warning restore CS0162
|
||||
}),
|
||||
};
|
||||
|
||||
var result = await tasks.Merge();
|
||||
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BUG-5: Error.New null type
|
||||
|
||||
[Fact]
|
||||
public void Error_New_WithNullType_ShouldThrowArgumentNullException()
|
||||
{
|
||||
// Null type should throw immediately at construction, not crash later in CompareTo/IsSimilarTo
|
||||
Should.Throw<ArgumentNullException>(() => Error.New(null!, "some message"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -37,6 +37,7 @@ public class GeneralUsage
|
||||
var result = Result.Success()
|
||||
.Append(() => Result.Failure<int>(error))
|
||||
.Append("test")
|
||||
.Tap(onSuccess: (i, s) => true.ShouldBeFalse("should not reach this path"))
|
||||
.Map((i, s) =>
|
||||
{
|
||||
true.ShouldBeFalse("should not reach this path");
|
||||
@@ -85,7 +86,7 @@ public class GeneralUsage
|
||||
return "";
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
result.ShouldBe("TEST_1;SOME");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,558 @@
|
||||
namespace Raliway.Tests.TryExtensions;
|
||||
|
||||
public class RunTests
|
||||
{
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Action (void) — sync
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
public class Action_Void
|
||||
{
|
||||
[Fact]
|
||||
public void No_args_success()
|
||||
{
|
||||
var result = Try.Run(() => { });
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void No_args_exception()
|
||||
{
|
||||
var result = Try.Run(new Action(() => throw new InvalidOperationException("boom")));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Message.ShouldBe("boom");
|
||||
result.Error!.Type.ShouldBe("System.InvalidOperationException");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void One_arg_success()
|
||||
{
|
||||
var captured = 0;
|
||||
var result = Try.Run<int>(x => captured = x, 42);
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
captured.ShouldBe(42);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void One_arg_exception()
|
||||
{
|
||||
var result = Try.Run(new Action<int>(_ => throw new InvalidOperationException("boom")), 42);
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Message.ShouldBe("boom");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Five_args_success()
|
||||
{
|
||||
var captured = "";
|
||||
var result = Try.Run<int, string, bool, double, char>(
|
||||
(a, b, c, d, e) => captured = $"{a}{b}{c}{d}{e}",
|
||||
1, "x", true, 3.14, 'z');
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
captured.ShouldBe("1xTrue3.14z");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Five_args_exception()
|
||||
{
|
||||
var result = Try.Run(
|
||||
new Action<int, string, bool, double, char>((_, _, _, _, _) => throw new InvalidOperationException("boom")),
|
||||
1, "x", true, 3.14, 'z');
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Message.ShouldBe("boom");
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Func<Result> — sync
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
public class Func_Result
|
||||
{
|
||||
[Fact]
|
||||
public void No_args_returns_success()
|
||||
{
|
||||
var result = Try.Run(() => Result.Success());
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void No_args_returns_failure()
|
||||
{
|
||||
var error = Error.New("nope");
|
||||
var result = Try.Run(() => Result.Failure(error));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error.ShouldBe(error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void No_args_exception_caught()
|
||||
{
|
||||
var result = Try.Run(new Func<Result>(() => throw new InvalidOperationException("boom")));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Message.ShouldBe("boom");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void One_arg_passes_through()
|
||||
{
|
||||
var result = Try.Run<int>(x => Result.Success(x), 42);
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Func<T> — sync
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
public class Func_T
|
||||
{
|
||||
[Fact]
|
||||
public void No_args_success()
|
||||
{
|
||||
var result = Try.Run(() => 42);
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
result.Value.ShouldBe(42);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void No_args_exception()
|
||||
{
|
||||
var result = Try.Run(new Func<int>(() => throw new InvalidOperationException("boom")));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Message.ShouldBe("boom");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void One_arg_success()
|
||||
{
|
||||
var result = Try.Run(x => $"val:{x}", 42);
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
result.Value.ShouldBe("val:42");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void One_arg_exception()
|
||||
{
|
||||
var result = Try.Run(new Func<int, string>(_ => throw new InvalidOperationException("boom")), 42);
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Message.ShouldBe("boom");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Null_value_is_preserved()
|
||||
{
|
||||
var result = Try.Run(() => (string?)null);
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
result.Value.ShouldBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Func<Result<T>> — sync
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
public class Func_ResultT
|
||||
{
|
||||
[Fact]
|
||||
public void No_args_returns_success()
|
||||
{
|
||||
var result = Try.Run(() => Result.Success(42));
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
result.Value.ShouldBe(42);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void No_args_returns_failure()
|
||||
{
|
||||
var error = Error.New("nope");
|
||||
var result = Try.Run(() => Result.Failure<int>(error));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error.ShouldBe(error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void No_args_exception_caught()
|
||||
{
|
||||
var result = Try.Run(new Func<Result<int>>(() => throw new InvalidOperationException("boom")));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Message.ShouldBe("boom");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void One_arg_passes_through()
|
||||
{
|
||||
var result = Try.Run(x => Result.Success($"val:{x}"), 42);
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
result.Value.ShouldBe("val:42");
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Task async — Action (void)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
public class Task_Action
|
||||
{
|
||||
[Fact]
|
||||
public async Task No_args_success()
|
||||
{
|
||||
var result = await Try.Run(() => Task.CompletedTask);
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task No_args_exception()
|
||||
{
|
||||
var result = await Try.Run(() => Task.FromException(new InvalidOperationException("boom")));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Message.ShouldBe("boom");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task One_arg_success()
|
||||
{
|
||||
var captured = 0;
|
||||
var result = await Try.Run(x => { captured = x; return Task.CompletedTask; }, 42);
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
captured.ShouldBe(42);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Task async — Func<Task<Result>>
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
public class Task_Func_Result
|
||||
{
|
||||
[Fact]
|
||||
public async Task No_args_returns_success()
|
||||
{
|
||||
var result = await Try.Run(() => Task.FromResult(Result.Success()));
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task No_args_returns_failure()
|
||||
{
|
||||
var error = Error.New("nope");
|
||||
var result = await Try.Run(() => Task.FromResult(Result.Failure(error)));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error.ShouldBe(error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task No_args_exception_caught()
|
||||
{
|
||||
var result = await Try.Run(new Func<Task<Result>>(() => throw new InvalidOperationException("boom")));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Message.ShouldBe("boom");
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Task async — Func<Task<T>>
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
public class Task_Func_T
|
||||
{
|
||||
[Fact]
|
||||
public async Task No_args_success()
|
||||
{
|
||||
var result = await Try.Run(() => Task.FromResult(42));
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
result.Value.ShouldBe(42);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task No_args_exception()
|
||||
{
|
||||
var result = await Try.Run(new Func<Task<int>>(() => throw new InvalidOperationException("boom")));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Message.ShouldBe("boom");
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Task async — Func<Task<Result<T>>>
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
public class Task_Func_ResultT
|
||||
{
|
||||
[Fact]
|
||||
public async Task No_args_returns_success()
|
||||
{
|
||||
var result = await Try.Run(() => Task.FromResult(Result.Success(42)));
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
result.Value.ShouldBe(42);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task No_args_returns_failure()
|
||||
{
|
||||
var error = Error.New("nope");
|
||||
var result = await Try.Run(() => Task.FromResult(Result.Failure<int>(error)));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error.ShouldBe(error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task No_args_exception_caught()
|
||||
{
|
||||
var result = await Try.Run(new Func<Task<Result<int>>>(() => throw new InvalidOperationException("boom")));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Message.ShouldBe("boom");
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// ValueTask async — Action (void)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
public class ValueTask_Action
|
||||
{
|
||||
[Fact]
|
||||
public async Task No_args_success()
|
||||
{
|
||||
var result = await Try.RunValueTask(() => ValueTask.CompletedTask);
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task No_args_exception()
|
||||
{
|
||||
var result = await Try.RunValueTask(() => ValueTask.FromException(new InvalidOperationException("boom")));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Message.ShouldBe("boom");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task One_arg_success()
|
||||
{
|
||||
var captured = 0;
|
||||
var result = await Try.RunValueTask(x => { captured = x; return ValueTask.CompletedTask; }, 42);
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
captured.ShouldBe(42);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// ValueTask async — Func<ValueTask<Result>>
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
public class ValueTask_Func_Result
|
||||
{
|
||||
[Fact]
|
||||
public async Task No_args_returns_success()
|
||||
{
|
||||
var result = await Try.RunValueTask(() => ValueTask.FromResult(Result.Success()));
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task No_args_returns_failure()
|
||||
{
|
||||
var error = Error.New("nope");
|
||||
var result = await Try.RunValueTask(() => ValueTask.FromResult(Result.Failure(error)));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error.ShouldBe(error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task No_args_exception_caught()
|
||||
{
|
||||
var result = await Try.RunValueTask(new Func<ValueTask<Result>>(() => throw new InvalidOperationException("boom")));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Message.ShouldBe("boom");
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// ValueTask async — Func<ValueTask<T>>
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
public class ValueTask_Func_T
|
||||
{
|
||||
[Fact]
|
||||
public async Task No_args_success()
|
||||
{
|
||||
var result = await Try.RunValueTask(() => ValueTask.FromResult(42));
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
result.Value.ShouldBe(42);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task No_args_exception()
|
||||
{
|
||||
var result = await Try.RunValueTask<int>(() => throw new InvalidOperationException("boom"));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Message.ShouldBe("boom");
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// ValueTask async — Func<ValueTask<Result<T>>>
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
public class ValueTask_Func_ResultT
|
||||
{
|
||||
[Fact]
|
||||
public async Task No_args_returns_success()
|
||||
{
|
||||
var result = await Try.RunValueTask(() => ValueTask.FromResult(Result.Success(42)));
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
result.Value.ShouldBe(42);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task No_args_returns_failure()
|
||||
{
|
||||
var error = Error.New("nope");
|
||||
var result = await Try.RunValueTask(() => ValueTask.FromResult(Result.Failure<int>(error)));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error.ShouldBe(error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task No_args_exception_caught()
|
||||
{
|
||||
var result = await Try.RunValueTask(new Func<ValueTask<Result<int>>>(() => throw new InvalidOperationException("boom")));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Message.ShouldBe("boom");
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// AggregateException unwrapping
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
public class AggregateException_Unwrapping
|
||||
{
|
||||
[Fact]
|
||||
public void Single_inner_exception_is_unwrapped()
|
||||
{
|
||||
var result = Try.Run(new Func<int>(() => throw new AggregateException(new InvalidOperationException("inner"))));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Type.ShouldBe("System.InvalidOperationException");
|
||||
result.Error!.Message.ShouldBe("inner");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Multiple_inner_exceptions_become_ManyErrors()
|
||||
{
|
||||
var result = Try.Run(new Func<int>(() => throw new AggregateException(
|
||||
new InvalidOperationException("first"),
|
||||
new ArgumentException("second"))));
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Type.ShouldBe("many_errors");
|
||||
var many = (ManyErrors)result.Error!;
|
||||
many.Count.ShouldBe(2);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Async exception in task body (not Task.FromException)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
public class Async_Exception_In_Body
|
||||
{
|
||||
[Fact]
|
||||
public async Task Task_throws_in_async_body()
|
||||
{
|
||||
var result = await Try.Run(async () =>
|
||||
{
|
||||
await Task.Yield();
|
||||
throw new InvalidOperationException("async boom");
|
||||
});
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Message.ShouldBe("async boom");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValueTask_throws_in_async_body()
|
||||
{
|
||||
var result = await Try.RunValueTask(async () =>
|
||||
{
|
||||
await Task.Yield();
|
||||
throw new InvalidOperationException("async boom vt");
|
||||
});
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Message.ShouldBe("async boom vt");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Task_FuncT_throws_in_async_body()
|
||||
{
|
||||
var result = await Try.Run<int>(async () =>
|
||||
{
|
||||
await Task.Yield();
|
||||
throw new InvalidOperationException("async boom");
|
||||
});
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Message.ShouldBe("async boom");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValueTask_ResultT_throws_in_async_body()
|
||||
{
|
||||
var result = await Try.RunValueTask<int>(async () =>
|
||||
{
|
||||
await Task.Yield();
|
||||
throw new InvalidOperationException("async boom vt");
|
||||
});
|
||||
result.IsFailure.ShouldBeTrue();
|
||||
result.Error!.Message.ShouldBe("async boom vt");
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Edge cases
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
public class Edge_Cases
|
||||
{
|
||||
[Fact]
|
||||
public void Reference_type_null_success_value_is_preserved()
|
||||
{
|
||||
var result = Try.Run(() => Result.Success<string?>(null));
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
result.Value.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Large_ValueTuple_is_preserved()
|
||||
{
|
||||
var result = Try.Run(() => (1, 2, 3, 4, 5));
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
result.Value.ShouldBe((1, 2, 3, 4, 5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Async_large_ValueTuple_is_preserved()
|
||||
{
|
||||
var result = await Try.Run(() => Task.FromResult((1, 2, 3, 4, 5)));
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
result.Value.ShouldBe((1, 2, 3, 4, 5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Three_args_all_passed_correctly()
|
||||
{
|
||||
var result = Try.Run(
|
||||
(a, b, c) => $"{a}-{b}-{c}",
|
||||
1, "two", true);
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
result.Value.ShouldBe("1-two-True");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Task_with_three_args()
|
||||
{
|
||||
var result = await Try.Run(
|
||||
(a, b, c) => Task.FromResult($"{a}-{b}-{c}"),
|
||||
1, "two", true);
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
result.Value.ShouldBe("1-two-True");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user