Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write bracket tests #47

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions http-scala-fx/src/test/scala/fx/HttpServerFixtures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,10 @@ trait HttpServerFixtures:
setup = _ => {
for {
server <- Resource(
HttpServer.create(InetSocketAddress(0), 0),
() => HttpServer.create(InetSocketAddress(0), 0),
(server, _) => server.stop(0))
serverExecutor <- Resource(
Executors.newVirtualThreadPerTaskExecutor,
() => Executors.newVirtualThreadPerTaskExecutor,
(executor, _) => executor.shutdown())
_ = server.setExecutor(serverExecutor)
httpContext = server.createContext("/root", handler)
Expand Down
4 changes: 3 additions & 1 deletion scala-fx/src/main/scala/fx/Fiber.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package fx

import java.util.concurrent.Future
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionException

opaque type Fiber[A] = Future[A]

Expand All @@ -20,7 +21,8 @@ def uncancellable[A](fn: () => A): A = {
case t: Throwable =>
promise.completeExceptionally(t)
})
promise.join
try promise.join
catch case e: CompletionException => throw e.getCause
}

def fork[B](f: () => B)(using structured: Structured): Fiber[B] =
Expand Down
12 changes: 6 additions & 6 deletions scala-fx/src/main/scala/fx/Resource.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@ import Tuple.{InverseMap, IsMappedBy, Map}
import scala.annotation.implicitNotFound

class Resource[A](
val acquire: Resources ?=> A,
val acquire: Resources ?=> () => A,
val release: (A, ExitCase) => Unit
):

def this(value: Resources ?=> A) = this(value, (_, _) => ())
def this(value: Resources ?=> () => A) = this(value, (_, _) => ())

def use[B](f: A => B): B = bracketCase(() => acquire, f, release)
def use[B](f: A => B): B = bracketCase(acquire, f, release)

def map[B](f: (A) => B): Resource[B] =
Resource(f(this.bind))
Resource(() => f(this.bind))

def flatMap[B](f: (A) => Resource[B]): Resource[B] =
Resource(f(this.bind).bind)
Resource(() => f(this.bind).bind)

def bind: A =
bracketCase(
() => {
val a = acquire
val a = acquire()
val finalizer: (ExitCase) => Unit = (ex: ExitCase) => release(a, ex)
summon[Resources].finalizers.updateAndGet(finalizer +: _)
a
Expand Down
6 changes: 4 additions & 2 deletions scala-fx/src/main/scala/fx/Use.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,11 @@ inline def bracketCase[A, B](
val res =
try use(acquired)
catch
case (e: CancellationException) =>
case e: InterruptedException =>
runReleaseAndRethrow(e, () => release(acquired, ExitCase.Cancelled(e)))
case (e: ExecutionException) =>
case e: CancellationException =>
runReleaseAndRethrow(e, () => release(acquired, ExitCase.Cancelled(e)))
case e: ExecutionException =>
runReleaseAndRethrow(
e.getCause,
() => release(acquired, ExitCase.Cancelled(e.getCause))
Expand Down
63 changes: 63 additions & 0 deletions scala-fx/src/test/scala/fx/BracketTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package fx

import fx.ResourcesTests.property
import org.scalacheck.Prop.forAll
import org.scalacheck.Properties

import java.util.concurrent.{CompletableFuture, CompletionException}

object BracketTests extends Properties("Bracket Tests"):

property("bracketCase identity") = forAll { (n: Int) =>
bracketCase(() => n, identity, (_, _) => ()) == n
}

case class CustomEx(val token: String) extends RuntimeException

property("bracketCase exception identity") = forAll { (msg: String) =>
val res =
try
bracketCase(
() => throw CustomEx(msg),
_ => throw RuntimeException("Cannot come here"),
(_, _) => ())
catch case CustomEx(msg) => msg

res == msg
}

property("bracketCase must run release task on use error") = forAll { (msg: String) =>
val promise = new CompletableFuture[ExitCase]
val res =
try bracketCase(() => (), _ => throw CustomEx(msg), (_, ex) => promise.complete(ex))
catch case CustomEx(msg) => msg

res == msg && promise.join() == ExitCase.Failure(CustomEx(msg))
}

property("bracketCase must run release task on use success") = forAll { (msg: String) =>
val promise = new CompletableFuture[ExitCase]
val res = bracketCase(() => (), _ => msg, (_, ex) => promise.complete(ex))

res == msg && promise.join() == ExitCase.Completed
}

property("bracketCase cancellation in use") = forAll { (msg: String) =>
val latch = new CompletableFuture[Unit]
val promise = new CompletableFuture[ExitCase]
structured {
val fiber = fork(() =>
bracketCase(
() => (),
_ => {
latch.complete(())
Thread.sleep(100_000)
},
(_, ex) => promise.complete(ex)))
latch.join()
fiber.cancel()
promise.join().isInstanceOf[ExitCase.Cancelled]
}
}

end BracketTests
10 changes: 5 additions & 5 deletions scala-fx/src/test/scala/fx/ResourcesTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import java.util.concurrent.ExecutionException
object ResourcesTests extends Properties("Resources Tests"):

property("Can consume resource") = forAll { (n: Int) =>
val r = Resource(n, (_, _) => ())
val r = Resource(() => n, (_, _) => ())
r.use(_ + 1) == n + 1
}

property("value resource is released with Complete") = forAll { (n: Int) =>
val p = CompletableFuture[ExitCase]()
val r = Resource(n, (_, ex) => require(p.complete(ex)))
val r = Resource(() => n, (_, ex) => require(p.complete(ex)))
r.use(_ => ())
p.join == ExitCase.Completed
}
Expand All @@ -25,13 +25,13 @@ object ResourcesTests extends Properties("Resources Tests"):

property("error resource finishes with error") = forAll { (n: String) =>
val p = CompletableFuture[ExitCase]()
val r = Resource[Int](throw CustomEx(n), (_, ex) => require(p.complete(ex)))
val r = Resource[Int](() => throw CustomEx(n), (_, ex) => require(p.complete(ex)))
val result =
try
r.use(_ + 1)
"unexpected"
catch case e: CompletionException => e.getCause.asInstanceOf[CustomEx].token
result == n
catch case CustomEx(msg) => msg
n == result
}

end ResourcesTests
Expand Down
46 changes: 46 additions & 0 deletions scala-fx/src/test/scala/fx/UncancellableTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package fx

import fx.ResourcesTests.property
import org.scalacheck.Prop.forAll
import org.scalacheck.Properties

import java.util.concurrent.{CompletableFuture, CompletionException}

object UncancellableTests extends Properties("Bracket Tests"):

property("uncancellable identity") = forAll { (n: Int) => uncancellable(() => n) == n }

case class CustomEx(val token: String) extends RuntimeException

property("uncancellable exception identity") = forAll { (msg: String) =>
val res =
try uncancellable(() => throw CustomEx(msg))
catch case CustomEx(msg) => msg

res == msg
}

// "Uncancellable back pressures withTimeoutOrNull" {
// runBlockingTest {
// checkAll(Arb.long(50, 100), Arb.long(300, 400)) {
// a
// , b ->
// val start = currentTime
//
// val n = withTimeoutOrNull(a.milliseconds) {
// uncancellable {
// delay(b.milliseconds)
// }
// }
//
// val duration = currentTime - start
//
// n shouldBe null // timed-out so should be null
// require((duration) >= b) {
// "Should've taken longer than $b milliseconds, but took $duration"
// }
// }
// }
// }

end UncancellableTests
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ trait HttpExtensionsSuiteFixtures { self: ScalaFXSuite =>
setup = _ => {
for {
server <- Resource(
HttpServer.create(InetSocketAddress(0), 0),
() => HttpServer.create(InetSocketAddress(0), 0),
(server, _) => server.stop(0))
serverExecutor <- Resource(
Executors.newVirtualThreadPerTaskExecutor,
() => Executors.newVirtualThreadPerTaskExecutor,
(executor, _) => executor.shutdown())
_ = server.setExecutor(serverExecutor)
httpContext = server.createContext("/root", handler)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ trait ToHttpBodyMapperFixtures { self: ScalaFXSuite =>
val fileBody = FunFixture(
setup = _ => {
Resource.apply(
{
() => {
val file = Files.createTempFile("fileBodyTest", ".txt")
Files.write(file, "test".getBytes())
FileBody(SttpFile.fromPath(file))
Expand Down