559 lines
21 KiB
C#
559 lines
21 KiB
C#
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");
|
|
}
|
|
}
|
|
}
|