-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
212 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package kyo | ||
|
||
import Ansi.* | ||
import kyo.kernel.* | ||
|
||
class CheckFailed(val message: String, val frame: Frame) extends AssertionError(message): | ||
override def getMessage() = | ||
Seq( | ||
"", | ||
"──────────────────────────────".dim, | ||
"Check failed! ".red.bold + message, | ||
"──────────────────────────────".dim, | ||
frame.parse.show, | ||
"──────────────────────────────".dim | ||
).mkString("\n") | ||
end CheckFailed | ||
|
||
sealed trait Check extends ArrowEffect[Const[CheckFailed], Const[Unit]] | ||
|
||
object Check: | ||
|
||
inline def apply(inline condition: Boolean)(using inline frame: Frame): Unit < Check = | ||
Check(condition, "") | ||
|
||
inline def apply(inline condition: Boolean, inline message: => String)(using inline frame: Frame): Unit < Check = | ||
if condition then () | ||
else ArrowEffect.suspend[Unit](Tag[Check], new CheckFailed(message, frame)) | ||
|
||
def runAbort[A: Flat, S](v: A < (Check & S))(using Frame): A < (Abort[CheckFailed] & S) = | ||
ArrowEffect.handle(Tag[Check], v)( | ||
[C] => (input, cont) => Abort.fail(input) | ||
) | ||
|
||
def runChunk[A: Flat, S](v: A < (Check & S))(using Frame): (Chunk[CheckFailed], A) < S = | ||
ArrowEffect.handle.state(Tag[Check], Chunk.empty[CheckFailed], v)( | ||
handle = [C] => | ||
(input, state, cont) => | ||
(state.append(input), cont(())), | ||
done = (state, result) => (state, result) | ||
) | ||
|
||
def runDiscard[A: Flat, S](v: A < (Check & S))(using Frame): A < S = | ||
ArrowEffect.handle(Tag[Check], v)( | ||
[C] => (_, cont) => cont(()) | ||
) | ||
|
||
end Check |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
package kyo | ||
|
||
class CheckTest extends Test: | ||
|
||
"apply" - { | ||
"with message" - { | ||
"passes when condition is true" in run { | ||
Check.runDiscard(Check(true, "This should pass").map(_ => succeed)) | ||
} | ||
|
||
"fails when condition is false" in run { | ||
Abort.run(Check.runAbort(Check(false, "This should fail"))).map { r => | ||
assert(r.failure.get.asInstanceOf[CheckFailed].message == "This should fail") | ||
} | ||
} | ||
} | ||
"no message" - { | ||
"passes when condition is true" in run { | ||
Check.runDiscard(Check(true).map(_ => succeed)) | ||
} | ||
|
||
"fails when condition is false" in run { | ||
Abort.run(Check.runAbort(Check(false))).map { r => | ||
assert(r.failure.get.asInstanceOf[CheckFailed].message == "") | ||
} | ||
} | ||
} | ||
} | ||
|
||
"runAbort" - { | ||
"returns success for passing checks" in run { | ||
val result = Check.runAbort { | ||
for | ||
_ <- Check(true, "This should pass") | ||
_ <- Check(1 + 1 == 2, "Basic math works") | ||
yield "All checks passed" | ||
} | ||
Abort.run(result).map(r => assert(r == Result.success("All checks passed"))) | ||
} | ||
|
||
"returns failure for failing checks" in run { | ||
val result = Check.runAbort { | ||
for | ||
_ <- Check(true, "This should pass") | ||
_ <- Check(false, "This should fail") | ||
_ <- Check(true, "This won't be reached") | ||
yield "Shouldn't get here" | ||
} | ||
Abort.run(result).map { r => | ||
assert(r.failure.get.asInstanceOf[CheckFailed].message == "This should fail") | ||
} | ||
} | ||
} | ||
|
||
"runChunk" - { | ||
"collects all check failures" in run { | ||
val result = Check.runChunk { | ||
for | ||
_ <- Check(false, "First failure") | ||
_ <- Check(true, "This passes") | ||
_ <- Check(false, "Second failure") | ||
yield "Done" | ||
} | ||
result.map { case (failures, value) => | ||
assert(failures.size == 2) | ||
assert(failures(0).message == "First failure") | ||
assert(failures(1).message == "Second failure") | ||
assert(value == "Done") | ||
} | ||
} | ||
} | ||
|
||
"runDiscard" - { | ||
"discards check failures and continues execution" in run { | ||
val result = Check.runDiscard { | ||
for | ||
_ <- Check(false, "This failure is discarded") | ||
_ <- Check(true, "This passes") | ||
yield "Execution completed" | ||
} | ||
result.map(r => assert(r == "Execution completed")) | ||
} | ||
} | ||
|
||
"multiple checks" in run { | ||
val result = Check.runChunk { | ||
for | ||
_ <- Check(true, "This should pass") | ||
_ <- Check(false, "This should fail") | ||
_ <- Check(true, "This should pass too") | ||
_ <- Check(false, "This should also fail") | ||
yield "Done" | ||
} | ||
result.map { case (failures, value) => | ||
assert(failures.size == 2) | ||
assert(failures(0).message == "This should fail") | ||
assert(failures(1).message == "This should also fail") | ||
assert(value == "Done") | ||
} | ||
} | ||
|
||
"checks with effects" in run { | ||
val result = Env.run(5) { | ||
Check.runChunk { | ||
for | ||
env <- Env.get[Int] | ||
_ <- Check(env > 0, "Env should be positive") | ||
_ <- Check(env < 10, "Env should be less than 10") | ||
_ <- Check(env % 2 != 0, "Env should be odd") | ||
yield env | ||
} | ||
} | ||
result.map { case (failures, value) => | ||
assert(failures.isEmpty) | ||
assert(value == 5) | ||
} | ||
} | ||
|
||
"combining with other effects" in run { | ||
val result = Var.run(0) { | ||
Check.runChunk { | ||
for | ||
_ <- Check(true, "Initial check") | ||
_ <- Var.update[Int](_ + 1) | ||
v <- Var.get[Int] | ||
_ <- Check(v == 1, "Var should be updated") | ||
yield v | ||
} | ||
} | ||
result.map { case (failures, value) => | ||
assert(failures.isEmpty) | ||
assert(value == 1) | ||
} | ||
} | ||
|
||
end CheckTest |