Skip to content

Commit

Permalink
Merge pull request #11 from id3as/master
Browse files Browse the repository at this point in the history
receive type class and show instances
  • Loading branch information
nwolverson authored Feb 4, 2022
2 parents 27299ea + 0038a2c commit 5eacc3a
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 54 deletions.
26 changes: 15 additions & 11 deletions src/Erl/Process.purs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ module Erl.Process
, spawnLink
, sendExitSignal
, class HasProcess
, class ReceivesMessage
, class HasSelf
, class HasReceive
, trapExit
, receiveWithTrap
, receiveWithTrapAndTimeout
Expand All @@ -41,6 +41,9 @@ toPid (Process pid) = pid
instance eqProcess :: Eq (Process a) where
eq a b = eq (toPid a) (toPid b)

instance Show (Process pid) where
show (Process pid) = "(Process " <> show pid <> ")"

newtype ProcessM (a :: Type) b
= ProcessM (Effect b)
derive newtype instance functorProcessM :: Functor (ProcessM a)
Expand All @@ -55,12 +58,6 @@ unsafeRunProcessM (ProcessM b) = b
instance monadEffectProcessM :: MonadEffect (ProcessM a) where
liftEffect = ProcessM

receive :: forall a. ProcessM a a
receive = ProcessM Raw.receive

receiveWithTimeout :: forall a. Milliseconds -> a -> ProcessM a a
receiveWithTimeout n a = ProcessM $ Raw.receiveWithTimeout n a

newtype ProcessTrapM (a :: Type) b
= ProcessTrapM (Effect b)
derive newtype instance functorProcessTrapM :: Functor (ProcessTrapM a)
Expand Down Expand Up @@ -118,9 +115,16 @@ instance selfProcessM :: HasSelf (ProcessM a) a where
self :: forall a. ProcessM a (Process a)
self = ProcessM $ Process <$> Raw.self

class ReceivesMessage :: forall k. k -> Type -> Constraint
class ReceivesMessage a msg | a -> msg
class HasReceive :: (Type -> Type) -> Type -> Type -> Constraint
class HasReceive a msg r | a -> msg r where
receive :: a r

receiveWithTimeout :: Milliseconds -> msg -> a r

instance messageTypeProcessM :: ReceivesMessage (ProcessM msg) msg
instance HasReceive (ProcessM msg) msg msg where
receive = ProcessM Raw.receive
receiveWithTimeout t d = ProcessM $ Raw.receiveWithTimeout t d

instance messageTypeProcessTrapM :: ReceivesMessage (ProcessTrapM msg) msg
instance HasReceive (ProcessTrapM msg) msg (Either ExitReason msg) where
receive = ProcessTrapM Raw.receiveWithTrap
receiveWithTimeout t d = ProcessTrapM $ Raw.receiveWithTrapAndTimeout t d
8 changes: 6 additions & 2 deletions src/Erl/Process/Raw.erl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
setProcessFlagTrapExit/1,
exit/1,
sendExitSignal/2,
unlink/1
unlink/1,
show_/1
]).

eqNative(X, Y) -> X == Y.
Expand Down Expand Up @@ -62,7 +63,7 @@ receiveWithTrapAndTimeout_(Timeout, Msg) ->
{'EXIT', Pid, Other } -> {left, {exitMsg, Pid, {other, Other}}};
X -> {right, X}
after
Timeout -> Msg
Timeout -> {right, Msg}
end
end.

Expand All @@ -77,3 +78,6 @@ exit(Term) -> fun () -> erlang:exit(Term) end.
sendExitSignal(Term, Pid) -> fun () -> erlang:exit(Pid, Term) end.

unlink(Pid) -> fun() -> erlang:unlink(Pid) end.

show_(Pid) ->
list_to_binary(erlang:pid_to_list(Pid)).
6 changes: 6 additions & 0 deletions src/Erl/Process/Raw.purs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ foreign import data Pid :: Type
instance eqPid :: Eq Pid where
eq = eqNative

instance Show Pid where
show = show_

foreign import show_ :: Pid -> String

foreign import eqNative :: forall a. a -> a -> Boolean

foreign import spawn :: (Effect Unit) -> Effect Pid
Expand All @@ -54,6 +59,7 @@ instance pidHasPid :: HasPid Pid where

data ExitReason
= ExitReason Pid ExitMsg

data ExitMsg
= Normal
| Killed
Expand Down
2 changes: 1 addition & 1 deletion test.dhall
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ let conf = ./spago.dhall

in conf
{ sources = conf.sources # [ "test/**/*.purs" ]
, dependencies = conf.dependencies # [ "console", "assert", "erl-test-eunit", "exceptions", "free", "unsafe-coerce"]
, dependencies = conf.dependencies # [ "console", "assert", "erl-test-eunit", "exceptions", "free", "unsafe-coerce"]
}
108 changes: 68 additions & 40 deletions test/Process.purs
Original file line number Diff line number Diff line change
@@ -1,82 +1,110 @@
module Test.Process where

import Prelude

import Control.Monad.Free (Free)
import Data.Either (Either(..), isLeft)
import Data.Time.Duration (Milliseconds(..))
import Effect.Class (liftEffect)
import Effect.Exception (throw)
import Erl.Process (ExitReason, ProcessM, receive, receiveWithTrap, self, spawn, spawnLink, trapExit, (!))
import Erl.Process (ExitReason, ProcessM, receive, receiveWithTimeout, self, spawn, spawnLink, trapExit, (!))
import Erl.Process.Raw as Raw
import Erl.Test.EUnit (TestF, suite, test)
import Foreign as Foreign
import Test.Assert (assertTrue)
import Unsafe.Coerce (unsafeCoerce)

data Foo = Foo Int | Blah String | Whatever Boolean Number
data Foo
= Foo Int
| Blah String
| Whatever Boolean Number
derive instance eqFoo :: Eq Foo

tests :: Free TestF Unit
tests =
tests =
suite "process tests" do
-- Use raw process communication to talk to the test process as it is not a typed Process
test "send stuff to spawned process" do
parent <- Raw.self
-- We can also do this inline or infer the types
let proc :: ProcessM Int Unit
proc = do
a :: Int <- receive
b :: Int <- receive
liftEffect $ parent `Raw.send` (a == 1 && b == 2)
let
proc :: ProcessM Int Unit
proc = do
a :: Int <- receive
b :: Int <- receive
liftEffect $ parent `Raw.send` (a == 1 && b == 2)
p <- spawn proc
p ! 1
p ! 2
Raw.receive >>= assertTrue

test "send stuff to spawned process, another type" do
parent <- Raw.self
p <- spawn do
a <- receive
b <- receive
liftEffect $ parent `Raw.send` (a == Foo 42 && b == Whatever true 1.0)
p <-
spawn do
a <- receive
b <- receive
liftEffect $ parent `Raw.send` (a == Foo 42 && b == Whatever true 1.0)
p ! Foo 42
p ! Whatever true 1.0
Raw.receive >>= assertTrue

test "sending tospawnLinked" do
parent <- Raw.self
p <- spawnLink do
a <- receive
b <- receive
liftEffect $ parent `Raw.send` (a == Foo 42 && b == Whatever true 1.0)
p <-
spawnLink do
a <- receive
b <- receive
liftEffect $ parent `Raw.send` (a == Foo 42 && b == Whatever true 1.0)
p ! Foo 42
p ! Whatever true 1.0
Raw.receive >>= assertTrue

test "receive timeout" do
parent <- Raw.self
_p <-
spawnLink do
a <- receiveWithTimeout (Milliseconds 10.0) "default"
liftEffect $ parent `Raw.send` (a == "default")
Raw.receive >>= assertTrue
test "self eq" do
parent <- Raw.self
p <- spawnLink do
child <- self
liftEffect $ parent `Raw.send` child
p <-
spawnLink do
child <- self
liftEffect $ parent `Raw.send` child
p' <- Raw.receive
assertTrue $ p == p'

test "trapExit" do
testPid <- Raw.self
void $ spawnLink do
parent <- self

trapExit do
_ <- liftEffect $ spawnLink do
liftEffect $ parent ! 1
liftEffect $ Raw.exit (Foreign.unsafeToForeign true)
pure unit

first <- receiveWithTrap
liftEffect $ case (unsafeCoerce first) :: Either ExitReason Int of
Right 1 -> pure unit
_other -> do
throw "failed recv"
second <- receiveWithTrap
liftEffect $ testPid `Raw.send` (isLeft second)
void
$ spawnLink do
parent <- self
trapExit do
_ <-
liftEffect
$ spawnLink do
liftEffect $ parent ! 1
liftEffect $ Raw.exit (Foreign.unsafeToForeign true)
pure unit
first <- receive
liftEffect
$ case (unsafeCoerce first) :: Either ExitReason Int of
Right 1 -> pure unit
_other -> do
throw "failed recv"
second <- receive
liftEffect $ testPid `Raw.send` (isLeft second)
Raw.receive >>= assertTrue
test "receive with trap timeout" do
testPid <- Raw.self
void
$ spawnLink do
trapExit do
_ <-
liftEffect
$ spawnLink do
_ <- receive
pure unit
a <- receiveWithTimeout (Milliseconds 100.0) "default"
liftEffect
$ case a of
Right "default" -> testPid `Raw.send` true
_ -> testPid `Raw.send` false
Raw.receive >>= assertTrue

0 comments on commit 5eacc3a

Please sign in to comment.